Skip to content

Commit

Permalink
New framework for lldb client-server communication tests.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
labath committed Jun 6, 2017
1 parent aaeada6 commit 015f17d
Show file tree
Hide file tree
Showing 10 changed files with 789 additions and 1 deletion.
3 changes: 2 additions & 1 deletion lldb/unittests/CMakeLists.txt
Expand Up @@ -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()
3 changes: 3 additions & 0 deletions lldb/unittests/tools/CMakeLists.txt
@@ -0,0 +1,3 @@
if(CMAKE_SYSTEM_NAME MATCHES "Android|Linux|NetBSD")
add_subdirectory(lldb-server)
endif()
13 changes: 13 additions & 0 deletions lldb/unittests/tools/lldb-server/CMakeLists.txt
@@ -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)
41 changes: 41 additions & 0 deletions lldb/unittests/tools/lldb-server/inferior/thread_inferior.cpp
@@ -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;
}
15 changes: 15 additions & 0 deletions lldb/unittests/tools/lldb-server/tests/CMakeLists.txt
@@ -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
)
207 changes: 207 additions & 0 deletions lldb/unittests/tools/lldb-server/tests/MessageObjects.cpp
@@ -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 &registers, 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
102 changes: 102 additions & 0 deletions lldb/unittests/tools/lldb-server/tests/MessageObjects.h
@@ -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 &registers, 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 comments on commit 015f17d

Please sign in to comment.