

Every programmer should read the source to abort() at some point in their life. - raldi
http://www.reddit.com/r/raldi/comments/iu0f7/every_programmer_should_read_the_source_code_t/

======
msarnoff
Reminded me of "5 ways to reboot a PC, none of them reliable" from a couple
months ago.

<http://news.ycombinator.com/item?id=2607116>

Interesting how fundamentally simple tasks like aborting a process or
rebooting the machine have very nontrivial (even kludgey) implementations.

------
cpeterso
For comparison, here's FreeBSD's abort():

[http://svnweb.freebsd.org/base/head/lib/libc/stdlib/abort.c?...](http://svnweb.freebsd.org/base/head/lib/libc/stdlib/abort.c?view=markup)

And GNU glibc's abort():

[http://sourceware.org/git/?p=glibc.git;a=blob_plain;f=stdlib...](http://sourceware.org/git/?p=glibc.git;a=blob_plain;f=stdlib/abort.c)

~~~
masklinn
And OSX's, which is rather similar to FreeBSD's but adds:

* Writing to NULL

* Writing to address 1 (unaligned write)

* Writing to text space (read-only machine code)

* Dividing by 0

* More violence than SIGABRT (SIGILL, SIGBUS)

[http://www.opensource.apple.com/source/Libc/Libc-262/stdlib/...](http://www.opensource.apple.com/source/Libc/Libc-262/stdlib/abort.c)

~~~
Jabbles
Since dereferencing a null pointer and dividing by 0 are undefined by C, is
the compiler required to emit the code for them? In practice, does it?

~~~
praptak
In practice, <asterisk>(int <asterisk>)0 (how do I escape asterisks on HN?)
and similar are popular idioms for "segfault here". Making them break is not
an optimization any compiler maintainer would bother to make - it requires a
special case and there don't seem to be any benefits to justify the effort.

~~~
jesboat
See the series of articles starting with [http://blog.llvm.org/2011/05/what-
every-c-programmer-should-...](http://blog.llvm.org/2011/05/what-every-c-
programmer-should-know.html)

------
kenjackson
Raymond Chen wrote a classic blog post on how process exits on WinXP. But if
you're a developer, know that it's not the same for Win7.

[http://blogs.msdn.com/b/oldnewthing/archive/2007/05/03/23833...](http://blogs.msdn.com/b/oldnewthing/archive/2007/05/03/2383346.aspx)

~~~
yuhong
Also see this:
[http://blogs.msdn.com/b/oldnewthing/archive/2007/05/04/24020...](http://blogs.msdn.com/b/oldnewthing/archive/2007/05/04/2402028.aspx)

------
p9idf
Plan 9's abort causes an access fault, causing the current process to enter
the `Broken' state. The process can then be inspected by a debugger. Pretty
elegant.

[http://plan9.bell-
labs.com/sources/plan9/sys/src/libc/9sys/a...](http://plan9.bell-
labs.com/sources/plan9/sys/src/libc/9sys/abort.c)

~~~
ristretto
again, what's with the while()? even if it doesnt cause a segmentation fault,
there's a chance it will evaluate to 0.

~~~
singular
gcc, at least, optimises out the null deref for both:-

    
    
        *(int *)0;
    

and:-

    
    
        for(;;)
            *(int *)0;
    

So the first bit of code does nothing, and the second slips off into an
infinite loop.

I suspect that treating expressions that demonstrably lack side effects (other
than the intended segfault here of course) as statements is undefined, and
hence these are getting optimised out (even with -O0).

Clearly with:-

    
    
        while(*(int *)0)
    

The expression is being evaluated and is therefore not elided, I guess the
choice of while is to 'be cute' as others have suggested, and I guess the
world is sane in plan 9 and 0 is readable so you can't get a situation where
it escapes the loop. Perhaps there is a deeper reason here that I am missing,
however.

~~~
raldi
(int *)0 is not defined as a pointer to memory address 0x0 on architectures
that support such an address.

0 cast as a pointer is defined by the spec to always be the NULL pointer,
which on such architectures would have a value other than 0x0 and not point
anywhere addressable.

------
thecoffman
Great article, picking apart low level code like this can be super informative
- and you've explained it well.

------
chrisledet
Link to abort.c source: <https://gist.github.com/1093410>

------
IgorPartola
I may be wrong, but I thought that in a multithreaded environment, doing i++
is not atomic and could result in garbled data. Instead you should use
__sync_add_and_fetch. However, I have no idea if it should be used inside
abort().

~~~
cube13
>I may be wrong, but I thought that in a multithreaded environment, doing i++
is not atomic and could result in garbled data. Instead you should use
__sync_add_and_fetch. However, I have no idea if it should be used inside
abort().

This is only true if the variable in question's memory is accessed by multiple
threads at the same time, and there isn't any locking or synchronization
method used to protect the memory.

In this case, even though it is a globally scoped variable, it's locked by the
globally scoped mutex declared in the file. All increments are done in the
locked sections, so there isn't any possibility of accessing the variable
without having a lock.

It should be noted that there is a very minor race condition when abort() is
called in two different threads sequentially, and every attempt up to line 89
doesn't work. The first call will get the lock, then go through to line 89,
where it released the lock. The second thread will then get the lock, and go
through the first section. When it hits the section line 89(if
(been_there_done_that == 0)), that will resolve to false, because
been_there_done_that is 1. It will then go on, leaving the first thread
deadlocked at the LOCK attempt on line 91. This _shouldn't_ result in any
missed functionality, but I actually wonder why they're releasing the lock in
the first place. Raise() isn't thread safe anyway, because the signal is
applied to all threads in the process. Plus, you're trying to suicide the
program. It's a bad idea to even have the possibility of multiple threads
trying to kill themselves at the same time.

~~~
raldi
The lock must be released because the same thread might reenter abort() in the
signal handler, and without a release in the parent abort(), the program would
hang.

------
snorkel
Wow, abort() is much more polite than I assumed.

------
known
Why only abort()? Entire Kernal code is beautiful <http://lxr.linux.no/>

------
KonradKlause
glibc's abort() is even more complex:

[http://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/abort...](http://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/abort.c;h=e9d0ab18fd7d05e3d7016d03794438ccd1e141d6;hb=HEAD)

~~~
jordanb
Most of the code in glibc has been heavily Dreppered.

~~~
ars
What does Dreppered mean?

~~~
jordanb
Ulrich Drepper tends to write software that is easy for him to read (one would
hope) -- but is _very_ difficult for anyone else to follow.

~~~
jrockway
But you have to admit, his abort is pretty damn easy to read.

~~~
jordanb
I admit it's heavily commented, and his usual tangle of preprocessor macros
are blissfully absent.

But it contains hints of Drepperification, like the superfluous use of
preincrement.

~~~
kelnos
When I don't care about the result, I always write preinc/decrement too. Sure,
it's superfluous on any non-braindead compiler (it should be able to see that
you don't care about the result of a postincrement and elide the temporary),
but it's just habit at this point. I fail to see how it reduces or changes
readability though.

Sounds like you just have an axe to grind with Drepper.

~~~
jordanb
I don't have anything personal against Drepper. I've never had any direct
experience with him of any kind.

I thoroughly enjoyed his article about memory. He is obviously an extremely
intelligent and knowledgeable guy.

I am afraid that he is too clever by half though, insofar as good code is
clean and readable first, and clever second. Every time I've had an
opportunity to interact with the glibc codebase I'm dismayed that such an
important, core piece of software has been written so cleverly that it
essentially can only be maintained by one guy.

~~~
kelnos
That's unfortunate; I'll have to look at some glibc code sometime. Could be
fun... or, as you point out, dismaying :-/

------
lubutu
musl's abort() is quite elegant: [http://git.etalabs.net/cgi-
bin/gitweb.cgi?p=musl;a=blob;f=sr...](http://git.etalabs.net/cgi-
bin/gitweb.cgi?p=musl;a=blob;f=src/exit/abort.c)

~~~
kelnos
And also not very robust. Sure, it'll halt execution -- of that one thread,
anyway -- but if the program has a SIGABRT handler installed that doesn't
exit, abort() will fail to do its job. It'd be nice to try just a _little_
harder to kill the program.

------
sagarun
I liked the way they commented there.Reading the code with comments was like
reading an comic book "Still here? We are screwed!" :-)

------
ristretto
Why is the outer while(1) loop needed? Seems redundant to me.

~~~
raldi
The link specifically asks that question -- there are some good guesses in the
comments.

~~~
ristretto
the one in line 87. It looks like it might be added later because it's not
even indented, but clearly it doesn't serve any purpose and it doesn't make it
more readable (you can tell the function never returns from the second
while(1) loop).

~~~
re
> it's not even indented

It's an issue of mixed tab-space indentation, with tabs being displayed at 4
spaces instead of the 8 spaces they were intended to be.

------
cheez
Every programmer should implement an operating system and compiler. I did. Now
get off my lawn.

~~~
KonradKlause
ACK

