| Summary: | pthreads: longjmp from signal handler jumps to the wrong location | ||
|---|---|---|---|
| Product: | Base System | Reporter: | Mike Hibler <mike> |
| Component: | bin | Assignee: | Jason Evans <jasone> |
| Status: | Closed FIXED | ||
| Severity: | Affects Only Me | ||
| Priority: | Normal | ||
| Version: | 4.1.1-RELEASE | ||
| Hardware: | Any | ||
| OS: | Any | ||
Responsible Changed From-To: freebsd-bugs->jasone Over to maintainer. On Thu, Oct 12, 2000 at 07:02:58PM -0700, mike@cs.utah.edu wrote: > >Description: > When using pthreads (gcc -pthread), performing a longjmp out of a > signal handler can sometimes return to the point at which the signal > occured rather than to the location indicated by the jmpbuf. Dan Eischen has some patches that appear to fix this, and he'll probably be committing them to -current within the next few days. It's still not certain whether the libc_r changes are going to be MFCed for FreeBSD 4.2, but there's some chance it will happen. Jason State Changed From-To: open->closed Fixed in both -stable and -current. |
When using pthreads (gcc -pthread), performing a longjmp out of a signal handler can sometimes return to the point at which the signal occured rather than to the location indicated by the jmpbuf. Specifically, when the signal is delivered from pthread_sigmask due to unblocking a pending signal, a longjmp returns to the end of pthread_sigmask instead of the corresponding setjmp location. This appears to have been introduced 1/19/2000 when "continuations" were added to "correctly handle [sig|_]longjmp() inside of a signal handler". The user's setjmp buf is saved in nested_jmp buf the jmpflags are not checked after delivering signals in pthread_sigmask. Fix: Suggested above, but haven't tried. How-To-Repeat: With the following program: #include <stdio.h> #include <pthread.h> #include <assert.h> #include <setjmp.h> #include <signal.h> #if 1 #define JMPBUF sigjmp_buf #define SETJMP(b) sigsetjmp((b), 0) #define LONGJMP(b,v) siglongjmp((b), (v)) #else #define JMPBUF jmp_buf #define SETJMP(b) setjmp((b)) #define LONGJMP(b,v) longjmp((b), (v)) #endif static JMPBUF jb; static pthread_mutex_t lock; static pthread_cond_t cond; static volatile int started, signaled; static sigset_t mask; static void handler(int sig, siginfo_t *si, void *sc) { printf("Got signal, longjmping...\n"); signaled = 1; LONGJMP(jb, 1); assert(0); } static void * cwloop(void *arg) { struct sigaction act; int err; err = pthread_mutex_lock(&lock); assert(err == 0); #ifdef SA_SIGINFO act.sa_flags = SA_SIGINFO; act.sa_sigaction = handler; #else act.sa_handler = handler; #endif sigemptyset(&act.sa_mask); sigaction(SIGUSR1, &act, 0); err = pthread_sigmask(SIG_BLOCK, &mask, 0); assert(err == 0); if (SETJMP(jb) != 0) { assert(signaled == 1); printf("WORKED!\n"); pthread_exit(0); } printf("Child thread running, waiting for signal...\n"); started = 1; err = pthread_cond_signal(&cond); assert(err == 0); err = pthread_mutex_unlock(&lock); assert(err == 0); while (!signaled) { err = pthread_sigmask(SIG_UNBLOCK, &mask, 0); assert(err == 0); err = pthread_sigmask(SIG_BLOCK, &mask, 0); assert(err == 0); } printf("FAILED!\n"); pthread_exit((void *)1); return 0; } main() { pthread_t th; int err; sigemptyset(&mask); sigaddset(&mask, SIGUSR1); err = pthread_mutex_init(&lock, 0); assert(err == 0); err = pthread_cond_init(&cond, 0); assert(err == 0); err = pthread_mutex_lock(&lock); assert(err == 0); err = pthread_create(&th, 0, cwloop, 0); assert(err == 0); while (!started) { err = pthread_cond_wait(&cond, &lock); assert(err == 0); } printf("Started child, sending SIGUSR1...\n"); err = pthread_mutex_unlock(&lock); assert(err == 0); err = pthread_kill(th, SIGUSR1); assert(err == 0); err = pthread_join(th, (void **)&err); assert(err == 0); printf("Child died with exit code %d\n", err); exit(0); }