What started as a simple signal queueing patch turned into an omnibus signal rework. Lots of details changed, but the big picture is the same. tests/blockfault.stdout.exp | 0 coregrind/core.h | 48 ++++-- coregrind/linux/core_os.c | 14 - coregrind/vg_main.c | 13 + coregrind/vg_mylibc.c | 6 coregrind/vg_scheduler.c | 123 +++++++-------- coregrind/vg_signals.c | 301 ++++++++++++++++++++++++++++++++------- coregrind/vg_syscalls.c | 14 + coregrind/x86-linux/ldt.c | 9 - coregrind/x86-linux/syscalls.c | 7 coregrind/x86/signal.c | 71 ++++++++- include/tool.h.base | 6 none/tests/Makefile.am | 4 none/tests/blockfault.c | 31 ++++ none/tests/blockfault.stderr.exp | 6 none/tests/blockfault.vgtest | 1 none/tests/x86/badseg.c | 2 17 files changed, 493 insertions(+), 163 deletions(-) diff -puN coregrind/core.h~signal-queue coregrind/core.h --- valgrind/coregrind/core.h~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/core.h 2005-01-11 10:16:31.000000000 -0800 @@ -652,6 +652,11 @@ struct _ThreadState { thread unblocks it or someone uses sigwaitsig/sigtimedwait. */ vki_sigset_t sig_mask; + /* A little signal queue for signals we can't get the kernel to + queue for us. This is only allocated as needed, since it should + be rare. */ + struct SigQueue *sig_queue; + /* Stacks. When a thread slot is freed, we don't deallocate its stack; we just leave it lying around for the next use of the slot. If the next use of the slot requires a larger stack, @@ -699,19 +704,6 @@ struct _ThreadState { }; //ThreadState; - -/* When something interesting happens in a signal handler, we need to - longjmp back into the scheduling loop. This enum is the code which - is returned via longjmp/setjmp to describe what happened. */ -typedef enum { - VgSig_None = 0, /* nothing special happened*/ - VgSig_AsyncSig = 1, /* we were signalled externally */ - VgSig_FaultSig, /* an instruction faulted */ - VgSig_FatalSig, /* a fatal signal */ - VgSig_Exiting, /* we're exiting */ -} VgSigCode; - - /* The thread table. */ extern ThreadState VG_(threads)[VG_N_THREADS]; @@ -794,8 +786,19 @@ extern void VG_(scheduler_init) ( void ) extern void VG_(pp_sched_status) ( void ); // Longjmp back to the scheduler and thus enter the sighandler immediately. -extern void VG_(resume_scheduler) ( ThreadId tid, VgSigCode code, - Int sigNo, const vki_siginfo_t *info ); +extern void VG_(resume_scheduler) ( ThreadId tid ); + +/* Copy the state of a thread from VG_(baseBlock), presumably after it + has been descheduled. For sanity-check purposes, fill the vacated + VG_(baseBlock) with garbage so as to make the system more likely to + fail quickly if we erroneously continue to poke around inside + VG_(baseBlock) without first doing a load_thread_state(). +*/ +void VG_(save_thread_state) ( ThreadId tid ); + +/* Copy the saved state of a thread into VG_(baseBlock), ready for it + to be run. */ +void VG_(load_thread_state) ( ThreadId tid ); /* The red-zone size which we put at the bottom (highest address) of thread stacks, for paranoia reasons. This can be arbitrary, and @@ -842,6 +845,9 @@ extern Int VG_(max_signal); extern void VG_(sigstartup_actions) ( void ); +/* Modify a thread's state so that when it next runs it will be + running in the signal handler (or doing the default action if there + is none). */ extern void VG_(deliver_signal) ( ThreadId tid, const vki_siginfo_t * ); extern Bool VG_(is_sig_ign) ( Int sigNo ); @@ -851,7 +857,7 @@ extern void VG_(poll_signals) ( ThreadId /* Fake system calls for signal handling. */ extern void VG_(do_sys_sigaltstack) ( ThreadId tid ); -extern Int VG_(do_sys_sigaction) ( ThreadId tid, Int signo, +extern Int VG_(do_sys_sigaction) ( Int signo, const struct vki_sigaction *new_act, struct vki_sigaction *old_act ); extern void VG_(do_sys_sigprocmask) ( ThreadId tid, Int how, @@ -881,6 +887,16 @@ extern void VG_(synth_fault_perms) (Thr extern void VG_(get_sigstack_bounds)( Addr* low, Addr* high ); +/* Extend the stack to cover addr, if possible */ +extern Bool VG_(extend_stack)(Addr addr, UInt maxsize); + +/* Returns True if the signal is OK for the client to use */ +extern Bool VG_(client_signal_OK)(Int sigNo); + +/* Forces the client's signal handler to SIG_DFL - generally just + before using that signal to kill the process. */ +extern void VG_(set_default_handler)(Int sig); + /* --------------------------------------------------------------------- Exports of vg_mylibc.c ------------------------------------------------------------------ */ diff -puN coregrind/vg_scheduler.c~signal-queue coregrind/vg_scheduler.c --- valgrind/coregrind/vg_scheduler.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/vg_scheduler.c 2005-01-11 10:25:55.000000000 -0800 @@ -277,18 +277,6 @@ ThreadId VG_(get_lwp_tid)(Int lwp) return VG_INVALID_THREADID; } -/* Copy the saved state of a thread into VG_(baseBlock), ready for it - to be run. */ -static void load_thread_state ( ThreadId tid ) -{ - vg_assert(vg_tid_currently_in_baseBlock == VG_INVALID_THREADID); - - VGA_(load_state)(&VG_(threads)[tid].arch, tid); - - vg_tid_currently_in_baseBlock = tid; - vg_tid_last_in_baseBlock = tid; -} - /* Mark a thread as Runnable. This will block until the run_sema is available, so that we get exclusive access to all the shared @@ -456,7 +444,7 @@ void VG_(vg_yield)(void) fail quickly if we erroneously continue to poke around inside VG_(baseBlock) without first doing a load_thread_state(). */ -static void save_thread_state ( ThreadId tid ) +void VG_(save_thread_state) ( ThreadId tid ) { vg_assert(vg_tid_currently_in_baseBlock != VG_INVALID_THREADID); @@ -465,9 +453,20 @@ static void save_thread_state ( ThreadId vg_tid_currently_in_baseBlock = VG_INVALID_THREADID; } +/* Copy the saved state of a thread into VG_(baseBlock), ready for it + to be run. */ +void VG_(load_thread_state) ( ThreadId tid ) +{ + vg_assert(vg_tid_currently_in_baseBlock == VG_INVALID_THREADID); + + VGA_(load_state)(&VG_(threads)[tid].arch, tid); + + vg_tid_currently_in_baseBlock = tid; + vg_tid_last_in_baseBlock = tid; +} + -void VG_(resume_scheduler)(ThreadId tid, VgSigCode why, - Int sigNo, const vki_siginfo_t *info) +void VG_(resume_scheduler)(ThreadId tid) { ThreadState *tst = VG_(get_ThreadState)(tid); @@ -476,25 +475,22 @@ void VG_(resume_scheduler)(ThreadId tid, if (tst->sched_jmpbuf_valid) { /* Can't continue; must longjmp back to the scheduler and thus enter the sighandler immediately. */ - tst->siginfo.si_signo = sigNo; - if (info != NULL) - VG_(memcpy)(&tst->siginfo, info, sizeof(vki_siginfo_t)); - LONGJMP(tst->sched_jmpbuf, why); + LONGJMP(tst->sched_jmpbuf, True); } } -#define SCHEDSETJMP(tid, sigcode, stmt) \ +#define SCHEDSETJMP(tid, jumped, stmt) \ do { \ ThreadState * volatile _qq_tst = VG_(get_ThreadState)(tid); \ \ - sigcode = SETJMP(_qq_tst->sched_jmpbuf); \ - if ((sigcode) == 0) { \ + (jumped) = SETJMP(_qq_tst->sched_jmpbuf); \ + if ((jumped) == 0) { \ vg_assert(!_qq_tst->sched_jmpbuf_valid); \ _qq_tst->sched_jmpbuf_valid = True; \ stmt; \ } else if (VG_(clo_trace_sched)) \ - VG_(printf)("SCHEDSETJMP(line %d) tid %d, sigcode=%d\n", __LINE__, tid, sigcode); \ + VG_(printf)("SCHEDSETJMP(line %d) tid %d, jumped=%d\n", __LINE__, tid, jumped); \ vg_assert(_qq_tst->sched_jmpbuf_valid); \ _qq_tst->sched_jmpbuf_valid = False; \ } while(0) @@ -505,30 +501,37 @@ static UInt run_thread_for_a_while ( ThreadId tid ) { volatile UInt trc = 0; - VgSigCode sigcode; + Bool jumped; vg_assert(VG_(is_valid_tid)(tid)); vg_assert(VG_(is_running_thread)(tid)); + vg_assert(!VG_(is_exiting)(tid)); VGP_PUSHCC(VgpRun); - load_thread_state ( tid ); + VG_(load_thread_state) ( tid ); /* there should be no undealt-with signals */ - vg_assert(VG_(threads)[tid].siginfo.si_signo == 0); + //vg_assert(VG_(threads)[tid].siginfo.si_signo == 0); - //VG_(printf)("running EIP = %p\n", VG_(threads)[tid].arch.m_eip); + //VG_(printf)("running EIP = %p ESP=%p\n", VG_(threads)[tid].arch.m_eip, VG_(threads)[tid].arch.m_esp); - SCHEDSETJMP(tid, sigcode, trc = VG_(run_innerloop)()); + SCHEDSETJMP(tid, jumped, trc = VG_(run_innerloop)()); - if (sigcode != VgSig_None) { + if (jumped) { /* We get here if the client took a fault, which caused our signal handler to longjmp. */ - vg_assert(sigcode == VgSig_FaultSig); /* or fatal... */ vg_assert(trc == 0); trc = VG_TRC_FAULT_SIGNAL; - } + VG_(sigprocmask)(VKI_SIG_SETMASK, &VG_(blocked_mask), NULL); + } + + /* If we were delivered a signal, then the ThreadState registers + may already be up to date. */ + if (VG_(is_VCPU_thread)(tid)) + VG_(save_thread_state) ( tid ); + else + vg_assert(VG_(get_VCPU_tid)() == VG_INVALID_THREADID); - save_thread_state ( tid ); VGP_POPCC(VgpRun); return trc; } @@ -537,6 +540,8 @@ UInt run_thread_for_a_while ( ThreadId t static void mostly_clear_thread_record ( ThreadId tid ) { + vki_sigset_t savedmask; + vg_assert(tid >= 0 && tid < VG_N_THREADS); VGA_(clear_thread)(&VG_(threads)[tid].arch); VG_(threads)[tid].tid = tid; @@ -550,6 +555,14 @@ void mostly_clear_thread_record ( Thread VG_(threads)[tid].altstack.ss_size = 0; VG_(threads)[tid].altstack.ss_flags = VKI_SS_DISABLE; + /* clear out queued signals */ + VG_(block_all_host_signals)(&savedmask); + if (VG_(threads)[tid].sig_queue != NULL) { + VG_(arena_free)(VG_AR_CORE, VG_(threads)[tid].sig_queue); + VG_(threads)[tid].sig_queue = NULL; + } + VG_(restore_all_host_signals)(&savedmask); + VG_(threads)[tid].sched_jmpbuf_valid = False; } @@ -589,6 +602,8 @@ void VG_(scheduler_init) ( void ) VG_(sema_init)(&run_sema); for (i = 0 /* NB; not 1 */; i < VG_N_THREADS; i++) { + VG_(threads)[i].sig_queue = NULL; + VGA_(os_state_init)(&VG_(threads)[i]); mostly_clear_thread_record(i); @@ -596,6 +611,7 @@ void VG_(scheduler_init) ( void ) VG_(threads)[i].stack_base = (Addr)NULL; VG_(threads)[i].stack_guard_size = 0; VG_(threads)[i].stack_highest_word = (Addr)NULL; + } tid_main = VG_(alloc_ThreadState)(); @@ -607,7 +623,7 @@ void VG_(scheduler_init) ( void ) vg_tid_last_in_baseBlock = tid_main; VGA_(init_thread)(&VG_(threads)[tid_main].arch); - save_thread_state ( tid_main ); + VG_(save_thread_state) ( tid_main ); /* Initial thread's stack is the original process stack */ VG_(threads)[tid_main].stack_highest_word = VG_(clstk_end) - sizeof(UInt); @@ -652,47 +668,23 @@ static void handle_tt_miss(ThreadId tid) static void handle_syscall(ThreadId tid) { ThreadState *tst = VG_(get_ThreadState)(tid); - VgSigCode sigcode; + Bool jumped; /* Syscall may or may not block; either way, it will be complete by the time this call returns, and we'll be runnable again. We could take a signal while the syscall runs. */ - SCHEDSETJMP(tid, sigcode, VG_(client_syscall)(tid)); + SCHEDSETJMP(tid, jumped, VG_(client_syscall)(tid)); if (!VG_(is_running_thread)(tid)) VG_(printf)("tid %d not running; running_tid=%d, tid %d status %d\n", tid, running_tid, tid, tst->status); - vg_assert(VG_(is_running_thread)(tid)); - switch(sigcode) { - case VgSig_None: - break; /* nothing */ - - case VgSig_Exiting: - vg_assert(VG_(is_exiting)(tid)); - break; - - case VgSig_AsyncSig: - case VgSig_FatalSig: - if (sigcode == VgSig_FatalSig) { - /* was fatal, we're exiting */ - vg_assert(VG_(is_exiting)(tid)); - } - + if (jumped) { VG_(sigprocmask)(VKI_SIG_SETMASK, &VG_(blocked_mask), NULL); - - tst->siginfo.si_signo = 0; /* don't care about signal state */ - break; - - case VgSig_FaultSig: - /* We should never get an instruction fault from doing a - syscall. */ - vg_assert(0); + VG_(poll_signals)(tid); } - - vg_assert(tst->siginfo.si_signo == 0); } /* @@ -781,7 +773,11 @@ VgSchedReturnCode VG_(scheduler) ( Threa case VG_TRC_EBP_JMP_YIELD: /* Explicit yield, because they're expecting to be spinning. */ - VG_(vg_yield)(); + //VG_(vg_yield)(); + VG_(dispatch_ctr) = 0; + //VG_(set_sleeping)(tid, VgTs_Yielding); + //VG_(do_syscall)(__NR_sched_yield); + //VG_(set_running)(tid); break; case VG_TRC_INNER_COUNTERZERO: @@ -795,9 +791,6 @@ VgSchedReturnCode VG_(scheduler) ( Threa before (ie, there's no window that unsafe signals could sneak in). */ VG_(sigprocmask)(VKI_SIG_SETMASK, &VG_(blocked_mask), NULL); - - VG_(deliver_signal)(tid, &tst->siginfo); - tst->siginfo.si_signo = 0; /* done */ break; default: diff -puN coregrind/vg_signals.c~signal-queue coregrind/vg_signals.c --- valgrind/coregrind/vg_signals.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/vg_signals.c 2005-01-11 10:16:31.000000000 -0800 @@ -102,6 +102,13 @@ vki_sigset_t VG_(blocked_mask); /* Maximum usable signal. */ Int VG_(max_signal) = _VKI_NSIG; +#define N_QUEUED_SIGNALS 4 + +typedef struct SigQueue { + Int next; + vki_siginfo_t sigs[N_QUEUED_SIGNALS]; +} SigQueue; + /* --------------------------------------------------------------------- HIGH LEVEL STUFF TO DO WITH SIGNALS: POLICY (MOSTLY) ------------------------------------------------------------------ */ @@ -451,17 +458,15 @@ void VG_(do_sys_sigaltstack) ( ThreadId } -Int VG_(do_sys_sigaction) ( ThreadId tid, Int signo, +Int VG_(do_sys_sigaction) ( Int signo, const struct vki_sigaction *new_act, struct vki_sigaction *old_act ) { - vg_assert(VG_(is_valid_tid)(tid)); - if (VG_(clo_trace_signals)) VG_(message)(Vg_DebugExtraMsg, - "sys_sigaction: tid %d, sigNo %d, " + "sys_sigaction: sigNo %d, " "new %p, old %p, new flags 0x%llx", - tid, signo, (UWord)new_act, (UWord)old_act, + signo, (UWord)new_act, (UWord)old_act, (ULong)(new_act ? new_act->sa_flags : 0) ); /* Rule out various error conditions. The aim is to ensure that if @@ -673,6 +678,15 @@ void VG_(restore_all_host_signals) ( /* vg_assert(ret == 0); } +Bool VG_(client_signal_OK)(Int sigNo) +{ + Bool ret = sigNo >= 1 && sigNo <= VKI_SIGVGRTUSERMAX; + + //VG_(printf)("client_signal_OK(%d) -> %d\n", sigNo, ret); + + return ret; +} + /* --------------------------------------------------------------------- The signal simulation proper. A simplified version of what the Linux kernel does. @@ -721,6 +735,9 @@ void vg_push_signal_frame ( ThreadId tid vg_assert(vg_scss.scss_per_sig[sigNo].scss_handler != VKI_SIG_IGN); vg_assert(vg_scss.scss_per_sig[sigNo].scss_handler != VKI_SIG_DFL); + /* This may fail if the client stack is busted; if that happens, + the whole process will exit rather than simply calling the + signal handler. */ VGA_(push_signal_frame)(tid, esp_top_of_frame, siginfo, vg_scss.scss_per_sig[sigNo].scss_handler, vg_scss.scss_per_sig[sigNo].scss_flags, @@ -1341,6 +1358,10 @@ static void synth_fault_common(ThreadId info.si_code = si_code; info._sifields._sigfault._addr = (void*)addr; + /* If they're trying to block the signal, force it to be delivered */ + if (VG_(sigismember)(&VG_(threads)[tid].sig_mask, VKI_SIGSEGV)) + VG_(set_default_handler)(VKI_SIGSEGV); + VG_(deliver_signal)(tid, &info); } @@ -1401,10 +1422,9 @@ void VG_(deliver_signal) ( ThreadId tid, If VG_(deliver_signal)() is being called on a thread, we want the signal to get through no matter what; if they're ignoring it, then we do this override (this is so we can send it SIGSEGV, - etc). - */ + etc). */ handler_fn = handler->scss_handler; - if (handler_fn == VKI_SIG_IGN) + if (handler_fn == VKI_SIG_IGN) handler_fn = VKI_SIG_DFL; vg_assert(handler_fn != VKI_SIG_IGN); @@ -1415,8 +1435,19 @@ void VG_(deliver_signal) ( ThreadId tid, /* Create a signal delivery frame, and set the client's %ESP and %EIP so that when execution continues, we will enter the signal handler with the frame on top of the client's stack, - as it expects. */ + as it expects. + + Signal delivery can fail if the client stack is too small or + missing, and we can't push the frame. If that happens, + push_signal_frame will cause the whole process to exit when + we next hit the scheduler. + */ vg_assert(VG_(is_valid_tid)(tid)); + + /* Before we muck with the thread's state, we need to work out where it is. */ + if (VG_(is_VCPU_thread)(tid)) { + VG_(save_thread_state)(tid); + } vg_push_signal_frame ( tid, info ); if (handler->scss_flags & VKI_SA_ONESHOT) { @@ -1440,6 +1471,85 @@ void VG_(deliver_signal) ( ThreadId tid, /* Thread state is ready to go - just add Runnable */ } +/* Make a signal pending for a thread, for later delivery. + VG_(poll_signals) will arrange for it to be delivered at the right + time. + + tid==0 means add it to the process-wide queue, and not sent it to a + specific thread. +*/ +void queue_signal(ThreadId tid, const vki_siginfo_t *si) +{ + ThreadState *tst; + SigQueue *sq; + vki_sigset_t savedmask; + + tst = VG_(get_ThreadState)(tid); + + /* Protect the signal queue against async deliveries */ + VG_(block_all_host_signals)(&savedmask); + + if (tst->sig_queue == NULL) { + tst->sig_queue = VG_(arena_malloc)(VG_AR_CORE, sizeof(*tst->sig_queue)); + VG_(memset)(tst->sig_queue, 0, sizeof(*tst->sig_queue)); + } + sq = tst->sig_queue; + + if (VG_(clo_trace_signals)) + VG_(message)(Vg_DebugMsg, "Queueing signal %d (idx %d) to thread %d", + si->si_signo, sq->next, tid); + + /* Add signal to the queue. If the queue gets overrun, then old + queued signals may get lost. */ + if (sq->sigs[sq->next].si_signo != 0) + VG_(message)(Vg_UserMsg, "Signal %d being dropped from thread %d's queue\n", + sq->sigs[sq->next].si_signo, tid); + + sq->sigs[sq->next] = *si; + sq->next = (sq->next+1) % N_QUEUED_SIGNALS; + + VG_(restore_all_host_signals)(&savedmask); +} + +/* + Returns the next queued signal for thread tid which is in "set". + tid==0 means process-wide signal. Set si_signo to 0 when the + signal has been delivered. + + Must be called with all signals blocked, to protect against async + deliveries. +*/ +static vki_siginfo_t *next_queued(ThreadId tid, const vki_sigset_t *set) +{ + ThreadState *tst = VG_(get_ThreadState)(tid); + SigQueue *sq; + Int idx; + vki_siginfo_t *ret = NULL; + + sq = tst->sig_queue; + if (sq == NULL) + goto out; + + idx = sq->next; + do { + if (0) + VG_(printf)("idx=%d si_signo=%d inset=%d\n", idx, + sq->sigs[idx].si_signo, VG_(sigismember)(set, sq->sigs[idx].si_signo)); + + if (sq->sigs[idx].si_signo != 0 && VG_(sigismember)(set, sq->sigs[idx].si_signo)) { + if (VG_(clo_trace_signals)) + VG_(message)(Vg_DebugMsg, "Returning queued signal %d (idx %d) for thread %d", + sq->sigs[idx].si_signo, idx, tid); + ret = &sq->sigs[idx]; + goto out; + } + + idx = (idx + 1) % N_QUEUED_SIGNALS; + } while(idx != sq->next); + out: + return ret; +} + /* Receive an async signal from the kernel. @@ -1457,6 +1567,10 @@ void vg_async_signalhandler ( Int sigNo, /* The thread isn't currently running, make it so before going on */ VG_(set_running)(tid); + if (VG_(clo_trace_signals)) + VG_(message)(Vg_DebugMsg, "Async handler got signal %d for tid %d info %d", + sigNo, tid, info->si_code); + /* Update thread state properly if this signal happened in or around a syscall */ VGA_(interrupted_syscall)(&tst->arch, uc, !!(vg_scss.scss_per_sig[sigNo].scss_flags & VKI_SA_RESTART)); @@ -1467,11 +1581,66 @@ void vg_async_signalhandler ( Int sigNo, /* longjmp back to the thread's main loop to start executing the handler. */ - VG_(resume_scheduler)(tid, VgSig_AsyncSig, sigNo, info); + VG_(resume_scheduler)(tid); VG_(core_panic)("vg_async_signalhandler: got unexpected signal while outside of scheduler"); } +/* Extend the stack to cover addr. maxsize is the limit the stack can grow to. + + Returns True on success, False on failure. + + Succeeds without doing anything if addr is already within a segment. + + Failure could be caused by: + - addr not below a growable segment + - new stack size would exceed maxsize + - mmap failed for some other reason + */ +Bool VG_(extend_stack)(Addr addr, UInt maxsize) +{ + Segment *seg; + Addr base; + UInt newsize; + + /* Find the next Segment above addr */ + seg = VG_(find_segment)(addr); + if (seg == NULL) + seg = VG_(first_segment)(); + else if (VG_(seg_contains)(seg, addr, sizeof(void *))) + return True; + else + seg = VG_(next_segment)(seg); + + /* If there isn't one, or it isn't growable, fail */ + if (seg == NULL || + !(seg->flags & SF_GROWDOWN) || + VG_(seg_contains)(seg, addr, sizeof(void *))) + return False; + + vg_assert(seg->addr > addr); + + /* Create the mapping */ + base = PGROUNDDN(addr); + newsize = seg->addr - base; + + if (seg->len + newsize >= maxsize) + return False; + + if (VG_(mmap)((Char *)base, newsize, + seg->prot, + VKI_MAP_PRIVATE | VKI_MAP_FIXED | VKI_MAP_ANONYMOUS | VKI_MAP_CLIENT, + seg->flags, + -1, 0) == (void *)-1) + return False; + + if (0) + VG_(printf)("extended stack: %p %d\n", + base, newsize); + + return True; +} + /* Recieve a sync signal from the host. */ @@ -1480,13 +1649,6 @@ void vg_sync_signalhandler ( Int sigNo, { ThreadId tid = VG_(get_lwp_tid)(VG_(gettid)()); - if (!VG_(is_running_thread)(tid)) { - /* This may possibly happen if someone sent us one of the sync - signals with a kill syscall. Get the CPU before going on. */ - vg_assert(info->si_code <= VKI_SI_USER); - VG_(set_running)(tid); - } - vg_assert(info != NULL); vg_assert(info->si_signo == sigNo); vg_assert(sigNo == VKI_SIGSEGV || @@ -1494,9 +1656,24 @@ void vg_sync_signalhandler ( Int sigNo, sigNo == VKI_SIGFPE || sigNo == VKI_SIGILL); + if (!VG_(is_running_thread)(tid)) { + /* This may possibly happen if someone sent us one of the sync + signals with a kill syscall. Get the CPU before going on. */ + vg_assert(info->si_code <= VKI_SI_USER); + VG_(set_running)(tid); + + /* Since every thread has these signals unblocked, we can't rely + on the kernel to route them properly, so we need to queue + them manually. */ + queue_signal(0, info); + VG_(resume_scheduler)(tid); + } + if (VG_(clo_trace_signals)) { - VG_(message)(Vg_DebugMsg, "signal %d arrived ... si_code=%d, EIP=%p", - sigNo, info->si_code, ARCH_INSTR_PTR(VG_(threads)[tid].arch) ); + VG_(message)(Vg_DebugMsg, "signal %d arrived ... si_code=%d, EIP=%p, eip=%p", + sigNo, info->si_code, + ARCH_INSTR_PTR(VG_(threads)[tid].arch), + UCONTEXT_INSTR_PTR(uc) ); } vg_assert(sigNo >= 1 && sigNo <= VG_(max_signal)); @@ -1533,28 +1710,20 @@ void vg_sync_signalhandler ( Int sigNo, } if (info->si_code == 1 && /* SEGV_MAPERR */ - seg != NULL && - fault >= esp && - fault < seg->addr && - (seg->flags & SF_GROWDOWN)) { + fault >= esp) { /* If the fault address is above esp but below the current known stack segment base, and it was a fault because there was nothing mapped there (as opposed to a permissions fault), then extend the stack segment. */ - Addr base = PGROUNDDN(esp); - if (seg->len + (seg->addr - base) <= VG_(threads)[tid].stack_size && - (void*)-1 != VG_(mmap)((Char *)base, seg->addr - base, - VKI_PROT_READ|VKI_PROT_WRITE|VKI_PROT_EXEC, - VKI_MAP_PRIVATE|VKI_MAP_FIXED|VKI_MAP_ANONYMOUS|VKI_MAP_CLIENT, - SF_STACK|SF_GROWDOWN, - -1, 0)) { + if (VG_(extend_stack)(fault, VG_(threads)[tid].stack_size)) { if (VG_(clo_trace_signals)) VG_(message)(Vg_DebugMsg, - " -> extended stack base to %p", base); + " -> extended stack base to %p", PGROUNDDN(fault)); return; // extension succeeded, restart instruction } else - VG_(message)(Vg_UserMsg, "Stack overflow in thread %d", tid); + VG_(message)(Vg_UserMsg, "Stack overflow in thread %d: can't grow stack to %p", + tid, fault); /* Fall into normal signal handling for all other cases */ } else if (info->si_code == 2 && /* SEGV_ACCERR */ @@ -1578,25 +1747,26 @@ void vg_sync_signalhandler ( Int sigNo, } } } - - if (info->si_code <= VKI_SI_USER) { - /* - OK, one of sync signals was sent async by user-mode, so try - to deliver it to someone who cares. - XXX For now, just drop it. - */ - VG_(message)(Vg_UserMsg, - "dropping signal %s sent by pid %d", - signame(sigNo), info->_sifields._kill._pid); - } else { + /* OK, this is a signal we really have to deal with. If it came + from the client's code, then we can jump back into the scheduler + and have it delivered. Otherwise it's a Valgrind bug. */ + { Addr context_ip; Char buf[1024]; - ThreadState *tst; + ThreadState *tst = VG_(get_ThreadState)(VG_(get_lwp_tid)(VG_(gettid)())); + + if (VG_(sigismember)(&tst->sig_mask, sigNo)) { + /* signal is blocked, but they're not allowed to block faults */ + VG_(set_default_handler)(sigNo); + } /* Can't continue; must longjmp back to the scheduler and thus enter the sighandler immediately. */ - VG_(resume_scheduler)(tid, VgSig_FaultSig, sigNo, info); + VG_(deliver_signal)(tid, info); + VG_(resume_scheduler)(tid); + + /* If resume_scheduler returns, it means we don't have longjmp set up, implying that we weren't running client code, and @@ -1655,12 +1825,7 @@ static void sigvgkill_handler(int signo, VG_(set_running)(tid); - /* Check that the signal comes from within, and ignore it if not. */ - if (si->si_code != VKI_SI_TKILL || - VG_(get_lwp_tid)(si->_sifields._kill._pid) == VG_INVALID_THREADID) - return; - - VG_(resume_scheduler)(tid, VgSig_Exiting, 0, 0); + VG_(resume_scheduler)(tid); VG_(core_panic)("sigvgkill_handler couldn't return to the scheduler\n"); } @@ -1685,15 +1850,31 @@ void pp_vg_ksigaction ( struct vki_sigac } /* + Force signal handler to default + */ +void VG_(set_default_handler)(Int signo) +{ + struct vki_sigaction sa; + + sa.ksa_handler = VKI_SIG_DFL; + sa.sa_flags = 0; + sa.sa_restorer = 0; + VG_(sigemptyset)(&sa.sa_mask); + + VG_(do_sys_sigaction)(signo, &sa, NULL); +} + +/* Poll for pending signals, and set the next one up for delivery. */ void VG_(poll_signals)(ThreadId tid) { static const struct vki_timespec zero = { 0, 0 }; - vki_siginfo_t si; + vki_siginfo_t si, *sip; vki_sigset_t pollset; ThreadState *tst = VG_(get_ThreadState)(tid); Int i; + vki_sigset_t saved_mask; /* look for all the signals this thread isn't blocking */ for(i = 0; i < _VKI_NSIG_WORDS; i++) @@ -1701,6 +1882,22 @@ void VG_(poll_signals)(ThreadId tid) VG_(sigdelset)(&pollset, VKI_SIGVGCHLD); /* never look for this */ + //VG_(printf)("tid %d pollset=%08x%08x\n", tid, pollset.sig[1], pollset.sig[0]); + + VG_(block_all_host_signals)(&saved_mask); + + /* First look for any queued pending signals */ + sip = next_queued(0, &pollset); /* process-wide */ + if (sip == NULL) + sip = next_queued(tid, &pollset); /* this thread */ + if (sip != NULL) { + VG_(deliver_signal)(tid, sip); + sip->si_signo = 0; + VG_(restore_all_host_signals)(&saved_mask); + return; + } + VG_(restore_all_host_signals)(&saved_mask); + /* Grab a single pending signal for this thread, and deliver it to the thread */ if (VG_(sigtimedwait)(&pollset, &si, &zero) > 0) { diff -puN coregrind/x86/signal.c~signal-queue coregrind/x86/signal.c --- valgrind/coregrind/x86/signal.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/x86/signal.c 2005-01-11 10:16:31.000000000 -0800 @@ -342,21 +342,88 @@ static void synth_ucontext(ThreadId tid, SET_THREAD_REG(zztid, zzval, ARCH_STACK_PTR, R_STACK_PTR, \ post_reg_write_deliver_signal) +static Bool seg_ok(Segment *seg, VgSigFrame *frame) +{ + if (!VG_(seg_overlaps)(seg, (Addr)frame, sizeof(*frame))) + return False; + + if ((seg->prot & (VKI_PROT_READ|VKI_PROT_WRITE)) != (VKI_PROT_READ|VKI_PROT_WRITE)) + return False; + + return True; +} + +/* Make sure that there are segments covering all of frame, and + they're read/write. */ +static Bool compat_segments(Segment *seg, VgSigFrame *frame) +{ + Segment *next; + Addr a = (Addr)frame; + UInt remains = sizeof(*frame); + + while(remains && seg) { + Addr end = seg->addr + seg->len; + + next = VG_(next_segment)(seg); + + if (!seg_ok(seg, frame)) + return False; + + if ((end - a) > remains) + remains = 0; + else { + remains -= end - a; + a = end; + } + + if (next->addr != end) + next = NULL; + + seg = next; + } + + return remains == 0; +} + void VGA_(push_signal_frame)(ThreadId tid, Addr esp_top_of_frame, const vki_siginfo_t *siginfo, void *handler, UInt flags, const vki_sigset_t *mask) { Addr esp; - ThreadState* tst; + ThreadState* tst = VG_(get_ThreadState)(tid); VgSigFrame* frame; Int sigNo = siginfo->si_signo; + Segment *stackseg = NULL; esp = esp_top_of_frame; esp -= sizeof(VgSigFrame); frame = (VgSigFrame*)esp; - tst = & VG_(threads)[tid]; + if (VG_(extend_stack)((Addr)frame, tst->stack_size)) { + stackseg = VG_(find_segment)((Addr)frame); + if (0 && stackseg) + VG_(printf)("frame=%p seg=%p-%p\n", + frame, stackseg->addr, stackseg->addr+stackseg->len); + } + + if (stackseg == NULL || !compat_segments(stackseg, frame)) { + VG_(message)(Vg_UserMsg, + "Can't extend stack to %p during signal delivery for thread %d:", + frame, tid); + if (stackseg == NULL) + VG_(message)(Vg_UserMsg, " no stack segment"); + else + VG_(message)(Vg_UserMsg, " too small or bad protection modes"); + + /* set SIGSEGV to default handler */ + VG_(set_default_handler)(VKI_SIGSEGV); + VG_(synth_fault_mapping)(tid, (Addr)frame); + + /* The whole process should be about to die, since the default + action of SIGSEGV to kill the whole process. */ + return; + } /* For tracking memory events, indicate the entire frame has been * allocated, but pretend that only the first four words are written */ diff -puN include/tool.h.base~signal-queue include/tool.h.base --- valgrind/include/tool.h.base~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/include/tool.h.base 2005-01-11 10:16:31.000000000 -0800 @@ -532,12 +532,12 @@ extern void VG_(init_shadow_range)(Addr extern Int VG_(sigfillset) ( vki_sigset_t* set ); extern Int VG_(sigemptyset) ( vki_sigset_t* set ); -extern Bool VG_(isfullsigset) ( vki_sigset_t* set ); -extern Bool VG_(isemptysigset) ( vki_sigset_t* set ); +extern Bool VG_(isfullsigset) ( const vki_sigset_t* set ); +extern Bool VG_(isemptysigset) ( const vki_sigset_t* set ); extern Int VG_(sigaddset) ( vki_sigset_t* set, Int signum ); extern Int VG_(sigdelset) ( vki_sigset_t* set, Int signum ); -extern Int VG_(sigismember) ( vki_sigset_t* set, Int signum ); +extern Int VG_(sigismember) ( const vki_sigset_t* set, Int signum ); extern void VG_(sigaddset_from_set) ( vki_sigset_t* dst, vki_sigset_t* src ); extern void VG_(sigdelset_from_set) ( vki_sigset_t* dst, vki_sigset_t* src ); diff -puN coregrind/vg_mylibc.c~signal-queue coregrind/vg_mylibc.c --- valgrind/coregrind/vg_mylibc.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/vg_mylibc.c 2005-01-11 10:16:31.000000000 -0800 @@ -59,7 +59,7 @@ Int VG_(sigemptyset)( vki_sigset_t* set return 0; } -Bool VG_(isemptysigset)( vki_sigset_t* set ) +Bool VG_(isemptysigset)( const vki_sigset_t* set ) { Int i; vg_assert(set != NULL); @@ -68,7 +68,7 @@ Bool VG_(isemptysigset)( vki_sigset_t* s return True; } -Bool VG_(isfullsigset)( vki_sigset_t* set ) +Bool VG_(isfullsigset)( const vki_sigset_t* set ) { Int i; vg_assert(set != NULL); @@ -100,7 +100,7 @@ Int VG_(sigdelset)( vki_sigset_t* set, I return 0; } -Int VG_(sigismember) ( vki_sigset_t* set, Int signum ) +Int VG_(sigismember) ( const vki_sigset_t* set, Int signum ) { if (set == NULL) return 0; diff -puN coregrind/vg_main.c~signal-queue coregrind/vg_main.c --- valgrind/coregrind/vg_main.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/vg_main.c 2005-01-11 10:16:31.000000000 -0800 @@ -2696,8 +2696,17 @@ int main(int argc, char **argv, char **e // Protect client trampoline page (which is also sysinfo stuff) // p: segment stuff [otherwise get seg faults...] //-------------------------------------------------------------- - VG_(mprotect)( (void *)VG_(client_trampoline_code), - VG_(trampoline_code_length), VKI_PROT_READ|VKI_PROT_EXEC ); + { + Segment *seg; + VG_(mprotect)( (void *)VG_(client_trampoline_code), + VG_(trampoline_code_length), VKI_PROT_READ|VKI_PROT_EXEC ); + + /* Make sure this segment isn't treated as stack */ + seg = VG_(find_segment)(VG_(client_trampoline_code)); + if (seg && VG_(seg_contains)(seg, VG_(client_trampoline_code), + VG_(trampoline_code_length))) + seg->flags &= ~(SF_STACK | SF_GROWDOWN); + } //============================================================== // Can use VG_(map)() after segments set up diff -puN none/tests/Makefile.am~signal-queue none/tests/Makefile.am --- valgrind/none/tests/Makefile.am~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/none/tests/Makefile.am 2005-01-11 10:16:31.000000000 -0800 @@ -5,6 +5,7 @@ noinst_SCRIPTS = filter_none_discards fi EXTRA_DIST = $(noinst_SCRIPTS) \ args.stderr.exp args.stdout.exp args.vgtest \ bitfield1.stderr.exp bitfield1.vgtest \ + blockfault.vgtest \ closeall.stderr.exp closeall.vgtest \ cmdline1.stderr.exp cmdline1.stdout.exp cmdline1.vgtest \ cmdline2.stderr.exp cmdline2.stdout.exp cmdline2.vgtest \ @@ -57,7 +58,7 @@ EXTRA_DIST = $(noinst_SCRIPTS) \ yield.stderr.exp yield.stdout.exp yield.vgtest check_PROGRAMS = \ - args bitfield1 closeall coolo_strlen \ + args bitfield1 blockfault closeall coolo_strlen \ discard exec-sigmask execve fcntl_setown floored fork \ fucomip getseg \ munmap_exe map_unaligned map_unmap mq mremap rcrl readline1 \ @@ -74,6 +75,7 @@ AM_CXXFLAGS = $(AM_CFLAGS) # generic C ones args_SOURCES = args.c bitfield1_SOURCES = bitfield1.c +blockfault_SOURCES = blockfault.c closeall_SOURCES = closeall.c coolo_strlen_SOURCES = coolo_strlen.c discard_SOURCES = discard.c diff -puN /dev/null none/tests/blockfault.c --- /dev/null 2004-02-23 13:02:56.000000000 -0800 +++ valgrind-jeremy/none/tests/blockfault.c 2005-01-11 10:16:31.000000000 -0800 @@ -0,0 +1,31 @@ +#include +#include +#include + +static void handler(int sig, siginfo_t *info, void *v) +{ + printf("info: sig=%d code=%d addr=%p\n", + info->si_signo, info->si_code, info->si_addr); + exit(0); +} + +/* Blocking a fault, ie SIGSEGV, won't work, and is the same as having + the default handler */ +int main() +{ + struct sigaction sa; + sigset_t mask; + + sa.sa_sigaction = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + + sigaction(SIGSEGV, &sa, NULL); + + sigfillset(&mask); + sigprocmask(SIG_BLOCK, &mask, NULL); + + *(volatile int *)1234 = 213; + + return 0; +} diff -puN /dev/null none/tests/blockfault.vgtest --- /dev/null 2004-02-23 13:02:56.000000000 -0800 +++ valgrind-jeremy/none/tests/blockfault.vgtest 2005-01-11 10:16:31.000000000 -0800 @@ -0,0 +1 @@ +prog: blockfault diff -puN /dev/null none/tests/blockfault.stderr.exp --- /dev/null 2004-02-23 13:02:56.000000000 -0800 +++ valgrind-jeremy/none/tests/blockfault.stderr.exp 2005-01-11 10:16:31.000000000 -0800 @@ -0,0 +1,6 @@ + + +Process terminating with default action of signal 11 (SIGSEGV) + Access not within mapped region at address 0x4D2 + at 0x804852D: main (blockfault.c:28) + diff -puN none/tests/blockfault.stdout.exp~signal-queue none/tests/blockfault.stdout.exp diff -puN coregrind/vg_syscalls.c~signal-queue coregrind/vg_syscalls.c --- valgrind/coregrind/vg_syscalls.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/vg_syscalls.c 2005-01-11 10:16:31.000000000 -0800 @@ -1934,6 +1934,10 @@ PRE(sys_fcntl, 0) case VKI_F_SETOWN: case VKI_F_SETSIG: case VKI_F_SETLEASE: + if (arg2 == VKI_F_SETSIG && !VG_(client_signal_OK)(arg3)) { + set_result(-VKI_EINVAL); + return; + } PRE_REG_READ3(long, "fcntl", unsigned int, fd, unsigned int, cmd, unsigned long, arg); break; @@ -4067,7 +4071,7 @@ PRE(sys_tkill, 0) /* int tkill(pid_t tid, int sig); */ PRINT("sys_tkill ( %d, %d )", arg1,arg2); PRE_REG_READ2(long, "tkill", int, tid, int, sig); - if (arg2 == VKI_SIGVGKILL || arg2 == VKI_SIGVGCHLD) + if (!VG_(client_signal_OK)(arg2)) set_result( -VKI_EINVAL ); } @@ -4085,7 +4089,7 @@ PRE(sys_tgkill, 0) /* int tgkill(pid_t tgid, pid_t tid, int sig); */ PRINT("sys_tgkill ( %d, %d, %d )", arg1,arg2,arg3); PRE_REG_READ3(long, "tgkill", int, tgid, int, tid, int, sig); - if (arg2 == VKI_SIGVGKILL || arg2 == VKI_SIGVGCHLD) + if (!VG_(client_signal_OK)(arg3)) set_result( -VKI_EINVAL ); } @@ -4103,7 +4107,7 @@ PRE(sys_kill, 0) /* int kill(pid_t pid, int sig); */ PRINT("sys_kill ( %d, %d )", arg1,arg2); PRE_REG_READ2(long, "kill", int, pid, int, sig); - if (arg2 == VKI_SIGVGKILL || arg2 == VKI_SIGVGCHLD) + if (!VG_(client_signal_OK)(arg2)) set_result( -VKI_EINVAL ); } @@ -5415,7 +5419,7 @@ PRE(sys_sigaction, Special) oldp = &old; } - set_result(VG_(do_sys_sigaction)(tid, arg1, newp, oldp)); + set_result(VG_(do_sys_sigaction)(arg1, newp, oldp)); if (arg3 != 0 && SYSRES == 0) { struct vki_old_sigaction *oldold = (struct vki_old_sigaction *)arg3; @@ -5446,7 +5450,7 @@ PRE(sys_rt_sigaction, Special) if (arg3 != 0) PRE_MEM_WRITE( "rt_sigaction(oldact)", arg3, sizeof(struct vki_sigaction)); - set_result(VG_(do_sys_sigaction)(tid, arg1, (const struct vki_sigaction *)arg2, + set_result(VG_(do_sys_sigaction)(arg1, (const struct vki_sigaction *)arg2, (struct vki_sigaction *)arg3)); } diff -puN coregrind/x86-linux/syscalls.c~signal-queue coregrind/x86-linux/syscalls.c --- valgrind/coregrind/x86-linux/syscalls.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/x86-linux/syscalls.c 2005-01-11 10:16:31.000000000 -0800 @@ -397,7 +397,12 @@ PRE(sys_clone, Special) /* Only look at the flags we really care about */ cloneflags = arg1 & (VKI_CLONE_VM | VKI_CLONE_FS | VKI_CLONE_FILES | VKI_CLONE_VFORK); - + + if ((arg1 & VKI_CSIGNAL) && !VG_(client_signal_OK)(arg1 & VKI_CSIGNAL)) { + set_result( -VKI_EINVAL ); + return; + } + switch(cloneflags) { case VKI_CLONE_VM | VKI_CLONE_FS | VKI_CLONE_FILES: /* thread creation */ diff -puN coregrind/linux/core_os.c~signal-queue coregrind/linux/core_os.c --- valgrind/coregrind/linux/core_os.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/linux/core_os.c 2005-01-11 10:16:31.000000000 -0800 @@ -137,8 +137,6 @@ void VGA_(intercept_libc_freeres_wrapper Clean up the client by calling __libc_freeres() (if requested) */ void VGA_(final_tidyup)(ThreadId tid) { - struct vki_sigaction sa; - vg_assert(VG_(is_running_thread)(tid)); if (!VG_(needs).libc_freeres || @@ -160,14 +158,10 @@ void VGA_(final_tidyup)(ThreadId tid) VG_(threads)[tid].sig_mask = VG_(blocked_mask); /* and restore handlers to default */ - sa.ksa_handler = VKI_SIG_DFL; - VG_(sigemptyset)(&sa.sa_mask); - sa.sa_flags = 0; - - VG_(do_sys_sigaction)(tid, VKI_SIGSEGV, &sa, NULL); - VG_(do_sys_sigaction)(tid, VKI_SIGBUS, &sa, NULL); - VG_(do_sys_sigaction)(tid, VKI_SIGILL, &sa, NULL); - VG_(do_sys_sigaction)(tid, VKI_SIGFPE, &sa, NULL); + VG_(set_default_handler)(VKI_SIGSEGV); + VG_(set_default_handler)(VKI_SIGBUS); + VG_(set_default_handler)(VKI_SIGILL); + VG_(set_default_handler)(VKI_SIGFPE); // We were exiting, so assert that... vg_assert(VG_(is_exiting)(tid)); diff -puN none/tests/x86/badseg.c~signal-queue none/tests/x86/badseg.c --- valgrind/none/tests/x86/badseg.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/none/tests/x86/badseg.c 2005-01-11 10:16:31.000000000 -0800 @@ -15,7 +15,7 @@ int main() int val; sa.sa_sigaction = handler; - sigemptyset(&sa.sa_mask); + sigfillset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); diff -puN coregrind/x86-linux/ldt.c~signal-queue coregrind/x86-linux/ldt.c --- valgrind/coregrind/x86-linux/ldt.c~signal-queue 2005-01-11 10:16:31.000000000 -0800 +++ valgrind-jeremy/coregrind/x86-linux/ldt.c 2005-01-11 10:16:31.000000000 -0800 @@ -190,8 +190,13 @@ Addr VG_(do_useseg) ( UInt seg_selector, /* Sanity check the segment selector. Ensure that RPL=11b (least privilege). This forms the bottom 2 bits of the selector. */ if ((seg_selector & 3) != 3) { - VG_(synth_fault)(VG_(get_VCPU_tid)()); - return 0; + ThreadId tid = VG_(get_VCPU_tid)(); + vg_assert(tid != VG_INVALID_THREADID); + + //VG_(printf)("synth fault EIP=%p\n", VG_(baseBlock)[VGOFF_(m_eip)]); + VG_(synth_fault)(tid); + VG_(resume_scheduler)(tid); + VG_(core_panic)("do_useseg called outside of scheduler loop?"); } /* Extract the table number */ _