Opened 9 months ago

Last modified 9 months ago

#18549 new bug

abort() calls debugger when no signal handler for SIGABRT is set, inviting the user to kill the app with SIGKILLTHR rather than terminate 'normally' with SIGABRT

Reported by: nielx Owned by: nobody
Priority: low Milestone: Unscheduled
Component: System/libroot.so Version: R1/beta4
Keywords: debugger, posix Cc:
Blocked By: Blocking:
Platform: All

Description (last modified by nielx)

Background

While testing binutils, one of the tests (test-pexecute) fails because it cannot seem to capture the signal that terminated a spawned child process. The child process was killed by calling abort(), which causes the SIGABRT signal. The child process did not capture that signal. Instead the Debugger window popped up, and when the process was terminated there, the SIGKILLTHR was returned instead.

Reproduction Steps The attached demonstration utility (test-signal.cpp) demonstrates the problem.

It does the following:

  1. When running without arguments, its sets up a new process using posix_spawn().
  2. The main process will wait for the completion of the child process using waitpid(), which captures (amongst other things) the signal that terminates the child.
  3. The child process uses abort() to terminate, which should send the SIGABRT signal.
  4. The debug_server captures the crashing process and brings up the dialog giving the user the options what to do. Select 'Kill' to terminate the process.
  5. The main process evaluates the reason why the child was terminated, and by which signal.

Expected Outcome

The main process can determine that the child process was terminated by SIGABRT.

Actual Outcome

The child process is marked as terminated by SIGKILLTHR

Note

Based on comments by waddlesplash and PulkoMandy below, the our abort() call in libroot calls the debugger() when the SIGABRT is not handled by the team. This then brings up the debug_server popup window that asks a user to decide what to do next (kill, debugger, core dump). A user can decide to open the Debugger and choose to continue execution. In that case, the application works as expected.

Attachments (1)

test-signal.cpp (1.4 KB ) - added by nielx 9 months ago.

Download all attachments as: .zip

Change History (5)

by nielx, 9 months ago

Attachment: test-signal.cpp added

comment:1 by waddlesplash, 9 months ago

No, this is actually a different issue: abort() doesn't raise the signal but calls debugger() if no signal handler is installed: https://github.com/haiku/haiku/blob/b761f9250ab357afa892fc1e1a9f39ca55317b16/src/system/libroot/posix/stdlib/exit.cpp#L286

Otherwise the application would just exit silently, which is not what we want.

I suppose, reading the specification, that we are indeed noncompliant here: https://pubs.opengroup.org/onlinepubs/9699919799/functions/abort.html

The status made available to wait(), waitid(), or waitpid() by abort() shall be that of a process terminated by the SIGABRT signal.

Further there are notes about "override blocking of the SIGABRT" signal, which I don't think we do.

The code to invoke debugger was added by me some years ago, I believe. I think the alternative here would be to add special handling in the kernel for SIGABRT to invoke Debugger automatically, like SIGSEGV do (when there's no handler, anyway), instead of invoking debugger(). Then we would get a crash dialog, and calling processes would see the correct signal.

in reply to:  1 comment:2 by X512, 9 months ago

Replying to waddlesplash:

I think the alternative here would be to add special handling in the kernel for SIGABRT to invoke Debugger automatically, like SIGSEGV do (when there's no handler, anyway), instead of invoking debugger().

Sounds good idea.

comment:3 by pulkomandy, 9 months ago

It looks like when the user terminates the process through the debug_server, the original unhandled signal is overwritten.

To confirm this, you can attach debugger to the process, then quit debugger and select to resume the debugged process. It should finish exiting cleanly.

I guess debug_server could offer an option to do that directly? "Attempt to continue" or something?

I think the alternative here would be to add special handling in the kernel for SIGABRT to invoke Debugger automatically, like SIGSEGV do (when there's no handler, anyway), instead of invoking debugger().

It should invoke the debug_server prompt, not Debugger directly.

I'm not sure it would change much to the core of the problem, however: we are stretching a bit the meaning of "abnormal program termination", and some programs seem to expect abort() to behave somewhat similarly to exit(EXIT_FAILURE), which, in our case, it doesn't.

@nielx, I am interested in your progress on running the binutils test suite, I tried it (for another binutils port) recently and ran into problems with expect: https://github.com/haikuports/haikuports/issues/8910

comment:4 by nielx, 9 months ago

Component: System/POSIXSystem/libroot.so
Description: modified (diff)
Priority: normallow
Summary: waitpid() does not capture signal of a terminated child process other than SIGKILLTHRabort() calls debugger when no signal handler for SIGABRT is set, inviting the user to kill the app with SIGKILLTHR rather than terminate 'normally' with SIGABRT

Thanks PulkoMandy. You were correct in that one could resume the process and get the correct outcome. I guess it was a case of user error (the user is me!). After reviewing the code (thanks waddlesplash), it is clear that the debugger() call only pauses the execution, not terminates it.

Arguably it is more of a UI problem than a bug in our POSIX implementation. Moving priority to low and even contemplating whether or not to change this into an enhancement.

Note: See TracTickets for help on using tickets.