diff options
| -rw-r--r-- | lldb/test/api/multithreaded/Makefile | 7 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/TestMultithreaded.py | 73 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/common.h | 67 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/driver.cpp | 38 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/inferior.cpp | 17 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/listener_test.cpp | 74 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/lldb-headers.h | 11 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/test_breakpoint_callback.cpp | 48 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/test_listener_event_description.cpp | 64 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/test_listener_event_process_state.cpp | 68 | ||||
| -rw-r--r-- | lldb/test/api/multithreaded/test_listener_resume.cpp | 53 |
11 files changed, 520 insertions, 0 deletions
diff --git a/lldb/test/api/multithreaded/Makefile b/lldb/test/api/multithreaded/Makefile new file mode 100644 index 00000000000..7a28a047713 --- /dev/null +++ b/lldb/test/api/multithreaded/Makefile @@ -0,0 +1,7 @@ +LEVEL = ../../make + +CXX_SOURCES := main.cpp + +clean: OBJECTS+=*.d.* *.d *.o *.pyc *.dSYM + +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/api/multithreaded/TestMultithreaded.py b/lldb/test/api/multithreaded/TestMultithreaded.py new file mode 100644 index 00000000000..4f5c039d32b --- /dev/null +++ b/lldb/test/api/multithreaded/TestMultithreaded.py @@ -0,0 +1,73 @@ +"""Test the lldb public C++ api breakpoint callbacks. """ + +import os, re, StringIO +import unittest2 +from lldbtest import * +import lldbutil +import subprocess + +class SBBreakpointCallbackCase(TestBase): + + mydir = os.path.join("api", "multithreaded") + + def setUp(self): + TestBase.setUp(self) + self.lib_dir = os.environ["LLDB_LIB_DIR"] + self.inferior = 'inferior_program' + if self.getArchitecture() != "i386": + self.buildProgram('inferior.cpp', self.inferior) + self.addTearDownHook(lambda: os.remove(self.inferior)) + + @unittest2.expectedFailure # llvm.org/pr-1600: SBBreakpoint.SetCallback() does nothing + @skipIfi386 + def test_breakpoint_callback(self): + """Test the that SBBreakpoint callback is invoked when a breakpoint is hit. """ + self.build_and_test('driver.cpp test_breakpoint_callback.cpp', + 'test_breakpoint_callback') + + @skipIfi386 + def test_sb_api_listener_event_description(self): + """ Test the description of an SBListener breakpoint event is valid.""" + self.build_and_test('driver.cpp listener_test.cpp test_listener_event_description.cpp', + 'test_listener_event_description') + pass + + @skipIfi386 + def test_sb_api_listener_event_process_state(self): + """ Test that a registered SBListener receives events when a process + changes state. + """ + self.build_and_test('driver.cpp listener_test.cpp test_listener_event_process_state.cpp', + 'test_listener_event_process_state') + pass + + + @skipIfi386 + def test_sb_api_listener_resume(self): + """ Test that a process can be resumed from a non-main thread. """ + self.build_and_test('driver.cpp listener_test.cpp test_listener_resume.cpp', + 'test_listener_resume') + pass + + def build_and_test(self, sources, test_name, args = None): + """ Build LLDB test from sources, and run expecting 0 exit code """ + self.buildDriver(sources, test_name) + self.addTearDownHook(lambda: os.remove(test_name)) + + exe = [os.path.join(os.getcwd(), test_name), self.inferior] + + if self.TraceOn(): + print "Running test %s" % " ".join(exe) + + check_call(exe, env={self.dylibPath : self.getLLDBLibraryEnvVal()}) + + + + def build_program(self, sources, program): + return self.buildDriver(sources, program) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/api/multithreaded/common.h b/lldb/test/api/multithreaded/common.h new file mode 100644 index 00000000000..317247e2018 --- /dev/null +++ b/lldb/test/api/multithreaded/common.h @@ -0,0 +1,67 @@ +#ifndef LLDB_TEST_API_COMMON_H +#define LLDB_TEST_API_COMMON_H + +#include <condition_variable> +#include <chrono> +#include <exception> +#include <iostream> +#include <mutex> +#include <string> +#include <queue> + +#include <unistd.h> + +/// Simple exception class with a message +struct Exception : public std::exception +{ + std::string s; + Exception(std::string ss) : s(ss) {} + const char* what() const throw() { return s.c_str(); } +}; + +// Synchronized data structure for listener to send events through +template<typename T> +class multithreaded_queue { + std::condition_variable m_condition; + std::mutex m_mutex; + std::queue<T> m_data; + bool m_notified; + +public: + + void push(T e) { + std::lock_guard<std::mutex> lock(m_mutex); + m_data.push(e); + m_notified = true; + m_condition.notify_all(); + } + + T pop(int timeout_seconds, bool &success) { + int count = 0; + while (count < timeout_seconds) { + std::unique_lock<std::mutex> lock(m_mutex); + if (!m_data.empty()) { + m_notified = false; + T ret = m_data.front(); + m_data.pop(); + success = true; + return ret; + } else if (!m_notified) + m_condition.wait_for(lock, std::chrono::seconds(1)); + count ++; + } + success = false; + return T(); + } +}; + +/// Allocates a char buffer with the current working directory on Linux/Darwin +inline char* get_working_dir() { +#ifdef __APPLE__ + return getwd(0); +#else + return get_current_dir_name(); +#endif +} + +#endif // LLDB_TEST_API_COMMON_H diff --git a/lldb/test/api/multithreaded/driver.cpp b/lldb/test/api/multithreaded/driver.cpp new file mode 100644 index 00000000000..fa0c48e0ecf --- /dev/null +++ b/lldb/test/api/multithreaded/driver.cpp @@ -0,0 +1,38 @@ + +/// LLDB C API Test Driver + +#include <algorithm> +#include <iostream> +#include <iterator> +#include <string> +#include <vector> + +#include "lldb-headers.h" + +#include "common.h" + +using namespace std; +using namespace lldb; + +void test(SBDebugger &dbg, std::vector<string> args); + +int main(int argc, char** argv) { + int code = 0; + + SBDebugger::Initialize(); + SBDebugger dbg = SBDebugger::Create(); + + try { + if (!dbg.IsValid()) + throw Exception("invalid debugger"); + vector<string> args(argv + 1, argv + argc); + + test(dbg, args); + } catch (Exception &e) { + cout << "ERROR: " << e.what() << endl; + code = 1; + } + + SBDebugger::Destroy(dbg); + return code; +} diff --git a/lldb/test/api/multithreaded/inferior.cpp b/lldb/test/api/multithreaded/inferior.cpp new file mode 100644 index 00000000000..9dbb289f98b --- /dev/null +++ b/lldb/test/api/multithreaded/inferior.cpp @@ -0,0 +1,17 @@ + +#include <iostream> + +using namespace std; + +int next() { + static int i = 0; + cout << "incrementing " << i << endl; + return ++i; +} + +int main() { + int i = 0; + while (i < 5) + i = next(); + return 0; +} diff --git a/lldb/test/api/multithreaded/listener_test.cpp b/lldb/test/api/multithreaded/listener_test.cpp new file mode 100644 index 00000000000..6697428bd1e --- /dev/null +++ b/lldb/test/api/multithreaded/listener_test.cpp @@ -0,0 +1,74 @@ +// LLDB test snippet that registers a listener with a process that hits +// a breakpoint. + +#include <atomic> +#include <iostream> +#include <string> +#include <thread> +#include <vector> + +#include "lldb-headers.h" +#include "common.h" + +using namespace lldb; +using namespace std; + +void listener_func(); +void check_listener(SBDebugger &dbg); + +// Listener thread and related variables +atomic<bool> g_done; +SBListener g_listener("test-listener"); +thread g_listener_thread; + +void shutdown_listener() { + g_done.store(true); + if (g_listener_thread.joinable()) + g_listener_thread.join(); +} + +void test(SBDebugger &dbg, std::vector<string> args) { + try { + g_done.store(false); + SBTarget target = dbg.CreateTarget(args.at(0).c_str()); + if (!target.IsValid()) throw Exception("invalid target"); + + SBBreakpoint breakpoint = target.BreakpointCreateByName("next"); + if (!breakpoint.IsValid()) throw Exception("invalid breakpoint"); + + std::unique_ptr<char> working_dir(get_working_dir()); + + SBError error; + SBProcess process = target.Launch(g_listener, + 0, 0, 0, 0, 0, + working_dir.get(), + 0, + false, + error); + if (!error.Success()) + throw Exception("Error launching process."); + + /* FIXME: the approach below deadlocks + SBProcess process = target.LaunchSimple(0, 0, working_dir.get()); + + // get debugger listener (which is attached to process by default) + g_listener = dbg.GetListener(); + */ + + // FIXME: because a listener is attached to the process at launch-time, + // registering the listener below results in two listeners being attached, + // which is not supported by LLDB. + // register listener + // process.GetBroadcaster().AddListener(g_listener, + // SBProcess::eBroadcastBitStateChanged); + + // start listener thread + g_listener_thread = thread(listener_func); + check_listener(dbg); + + } catch (Exception &e) { + shutdown_listener(); + throw e; + } + shutdown_listener(); +} diff --git a/lldb/test/api/multithreaded/lldb-headers.h b/lldb/test/api/multithreaded/lldb-headers.h new file mode 100644 index 00000000000..da0914b3b07 --- /dev/null +++ b/lldb/test/api/multithreaded/lldb-headers.h @@ -0,0 +1,11 @@ + +#ifndef LLDB_HEADERS_H +#define LLDB_HEADERS_H + +#ifdef __APPLE__ +#include <LLDB/LLDB.h> +#else +#include "lldb/API/LLDB.h" +#endif + +#endif // LLDB_HEADERS_H diff --git a/lldb/test/api/multithreaded/test_breakpoint_callback.cpp b/lldb/test/api/multithreaded/test_breakpoint_callback.cpp new file mode 100644 index 00000000000..237e87212c5 --- /dev/null +++ b/lldb/test/api/multithreaded/test_breakpoint_callback.cpp @@ -0,0 +1,48 @@ + +// LLDB C++ API Test: verify that the function registered with +// SBBreakpoint.SetCallback() is invoked when a breakpoint is hit. + +#include <mutex> +#include <iostream> +#include <vector> +#include <string> + +#include "lldb-headers.h" + +#include "common.h" + +using namespace std; +using namespace lldb; + +mutex g_mutex; +condition_variable g_condition; +int g_breakpoint_hit_count = 0; + +bool BPCallback (void *baton, + SBProcess &process, + SBThread &thread, + SBBreakpointLocation &location) { + lock_guard<mutex> lock(g_mutex); + g_breakpoint_hit_count += 1; + g_condition.notify_all(); + return true; +} + +void test(SBDebugger &dbg, vector<string> args) { + SBTarget target = dbg.CreateTarget(args.at(0).c_str()); + if (!target.IsValid()) throw Exception("invalid target"); + + SBBreakpoint breakpoint = target.BreakpointCreateByName("next"); + if (!breakpoint.IsValid()) throw Exception("invalid breakpoint"); + breakpoint.SetCallback(BPCallback, 0); + + std::unique_ptr<char> working_dir = get_working_dir(); + SBProcess process = target.LaunchSimple(0, 0, working_dir.get()); + + { + unique_lock<mutex> lock(g_mutex); + g_condition.wait_for(lock, chrono::seconds(5)); + if (g_breakpoint_hit_count != 1) + throw Exception("Breakpoint hit count expected to be 1"); + } +} diff --git a/lldb/test/api/multithreaded/test_listener_event_description.cpp b/lldb/test/api/multithreaded/test_listener_event_description.cpp new file mode 100644 index 00000000000..b0a10c3999c --- /dev/null +++ b/lldb/test/api/multithreaded/test_listener_event_description.cpp @@ -0,0 +1,64 @@ + +// LLDB C++ API Test: verify the event description that is received by an +// SBListener object registered with a process with a breakpoint. + +#include <atomic> +#include <array> +#include <iostream> +#include <string> +#include <thread> + +#include "lldb-headers.h" + +#include "common.h" + +using namespace lldb; +using namespace std; + +// listener thread control +extern atomic<bool> g_done; + +multithreaded_queue<string> g_event_descriptions; + +extern SBListener g_listener; + +void listener_func() { + while (!g_done) { + SBEvent event; + bool got_event = g_listener.WaitForEvent(1, event); + if (got_event) { + if (!event.IsValid()) + throw Exception("event is not valid in listener thread"); + + SBStream description; + event.GetDescription(description); + string str(description.GetData()); + g_event_descriptions.push(str); + } + } +} + +void check_listener(SBDebugger &dbg) { + array<string, 2> expected_states = {"running", "stopped"}; + for(string & state : expected_states) { + bool got_description = false; + string desc = g_event_descriptions.pop(5, got_description); + + if (!got_description) + throw Exception("Did not get expected event description"); + + + if (desc.find("state-changed") == desc.npos) + throw Exception("Event description incorrect: missing 'state-changed'"); + + string state_search_str = "state = " + state; + if (desc.find(state_search_str) == desc.npos) + throw Exception("Event description incorrect: expected state " + + state + + " but desc was " + + desc); + + if (desc.find("pid = ") == desc.npos) + throw Exception("Event description incorrect: missing process pid"); + } +} diff --git a/lldb/test/api/multithreaded/test_listener_event_process_state.cpp b/lldb/test/api/multithreaded/test_listener_event_process_state.cpp new file mode 100644 index 00000000000..943a96938f9 --- /dev/null +++ b/lldb/test/api/multithreaded/test_listener_event_process_state.cpp @@ -0,0 +1,68 @@ + +// LLDB C++ API Test: verify the event description as obtained by calling +// SBEvent::GetCStringFromEvent that is received by an +// SBListener object registered with a process with a breakpoint. + +#include <atomic> +#include <iostream> +#include <string> +#include <thread> + +#include "lldb-headers.h" + +#include "common.h" + +using namespace lldb; +using namespace std; + +// listener thread control +extern atomic<bool> g_done; + +multithreaded_queue<string> g_thread_descriptions; +multithreaded_queue<string> g_frame_functions; + +extern SBListener g_listener; + +void listener_func() { + while (!g_done) { + SBEvent event; + bool got_event = g_listener.WaitForEvent(1, event); + if (got_event) { + if (!event.IsValid()) + throw Exception("event is not valid in listener thread"); + + // send process description + SBProcess process = SBProcess::GetProcessFromEvent(event); + SBStream description; + + for (int i = 0; i < process.GetNumThreads(); ++i) { + // send each thread description + description.Clear(); + SBThread thread = process.GetThreadAtIndex(i); + thread.GetDescription(description); + g_thread_descriptions.push(description.GetData()); + + // send each frame function name + uint32_t num_frames = thread.GetNumFrames(); + for(int j = 0; j < num_frames; ++j) { + const char* function_name = thread.GetFrameAtIndex(j).GetFunction().GetName(); + if (function_name) + g_frame_functions.push(function_name); + } + } + } + } +} + +void check_listener(SBDebugger &dbg) { + // check thread description + bool got_description = false; + string desc = g_thread_descriptions.pop(5, got_description); + if (!got_description) + throw Exception("Expected at least one thread description string"); + + // check at least one frame has a function name + desc = g_frame_functions.pop(5, got_description); + if (!got_description) + throw Exception("Expected at least one frame function name string"); +} diff --git a/lldb/test/api/multithreaded/test_listener_resume.cpp b/lldb/test/api/multithreaded/test_listener_resume.cpp new file mode 100644 index 00000000000..8cf786b1160 --- /dev/null +++ b/lldb/test/api/multithreaded/test_listener_resume.cpp @@ -0,0 +1,53 @@ + +// LLDB C++ API Test: verify the event description as obtained by calling +// SBEvent::GetCStringFromEvent that is received by an +// SBListener object registered with a process with a breakpoint. + +#include <atomic> +#include <iostream> +#include <string> +#include <thread> + +#include "lldb-headers.h" + +#include "common.h" + +using namespace lldb; +using namespace std; + +// listener thread control +extern atomic<bool> g_done; + +// used by listener thread to communicate a successful process continue command +// back to the checking thread. + +multithreaded_queue<bool> g_process_started; + +extern SBListener g_listener; + +void listener_func() { + while (!g_done) { + SBEvent event; + bool got_event = g_listener.WaitForEvent(1, event); + if (got_event) { + if (!event.IsValid()) + throw Exception("event is not valid in listener thread"); + + SBProcess process = SBProcess::GetProcessFromEvent(event); + if (process.GetState() == eStateStopped) { + SBError error = process.Continue(); + if (!error.Success()) + throw Exception(string("Cannot continue process from listener thread: ") + + error.GetCString()); + g_process_started.push(true); + } + } + } +} + +void check_listener(SBDebugger &dbg) { + bool got_message = false; + while (!got_message) + g_process_started.pop(5, got_message); + g_done = true; +} |

