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 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/coregrind/vg_constants.h b/coregrind/vg_constants.h
index 191e498..710b12c 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 af23b40..4b36e44 100644
--- a/coregrind/vg_include.h
+++ b/coregrind/vg_include.h
@@ -437,7 +437,6 @@
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 @@
/* 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 @@
/* 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 @@
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 b98459f..1b6bba3 100644
--- a/coregrind/vg_main.c
+++ b/coregrind/vg_main.c
@@ -331,6 +331,9 @@
/* 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 @@
{
Int i;
VgSchedReturnCode src;
+ ThreadState* tst;
/* Set up our stack sanity-check words. */
for (i = 0; i < 10; i++) {
@@ -1105,17 +1109,46 @@
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)();
+ /* 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 :) */
- /* 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)();
+ 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 a585587..ea24997 100644
--- a/coregrind/vg_scheduler.c
+++ b/coregrind/vg_scheduler.c
@@ -1142,7 +1142,7 @@
/* 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 @@
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 @@
/* Actually run thread tid. */
while (True) {
+ VG_(last_run_tid) = tid;
+
/* For stats purposes only. */
VG_(num_scheduling_events_MINOR) ++;
@@ -1334,7 +1336,9 @@
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 @@
}
# endif
+ if (vg_threads[tid].m_eax == __NR_exit)
+ return VgSrc_ExitSyscall;
+
sched_do_syscall(tid);
# if 0
@@ -1413,14 +1420,6 @@
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 @@
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 5616421..a0bb4ea 100644
--- a/coregrind/vg_startup.S
+++ b/coregrind/vg_startup.S
@@ -104,40 +104,20 @@
-.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 191e498..710b12c 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 af23b40..4b36e44 100644
--- a/vg_include.h
+++ b/vg_include.h
@@ -437,7 +437,6 @@
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 @@
/* 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 @@
/* 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 @@
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 b98459f..1b6bba3 100644
--- a/vg_main.c
+++ b/vg_main.c
@@ -331,6 +331,9 @@
/* 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 @@
{
Int i;
VgSchedReturnCode src;
+ ThreadState* tst;
/* Set up our stack sanity-check words. */
for (i = 0; i < 10; i++) {
@@ -1105,17 +1109,46 @@
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)();
+ /* 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 :) */
- /* 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)();
+ 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 a585587..ea24997 100644
--- a/vg_scheduler.c
+++ b/vg_scheduler.c
@@ -1142,7 +1142,7 @@
/* 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 @@
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 @@
/* Actually run thread tid. */
while (True) {
+ VG_(last_run_tid) = tid;
+
/* For stats purposes only. */
VG_(num_scheduling_events_MINOR) ++;
@@ -1334,7 +1336,9 @@
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 @@
}
# endif
+ if (vg_threads[tid].m_eax == __NR_exit)
+ return VgSrc_ExitSyscall;
+
sched_do_syscall(tid);
# if 0
@@ -1413,14 +1420,6 @@
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 @@
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 5616421..a0bb4ea 100644
--- a/vg_startup.S
+++ b/vg_startup.S
@@ -104,40 +104,20 @@
-.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 f3b7052..ab43451 100644
--- a/vg_syscall_mem.c
+++ b/vg_syscall_mem.c
@@ -332,6 +332,10 @@
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 @@
# 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 @@
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 */