www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Throw stack trace from program kill

reply Hipreme <msnmancini hotmail.com> writes:
Is there some way to throw a stack trace when killing the program 
from the CTRL+C from the terminal?

It would help a lot into debugging occasional infinity loops
Jan 16 2022
next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/16/22 07:15, Hipreme wrote:
 Is there some way to throw a stack trace when killing the program from
 the CTRL+C from the terminal?
I am interested in how to add Ctrl+C support for a D program as well.
 It would help a lot into debugging occasional infinity loops
One way of achieving that is starting the program in a debugger. For example, with gdb: $ gdb --args ./my_program its_command_line_arguments [ ... gdb prints some information ... ] (gdb) run [ ... gdb starts the program ... ] Press Ctrl+C here to see the backtrace. Aside, this method is the easiest way of profiling a program to determine where it's spending most of its time. Ali
Jan 16 2022
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 16 January 2022 at 15:15:07 UTC, Hipreme wrote:
 Is there some way to throw a stack trace when killing the 
 program from the CTRL+C from the terminal?

 It would help a lot into debugging occasional infinity loops
On POSIX, you can use the `sigaction` function to install a signal handler for `SIGINT`, the signal generated by CTRL+C. To terminate the program with a stack trace, simply have the signal handler `throw` an `Error`. Here's an example program that demonstrates the technique: ```d import core.sys.posix.signal; extern(C) void handleCtrlC(int) { throw new Error("Killed by CTRL+C"); } void main() { sigaction_t act = { sa_handler: &handleCtrlC }; int errcode = sigaction(SIGINT, &act, null); f(); // call some functions } void f() { g(); } void g() { h(); } void h() { while (1) {} // wait for ctrl+c } ``` Make sure to compile with the `-g` option if you want your stack trace to have filenames and line numbers.
Jan 16 2022
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/16/22 1:03 PM, Paul Backus wrote:
 On Sunday, 16 January 2022 at 15:15:07 UTC, Hipreme wrote:
 Is there some way to throw a stack trace when killing the program from 
 the CTRL+C from the terminal?

 It would help a lot into debugging occasional infinity loops
On POSIX, you can use the `sigaction` function to install a signal handler for `SIGINT`, the signal generated by CTRL+C. To terminate the program with a stack trace, simply have the signal handler `throw` an `Error`. Here's an example program that demonstrates the technique: ```d import core.sys.posix.signal; extern(C) void handleCtrlC(int) {     throw new Error("Killed by CTRL+C"); } void main() {     sigaction_t act = { sa_handler: &handleCtrlC };     int errcode = sigaction(SIGINT, &act, null);     f(); // call some functions } void f() { g(); } void g() { h(); } void h() {     while (1) {} // wait for ctrl+c } ``` Make sure to compile with the `-g` option if you want your stack trace to have filenames and line numbers.
Does this work normally? The memory error handler for Linux jumps through a lot of hoops to be able to throw an error from a signal handler. See https://github.com/dlang/druntime/blob/master/src/etc/linux/memoryerror.d -Steve
Jan 16 2022
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 16 January 2022 at 18:22:04 UTC, Steven Schveighoffer 
wrote:
 Does this work normally? The memory error handler for Linux 
 jumps through a lot of hoops to be able to throw an error from 
 a signal handler. See 
 https://github.com/dlang/druntime/blob/master/src/etc/linux/memoryerror.d
It worked when I tested it, but I'm not sure how reliable it is. A more conservative implementation would be something like ```d extern(C) void handleCtrlC(int) { import core.stdc.stdlib: exit; import std.stdio: writeln; try throw new Exception("Killed by CTRL+C"); catch (Exception e) { writeln(e.message); writeln(e.info); exit(1); } } ```
Jan 16 2022
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/16/22 1:33 PM, Paul Backus wrote:

 It worked when I tested it,
Yeah, but your example is designed specifically to only encounter the signal in one specific spot (inside a D function).
 but I'm not sure how reliable it is. A more 
 conservative implementation would be something like
 
 ```d
 extern(C) void handleCtrlC(int)
 {
      import core.stdc.stdlib: exit;
      import std.stdio: writeln;
 
      try throw new Exception("Killed by CTRL+C");
      catch (Exception e)
      {
          writeln(e.message);
          writeln(e.info);
          exit(1);
      }
 }
 ```
This too is not going to be a good idea. writeln(e.info) is going to possibly start allocating. A signal can come at any time, even when locks are held or things are in an intermediate state. I use Adam's approach normally -- set a flag and act on it later. -Steve
Jan 16 2022
next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/16/22 1:42 PM, Steven Schveighoffer wrote:

 This too is not going to be a good idea. writeln(e.info) is going to 
 possibly start allocating. A signal can come at any time, even when 
 locks are held or things are in an intermediate state.
That being said, if this is being used for debugging, there's no harm in trying it, and seeing what happens. I just wouldn't count on it normally in production to do the right thing. -Steve
Jan 16 2022
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Jan 16, 2022 at 01:42:21PM -0500, Steven Schveighoffer via
Digitalmars-d-learn wrote:
 On 1/16/22 1:33 PM, Paul Backus wrote:
[...]
 ```d
 extern(C) void handleCtrlC(int)
 {
      import core.stdc.stdlib: exit;
      import std.stdio: writeln;
 
      try throw new Exception("Killed by CTRL+C");
      catch (Exception e)
      {
          writeln(e.message);
          writeln(e.info);
          exit(1);
      }
 }
 ```
This too is not going to be a good idea. writeln(e.info) is going to possibly start allocating. A signal can come at any time, even when locks are held or things are in an intermediate state.
Yeah, this is generally a bad idea. Code registered as a signal handler will get called in what POSIX calls "signal handler context", where you're not allowed to call a lot of C library functions because the signal handler could be invoked while inside a non-reentrant library function or syscall. While this may sometimes work, it may crash horribly when the signal happens to arrive at the wrong time, or deadlock. Many POSIX functions are marked as unsafe to call from signal handler context, and this in particular includes anything that may allocate memory (bad things will happen if you try to call, e.g., malloc from a signal handler while the code happens to be inside another malloc call -- it will likely deadlock on the malloc internal mutex). Generally, a signal handler should do the absolute minimum to inform the main program that the signal was received, and then quickly return back to the interrupted code and let the main program react to the signal later at a more convenient time. The usual idiom is to write a single byte (or a small number of bytes) to a pipe that's read by the program's main loop. The write syscall is one of the few syscalls that are signal-handler safe, and is a convenient way to flag the receipt of the signal without needing to worry about thread synchronization issues (sin ce the OS takes care of that inside the write() syscall).
 I use Adam's approach normally -- set a flag and act on it later.
[...] Yes, this is generally the recommended way of dealing with a signal. Though I'd add, as an interesting footnote, that sometimes you *can* do trickier things inside a signal handler. One example is something we came up with once, a hack to convert a SEGV into a D exception. The way it works is by the signal handler take advantage of a piece of information that the SEGV signal provides to it, which is the EIP value of the code that caused the segfault, and the location in the code that the signal handler would return to when it exits. The signal handler uses what amounts to a stack overflow exploit by overwriting this return address to point instead to a function that allocates and throws a SegfaultException. Once the signal handler returns, any syscalls the code may have been in will finish running, then return. Now we're officially outside signal handler context, so it's safe to now allocate and throw the exception. Of course, the special function also needs to reconstruct a proper stack frame so that the stack unwinding of the thrown exception won't go haywire. The gory details are here: https://forum.dlang.org/thread/jjn6dj$193c$1 digitalmars.com Obviously, this is extremely tricky and system-dependent stuff that has to be done with extreme care. NOT recommended if you don't know exactly what you're doing! T -- By understanding a machine-oriented language, the programmer will tend to use a much more efficient method; it is much closer to reality. -- D. Knuth
Jan 16 2022
prev sibling next sibling parent Adam D Ruppe <destructionator gmail.com> writes:
On Sunday, 16 January 2022 at 18:03:53 UTC, Paul Backus wrote:
 extern(C) void handleCtrlC(int)
 {
 	throw new Error("Killed by CTRL+C");
 }
This is really iffy since signals can come to random threads at random times. The best thing to do is typically to just set a "user requested quit" flag from the signal handler and then make sure you check that periodically in other parts of your program. Then those other parts can exit gracefully or throw sanely or whatever.
Jan 16 2022
prev sibling next sibling parent Era Scarecrow <rtcvb32 yahoo.com> writes:
On Sunday, 16 January 2022 at 18:03:53 UTC, Paul Backus wrote:
 On POSIX, you can use the `sigaction` function to install a 
 signal handler for `SIGINT`, the signal generated by CTRL+C. To 
 terminate the program with a stack trace, simply have the 
 signal handler `throw` an `Error`.
I never quite got deep enough to start using these. Though i can tell a lot of programs take advantage of this in different ways. Example, optipng or jpegoptim will likely have a catch and if it's killed it would do cleanup then quit. **So**, normally said image optimizers create a new file as **somefile.jpg.tmp12345**, and if it is uninterrupted **somefile.jpg** is deleted and **somefile.jpg.tmp12345** is renamed to the original file; On the other hand interrupted execution would close the temp file and then delete it before returning control, leaving the original file untouched. As for how to handle things outside of cleanup, I'm not quite so sure. I don't see why you couldn't do a stacktrace or core dump a file with the current state you could then look at (*and maybe attach a debugger*).
Jan 16 2022
prev sibling parent Hipreme <msnmancini hotmail.com> writes:
On Sunday, 16 January 2022 at 18:03:53 UTC, Paul Backus wrote:
 On Sunday, 16 January 2022 at 15:15:07 UTC, Hipreme wrote:
 Is there some way to throw a stack trace when killing the 
 program from the CTRL+C from the terminal?

 It would help a lot into debugging occasional infinity loops
On POSIX, you can use the `sigaction` function to install a signal handler for `SIGINT`, the signal generated by CTRL+C. To terminate the program with a stack trace, simply have the signal handler `throw` an `Error`. Here's an example program that demonstrates the technique: ```d import core.sys.posix.signal; extern(C) void handleCtrlC(int) { throw new Error("Killed by CTRL+C"); } void main() { sigaction_t act = { sa_handler: &handleCtrlC }; int errcode = sigaction(SIGINT, &act, null); f(); // call some functions } void f() { g(); } void g() { h(); } void h() { while (1) {} // wait for ctrl+c } ``` Make sure to compile with the `-g` option if you want your stack trace to have filenames and line numbers.
```d import std.stdio; version(Posix) { import core.sys.posix.signal; extern(C) void handleCtrlC(int) { throw new Error("Killed by CTRL+C"); } bool setupSignal() { sigaction_t act = {sa_handler: &handleCtrlC}; int errCode = sigaction(SIGINT, &act, null); return errCode == 0; } } else version(Windows) { import core.sys.windows.windef; import core.sys.windows.wincon; extern(Windows) BOOL handleCtrlC(DWORD dwType) { switch(dwType) { case CTRL_C_EVENT: throw new Error("Killed by CTRL+C"); break; case CTRL_BREAK_EVENT: throw new Error("Killed by break"); break; default: throw new Error("Killed by unknown event"); break; } return TRUE; } bool setupSignal() { return SetConsoleCtrlHandler(cast(PHANDLER_ROUTINE)&handleCtrlC, TRUE) == TRUE; } } else{bool setupSignal(){return false;}} void a(){b();} void b(){c();} void c(){for(;;){}} void main(string[] args) { if(!setupSignal) writeln("Could not setup signal, exiting."); else a(); } ``` This is my current solution. The problem is that on Windows, it actually has the stack trace: ``` 0x76909A63 in CtrlRoutine 0x76BD6359 in BaseThreadInitThunk 0x76FF7B74 in RtlGetAppContainerNamedObjectPath 0x76FF7B44 in RtlGetAppContainerNamedObjectPath ``` If I throw that error on the Ctrl, it actually shows a message box: `The exception unknown software exception (0xe04400001) occurred in the application at location: ...` Maybe there is some specific formatting for throwing exceptions to Windows recognize it?
Jan 19 2022