Jump to page: 1 2
Thread overview
Throw stack trace from program kill
Jan 16, 2022
Hipreme
Jan 16, 2022
Ali Çehreli
Jan 16, 2022
Paul Backus
Jan 16, 2022
Paul Backus
Jan 17, 2022
H. S. Teoh
Jan 16, 2022
Adam D Ruppe
Jan 17, 2022
Era Scarecrow
Jan 19, 2022
Hipreme
January 16, 2022

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

January 16, 2022
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

January 16, 2022

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:

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.

January 16, 2022

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:

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

January 16, 2022
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.
January 16, 2022

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

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);
	}
}
January 16, 2022

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

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

January 16, 2022

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

January 16, 2022
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
January 17, 2022

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).

« First   ‹ Prev
1 2