From 8c074015047cf88fea703d981ba214d895a744d3 Mon Sep 17 00:00:00 2001 From: Jason Molenda Date: Thu, 10 Jul 2014 02:17:31 +0000 Subject: [PATCH] Add a new test in api/multiple-debuggers which tries to create 50 debug sessions simultaneously to expose race conditoin/locking issues. This directory has an inferior program, testprog.cpp that has a couple of functions we can put breakpoints on. It has a driver program, multi-process-driver.cpp, which links against the LLDB solib and uses the SB APIs. It creates 50 pthreads, creates a debugger on all of them, launches a debug session of the inferior testprog, hits a couple breakpoints, walks the stack, continues, etc., and then kills the inferior and ends the debug session. A pass is if all fifty debug sessions complete successfully in the alloted time (~60 seconds). We may need to tweak this one to work correctly on different platforms/targets but I wanted to get it checked in to start. llvm-svn: 212671 --- lldb/test/api/multiple-debuggers/Makefile | 7 + .../multiple-debuggers/TestMultipleDebuggers.py | 46 ++++ .../multiple-debuggers/multi-process-driver.cpp | 265 +++++++++++++++++++++ lldb/test/api/multiple-debuggers/testprog.cpp | 12 + 4 files changed, 330 insertions(+) create mode 100644 lldb/test/api/multiple-debuggers/Makefile create mode 100644 lldb/test/api/multiple-debuggers/TestMultipleDebuggers.py create mode 100644 lldb/test/api/multiple-debuggers/multi-process-driver.cpp create mode 100644 lldb/test/api/multiple-debuggers/testprog.cpp diff --git a/lldb/test/api/multiple-debuggers/Makefile b/lldb/test/api/multiple-debuggers/Makefile new file mode 100644 index 0000000..a64bab7 --- /dev/null +++ b/lldb/test/api/multiple-debuggers/Makefile @@ -0,0 +1,7 @@ +LEVEL = ../../make + +MAKE_DSYM := NO + +CXX_SOURCES := multi-process-driver.cpp testprog.cpp + +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/api/multiple-debuggers/TestMultipleDebuggers.py b/lldb/test/api/multiple-debuggers/TestMultipleDebuggers.py new file mode 100644 index 0000000..0ea227c --- /dev/null +++ b/lldb/test/api/multiple-debuggers/TestMultipleDebuggers.py @@ -0,0 +1,46 @@ +"""Test the lldb public C++ api when doing multiple debug sessions simultaneously.""" + +import os, re, StringIO +import unittest2 +from lldbtest import * +import lldbutil +import lldb +import subprocess + +class TestMultipleSimultaneousDebuggers(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + TestBase.setUp(self) + self.lib_dir = os.environ["LLDB_LIB_DIR"] + + @skipIfi386 + def test_whatever(self): + + self.driver_exe = os.path.join(os.getcwd(), "multi-process-driver") + self.buildDriver('multi-process-driver.cpp', self.driver_exe) + self.addTearDownHook(lambda: os.remove(self.driver_exe)) + + self.inferior_exe = os.path.join(os.getcwd(), "testprog") + self.buildDriver('testprog.cpp', self.inferior_exe) + self.addTearDownHook(lambda: os.remove(self.inferior_exe)) + + env = {self.dylibPath : self.getLLDBLibraryEnvVal()} + +# check_call will raise a CalledProcessError if multi-process-driver doesn't return +# exit code 0 to indicate success. We can let this exception go - the test harness +# will recognize it as a test failure. + + if self.TraceOn(): + print "Running test %s" % self.driver_exe + check_call([self.driver_exe, self.inferior_exe], env=env) + else: + with open(os.devnull, 'w') as fnull: + check_call([self.driver_exe, self.inferior_exe], env=env, stdout=fnull, stderr=fnull) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/api/multiple-debuggers/multi-process-driver.cpp b/lldb/test/api/multiple-debuggers/multi-process-driver.cpp new file mode 100644 index 0000000..87b3fff --- /dev/null +++ b/lldb/test/api/multiple-debuggers/multi-process-driver.cpp @@ -0,0 +1,265 @@ +#include +#include + + +#ifdef __APPLE__ +#include +#include +#include +#include +#else +#include "lldb/API/LLDB.h" +#include +#include +#include +#endif + +#define NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS 50 + +#define DEBUG 0 + +using namespace lldb; + +bool *completed_threads_array = 0; +bool *successful_threads_array = 0; + +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("testprog", "x86_64"); + SBCommandInterpreter command_interp = debugger.GetCommandInterpreter(); + if (target.IsValid()) + { + SBBreakpoint bar_br = target.BreakpointCreateByName ("bar", "testprog"); + if (!bar_br.IsValid()) + { + printf ("#%lld: failed to set breakpoint on bar, exiting.\n", threadnum); + exit (1); + } + SBBreakpoint foo_br = target.BreakpointCreateByName ("foo", "testprog"); + if (!foo_br.IsValid()) + { + printf ("#%lld: 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 ("#%lld: backtrace while @ foo() failed\n", threadnum); + completed_threads_array[threadnum] = true; + return (void *) 1; + } + + if (strcmp (process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetFunctionName(), "foo") != 0) + { +#if DEBUG == 1 + printf ("#%lld: 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 ("#%lld: Process exited\n", threadnum); + completed_threads_array[threadnum] = true; + return (void *) 1; + } + + + if (!walk_stack_to_main (process.GetThreadAtIndex(0))) + { + printf ("#%lld: backtrace while @ bar() failed\n", threadnum); + completed_threads_array[threadnum] = true; + return (void *) 1; + } + + if (strcmp (process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetFunctionName(), "bar") != 0) + { + printf ("#%lld: 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 ("#%lld: All good!\n", threadnum); +#endif + successful_threads_array[threadnum] = true; + completed_threads_array[threadnum] = true; + return (void*) 0; + } + else + { + printf("#%lld: process failed to launch\n", threadnum); + successful_threads_array[threadnum] = false; + completed_threads_array[threadnum] = true; + return (void*) 0; + } + } + else + { + printf ("#%lld: did not get valid target\n", threadnum); + successful_threads_array[threadnum] = false; + completed_threads_array[threadnum] = true; + return (void*) 0; + } + } + else + { + printf ("#%lld: 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 main () +{ + 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); + + for (uint64_t i = 0; i< NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS; i++) + { + pthread_t thread; + pthread_create (&thread, NULL, do_one_debugger, (void*) i); + } + + + int max_time_to_wait = 20; // 20 iterations, or 60 seconds + int iter = 0; + while (1) + { + sleep (3); + bool all_done = true; + int successful_threads = 0; + int total_completed_threads = 0; + for (uint64_t i = 0; i < NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS; i++) + { + if (successful_threads_array[i] == true) + successful_threads++; + if (completed_threads_array[i] == true) + total_completed_threads++; + if (completed_threads_array[i] == false) + { + all_done = false; + } + } + if (all_done) + { +#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); + break; + } + } + + + SBDebugger::Terminate(); + exit (1); +} diff --git a/lldb/test/api/multiple-debuggers/testprog.cpp b/lldb/test/api/multiple-debuggers/testprog.cpp new file mode 100644 index 0000000..c9d1ea1 --- /dev/null +++ b/lldb/test/api/multiple-debuggers/testprog.cpp @@ -0,0 +1,12 @@ +int bar () +{ + return 5; +} +int foo () +{ + return bar() + 5; +} +int main () +{ + return foo(); +} -- 2.7.4