| // 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 <inttypes.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include "lldb/API/LLDB.h" | |
| #include "lldb/API/SBCommandInterpreter.h" | |
| #include "lldb/API/SBCommandReturnObject.h" | |
| #include "lldb/API/SBDebugger.h" | |
| #include <chrono> | |
| #include <csignal> | |
| #include <thread> | |
| #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); | |
| } | |
| // We do not call SBDebugger::Terminate() here because it will destroy | |
| // data that might be being used by threads that are still running. Which | |
| // would change the timeout into an unrelated crash. | |
| // _exit instead of exit, to skip more things that could cause a crash. | |
| _exit(1); | |
| } |