forked from OSchip/llvm-project
New framework for lldb client-server communication tests.
Summary: This is a new C++ test framework based on Google Test, and one working example test. The intention is to replace the existing tests in packages/Python/lldbsuite/test/tools/lldb-server/ with this suite and use this framework for all new client server tests. Reviewers: labath, beanz Reviewed By: labath, beanz Subscribers: beanz, emaste, zturner, tberghammer, takuto.ikuta, krytarowski, mgorny, lldb-commits Differential Revision: https://reviews.llvm.org/D32930 Patch by Jason Majors <jmajors@google.com> llvm-svn: 304793
This commit is contained in:
parent
aaeada6c75
commit
015f17d3cc
|
@ -68,9 +68,10 @@ add_subdirectory(Signals)
|
|||
add_subdirectory(Symbol)
|
||||
add_subdirectory(SymbolFile)
|
||||
add_subdirectory(Target)
|
||||
add_subdirectory(tools)
|
||||
add_subdirectory(UnwindAssembly)
|
||||
add_subdirectory(Utility)
|
||||
|
||||
if(LLDB_CAN_USE_DEBUGSERVER)
|
||||
add_subdirectory(debugserver)
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
if(CMAKE_SYSTEM_NAME MATCHES "Android|Linux|NetBSD")
|
||||
add_subdirectory(lldb-server)
|
||||
endif()
|
|
@ -0,0 +1,13 @@
|
|||
function(add_lldb_test_executable test_name)
|
||||
set(EXCLUDE_FROM_ALL ON)
|
||||
add_llvm_executable(${test_name} NO_INSTALL_RPATH ${ARGN})
|
||||
set(outdir ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR})
|
||||
set_output_directory(${test_name} BINARY_DIR ${outdir} LIBRARY_DIR ${outdir})
|
||||
endfunction()
|
||||
|
||||
add_lldb_test_executable(thread_inferior inferior/thread_inferior.cpp)
|
||||
|
||||
add_definitions(-DLLDB_SERVER="$<TARGET_FILE:lldb-server>")
|
||||
add_definitions(-DTHREAD_INFERIOR="${CMAKE_CURRENT_BINARY_DIR}/thread_inferior")
|
||||
add_subdirectory(tests)
|
||||
add_dependencies(LLDBServerTests thread_inferior)
|
|
@ -0,0 +1,41 @@
|
|||
//===-- thread_inferior.cpp -------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int thread_count = 2;
|
||||
if (argc > 1) {
|
||||
thread_count = std::stoi(argv[1], nullptr, 10);
|
||||
}
|
||||
|
||||
std::atomic<bool> delay(true);
|
||||
std::vector<std::thread> threads;
|
||||
for (int i = 0; i < thread_count; i++) {
|
||||
threads.push_back(std::thread([&delay] {
|
||||
while (delay.load())
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}));
|
||||
}
|
||||
|
||||
// Cause a break.
|
||||
volatile char *p = NULL;
|
||||
*p = 'a';
|
||||
|
||||
delay.store(false);
|
||||
for (std::thread& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
add_lldb_unittest(LLDBServerTests
|
||||
TestClient.cpp
|
||||
MessageObjects.cpp
|
||||
ThreadIdsInJstopinfoTest.cpp
|
||||
|
||||
LINK_LIBS
|
||||
lldbHost
|
||||
lldbCore
|
||||
lldbInterpreter
|
||||
lldbTarget
|
||||
lldbPluginPlatformLinux
|
||||
lldbPluginProcessGDBRemote
|
||||
LINK_COMPONENTS
|
||||
Support
|
||||
)
|
|
@ -0,0 +1,207 @@
|
|||
//===-- MessageObjects.cpp --------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "MessageObjects.h"
|
||||
#include "lldb/Core/StructuredData.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace lldb_private;
|
||||
using namespace llvm;
|
||||
using namespace llvm::support;
|
||||
namespace llgs_tests {
|
||||
|
||||
Expected<ProcessInfo> ProcessInfo::Create(StringRef response) {
|
||||
ProcessInfo process_info;
|
||||
auto elements_or_error = SplitPairList("ProcessInfo", response);
|
||||
if (!elements_or_error)
|
||||
return elements_or_error.takeError();
|
||||
|
||||
auto &elements = *elements_or_error;
|
||||
if (elements["pid"].getAsInteger(16, process_info.m_pid))
|
||||
return make_parsing_error("ProcessInfo: pid");
|
||||
if (elements["parent-pid"].getAsInteger(16, process_info.m_parent_pid))
|
||||
return make_parsing_error("ProcessInfo: parent-pid");
|
||||
if (elements["real-uid"].getAsInteger(16, process_info.m_real_uid))
|
||||
return make_parsing_error("ProcessInfo: real-uid");
|
||||
if (elements["real-gid"].getAsInteger(16, process_info.m_real_gid))
|
||||
return make_parsing_error("ProcessInfo: real-uid");
|
||||
if (elements["effective-uid"].getAsInteger(16, process_info.m_effective_uid))
|
||||
return make_parsing_error("ProcessInfo: effective-uid");
|
||||
if (elements["effective-gid"].getAsInteger(16, process_info.m_effective_gid))
|
||||
return make_parsing_error("ProcessInfo: effective-gid");
|
||||
if (elements["ptrsize"].getAsInteger(10, process_info.m_ptrsize))
|
||||
return make_parsing_error("ProcessInfo: ptrsize");
|
||||
|
||||
process_info.m_triple = fromHex(elements["triple"]);
|
||||
StringRef endian_str = elements["endian"];
|
||||
if (endian_str == "little")
|
||||
process_info.m_endian = support::little;
|
||||
else if (endian_str == "big")
|
||||
process_info.m_endian = support::big;
|
||||
else
|
||||
return make_parsing_error("ProcessInfo: endian");
|
||||
|
||||
return process_info;
|
||||
}
|
||||
|
||||
lldb::pid_t ProcessInfo::GetPid() const { return m_pid; }
|
||||
|
||||
endianness ProcessInfo::GetEndian() const { return m_endian; }
|
||||
|
||||
//====== ThreadInfo ============================================================
|
||||
ThreadInfo::ThreadInfo(StringRef name, StringRef reason,
|
||||
const RegisterMap ®isters, unsigned int signal)
|
||||
: m_name(name.str()), m_reason(reason.str()), m_registers(registers),
|
||||
m_signal(signal) {}
|
||||
|
||||
StringRef ThreadInfo::ReadRegister(unsigned int register_id) const {
|
||||
return m_registers.lookup(register_id);
|
||||
}
|
||||
|
||||
bool ThreadInfo::ReadRegisterAsUint64(unsigned int register_id,
|
||||
uint64_t &value) const {
|
||||
StringRef value_str(m_registers.lookup(register_id));
|
||||
if (value_str.getAsInteger(16, value)) {
|
||||
GTEST_LOG_(ERROR)
|
||||
<< formatv("ThreadInfo: Unable to parse register value at {0}.",
|
||||
register_id)
|
||||
.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
sys::swapByteOrder(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
//====== JThreadsInfo ==========================================================
|
||||
Expected<JThreadsInfo> JThreadsInfo::Create(StringRef response,
|
||||
endianness endian) {
|
||||
JThreadsInfo jthreads_info;
|
||||
|
||||
StructuredData::ObjectSP json = StructuredData::ParseJSON(response);
|
||||
StructuredData::Array *array = json->GetAsArray();
|
||||
if (!array)
|
||||
return make_parsing_error("JThreadsInfo: JSON array");
|
||||
|
||||
for (size_t i = 0; i < array->GetSize(); i++) {
|
||||
StructuredData::Dictionary *thread_info;
|
||||
array->GetItemAtIndexAsDictionary(i, thread_info);
|
||||
if (!thread_info)
|
||||
return make_parsing_error("JThreadsInfo: JSON obj at {0}", i);
|
||||
|
||||
StringRef name, reason;
|
||||
thread_info->GetValueForKeyAsString("name", name);
|
||||
thread_info->GetValueForKeyAsString("reason", reason);
|
||||
uint64_t signal;
|
||||
thread_info->GetValueForKeyAsInteger("signal", signal);
|
||||
uint64_t tid;
|
||||
thread_info->GetValueForKeyAsInteger("tid", tid);
|
||||
|
||||
StructuredData::Dictionary *register_dict;
|
||||
thread_info->GetValueForKeyAsDictionary("registers", register_dict);
|
||||
if (!register_dict)
|
||||
return make_parsing_error("JThreadsInfo: registers JSON obj");
|
||||
|
||||
RegisterMap registers;
|
||||
|
||||
auto keys_obj = register_dict->GetKeys();
|
||||
auto keys = keys_obj->GetAsArray();
|
||||
for (size_t i = 0; i < keys->GetSize(); i++) {
|
||||
StringRef key_str, value_str;
|
||||
keys->GetItemAtIndexAsString(i, key_str);
|
||||
register_dict->GetValueForKeyAsString(key_str, value_str);
|
||||
unsigned int register_id;
|
||||
if (key_str.getAsInteger(10, register_id))
|
||||
return make_parsing_error("JThreadsInfo: register key[{0}]", i);
|
||||
|
||||
registers[register_id] = value_str.str();
|
||||
}
|
||||
|
||||
jthreads_info.m_thread_infos[tid] =
|
||||
ThreadInfo(name, reason, registers, signal);
|
||||
}
|
||||
|
||||
return jthreads_info;
|
||||
}
|
||||
|
||||
const ThreadInfoMap &JThreadsInfo::GetThreadInfos() const {
|
||||
return m_thread_infos;
|
||||
}
|
||||
|
||||
//====== StopReply =============================================================
|
||||
const U64Map &StopReply::GetThreadPcs() const { return m_thread_pcs; }
|
||||
|
||||
Expected<StopReply> StopReply::Create(StringRef response,
|
||||
llvm::support::endianness endian) {
|
||||
StopReply stop_reply;
|
||||
|
||||
auto elements_or_error = SplitPairList("StopReply", response);
|
||||
if (auto split_error = elements_or_error.takeError()) {
|
||||
return std::move(split_error);
|
||||
}
|
||||
|
||||
auto elements = *elements_or_error;
|
||||
stop_reply.m_name = elements["name"];
|
||||
stop_reply.m_reason = elements["reason"];
|
||||
|
||||
SmallVector<StringRef, 20> threads;
|
||||
SmallVector<StringRef, 20> pcs;
|
||||
elements["threads"].split(threads, ',');
|
||||
elements["thread-pcs"].split(pcs, ',');
|
||||
if (threads.size() != pcs.size())
|
||||
return make_parsing_error("StopReply: thread/PC count mismatch");
|
||||
|
||||
for (size_t i = 0; i < threads.size(); i++) {
|
||||
lldb::tid_t thread_id;
|
||||
uint64_t pc;
|
||||
if (threads[i].getAsInteger(16, thread_id))
|
||||
return make_parsing_error("StopReply: thread ID at [{0}].", i);
|
||||
if (pcs[i].getAsInteger(16, pc))
|
||||
return make_parsing_error("StopReply: thread PC at [{0}].", i);
|
||||
|
||||
stop_reply.m_thread_pcs[thread_id] = pc;
|
||||
}
|
||||
|
||||
for (auto i = elements.begin(); i != elements.end(); i++) {
|
||||
StringRef key = i->getKey();
|
||||
StringRef val = i->getValue();
|
||||
if (key.size() >= 9 && key[0] == 'T' && key.substr(3, 6) == "thread") {
|
||||
if (val.getAsInteger(16, stop_reply.m_thread))
|
||||
return make_parsing_error("StopReply: thread id");
|
||||
if (key.substr(1, 2).getAsInteger(16, stop_reply.m_signal))
|
||||
return make_parsing_error("StopReply: stop signal");
|
||||
} else if (key.size() == 2) {
|
||||
unsigned int reg;
|
||||
if (!key.getAsInteger(16, reg)) {
|
||||
stop_reply.m_registers[reg] = val.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stop_reply;
|
||||
}
|
||||
|
||||
//====== Globals ===============================================================
|
||||
Expected<StringMap<StringRef>> SplitPairList(StringRef caller, StringRef str) {
|
||||
SmallVector<StringRef, 20> elements;
|
||||
str.split(elements, ';');
|
||||
|
||||
StringMap<StringRef> pairs;
|
||||
for (StringRef s : elements) {
|
||||
std::pair<StringRef, StringRef> pair = s.split(':');
|
||||
if (pairs.count(pair.first))
|
||||
return make_parsing_error("{0}: Duplicate Key: {1}", caller, pair.first);
|
||||
|
||||
pairs.insert(s.split(':'));
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
} // namespace llgs_tests
|
|
@ -0,0 +1,102 @@
|
|||
//===-- MessageObjects.h ----------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "lldb/lldb-types.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/Support/Endian.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include <string>
|
||||
|
||||
namespace llgs_tests {
|
||||
class ThreadInfo;
|
||||
typedef llvm::DenseMap<uint64_t, ThreadInfo> ThreadInfoMap;
|
||||
typedef llvm::DenseMap<uint64_t, uint64_t> U64Map;
|
||||
typedef llvm::DenseMap<unsigned int, std::string> RegisterMap;
|
||||
|
||||
class ProcessInfo {
|
||||
public:
|
||||
static llvm::Expected<ProcessInfo> Create(llvm::StringRef response);
|
||||
lldb::pid_t GetPid() const;
|
||||
llvm::support::endianness GetEndian() const;
|
||||
|
||||
private:
|
||||
ProcessInfo() = default;
|
||||
lldb::pid_t m_pid;
|
||||
lldb::pid_t m_parent_pid;
|
||||
uint32_t m_real_uid;
|
||||
uint32_t m_real_gid;
|
||||
uint32_t m_effective_uid;
|
||||
uint32_t m_effective_gid;
|
||||
std::string m_triple;
|
||||
llvm::SmallString<16> m_ostype;
|
||||
llvm::support::endianness m_endian;
|
||||
unsigned int m_ptrsize;
|
||||
};
|
||||
|
||||
class ThreadInfo {
|
||||
public:
|
||||
ThreadInfo() = default;
|
||||
ThreadInfo(llvm::StringRef name, llvm::StringRef reason,
|
||||
const RegisterMap ®isters, unsigned int signal);
|
||||
|
||||
llvm::StringRef ReadRegister(unsigned int register_id) const;
|
||||
bool ReadRegisterAsUint64(unsigned int register_id, uint64_t &value) const;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::string m_reason;
|
||||
RegisterMap m_registers;
|
||||
unsigned int m_signal;
|
||||
};
|
||||
|
||||
class JThreadsInfo {
|
||||
public:
|
||||
static llvm::Expected<JThreadsInfo> Create(llvm::StringRef response,
|
||||
llvm::support::endianness endian);
|
||||
|
||||
const ThreadInfoMap &GetThreadInfos() const;
|
||||
|
||||
private:
|
||||
JThreadsInfo() = default;
|
||||
ThreadInfoMap m_thread_infos;
|
||||
};
|
||||
|
||||
class StopReply {
|
||||
public:
|
||||
static llvm::Expected<StopReply> Create(llvm::StringRef response,
|
||||
llvm::support::endianness endian);
|
||||
const U64Map &GetThreadPcs() const;
|
||||
|
||||
private:
|
||||
StopReply() = default;
|
||||
void ParseResponse(llvm::StringRef response,
|
||||
llvm::support::endianness endian);
|
||||
unsigned int m_signal;
|
||||
lldb::tid_t m_thread;
|
||||
std::string m_name;
|
||||
U64Map m_thread_pcs;
|
||||
RegisterMap m_registers;
|
||||
std::string m_reason;
|
||||
};
|
||||
|
||||
// Common functions for parsing packet data.
|
||||
llvm::Expected<llvm::StringMap<llvm::StringRef>>
|
||||
SplitPairList(llvm::StringRef caller, llvm::StringRef s);
|
||||
|
||||
template <typename... Args>
|
||||
llvm::Error make_parsing_error(llvm::StringRef format, Args &&... args) {
|
||||
std::string error =
|
||||
"Unable to parse " +
|
||||
llvm::formatv(format.data(), std::forward<Args>(args)...).str();
|
||||
return llvm::make_error<llvm::StringError>(error,
|
||||
llvm::inconvertibleErrorCode());
|
||||
}
|
||||
} // namespace llgs_tests
|
|
@ -0,0 +1,287 @@
|
|||
//===-- TestClient.cpp ------------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TestClient.h"
|
||||
#include "lldb/Core/ArchSpec.h"
|
||||
#include "lldb/Host/HostInfo.h"
|
||||
#include "lldb/Host/common/TCPSocket.h"
|
||||
#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h"
|
||||
#include "lldb/Host/posix/ProcessLauncherPosix.h"
|
||||
#include "lldb/Interpreter/Args.h"
|
||||
#include "lldb/Target/ProcessLaunchInfo.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <cstdlib>
|
||||
#include <future>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
using namespace llvm;
|
||||
|
||||
namespace llgs_tests {
|
||||
void TestClient::Initialize() { HostInfo::Initialize(); }
|
||||
|
||||
TestClient::TestClient(const std::string &test_name,
|
||||
const std::string &test_case_name)
|
||||
: m_test_name(test_name), m_test_case_name(test_case_name),
|
||||
m_pc_register(UINT_MAX) {}
|
||||
|
||||
TestClient::~TestClient() {}
|
||||
|
||||
bool TestClient::StartDebugger() {
|
||||
const ArchSpec &arch_spec = HostInfo::GetArchitecture();
|
||||
Args args;
|
||||
args.AppendArgument(LLDB_SERVER);
|
||||
args.AppendArgument("gdbserver");
|
||||
args.AppendArgument("--log-channels=gdb-remote packets");
|
||||
args.AppendArgument("--reverse-connect");
|
||||
std::string log_file_name = GenerateLogFileName(arch_spec);
|
||||
if (log_file_name.size()) {
|
||||
args.AppendArgument("--log-file=" + log_file_name);
|
||||
}
|
||||
|
||||
Status error;
|
||||
TCPSocket listen_socket(true, false);
|
||||
error = listen_socket.Listen("127.0.0.1:0", 5);
|
||||
if (error.Fail()) {
|
||||
GTEST_LOG_(ERROR) << "Unable to open listen socket.";
|
||||
return false;
|
||||
}
|
||||
|
||||
char connect_remote_address[64];
|
||||
snprintf(connect_remote_address, sizeof(connect_remote_address),
|
||||
"localhost:%u", listen_socket.GetLocalPortNumber());
|
||||
|
||||
args.AppendArgument(connect_remote_address);
|
||||
|
||||
m_server_process_info.SetArchitecture(arch_spec);
|
||||
m_server_process_info.SetArguments(args, true);
|
||||
Status status = Host::LaunchProcess(m_server_process_info);
|
||||
if (status.Fail()) {
|
||||
GTEST_LOG_(ERROR)
|
||||
<< formatv("Failure to launch lldb server: {0}.", status).str();
|
||||
return false;
|
||||
}
|
||||
|
||||
char connect_remote_uri[64];
|
||||
snprintf(connect_remote_uri, sizeof(connect_remote_uri), "connect://%s",
|
||||
connect_remote_address);
|
||||
Socket *accept_socket;
|
||||
listen_socket.Accept(accept_socket);
|
||||
SetConnection(new ConnectionFileDescriptor(accept_socket));
|
||||
|
||||
SendAck(); // Send this as a handshake.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestClient::StopDebugger() {
|
||||
std::string response;
|
||||
return SendMessage("k", response, PacketResult::ErrorDisconnected);
|
||||
}
|
||||
|
||||
bool TestClient::SetInferior(llvm::ArrayRef<std::string> inferior_args) {
|
||||
std::stringstream command;
|
||||
command << "A";
|
||||
for (size_t i = 0; i < inferior_args.size(); i++) {
|
||||
if (i > 0)
|
||||
command << ',';
|
||||
std::string hex_encoded = toHex(inferior_args[i]);
|
||||
command << hex_encoded.size() << ',' << i << ',' << hex_encoded;
|
||||
}
|
||||
|
||||
if (!SendMessage(command.str()))
|
||||
return false;
|
||||
if (!SendMessage("qLaunchSuccess"))
|
||||
return false;
|
||||
std::string response;
|
||||
if (!SendMessage("qProcessInfo", response))
|
||||
return false;
|
||||
auto create_or_error = ProcessInfo::Create(response);
|
||||
if (auto create_error = create_or_error.takeError()) {
|
||||
GTEST_LOG_(ERROR) << toString(std::move(create_error));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_process_info = *create_or_error;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestClient::ListThreadsInStopReply() {
|
||||
return SendMessage("QListThreadsInStopReply");
|
||||
}
|
||||
|
||||
bool TestClient::SetBreakpoint(unsigned long address) {
|
||||
std::stringstream command;
|
||||
command << "Z0," << std::hex << address << ",1";
|
||||
return SendMessage(command.str());
|
||||
}
|
||||
|
||||
bool TestClient::ContinueAll() { return Continue("vCont;c"); }
|
||||
|
||||
bool TestClient::ContinueThread(unsigned long thread_id) {
|
||||
return Continue(formatv("vCont;c:{0:x-}", thread_id).str());
|
||||
}
|
||||
|
||||
const ProcessInfo &TestClient::GetProcessInfo() { return *m_process_info; }
|
||||
|
||||
Optional<JThreadsInfo> TestClient::GetJThreadsInfo() {
|
||||
std::string response;
|
||||
if (!SendMessage("jThreadsInfo", response))
|
||||
return llvm::None;
|
||||
auto creation = JThreadsInfo::Create(response, m_process_info->GetEndian());
|
||||
if (auto create_error = creation.takeError()) {
|
||||
GTEST_LOG_(ERROR) << toString(std::move(create_error));
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
return std::move(*creation);
|
||||
}
|
||||
|
||||
const StopReply &TestClient::GetLatestStopReply() {
|
||||
return m_stop_reply.getValue();
|
||||
}
|
||||
|
||||
bool TestClient::SendMessage(StringRef message) {
|
||||
std::string dummy_string;
|
||||
return SendMessage(message, dummy_string);
|
||||
}
|
||||
|
||||
bool TestClient::SendMessage(StringRef message, std::string &response_string) {
|
||||
if (!SendMessage(message, response_string, PacketResult::Success))
|
||||
return false;
|
||||
else if (response_string[0] == 'E') {
|
||||
GTEST_LOG_(ERROR) << "Error " << response_string
|
||||
<< " while sending message: " << message.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestClient::SendMessage(StringRef message, std::string &response_string,
|
||||
PacketResult expected_result) {
|
||||
StringExtractorGDBRemote response;
|
||||
GTEST_LOG_(INFO) << "Send Packet: " << message.str();
|
||||
PacketResult result = SendPacketAndWaitForResponse(message, response, false);
|
||||
response.GetEscapedBinaryData(response_string);
|
||||
GTEST_LOG_(INFO) << "Read Packet: " << response_string;
|
||||
if (result != expected_result) {
|
||||
GTEST_LOG_(ERROR) << FormatFailedResult(message, result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int TestClient::GetPcRegisterId() {
|
||||
if (m_pc_register != UINT_MAX)
|
||||
return m_pc_register;
|
||||
|
||||
for (unsigned int register_id = 0;; register_id++) {
|
||||
std::string message = formatv("qRegisterInfo{0:x-}", register_id).str();
|
||||
std::string response;
|
||||
if (!SendMessage(message, response)) {
|
||||
GTEST_LOG_(ERROR) << "Unable to query register ID for PC register.";
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
||||
auto elements_or_error = SplitPairList("GetPcRegisterId", response);
|
||||
if (auto split_error = elements_or_error.takeError()) {
|
||||
GTEST_LOG_(ERROR) << "GetPcRegisterId: Error splitting response: "
|
||||
<< response;
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
||||
auto elements = *elements_or_error;
|
||||
if (elements["alt-name"] == "pc" || elements["generic"] == "pc") {
|
||||
m_pc_register = register_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return m_pc_register;
|
||||
}
|
||||
|
||||
bool TestClient::Continue(StringRef message) {
|
||||
if (!m_process_info.hasValue()) {
|
||||
GTEST_LOG_(ERROR) << "Continue() called before m_process_info initialized.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string response;
|
||||
if (!SendMessage(message, response))
|
||||
return false;
|
||||
auto creation = StopReply::Create(response, m_process_info->GetEndian());
|
||||
if (auto create_error = creation.takeError()) {
|
||||
GTEST_LOG_(ERROR) << toString(std::move(create_error));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stop_reply = std::move(*creation);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string TestClient::GenerateLogFileName(const ArchSpec &arch) const {
|
||||
char *log_directory = getenv("LOG_FILE_DIRECTORY");
|
||||
if (!log_directory)
|
||||
return "";
|
||||
|
||||
if (!llvm::sys::fs::is_directory(log_directory)) {
|
||||
GTEST_LOG_(WARNING) << "Cannot access log directory: " << log_directory;
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string log_file_name;
|
||||
raw_string_ostream log_file(log_file_name);
|
||||
log_file << log_directory << "/lldb-" << m_test_case_name << '-'
|
||||
<< m_test_name << '-' << arch.GetArchitectureName() << ".log";
|
||||
return log_file.str();
|
||||
}
|
||||
|
||||
std::string TestClient::FormatFailedResult(const std::string &message,
|
||||
PacketResult result) {
|
||||
std::string formatted_error;
|
||||
raw_string_ostream error_stream(formatted_error);
|
||||
error_stream << "Failure sending message: " << message << " Result: ";
|
||||
|
||||
switch (result) {
|
||||
case PacketResult::ErrorSendFailed:
|
||||
error_stream << "ErrorSendFailed";
|
||||
break;
|
||||
case PacketResult::ErrorSendAck:
|
||||
error_stream << "ErrorSendAck";
|
||||
break;
|
||||
case PacketResult::ErrorReplyFailed:
|
||||
error_stream << "ErrorReplyFailed";
|
||||
break;
|
||||
case PacketResult::ErrorReplyTimeout:
|
||||
error_stream << "ErrorReplyTimeout";
|
||||
break;
|
||||
case PacketResult::ErrorReplyInvalid:
|
||||
error_stream << "ErrorReplyInvalid";
|
||||
break;
|
||||
case PacketResult::ErrorReplyAck:
|
||||
error_stream << "ErrorReplyAck";
|
||||
break;
|
||||
case PacketResult::ErrorDisconnected:
|
||||
error_stream << "ErrorDisconnected";
|
||||
break;
|
||||
case PacketResult::ErrorNoSequenceLock:
|
||||
error_stream << "ErrorNoSequenceLock";
|
||||
break;
|
||||
default:
|
||||
error_stream << "Unknown Error";
|
||||
}
|
||||
|
||||
error_stream.str();
|
||||
return formatted_error;
|
||||
}
|
||||
} // namespace llgs_tests
|
|
@ -0,0 +1,61 @@
|
|||
//===-- TestClient.h --------------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "MessageObjects.h"
|
||||
#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h"
|
||||
#include "lldb/Core/ArchSpec.h"
|
||||
#include "lldb/Target/ProcessLaunchInfo.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace llgs_tests {
|
||||
// TODO: Make the test client an abstract base class, with different children
|
||||
// for different types of connections: llgs v. debugserver
|
||||
class TestClient
|
||||
: public lldb_private::process_gdb_remote::GDBRemoteCommunicationClient {
|
||||
public:
|
||||
static void Initialize();
|
||||
TestClient(const std::string &test_name, const std::string &test_case_name);
|
||||
virtual ~TestClient();
|
||||
LLVM_NODISCARD bool StartDebugger();
|
||||
LLVM_NODISCARD bool StopDebugger();
|
||||
LLVM_NODISCARD bool SetInferior(llvm::ArrayRef<std::string> inferior_args);
|
||||
LLVM_NODISCARD bool ListThreadsInStopReply();
|
||||
LLVM_NODISCARD bool SetBreakpoint(unsigned long address);
|
||||
LLVM_NODISCARD bool ContinueAll();
|
||||
LLVM_NODISCARD bool ContinueThread(unsigned long thread_id);
|
||||
const ProcessInfo &GetProcessInfo();
|
||||
llvm::Optional<JThreadsInfo> GetJThreadsInfo();
|
||||
const StopReply &GetLatestStopReply();
|
||||
LLVM_NODISCARD bool SendMessage(llvm::StringRef message);
|
||||
LLVM_NODISCARD bool SendMessage(llvm::StringRef message,
|
||||
std::string &response_string);
|
||||
LLVM_NODISCARD bool SendMessage(llvm::StringRef message,
|
||||
std::string &response_string,
|
||||
PacketResult expected_result);
|
||||
unsigned int GetPcRegisterId();
|
||||
|
||||
private:
|
||||
LLVM_NODISCARD bool Continue(llvm::StringRef message);
|
||||
LLVM_NODISCARD bool GenerateConnectionAddress(std::string &address);
|
||||
std::string GenerateLogFileName(const lldb_private::ArchSpec &arch) const;
|
||||
std::string FormatFailedResult(
|
||||
const std::string &message,
|
||||
lldb_private::process_gdb_remote::GDBRemoteCommunication::PacketResult
|
||||
result);
|
||||
|
||||
llvm::Optional<ProcessInfo> m_process_info;
|
||||
llvm::Optional<StopReply> m_stop_reply;
|
||||
lldb_private::ProcessLaunchInfo m_server_process_info;
|
||||
std::string m_test_name;
|
||||
std::string m_test_case_name;
|
||||
unsigned int m_pc_register;
|
||||
};
|
||||
} // namespace llgs_tests
|
|
@ -0,0 +1,58 @@
|
|||
//===-- ThreadsInJstopinfoTest.cpp ------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TestClient.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <string>
|
||||
|
||||
using namespace llgs_tests;
|
||||
|
||||
class ThreadsInJstopinfoTest : public ::testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() { TestClient::Initialize(); }
|
||||
};
|
||||
|
||||
TEST_F(ThreadsInJstopinfoTest, TestStopReplyContainsThreadPcsLlgs) {
|
||||
std::vector<std::string> inferior_args;
|
||||
// This inferior spawns N threads, then forces a break.
|
||||
inferior_args.push_back(THREAD_INFERIOR);
|
||||
inferior_args.push_back("4");
|
||||
|
||||
auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||
|
||||
TestClient client(test_info->name(), test_info->test_case_name());
|
||||
ASSERT_TRUE(client.StartDebugger());
|
||||
ASSERT_TRUE(client.SetInferior(inferior_args));
|
||||
ASSERT_TRUE(client.ListThreadsInStopReply());
|
||||
ASSERT_TRUE(client.ContinueAll());
|
||||
unsigned int pc_reg = client.GetPcRegisterId();
|
||||
ASSERT_NE(pc_reg, UINT_MAX);
|
||||
|
||||
auto jthreads_info = client.GetJThreadsInfo();
|
||||
ASSERT_TRUE(jthreads_info);
|
||||
|
||||
auto stop_reply = client.GetLatestStopReply();
|
||||
auto stop_reply_pcs = stop_reply.GetThreadPcs();
|
||||
auto thread_infos = jthreads_info->GetThreadInfos();
|
||||
ASSERT_EQ(stop_reply_pcs.size(), thread_infos.size())
|
||||
<< "Thread count mismatch.";
|
||||
|
||||
for (auto stop_reply_pc : stop_reply_pcs) {
|
||||
unsigned long tid = stop_reply_pc.first;
|
||||
ASSERT_TRUE(thread_infos.find(tid) != thread_infos.end())
|
||||
<< "Thread ID: " << tid << " not in JThreadsInfo.";
|
||||
uint64_t pc_value;
|
||||
ASSERT_TRUE(thread_infos[tid].ReadRegisterAsUint64(pc_reg, pc_value))
|
||||
<< "Failure reading ThreadInfo register " << pc_reg;
|
||||
ASSERT_EQ(stop_reply_pcs[tid], pc_value)
|
||||
<< "Mismatched PC for thread: " << tid;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(client.StopDebugger());
|
||||
}
|
Loading…
Reference in New Issue