views:

1618

answers:

9

I have a program that segfaults from pointer arithmetic sometimes. I know this happens, but I can't easily check ahead of time to see whether it segfaults or not - either I can "pre-scan" input data to see if it will cause a segfault (which can be impossible to determine), or I can refit it to not use pointer arithmetic, which would require a significantly larger amount of work, or I can try to catch a segfault. So my question:

1) How, in C, can I catch a segfault? I know something in the OS causes a segfault, but what can a C program do in the event that it segfaults to die a bit more gracefully than just Segmentation fault?

2) How portable is this?

I imagine this is a highly unportable behavior, so if you post any code to catch a segfault, please tell me what it works on. I'm on Mac OS X but I'd like my program to work on as many platforms as it can and I want to see what my options are.

And don't worry - basically all I want to do is print a more user-friendly error message and free some malloc()ed memory, and then die. I'm not planning on just ignoring all segfaults I get and plowing ahead.

+6  A: 

You have to define a signal handler. This is done on Unix systems using the function sigaction. I've done this with the same code on Fedora 64- and 32-bit, and on Sun Solaris.

Scottie T
+2  A: 

When your app segfaults, the memory you alloc'd is freed and your process killed already. Leaving it as it is would at least provide the user if the app exited intentionally or not.

ssg
That's convenient. I didn't know segfaults freed my memory for me.
Chris Lutz
any time a process terminates, the system frees all the memory it had allocated it. I believe this is true across all operating systems, but it's certainly true for the big 3.
rmeador
Not only the memory you allocated but also handles you created are closed too.
ssg
This only happens if there is no signal handler installed for SIGSEGV
bdonlan
'This' being 'the process exits'. Memory is always freed on process exit on systems with good process separation (e.g. Windows, Unix).
ijw
+19  A: 

Well, SIGSEGV is trappable, and this is POSIX, so it is portable in that sense.

Bu I'm concerned that you seem to want to handle the segfault rather than fix the problem that causes the segfault. If I had to pick whether it was the OS at fault, or my own code, I know which I would choose. I suggest you hunt down that bug, fix it, then write a test case to make sure it never bites you again.

Paul Beckingham
Without delving into gritty details, the default mode is safe and protects the user from ever running out of memory (it uses a linked list). They have to choose to use the "less safe" version explicitly, and should be aware of the consequences of doing so.
Chris Lutz
The "less safe" version is provided for compatibility with other programs that DON'T provide an infinite linked list like mine does. Plus, if nothing else, I'll learn something new.
Chris Lutz
Understood - you have good reasons. I had to point it out though...
Paul Beckingham
I think he means that 'something' traps the violation in the OS, not that the OS is the cause of the fault.
ijw
+1  A: 

I think you are trying to solve a problem that doesn't exist. At least you are working on the wrong end. You won't be able to catch a segmentation fault, as this error/exception is thrown by the OS (it is caused by your program, the OS just catches it).

I'd advise you to rethink your strategy regarding the input: Why is it impossible to sanitize it? The most important to do is size checking, for this the C stdlib has appropriate functions. Then of course you'd have to check for valid input regarding the content. Yes, this will probably result in a lot of work, but it's the only way to write a robust program.

EDIT: I'm not much of a C expert, didn't know that even a segmentation fault could be handled by a signal handler. Still, I think it's not the right way to go for the reasons mentioned above.

paprika
Size checking isn't the problem - the user enters a program (in brainf*ck) and my program runs it. Checking for valid input runs up against the halting problem. Plus, some programs will only segfault sometimes (i.e. with more data input), and whether or not they will depends on user input.
Chris Lutz
@Chris Lutz:Ahhh... in this context your question looks much more plausible. I can see now why you want -- in fact, why it's your only feasible option -- to catch SIGSEGV.
paprika
It's not the only "feasable" option. I could rewrite it to use an array and a variable that points to an element of that array, and then have an easy check on whether or not the pointer is out of bounds (i.e. < 0 or > MAX), but this would look even more hideous (from where I'm standing).
Chris Lutz
+4  A: 

You can use the function signal to install a new signal handler for the signal:

   #include <signal.h>
   void (*signal(int signum, void (*sighandler)(int)))(int);

Something like the following code:

signal(SIGINT , clean_exit_on_sig);
signal(SIGABRT , clean_exit_on_sig);
signal(SIGILL , clean_exit_on_sig);
signal(SIGFPE , clean_exit_on_sig);
signal(SIGSEGV, clean_exit_on_sig); // <-- this one is for segmentation fault
signal(SIGTERM , clean_exit_on_sig);

void 
clean_exit_on_sig(int sig_num)
{
        printf ("\n Signal %d received",sig_num);
}
Igor Oks
...noting that by the time you've got to a SEGV - where a pointer read/write just *happens* to have hit inaccessible memory - it's quite likely it's already managed to stomp over allocated, accessible memory, containing your data and the free block list. So don't expect alloc to work, and don't expect any data in memory to be sane.
ijw
+1  A: 

You'll need to provide a SIGSEGV handler, this one looks quite decent.

arul
+1  A: 

There's an example of how to catch SIGSEGV and print a stack trace using glibc's backtrace() here:

how to generate a stacktrace when my C++ app crashes

You can use this to catch your segfault and clean up, but be warned: you should not be doing too much stuff in a signal handler, especially things that involve making calls like malloc(). There are a lot of calls that aren't signal safe, and you can end up shooting yourself in the foot if you make, say, a call to malloc from within malloc.

tgamblin
+1  A: 

signal handling is (relatively) portable across unix machines (this includes mac and linux). The big differences are in the exception detail, which is passed as argument to the signal handling routine. Sorrty, but you will probably need a bunch of #ifdefs for that, if you want to print more reasonable error messages (such as where and due to which address the fault happened) ...

ok, here is a code fragment for you to start with:

#include <signal.h>

/* reached when a segv occurrs */
void
SEGVFunction( SIGARGS )
{
     ...
}

...
main(...) {
    signal(SIGSEGV, SEGVFunction); /* tell the OS, where to go in case... */
    ...
    ... do your work ...
}

Your task is to:

  • check what SIGARGS is (OS dependent, so use an ifdef)
  • see how to extract fault-address and pc from the exception information in sigArgs
  • print reasonable message
  • exit

in theory, you could even patch the pc in the signal handler (to after the faulting instruction), and proceed. However, typical signal handlers either exit() or to a longjmp() back into a save place in the main.

regards

blabla999
+4  A: 

The safe actions in a signal handler are very limited. It's unsafe to call any library function not known to be re-entrant, which will exclude, for example, free() and printf(). Best practice is to set a variable and return, but this doesn't help you very much. It's also safe to use system calls such as write().

Note that in the two backtrace examples given here, the backtrace_symbols_fd() function will be safe because it uses the raw fd directly, but the call to fprintf() is incorrect, and should be replaced by a use of write().

Dale Hagglund
Wow, that's rough. I'll keep these Draconian rules in mind. Maybe I really SHOULDN'T be trying to catch the segfault...
Chris Lutz
I certainly agree with the other comments suggesting that the right thing to do is fix the bug causing the segv.
Dale Hagglund