Segfault from Undefined Behavior
2025-03-17, Mon
1. Story
When I tried to run a harmless looking code snippet from Figure 11.5
Thread Cleanup Handler
of book Advanced Programming in Unix
Environment
1, I kept getting this "segmentation fault"
or "bus error" in macOS. This sample code demonstrates the usage of
cleanup handlers for pthreads, and a simplified version is listed
below for referene:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> void cleanup(void *arg) { fprintf(stdout, "cleanup: %s\n", (char *)arg); } void * thr_fn1(void *arg) { fprintf(stdout, "thread 1 start\n"); pthread_cleanup_push(cleanup, "thread 1 first handler"); pthread_cleanup_push(cleanup, "thread 1 second handler"); fprintf(stdout, "thread 1 push complete\n"); if (arg) { return ((void *)1); } pthread_cleanup_pop(0); pthread_cleanup_pop(0); return ((void *)1); } int main(void) { int err; pthread_t tid1; void *tret; err = pthread_create(&tid1, NULL, thr_fn1, (void *)1); if (err != 0) fprintf(stderr, "Can't create thread 1: %s\n", strerror(err)), exit(1); err = pthread_join(tid1, &tret); if (err !=0 ) fprintf(stderr, "can't join with thread 1: %s\n", strerror(err)), exit(1); fprintf(stdout, "thread 1 exit code %ld\n", (long)tret); exit(0); }
Run the code and the reslult looks like this:
thread 1 start thread 1 push complete cleanup: thread 1 second handler Segmentation fault: 11
Since the error was thrown in the line of pthread_join(3)
, I
initially thought there's something wrong with the way I invoke this
library function. What makes things worse, is that the manual of
these pthread
functions in macOS all claim that they conform to the
standard, e.g. The pthread_join() function conforms to ISO/IEC
9945-1:1996 (“POSIX.1”)
. So what exactly is going on?
As it turns out, those claims are indeed true. However, the POSIX.1
standard for pthread_cleanup_push
and pthread_cleanup_pop
includes
the following words: "The effect of the use of return
, break
,
continue
, and goto
to prematurely leave a code block described by
a pair of pthread_cleanup_push()
and pthread_cleanup_pop()
functions calls is undefined." – And this undefined
part depends
on implementation: GNU/Linux and macOS handle it differently.
As expressed in the POSIX.1 standard and explicitly stated in the
book1, the "portable way to return in between these two
functions is to call pthread_exit
". In order to make the code above
work across different systems, the only change needed is to replace
the first return ((void *)1);
with pthread_exit((void *)1);
. With
this one line change applied, code run now results into the expected
output:
thread 1 start thread 1 push complete cleanup: thread 1 second handler cleanup: thread 1 first handler thread 1 exit code 1
End of story.
2. Conclusion
This undefined behavior is a new concept to me, since I rarely saw them in my previous spec readings. I assume it will become more common when one is getting closer to system programming.
Also, I choose to put content of Figure 11.6 from the same book1 here for reference. More entries might get added here as I delve deeper into the topic.
Process Primitive | Thread Primitive | Description |
---|---|---|
fork |
pthread_create |
create a new flow of control |
exit |
pthread_exit |
exit from an existing flow of control |
waitpid |
pthread_join |
get exit status from flow of control |
atexit |
pthread_cleanup_push |
register funtion to be called at exit from flow of control |
getpid |
pthread_self |
get ID for flow of control |
abort |
pthread_cancel |
request abnormal termination of flow of control |
2.1. Pthread synchronization
Pthread synchronization objects are listed here as well for comparison:
Type | Purpose | Description |
---|---|---|
pthread_mutex_t |
Mutually-Exclusive Lock | Lock, Unlocked |
pthread_rwlock_t |
Shared-Exclusive Lock | Read-Lock, Write-Lock, Unlocked |
pthread_spin_t |
Spin Lock | Wait for lock by spinning instead of sleeping |
pthread_cond_t |
Condition | Work with mutex and predicate |
pthread_barrier_t |
Barrier with Count | Barrier lifts only when count is met |
pthread_once_t |
Once Indicator | Ensure one-time execution of a routine |
pthread_key_t |
Local Data Tracker | Track thread specific data items |
Notice the interesting terms used here? i.e. thread and spinning. I always wondered why the word spinlock is employed – until I dropped the "lock" part and think only about "spinning thread". Things start to make more sense now.
Footnotes:
Official site of the book: http://www.apuebook.com/code3e.html
pthread_mutex_init(3)
manual page: https://www.man7.org/linux/man-pages/man3/pthread_mutex_lock.3.html
Search result of manpages-posix in Debian: https://packages.debian.org/search?keywords=manpages-posix