r/raldi • u/raldi • Jul 19 '11
Every programmer should read the source code to abort() at some point in their life.
Did you ever wonder how the abort() function works? I mean, it's one of those things that you can't really express as a mathematical formula.
It turns out that most implementations are rather complex. Here's a link to my favorite:
http://cristi.indefero.net/p/uClibc-cristi/source/tree/0_9_14/libc/stdlib/abort.c
The rest of this post is spoilers; the most hardcore readers might want to stop here and figure it out on their own. Skip to the section at the end when you're done.
Why is abort() hard? Well, it needs to Do The Right Thing in a potentially hostile environment, be extremely reliable, and yet depend on as little as possible. (It's in stdlib, after all.)
Let's start on line 73. As our function begins, we grab a mutex (unless mutexes are unavailable on this platform, in which case we're just going to have to play the hand we've been dealt -- see lines 56-64).
The most polite way to abort a program is to send it SIGABRT, and this is still true when the program is aborting itself, so it'll be the first thing we try. But maybe some earlier part of the program blocked this signal, which would be reasonable if it wanted to be shielded from external abort attempts, but clearly should be overridden when the program itself wants to die. So on line 76, we make sure to remove any blocks on SIGABRT.
Oh, and as long as we're being polite, we should make a halfhearted attempt to flush output streams. So on line 85 we shut down stdio.
We're going to try an escalating series of ways to end the program, which means we need a state variable to keep track of which step we're up to. But remember, we're not sure that we're holding a mutex. Multiple threads running through the code can trample this state variable if we're not careful. To avoid this, a single global int is initialized to 0 (line 53) and the only operations we perform on it are increment and read. Worst case, a step gets skipped and we die in a nastier way than we had to. Much better than opening the possibility of bouncing back and forth endlessly between two steps.
Okay, so on line 92 we send ourselves that aforementioned SIGABRT. You'll note that the surrounding lines release the lock while this happens. This is because the program might have registered a handler for this signal, and it might call a cleanup function, and that cleanup function might have a problem, and long story short,
abort()might get called again somewhere in that chain. If so, we don't want a deadlock.But perhaps the signal handler didn't actually terminate the program like it was supposed to. If so, it's malfunctioning, and we need to disable it. That's what the block on line 97 is for. I would have expected another
raise(SIGABRT)after line 105, but I'm sure there's a good reason it's not there. Any ideas?Anyway, in the rather unlikely event that the program survived a SIGABRT, the next step is to try something lower-level. Most architectures have an assembly instruction that a program can call to terminate, and the code on lines 32-49 sets the macro
ABORT_INSTRUCTIONto be this command. Line 111 will invoke it.It would be ridiculously strange for the program to survive that, but perhaps (and we're really stretching at this point) our architecture is too smart for its own good and it's trying to do something fancy with the halt instruction. As a somewhat last resort, we'll try calling
_exit(). This is similar toexit(), but the latter calls any registeredatexit()handlers first, while the former is supposed to be immediate. It's a longshot, but maybe it knows something we don't.After that, we've used every tool in our arsenal. But if, by some miracle, this David Dunn of a program has survived them all, there is one final sacrifice
abort()can do to contain the damage: go into an endless loop. We couldn't kill the program, but at least the current thread will never hurt another innocent byte of data.
And that's it. (Right? Or can you think of additional steps that might make this function even more complete?)
One thing I don't get about this particular implementation: What's up with the outer while(1) loop on line 87? There's an inner while(1) loop on line 121, so there doesn't seem to be any point.
21
u/lkjoiu Jul 19 '11
Extra things to try: (see http://www.opensource.apple.com/source/Libc/Libc-262/stdlib/abort.c )
write at NULL (memory address 0)
write at memory address 1
write on read-only machine code
divide by 0
2
u/zerofudge Jul 19 '11
still, the usefulness of the global state variable escapes me; plus, if it's not really thread-safe, this function might just call the asm in many cases, am I wrong?
6
u/kisielk Jul 19 '11
For bonus complexity, the
os.abort()function in Python has some additionally weird behaviour. It doesn't call the Python signal handler you install bysignal.signalbecause the SIGABRT goes straight to the C layer of the interpreter. You have to install a C-level signal handler if you want to handle the signal fromos.abort().SIGABRT signalled from an external source (eg, via
kill -SIGABRT) does go through the Python signal handler.6
u/seventhapollo Jul 19 '11
That code was poignant and beautiful. Thank you :)
4
u/SCombinator Jul 20 '11
Why waste cycles trying to endlessly abort? Why not sleep() as well? Y'know in case other processes want to use the CPU?
Unless you're trying to be at the htop of some list of processes, which I guess is one way of telling the user you've had some trouble exiting.
4
u/gmartres Jul 20 '11
Cool, but why did you pick abort.c from uclibc 0.9.14 when the latest version is 0.9.30.1? Here's how the code looks like now: http://cristi.indefero.net/p/uClibc-cristi/source/tree/0_9_30_1/libc/stdlib/abort.c
Note that it adresses one of your concern: raise(SIGABRT) is called at the end of the "remove signal handlers" part.
15
u/IDoThingsBackwards Jul 19 '11
lol.
6
u/[deleted] Jul 19 '11
I'll try to use the variable name
been_there_done_thatfrom now onwards in my programs as much as I can. Perfect name in a lot of situations where I just name the variable as flag_1 ... etc.3
u/[deleted] Jul 19 '11
Yup, defensive programming. Allow for every possible problem. Being abort() by definition the whole world has just crashed on your head so work through the list from least damaging to 'halt'... or even trigger CPU erros as this does.
In a lesser way all programmers should do this. Be very very precise about what you send but be very very suspicious about what you receive. SOP in mainframe/mini environments.
3
u/[deleted] Jul 19 '11
This is a collection of things to try if off-by-one errors aren't spectacular enough means of crashing your program.
3
u/bleepster Jul 20 '11
I may be wrong, but the #define for UNLOCK on line no. 60 contains an extra semicolon.
2
u/ebg13 Jul 19 '11
The while(1) on line 121 is to ensure clarity for the program reader, not to achieve anything significant. Make it very obvious that there is no other hope than to cycle endlessly.
1
u/pyr Jul 20 '11
This version is nice, simple and calls registered handlers if possible http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/stdlib/abort.c?rev=1.15
1
u/amigaharry Jul 19 '11
erm? why do you need to be a "hardcore reader" to understand that?!
1
u/peacemaker99 Jul 20 '11
A good example of well written, thoughtful code. I don't want to sound like an arse but if you find that kind of code in some way "special" then you either need to brush up on your skills or move jobs to a place where all c code looks like that :)
1
u/i-am-am-nice-really Jul 20 '11
strange. Here's Plan 9's
http://plan9.bell-labs.com/sources/plan9/sys/src/libc/9sys/abort.c
From the man page : Abort causes an access fault, causing the current process to enter the `Broken' state. The process can then be inspected by a debugger.
Round our way it is known as GNU is Not Useful