// This program creates NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS of pthreads, // creates an lldb Debugger on each thread, creates targets, inserts two // breakpoints, runs to the first breakpoint, backtraces, runs to the second // breakpoint, backtraces, kills the inferior process, closes down the // debugger. // The main thread keeps track of which pthreads have completed and which // pthreads have completed successfully, and exits when all pthreads have // completed successfully, or our time limit has been exceeded. // This test file helps to uncover race conditions and locking mistakes // that are hit when lldb is being used to debug multiple processes // simultaneously. #include #include #include #include #include "lldb/API/LLDB.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBCommandReturnObject.h" #include "lldb/API/SBDebugger.h" #include #include #include #define NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS 10 #define DEBUG 0 #define STR1(x) #x #define STR(x) STR1(x) using namespace lldb; bool *completed_threads_array = 0; bool *successful_threads_array = 0; const char *inferior_process_name = "testprog"; bool wait_for_stop_event (SBProcess process, SBListener listener) { bool stopped = false; while (!stopped) { SBEvent event; bool waitfor_ret = listener.WaitForEvent (2, event); if (event.GetType() == SBProcess::eBroadcastBitStateChanged) { if (process.GetState() == StateType::eStateStopped || process.GetState() == StateType::eStateCrashed || process.GetState() == StateType::eStateDetached || process.GetState() == StateType::eStateExited) { stopped = true; } } } return stopped; } bool walk_stack_to_main (SBThread thread) { if (thread.IsValid() == 0) { return false; } bool found_main = false; uint32_t curr_frame = 0; const uint32_t framecount = thread.GetNumFrames(); while (!found_main && curr_frame < framecount) { SBFrame frame = thread.GetFrameAtIndex (curr_frame); if (strcmp (frame.GetFunctionName(), "main") == 0) { found_main = true; break; } curr_frame += 1; } return found_main; } void *do_one_debugger (void *in) { uint64_t threadnum = (uint64_t) in; #if defined (__APPLE__) char *threadname; asprintf (&threadname, "thread #%lld", threadnum); pthread_setname_np (threadname); free (threadname); #endif #if DEBUG == 1 printf ("#%lld: Starting debug session\n", threadnum); #endif SBDebugger debugger = lldb::SBDebugger::Create (false); if (debugger.IsValid ()) { debugger.SetAsync (true); SBTarget target = debugger.CreateTargetWithFileAndArch(inferior_process_name, STR(LLDB_HOST_ARCH)); SBCommandInterpreter command_interp = debugger.GetCommandInterpreter(); if (target.IsValid()) { SBBreakpoint bar_br = target.BreakpointCreateByName ("bar", "testprog"); if (!bar_br.IsValid()) { printf ("#%" PRIu64 ": failed to set breakpoint on bar, exiting.\n", threadnum); exit (1); } SBBreakpoint foo_br = target.BreakpointCreateByName ("foo", "testprog"); if (!foo_br.IsValid()) { printf ("#%" PRIu64 ": Failed to set breakpoint on foo()\n", threadnum); } SBLaunchInfo launch_info (NULL); SBError error; SBProcess process = target.Launch (launch_info, error); if (process.IsValid()) { SBListener listener = debugger.GetListener(); SBBroadcaster broadcaster = process.GetBroadcaster(); uint32_t rc = broadcaster.AddListener (listener, SBProcess::eBroadcastBitStateChanged); if (rc == 0) { printf ("adding listener failed\n"); exit (1); } wait_for_stop_event (process, listener); if (!walk_stack_to_main (process.GetThreadAtIndex(0))) { printf ("#%" PRIu64 ": backtrace while @ foo() failed\n", threadnum); completed_threads_array[threadnum] = true; return (void *) 1; } // On Linux the () are included. const char* hit_fn = process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetFunctionName(); if (strcmp (hit_fn, "foo") != 0 && strcmp (hit_fn, "foo()") != 0) { #if DEBUG == 1 printf ("#%" PRIu64 ": First breakpoint did not stop at foo(), instead stopped at '%s'\n", threadnum, process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetFunctionName()); #endif completed_threads_array[threadnum] = true; return (void*) 1; } process.Continue(); wait_for_stop_event (process, listener); if (process.GetState() == StateType::eStateExited) { printf ("#%" PRIu64 ": Process exited\n", threadnum); completed_threads_array[threadnum] = true; return (void *) 1; } if (!walk_stack_to_main (process.GetThreadAtIndex(0))) { printf ("#%" PRIu64 ": backtrace while @ bar() failed\n", threadnum); completed_threads_array[threadnum] = true; return (void *) 1; } hit_fn = process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetFunctionName(); if (strcmp (hit_fn, "bar") != 0 && strcmp (hit_fn, "bar()") != 0) { printf ("#%" PRIu64 ": First breakpoint did not stop at bar()\n", threadnum); completed_threads_array[threadnum] = true; return (void*) 1; } process.Kill(); wait_for_stop_event (process, listener); SBDebugger::Destroy(debugger); #if DEBUG == 1 printf ("#%" PRIu64 ": All good!\n", threadnum); #endif successful_threads_array[threadnum] = true; completed_threads_array[threadnum] = true; return (void*) 0; } else { printf("#%" PRIu64 ": process failed to launch\n", threadnum); successful_threads_array[threadnum] = false; completed_threads_array[threadnum] = true; return (void*) 0; } } else { printf ("#%" PRIu64 ": did not get valid target\n", threadnum); successful_threads_array[threadnum] = false; completed_threads_array[threadnum] = true; return (void*) 0; } } else { printf ("#%" PRIu64 ": did not get debugger\n", threadnum); successful_threads_array[threadnum] = false; completed_threads_array[threadnum] = true; return (void*) 0; } completed_threads_array[threadnum] = true; return (void*) 1; } int count_completed_threads(int num_threads) { int num_completed_threads = 0; for (int i = 0; i < num_threads; i++) if (completed_threads_array[i]) num_completed_threads++; return num_completed_threads; } int count_successful_threads(int num_threads) { int num_successful_threads = 0; for (int i = 0; i < num_threads; i++) if (successful_threads_array[i]) num_successful_threads++; return num_successful_threads; } int main (int argc, char **argv) { #if !defined(_MSC_VER) signal(SIGPIPE, SIG_IGN); #endif SBDebugger::Initialize(); completed_threads_array = (bool *) malloc (sizeof (bool) * NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); memset (completed_threads_array, 0, sizeof (bool) * NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); successful_threads_array = (bool *) malloc (sizeof (bool) * NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); memset (successful_threads_array, 0, sizeof (bool) * NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); if (argc > 1 && argv[1] != NULL) { inferior_process_name = argv[1]; } std::thread threads[NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS]; for (uint64_t i = 0; i< NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS; i++) { threads[i] = std::move(std::thread(do_one_debugger, (void*)i)); } int max_time_to_wait = 40; // 40 iterations, or 120 seconds if (getenv("ASAN_OPTIONS")) max_time_to_wait *= 4; for (int iter = 0; iter < max_time_to_wait; iter++) { std::this_thread::sleep_for(std::chrono::seconds(3)); int successful_threads = count_successful_threads(NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); int total_completed_threads = count_completed_threads(NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); if (total_completed_threads == NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS) { #if DEBUG == 1 printf ("All threads completed.\n"); printf ("%d threads completed successfully out of %d\n", successful_threads, NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); #endif SBDebugger::Terminate(); exit(0); } else { #if DEBUG == 1 printf ("%d threads completed so far (%d successfully), out of %d\n", total_completed_threads, successful_threads, NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); #endif } if (iter == max_time_to_wait) printf("reached maximum timeout but only %d threads have completed " "so far " "(%d successfully), out of %d. Exiting.\n", total_completed_threads, successful_threads, NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); } SBDebugger::Terminate(); exit (1); }