cdestructoratexit

100% Coverage Exit Handler That Handles All Ways A Program Can Exit In C


I have a program where I allocated a lot of data and used multiple libraries. I have a cleanup procedure (can be a function, label, setjmp, or just a section of code) that frees allocated memory and unrefs library objects. But it doesn't always run when the program exits unexpectedly.

I want to write a single handler that gets called whenever a program exits, by any means. That is, normal exits, all sort of signals, segfaults, etc.

I tried atexit, __attribute__((destructor)), goto label, and signal handlers. But they still don't quite handle each and every case. For example, atexit doesn't handle signals. And given that there're 31+ signals in Linux, it would be tedious to handle them all (it would be mostly the same handler, yes, but I would have to call signal(), or sigaction() 31+ times). That's not to say that there're signals that are completely uncatchable like SIGKILL, which can't be handled. Should I use __attribute__((cleanup(...))) to cleanup each variable individually? Or is it immature and unportable?

I know I don't need to support all of them prematurely (to avoid the pit of premature optimization). But I want to know what large programs like Firefox, LibreOffice, GTK, KDE, etc. do to achieve this. Or do I just try my best to cover every case possible?


Solution

  • Having such a handler isn't practical.

    First, setting up a signal handler for all catchable signals isn't really an issue. You just loop through the signal numbers and set the same handler for each of them. The issue is what that handler does.

    If you get a catchable signal that would normally terminate a program, you can't do the cleanup in the signal handler since functions like free and dlclose are not async-signal safe. You also can't call exit (to allow atexit handlers to fire) in the signal handler because that is also not async-signal safe.

    You could set a flag in the signal handler which you could then check elsewhere in order to call exit, however you'd have a potentially large number of places you'd have to check that flag. And if the signal was raised due to something like a segfault or floating point exception, you might not be able to get back to the code where the fault occurred. Worse yet, if the segfault happened due to a corrupted heap, there's nothing you can do regarding freeing memory.

    And of course, like you mentioned, it's impossible to catch SIGKILL.

    The best you can do is to set up a signal handler for those signals that aren't caused by a fault such as SIGINT or SIGTERM, have the signal handler set a flag, then check that flag in a few key places and call exit if it's set. Then you would have your atexit handlers take care of the actual cleanup.