From: Julian Seward Date: Fri, 3 May 2002 19:09:05 +0000 (+0000) Subject: Change the way Valgrind exits. X-Git-Tag: svn/VALGRIND_1_0_3~251 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3a27858aea8f644ee2528b085bcdf05c79ba23d7;p=thirdparty%2Fvalgrind.git Change the way Valgrind exits. Until now, valgrind waited for ld.so to call the .fini code in valgrind.so, and took this as its cue to switch back to the real CPU for the rest of the journey. This is a problem if ld.so subsequently calls other .so's .fini code and threading is in use, because they do pthread_* calls which cannot be handled by valgrind's libpthread.so without valgrind actually being active. So we ignore the call to valgrind's .fini code, and run the program all the way up to the point where it calls syscall exit() to disappear. This makes the order in which the .fini sections are run irrelevant, since Valgrind has control during all of them, and so threading facilities are still available for all of them. This change means Mozilla 1.0RC1 now exits a lot more cleanly than it did. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@201 --- diff --git a/coregrind/vg_constants.h b/coregrind/vg_constants.h index 191e498897..710b12cb90 100644 --- a/coregrind/vg_constants.h +++ b/coregrind/vg_constants.h @@ -103,7 +103,6 @@ /* Assembly code stubs make these requests ... */ #define VG_USERREQ__SIGNAL_RETURNS 0x4001 #define VG_USERREQ__PTHREAD_RETURNS 0x4002 -#define VG_USERREQ__SHUTDOWN_VALGRIND 0x4003 #endif /* ndef __VG_INCLUDE_H */ diff --git a/coregrind/vg_include.h b/coregrind/vg_include.h index af23b40c52..4b36e443da 100644 --- a/coregrind/vg_include.h +++ b/coregrind/vg_include.h @@ -437,7 +437,6 @@ extern Bool VG_(is_empty_arena) ( ArenaId aid ); In vg_constants.h: #define VG_USERREQ__SIGNAL_RETURNS 0x4001 #define VG_USERREQ__PTHREAD_RETURNS 0x4002 -#define VG_USERREQ__SHUTDOWN_VALGRIND 0x4003 */ @@ -602,9 +601,17 @@ extern ThreadId VG_(identify_stack_addr)( Addr a ); /* Return codes from the scheduler. */ typedef - enum { VgSrc_Deadlock, VgSrc_Shutdown, VgSrc_BbsDone } + enum { + VgSrc_Deadlock, /* no runnable threads and no prospect of any + even if we wait for a long time */ + VgSrc_ExitSyscall, /* client called exit(). This is the normal + route out. */ + VgSrc_BbsDone /* In a debugging run, the specified number of + bbs has been completed. */ + } VgSchedReturnCode; + /* The scheduler. */ extern VgSchedReturnCode VG_(scheduler) ( void ); @@ -1320,6 +1327,9 @@ extern Bool VG_(running_on_simd_CPU); /* Initially False */ /* The current LRU epoch. */ extern UInt VG_(current_epoch); +/* This is the ThreadId of the last thread the scheduler ran. */ +extern ThreadId VG_(last_run_tid); + /* --- Counters, for informational purposes only. --- */ @@ -1549,7 +1559,6 @@ extern void VG_(do_syscall) ( void ); Exports of vg_startup.S ------------------------------------------------------------------ */ -extern void VG_(shutdown); extern void VG_(switch_to_real_CPU) ( void ); extern void VG_(swizzle_esp_then_start_GDB) ( Addr m_eip_at_error, diff --git a/coregrind/vg_main.c b/coregrind/vg_main.c index b98459f34d..1b6bba33be 100644 --- a/coregrind/vg_main.c +++ b/coregrind/vg_main.c @@ -331,6 +331,9 @@ Bool VG_(disassemble) = False; /* The current LRU epoch. */ UInt VG_(current_epoch) = 0; +/* This is the ThreadId of the last thread the scheduler ran. */ +ThreadId VG_(last_run_tid) = 0; + /* --------------------------------------------------------------------- Counters, for informational purposes only. @@ -975,6 +978,7 @@ void VG_(main) ( void ) { Int i; VgSchedReturnCode src; + ThreadState* tst; /* Set up our stack sanity-check words. */ for (i = 0; i < 10; i++) { @@ -1105,17 +1109,46 @@ void VG_(main) ( void ) VG_(mash_LD_PRELOAD_string)(VG_(getenv)("LD_PRELOAD")); } - /* Prepare to restore state to the real CPU. */ - VG_(load_thread_state)(1 /* root thread */); - VG_(copy_baseBlock_to_m_state_static)(); - - /* This pushes a return address on the simulator's stack, which - is abandoned. We call vg_sigshutdown_actions() at the end - of vg_switch_to_real_CPU(), so as to ensure that the original - stack and machine state is restored before the real signal - mechanism is restored. - */ - VG_(switch_to_real_CPU)(); + /* Decide how to exit. This depends on what the scheduler + returned. */ + switch (src) { + case VgSrc_ExitSyscall: /* the normal way out */ + vg_assert(VG_(last_run_tid) > 0 + && VG_(last_run_tid) < VG_N_THREADS); + tst = VG_(get_thread_state)(VG_(last_run_tid)); + vg_assert(tst->status == VgTs_Runnable); + /* The thread's %EBX will hold the arg to exit(), so we just + do exit with that arg. */ + VG_(exit)( tst->m_ebx ); + /* NOT ALIVE HERE! */ + VG_(panic)("entered the afterlife in vg_main() -- ExitSyscall"); + break; /* what the hell :) */ + + case VgSrc_Deadlock: + /* Just exit now. No point in continuing. */ + VG_(exit)(0); + VG_(panic)("entered the afterlife in vg_main() -- Deadlock"); + break; + + case VgSrc_BbsDone: + /* Tricky; we have to try and switch back to the real CPU. + This is all very dodgy and won't work at all in the + presence of threads, or if the client happened to be + running a signal handler. */ + /* Prepare to restore state to the real CPU. */ + VG_(load_thread_state)(1 /* root thread */ ); + VG_(copy_baseBlock_to_m_state_static)(); + + /* This pushes a return address on the simulator's stack, + which is abandoned. We call vg_sigshutdown_actions() at + the end of vg_switch_to_real_CPU(), so as to ensure that + the original stack and machine state is restored before + the real signal mechanism is restored. */ + VG_(switch_to_real_CPU)(); + + default: + VG_(panic)("vg_main(): unexpected scheduler return code"); + } } diff --git a/coregrind/vg_scheduler.c b/coregrind/vg_scheduler.c index a58558739d..ea24997adb 100644 --- a/coregrind/vg_scheduler.c +++ b/coregrind/vg_scheduler.c @@ -1142,7 +1142,7 @@ VgSchedReturnCode VG_(scheduler) ( void ) /* Start with the root thread. tid in general indicates the currently runnable/just-finished-running thread. */ - tid = 1; + VG_(last_run_tid) = tid = 1; /* This is the top level scheduler loop. It falls into three phases. */ @@ -1239,8 +1239,8 @@ VgSchedReturnCode VG_(scheduler) ( void ) while, and go round again, in the hope that eventually a thread becomes runnable. */ nanosleep_for_a_while(); - // pp_sched_status(); - // VG_(printf)(".\n"); + /* pp_sched_status(); */ + /* VG_(printf)(".\n"); */ } @@ -1275,6 +1275,8 @@ VgSchedReturnCode VG_(scheduler) ( void ) /* Actually run thread tid. */ while (True) { + VG_(last_run_tid) = tid; + /* For stats purposes only. */ VG_(num_scheduling_events_MINOR) ++; @@ -1334,7 +1336,9 @@ VgSchedReturnCode VG_(scheduler) ( void ) if (trc == VG_TRC_EBP_JMP_SYSCALL) { /* Do a syscall for the vthread tid. This could cause it - to become non-runnable. */ + to become non-runnable. One special case: spot the + client doing calls to exit() and take this as the cue + to exit. */ # if 0 { UInt* esp; Int i; esp=(UInt*)vg_threads[tid].m_esp; @@ -1344,6 +1348,9 @@ VgSchedReturnCode VG_(scheduler) ( void ) } # endif + if (vg_threads[tid].m_eax == __NR_exit) + return VgSrc_ExitSyscall; + sched_do_syscall(tid); # if 0 @@ -1413,14 +1420,6 @@ VgSchedReturnCode VG_(scheduler) ( void ) 1, whereupon the signal will be "delivered". */ break; -#if 0 - case VG_TRC_EBP_JMP_SYSCALL: - /* Do a syscall for the vthread tid. This could cause it - to become non-runnable. */ - sched_do_syscall(tid); - break; -#endif - case VG_TRC_EBP_JMP_CLIENTREQ: /* Do a client request for the vthread tid. Note that some requests will have been handled by @@ -1442,11 +1441,7 @@ VgSchedReturnCode VG_(scheduler) ( void ) other blocked threads become runnable. In general we can and often do mess with the state of arbitrary threads at this point. */ - if (request_code == VG_USERREQ__SHUTDOWN_VALGRIND) { - return VgSrc_Shutdown; - } else { - do_nontrivial_clientreq(tid); - } + do_nontrivial_clientreq(tid); break; default: diff --git a/coregrind/vg_startup.S b/coregrind/vg_startup.S index 561642196b..a0bb4eab79 100644 --- a/coregrind/vg_startup.S +++ b/coregrind/vg_startup.S @@ -104,40 +104,20 @@ first_insn_to_simulate: -.global VG_(shutdown) VG_(shutdown): - # ld.so will call here after execution of the program proper - # is complete, to allow libraries to close down cleanly. - # Note that we will enter here on the synthetic CPU, not - # the real one! So the interpreter must notice when this - # procedure is called, and use that as its cue to switch - # back to the real CPU. As usual we have a client request - # to do this. To make sense of this you need to read the - # definition of VALGRIND_MAGIC_SEQUENCE in valgrind.h. - pushl %eax - pushl %edx - subl $20, %esp # allocate arg block - movl %esp, %eax # %eax == &_zzq_args[0] - movl $VG_USERREQ__SHUTDOWN_VALGRIND, 0(%eax) # request - # dont bother to fill in arg1 .. 4, not important - # and now the magic sequence itself: - roll $29, %eax - roll $3, %eax - rorl $27, %eax - rorl $5, %eax - roll $13, %eax - roll $19, %eax - # valgrind now exits. the following insns are - # executed on the real CPU. - addl $20, %esp - popl %edx - popl %eax + # Just return, and ignore any attempt by ld.so to call + # valgrind.sos exit function. We just run the client all + # the way to the final exit() syscall. This sidesteps + # problems caused by ld.so calling the finalisation code + # of other .sos *after* it shuts down valgrind, which + # was causing big problems with threads. ret + + .global VG_(switch_to_real_CPU) VG_(switch_to_real_CPU): - # Once Valgrind has decided it needs to exit, either - # because it has detected a call to vg_shutdown, or + # Once Valgrind has decided it needs to exit, # because the specified number of insns have been completed # during a debugging run, it jumps here, which copies the # simulators state into the real machine state. Execution diff --git a/vg_constants.h b/vg_constants.h index 191e498897..710b12cb90 100644 --- a/vg_constants.h +++ b/vg_constants.h @@ -103,7 +103,6 @@ /* Assembly code stubs make these requests ... */ #define VG_USERREQ__SIGNAL_RETURNS 0x4001 #define VG_USERREQ__PTHREAD_RETURNS 0x4002 -#define VG_USERREQ__SHUTDOWN_VALGRIND 0x4003 #endif /* ndef __VG_INCLUDE_H */ diff --git a/vg_include.h b/vg_include.h index af23b40c52..4b36e443da 100644 --- a/vg_include.h +++ b/vg_include.h @@ -437,7 +437,6 @@ extern Bool VG_(is_empty_arena) ( ArenaId aid ); In vg_constants.h: #define VG_USERREQ__SIGNAL_RETURNS 0x4001 #define VG_USERREQ__PTHREAD_RETURNS 0x4002 -#define VG_USERREQ__SHUTDOWN_VALGRIND 0x4003 */ @@ -602,9 +601,17 @@ extern ThreadId VG_(identify_stack_addr)( Addr a ); /* Return codes from the scheduler. */ typedef - enum { VgSrc_Deadlock, VgSrc_Shutdown, VgSrc_BbsDone } + enum { + VgSrc_Deadlock, /* no runnable threads and no prospect of any + even if we wait for a long time */ + VgSrc_ExitSyscall, /* client called exit(). This is the normal + route out. */ + VgSrc_BbsDone /* In a debugging run, the specified number of + bbs has been completed. */ + } VgSchedReturnCode; + /* The scheduler. */ extern VgSchedReturnCode VG_(scheduler) ( void ); @@ -1320,6 +1327,9 @@ extern Bool VG_(running_on_simd_CPU); /* Initially False */ /* The current LRU epoch. */ extern UInt VG_(current_epoch); +/* This is the ThreadId of the last thread the scheduler ran. */ +extern ThreadId VG_(last_run_tid); + /* --- Counters, for informational purposes only. --- */ @@ -1549,7 +1559,6 @@ extern void VG_(do_syscall) ( void ); Exports of vg_startup.S ------------------------------------------------------------------ */ -extern void VG_(shutdown); extern void VG_(switch_to_real_CPU) ( void ); extern void VG_(swizzle_esp_then_start_GDB) ( Addr m_eip_at_error, diff --git a/vg_main.c b/vg_main.c index b98459f34d..1b6bba33be 100644 --- a/vg_main.c +++ b/vg_main.c @@ -331,6 +331,9 @@ Bool VG_(disassemble) = False; /* The current LRU epoch. */ UInt VG_(current_epoch) = 0; +/* This is the ThreadId of the last thread the scheduler ran. */ +ThreadId VG_(last_run_tid) = 0; + /* --------------------------------------------------------------------- Counters, for informational purposes only. @@ -975,6 +978,7 @@ void VG_(main) ( void ) { Int i; VgSchedReturnCode src; + ThreadState* tst; /* Set up our stack sanity-check words. */ for (i = 0; i < 10; i++) { @@ -1105,17 +1109,46 @@ void VG_(main) ( void ) VG_(mash_LD_PRELOAD_string)(VG_(getenv)("LD_PRELOAD")); } - /* Prepare to restore state to the real CPU. */ - VG_(load_thread_state)(1 /* root thread */); - VG_(copy_baseBlock_to_m_state_static)(); - - /* This pushes a return address on the simulator's stack, which - is abandoned. We call vg_sigshutdown_actions() at the end - of vg_switch_to_real_CPU(), so as to ensure that the original - stack and machine state is restored before the real signal - mechanism is restored. - */ - VG_(switch_to_real_CPU)(); + /* Decide how to exit. This depends on what the scheduler + returned. */ + switch (src) { + case VgSrc_ExitSyscall: /* the normal way out */ + vg_assert(VG_(last_run_tid) > 0 + && VG_(last_run_tid) < VG_N_THREADS); + tst = VG_(get_thread_state)(VG_(last_run_tid)); + vg_assert(tst->status == VgTs_Runnable); + /* The thread's %EBX will hold the arg to exit(), so we just + do exit with that arg. */ + VG_(exit)( tst->m_ebx ); + /* NOT ALIVE HERE! */ + VG_(panic)("entered the afterlife in vg_main() -- ExitSyscall"); + break; /* what the hell :) */ + + case VgSrc_Deadlock: + /* Just exit now. No point in continuing. */ + VG_(exit)(0); + VG_(panic)("entered the afterlife in vg_main() -- Deadlock"); + break; + + case VgSrc_BbsDone: + /* Tricky; we have to try and switch back to the real CPU. + This is all very dodgy and won't work at all in the + presence of threads, or if the client happened to be + running a signal handler. */ + /* Prepare to restore state to the real CPU. */ + VG_(load_thread_state)(1 /* root thread */ ); + VG_(copy_baseBlock_to_m_state_static)(); + + /* This pushes a return address on the simulator's stack, + which is abandoned. We call vg_sigshutdown_actions() at + the end of vg_switch_to_real_CPU(), so as to ensure that + the original stack and machine state is restored before + the real signal mechanism is restored. */ + VG_(switch_to_real_CPU)(); + + default: + VG_(panic)("vg_main(): unexpected scheduler return code"); + } } diff --git a/vg_scheduler.c b/vg_scheduler.c index a58558739d..ea24997adb 100644 --- a/vg_scheduler.c +++ b/vg_scheduler.c @@ -1142,7 +1142,7 @@ VgSchedReturnCode VG_(scheduler) ( void ) /* Start with the root thread. tid in general indicates the currently runnable/just-finished-running thread. */ - tid = 1; + VG_(last_run_tid) = tid = 1; /* This is the top level scheduler loop. It falls into three phases. */ @@ -1239,8 +1239,8 @@ VgSchedReturnCode VG_(scheduler) ( void ) while, and go round again, in the hope that eventually a thread becomes runnable. */ nanosleep_for_a_while(); - // pp_sched_status(); - // VG_(printf)(".\n"); + /* pp_sched_status(); */ + /* VG_(printf)(".\n"); */ } @@ -1275,6 +1275,8 @@ VgSchedReturnCode VG_(scheduler) ( void ) /* Actually run thread tid. */ while (True) { + VG_(last_run_tid) = tid; + /* For stats purposes only. */ VG_(num_scheduling_events_MINOR) ++; @@ -1334,7 +1336,9 @@ VgSchedReturnCode VG_(scheduler) ( void ) if (trc == VG_TRC_EBP_JMP_SYSCALL) { /* Do a syscall for the vthread tid. This could cause it - to become non-runnable. */ + to become non-runnable. One special case: spot the + client doing calls to exit() and take this as the cue + to exit. */ # if 0 { UInt* esp; Int i; esp=(UInt*)vg_threads[tid].m_esp; @@ -1344,6 +1348,9 @@ VgSchedReturnCode VG_(scheduler) ( void ) } # endif + if (vg_threads[tid].m_eax == __NR_exit) + return VgSrc_ExitSyscall; + sched_do_syscall(tid); # if 0 @@ -1413,14 +1420,6 @@ VgSchedReturnCode VG_(scheduler) ( void ) 1, whereupon the signal will be "delivered". */ break; -#if 0 - case VG_TRC_EBP_JMP_SYSCALL: - /* Do a syscall for the vthread tid. This could cause it - to become non-runnable. */ - sched_do_syscall(tid); - break; -#endif - case VG_TRC_EBP_JMP_CLIENTREQ: /* Do a client request for the vthread tid. Note that some requests will have been handled by @@ -1442,11 +1441,7 @@ VgSchedReturnCode VG_(scheduler) ( void ) other blocked threads become runnable. In general we can and often do mess with the state of arbitrary threads at this point. */ - if (request_code == VG_USERREQ__SHUTDOWN_VALGRIND) { - return VgSrc_Shutdown; - } else { - do_nontrivial_clientreq(tid); - } + do_nontrivial_clientreq(tid); break; default: diff --git a/vg_startup.S b/vg_startup.S index 561642196b..a0bb4eab79 100644 --- a/vg_startup.S +++ b/vg_startup.S @@ -104,40 +104,20 @@ first_insn_to_simulate: -.global VG_(shutdown) VG_(shutdown): - # ld.so will call here after execution of the program proper - # is complete, to allow libraries to close down cleanly. - # Note that we will enter here on the synthetic CPU, not - # the real one! So the interpreter must notice when this - # procedure is called, and use that as its cue to switch - # back to the real CPU. As usual we have a client request - # to do this. To make sense of this you need to read the - # definition of VALGRIND_MAGIC_SEQUENCE in valgrind.h. - pushl %eax - pushl %edx - subl $20, %esp # allocate arg block - movl %esp, %eax # %eax == &_zzq_args[0] - movl $VG_USERREQ__SHUTDOWN_VALGRIND, 0(%eax) # request - # dont bother to fill in arg1 .. 4, not important - # and now the magic sequence itself: - roll $29, %eax - roll $3, %eax - rorl $27, %eax - rorl $5, %eax - roll $13, %eax - roll $19, %eax - # valgrind now exits. the following insns are - # executed on the real CPU. - addl $20, %esp - popl %edx - popl %eax + # Just return, and ignore any attempt by ld.so to call + # valgrind.sos exit function. We just run the client all + # the way to the final exit() syscall. This sidesteps + # problems caused by ld.so calling the finalisation code + # of other .sos *after* it shuts down valgrind, which + # was causing big problems with threads. ret + + .global VG_(switch_to_real_CPU) VG_(switch_to_real_CPU): - # Once Valgrind has decided it needs to exit, either - # because it has detected a call to vg_shutdown, or + # Once Valgrind has decided it needs to exit, # because the specified number of insns have been completed # during a debugging run, it jumps here, which copies the # simulators state into the real machine state. Execution diff --git a/vg_syscall_mem.c b/vg_syscall_mem.c index f3b7052f8a..ab43451f22 100644 --- a/vg_syscall_mem.c +++ b/vg_syscall_mem.c @@ -332,6 +332,10 @@ void VG_(perform_assumed_nonblocking_syscall) ( ThreadId tid ) switch (syscallno) { + case __NR_exit: + VG_(panic)("syscall exit() not caught by the scheduler?!"); + break; + case __NR_sigaltstack: VG_(unimplemented) ("client signals on alternative stack (SA_ONSTACK)"); @@ -348,7 +352,10 @@ void VG_(perform_assumed_nonblocking_syscall) ( ThreadId tid ) # if defined(__NR_modify_ldt) case __NR_modify_ldt: VG_(unimplemented) - ("modify_ldt(): I (JRS) haven't investigated this yet; sorry."); + ("modify_ldt(): I (JRS) haven't investigated this yet; sorry.\n " + "This might be caused by linking to NVidia's libGL.so, so\n " + "avoiding it, if you can, _might_ help you. For example,\n " + "re-build any Qt libraries you are using without OpenGL support."); break; # endif @@ -715,18 +722,6 @@ void VG_(perform_assumed_nonblocking_syscall) ( ThreadId tid ) vg_assert(VG_(is_kerror)(res)); break; - case __NR_exit: /* syscall 1 */ - /* void _exit(int status); */ - if (VG_(clo_trace_syscalls)) - VG_(printf)("exit ( %d )\n", arg1); - VG_(message)(Vg_UserMsg, - "Warning: client exiting by calling exit(%d). Bye!", - arg1); - - KERNEL_DO_SYSCALL(tid,res); - /* Definitely should not be alive here :) */ - break; - /* !!!!!!!!!!!!!!!!!!!!! end !!!!!!!!!!!!!!!!!!!!! */ case __NR_access: /* syscall 33 */