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