Adding C++ tests that drive LLDB from multiple threads
- test_breakpoint_callback -- filed llvm.org/pr-16000 - test_listener_resume -- resume a process from a thread waiting on SBListener - test_listener_event_description -- SBEvent description from SBListener thread - test_listener_event_process -- query process/thread/stack info from SBListener thread llvm-svn: 181819
This commit is contained in:
parent
04e5d58c8d
commit
b2132553d9
|
@ -0,0 +1,7 @@
|
|||
LEVEL = ../../make
|
||||
|
||||
CXX_SOURCES := main.cpp
|
||||
|
||||
clean: OBJECTS+=*.d.* *.d *.o *.pyc *.dSYM
|
||||
|
||||
include $(LEVEL)/Makefile.rules
|
|
@ -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()
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue