diff --git a/src/main.c b/src/main.c index b36b4f1..dc5aae3 100644 --- a/src/main.c +++ b/src/main.c @@ -35,6 +35,7 @@ #include "runtime/forkipc.h" #include "runtime/proctitle.h" +#include "runtime/thread.h" #include "syscall/fuse.h" #include "syscall/path.h" @@ -547,9 +548,22 @@ int main(int argc, char **argv) */ int exit_code = vcpu_run_loop(vcpu, vexit, &g, verbose, timeout_sec); - /* Tear down debugger state before freeing guest/vCPU resources. */ + /* Tear down debugger state before joining workers: a worker parked in + * gdb_stub_handle_stop() stays active (not deactivated) until this + * broadcasts resume_cond, so joining first would just time out and + * detach it while it is still paused. */ gdb_stub_shutdown(); + /* Wait for worker vCPU threads to stop before tearing down guest memory. + * The main thread leaves the run loop as soon as it observes the + * exit_group flag, but sibling vCPU threads may still be mid-iteration in + * their own run loops (e.g. touching shim_globals). cleanup_main_resources + * unmaps the guest slab via guest_destroy, so a still-running worker would + * fault on freed guest memory and crash the host with SIGSEGV, masking the + * real exit code. thread_join_workers() is a no-op once the workers have + * already wound down (the common single-threaded case). */ + thread_join_workers(); + /* Diagnostic counter dump runs before guest_destroy so the shim_data * mapping is still valid. ELFUSE_SHIM_STATS is the gate; an unset variable * produces no output.