mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-17 00:26:43 +00:00

This reapplies commit
232525f069
.
The original commit triggered a sanitizer failure when `Target` was
destroyed. In `Target::Destroy`, `DeleteCurrentProcess` was called, but
it did not destroy the thread creation breakpoints for the underlying
`ProcessGDBRemote` because `ProcessGDBRemote::Clear` was not called in
that path.
`Target `then proceeded to destroy its breakpoints, which resulted in a
call to the destructor of a `std::vector` containing the breakpoints.
Through a sequence of complicated events, destroying breakpoints caused
the reference count of the underlying `ProcessGDBRemote` to finally
reach zero. This, in turn, called `ProcessGDBRemote::Clear`, which
attempted to destroy the breakpoints. To do that, it would go back into
the Target's vector of breakpoints, which we are in the middle of
destroying.
We solve this by moving the breakpoint deletion into
`Process:DoDestroy`, which is a virtual Process method that will be
called much earlier.
5994 lines
228 KiB
C++
5994 lines
228 KiB
C++
//===-- ProcessGDBRemote.cpp ----------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Host/Config.h"
|
|
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#if LLDB_ENABLE_POSIX
|
|
#include <netinet/in.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <sys/stat.h>
|
|
#if defined(__APPLE__)
|
|
#include <sys/sysctl.h>
|
|
#endif
|
|
#include <ctime>
|
|
#include <sys/types.h>
|
|
|
|
#include "lldb/Breakpoint/Watchpoint.h"
|
|
#include "lldb/Breakpoint/WatchpointAlgorithms.h"
|
|
#include "lldb/Breakpoint/WatchpointResource.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/ModuleSpec.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Core/Value.h"
|
|
#include "lldb/DataFormatters/FormatManager.h"
|
|
#include "lldb/Host/ConnectionFileDescriptor.h"
|
|
#include "lldb/Host/FileSystem.h"
|
|
#include "lldb/Host/HostThread.h"
|
|
#include "lldb/Host/PosixApi.h"
|
|
#include "lldb/Host/PseudoTerminal.h"
|
|
#include "lldb/Host/StreamFile.h"
|
|
#include "lldb/Host/ThreadLauncher.h"
|
|
#include "lldb/Host/XML.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/CommandObject.h"
|
|
#include "lldb/Interpreter/CommandObjectMultiword.h"
|
|
#include "lldb/Interpreter/CommandReturnObject.h"
|
|
#include "lldb/Interpreter/OptionArgParser.h"
|
|
#include "lldb/Interpreter/OptionGroupBoolean.h"
|
|
#include "lldb/Interpreter/OptionGroupUInt64.h"
|
|
#include "lldb/Interpreter/OptionValueProperties.h"
|
|
#include "lldb/Interpreter/Options.h"
|
|
#include "lldb/Interpreter/Property.h"
|
|
#include "lldb/Symbol/ObjectFile.h"
|
|
#include "lldb/Target/ABI.h"
|
|
#include "lldb/Target/DynamicLoader.h"
|
|
#include "lldb/Target/MemoryRegionInfo.h"
|
|
#include "lldb/Target/RegisterFlags.h"
|
|
#include "lldb/Target/SystemRuntime.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/TargetList.h"
|
|
#include "lldb/Target/ThreadPlanCallFunction.h"
|
|
#include "lldb/Utility/Args.h"
|
|
#include "lldb/Utility/FileSpec.h"
|
|
#include "lldb/Utility/LLDBLog.h"
|
|
#include "lldb/Utility/State.h"
|
|
#include "lldb/Utility/StreamString.h"
|
|
#include "lldb/Utility/Timer.h"
|
|
#include <algorithm>
|
|
#include <csignal>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <thread>
|
|
|
|
#include "GDBRemoteRegisterContext.h"
|
|
#include "GDBRemoteRegisterFallback.h"
|
|
#include "Plugins/Process/Utility/GDBRemoteSignals.h"
|
|
#include "Plugins/Process/Utility/InferiorCallPOSIX.h"
|
|
#include "Plugins/Process/Utility/StopInfoMachException.h"
|
|
#include "ProcessGDBRemote.h"
|
|
#include "ProcessGDBRemoteLog.h"
|
|
#include "ThreadGDBRemote.h"
|
|
#include "lldb/Host/Host.h"
|
|
#include "lldb/Utility/StringExtractorGDBRemote.h"
|
|
|
|
#include "llvm/ADT/ScopeExit.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
#include "llvm/ADT/StringSwitch.h"
|
|
#include "llvm/Support/FormatAdapters.h"
|
|
#include "llvm/Support/Threading.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
#define DEBUGSERVER_BASENAME "debugserver"
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
using namespace lldb_private::process_gdb_remote;
|
|
|
|
LLDB_PLUGIN_DEFINE(ProcessGDBRemote)
|
|
|
|
namespace lldb {
|
|
// Provide a function that can easily dump the packet history if we know a
|
|
// ProcessGDBRemote * value (which we can get from logs or from debugging). We
|
|
// need the function in the lldb namespace so it makes it into the final
|
|
// executable since the LLDB shared library only exports stuff in the lldb
|
|
// namespace. This allows you to attach with a debugger and call this function
|
|
// and get the packet history dumped to a file.
|
|
void DumpProcessGDBRemotePacketHistory(void *p, const char *path) {
|
|
auto file = FileSystem::Instance().Open(
|
|
FileSpec(path), File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate);
|
|
if (!file) {
|
|
llvm::consumeError(file.takeError());
|
|
return;
|
|
}
|
|
StreamFile stream(std::move(file.get()));
|
|
((Process *)p)->DumpPluginHistory(stream);
|
|
}
|
|
} // namespace lldb
|
|
|
|
namespace {
|
|
|
|
#define LLDB_PROPERTIES_processgdbremote
|
|
#include "ProcessGDBRemoteProperties.inc"
|
|
|
|
enum {
|
|
#define LLDB_PROPERTIES_processgdbremote
|
|
#include "ProcessGDBRemotePropertiesEnum.inc"
|
|
};
|
|
|
|
class PluginProperties : public Properties {
|
|
public:
|
|
static llvm::StringRef GetSettingName() {
|
|
return ProcessGDBRemote::GetPluginNameStatic();
|
|
}
|
|
|
|
PluginProperties() : Properties() {
|
|
m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName());
|
|
m_collection_sp->Initialize(g_processgdbremote_properties);
|
|
}
|
|
|
|
~PluginProperties() override = default;
|
|
|
|
uint64_t GetPacketTimeout() {
|
|
const uint32_t idx = ePropertyPacketTimeout;
|
|
return GetPropertyAtIndexAs<uint64_t>(
|
|
idx, g_processgdbremote_properties[idx].default_uint_value);
|
|
}
|
|
|
|
bool SetPacketTimeout(uint64_t timeout) {
|
|
const uint32_t idx = ePropertyPacketTimeout;
|
|
return SetPropertyAtIndex(idx, timeout);
|
|
}
|
|
|
|
FileSpec GetTargetDefinitionFile() const {
|
|
const uint32_t idx = ePropertyTargetDefinitionFile;
|
|
return GetPropertyAtIndexAs<FileSpec>(idx, {});
|
|
}
|
|
|
|
bool GetUseSVR4() const {
|
|
const uint32_t idx = ePropertyUseSVR4;
|
|
return GetPropertyAtIndexAs<bool>(
|
|
idx, g_processgdbremote_properties[idx].default_uint_value != 0);
|
|
}
|
|
|
|
bool GetUseGPacketForReading() const {
|
|
const uint32_t idx = ePropertyUseGPacketForReading;
|
|
return GetPropertyAtIndexAs<bool>(idx, true);
|
|
}
|
|
};
|
|
|
|
std::chrono::seconds ResumeTimeout() { return std::chrono::seconds(5); }
|
|
|
|
} // namespace
|
|
|
|
static PluginProperties &GetGlobalPluginProperties() {
|
|
static PluginProperties g_settings;
|
|
return g_settings;
|
|
}
|
|
|
|
// TODO Randomly assigning a port is unsafe. We should get an unused
|
|
// ephemeral port from the kernel and make sure we reserve it before passing it
|
|
// to debugserver.
|
|
|
|
#if defined(__APPLE__)
|
|
#define LOW_PORT (IPPORT_RESERVED)
|
|
#define HIGH_PORT (IPPORT_HIFIRSTAUTO)
|
|
#else
|
|
#define LOW_PORT (1024u)
|
|
#define HIGH_PORT (49151u)
|
|
#endif
|
|
|
|
llvm::StringRef ProcessGDBRemote::GetPluginDescriptionStatic() {
|
|
return "GDB Remote protocol based debugging plug-in.";
|
|
}
|
|
|
|
void ProcessGDBRemote::Terminate() {
|
|
PluginManager::UnregisterPlugin(ProcessGDBRemote::CreateInstance);
|
|
}
|
|
|
|
lldb::ProcessSP ProcessGDBRemote::CreateInstance(
|
|
lldb::TargetSP target_sp, ListenerSP listener_sp,
|
|
const FileSpec *crash_file_path, bool can_connect) {
|
|
lldb::ProcessSP process_sp;
|
|
if (crash_file_path == nullptr)
|
|
process_sp = std::shared_ptr<ProcessGDBRemote>(
|
|
new ProcessGDBRemote(target_sp, listener_sp));
|
|
return process_sp;
|
|
}
|
|
|
|
void ProcessGDBRemote::DumpPluginHistory(Stream &s) {
|
|
GDBRemoteCommunicationClient &gdb_comm(GetGDBRemote());
|
|
gdb_comm.DumpHistory(s);
|
|
}
|
|
|
|
std::chrono::seconds ProcessGDBRemote::GetPacketTimeout() {
|
|
return std::chrono::seconds(GetGlobalPluginProperties().GetPacketTimeout());
|
|
}
|
|
|
|
ArchSpec ProcessGDBRemote::GetSystemArchitecture() {
|
|
return m_gdb_comm.GetHostArchitecture();
|
|
}
|
|
|
|
bool ProcessGDBRemote::CanDebug(lldb::TargetSP target_sp,
|
|
bool plugin_specified_by_name) {
|
|
if (plugin_specified_by_name)
|
|
return true;
|
|
|
|
// For now we are just making sure the file exists for a given module
|
|
Module *exe_module = target_sp->GetExecutableModulePointer();
|
|
if (exe_module) {
|
|
ObjectFile *exe_objfile = exe_module->GetObjectFile();
|
|
// We can't debug core files...
|
|
switch (exe_objfile->GetType()) {
|
|
case ObjectFile::eTypeInvalid:
|
|
case ObjectFile::eTypeCoreFile:
|
|
case ObjectFile::eTypeDebugInfo:
|
|
case ObjectFile::eTypeObjectFile:
|
|
case ObjectFile::eTypeSharedLibrary:
|
|
case ObjectFile::eTypeStubLibrary:
|
|
case ObjectFile::eTypeJIT:
|
|
return false;
|
|
case ObjectFile::eTypeExecutable:
|
|
case ObjectFile::eTypeDynamicLinker:
|
|
case ObjectFile::eTypeUnknown:
|
|
break;
|
|
}
|
|
return FileSystem::Instance().Exists(exe_module->GetFileSpec());
|
|
}
|
|
// However, if there is no executable module, we return true since we might
|
|
// be preparing to attach.
|
|
return true;
|
|
}
|
|
|
|
// ProcessGDBRemote constructor
|
|
ProcessGDBRemote::ProcessGDBRemote(lldb::TargetSP target_sp,
|
|
ListenerSP listener_sp)
|
|
: Process(target_sp, listener_sp),
|
|
m_debugserver_pid(LLDB_INVALID_PROCESS_ID), m_register_info_sp(nullptr),
|
|
m_async_broadcaster(nullptr, "lldb.process.gdb-remote.async-broadcaster"),
|
|
m_async_listener_sp(
|
|
Listener::MakeListener("lldb.process.gdb-remote.async-listener")),
|
|
m_async_thread_state_mutex(), m_thread_ids(), m_thread_pcs(),
|
|
m_jstopinfo_sp(), m_jthreadsinfo_sp(), m_continue_c_tids(),
|
|
m_continue_C_tids(), m_continue_s_tids(), m_continue_S_tids(),
|
|
m_max_memory_size(0), m_remote_stub_max_memory_size(0),
|
|
m_addr_to_mmap_size(), m_thread_create_bp_sp(),
|
|
m_waiting_for_attach(false), m_command_sp(), m_breakpoint_pc_offset(0),
|
|
m_initial_tid(LLDB_INVALID_THREAD_ID), m_allow_flash_writes(false),
|
|
m_erased_flash_ranges(), m_vfork_in_progress_count(0) {
|
|
m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit,
|
|
"async thread should exit");
|
|
m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue,
|
|
"async thread continue");
|
|
m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadDidExit,
|
|
"async thread did exit");
|
|
|
|
Log *log = GetLog(GDBRLog::Async);
|
|
|
|
const uint32_t async_event_mask =
|
|
eBroadcastBitAsyncContinue | eBroadcastBitAsyncThreadShouldExit;
|
|
|
|
if (m_async_listener_sp->StartListeningForEvents(
|
|
&m_async_broadcaster, async_event_mask) != async_event_mask) {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s failed to listen for "
|
|
"m_async_broadcaster events",
|
|
__FUNCTION__);
|
|
}
|
|
|
|
const uint64_t timeout_seconds =
|
|
GetGlobalPluginProperties().GetPacketTimeout();
|
|
if (timeout_seconds > 0)
|
|
m_gdb_comm.SetPacketTimeout(std::chrono::seconds(timeout_seconds));
|
|
|
|
m_use_g_packet_for_reading =
|
|
GetGlobalPluginProperties().GetUseGPacketForReading();
|
|
}
|
|
|
|
// Destructor
|
|
ProcessGDBRemote::~ProcessGDBRemote() {
|
|
// m_mach_process.UnregisterNotificationCallbacks (this);
|
|
Clear();
|
|
// We need to call finalize on the process before destroying ourselves to
|
|
// make sure all of the broadcaster cleanup goes as planned. If we destruct
|
|
// this class, then Process::~Process() might have problems trying to fully
|
|
// destroy the broadcaster.
|
|
Finalize(true /* destructing */);
|
|
|
|
// The general Finalize is going to try to destroy the process and that
|
|
// SHOULD shut down the async thread. However, if we don't kill it it will
|
|
// get stranded and its connection will go away so when it wakes up it will
|
|
// crash. So kill it for sure here.
|
|
StopAsyncThread();
|
|
KillDebugserverProcess();
|
|
}
|
|
|
|
bool ProcessGDBRemote::ParsePythonTargetDefinition(
|
|
const FileSpec &target_definition_fspec) {
|
|
ScriptInterpreter *interpreter =
|
|
GetTarget().GetDebugger().GetScriptInterpreter();
|
|
Status error;
|
|
StructuredData::ObjectSP module_object_sp(
|
|
interpreter->LoadPluginModule(target_definition_fspec, error));
|
|
if (module_object_sp) {
|
|
StructuredData::DictionarySP target_definition_sp(
|
|
interpreter->GetDynamicSettings(module_object_sp, &GetTarget(),
|
|
"gdb-server-target-definition", error));
|
|
|
|
if (target_definition_sp) {
|
|
StructuredData::ObjectSP target_object(
|
|
target_definition_sp->GetValueForKey("host-info"));
|
|
if (target_object) {
|
|
if (auto host_info_dict = target_object->GetAsDictionary()) {
|
|
StructuredData::ObjectSP triple_value =
|
|
host_info_dict->GetValueForKey("triple");
|
|
if (auto triple_string_value = triple_value->GetAsString()) {
|
|
std::string triple_string =
|
|
std::string(triple_string_value->GetValue());
|
|
ArchSpec host_arch(triple_string.c_str());
|
|
if (!host_arch.IsCompatibleMatch(GetTarget().GetArchitecture())) {
|
|
GetTarget().SetArchitecture(host_arch);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_breakpoint_pc_offset = 0;
|
|
StructuredData::ObjectSP breakpoint_pc_offset_value =
|
|
target_definition_sp->GetValueForKey("breakpoint-pc-offset");
|
|
if (breakpoint_pc_offset_value) {
|
|
if (auto breakpoint_pc_int_value =
|
|
breakpoint_pc_offset_value->GetAsSignedInteger())
|
|
m_breakpoint_pc_offset = breakpoint_pc_int_value->GetValue();
|
|
}
|
|
|
|
if (m_register_info_sp->SetRegisterInfo(
|
|
*target_definition_sp, GetTarget().GetArchitecture()) > 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static size_t SplitCommaSeparatedRegisterNumberString(
|
|
const llvm::StringRef &comma_separated_register_numbers,
|
|
std::vector<uint32_t> ®nums, int base) {
|
|
regnums.clear();
|
|
for (llvm::StringRef x : llvm::split(comma_separated_register_numbers, ',')) {
|
|
uint32_t reg;
|
|
if (llvm::to_integer(x, reg, base))
|
|
regnums.push_back(reg);
|
|
}
|
|
return regnums.size();
|
|
}
|
|
|
|
void ProcessGDBRemote::BuildDynamicRegisterInfo(bool force) {
|
|
if (!force && m_register_info_sp)
|
|
return;
|
|
|
|
m_register_info_sp = std::make_shared<GDBRemoteDynamicRegisterInfo>();
|
|
|
|
// Check if qHostInfo specified a specific packet timeout for this
|
|
// connection. If so then lets update our setting so the user knows what the
|
|
// timeout is and can see it.
|
|
const auto host_packet_timeout = m_gdb_comm.GetHostDefaultPacketTimeout();
|
|
if (host_packet_timeout > std::chrono::seconds(0)) {
|
|
GetGlobalPluginProperties().SetPacketTimeout(host_packet_timeout.count());
|
|
}
|
|
|
|
// Register info search order:
|
|
// 1 - Use the target definition python file if one is specified.
|
|
// 2 - If the target definition doesn't have any of the info from the
|
|
// target.xml (registers) then proceed to read the target.xml.
|
|
// 3 - Fall back on the qRegisterInfo packets.
|
|
// 4 - Use hardcoded defaults if available.
|
|
|
|
FileSpec target_definition_fspec =
|
|
GetGlobalPluginProperties().GetTargetDefinitionFile();
|
|
if (!FileSystem::Instance().Exists(target_definition_fspec)) {
|
|
// If the filename doesn't exist, it may be a ~ not having been expanded -
|
|
// try to resolve it.
|
|
FileSystem::Instance().Resolve(target_definition_fspec);
|
|
}
|
|
if (target_definition_fspec) {
|
|
// See if we can get register definitions from a python file
|
|
if (ParsePythonTargetDefinition(target_definition_fspec))
|
|
return;
|
|
|
|
Debugger::ReportError("target description file " +
|
|
target_definition_fspec.GetPath() +
|
|
" failed to parse",
|
|
GetTarget().GetDebugger().GetID());
|
|
}
|
|
|
|
const ArchSpec &target_arch = GetTarget().GetArchitecture();
|
|
const ArchSpec &remote_host_arch = m_gdb_comm.GetHostArchitecture();
|
|
const ArchSpec &remote_process_arch = m_gdb_comm.GetProcessArchitecture();
|
|
|
|
// Use the process' architecture instead of the host arch, if available
|
|
ArchSpec arch_to_use;
|
|
if (remote_process_arch.IsValid())
|
|
arch_to_use = remote_process_arch;
|
|
else
|
|
arch_to_use = remote_host_arch;
|
|
|
|
if (!arch_to_use.IsValid())
|
|
arch_to_use = target_arch;
|
|
|
|
if (GetGDBServerRegisterInfo(arch_to_use))
|
|
return;
|
|
|
|
char packet[128];
|
|
std::vector<DynamicRegisterInfo::Register> registers;
|
|
uint32_t reg_num = 0;
|
|
for (StringExtractorGDBRemote::ResponseType response_type =
|
|
StringExtractorGDBRemote::eResponse;
|
|
response_type == StringExtractorGDBRemote::eResponse; ++reg_num) {
|
|
const int packet_len =
|
|
::snprintf(packet, sizeof(packet), "qRegisterInfo%x", reg_num);
|
|
assert(packet_len < (int)sizeof(packet));
|
|
UNUSED_IF_ASSERT_DISABLED(packet_len);
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet, response) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
response_type = response.GetResponseType();
|
|
if (response_type == StringExtractorGDBRemote::eResponse) {
|
|
llvm::StringRef name;
|
|
llvm::StringRef value;
|
|
DynamicRegisterInfo::Register reg_info;
|
|
|
|
while (response.GetNameColonValue(name, value)) {
|
|
if (name == "name") {
|
|
reg_info.name.SetString(value);
|
|
} else if (name == "alt-name") {
|
|
reg_info.alt_name.SetString(value);
|
|
} else if (name == "bitsize") {
|
|
if (!value.getAsInteger(0, reg_info.byte_size))
|
|
reg_info.byte_size /= CHAR_BIT;
|
|
} else if (name == "offset") {
|
|
value.getAsInteger(0, reg_info.byte_offset);
|
|
} else if (name == "encoding") {
|
|
const Encoding encoding = Args::StringToEncoding(value);
|
|
if (encoding != eEncodingInvalid)
|
|
reg_info.encoding = encoding;
|
|
} else if (name == "format") {
|
|
if (!OptionArgParser::ToFormat(value.str().c_str(), reg_info.format, nullptr)
|
|
.Success())
|
|
reg_info.format =
|
|
llvm::StringSwitch<Format>(value)
|
|
.Case("binary", eFormatBinary)
|
|
.Case("decimal", eFormatDecimal)
|
|
.Case("hex", eFormatHex)
|
|
.Case("float", eFormatFloat)
|
|
.Case("vector-sint8", eFormatVectorOfSInt8)
|
|
.Case("vector-uint8", eFormatVectorOfUInt8)
|
|
.Case("vector-sint16", eFormatVectorOfSInt16)
|
|
.Case("vector-uint16", eFormatVectorOfUInt16)
|
|
.Case("vector-sint32", eFormatVectorOfSInt32)
|
|
.Case("vector-uint32", eFormatVectorOfUInt32)
|
|
.Case("vector-float32", eFormatVectorOfFloat32)
|
|
.Case("vector-uint64", eFormatVectorOfUInt64)
|
|
.Case("vector-uint128", eFormatVectorOfUInt128)
|
|
.Default(eFormatInvalid);
|
|
} else if (name == "set") {
|
|
reg_info.set_name.SetString(value);
|
|
} else if (name == "gcc" || name == "ehframe") {
|
|
value.getAsInteger(0, reg_info.regnum_ehframe);
|
|
} else if (name == "dwarf") {
|
|
value.getAsInteger(0, reg_info.regnum_dwarf);
|
|
} else if (name == "generic") {
|
|
reg_info.regnum_generic = Args::StringToGenericRegister(value);
|
|
} else if (name == "container-regs") {
|
|
SplitCommaSeparatedRegisterNumberString(value, reg_info.value_regs, 16);
|
|
} else if (name == "invalidate-regs") {
|
|
SplitCommaSeparatedRegisterNumberString(value, reg_info.invalidate_regs, 16);
|
|
}
|
|
}
|
|
|
|
assert(reg_info.byte_size != 0);
|
|
registers.push_back(reg_info);
|
|
} else {
|
|
break; // ensure exit before reg_num is incremented
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (registers.empty())
|
|
registers = GetFallbackRegisters(arch_to_use);
|
|
|
|
AddRemoteRegisters(registers, arch_to_use);
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoWillLaunch(lldb_private::Module *module) {
|
|
return WillLaunchOrAttach();
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoWillAttachToProcessWithID(lldb::pid_t pid) {
|
|
return WillLaunchOrAttach();
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoWillAttachToProcessWithName(const char *process_name,
|
|
bool wait_for_launch) {
|
|
return WillLaunchOrAttach();
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoConnectRemote(llvm::StringRef remote_url) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
|
|
Status error(WillLaunchOrAttach());
|
|
if (error.Fail())
|
|
return error;
|
|
|
|
error = ConnectToDebugserver(remote_url);
|
|
if (error.Fail())
|
|
return error;
|
|
|
|
StartAsyncThread();
|
|
|
|
lldb::pid_t pid = m_gdb_comm.GetCurrentProcessID();
|
|
if (pid == LLDB_INVALID_PROCESS_ID) {
|
|
// We don't have a valid process ID, so note that we are connected and
|
|
// could now request to launch or attach, or get remote process listings...
|
|
SetPrivateState(eStateConnected);
|
|
} else {
|
|
// We have a valid process
|
|
SetID(pid);
|
|
GetThreadList();
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.GetStopReply(response)) {
|
|
SetLastStopPacket(response);
|
|
|
|
Target &target = GetTarget();
|
|
if (!target.GetArchitecture().IsValid()) {
|
|
if (m_gdb_comm.GetProcessArchitecture().IsValid()) {
|
|
target.SetArchitecture(m_gdb_comm.GetProcessArchitecture());
|
|
} else {
|
|
if (m_gdb_comm.GetHostArchitecture().IsValid()) {
|
|
target.SetArchitecture(m_gdb_comm.GetHostArchitecture());
|
|
}
|
|
}
|
|
}
|
|
|
|
const StateType state = SetThreadStopInfo(response);
|
|
if (state != eStateInvalid) {
|
|
SetPrivateState(state);
|
|
} else
|
|
error = Status::FromErrorStringWithFormat(
|
|
"Process %" PRIu64 " was reported after connecting to "
|
|
"'%s', but state was not stopped: %s",
|
|
pid, remote_url.str().c_str(), StateAsCString(state));
|
|
} else
|
|
error = Status::FromErrorStringWithFormat(
|
|
"Process %" PRIu64 " was reported after connecting to '%s', "
|
|
"but no stop reply packet was received",
|
|
pid, remote_url.str().c_str());
|
|
}
|
|
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s pid %" PRIu64
|
|
": normalizing target architecture initial triple: %s "
|
|
"(GetTarget().GetArchitecture().IsValid() %s, "
|
|
"m_gdb_comm.GetHostArchitecture().IsValid(): %s)",
|
|
__FUNCTION__, GetID(),
|
|
GetTarget().GetArchitecture().GetTriple().getTriple().c_str(),
|
|
GetTarget().GetArchitecture().IsValid() ? "true" : "false",
|
|
m_gdb_comm.GetHostArchitecture().IsValid() ? "true" : "false");
|
|
|
|
if (error.Success() && !GetTarget().GetArchitecture().IsValid() &&
|
|
m_gdb_comm.GetHostArchitecture().IsValid()) {
|
|
// Prefer the *process'* architecture over that of the *host*, if
|
|
// available.
|
|
if (m_gdb_comm.GetProcessArchitecture().IsValid())
|
|
GetTarget().SetArchitecture(m_gdb_comm.GetProcessArchitecture());
|
|
else
|
|
GetTarget().SetArchitecture(m_gdb_comm.GetHostArchitecture());
|
|
}
|
|
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s pid %" PRIu64
|
|
": normalized target architecture triple: %s",
|
|
__FUNCTION__, GetID(),
|
|
GetTarget().GetArchitecture().GetTriple().getTriple().c_str());
|
|
|
|
return error;
|
|
}
|
|
|
|
Status ProcessGDBRemote::WillLaunchOrAttach() {
|
|
Status error;
|
|
m_stdio_communication.Clear();
|
|
return error;
|
|
}
|
|
|
|
// Process Control
|
|
Status ProcessGDBRemote::DoLaunch(lldb_private::Module *exe_module,
|
|
ProcessLaunchInfo &launch_info) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
Status error;
|
|
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s() entered", __FUNCTION__);
|
|
|
|
uint32_t launch_flags = launch_info.GetFlags().Get();
|
|
FileSpec stdin_file_spec{};
|
|
FileSpec stdout_file_spec{};
|
|
FileSpec stderr_file_spec{};
|
|
FileSpec working_dir = launch_info.GetWorkingDirectory();
|
|
|
|
const FileAction *file_action;
|
|
file_action = launch_info.GetFileActionForFD(STDIN_FILENO);
|
|
if (file_action) {
|
|
if (file_action->GetAction() == FileAction::eFileActionOpen)
|
|
stdin_file_spec = file_action->GetFileSpec();
|
|
}
|
|
file_action = launch_info.GetFileActionForFD(STDOUT_FILENO);
|
|
if (file_action) {
|
|
if (file_action->GetAction() == FileAction::eFileActionOpen)
|
|
stdout_file_spec = file_action->GetFileSpec();
|
|
}
|
|
file_action = launch_info.GetFileActionForFD(STDERR_FILENO);
|
|
if (file_action) {
|
|
if (file_action->GetAction() == FileAction::eFileActionOpen)
|
|
stderr_file_spec = file_action->GetFileSpec();
|
|
}
|
|
|
|
if (log) {
|
|
if (stdin_file_spec || stdout_file_spec || stderr_file_spec)
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s provided with STDIO paths via "
|
|
"launch_info: stdin=%s, stdout=%s, stderr=%s",
|
|
__FUNCTION__,
|
|
stdin_file_spec ? stdin_file_spec.GetPath().c_str() : "<null>",
|
|
stdout_file_spec ? stdout_file_spec.GetPath().c_str() : "<null>",
|
|
stderr_file_spec ? stderr_file_spec.GetPath().c_str() : "<null>");
|
|
else
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s no STDIO paths given via launch_info",
|
|
__FUNCTION__);
|
|
}
|
|
|
|
const bool disable_stdio = (launch_flags & eLaunchFlagDisableSTDIO) != 0;
|
|
if (stdin_file_spec || disable_stdio) {
|
|
// the inferior will be reading stdin from the specified file or stdio is
|
|
// completely disabled
|
|
m_stdin_forward = false;
|
|
} else {
|
|
m_stdin_forward = true;
|
|
}
|
|
|
|
// ::LogSetBitMask (GDBR_LOG_DEFAULT);
|
|
// ::LogSetOptions (LLDB_LOG_OPTION_THREADSAFE |
|
|
// LLDB_LOG_OPTION_PREPEND_TIMESTAMP |
|
|
// LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD);
|
|
// ::LogSetLogFile ("/dev/stdout");
|
|
|
|
error = EstablishConnectionIfNeeded(launch_info);
|
|
if (error.Success()) {
|
|
PseudoTerminal pty;
|
|
const bool disable_stdio = (launch_flags & eLaunchFlagDisableSTDIO) != 0;
|
|
|
|
PlatformSP platform_sp(GetTarget().GetPlatform());
|
|
if (disable_stdio) {
|
|
// set to /dev/null unless redirected to a file above
|
|
if (!stdin_file_spec)
|
|
stdin_file_spec.SetFile(FileSystem::DEV_NULL,
|
|
FileSpec::Style::native);
|
|
if (!stdout_file_spec)
|
|
stdout_file_spec.SetFile(FileSystem::DEV_NULL,
|
|
FileSpec::Style::native);
|
|
if (!stderr_file_spec)
|
|
stderr_file_spec.SetFile(FileSystem::DEV_NULL,
|
|
FileSpec::Style::native);
|
|
} else if (platform_sp && platform_sp->IsHost()) {
|
|
// If the debugserver is local and we aren't disabling STDIO, lets use
|
|
// a pseudo terminal to instead of relying on the 'O' packets for stdio
|
|
// since 'O' packets can really slow down debugging if the inferior
|
|
// does a lot of output.
|
|
if ((!stdin_file_spec || !stdout_file_spec || !stderr_file_spec) &&
|
|
!errorToBool(pty.OpenFirstAvailablePrimary(O_RDWR | O_NOCTTY))) {
|
|
FileSpec secondary_name(pty.GetSecondaryName());
|
|
|
|
if (!stdin_file_spec)
|
|
stdin_file_spec = secondary_name;
|
|
|
|
if (!stdout_file_spec)
|
|
stdout_file_spec = secondary_name;
|
|
|
|
if (!stderr_file_spec)
|
|
stderr_file_spec = secondary_name;
|
|
}
|
|
LLDB_LOGF(
|
|
log,
|
|
"ProcessGDBRemote::%s adjusted STDIO paths for local platform "
|
|
"(IsHost() is true) using secondary: stdin=%s, stdout=%s, "
|
|
"stderr=%s",
|
|
__FUNCTION__,
|
|
stdin_file_spec ? stdin_file_spec.GetPath().c_str() : "<null>",
|
|
stdout_file_spec ? stdout_file_spec.GetPath().c_str() : "<null>",
|
|
stderr_file_spec ? stderr_file_spec.GetPath().c_str() : "<null>");
|
|
}
|
|
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s final STDIO paths after all "
|
|
"adjustments: stdin=%s, stdout=%s, stderr=%s",
|
|
__FUNCTION__,
|
|
stdin_file_spec ? stdin_file_spec.GetPath().c_str() : "<null>",
|
|
stdout_file_spec ? stdout_file_spec.GetPath().c_str() : "<null>",
|
|
stderr_file_spec ? stderr_file_spec.GetPath().c_str() : "<null>");
|
|
|
|
if (stdin_file_spec)
|
|
m_gdb_comm.SetSTDIN(stdin_file_spec);
|
|
if (stdout_file_spec)
|
|
m_gdb_comm.SetSTDOUT(stdout_file_spec);
|
|
if (stderr_file_spec)
|
|
m_gdb_comm.SetSTDERR(stderr_file_spec);
|
|
|
|
m_gdb_comm.SetDisableASLR(launch_flags & eLaunchFlagDisableASLR);
|
|
m_gdb_comm.SetDetachOnError(launch_flags & eLaunchFlagDetachOnError);
|
|
|
|
m_gdb_comm.SendLaunchArchPacket(
|
|
GetTarget().GetArchitecture().GetArchitectureName());
|
|
|
|
const char *launch_event_data = launch_info.GetLaunchEventData();
|
|
if (launch_event_data != nullptr && *launch_event_data != '\0')
|
|
m_gdb_comm.SendLaunchEventDataPacket(launch_event_data);
|
|
|
|
if (working_dir) {
|
|
m_gdb_comm.SetWorkingDir(working_dir);
|
|
}
|
|
|
|
// Send the environment and the program + arguments after we connect
|
|
m_gdb_comm.SendEnvironment(launch_info.GetEnvironment());
|
|
|
|
{
|
|
// Scope for the scoped timeout object
|
|
GDBRemoteCommunication::ScopedTimeout timeout(m_gdb_comm,
|
|
std::chrono::seconds(10));
|
|
|
|
// Since we can't send argv0 separate from the executable path, we need to
|
|
// make sure to use the actual executable path found in the launch_info...
|
|
Args args = launch_info.GetArguments();
|
|
if (FileSpec exe_file = launch_info.GetExecutableFile())
|
|
args.ReplaceArgumentAtIndex(0, exe_file.GetPath(false));
|
|
if (llvm::Error err = m_gdb_comm.LaunchProcess(args)) {
|
|
error = Status::FromErrorStringWithFormatv(
|
|
"Cannot launch '{0}': {1}", args.GetArgumentAtIndex(0),
|
|
llvm::fmt_consume(std::move(err)));
|
|
} else {
|
|
SetID(m_gdb_comm.GetCurrentProcessID());
|
|
}
|
|
}
|
|
|
|
if (GetID() == LLDB_INVALID_PROCESS_ID) {
|
|
LLDB_LOGF(log, "failed to connect to debugserver: %s",
|
|
error.AsCString());
|
|
KillDebugserverProcess();
|
|
return error;
|
|
}
|
|
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.GetStopReply(response)) {
|
|
SetLastStopPacket(response);
|
|
|
|
const ArchSpec &process_arch = m_gdb_comm.GetProcessArchitecture();
|
|
|
|
if (process_arch.IsValid()) {
|
|
GetTarget().MergeArchitecture(process_arch);
|
|
} else {
|
|
const ArchSpec &host_arch = m_gdb_comm.GetHostArchitecture();
|
|
if (host_arch.IsValid())
|
|
GetTarget().MergeArchitecture(host_arch);
|
|
}
|
|
|
|
SetPrivateState(SetThreadStopInfo(response));
|
|
|
|
if (!disable_stdio) {
|
|
if (pty.GetPrimaryFileDescriptor() != PseudoTerminal::invalid_fd)
|
|
SetSTDIOFileDescriptor(pty.ReleasePrimaryFileDescriptor());
|
|
}
|
|
}
|
|
} else {
|
|
LLDB_LOGF(log, "failed to connect to debugserver: %s", error.AsCString());
|
|
}
|
|
return error;
|
|
}
|
|
|
|
Status ProcessGDBRemote::ConnectToDebugserver(llvm::StringRef connect_url) {
|
|
Status error;
|
|
// Only connect if we have a valid connect URL
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
|
|
if (!connect_url.empty()) {
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s Connecting to %s", __FUNCTION__,
|
|
connect_url.str().c_str());
|
|
std::unique_ptr<ConnectionFileDescriptor> conn_up(
|
|
new ConnectionFileDescriptor());
|
|
if (conn_up) {
|
|
const uint32_t max_retry_count = 50;
|
|
uint32_t retry_count = 0;
|
|
while (!m_gdb_comm.IsConnected()) {
|
|
if (conn_up->Connect(connect_url, &error) == eConnectionStatusSuccess) {
|
|
m_gdb_comm.SetConnection(std::move(conn_up));
|
|
break;
|
|
}
|
|
|
|
retry_count++;
|
|
|
|
if (retry_count >= max_retry_count)
|
|
break;
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_gdb_comm.IsConnected()) {
|
|
if (error.Success())
|
|
error = Status::FromErrorString("not connected to remote gdb server");
|
|
return error;
|
|
}
|
|
|
|
// We always seem to be able to open a connection to a local port so we need
|
|
// to make sure we can then send data to it. If we can't then we aren't
|
|
// actually connected to anything, so try and do the handshake with the
|
|
// remote GDB server and make sure that goes alright.
|
|
if (!m_gdb_comm.HandshakeWithServer(&error)) {
|
|
m_gdb_comm.Disconnect();
|
|
if (error.Success())
|
|
error = Status::FromErrorString("not connected to remote gdb server");
|
|
return error;
|
|
}
|
|
|
|
m_gdb_comm.GetEchoSupported();
|
|
m_gdb_comm.GetThreadSuffixSupported();
|
|
m_gdb_comm.GetListThreadsInStopReplySupported();
|
|
m_gdb_comm.GetHostInfo();
|
|
m_gdb_comm.GetVContSupported('c');
|
|
m_gdb_comm.GetVAttachOrWaitSupported();
|
|
m_gdb_comm.EnableErrorStringInPacket();
|
|
|
|
// First dispatch any commands from the platform:
|
|
auto handle_cmds = [&] (const Args &args) -> void {
|
|
for (const Args::ArgEntry &entry : args) {
|
|
StringExtractorGDBRemote response;
|
|
m_gdb_comm.SendPacketAndWaitForResponse(
|
|
entry.c_str(), response);
|
|
}
|
|
};
|
|
|
|
PlatformSP platform_sp = GetTarget().GetPlatform();
|
|
if (platform_sp) {
|
|
handle_cmds(platform_sp->GetExtraStartupCommands());
|
|
}
|
|
|
|
// Then dispatch any process commands:
|
|
handle_cmds(GetExtraStartupCommands());
|
|
|
|
return error;
|
|
}
|
|
|
|
void ProcessGDBRemote::DidLaunchOrAttach(ArchSpec &process_arch) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
BuildDynamicRegisterInfo(false);
|
|
|
|
// See if the GDB server supports qHostInfo or qProcessInfo packets. Prefer
|
|
// qProcessInfo as it will be more specific to our process.
|
|
|
|
const ArchSpec &remote_process_arch = m_gdb_comm.GetProcessArchitecture();
|
|
if (remote_process_arch.IsValid()) {
|
|
process_arch = remote_process_arch;
|
|
LLDB_LOG(log, "gdb-remote had process architecture, using {0} {1}",
|
|
process_arch.GetArchitectureName(),
|
|
process_arch.GetTriple().getTriple());
|
|
} else {
|
|
process_arch = m_gdb_comm.GetHostArchitecture();
|
|
LLDB_LOG(log,
|
|
"gdb-remote did not have process architecture, using gdb-remote "
|
|
"host architecture {0} {1}",
|
|
process_arch.GetArchitectureName(),
|
|
process_arch.GetTriple().getTriple());
|
|
}
|
|
|
|
AddressableBits addressable_bits = m_gdb_comm.GetAddressableBits();
|
|
SetAddressableBitMasks(addressable_bits);
|
|
|
|
if (process_arch.IsValid()) {
|
|
const ArchSpec &target_arch = GetTarget().GetArchitecture();
|
|
if (target_arch.IsValid()) {
|
|
LLDB_LOG(log, "analyzing target arch, currently {0} {1}",
|
|
target_arch.GetArchitectureName(),
|
|
target_arch.GetTriple().getTriple());
|
|
|
|
// If the remote host is ARM and we have apple as the vendor, then
|
|
// ARM executables and shared libraries can have mixed ARM
|
|
// architectures.
|
|
// You can have an armv6 executable, and if the host is armv7, then the
|
|
// system will load the best possible architecture for all shared
|
|
// libraries it has, so we really need to take the remote host
|
|
// architecture as our defacto architecture in this case.
|
|
|
|
if ((process_arch.GetMachine() == llvm::Triple::arm ||
|
|
process_arch.GetMachine() == llvm::Triple::thumb) &&
|
|
process_arch.GetTriple().getVendor() == llvm::Triple::Apple) {
|
|
GetTarget().SetArchitecture(process_arch);
|
|
LLDB_LOG(log,
|
|
"remote process is ARM/Apple, "
|
|
"setting target arch to {0} {1}",
|
|
process_arch.GetArchitectureName(),
|
|
process_arch.GetTriple().getTriple());
|
|
} else {
|
|
// Fill in what is missing in the triple
|
|
const llvm::Triple &remote_triple = process_arch.GetTriple();
|
|
llvm::Triple new_target_triple = target_arch.GetTriple();
|
|
if (new_target_triple.getVendorName().size() == 0) {
|
|
new_target_triple.setVendor(remote_triple.getVendor());
|
|
|
|
if (new_target_triple.getOSName().size() == 0) {
|
|
new_target_triple.setOS(remote_triple.getOS());
|
|
|
|
if (new_target_triple.getEnvironmentName().size() == 0)
|
|
new_target_triple.setEnvironment(remote_triple.getEnvironment());
|
|
}
|
|
|
|
ArchSpec new_target_arch = target_arch;
|
|
new_target_arch.SetTriple(new_target_triple);
|
|
GetTarget().SetArchitecture(new_target_arch);
|
|
}
|
|
}
|
|
|
|
LLDB_LOG(log,
|
|
"final target arch after adjustments for remote architecture: "
|
|
"{0} {1}",
|
|
target_arch.GetArchitectureName(),
|
|
target_arch.GetTriple().getTriple());
|
|
} else {
|
|
// The target doesn't have a valid architecture yet, set it from the
|
|
// architecture we got from the remote GDB server
|
|
GetTarget().SetArchitecture(process_arch);
|
|
}
|
|
}
|
|
|
|
// Target and Process are reasonably initailized;
|
|
// load any binaries we have metadata for / set load address.
|
|
LoadStubBinaries();
|
|
MaybeLoadExecutableModule();
|
|
|
|
// Find out which StructuredDataPlugins are supported by the debug monitor.
|
|
// These plugins transmit data over async $J packets.
|
|
if (StructuredData::Array *supported_packets =
|
|
m_gdb_comm.GetSupportedStructuredDataPlugins())
|
|
MapSupportedStructuredDataPlugins(*supported_packets);
|
|
|
|
// If connected to LLDB ("native-signals+"), use signal defs for
|
|
// the remote platform. If connected to GDB, just use the standard set.
|
|
if (!m_gdb_comm.UsesNativeSignals()) {
|
|
SetUnixSignals(std::make_shared<GDBRemoteSignals>());
|
|
} else {
|
|
PlatformSP platform_sp = GetTarget().GetPlatform();
|
|
if (platform_sp && platform_sp->IsConnected())
|
|
SetUnixSignals(platform_sp->GetUnixSignals());
|
|
else
|
|
SetUnixSignals(UnixSignals::Create(GetTarget().GetArchitecture()));
|
|
}
|
|
}
|
|
|
|
void ProcessGDBRemote::LoadStubBinaries() {
|
|
// The remote stub may know about the "main binary" in
|
|
// the context of a firmware debug session, and can
|
|
// give us a UUID and an address/slide of where the
|
|
// binary is loaded in memory.
|
|
UUID standalone_uuid;
|
|
addr_t standalone_value;
|
|
bool standalone_value_is_offset;
|
|
if (m_gdb_comm.GetProcessStandaloneBinary(standalone_uuid, standalone_value,
|
|
standalone_value_is_offset)) {
|
|
ModuleSP module_sp;
|
|
|
|
if (standalone_uuid.IsValid()) {
|
|
const bool force_symbol_search = true;
|
|
const bool notify = true;
|
|
const bool set_address_in_target = true;
|
|
const bool allow_memory_image_last_resort = false;
|
|
DynamicLoader::LoadBinaryWithUUIDAndAddress(
|
|
this, "", standalone_uuid, standalone_value,
|
|
standalone_value_is_offset, force_symbol_search, notify,
|
|
set_address_in_target, allow_memory_image_last_resort);
|
|
}
|
|
}
|
|
|
|
// The remote stub may know about a list of binaries to
|
|
// force load into the process -- a firmware type situation
|
|
// where multiple binaries are present in virtual memory,
|
|
// and we are only given the addresses of the binaries.
|
|
// Not intended for use with userland debugging, when we use
|
|
// a DynamicLoader plugin that knows how to find the loaded
|
|
// binaries, and will track updates as binaries are added.
|
|
|
|
std::vector<addr_t> bin_addrs = m_gdb_comm.GetProcessStandaloneBinaries();
|
|
if (bin_addrs.size()) {
|
|
UUID uuid;
|
|
const bool value_is_slide = false;
|
|
for (addr_t addr : bin_addrs) {
|
|
const bool notify = true;
|
|
// First see if this is a special platform
|
|
// binary that may determine the DynamicLoader and
|
|
// Platform to be used in this Process and Target.
|
|
if (GetTarget()
|
|
.GetDebugger()
|
|
.GetPlatformList()
|
|
.LoadPlatformBinaryAndSetup(this, addr, notify))
|
|
continue;
|
|
|
|
const bool force_symbol_search = true;
|
|
const bool set_address_in_target = true;
|
|
const bool allow_memory_image_last_resort = false;
|
|
// Second manually load this binary into the Target.
|
|
DynamicLoader::LoadBinaryWithUUIDAndAddress(
|
|
this, llvm::StringRef(), uuid, addr, value_is_slide,
|
|
force_symbol_search, notify, set_address_in_target,
|
|
allow_memory_image_last_resort);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessGDBRemote::MaybeLoadExecutableModule() {
|
|
ModuleSP module_sp = GetTarget().GetExecutableModule();
|
|
if (!module_sp)
|
|
return;
|
|
|
|
std::optional<QOffsets> offsets = m_gdb_comm.GetQOffsets();
|
|
if (!offsets)
|
|
return;
|
|
|
|
bool is_uniform =
|
|
size_t(llvm::count(offsets->offsets, offsets->offsets[0])) ==
|
|
offsets->offsets.size();
|
|
if (!is_uniform)
|
|
return; // TODO: Handle non-uniform responses.
|
|
|
|
bool changed = false;
|
|
module_sp->SetLoadAddress(GetTarget(), offsets->offsets[0],
|
|
/*value_is_offset=*/true, changed);
|
|
if (changed) {
|
|
ModuleList list;
|
|
list.Append(module_sp);
|
|
m_process->GetTarget().ModulesDidLoad(list);
|
|
}
|
|
}
|
|
|
|
void ProcessGDBRemote::DidLaunch() {
|
|
ArchSpec process_arch;
|
|
DidLaunchOrAttach(process_arch);
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoAttachToProcessWithID(
|
|
lldb::pid_t attach_pid, const ProcessAttachInfo &attach_info) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
Status error;
|
|
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s()", __FUNCTION__);
|
|
|
|
// Clear out and clean up from any current state
|
|
Clear();
|
|
if (attach_pid != LLDB_INVALID_PROCESS_ID) {
|
|
error = EstablishConnectionIfNeeded(attach_info);
|
|
if (error.Success()) {
|
|
m_gdb_comm.SetDetachOnError(attach_info.GetDetachOnError());
|
|
|
|
char packet[64];
|
|
const int packet_len =
|
|
::snprintf(packet, sizeof(packet), "vAttach;%" PRIx64, attach_pid);
|
|
SetID(attach_pid);
|
|
auto data_sp =
|
|
std::make_shared<EventDataBytes>(llvm::StringRef(packet, packet_len));
|
|
m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue, data_sp);
|
|
} else
|
|
SetExitStatus(-1, error.AsCString());
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoAttachToProcessWithName(
|
|
const char *process_name, const ProcessAttachInfo &attach_info) {
|
|
Status error;
|
|
// Clear out and clean up from any current state
|
|
Clear();
|
|
|
|
if (process_name && process_name[0]) {
|
|
error = EstablishConnectionIfNeeded(attach_info);
|
|
if (error.Success()) {
|
|
StreamString packet;
|
|
|
|
m_gdb_comm.SetDetachOnError(attach_info.GetDetachOnError());
|
|
|
|
if (attach_info.GetWaitForLaunch()) {
|
|
if (!m_gdb_comm.GetVAttachOrWaitSupported()) {
|
|
packet.PutCString("vAttachWait");
|
|
} else {
|
|
if (attach_info.GetIgnoreExisting())
|
|
packet.PutCString("vAttachWait");
|
|
else
|
|
packet.PutCString("vAttachOrWait");
|
|
}
|
|
} else
|
|
packet.PutCString("vAttachName");
|
|
packet.PutChar(';');
|
|
packet.PutBytesAsRawHex8(process_name, strlen(process_name),
|
|
endian::InlHostByteOrder(),
|
|
endian::InlHostByteOrder());
|
|
|
|
auto data_sp = std::make_shared<EventDataBytes>(packet.GetString());
|
|
m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue, data_sp);
|
|
|
|
} else
|
|
SetExitStatus(-1, error.AsCString());
|
|
}
|
|
return error;
|
|
}
|
|
|
|
llvm::Expected<TraceSupportedResponse> ProcessGDBRemote::TraceSupported() {
|
|
return m_gdb_comm.SendTraceSupported(GetInterruptTimeout());
|
|
}
|
|
|
|
llvm::Error ProcessGDBRemote::TraceStop(const TraceStopRequest &request) {
|
|
return m_gdb_comm.SendTraceStop(request, GetInterruptTimeout());
|
|
}
|
|
|
|
llvm::Error ProcessGDBRemote::TraceStart(const llvm::json::Value &request) {
|
|
return m_gdb_comm.SendTraceStart(request, GetInterruptTimeout());
|
|
}
|
|
|
|
llvm::Expected<std::string>
|
|
ProcessGDBRemote::TraceGetState(llvm::StringRef type) {
|
|
return m_gdb_comm.SendTraceGetState(type, GetInterruptTimeout());
|
|
}
|
|
|
|
llvm::Expected<std::vector<uint8_t>>
|
|
ProcessGDBRemote::TraceGetBinaryData(const TraceGetBinaryDataRequest &request) {
|
|
return m_gdb_comm.SendTraceGetBinaryData(request, GetInterruptTimeout());
|
|
}
|
|
|
|
void ProcessGDBRemote::DidExit() {
|
|
// When we exit, disconnect from the GDB server communications
|
|
m_gdb_comm.Disconnect();
|
|
}
|
|
|
|
void ProcessGDBRemote::DidAttach(ArchSpec &process_arch) {
|
|
// If you can figure out what the architecture is, fill it in here.
|
|
process_arch.Clear();
|
|
DidLaunchOrAttach(process_arch);
|
|
}
|
|
|
|
Status ProcessGDBRemote::WillResume() {
|
|
m_continue_c_tids.clear();
|
|
m_continue_C_tids.clear();
|
|
m_continue_s_tids.clear();
|
|
m_continue_S_tids.clear();
|
|
m_jstopinfo_sp.reset();
|
|
m_jthreadsinfo_sp.reset();
|
|
return Status();
|
|
}
|
|
|
|
bool ProcessGDBRemote::SupportsReverseDirection() {
|
|
return m_gdb_comm.GetReverseStepSupported() ||
|
|
m_gdb_comm.GetReverseContinueSupported();
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoResume(RunDirection direction) {
|
|
Status error;
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
LLDB_LOGF(log, "ProcessGDBRemote::Resume(%s)",
|
|
direction == RunDirection::eRunForward ? "" : "reverse");
|
|
|
|
ListenerSP listener_sp(
|
|
Listener::MakeListener("gdb-remote.resume-packet-sent"));
|
|
if (listener_sp->StartListeningForEvents(
|
|
&m_gdb_comm, GDBRemoteClientBase::eBroadcastBitRunPacketSent)) {
|
|
listener_sp->StartListeningForEvents(
|
|
&m_async_broadcaster,
|
|
ProcessGDBRemote::eBroadcastBitAsyncThreadDidExit);
|
|
|
|
const size_t num_threads = GetThreadList().GetSize();
|
|
|
|
StreamString continue_packet;
|
|
bool continue_packet_error = false;
|
|
// Number of threads continuing with "c", i.e. continuing without a signal
|
|
// to deliver.
|
|
const size_t num_continue_c_tids = m_continue_c_tids.size();
|
|
// Number of threads continuing with "C", i.e. continuing with a signal to
|
|
// deliver.
|
|
const size_t num_continue_C_tids = m_continue_C_tids.size();
|
|
// Number of threads continuing with "s", i.e. single-stepping.
|
|
const size_t num_continue_s_tids = m_continue_s_tids.size();
|
|
// Number of threads continuing with "S", i.e. single-stepping with a signal
|
|
// to deliver.
|
|
const size_t num_continue_S_tids = m_continue_S_tids.size();
|
|
if (direction == RunDirection::eRunForward &&
|
|
m_gdb_comm.HasAnyVContSupport()) {
|
|
std::string pid_prefix;
|
|
if (m_gdb_comm.GetMultiprocessSupported())
|
|
pid_prefix = llvm::formatv("p{0:x-}.", GetID());
|
|
|
|
if (num_continue_c_tids == num_threads ||
|
|
(m_continue_c_tids.empty() && m_continue_C_tids.empty() &&
|
|
m_continue_s_tids.empty() && m_continue_S_tids.empty())) {
|
|
// All threads are continuing
|
|
if (m_gdb_comm.GetMultiprocessSupported())
|
|
continue_packet.Format("vCont;c:{0}-1", pid_prefix);
|
|
else
|
|
continue_packet.PutCString("c");
|
|
} else {
|
|
continue_packet.PutCString("vCont");
|
|
|
|
if (!m_continue_c_tids.empty()) {
|
|
if (m_gdb_comm.GetVContSupported('c')) {
|
|
for (tid_collection::const_iterator
|
|
t_pos = m_continue_c_tids.begin(),
|
|
t_end = m_continue_c_tids.end();
|
|
t_pos != t_end; ++t_pos)
|
|
continue_packet.Format(";c:{0}{1:x-}", pid_prefix, *t_pos);
|
|
} else
|
|
continue_packet_error = true;
|
|
}
|
|
|
|
if (!continue_packet_error && !m_continue_C_tids.empty()) {
|
|
if (m_gdb_comm.GetVContSupported('C')) {
|
|
for (tid_sig_collection::const_iterator
|
|
s_pos = m_continue_C_tids.begin(),
|
|
s_end = m_continue_C_tids.end();
|
|
s_pos != s_end; ++s_pos)
|
|
continue_packet.Format(";C{0:x-2}:{1}{2:x-}", s_pos->second,
|
|
pid_prefix, s_pos->first);
|
|
} else
|
|
continue_packet_error = true;
|
|
}
|
|
|
|
if (!continue_packet_error && !m_continue_s_tids.empty()) {
|
|
if (m_gdb_comm.GetVContSupported('s')) {
|
|
for (tid_collection::const_iterator
|
|
t_pos = m_continue_s_tids.begin(),
|
|
t_end = m_continue_s_tids.end();
|
|
t_pos != t_end; ++t_pos)
|
|
continue_packet.Format(";s:{0}{1:x-}", pid_prefix, *t_pos);
|
|
} else
|
|
continue_packet_error = true;
|
|
}
|
|
|
|
if (!continue_packet_error && !m_continue_S_tids.empty()) {
|
|
if (m_gdb_comm.GetVContSupported('S')) {
|
|
for (tid_sig_collection::const_iterator
|
|
s_pos = m_continue_S_tids.begin(),
|
|
s_end = m_continue_S_tids.end();
|
|
s_pos != s_end; ++s_pos)
|
|
continue_packet.Format(";S{0:x-2}:{1}{2:x-}", s_pos->second,
|
|
pid_prefix, s_pos->first);
|
|
} else
|
|
continue_packet_error = true;
|
|
}
|
|
|
|
if (continue_packet_error)
|
|
continue_packet.Clear();
|
|
}
|
|
} else
|
|
continue_packet_error = true;
|
|
|
|
if (direction == RunDirection::eRunForward && continue_packet_error) {
|
|
// Either no vCont support, or we tried to use part of the vCont packet
|
|
// that wasn't supported by the remote GDB server. We need to try and
|
|
// make a simple packet that can do our continue.
|
|
if (num_continue_c_tids > 0) {
|
|
if (num_continue_c_tids == num_threads) {
|
|
// All threads are resuming...
|
|
m_gdb_comm.SetCurrentThreadForRun(-1);
|
|
continue_packet.PutChar('c');
|
|
continue_packet_error = false;
|
|
} else if (num_continue_c_tids == 1 && num_continue_C_tids == 0 &&
|
|
num_continue_s_tids == 0 && num_continue_S_tids == 0) {
|
|
// Only one thread is continuing
|
|
m_gdb_comm.SetCurrentThreadForRun(m_continue_c_tids.front());
|
|
continue_packet.PutChar('c');
|
|
continue_packet_error = false;
|
|
}
|
|
}
|
|
|
|
if (continue_packet_error && num_continue_C_tids > 0) {
|
|
if ((num_continue_C_tids + num_continue_c_tids) == num_threads &&
|
|
num_continue_C_tids > 0 && num_continue_s_tids == 0 &&
|
|
num_continue_S_tids == 0) {
|
|
const int continue_signo = m_continue_C_tids.front().second;
|
|
// Only one thread is continuing
|
|
if (num_continue_C_tids > 1) {
|
|
// More that one thread with a signal, yet we don't have vCont
|
|
// support and we are being asked to resume each thread with a
|
|
// signal, we need to make sure they are all the same signal, or we
|
|
// can't issue the continue accurately with the current support...
|
|
if (num_continue_C_tids > 1) {
|
|
continue_packet_error = false;
|
|
for (size_t i = 1; i < m_continue_C_tids.size(); ++i) {
|
|
if (m_continue_C_tids[i].second != continue_signo)
|
|
continue_packet_error = true;
|
|
}
|
|
}
|
|
if (!continue_packet_error)
|
|
m_gdb_comm.SetCurrentThreadForRun(-1);
|
|
} else {
|
|
// Set the continue thread ID
|
|
continue_packet_error = false;
|
|
m_gdb_comm.SetCurrentThreadForRun(m_continue_C_tids.front().first);
|
|
}
|
|
if (!continue_packet_error) {
|
|
// Add threads continuing with the same signo...
|
|
continue_packet.Printf("C%2.2x", continue_signo);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (continue_packet_error && num_continue_s_tids > 0) {
|
|
if (num_continue_s_tids == num_threads) {
|
|
// All threads are resuming...
|
|
m_gdb_comm.SetCurrentThreadForRun(-1);
|
|
|
|
continue_packet.PutChar('s');
|
|
|
|
continue_packet_error = false;
|
|
} else if (num_continue_c_tids == 0 && num_continue_C_tids == 0 &&
|
|
num_continue_s_tids == 1 && num_continue_S_tids == 0) {
|
|
// Only one thread is stepping
|
|
m_gdb_comm.SetCurrentThreadForRun(m_continue_s_tids.front());
|
|
continue_packet.PutChar('s');
|
|
continue_packet_error = false;
|
|
}
|
|
}
|
|
|
|
if (!continue_packet_error && num_continue_S_tids > 0) {
|
|
if (num_continue_S_tids == num_threads) {
|
|
const int step_signo = m_continue_S_tids.front().second;
|
|
// Are all threads trying to step with the same signal?
|
|
continue_packet_error = false;
|
|
if (num_continue_S_tids > 1) {
|
|
for (size_t i = 1; i < num_threads; ++i) {
|
|
if (m_continue_S_tids[i].second != step_signo)
|
|
continue_packet_error = true;
|
|
}
|
|
}
|
|
if (!continue_packet_error) {
|
|
// Add threads stepping with the same signo...
|
|
m_gdb_comm.SetCurrentThreadForRun(-1);
|
|
continue_packet.Printf("S%2.2x", step_signo);
|
|
}
|
|
} else if (num_continue_c_tids == 0 && num_continue_C_tids == 0 &&
|
|
num_continue_s_tids == 0 && num_continue_S_tids == 1) {
|
|
// Only one thread is stepping with signal
|
|
m_gdb_comm.SetCurrentThreadForRun(m_continue_S_tids.front().first);
|
|
continue_packet.Printf("S%2.2x", m_continue_S_tids.front().second);
|
|
continue_packet_error = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (direction == RunDirection::eRunReverse) {
|
|
if (num_continue_s_tids > 0 || num_continue_S_tids > 0) {
|
|
if (!m_gdb_comm.GetReverseStepSupported()) {
|
|
LLDB_LOGF(log, "ProcessGDBRemote::DoResume: target does not "
|
|
"support reverse-stepping");
|
|
return Status::FromErrorString(
|
|
"target does not support reverse-stepping");
|
|
}
|
|
|
|
if (num_continue_S_tids > 0) {
|
|
LLDB_LOGF(
|
|
log,
|
|
"ProcessGDBRemote::DoResume: Signals not supported in reverse");
|
|
return Status::FromErrorString(
|
|
"can't deliver signals while running in reverse");
|
|
}
|
|
|
|
if (num_continue_s_tids > 1) {
|
|
LLDB_LOGF(log, "ProcessGDBRemote::DoResume: can't step multiple "
|
|
"threads in reverse");
|
|
return Status::FromErrorString(
|
|
"can't step multiple threads while reverse-stepping");
|
|
}
|
|
|
|
m_gdb_comm.SetCurrentThreadForRun(m_continue_s_tids.front());
|
|
continue_packet.PutCString("bs");
|
|
} else {
|
|
if (!m_gdb_comm.GetReverseContinueSupported()) {
|
|
LLDB_LOGF(log, "ProcessGDBRemote::DoResume: target does not "
|
|
"support reverse-continue");
|
|
return Status::FromErrorString(
|
|
"target does not support reverse-continue");
|
|
}
|
|
|
|
if (num_continue_C_tids > 0) {
|
|
LLDB_LOGF(
|
|
log,
|
|
"ProcessGDBRemote::DoResume: Signals not supported in reverse");
|
|
return Status::FromErrorString(
|
|
"can't deliver signals while running in reverse");
|
|
}
|
|
|
|
// All threads continue whether requested or not ---
|
|
// we can't change how threads ran in the past.
|
|
continue_packet.PutCString("bc");
|
|
}
|
|
|
|
continue_packet_error = false;
|
|
}
|
|
|
|
if (continue_packet_error) {
|
|
return Status::FromErrorString(
|
|
"can't make continue packet for this resume");
|
|
} else {
|
|
EventSP event_sp;
|
|
if (!m_async_thread.IsJoinable()) {
|
|
error = Status::FromErrorString(
|
|
"Trying to resume but the async thread is dead.");
|
|
LLDB_LOGF(log, "ProcessGDBRemote::DoResume: Trying to resume but the "
|
|
"async thread is dead.");
|
|
return error;
|
|
}
|
|
|
|
auto data_sp =
|
|
std::make_shared<EventDataBytes>(continue_packet.GetString());
|
|
m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue, data_sp);
|
|
|
|
if (!listener_sp->GetEvent(event_sp, ResumeTimeout())) {
|
|
error = Status::FromErrorString("Resume timed out.");
|
|
LLDB_LOGF(log, "ProcessGDBRemote::DoResume: Resume timed out.");
|
|
} else if (event_sp->BroadcasterIs(&m_async_broadcaster)) {
|
|
error = Status::FromErrorString(
|
|
"Broadcast continue, but the async thread was "
|
|
"killed before we got an ack back.");
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::DoResume: Broadcast continue, but the "
|
|
"async thread was killed before we got an ack back.");
|
|
return error;
|
|
}
|
|
}
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void ProcessGDBRemote::ClearThreadIDList() {
|
|
std::lock_guard<std::recursive_mutex> guard(m_thread_list_real.GetMutex());
|
|
m_thread_ids.clear();
|
|
m_thread_pcs.clear();
|
|
}
|
|
|
|
size_t ProcessGDBRemote::UpdateThreadIDsFromStopReplyThreadsValue(
|
|
llvm::StringRef value) {
|
|
m_thread_ids.clear();
|
|
lldb::pid_t pid = m_gdb_comm.GetCurrentProcessID();
|
|
StringExtractorGDBRemote thread_ids{value};
|
|
|
|
do {
|
|
auto pid_tid = thread_ids.GetPidTid(pid);
|
|
if (pid_tid && pid_tid->first == pid) {
|
|
lldb::tid_t tid = pid_tid->second;
|
|
if (tid != LLDB_INVALID_THREAD_ID &&
|
|
tid != StringExtractorGDBRemote::AllProcesses)
|
|
m_thread_ids.push_back(tid);
|
|
}
|
|
} while (thread_ids.GetChar() == ',');
|
|
|
|
return m_thread_ids.size();
|
|
}
|
|
|
|
size_t ProcessGDBRemote::UpdateThreadPCsFromStopReplyThreadsValue(
|
|
llvm::StringRef value) {
|
|
m_thread_pcs.clear();
|
|
for (llvm::StringRef x : llvm::split(value, ',')) {
|
|
lldb::addr_t pc;
|
|
if (llvm::to_integer(x, pc, 16))
|
|
m_thread_pcs.push_back(pc);
|
|
}
|
|
return m_thread_pcs.size();
|
|
}
|
|
|
|
bool ProcessGDBRemote::UpdateThreadIDList() {
|
|
std::lock_guard<std::recursive_mutex> guard(m_thread_list_real.GetMutex());
|
|
|
|
if (m_jthreadsinfo_sp) {
|
|
// If we have the JSON threads info, we can get the thread list from that
|
|
StructuredData::Array *thread_infos = m_jthreadsinfo_sp->GetAsArray();
|
|
if (thread_infos && thread_infos->GetSize() > 0) {
|
|
m_thread_ids.clear();
|
|
m_thread_pcs.clear();
|
|
thread_infos->ForEach([this](StructuredData::Object *object) -> bool {
|
|
StructuredData::Dictionary *thread_dict = object->GetAsDictionary();
|
|
if (thread_dict) {
|
|
// Set the thread stop info from the JSON dictionary
|
|
SetThreadStopInfo(thread_dict);
|
|
lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
|
|
if (thread_dict->GetValueForKeyAsInteger<lldb::tid_t>("tid", tid))
|
|
m_thread_ids.push_back(tid);
|
|
}
|
|
return true; // Keep iterating through all thread_info objects
|
|
});
|
|
}
|
|
if (!m_thread_ids.empty())
|
|
return true;
|
|
} else {
|
|
// See if we can get the thread IDs from the current stop reply packets
|
|
// that might contain a "threads" key/value pair
|
|
|
|
if (m_last_stop_packet) {
|
|
// Get the thread stop info
|
|
StringExtractorGDBRemote &stop_info = *m_last_stop_packet;
|
|
const std::string &stop_info_str = std::string(stop_info.GetStringRef());
|
|
|
|
m_thread_pcs.clear();
|
|
const size_t thread_pcs_pos = stop_info_str.find(";thread-pcs:");
|
|
if (thread_pcs_pos != std::string::npos) {
|
|
const size_t start = thread_pcs_pos + strlen(";thread-pcs:");
|
|
const size_t end = stop_info_str.find(';', start);
|
|
if (end != std::string::npos) {
|
|
std::string value = stop_info_str.substr(start, end - start);
|
|
UpdateThreadPCsFromStopReplyThreadsValue(value);
|
|
}
|
|
}
|
|
|
|
const size_t threads_pos = stop_info_str.find(";threads:");
|
|
if (threads_pos != std::string::npos) {
|
|
const size_t start = threads_pos + strlen(";threads:");
|
|
const size_t end = stop_info_str.find(';', start);
|
|
if (end != std::string::npos) {
|
|
std::string value = stop_info_str.substr(start, end - start);
|
|
if (UpdateThreadIDsFromStopReplyThreadsValue(value))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool sequence_mutex_unavailable = false;
|
|
m_gdb_comm.GetCurrentThreadIDs(m_thread_ids, sequence_mutex_unavailable);
|
|
if (sequence_mutex_unavailable) {
|
|
return false; // We just didn't get the list
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ProcessGDBRemote::DoUpdateThreadList(ThreadList &old_thread_list,
|
|
ThreadList &new_thread_list) {
|
|
// locker will keep a mutex locked until it goes out of scope
|
|
Log *log = GetLog(GDBRLog::Thread);
|
|
LLDB_LOGV(log, "pid = {0}", GetID());
|
|
|
|
size_t num_thread_ids = m_thread_ids.size();
|
|
// The "m_thread_ids" thread ID list should always be updated after each stop
|
|
// reply packet, but in case it isn't, update it here.
|
|
if (num_thread_ids == 0) {
|
|
if (!UpdateThreadIDList())
|
|
return false;
|
|
num_thread_ids = m_thread_ids.size();
|
|
}
|
|
|
|
ThreadList old_thread_list_copy(old_thread_list);
|
|
if (num_thread_ids > 0) {
|
|
for (size_t i = 0; i < num_thread_ids; ++i) {
|
|
lldb::tid_t tid = m_thread_ids[i];
|
|
ThreadSP thread_sp(
|
|
old_thread_list_copy.RemoveThreadByProtocolID(tid, false));
|
|
if (!thread_sp) {
|
|
thread_sp = std::make_shared<ThreadGDBRemote>(*this, tid);
|
|
LLDB_LOGV(log, "Making new thread: {0} for thread ID: {1:x}.",
|
|
thread_sp.get(), thread_sp->GetID());
|
|
} else {
|
|
LLDB_LOGV(log, "Found old thread: {0} for thread ID: {1:x}.",
|
|
thread_sp.get(), thread_sp->GetID());
|
|
}
|
|
|
|
SetThreadPc(thread_sp, i);
|
|
new_thread_list.AddThreadSortedByIndexID(thread_sp);
|
|
}
|
|
}
|
|
|
|
// Whatever that is left in old_thread_list_copy are not present in
|
|
// new_thread_list. Remove non-existent threads from internal id table.
|
|
size_t old_num_thread_ids = old_thread_list_copy.GetSize(false);
|
|
for (size_t i = 0; i < old_num_thread_ids; i++) {
|
|
ThreadSP old_thread_sp(old_thread_list_copy.GetThreadAtIndex(i, false));
|
|
if (old_thread_sp) {
|
|
lldb::tid_t old_thread_id = old_thread_sp->GetProtocolID();
|
|
m_thread_id_to_index_id_map.erase(old_thread_id);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ProcessGDBRemote::SetThreadPc(const ThreadSP &thread_sp, uint64_t index) {
|
|
if (m_thread_ids.size() == m_thread_pcs.size() && thread_sp.get() &&
|
|
GetByteOrder() != eByteOrderInvalid) {
|
|
ThreadGDBRemote *gdb_thread =
|
|
static_cast<ThreadGDBRemote *>(thread_sp.get());
|
|
RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
|
|
if (reg_ctx_sp) {
|
|
uint32_t pc_regnum = reg_ctx_sp->ConvertRegisterKindToRegisterNumber(
|
|
eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC);
|
|
if (pc_regnum != LLDB_INVALID_REGNUM) {
|
|
gdb_thread->PrivateSetRegisterValue(pc_regnum, m_thread_pcs[index]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ProcessGDBRemote::GetThreadStopInfoFromJSON(
|
|
ThreadGDBRemote *thread, const StructuredData::ObjectSP &thread_infos_sp) {
|
|
// See if we got thread stop infos for all threads via the "jThreadsInfo"
|
|
// packet
|
|
if (thread_infos_sp) {
|
|
StructuredData::Array *thread_infos = thread_infos_sp->GetAsArray();
|
|
if (thread_infos) {
|
|
lldb::tid_t tid;
|
|
const size_t n = thread_infos->GetSize();
|
|
for (size_t i = 0; i < n; ++i) {
|
|
StructuredData::Dictionary *thread_dict =
|
|
thread_infos->GetItemAtIndex(i)->GetAsDictionary();
|
|
if (thread_dict) {
|
|
if (thread_dict->GetValueForKeyAsInteger<lldb::tid_t>(
|
|
"tid", tid, LLDB_INVALID_THREAD_ID)) {
|
|
if (tid == thread->GetID())
|
|
return (bool)SetThreadStopInfo(thread_dict);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ProcessGDBRemote::CalculateThreadStopInfo(ThreadGDBRemote *thread) {
|
|
// See if we got thread stop infos for all threads via the "jThreadsInfo"
|
|
// packet
|
|
if (GetThreadStopInfoFromJSON(thread, m_jthreadsinfo_sp))
|
|
return true;
|
|
|
|
// See if we got thread stop info for any threads valid stop info reasons
|
|
// threads via the "jstopinfo" packet stop reply packet key/value pair?
|
|
if (m_jstopinfo_sp) {
|
|
// If we have "jstopinfo" then we have stop descriptions for all threads
|
|
// that have stop reasons, and if there is no entry for a thread, then it
|
|
// has no stop reason.
|
|
if (!GetThreadStopInfoFromJSON(thread, m_jstopinfo_sp))
|
|
thread->SetStopInfo(StopInfoSP());
|
|
return true;
|
|
}
|
|
|
|
// Fall back to using the qThreadStopInfo packet
|
|
StringExtractorGDBRemote stop_packet;
|
|
if (GetGDBRemote().GetThreadStopInfo(thread->GetProtocolID(), stop_packet))
|
|
return SetThreadStopInfo(stop_packet) == eStateStopped;
|
|
return false;
|
|
}
|
|
|
|
void ProcessGDBRemote::ParseExpeditedRegisters(
|
|
ExpeditedRegisterMap &expedited_register_map, ThreadSP thread_sp) {
|
|
ThreadGDBRemote *gdb_thread = static_cast<ThreadGDBRemote *>(thread_sp.get());
|
|
RegisterContextSP gdb_reg_ctx_sp(gdb_thread->GetRegisterContext());
|
|
|
|
for (const auto &pair : expedited_register_map) {
|
|
StringExtractor reg_value_extractor(pair.second);
|
|
WritableDataBufferSP buffer_sp(
|
|
new DataBufferHeap(reg_value_extractor.GetStringRef().size() / 2, 0));
|
|
reg_value_extractor.GetHexBytes(buffer_sp->GetData(), '\xcc');
|
|
uint32_t lldb_regnum = gdb_reg_ctx_sp->ConvertRegisterKindToRegisterNumber(
|
|
eRegisterKindProcessPlugin, pair.first);
|
|
gdb_thread->PrivateSetRegisterValue(lldb_regnum, buffer_sp->GetData());
|
|
}
|
|
}
|
|
|
|
ThreadSP ProcessGDBRemote::SetThreadStopInfo(
|
|
lldb::tid_t tid, ExpeditedRegisterMap &expedited_register_map,
|
|
uint8_t signo, const std::string &thread_name, const std::string &reason,
|
|
const std::string &description, uint32_t exc_type,
|
|
const std::vector<addr_t> &exc_data, addr_t thread_dispatch_qaddr,
|
|
bool queue_vars_valid, // Set to true if queue_name, queue_kind and
|
|
// queue_serial are valid
|
|
LazyBool associated_with_dispatch_queue, addr_t dispatch_queue_t,
|
|
std::string &queue_name, QueueKind queue_kind, uint64_t queue_serial) {
|
|
|
|
if (tid == LLDB_INVALID_THREAD_ID)
|
|
return nullptr;
|
|
|
|
ThreadSP thread_sp;
|
|
// Scope for "locker" below
|
|
{
|
|
// m_thread_list_real does have its own mutex, but we need to hold onto the
|
|
// mutex between the call to m_thread_list_real.FindThreadByID(...) and the
|
|
// m_thread_list_real.AddThread(...) so it doesn't change on us
|
|
std::lock_guard<std::recursive_mutex> guard(m_thread_list_real.GetMutex());
|
|
thread_sp = m_thread_list_real.FindThreadByProtocolID(tid, false);
|
|
|
|
if (!thread_sp) {
|
|
// Create the thread if we need to
|
|
thread_sp = std::make_shared<ThreadGDBRemote>(*this, tid);
|
|
m_thread_list_real.AddThread(thread_sp);
|
|
}
|
|
}
|
|
|
|
ThreadGDBRemote *gdb_thread = static_cast<ThreadGDBRemote *>(thread_sp.get());
|
|
RegisterContextSP reg_ctx_sp(gdb_thread->GetRegisterContext());
|
|
|
|
reg_ctx_sp->InvalidateIfNeeded(true);
|
|
|
|
auto iter = std::find(m_thread_ids.begin(), m_thread_ids.end(), tid);
|
|
if (iter != m_thread_ids.end())
|
|
SetThreadPc(thread_sp, iter - m_thread_ids.begin());
|
|
|
|
ParseExpeditedRegisters(expedited_register_map, thread_sp);
|
|
|
|
if (reg_ctx_sp->ReconfigureRegisterInfo()) {
|
|
// Now we have changed the offsets of all the registers, so the values
|
|
// will be corrupted.
|
|
reg_ctx_sp->InvalidateAllRegisters();
|
|
// Expedited registers values will never contain registers that would be
|
|
// resized by a reconfigure. So we are safe to continue using these
|
|
// values.
|
|
ParseExpeditedRegisters(expedited_register_map, thread_sp);
|
|
}
|
|
|
|
thread_sp->SetName(thread_name.empty() ? nullptr : thread_name.c_str());
|
|
|
|
gdb_thread->SetThreadDispatchQAddr(thread_dispatch_qaddr);
|
|
// Check if the GDB server was able to provide the queue name, kind and serial
|
|
// number
|
|
if (queue_vars_valid)
|
|
gdb_thread->SetQueueInfo(std::move(queue_name), queue_kind, queue_serial,
|
|
dispatch_queue_t, associated_with_dispatch_queue);
|
|
else
|
|
gdb_thread->ClearQueueInfo();
|
|
|
|
gdb_thread->SetAssociatedWithLibdispatchQueue(associated_with_dispatch_queue);
|
|
|
|
if (dispatch_queue_t != LLDB_INVALID_ADDRESS)
|
|
gdb_thread->SetQueueLibdispatchQueueAddress(dispatch_queue_t);
|
|
|
|
// Make sure we update our thread stop reason just once, but don't overwrite
|
|
// the stop info for threads that haven't moved:
|
|
StopInfoSP current_stop_info_sp = thread_sp->GetPrivateStopInfo(false);
|
|
if (thread_sp->GetTemporaryResumeState() == eStateSuspended &&
|
|
current_stop_info_sp) {
|
|
thread_sp->SetStopInfo(current_stop_info_sp);
|
|
return thread_sp;
|
|
}
|
|
|
|
if (!thread_sp->StopInfoIsUpToDate()) {
|
|
thread_sp->SetStopInfo(StopInfoSP());
|
|
|
|
addr_t pc = thread_sp->GetRegisterContext()->GetPC();
|
|
BreakpointSiteSP bp_site_sp =
|
|
thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc);
|
|
if (bp_site_sp && bp_site_sp->IsEnabled())
|
|
thread_sp->SetThreadStoppedAtUnexecutedBP(pc);
|
|
|
|
if (exc_type != 0) {
|
|
// For thread plan async interrupt, creating stop info on the
|
|
// original async interrupt request thread instead. If interrupt thread
|
|
// does not exist anymore we fallback to current signal receiving thread
|
|
// instead.
|
|
ThreadSP interrupt_thread;
|
|
if (m_interrupt_tid != LLDB_INVALID_THREAD_ID)
|
|
interrupt_thread = HandleThreadAsyncInterrupt(signo, description);
|
|
if (interrupt_thread)
|
|
thread_sp = interrupt_thread;
|
|
else {
|
|
const size_t exc_data_size = exc_data.size();
|
|
thread_sp->SetStopInfo(
|
|
StopInfoMachException::CreateStopReasonWithMachException(
|
|
*thread_sp, exc_type, exc_data_size,
|
|
exc_data_size >= 1 ? exc_data[0] : 0,
|
|
exc_data_size >= 2 ? exc_data[1] : 0,
|
|
exc_data_size >= 3 ? exc_data[2] : 0));
|
|
}
|
|
} else {
|
|
bool handled = false;
|
|
bool did_exec = false;
|
|
// debugserver can send reason = "none" which is equivalent
|
|
// to no reason.
|
|
if (!reason.empty() && reason != "none") {
|
|
if (reason == "trace") {
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonToTrace(*thread_sp));
|
|
handled = true;
|
|
} else if (reason == "breakpoint") {
|
|
thread_sp->SetThreadHitBreakpointSite();
|
|
if (bp_site_sp) {
|
|
// If the breakpoint is for this thread, then we'll report the hit,
|
|
// but if it is for another thread, we can just report no reason.
|
|
// We don't need to worry about stepping over the breakpoint here,
|
|
// that will be taken care of when the thread resumes and notices
|
|
// that there's a breakpoint under the pc.
|
|
handled = true;
|
|
if (bp_site_sp->ValidForThisThread(*thread_sp)) {
|
|
thread_sp->SetStopInfo(
|
|
StopInfo::CreateStopReasonWithBreakpointSiteID(
|
|
*thread_sp, bp_site_sp->GetID()));
|
|
} else {
|
|
StopInfoSP invalid_stop_info_sp;
|
|
thread_sp->SetStopInfo(invalid_stop_info_sp);
|
|
}
|
|
}
|
|
} else if (reason == "trap") {
|
|
// Let the trap just use the standard signal stop reason below...
|
|
} else if (reason == "watchpoint") {
|
|
// We will have between 1 and 3 fields in the description.
|
|
//
|
|
// \a wp_addr which is the original start address that
|
|
// lldb requested be watched, or an address that the
|
|
// hardware reported. This address should be within the
|
|
// range of a currently active watchpoint region - lldb
|
|
// should be able to find a watchpoint with this address.
|
|
//
|
|
// \a wp_index is the hardware watchpoint register number.
|
|
//
|
|
// \a wp_hit_addr is the actual address reported by the hardware,
|
|
// which may be outside the range of a region we are watching.
|
|
//
|
|
// On MIPS, we may get a false watchpoint exception where an
|
|
// access to the same 8 byte granule as a watchpoint will trigger,
|
|
// even if the access was not within the range of the watched
|
|
// region. When we get a \a wp_hit_addr outside the range of any
|
|
// set watchpoint, continue execution without making it visible to
|
|
// the user.
|
|
//
|
|
// On ARM, a related issue where a large access that starts
|
|
// before the watched region (and extends into the watched
|
|
// region) may report a hit address before the watched region.
|
|
// lldb will not find the "nearest" watchpoint to
|
|
// disable/step/re-enable it, so one of the valid watchpoint
|
|
// addresses should be provided as \a wp_addr.
|
|
StringExtractor desc_extractor(description.c_str());
|
|
// FIXME NativeThreadLinux::SetStoppedByWatchpoint sends this
|
|
// up as
|
|
// <address within wp range> <wp hw index> <actual accessed addr>
|
|
// but this is not reading the <wp hw index>. Seems like it
|
|
// wouldn't work on MIPS, where that third field is important.
|
|
addr_t wp_addr = desc_extractor.GetU64(LLDB_INVALID_ADDRESS);
|
|
addr_t wp_hit_addr = desc_extractor.GetU64(LLDB_INVALID_ADDRESS);
|
|
watch_id_t watch_id = LLDB_INVALID_WATCH_ID;
|
|
bool silently_continue = false;
|
|
WatchpointResourceSP wp_resource_sp;
|
|
if (wp_hit_addr != LLDB_INVALID_ADDRESS) {
|
|
wp_resource_sp =
|
|
m_watchpoint_resource_list.FindByAddress(wp_hit_addr);
|
|
// On MIPS, \a wp_hit_addr outside the range of a watched
|
|
// region means we should silently continue, it is a false hit.
|
|
ArchSpec::Core core = GetTarget().GetArchitecture().GetCore();
|
|
if (!wp_resource_sp && core >= ArchSpec::kCore_mips_first &&
|
|
core <= ArchSpec::kCore_mips_last)
|
|
silently_continue = true;
|
|
}
|
|
if (!wp_resource_sp && wp_addr != LLDB_INVALID_ADDRESS)
|
|
wp_resource_sp = m_watchpoint_resource_list.FindByAddress(wp_addr);
|
|
if (!wp_resource_sp) {
|
|
Log *log(GetLog(GDBRLog::Watchpoints));
|
|
LLDB_LOGF(log, "failed to find watchpoint");
|
|
watch_id = LLDB_INVALID_SITE_ID;
|
|
} else {
|
|
// LWP_TODO: This is hardcoding a single Watchpoint in a
|
|
// Resource, need to add
|
|
// StopInfo::CreateStopReasonWithWatchpointResource which
|
|
// represents all watchpoints that were tripped at this stop.
|
|
watch_id = wp_resource_sp->GetConstituentAtIndex(0)->GetID();
|
|
}
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithWatchpointID(
|
|
*thread_sp, watch_id, silently_continue));
|
|
handled = true;
|
|
} else if (reason == "exception") {
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithException(
|
|
*thread_sp, description.c_str()));
|
|
handled = true;
|
|
} else if (reason == "history boundary") {
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonHistoryBoundary(
|
|
*thread_sp, description.c_str()));
|
|
handled = true;
|
|
} else if (reason == "exec") {
|
|
did_exec = true;
|
|
thread_sp->SetStopInfo(
|
|
StopInfo::CreateStopReasonWithExec(*thread_sp));
|
|
handled = true;
|
|
} else if (reason == "processor trace") {
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonProcessorTrace(
|
|
*thread_sp, description.c_str()));
|
|
} else if (reason == "fork") {
|
|
StringExtractor desc_extractor(description.c_str());
|
|
lldb::pid_t child_pid =
|
|
desc_extractor.GetU64(LLDB_INVALID_PROCESS_ID);
|
|
lldb::tid_t child_tid = desc_extractor.GetU64(LLDB_INVALID_THREAD_ID);
|
|
thread_sp->SetStopInfo(
|
|
StopInfo::CreateStopReasonFork(*thread_sp, child_pid, child_tid));
|
|
handled = true;
|
|
} else if (reason == "vfork") {
|
|
StringExtractor desc_extractor(description.c_str());
|
|
lldb::pid_t child_pid =
|
|
desc_extractor.GetU64(LLDB_INVALID_PROCESS_ID);
|
|
lldb::tid_t child_tid = desc_extractor.GetU64(LLDB_INVALID_THREAD_ID);
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonVFork(
|
|
*thread_sp, child_pid, child_tid));
|
|
handled = true;
|
|
} else if (reason == "vforkdone") {
|
|
thread_sp->SetStopInfo(
|
|
StopInfo::CreateStopReasonVForkDone(*thread_sp));
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
if (!handled && signo && !did_exec) {
|
|
if (signo == SIGTRAP) {
|
|
// Currently we are going to assume SIGTRAP means we are either
|
|
// hitting a breakpoint or hardware single stepping.
|
|
|
|
// We can't disambiguate between stepping-to-a-breakpointsite and
|
|
// hitting-a-breakpointsite.
|
|
//
|
|
// A user can instruction-step, and be stopped at a BreakpointSite.
|
|
// Or a user can be sitting at a BreakpointSite,
|
|
// instruction-step which hits the breakpoint and the pc does not
|
|
// advance.
|
|
//
|
|
// In both cases, we're at a BreakpointSite when stopped, and
|
|
// the resume state was eStateStepping.
|
|
|
|
// Assume if we're at a BreakpointSite, we hit it.
|
|
handled = true;
|
|
addr_t pc =
|
|
thread_sp->GetRegisterContext()->GetPC() + m_breakpoint_pc_offset;
|
|
BreakpointSiteSP bp_site_sp =
|
|
thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(
|
|
pc);
|
|
|
|
// We can't know if we hit it or not. So if we are stopped at
|
|
// a BreakpointSite, assume we hit it, and should step past the
|
|
// breakpoint when we resume. This is contrary to how we handle
|
|
// BreakpointSites in any other location, but we can't know for
|
|
// sure what happened so it's a reasonable default.
|
|
if (bp_site_sp) {
|
|
if (bp_site_sp->IsEnabled())
|
|
thread_sp->SetThreadHitBreakpointSite();
|
|
|
|
if (bp_site_sp->ValidForThisThread(*thread_sp)) {
|
|
if (m_breakpoint_pc_offset != 0)
|
|
thread_sp->GetRegisterContext()->SetPC(pc);
|
|
thread_sp->SetStopInfo(
|
|
StopInfo::CreateStopReasonWithBreakpointSiteID(
|
|
*thread_sp, bp_site_sp->GetID()));
|
|
} else {
|
|
StopInfoSP invalid_stop_info_sp;
|
|
thread_sp->SetStopInfo(invalid_stop_info_sp);
|
|
}
|
|
} else {
|
|
// If we were stepping then assume the stop was the result of the
|
|
// trace. If we were not stepping then report the SIGTRAP.
|
|
if (thread_sp->GetTemporaryResumeState() == eStateStepping)
|
|
thread_sp->SetStopInfo(
|
|
StopInfo::CreateStopReasonToTrace(*thread_sp));
|
|
else
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithSignal(
|
|
*thread_sp, signo, description.c_str()));
|
|
}
|
|
}
|
|
if (!handled) {
|
|
// For thread plan async interrupt, creating stop info on the
|
|
// original async interrupt request thread instead. If interrupt
|
|
// thread does not exist anymore we fallback to current signal
|
|
// receiving thread instead.
|
|
ThreadSP interrupt_thread;
|
|
if (m_interrupt_tid != LLDB_INVALID_THREAD_ID)
|
|
interrupt_thread = HandleThreadAsyncInterrupt(signo, description);
|
|
if (interrupt_thread)
|
|
thread_sp = interrupt_thread;
|
|
else
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithSignal(
|
|
*thread_sp, signo, description.c_str()));
|
|
}
|
|
}
|
|
|
|
if (!description.empty()) {
|
|
lldb::StopInfoSP stop_info_sp(thread_sp->GetStopInfo());
|
|
if (stop_info_sp) {
|
|
const char *stop_info_desc = stop_info_sp->GetDescription();
|
|
if (!stop_info_desc || !stop_info_desc[0])
|
|
stop_info_sp->SetDescription(description.c_str());
|
|
} else {
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithException(
|
|
*thread_sp, description.c_str()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return thread_sp;
|
|
}
|
|
|
|
ThreadSP
|
|
ProcessGDBRemote::HandleThreadAsyncInterrupt(uint8_t signo,
|
|
const std::string &description) {
|
|
ThreadSP thread_sp;
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(m_thread_list_real.GetMutex());
|
|
thread_sp = m_thread_list_real.FindThreadByProtocolID(m_interrupt_tid,
|
|
/*can_update=*/false);
|
|
}
|
|
if (thread_sp)
|
|
thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithInterrupt(
|
|
*thread_sp, signo, description.c_str()));
|
|
// Clear m_interrupt_tid regardless we can find original interrupt thread or
|
|
// not.
|
|
m_interrupt_tid = LLDB_INVALID_THREAD_ID;
|
|
return thread_sp;
|
|
}
|
|
|
|
lldb::ThreadSP
|
|
ProcessGDBRemote::SetThreadStopInfo(StructuredData::Dictionary *thread_dict) {
|
|
static constexpr llvm::StringLiteral g_key_tid("tid");
|
|
static constexpr llvm::StringLiteral g_key_name("name");
|
|
static constexpr llvm::StringLiteral g_key_reason("reason");
|
|
static constexpr llvm::StringLiteral g_key_metype("metype");
|
|
static constexpr llvm::StringLiteral g_key_medata("medata");
|
|
static constexpr llvm::StringLiteral g_key_qaddr("qaddr");
|
|
static constexpr llvm::StringLiteral g_key_dispatch_queue_t(
|
|
"dispatch_queue_t");
|
|
static constexpr llvm::StringLiteral g_key_associated_with_dispatch_queue(
|
|
"associated_with_dispatch_queue");
|
|
static constexpr llvm::StringLiteral g_key_queue_name("qname");
|
|
static constexpr llvm::StringLiteral g_key_queue_kind("qkind");
|
|
static constexpr llvm::StringLiteral g_key_queue_serial_number("qserialnum");
|
|
static constexpr llvm::StringLiteral g_key_registers("registers");
|
|
static constexpr llvm::StringLiteral g_key_memory("memory");
|
|
static constexpr llvm::StringLiteral g_key_description("description");
|
|
static constexpr llvm::StringLiteral g_key_signal("signal");
|
|
|
|
// Stop with signal and thread info
|
|
lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
|
|
uint8_t signo = 0;
|
|
std::string value;
|
|
std::string thread_name;
|
|
std::string reason;
|
|
std::string description;
|
|
uint32_t exc_type = 0;
|
|
std::vector<addr_t> exc_data;
|
|
addr_t thread_dispatch_qaddr = LLDB_INVALID_ADDRESS;
|
|
ExpeditedRegisterMap expedited_register_map;
|
|
bool queue_vars_valid = false;
|
|
addr_t dispatch_queue_t = LLDB_INVALID_ADDRESS;
|
|
LazyBool associated_with_dispatch_queue = eLazyBoolCalculate;
|
|
std::string queue_name;
|
|
QueueKind queue_kind = eQueueKindUnknown;
|
|
uint64_t queue_serial_number = 0;
|
|
// Iterate through all of the thread dictionary key/value pairs from the
|
|
// structured data dictionary
|
|
|
|
// FIXME: we're silently ignoring invalid data here
|
|
thread_dict->ForEach([this, &tid, &expedited_register_map, &thread_name,
|
|
&signo, &reason, &description, &exc_type, &exc_data,
|
|
&thread_dispatch_qaddr, &queue_vars_valid,
|
|
&associated_with_dispatch_queue, &dispatch_queue_t,
|
|
&queue_name, &queue_kind, &queue_serial_number](
|
|
llvm::StringRef key,
|
|
StructuredData::Object *object) -> bool {
|
|
if (key == g_key_tid) {
|
|
// thread in big endian hex
|
|
tid = object->GetUnsignedIntegerValue(LLDB_INVALID_THREAD_ID);
|
|
} else if (key == g_key_metype) {
|
|
// exception type in big endian hex
|
|
exc_type = object->GetUnsignedIntegerValue(0);
|
|
} else if (key == g_key_medata) {
|
|
// exception data in big endian hex
|
|
StructuredData::Array *array = object->GetAsArray();
|
|
if (array) {
|
|
array->ForEach([&exc_data](StructuredData::Object *object) -> bool {
|
|
exc_data.push_back(object->GetUnsignedIntegerValue());
|
|
return true; // Keep iterating through all array items
|
|
});
|
|
}
|
|
} else if (key == g_key_name) {
|
|
thread_name = std::string(object->GetStringValue());
|
|
} else if (key == g_key_qaddr) {
|
|
thread_dispatch_qaddr =
|
|
object->GetUnsignedIntegerValue(LLDB_INVALID_ADDRESS);
|
|
} else if (key == g_key_queue_name) {
|
|
queue_vars_valid = true;
|
|
queue_name = std::string(object->GetStringValue());
|
|
} else if (key == g_key_queue_kind) {
|
|
std::string queue_kind_str = std::string(object->GetStringValue());
|
|
if (queue_kind_str == "serial") {
|
|
queue_vars_valid = true;
|
|
queue_kind = eQueueKindSerial;
|
|
} else if (queue_kind_str == "concurrent") {
|
|
queue_vars_valid = true;
|
|
queue_kind = eQueueKindConcurrent;
|
|
}
|
|
} else if (key == g_key_queue_serial_number) {
|
|
queue_serial_number = object->GetUnsignedIntegerValue(0);
|
|
if (queue_serial_number != 0)
|
|
queue_vars_valid = true;
|
|
} else if (key == g_key_dispatch_queue_t) {
|
|
dispatch_queue_t = object->GetUnsignedIntegerValue(0);
|
|
if (dispatch_queue_t != 0 && dispatch_queue_t != LLDB_INVALID_ADDRESS)
|
|
queue_vars_valid = true;
|
|
} else if (key == g_key_associated_with_dispatch_queue) {
|
|
queue_vars_valid = true;
|
|
bool associated = object->GetBooleanValue();
|
|
if (associated)
|
|
associated_with_dispatch_queue = eLazyBoolYes;
|
|
else
|
|
associated_with_dispatch_queue = eLazyBoolNo;
|
|
} else if (key == g_key_reason) {
|
|
reason = std::string(object->GetStringValue());
|
|
} else if (key == g_key_description) {
|
|
description = std::string(object->GetStringValue());
|
|
} else if (key == g_key_registers) {
|
|
StructuredData::Dictionary *registers_dict = object->GetAsDictionary();
|
|
|
|
if (registers_dict) {
|
|
registers_dict->ForEach(
|
|
[&expedited_register_map](llvm::StringRef key,
|
|
StructuredData::Object *object) -> bool {
|
|
uint32_t reg;
|
|
if (llvm::to_integer(key, reg))
|
|
expedited_register_map[reg] =
|
|
std::string(object->GetStringValue());
|
|
return true; // Keep iterating through all array items
|
|
});
|
|
}
|
|
} else if (key == g_key_memory) {
|
|
StructuredData::Array *array = object->GetAsArray();
|
|
if (array) {
|
|
array->ForEach([this](StructuredData::Object *object) -> bool {
|
|
StructuredData::Dictionary *mem_cache_dict =
|
|
object->GetAsDictionary();
|
|
if (mem_cache_dict) {
|
|
lldb::addr_t mem_cache_addr = LLDB_INVALID_ADDRESS;
|
|
if (mem_cache_dict->GetValueForKeyAsInteger<lldb::addr_t>(
|
|
"address", mem_cache_addr)) {
|
|
if (mem_cache_addr != LLDB_INVALID_ADDRESS) {
|
|
llvm::StringRef str;
|
|
if (mem_cache_dict->GetValueForKeyAsString("bytes", str)) {
|
|
StringExtractor bytes(str);
|
|
bytes.SetFilePos(0);
|
|
|
|
const size_t byte_size = bytes.GetStringRef().size() / 2;
|
|
WritableDataBufferSP data_buffer_sp(
|
|
new DataBufferHeap(byte_size, 0));
|
|
const size_t bytes_copied =
|
|
bytes.GetHexBytes(data_buffer_sp->GetData(), 0);
|
|
if (bytes_copied == byte_size)
|
|
m_memory_cache.AddL1CacheData(mem_cache_addr,
|
|
data_buffer_sp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true; // Keep iterating through all array items
|
|
});
|
|
}
|
|
|
|
} else if (key == g_key_signal)
|
|
signo = object->GetUnsignedIntegerValue(LLDB_INVALID_SIGNAL_NUMBER);
|
|
return true; // Keep iterating through all dictionary key/value pairs
|
|
});
|
|
|
|
return SetThreadStopInfo(tid, expedited_register_map, signo, thread_name,
|
|
reason, description, exc_type, exc_data,
|
|
thread_dispatch_qaddr, queue_vars_valid,
|
|
associated_with_dispatch_queue, dispatch_queue_t,
|
|
queue_name, queue_kind, queue_serial_number);
|
|
}
|
|
|
|
StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
|
|
lldb::pid_t pid = m_gdb_comm.GetCurrentProcessID();
|
|
stop_packet.SetFilePos(0);
|
|
const char stop_type = stop_packet.GetChar();
|
|
switch (stop_type) {
|
|
case 'T':
|
|
case 'S': {
|
|
// This is a bit of a hack, but it is required. If we did exec, we need to
|
|
// clear our thread lists and also know to rebuild our dynamic register
|
|
// info before we lookup and threads and populate the expedited register
|
|
// values so we need to know this right away so we can cleanup and update
|
|
// our registers.
|
|
const uint32_t stop_id = GetStopID();
|
|
if (stop_id == 0) {
|
|
// Our first stop, make sure we have a process ID, and also make sure we
|
|
// know about our registers
|
|
if (GetID() == LLDB_INVALID_PROCESS_ID && pid != LLDB_INVALID_PROCESS_ID)
|
|
SetID(pid);
|
|
BuildDynamicRegisterInfo(true);
|
|
}
|
|
// Stop with signal and thread info
|
|
lldb::pid_t stop_pid = LLDB_INVALID_PROCESS_ID;
|
|
lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
|
|
const uint8_t signo = stop_packet.GetHexU8();
|
|
llvm::StringRef key;
|
|
llvm::StringRef value;
|
|
std::string thread_name;
|
|
std::string reason;
|
|
std::string description;
|
|
uint32_t exc_type = 0;
|
|
std::vector<addr_t> exc_data;
|
|
addr_t thread_dispatch_qaddr = LLDB_INVALID_ADDRESS;
|
|
bool queue_vars_valid =
|
|
false; // says if locals below that start with "queue_" are valid
|
|
addr_t dispatch_queue_t = LLDB_INVALID_ADDRESS;
|
|
LazyBool associated_with_dispatch_queue = eLazyBoolCalculate;
|
|
std::string queue_name;
|
|
QueueKind queue_kind = eQueueKindUnknown;
|
|
uint64_t queue_serial_number = 0;
|
|
ExpeditedRegisterMap expedited_register_map;
|
|
AddressableBits addressable_bits;
|
|
while (stop_packet.GetNameColonValue(key, value)) {
|
|
if (key.compare("metype") == 0) {
|
|
// exception type in big endian hex
|
|
value.getAsInteger(16, exc_type);
|
|
} else if (key.compare("medata") == 0) {
|
|
// exception data in big endian hex
|
|
uint64_t x;
|
|
value.getAsInteger(16, x);
|
|
exc_data.push_back(x);
|
|
} else if (key.compare("thread") == 0) {
|
|
// thread-id
|
|
StringExtractorGDBRemote thread_id{value};
|
|
auto pid_tid = thread_id.GetPidTid(pid);
|
|
if (pid_tid) {
|
|
stop_pid = pid_tid->first;
|
|
tid = pid_tid->second;
|
|
} else
|
|
tid = LLDB_INVALID_THREAD_ID;
|
|
} else if (key.compare("threads") == 0) {
|
|
std::lock_guard<std::recursive_mutex> guard(
|
|
m_thread_list_real.GetMutex());
|
|
UpdateThreadIDsFromStopReplyThreadsValue(value);
|
|
} else if (key.compare("thread-pcs") == 0) {
|
|
m_thread_pcs.clear();
|
|
// A comma separated list of all threads in the current
|
|
// process that includes the thread for this stop reply packet
|
|
lldb::addr_t pc;
|
|
while (!value.empty()) {
|
|
llvm::StringRef pc_str;
|
|
std::tie(pc_str, value) = value.split(',');
|
|
if (pc_str.getAsInteger(16, pc))
|
|
pc = LLDB_INVALID_ADDRESS;
|
|
m_thread_pcs.push_back(pc);
|
|
}
|
|
} else if (key.compare("jstopinfo") == 0) {
|
|
StringExtractor json_extractor(value);
|
|
std::string json;
|
|
// Now convert the HEX bytes into a string value
|
|
json_extractor.GetHexByteString(json);
|
|
|
|
// This JSON contains thread IDs and thread stop info for all threads.
|
|
// It doesn't contain expedited registers, memory or queue info.
|
|
m_jstopinfo_sp = StructuredData::ParseJSON(json);
|
|
} else if (key.compare("hexname") == 0) {
|
|
StringExtractor name_extractor(value);
|
|
std::string name;
|
|
// Now convert the HEX bytes into a string value
|
|
name_extractor.GetHexByteString(thread_name);
|
|
} else if (key.compare("name") == 0) {
|
|
thread_name = std::string(value);
|
|
} else if (key.compare("qaddr") == 0) {
|
|
value.getAsInteger(16, thread_dispatch_qaddr);
|
|
} else if (key.compare("dispatch_queue_t") == 0) {
|
|
queue_vars_valid = true;
|
|
value.getAsInteger(16, dispatch_queue_t);
|
|
} else if (key.compare("qname") == 0) {
|
|
queue_vars_valid = true;
|
|
StringExtractor name_extractor(value);
|
|
// Now convert the HEX bytes into a string value
|
|
name_extractor.GetHexByteString(queue_name);
|
|
} else if (key.compare("qkind") == 0) {
|
|
queue_kind = llvm::StringSwitch<QueueKind>(value)
|
|
.Case("serial", eQueueKindSerial)
|
|
.Case("concurrent", eQueueKindConcurrent)
|
|
.Default(eQueueKindUnknown);
|
|
queue_vars_valid = queue_kind != eQueueKindUnknown;
|
|
} else if (key.compare("qserialnum") == 0) {
|
|
if (!value.getAsInteger(0, queue_serial_number))
|
|
queue_vars_valid = true;
|
|
} else if (key.compare("reason") == 0) {
|
|
reason = std::string(value);
|
|
} else if (key.compare("description") == 0) {
|
|
StringExtractor desc_extractor(value);
|
|
// Now convert the HEX bytes into a string value
|
|
desc_extractor.GetHexByteString(description);
|
|
} else if (key.compare("memory") == 0) {
|
|
// Expedited memory. GDB servers can choose to send back expedited
|
|
// memory that can populate the L1 memory cache in the process so that
|
|
// things like the frame pointer backchain can be expedited. This will
|
|
// help stack backtracing be more efficient by not having to send as
|
|
// many memory read requests down the remote GDB server.
|
|
|
|
// Key/value pair format: memory:<addr>=<bytes>;
|
|
// <addr> is a number whose base will be interpreted by the prefix:
|
|
// "0x[0-9a-fA-F]+" for hex
|
|
// "0[0-7]+" for octal
|
|
// "[1-9]+" for decimal
|
|
// <bytes> is native endian ASCII hex bytes just like the register
|
|
// values
|
|
llvm::StringRef addr_str, bytes_str;
|
|
std::tie(addr_str, bytes_str) = value.split('=');
|
|
if (!addr_str.empty() && !bytes_str.empty()) {
|
|
lldb::addr_t mem_cache_addr = LLDB_INVALID_ADDRESS;
|
|
if (!addr_str.getAsInteger(0, mem_cache_addr)) {
|
|
StringExtractor bytes(bytes_str);
|
|
const size_t byte_size = bytes.GetBytesLeft() / 2;
|
|
WritableDataBufferSP data_buffer_sp(
|
|
new DataBufferHeap(byte_size, 0));
|
|
const size_t bytes_copied =
|
|
bytes.GetHexBytes(data_buffer_sp->GetData(), 0);
|
|
if (bytes_copied == byte_size)
|
|
m_memory_cache.AddL1CacheData(mem_cache_addr, data_buffer_sp);
|
|
}
|
|
}
|
|
} else if (key.compare("watch") == 0 || key.compare("rwatch") == 0 ||
|
|
key.compare("awatch") == 0) {
|
|
// Support standard GDB remote stop reply packet 'TAAwatch:addr'
|
|
lldb::addr_t wp_addr = LLDB_INVALID_ADDRESS;
|
|
value.getAsInteger(16, wp_addr);
|
|
|
|
WatchpointResourceSP wp_resource_sp =
|
|
m_watchpoint_resource_list.FindByAddress(wp_addr);
|
|
|
|
// Rewrite gdb standard watch/rwatch/awatch to
|
|
// "reason:watchpoint" + "description:ADDR",
|
|
// which is parsed in SetThreadStopInfo.
|
|
reason = "watchpoint";
|
|
StreamString ostr;
|
|
ostr.Printf("%" PRIu64, wp_addr);
|
|
description = std::string(ostr.GetString());
|
|
} else if (key.compare("swbreak") == 0 || key.compare("hwbreak") == 0) {
|
|
reason = "breakpoint";
|
|
} else if (key.compare("replaylog") == 0) {
|
|
reason = "history boundary";
|
|
} else if (key.compare("library") == 0) {
|
|
auto error = LoadModules();
|
|
if (error) {
|
|
Log *log(GetLog(GDBRLog::Process));
|
|
LLDB_LOG_ERROR(log, std::move(error), "Failed to load modules: {0}");
|
|
}
|
|
} else if (key.compare("fork") == 0 || key.compare("vfork") == 0) {
|
|
// fork includes child pid/tid in thread-id format
|
|
StringExtractorGDBRemote thread_id{value};
|
|
auto pid_tid = thread_id.GetPidTid(LLDB_INVALID_PROCESS_ID);
|
|
if (!pid_tid) {
|
|
Log *log(GetLog(GDBRLog::Process));
|
|
LLDB_LOG(log, "Invalid PID/TID to fork: {0}", value);
|
|
pid_tid = {{LLDB_INVALID_PROCESS_ID, LLDB_INVALID_THREAD_ID}};
|
|
}
|
|
|
|
reason = key.str();
|
|
StreamString ostr;
|
|
ostr.Printf("%" PRIu64 " %" PRIu64, pid_tid->first, pid_tid->second);
|
|
description = std::string(ostr.GetString());
|
|
} else if (key.compare("addressing_bits") == 0) {
|
|
uint64_t addressing_bits;
|
|
if (!value.getAsInteger(0, addressing_bits)) {
|
|
addressable_bits.SetAddressableBits(addressing_bits);
|
|
}
|
|
} else if (key.compare("low_mem_addressing_bits") == 0) {
|
|
uint64_t addressing_bits;
|
|
if (!value.getAsInteger(0, addressing_bits)) {
|
|
addressable_bits.SetLowmemAddressableBits(addressing_bits);
|
|
}
|
|
} else if (key.compare("high_mem_addressing_bits") == 0) {
|
|
uint64_t addressing_bits;
|
|
if (!value.getAsInteger(0, addressing_bits)) {
|
|
addressable_bits.SetHighmemAddressableBits(addressing_bits);
|
|
}
|
|
} else if (key.size() == 2 && ::isxdigit(key[0]) && ::isxdigit(key[1])) {
|
|
uint32_t reg = UINT32_MAX;
|
|
if (!key.getAsInteger(16, reg))
|
|
expedited_register_map[reg] = std::string(std::move(value));
|
|
}
|
|
// swbreak and hwbreak are also expected keys, but we don't need to
|
|
// change our behaviour for them because lldb always expects the remote
|
|
// to adjust the program counter (if relevant, e.g., for x86 targets)
|
|
}
|
|
|
|
if (stop_pid != LLDB_INVALID_PROCESS_ID && stop_pid != pid) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
LLDB_LOG(log,
|
|
"Received stop for incorrect PID = {0} (inferior PID = {1})",
|
|
stop_pid, pid);
|
|
return eStateInvalid;
|
|
}
|
|
|
|
if (tid == LLDB_INVALID_THREAD_ID) {
|
|
// A thread id may be invalid if the response is old style 'S' packet
|
|
// which does not provide the
|
|
// thread information. So update the thread list and choose the first
|
|
// one.
|
|
UpdateThreadIDList();
|
|
|
|
if (!m_thread_ids.empty()) {
|
|
tid = m_thread_ids.front();
|
|
}
|
|
}
|
|
|
|
SetAddressableBitMasks(addressable_bits);
|
|
|
|
ThreadSP thread_sp = SetThreadStopInfo(
|
|
tid, expedited_register_map, signo, thread_name, reason, description,
|
|
exc_type, exc_data, thread_dispatch_qaddr, queue_vars_valid,
|
|
associated_with_dispatch_queue, dispatch_queue_t, queue_name,
|
|
queue_kind, queue_serial_number);
|
|
|
|
return eStateStopped;
|
|
} break;
|
|
|
|
case 'W':
|
|
case 'X':
|
|
// process exited
|
|
return eStateExited;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return eStateInvalid;
|
|
}
|
|
|
|
void ProcessGDBRemote::RefreshStateAfterStop() {
|
|
std::lock_guard<std::recursive_mutex> guard(m_thread_list_real.GetMutex());
|
|
|
|
m_thread_ids.clear();
|
|
m_thread_pcs.clear();
|
|
|
|
// Set the thread stop info. It might have a "threads" key whose value is a
|
|
// list of all thread IDs in the current process, so m_thread_ids might get
|
|
// set.
|
|
// Check to see if SetThreadStopInfo() filled in m_thread_ids?
|
|
if (m_thread_ids.empty()) {
|
|
// No, we need to fetch the thread list manually
|
|
UpdateThreadIDList();
|
|
}
|
|
|
|
// We might set some stop info's so make sure the thread list is up to
|
|
// date before we do that or we might overwrite what was computed here.
|
|
UpdateThreadListIfNeeded();
|
|
|
|
if (m_last_stop_packet)
|
|
SetThreadStopInfo(*m_last_stop_packet);
|
|
m_last_stop_packet.reset();
|
|
|
|
// If we have queried for a default thread id
|
|
if (m_initial_tid != LLDB_INVALID_THREAD_ID) {
|
|
m_thread_list.SetSelectedThreadByID(m_initial_tid);
|
|
m_initial_tid = LLDB_INVALID_THREAD_ID;
|
|
}
|
|
|
|
// Let all threads recover from stopping and do any clean up based on the
|
|
// previous thread state (if any).
|
|
m_thread_list_real.RefreshStateAfterStop();
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoHalt(bool &caused_stop) {
|
|
Status error;
|
|
|
|
if (m_public_state.GetValue() == eStateAttaching) {
|
|
// We are being asked to halt during an attach. We used to just close our
|
|
// file handle and debugserver will go away, but with remote proxies, it
|
|
// is better to send a positive signal, so let's send the interrupt first...
|
|
caused_stop = m_gdb_comm.Interrupt(GetInterruptTimeout());
|
|
m_gdb_comm.Disconnect();
|
|
} else
|
|
caused_stop = m_gdb_comm.Interrupt(GetInterruptTimeout());
|
|
return error;
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoDetach(bool keep_stopped) {
|
|
Status error;
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
LLDB_LOGF(log, "ProcessGDBRemote::DoDetach(keep_stopped: %i)", keep_stopped);
|
|
|
|
error = m_gdb_comm.Detach(keep_stopped);
|
|
if (log) {
|
|
if (error.Success())
|
|
log->PutCString(
|
|
"ProcessGDBRemote::DoDetach() detach packet sent successfully");
|
|
else
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::DoDetach() detach packet send failed: %s",
|
|
error.AsCString() ? error.AsCString() : "<unknown error>");
|
|
}
|
|
|
|
if (!error.Success())
|
|
return error;
|
|
|
|
// Sleep for one second to let the process get all detached...
|
|
StopAsyncThread();
|
|
|
|
SetPrivateState(eStateDetached);
|
|
ResumePrivateStateThread();
|
|
|
|
// KillDebugserverProcess ();
|
|
return error;
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoDestroy() {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
LLDB_LOGF(log, "ProcessGDBRemote::DoDestroy()");
|
|
|
|
// Interrupt if our inferior is running...
|
|
int exit_status = SIGABRT;
|
|
std::string exit_string;
|
|
|
|
if (m_gdb_comm.IsConnected()) {
|
|
if (m_public_state.GetValue() != eStateAttaching) {
|
|
llvm::Expected<int> kill_res = m_gdb_comm.KillProcess(GetID());
|
|
|
|
if (kill_res) {
|
|
exit_status = kill_res.get();
|
|
#if defined(__APPLE__)
|
|
// For Native processes on Mac OS X, we launch through the Host
|
|
// Platform, then hand the process off to debugserver, which becomes
|
|
// the parent process through "PT_ATTACH". Then when we go to kill
|
|
// the process on Mac OS X we call ptrace(PT_KILL) to kill it, then
|
|
// we call waitpid which returns with no error and the correct
|
|
// status. But amusingly enough that doesn't seem to actually reap
|
|
// the process, but instead it is left around as a Zombie. Probably
|
|
// the kernel is in the process of switching ownership back to lldb
|
|
// which was the original parent, and gets confused in the handoff.
|
|
// Anyway, so call waitpid here to finally reap it.
|
|
PlatformSP platform_sp(GetTarget().GetPlatform());
|
|
if (platform_sp && platform_sp->IsHost()) {
|
|
int status;
|
|
::pid_t reap_pid;
|
|
reap_pid = waitpid(GetID(), &status, WNOHANG);
|
|
LLDB_LOGF(log, "Reaped pid: %d, status: %d.\n", reap_pid, status);
|
|
}
|
|
#endif
|
|
ClearThreadIDList();
|
|
exit_string.assign("killed");
|
|
} else {
|
|
exit_string.assign(llvm::toString(kill_res.takeError()));
|
|
}
|
|
} else {
|
|
exit_string.assign("killed or interrupted while attaching.");
|
|
}
|
|
} else {
|
|
// If we missed setting the exit status on the way out, do it here.
|
|
// NB set exit status can be called multiple times, the first one sets the
|
|
// status.
|
|
exit_string.assign("destroying when not connected to debugserver");
|
|
}
|
|
|
|
SetExitStatus(exit_status, exit_string.c_str());
|
|
|
|
StopAsyncThread();
|
|
KillDebugserverProcess();
|
|
RemoveNewThreadBreakpoints();
|
|
return Status();
|
|
}
|
|
|
|
void ProcessGDBRemote::RemoveNewThreadBreakpoints() {
|
|
if (m_thread_create_bp_sp) {
|
|
if (TargetSP target_sp = m_target_wp.lock())
|
|
target_sp->RemoveBreakpointByID(m_thread_create_bp_sp->GetID());
|
|
m_thread_create_bp_sp.reset();
|
|
}
|
|
}
|
|
|
|
void ProcessGDBRemote::SetLastStopPacket(
|
|
const StringExtractorGDBRemote &response) {
|
|
const bool did_exec =
|
|
response.GetStringRef().find(";reason:exec;") != std::string::npos;
|
|
if (did_exec) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
LLDB_LOGF(log, "ProcessGDBRemote::SetLastStopPacket () - detected exec");
|
|
|
|
m_thread_list_real.Clear();
|
|
m_thread_list.Clear();
|
|
BuildDynamicRegisterInfo(true);
|
|
m_gdb_comm.ResetDiscoverableSettings(did_exec);
|
|
}
|
|
|
|
m_last_stop_packet = response;
|
|
}
|
|
|
|
void ProcessGDBRemote::SetUnixSignals(const UnixSignalsSP &signals_sp) {
|
|
Process::SetUnixSignals(std::make_shared<GDBRemoteSignals>(signals_sp));
|
|
}
|
|
|
|
// Process Queries
|
|
|
|
bool ProcessGDBRemote::IsAlive() {
|
|
return m_gdb_comm.IsConnected() && Process::IsAlive();
|
|
}
|
|
|
|
addr_t ProcessGDBRemote::GetImageInfoAddress() {
|
|
// request the link map address via the $qShlibInfoAddr packet
|
|
lldb::addr_t addr = m_gdb_comm.GetShlibInfoAddr();
|
|
|
|
// the loaded module list can also provides a link map address
|
|
if (addr == LLDB_INVALID_ADDRESS) {
|
|
llvm::Expected<LoadedModuleInfoList> list = GetLoadedModuleList();
|
|
if (!list) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
LLDB_LOG_ERROR(log, list.takeError(), "Failed to read module list: {0}.");
|
|
} else {
|
|
addr = list->m_link_map;
|
|
}
|
|
}
|
|
|
|
return addr;
|
|
}
|
|
|
|
void ProcessGDBRemote::WillPublicStop() {
|
|
// See if the GDB remote client supports the JSON threads info. If so, we
|
|
// gather stop info for all threads, expedited registers, expedited memory,
|
|
// runtime queue information (iOS and MacOSX only), and more. Expediting
|
|
// memory will help stack backtracing be much faster. Expediting registers
|
|
// will make sure we don't have to read the thread registers for GPRs.
|
|
m_jthreadsinfo_sp = m_gdb_comm.GetThreadsInfo();
|
|
|
|
if (m_jthreadsinfo_sp) {
|
|
// Now set the stop info for each thread and also expedite any registers
|
|
// and memory that was in the jThreadsInfo response.
|
|
StructuredData::Array *thread_infos = m_jthreadsinfo_sp->GetAsArray();
|
|
if (thread_infos) {
|
|
const size_t n = thread_infos->GetSize();
|
|
for (size_t i = 0; i < n; ++i) {
|
|
StructuredData::Dictionary *thread_dict =
|
|
thread_infos->GetItemAtIndex(i)->GetAsDictionary();
|
|
if (thread_dict)
|
|
SetThreadStopInfo(thread_dict);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process Memory
|
|
size_t ProcessGDBRemote::DoReadMemory(addr_t addr, void *buf, size_t size,
|
|
Status &error) {
|
|
using xPacketState = GDBRemoteCommunicationClient::xPacketState;
|
|
|
|
GetMaxMemorySize();
|
|
xPacketState x_state = m_gdb_comm.GetxPacketState();
|
|
|
|
// M and m packets take 2 bytes for 1 byte of memory
|
|
size_t max_memory_size = x_state != xPacketState::Unimplemented
|
|
? m_max_memory_size
|
|
: m_max_memory_size / 2;
|
|
if (size > max_memory_size) {
|
|
// Keep memory read sizes down to a sane limit. This function will be
|
|
// called multiple times in order to complete the task by
|
|
// lldb_private::Process so it is ok to do this.
|
|
size = max_memory_size;
|
|
}
|
|
|
|
char packet[64];
|
|
int packet_len;
|
|
packet_len = ::snprintf(packet, sizeof(packet), "%c%" PRIx64 ",%" PRIx64,
|
|
x_state != xPacketState::Unimplemented ? 'x' : 'm',
|
|
(uint64_t)addr, (uint64_t)size);
|
|
assert(packet_len + 1 < (int)sizeof(packet));
|
|
UNUSED_IF_ASSERT_DISABLED(packet_len);
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet, response,
|
|
GetInterruptTimeout()) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
if (response.IsNormalResponse()) {
|
|
error.Clear();
|
|
if (x_state != xPacketState::Unimplemented) {
|
|
// The lower level GDBRemoteCommunication packet receive layer has
|
|
// already de-quoted any 0x7d character escaping that was present in
|
|
// the packet
|
|
|
|
llvm::StringRef data_received = response.GetStringRef();
|
|
if (x_state == xPacketState::Prefixed &&
|
|
!data_received.consume_front("b")) {
|
|
error = Status::FromErrorStringWithFormatv(
|
|
"unexpected response to GDB server memory read packet '{0}': "
|
|
"'{1}'",
|
|
packet, data_received);
|
|
return 0;
|
|
}
|
|
// Don't write past the end of BUF if the remote debug server gave us
|
|
// too much data for some reason.
|
|
size_t memcpy_size = std::min(size, data_received.size());
|
|
memcpy(buf, data_received.data(), memcpy_size);
|
|
return memcpy_size;
|
|
} else {
|
|
return response.GetHexBytes(
|
|
llvm::MutableArrayRef<uint8_t>((uint8_t *)buf, size), '\xdd');
|
|
}
|
|
} else if (response.IsErrorResponse())
|
|
error = Status::FromErrorStringWithFormat(
|
|
"memory read failed for 0x%" PRIx64, addr);
|
|
else if (response.IsUnsupportedResponse())
|
|
error = Status::FromErrorStringWithFormat(
|
|
"GDB server does not support reading memory");
|
|
else
|
|
error = Status::FromErrorStringWithFormat(
|
|
"unexpected response to GDB server memory read packet '%s': '%s'",
|
|
packet, response.GetStringRef().data());
|
|
} else {
|
|
error = Status::FromErrorStringWithFormat("failed to send packet: '%s'",
|
|
packet);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool ProcessGDBRemote::SupportsMemoryTagging() {
|
|
return m_gdb_comm.GetMemoryTaggingSupported();
|
|
}
|
|
|
|
llvm::Expected<std::vector<uint8_t>>
|
|
ProcessGDBRemote::DoReadMemoryTags(lldb::addr_t addr, size_t len,
|
|
int32_t type) {
|
|
// By this point ReadMemoryTags has validated that tagging is enabled
|
|
// for this target/process/address.
|
|
DataBufferSP buffer_sp = m_gdb_comm.ReadMemoryTags(addr, len, type);
|
|
if (!buffer_sp) {
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Error reading memory tags from remote");
|
|
}
|
|
|
|
// Return the raw tag data
|
|
llvm::ArrayRef<uint8_t> tag_data = buffer_sp->GetData();
|
|
std::vector<uint8_t> got;
|
|
got.reserve(tag_data.size());
|
|
std::copy(tag_data.begin(), tag_data.end(), std::back_inserter(got));
|
|
return got;
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoWriteMemoryTags(lldb::addr_t addr, size_t len,
|
|
int32_t type,
|
|
const std::vector<uint8_t> &tags) {
|
|
// By now WriteMemoryTags should have validated that tagging is enabled
|
|
// for this target/process.
|
|
return m_gdb_comm.WriteMemoryTags(addr, len, type, tags);
|
|
}
|
|
|
|
Status ProcessGDBRemote::WriteObjectFile(
|
|
std::vector<ObjectFile::LoadableData> entries) {
|
|
Status error;
|
|
// Sort the entries by address because some writes, like those to flash
|
|
// memory, must happen in order of increasing address.
|
|
std::stable_sort(
|
|
std::begin(entries), std::end(entries),
|
|
[](const ObjectFile::LoadableData a, const ObjectFile::LoadableData b) {
|
|
return a.Dest < b.Dest;
|
|
});
|
|
m_allow_flash_writes = true;
|
|
error = Process::WriteObjectFile(entries);
|
|
if (error.Success())
|
|
error = FlashDone();
|
|
else
|
|
// Even though some of the writing failed, try to send a flash done if some
|
|
// of the writing succeeded so the flash state is reset to normal, but
|
|
// don't stomp on the error status that was set in the write failure since
|
|
// that's the one we want to report back.
|
|
FlashDone();
|
|
m_allow_flash_writes = false;
|
|
return error;
|
|
}
|
|
|
|
bool ProcessGDBRemote::HasErased(FlashRange range) {
|
|
auto size = m_erased_flash_ranges.GetSize();
|
|
for (size_t i = 0; i < size; ++i)
|
|
if (m_erased_flash_ranges.GetEntryAtIndex(i)->Contains(range))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
Status ProcessGDBRemote::FlashErase(lldb::addr_t addr, size_t size) {
|
|
Status status;
|
|
|
|
MemoryRegionInfo region;
|
|
status = GetMemoryRegionInfo(addr, region);
|
|
if (!status.Success())
|
|
return status;
|
|
|
|
// The gdb spec doesn't say if erasures are allowed across multiple regions,
|
|
// but we'll disallow it to be safe and to keep the logic simple by worring
|
|
// about only one region's block size. DoMemoryWrite is this function's
|
|
// primary user, and it can easily keep writes within a single memory region
|
|
if (addr + size > region.GetRange().GetRangeEnd()) {
|
|
status =
|
|
Status::FromErrorString("Unable to erase flash in multiple regions");
|
|
return status;
|
|
}
|
|
|
|
uint64_t blocksize = region.GetBlocksize();
|
|
if (blocksize == 0) {
|
|
status =
|
|
Status::FromErrorString("Unable to erase flash because blocksize is 0");
|
|
return status;
|
|
}
|
|
|
|
// Erasures can only be done on block boundary adresses, so round down addr
|
|
// and round up size
|
|
lldb::addr_t block_start_addr = addr - (addr % blocksize);
|
|
size += (addr - block_start_addr);
|
|
if ((size % blocksize) != 0)
|
|
size += (blocksize - size % blocksize);
|
|
|
|
FlashRange range(block_start_addr, size);
|
|
|
|
if (HasErased(range))
|
|
return status;
|
|
|
|
// We haven't erased the entire range, but we may have erased part of it.
|
|
// (e.g., block A is already erased and range starts in A and ends in B). So,
|
|
// adjust range if necessary to exclude already erased blocks.
|
|
if (!m_erased_flash_ranges.IsEmpty()) {
|
|
// Assuming that writes and erasures are done in increasing addr order,
|
|
// because that is a requirement of the vFlashWrite command. Therefore, we
|
|
// only need to look at the last range in the list for overlap.
|
|
const auto &last_range = *m_erased_flash_ranges.Back();
|
|
if (range.GetRangeBase() < last_range.GetRangeEnd()) {
|
|
auto overlap = last_range.GetRangeEnd() - range.GetRangeBase();
|
|
// overlap will be less than range.GetByteSize() or else HasErased()
|
|
// would have been true
|
|
range.SetByteSize(range.GetByteSize() - overlap);
|
|
range.SetRangeBase(range.GetRangeBase() + overlap);
|
|
}
|
|
}
|
|
|
|
StreamString packet;
|
|
packet.Printf("vFlashErase:%" PRIx64 ",%" PRIx64, range.GetRangeBase(),
|
|
(uint64_t)range.GetByteSize());
|
|
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response,
|
|
GetInterruptTimeout()) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
if (response.IsOKResponse()) {
|
|
m_erased_flash_ranges.Insert(range, true);
|
|
} else {
|
|
if (response.IsErrorResponse())
|
|
status = Status::FromErrorStringWithFormat(
|
|
"flash erase failed for 0x%" PRIx64, addr);
|
|
else if (response.IsUnsupportedResponse())
|
|
status = Status::FromErrorStringWithFormat(
|
|
"GDB server does not support flashing");
|
|
else
|
|
status = Status::FromErrorStringWithFormat(
|
|
"unexpected response to GDB server flash erase packet '%s': '%s'",
|
|
packet.GetData(), response.GetStringRef().data());
|
|
}
|
|
} else {
|
|
status = Status::FromErrorStringWithFormat("failed to send packet: '%s'",
|
|
packet.GetData());
|
|
}
|
|
return status;
|
|
}
|
|
|
|
Status ProcessGDBRemote::FlashDone() {
|
|
Status status;
|
|
// If we haven't erased any blocks, then we must not have written anything
|
|
// either, so there is no need to actually send a vFlashDone command
|
|
if (m_erased_flash_ranges.IsEmpty())
|
|
return status;
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse("vFlashDone", response,
|
|
GetInterruptTimeout()) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
if (response.IsOKResponse()) {
|
|
m_erased_flash_ranges.Clear();
|
|
} else {
|
|
if (response.IsErrorResponse())
|
|
status = Status::FromErrorStringWithFormat("flash done failed");
|
|
else if (response.IsUnsupportedResponse())
|
|
status = Status::FromErrorStringWithFormat(
|
|
"GDB server does not support flashing");
|
|
else
|
|
status = Status::FromErrorStringWithFormat(
|
|
"unexpected response to GDB server flash done packet: '%s'",
|
|
response.GetStringRef().data());
|
|
}
|
|
} else {
|
|
status =
|
|
Status::FromErrorStringWithFormat("failed to send flash done packet");
|
|
}
|
|
return status;
|
|
}
|
|
|
|
size_t ProcessGDBRemote::DoWriteMemory(addr_t addr, const void *buf,
|
|
size_t size, Status &error) {
|
|
GetMaxMemorySize();
|
|
// M and m packets take 2 bytes for 1 byte of memory
|
|
size_t max_memory_size = m_max_memory_size / 2;
|
|
if (size > max_memory_size) {
|
|
// Keep memory read sizes down to a sane limit. This function will be
|
|
// called multiple times in order to complete the task by
|
|
// lldb_private::Process so it is ok to do this.
|
|
size = max_memory_size;
|
|
}
|
|
|
|
StreamGDBRemote packet;
|
|
|
|
MemoryRegionInfo region;
|
|
Status region_status = GetMemoryRegionInfo(addr, region);
|
|
|
|
bool is_flash =
|
|
region_status.Success() && region.GetFlash() == MemoryRegionInfo::eYes;
|
|
|
|
if (is_flash) {
|
|
if (!m_allow_flash_writes) {
|
|
error = Status::FromErrorString("Writing to flash memory is not allowed");
|
|
return 0;
|
|
}
|
|
// Keep the write within a flash memory region
|
|
if (addr + size > region.GetRange().GetRangeEnd())
|
|
size = region.GetRange().GetRangeEnd() - addr;
|
|
// Flash memory must be erased before it can be written
|
|
error = FlashErase(addr, size);
|
|
if (!error.Success())
|
|
return 0;
|
|
packet.Printf("vFlashWrite:%" PRIx64 ":", addr);
|
|
packet.PutEscapedBytes(buf, size);
|
|
} else {
|
|
packet.Printf("M%" PRIx64 ",%" PRIx64 ":", addr, (uint64_t)size);
|
|
packet.PutBytesAsRawHex8(buf, size, endian::InlHostByteOrder(),
|
|
endian::InlHostByteOrder());
|
|
}
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response,
|
|
GetInterruptTimeout()) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
if (response.IsOKResponse()) {
|
|
error.Clear();
|
|
return size;
|
|
} else if (response.IsErrorResponse())
|
|
error = Status::FromErrorStringWithFormat(
|
|
"memory write failed for 0x%" PRIx64, addr);
|
|
else if (response.IsUnsupportedResponse())
|
|
error = Status::FromErrorStringWithFormat(
|
|
"GDB server does not support writing memory");
|
|
else
|
|
error = Status::FromErrorStringWithFormat(
|
|
"unexpected response to GDB server memory write packet '%s': '%s'",
|
|
packet.GetData(), response.GetStringRef().data());
|
|
} else {
|
|
error = Status::FromErrorStringWithFormat("failed to send packet: '%s'",
|
|
packet.GetData());
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
lldb::addr_t ProcessGDBRemote::DoAllocateMemory(size_t size,
|
|
uint32_t permissions,
|
|
Status &error) {
|
|
Log *log = GetLog(LLDBLog::Process | LLDBLog::Expressions);
|
|
addr_t allocated_addr = LLDB_INVALID_ADDRESS;
|
|
|
|
if (m_gdb_comm.SupportsAllocDeallocMemory() != eLazyBoolNo) {
|
|
allocated_addr = m_gdb_comm.AllocateMemory(size, permissions);
|
|
if (allocated_addr != LLDB_INVALID_ADDRESS ||
|
|
m_gdb_comm.SupportsAllocDeallocMemory() == eLazyBoolYes)
|
|
return allocated_addr;
|
|
}
|
|
|
|
if (m_gdb_comm.SupportsAllocDeallocMemory() == eLazyBoolNo) {
|
|
// Call mmap() to create memory in the inferior..
|
|
unsigned prot = 0;
|
|
if (permissions & lldb::ePermissionsReadable)
|
|
prot |= eMmapProtRead;
|
|
if (permissions & lldb::ePermissionsWritable)
|
|
prot |= eMmapProtWrite;
|
|
if (permissions & lldb::ePermissionsExecutable)
|
|
prot |= eMmapProtExec;
|
|
|
|
if (InferiorCallMmap(this, allocated_addr, 0, size, prot,
|
|
eMmapFlagsAnon | eMmapFlagsPrivate, -1, 0))
|
|
m_addr_to_mmap_size[allocated_addr] = size;
|
|
else {
|
|
allocated_addr = LLDB_INVALID_ADDRESS;
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s no direct stub support for memory "
|
|
"allocation, and InferiorCallMmap also failed - is stub "
|
|
"missing register context save/restore capability?",
|
|
__FUNCTION__);
|
|
}
|
|
}
|
|
|
|
if (allocated_addr == LLDB_INVALID_ADDRESS)
|
|
error = Status::FromErrorStringWithFormat(
|
|
"unable to allocate %" PRIu64 " bytes of memory with permissions %s",
|
|
(uint64_t)size, GetPermissionsAsCString(permissions));
|
|
else
|
|
error.Clear();
|
|
return allocated_addr;
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoGetMemoryRegionInfo(addr_t load_addr,
|
|
MemoryRegionInfo ®ion_info) {
|
|
|
|
Status error(m_gdb_comm.GetMemoryRegionInfo(load_addr, region_info));
|
|
return error;
|
|
}
|
|
|
|
std::optional<uint32_t> ProcessGDBRemote::GetWatchpointSlotCount() {
|
|
return m_gdb_comm.GetWatchpointSlotCount();
|
|
}
|
|
|
|
std::optional<bool> ProcessGDBRemote::DoGetWatchpointReportedAfter() {
|
|
return m_gdb_comm.GetWatchpointReportedAfter();
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoDeallocateMemory(lldb::addr_t addr) {
|
|
Status error;
|
|
LazyBool supported = m_gdb_comm.SupportsAllocDeallocMemory();
|
|
|
|
switch (supported) {
|
|
case eLazyBoolCalculate:
|
|
// We should never be deallocating memory without allocating memory first
|
|
// so we should never get eLazyBoolCalculate
|
|
error = Status::FromErrorString(
|
|
"tried to deallocate memory without ever allocating memory");
|
|
break;
|
|
|
|
case eLazyBoolYes:
|
|
if (!m_gdb_comm.DeallocateMemory(addr))
|
|
error = Status::FromErrorStringWithFormat(
|
|
"unable to deallocate memory at 0x%" PRIx64, addr);
|
|
break;
|
|
|
|
case eLazyBoolNo:
|
|
// Call munmap() to deallocate memory in the inferior..
|
|
{
|
|
MMapMap::iterator pos = m_addr_to_mmap_size.find(addr);
|
|
if (pos != m_addr_to_mmap_size.end() &&
|
|
InferiorCallMunmap(this, addr, pos->second))
|
|
m_addr_to_mmap_size.erase(pos);
|
|
else
|
|
error = Status::FromErrorStringWithFormat(
|
|
"unable to deallocate memory at 0x%" PRIx64, addr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
// Process STDIO
|
|
size_t ProcessGDBRemote::PutSTDIN(const char *src, size_t src_len,
|
|
Status &error) {
|
|
if (m_stdio_communication.IsConnected()) {
|
|
ConnectionStatus status;
|
|
m_stdio_communication.WriteAll(src, src_len, status, nullptr);
|
|
} else if (m_stdin_forward) {
|
|
m_gdb_comm.SendStdinNotification(src, src_len);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Status ProcessGDBRemote::EnableBreakpointSite(BreakpointSite *bp_site) {
|
|
Status error;
|
|
assert(bp_site != nullptr);
|
|
|
|
// Get logging info
|
|
Log *log = GetLog(GDBRLog::Breakpoints);
|
|
user_id_t site_id = bp_site->GetID();
|
|
|
|
// Get the breakpoint address
|
|
const addr_t addr = bp_site->GetLoadAddress();
|
|
|
|
// Log that a breakpoint was requested
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::EnableBreakpointSite (size_id = %" PRIu64
|
|
") address = 0x%" PRIx64,
|
|
site_id, (uint64_t)addr);
|
|
|
|
// Breakpoint already exists and is enabled
|
|
if (bp_site->IsEnabled()) {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::EnableBreakpointSite (size_id = %" PRIu64
|
|
") address = 0x%" PRIx64 " -- SUCCESS (already enabled)",
|
|
site_id, (uint64_t)addr);
|
|
return error;
|
|
}
|
|
|
|
// Get the software breakpoint trap opcode size
|
|
const size_t bp_op_size = GetSoftwareBreakpointTrapOpcode(bp_site);
|
|
|
|
// SupportsGDBStoppointPacket() simply checks a boolean, indicating if this
|
|
// breakpoint type is supported by the remote stub. These are set to true by
|
|
// default, and later set to false only after we receive an unimplemented
|
|
// response when sending a breakpoint packet. This means initially that
|
|
// unless we were specifically instructed to use a hardware breakpoint, LLDB
|
|
// will attempt to set a software breakpoint. HardwareRequired() also queries
|
|
// a boolean variable which indicates if the user specifically asked for
|
|
// hardware breakpoints. If true then we will skip over software
|
|
// breakpoints.
|
|
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware) &&
|
|
(!bp_site->HardwareRequired())) {
|
|
// Try to send off a software breakpoint packet ($Z0)
|
|
uint8_t error_no = m_gdb_comm.SendGDBStoppointTypePacket(
|
|
eBreakpointSoftware, true, addr, bp_op_size, GetInterruptTimeout());
|
|
if (error_no == 0) {
|
|
// The breakpoint was placed successfully
|
|
bp_site->SetEnabled(true);
|
|
bp_site->SetType(BreakpointSite::eExternal);
|
|
return error;
|
|
}
|
|
|
|
// SendGDBStoppointTypePacket() will return an error if it was unable to
|
|
// set this breakpoint. We need to differentiate between a error specific
|
|
// to placing this breakpoint or if we have learned that this breakpoint
|
|
// type is unsupported. To do this, we must test the support boolean for
|
|
// this breakpoint type to see if it now indicates that this breakpoint
|
|
// type is unsupported. If they are still supported then we should return
|
|
// with the error code. If they are now unsupported, then we would like to
|
|
// fall through and try another form of breakpoint.
|
|
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware)) {
|
|
if (error_no != UINT8_MAX)
|
|
error = Status::FromErrorStringWithFormat(
|
|
"error: %d sending the breakpoint request", error_no);
|
|
else
|
|
error = Status::FromErrorString("error sending the breakpoint request");
|
|
return error;
|
|
}
|
|
|
|
// We reach here when software breakpoints have been found to be
|
|
// unsupported. For future calls to set a breakpoint, we will not attempt
|
|
// to set a breakpoint with a type that is known not to be supported.
|
|
LLDB_LOGF(log, "Software breakpoints are unsupported");
|
|
|
|
// So we will fall through and try a hardware breakpoint
|
|
}
|
|
|
|
// The process of setting a hardware breakpoint is much the same as above.
|
|
// We check the supported boolean for this breakpoint type, and if it is
|
|
// thought to be supported then we will try to set this breakpoint with a
|
|
// hardware breakpoint.
|
|
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) {
|
|
// Try to send off a hardware breakpoint packet ($Z1)
|
|
uint8_t error_no = m_gdb_comm.SendGDBStoppointTypePacket(
|
|
eBreakpointHardware, true, addr, bp_op_size, GetInterruptTimeout());
|
|
if (error_no == 0) {
|
|
// The breakpoint was placed successfully
|
|
bp_site->SetEnabled(true);
|
|
bp_site->SetType(BreakpointSite::eHardware);
|
|
return error;
|
|
}
|
|
|
|
// Check if the error was something other then an unsupported breakpoint
|
|
// type
|
|
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) {
|
|
// Unable to set this hardware breakpoint
|
|
if (error_no != UINT8_MAX)
|
|
error = Status::FromErrorStringWithFormat(
|
|
"error: %d sending the hardware breakpoint request "
|
|
"(hardware breakpoint resources might be exhausted or unavailable)",
|
|
error_no);
|
|
else
|
|
error = Status::FromErrorString(
|
|
"error sending the hardware breakpoint request "
|
|
"(hardware breakpoint resources "
|
|
"might be exhausted or unavailable)");
|
|
return error;
|
|
}
|
|
|
|
// We will reach here when the stub gives an unsupported response to a
|
|
// hardware breakpoint
|
|
LLDB_LOGF(log, "Hardware breakpoints are unsupported");
|
|
|
|
// Finally we will falling through to a #trap style breakpoint
|
|
}
|
|
|
|
// Don't fall through when hardware breakpoints were specifically requested
|
|
if (bp_site->HardwareRequired()) {
|
|
error = Status::FromErrorString("hardware breakpoints are not supported");
|
|
return error;
|
|
}
|
|
|
|
// As a last resort we want to place a manual breakpoint. An instruction is
|
|
// placed into the process memory using memory write packets.
|
|
return EnableSoftwareBreakpoint(bp_site);
|
|
}
|
|
|
|
Status ProcessGDBRemote::DisableBreakpointSite(BreakpointSite *bp_site) {
|
|
Status error;
|
|
assert(bp_site != nullptr);
|
|
addr_t addr = bp_site->GetLoadAddress();
|
|
user_id_t site_id = bp_site->GetID();
|
|
Log *log = GetLog(GDBRLog::Breakpoints);
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::DisableBreakpointSite (site_id = %" PRIu64
|
|
") addr = 0x%8.8" PRIx64,
|
|
site_id, (uint64_t)addr);
|
|
|
|
if (bp_site->IsEnabled()) {
|
|
const size_t bp_op_size = GetSoftwareBreakpointTrapOpcode(bp_site);
|
|
|
|
BreakpointSite::Type bp_type = bp_site->GetType();
|
|
switch (bp_type) {
|
|
case BreakpointSite::eSoftware:
|
|
error = DisableSoftwareBreakpoint(bp_site);
|
|
break;
|
|
|
|
case BreakpointSite::eHardware:
|
|
if (m_gdb_comm.SendGDBStoppointTypePacket(eBreakpointHardware, false,
|
|
addr, bp_op_size,
|
|
GetInterruptTimeout()))
|
|
error = Status::FromErrorString("unknown error");
|
|
break;
|
|
|
|
case BreakpointSite::eExternal: {
|
|
if (m_gdb_comm.SendGDBStoppointTypePacket(eBreakpointSoftware, false,
|
|
addr, bp_op_size,
|
|
GetInterruptTimeout()))
|
|
error = Status::FromErrorString("unknown error");
|
|
} break;
|
|
}
|
|
if (error.Success())
|
|
bp_site->SetEnabled(false);
|
|
} else {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::DisableBreakpointSite (site_id = %" PRIu64
|
|
") addr = 0x%8.8" PRIx64 " -- SUCCESS (already disabled)",
|
|
site_id, (uint64_t)addr);
|
|
return error;
|
|
}
|
|
|
|
if (error.Success())
|
|
error = Status::FromErrorString("unknown error");
|
|
return error;
|
|
}
|
|
|
|
// Pre-requisite: wp != NULL.
|
|
static GDBStoppointType
|
|
GetGDBStoppointType(const WatchpointResourceSP &wp_res_sp) {
|
|
assert(wp_res_sp);
|
|
bool read = wp_res_sp->WatchpointResourceRead();
|
|
bool write = wp_res_sp->WatchpointResourceWrite();
|
|
|
|
assert((read || write) &&
|
|
"WatchpointResource type is neither read nor write");
|
|
if (read && write)
|
|
return eWatchpointReadWrite;
|
|
else if (read)
|
|
return eWatchpointRead;
|
|
else
|
|
return eWatchpointWrite;
|
|
}
|
|
|
|
Status ProcessGDBRemote::EnableWatchpoint(WatchpointSP wp_sp, bool notify) {
|
|
Status error;
|
|
if (!wp_sp) {
|
|
error = Status::FromErrorString("No watchpoint specified");
|
|
return error;
|
|
}
|
|
user_id_t watchID = wp_sp->GetID();
|
|
addr_t addr = wp_sp->GetLoadAddress();
|
|
Log *log(GetLog(GDBRLog::Watchpoints));
|
|
LLDB_LOGF(log, "ProcessGDBRemote::EnableWatchpoint(watchID = %" PRIu64 ")",
|
|
watchID);
|
|
if (wp_sp->IsEnabled()) {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::EnableWatchpoint(watchID = %" PRIu64
|
|
") addr = 0x%8.8" PRIx64 ": watchpoint already enabled.",
|
|
watchID, (uint64_t)addr);
|
|
return error;
|
|
}
|
|
|
|
bool read = wp_sp->WatchpointRead();
|
|
bool write = wp_sp->WatchpointWrite() || wp_sp->WatchpointModify();
|
|
size_t size = wp_sp->GetByteSize();
|
|
|
|
ArchSpec target_arch = GetTarget().GetArchitecture();
|
|
WatchpointHardwareFeature supported_features =
|
|
m_gdb_comm.GetSupportedWatchpointTypes();
|
|
|
|
std::vector<WatchpointResourceSP> resources =
|
|
WatchpointAlgorithms::AtomizeWatchpointRequest(
|
|
addr, size, read, write, supported_features, target_arch);
|
|
|
|
// LWP_TODO: Now that we know the WP Resources needed to implement this
|
|
// Watchpoint, we need to look at currently allocated Resources in the
|
|
// Process and if they match, or are within the same memory granule, or
|
|
// overlapping memory ranges, then we need to combine them. e.g. one
|
|
// Watchpoint watching 1 byte at 0x1002 and a second watchpoint watching 1
|
|
// byte at 0x1003, they must use the same hardware watchpoint register
|
|
// (Resource) to watch them.
|
|
|
|
// This may mean that an existing resource changes its type (read to
|
|
// read+write) or address range it is watching, in which case the old
|
|
// watchpoint needs to be disabled and the new Resource addr/size/type
|
|
// watchpoint enabled.
|
|
|
|
// If we modify a shared Resource to accomodate this newly added Watchpoint,
|
|
// and we are unable to set all of the Resources for it in the inferior, we
|
|
// will return an error for this Watchpoint and the shared Resource should
|
|
// be restored. e.g. this Watchpoint requires three Resources, one which
|
|
// is shared with another Watchpoint. We extend the shared Resouce to
|
|
// handle both Watchpoints and we try to set two new ones. But if we don't
|
|
// have sufficient watchpoint register for all 3, we need to show an error
|
|
// for creating this Watchpoint and we should reset the shared Resource to
|
|
// its original configuration because it is no longer shared.
|
|
|
|
bool set_all_resources = true;
|
|
std::vector<WatchpointResourceSP> succesfully_set_resources;
|
|
for (const auto &wp_res_sp : resources) {
|
|
addr_t addr = wp_res_sp->GetLoadAddress();
|
|
size_t size = wp_res_sp->GetByteSize();
|
|
GDBStoppointType type = GetGDBStoppointType(wp_res_sp);
|
|
if (!m_gdb_comm.SupportsGDBStoppointPacket(type) ||
|
|
m_gdb_comm.SendGDBStoppointTypePacket(type, true, addr, size,
|
|
GetInterruptTimeout())) {
|
|
set_all_resources = false;
|
|
break;
|
|
} else {
|
|
succesfully_set_resources.push_back(wp_res_sp);
|
|
}
|
|
}
|
|
if (set_all_resources) {
|
|
wp_sp->SetEnabled(true, notify);
|
|
for (const auto &wp_res_sp : resources) {
|
|
// LWP_TODO: If we expanded/reused an existing Resource,
|
|
// it's already in the WatchpointResourceList.
|
|
wp_res_sp->AddConstituent(wp_sp);
|
|
m_watchpoint_resource_list.Add(wp_res_sp);
|
|
}
|
|
return error;
|
|
} else {
|
|
// We failed to allocate one of the resources. Unset all
|
|
// of the new resources we did successfully set in the
|
|
// process.
|
|
for (const auto &wp_res_sp : succesfully_set_resources) {
|
|
addr_t addr = wp_res_sp->GetLoadAddress();
|
|
size_t size = wp_res_sp->GetByteSize();
|
|
GDBStoppointType type = GetGDBStoppointType(wp_res_sp);
|
|
m_gdb_comm.SendGDBStoppointTypePacket(type, false, addr, size,
|
|
GetInterruptTimeout());
|
|
}
|
|
error = Status::FromErrorString(
|
|
"Setting one of the watchpoint resources failed");
|
|
}
|
|
return error;
|
|
}
|
|
|
|
Status ProcessGDBRemote::DisableWatchpoint(WatchpointSP wp_sp, bool notify) {
|
|
Status error;
|
|
if (!wp_sp) {
|
|
error = Status::FromErrorString("Watchpoint argument was NULL.");
|
|
return error;
|
|
}
|
|
|
|
user_id_t watchID = wp_sp->GetID();
|
|
|
|
Log *log(GetLog(GDBRLog::Watchpoints));
|
|
|
|
addr_t addr = wp_sp->GetLoadAddress();
|
|
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::DisableWatchpoint (watchID = %" PRIu64
|
|
") addr = 0x%8.8" PRIx64,
|
|
watchID, (uint64_t)addr);
|
|
|
|
if (!wp_sp->IsEnabled()) {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::DisableWatchpoint (watchID = %" PRIu64
|
|
") addr = 0x%8.8" PRIx64 " -- SUCCESS (already disabled)",
|
|
watchID, (uint64_t)addr);
|
|
// See also 'class WatchpointSentry' within StopInfo.cpp. This disabling
|
|
// attempt might come from the user-supplied actions, we'll route it in
|
|
// order for the watchpoint object to intelligently process this action.
|
|
wp_sp->SetEnabled(false, notify);
|
|
return error;
|
|
}
|
|
|
|
if (wp_sp->IsHardware()) {
|
|
bool disabled_all = true;
|
|
|
|
std::vector<WatchpointResourceSP> unused_resources;
|
|
for (const auto &wp_res_sp : m_watchpoint_resource_list.Sites()) {
|
|
if (wp_res_sp->ConstituentsContains(wp_sp)) {
|
|
GDBStoppointType type = GetGDBStoppointType(wp_res_sp);
|
|
addr_t addr = wp_res_sp->GetLoadAddress();
|
|
size_t size = wp_res_sp->GetByteSize();
|
|
if (m_gdb_comm.SendGDBStoppointTypePacket(type, false, addr, size,
|
|
GetInterruptTimeout())) {
|
|
disabled_all = false;
|
|
} else {
|
|
wp_res_sp->RemoveConstituent(wp_sp);
|
|
if (wp_res_sp->GetNumberOfConstituents() == 0)
|
|
unused_resources.push_back(wp_res_sp);
|
|
}
|
|
}
|
|
}
|
|
for (auto &wp_res_sp : unused_resources)
|
|
m_watchpoint_resource_list.Remove(wp_res_sp->GetID());
|
|
|
|
wp_sp->SetEnabled(false, notify);
|
|
if (!disabled_all)
|
|
error = Status::FromErrorString(
|
|
"Failure disabling one of the watchpoint locations");
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void ProcessGDBRemote::Clear() {
|
|
m_thread_list_real.Clear();
|
|
m_thread_list.Clear();
|
|
}
|
|
|
|
Status ProcessGDBRemote::DoSignal(int signo) {
|
|
Status error;
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
LLDB_LOGF(log, "ProcessGDBRemote::DoSignal (signal = %d)", signo);
|
|
|
|
if (!m_gdb_comm.SendAsyncSignal(signo, GetInterruptTimeout()))
|
|
error =
|
|
Status::FromErrorStringWithFormat("failed to send signal %i", signo);
|
|
return error;
|
|
}
|
|
|
|
Status
|
|
ProcessGDBRemote::EstablishConnectionIfNeeded(const ProcessInfo &process_info) {
|
|
// Make sure we aren't already connected?
|
|
if (m_gdb_comm.IsConnected())
|
|
return Status();
|
|
|
|
PlatformSP platform_sp(GetTarget().GetPlatform());
|
|
if (platform_sp && !platform_sp->IsHost())
|
|
return Status::FromErrorString("Lost debug server connection");
|
|
|
|
auto error = LaunchAndConnectToDebugserver(process_info);
|
|
if (error.Fail()) {
|
|
const char *error_string = error.AsCString();
|
|
if (error_string == nullptr)
|
|
error_string = "unable to launch " DEBUGSERVER_BASENAME;
|
|
}
|
|
return error;
|
|
}
|
|
#if !defined(_WIN32)
|
|
#define USE_SOCKETPAIR_FOR_LOCAL_CONNECTION 1
|
|
#endif
|
|
|
|
#ifdef USE_SOCKETPAIR_FOR_LOCAL_CONNECTION
|
|
static bool SetCloexecFlag(int fd) {
|
|
#if defined(FD_CLOEXEC)
|
|
int flags = ::fcntl(fd, F_GETFD);
|
|
if (flags == -1)
|
|
return false;
|
|
return (::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
Status ProcessGDBRemote::LaunchAndConnectToDebugserver(
|
|
const ProcessInfo &process_info) {
|
|
using namespace std::placeholders; // For _1, _2, etc.
|
|
|
|
Status error;
|
|
if (m_debugserver_pid == LLDB_INVALID_PROCESS_ID) {
|
|
// If we locate debugserver, keep that located version around
|
|
static FileSpec g_debugserver_file_spec;
|
|
|
|
ProcessLaunchInfo debugserver_launch_info;
|
|
// Make debugserver run in its own session so signals generated by special
|
|
// terminal key sequences (^C) don't affect debugserver.
|
|
debugserver_launch_info.SetLaunchInSeparateProcessGroup(true);
|
|
|
|
const std::weak_ptr<ProcessGDBRemote> this_wp =
|
|
std::static_pointer_cast<ProcessGDBRemote>(shared_from_this());
|
|
debugserver_launch_info.SetMonitorProcessCallback(
|
|
std::bind(MonitorDebugserverProcess, this_wp, _1, _2, _3));
|
|
debugserver_launch_info.SetUserID(process_info.GetUserID());
|
|
|
|
#if defined(__APPLE__)
|
|
// On macOS 11, we need to support x86_64 applications translated to
|
|
// arm64. We check whether a binary is translated and spawn the correct
|
|
// debugserver accordingly.
|
|
int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID,
|
|
static_cast<int>(process_info.GetProcessID()) };
|
|
struct kinfo_proc processInfo;
|
|
size_t bufsize = sizeof(processInfo);
|
|
if (sysctl(mib, (unsigned)(sizeof(mib)/sizeof(int)), &processInfo,
|
|
&bufsize, NULL, 0) == 0 && bufsize > 0) {
|
|
if (processInfo.kp_proc.p_flag & P_TRANSLATED) {
|
|
FileSpec rosetta_debugserver("/Library/Apple/usr/libexec/oah/debugserver");
|
|
debugserver_launch_info.SetExecutableFile(rosetta_debugserver, false);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
shared_fd_t communication_fd = SharedSocket::kInvalidFD;
|
|
#ifdef USE_SOCKETPAIR_FOR_LOCAL_CONNECTION
|
|
// Use a socketpair on non-Windows systems for security and performance
|
|
// reasons.
|
|
int sockets[2]; /* the pair of socket descriptors */
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) {
|
|
error = Status::FromErrno();
|
|
return error;
|
|
}
|
|
|
|
int our_socket = sockets[0];
|
|
int gdb_socket = sockets[1];
|
|
auto cleanup_our = llvm::make_scope_exit([&]() { close(our_socket); });
|
|
auto cleanup_gdb = llvm::make_scope_exit([&]() { close(gdb_socket); });
|
|
|
|
// Don't let any child processes inherit our communication socket
|
|
SetCloexecFlag(our_socket);
|
|
communication_fd = gdb_socket;
|
|
#endif
|
|
|
|
error = m_gdb_comm.StartDebugserverProcess(
|
|
nullptr, GetTarget().GetPlatform().get(), debugserver_launch_info,
|
|
nullptr, nullptr, communication_fd);
|
|
|
|
if (error.Success())
|
|
m_debugserver_pid = debugserver_launch_info.GetProcessID();
|
|
else
|
|
m_debugserver_pid = LLDB_INVALID_PROCESS_ID;
|
|
|
|
if (m_debugserver_pid != LLDB_INVALID_PROCESS_ID) {
|
|
#ifdef USE_SOCKETPAIR_FOR_LOCAL_CONNECTION
|
|
// Our process spawned correctly, we can now set our connection to use
|
|
// our end of the socket pair
|
|
cleanup_our.release();
|
|
m_gdb_comm.SetConnection(
|
|
std::make_unique<ConnectionFileDescriptor>(our_socket, true));
|
|
#endif
|
|
StartAsyncThread();
|
|
}
|
|
|
|
if (error.Fail()) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
|
|
LLDB_LOGF(log, "failed to start debugserver process: %s",
|
|
error.AsCString());
|
|
return error;
|
|
}
|
|
|
|
if (m_gdb_comm.IsConnected()) {
|
|
// Finish the connection process by doing the handshake without
|
|
// connecting (send NULL URL)
|
|
error = ConnectToDebugserver("");
|
|
} else {
|
|
error = Status::FromErrorString("connection failed");
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void ProcessGDBRemote::MonitorDebugserverProcess(
|
|
std::weak_ptr<ProcessGDBRemote> process_wp, lldb::pid_t debugserver_pid,
|
|
int signo, // Zero for no signal
|
|
int exit_status // Exit value of process if signal is zero
|
|
) {
|
|
// "debugserver_pid" argument passed in is the process ID for debugserver
|
|
// that we are tracking...
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s(process_wp, pid=%" PRIu64
|
|
", signo=%i (0x%x), exit_status=%i)",
|
|
__FUNCTION__, debugserver_pid, signo, signo, exit_status);
|
|
|
|
std::shared_ptr<ProcessGDBRemote> process_sp = process_wp.lock();
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s(process = %p)", __FUNCTION__,
|
|
static_cast<void *>(process_sp.get()));
|
|
if (!process_sp || process_sp->m_debugserver_pid != debugserver_pid)
|
|
return;
|
|
|
|
// Sleep for a half a second to make sure our inferior process has time to
|
|
// set its exit status before we set it incorrectly when both the debugserver
|
|
// and the inferior process shut down.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
|
|
// If our process hasn't yet exited, debugserver might have died. If the
|
|
// process did exit, then we are reaping it.
|
|
const StateType state = process_sp->GetState();
|
|
|
|
if (state != eStateInvalid && state != eStateUnloaded &&
|
|
state != eStateExited && state != eStateDetached) {
|
|
StreamString stream;
|
|
if (signo == 0)
|
|
stream.Format(DEBUGSERVER_BASENAME " died with an exit status of {0:x8}",
|
|
exit_status);
|
|
else {
|
|
llvm::StringRef signal_name =
|
|
process_sp->GetUnixSignals()->GetSignalAsStringRef(signo);
|
|
const char *format_str = DEBUGSERVER_BASENAME " died with signal {0}";
|
|
if (!signal_name.empty())
|
|
stream.Format(format_str, signal_name);
|
|
else
|
|
stream.Format(format_str, signo);
|
|
}
|
|
process_sp->SetExitStatus(-1, stream.GetString());
|
|
}
|
|
// Debugserver has exited we need to let our ProcessGDBRemote know that it no
|
|
// longer has a debugserver instance
|
|
process_sp->m_debugserver_pid = LLDB_INVALID_PROCESS_ID;
|
|
}
|
|
|
|
void ProcessGDBRemote::KillDebugserverProcess() {
|
|
m_gdb_comm.Disconnect();
|
|
if (m_debugserver_pid != LLDB_INVALID_PROCESS_ID) {
|
|
Host::Kill(m_debugserver_pid, SIGINT);
|
|
m_debugserver_pid = LLDB_INVALID_PROCESS_ID;
|
|
}
|
|
}
|
|
|
|
void ProcessGDBRemote::Initialize() {
|
|
static llvm::once_flag g_once_flag;
|
|
|
|
llvm::call_once(g_once_flag, []() {
|
|
PluginManager::RegisterPlugin(GetPluginNameStatic(),
|
|
GetPluginDescriptionStatic(), CreateInstance,
|
|
DebuggerInitialize);
|
|
});
|
|
}
|
|
|
|
void ProcessGDBRemote::DebuggerInitialize(Debugger &debugger) {
|
|
if (!PluginManager::GetSettingForProcessPlugin(
|
|
debugger, PluginProperties::GetSettingName())) {
|
|
const bool is_global_setting = true;
|
|
PluginManager::CreateSettingForProcessPlugin(
|
|
debugger, GetGlobalPluginProperties().GetValueProperties(),
|
|
"Properties for the gdb-remote process plug-in.", is_global_setting);
|
|
}
|
|
}
|
|
|
|
bool ProcessGDBRemote::StartAsyncThread() {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s ()", __FUNCTION__);
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_async_thread_state_mutex);
|
|
if (!m_async_thread.IsJoinable()) {
|
|
// Create a thread that watches our internal state and controls which
|
|
// events make it to clients (into the DCProcess event queue).
|
|
|
|
llvm::Expected<HostThread> async_thread =
|
|
ThreadLauncher::LaunchThread("<lldb.process.gdb-remote.async>", [this] {
|
|
return ProcessGDBRemote::AsyncThread();
|
|
});
|
|
if (!async_thread) {
|
|
LLDB_LOG_ERROR(GetLog(LLDBLog::Host), async_thread.takeError(),
|
|
"failed to launch host thread: {0}");
|
|
return false;
|
|
}
|
|
m_async_thread = *async_thread;
|
|
} else
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s () - Called when Async thread was "
|
|
"already running.",
|
|
__FUNCTION__);
|
|
|
|
return m_async_thread.IsJoinable();
|
|
}
|
|
|
|
void ProcessGDBRemote::StopAsyncThread() {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s ()", __FUNCTION__);
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_async_thread_state_mutex);
|
|
if (m_async_thread.IsJoinable()) {
|
|
m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncThreadShouldExit);
|
|
|
|
// This will shut down the async thread.
|
|
m_gdb_comm.Disconnect(); // Disconnect from the debug server.
|
|
|
|
// Stop the stdio thread
|
|
m_async_thread.Join(nullptr);
|
|
m_async_thread.Reset();
|
|
} else
|
|
LLDB_LOGF(
|
|
log,
|
|
"ProcessGDBRemote::%s () - Called when Async thread was not running.",
|
|
__FUNCTION__);
|
|
}
|
|
|
|
thread_result_t ProcessGDBRemote::AsyncThread() {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s(pid = %" PRIu64 ") thread starting...",
|
|
__FUNCTION__, GetID());
|
|
|
|
EventSP event_sp;
|
|
|
|
// We need to ignore any packets that come in after we have
|
|
// have decided the process has exited. There are some
|
|
// situations, for instance when we try to interrupt a running
|
|
// process and the interrupt fails, where another packet might
|
|
// get delivered after we've decided to give up on the process.
|
|
// But once we've decided we are done with the process we will
|
|
// not be in a state to do anything useful with new packets.
|
|
// So it is safer to simply ignore any remaining packets by
|
|
// explicitly checking for eStateExited before reentering the
|
|
// fetch loop.
|
|
|
|
bool done = false;
|
|
while (!done && GetPrivateState() != eStateExited) {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s(pid = %" PRIu64
|
|
") listener.WaitForEvent (NULL, event_sp)...",
|
|
__FUNCTION__, GetID());
|
|
|
|
if (m_async_listener_sp->GetEvent(event_sp, std::nullopt)) {
|
|
const uint32_t event_type = event_sp->GetType();
|
|
if (event_sp->BroadcasterIs(&m_async_broadcaster)) {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s(pid = %" PRIu64
|
|
") Got an event of type: %d...",
|
|
__FUNCTION__, GetID(), event_type);
|
|
|
|
switch (event_type) {
|
|
case eBroadcastBitAsyncContinue: {
|
|
const EventDataBytes *continue_packet =
|
|
EventDataBytes::GetEventDataFromEvent(event_sp.get());
|
|
|
|
if (continue_packet) {
|
|
const char *continue_cstr =
|
|
(const char *)continue_packet->GetBytes();
|
|
const size_t continue_cstr_len = continue_packet->GetByteSize();
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s(pid = %" PRIu64
|
|
") got eBroadcastBitAsyncContinue: %s",
|
|
__FUNCTION__, GetID(), continue_cstr);
|
|
|
|
if (::strstr(continue_cstr, "vAttach") == nullptr)
|
|
SetPrivateState(eStateRunning);
|
|
StringExtractorGDBRemote response;
|
|
|
|
StateType stop_state =
|
|
GetGDBRemote().SendContinuePacketAndWaitForResponse(
|
|
*this, *GetUnixSignals(),
|
|
llvm::StringRef(continue_cstr, continue_cstr_len),
|
|
GetInterruptTimeout(), response);
|
|
|
|
// We need to immediately clear the thread ID list so we are sure
|
|
// to get a valid list of threads. The thread ID list might be
|
|
// contained within the "response", or the stop reply packet that
|
|
// caused the stop. So clear it now before we give the stop reply
|
|
// packet to the process using the
|
|
// SetLastStopPacket()...
|
|
ClearThreadIDList();
|
|
|
|
switch (stop_state) {
|
|
case eStateStopped:
|
|
case eStateCrashed:
|
|
case eStateSuspended:
|
|
SetLastStopPacket(response);
|
|
SetPrivateState(stop_state);
|
|
break;
|
|
|
|
case eStateExited: {
|
|
SetLastStopPacket(response);
|
|
ClearThreadIDList();
|
|
response.SetFilePos(1);
|
|
|
|
int exit_status = response.GetHexU8();
|
|
std::string desc_string;
|
|
if (response.GetBytesLeft() > 0 && response.GetChar('-') == ';') {
|
|
llvm::StringRef desc_str;
|
|
llvm::StringRef desc_token;
|
|
while (response.GetNameColonValue(desc_token, desc_str)) {
|
|
if (desc_token != "description")
|
|
continue;
|
|
StringExtractor extractor(desc_str);
|
|
extractor.GetHexByteString(desc_string);
|
|
}
|
|
}
|
|
SetExitStatus(exit_status, desc_string.c_str());
|
|
done = true;
|
|
break;
|
|
}
|
|
case eStateInvalid: {
|
|
// Check to see if we were trying to attach and if we got back
|
|
// the "E87" error code from debugserver -- this indicates that
|
|
// the process is not debuggable. Return a slightly more
|
|
// helpful error message about why the attach failed.
|
|
if (::strstr(continue_cstr, "vAttach") != nullptr &&
|
|
response.GetError() == 0x87) {
|
|
SetExitStatus(-1, "cannot attach to process due to "
|
|
"System Integrity Protection");
|
|
} else if (::strstr(continue_cstr, "vAttach") != nullptr &&
|
|
response.GetStatus().Fail()) {
|
|
SetExitStatus(-1, response.GetStatus().AsCString());
|
|
} else {
|
|
SetExitStatus(-1, "lost connection");
|
|
}
|
|
done = true;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
SetPrivateState(stop_state);
|
|
break;
|
|
} // switch(stop_state)
|
|
} // if (continue_packet)
|
|
} // case eBroadcastBitAsyncContinue
|
|
break;
|
|
|
|
case eBroadcastBitAsyncThreadShouldExit:
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s(pid = %" PRIu64
|
|
") got eBroadcastBitAsyncThreadShouldExit...",
|
|
__FUNCTION__, GetID());
|
|
done = true;
|
|
break;
|
|
|
|
default:
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s(pid = %" PRIu64
|
|
") got unknown event 0x%8.8x",
|
|
__FUNCTION__, GetID(), event_type);
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s(pid = %" PRIu64
|
|
") listener.WaitForEvent (NULL, event_sp) => false",
|
|
__FUNCTION__, GetID());
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s(pid = %" PRIu64 ") thread exiting...",
|
|
__FUNCTION__, GetID());
|
|
|
|
return {};
|
|
}
|
|
|
|
// uint32_t
|
|
// ProcessGDBRemote::ListProcessesMatchingName (const char *name, StringList
|
|
// &matches, std::vector<lldb::pid_t> &pids)
|
|
//{
|
|
// // If we are planning to launch the debugserver remotely, then we need to
|
|
// fire up a debugserver
|
|
// // process and ask it for the list of processes. But if we are local, we
|
|
// can let the Host do it.
|
|
// if (m_local_debugserver)
|
|
// {
|
|
// return Host::ListProcessesMatchingName (name, matches, pids);
|
|
// }
|
|
// else
|
|
// {
|
|
// // FIXME: Implement talking to the remote debugserver.
|
|
// return 0;
|
|
// }
|
|
//
|
|
//}
|
|
//
|
|
bool ProcessGDBRemote::NewThreadNotifyBreakpointHit(
|
|
void *baton, StoppointCallbackContext *context, lldb::user_id_t break_id,
|
|
lldb::user_id_t break_loc_id) {
|
|
// I don't think I have to do anything here, just make sure I notice the new
|
|
// thread when it starts to
|
|
// run so I can stop it if that's what I want to do.
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
LLDB_LOGF(log, "Hit New Thread Notification breakpoint.");
|
|
return false;
|
|
}
|
|
|
|
Status ProcessGDBRemote::UpdateAutomaticSignalFiltering() {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
LLDB_LOG(log, "Check if need to update ignored signals");
|
|
|
|
// QPassSignals package is not supported by the server, there is no way we
|
|
// can ignore any signals on server side.
|
|
if (!m_gdb_comm.GetQPassSignalsSupported())
|
|
return Status();
|
|
|
|
// No signals, nothing to send.
|
|
if (m_unix_signals_sp == nullptr)
|
|
return Status();
|
|
|
|
// Signals' version hasn't changed, no need to send anything.
|
|
uint64_t new_signals_version = m_unix_signals_sp->GetVersion();
|
|
if (new_signals_version == m_last_signals_version) {
|
|
LLDB_LOG(log, "Signals' version hasn't changed. version={0}",
|
|
m_last_signals_version);
|
|
return Status();
|
|
}
|
|
|
|
auto signals_to_ignore =
|
|
m_unix_signals_sp->GetFilteredSignals(false, false, false);
|
|
Status error = m_gdb_comm.SendSignalsToIgnore(signals_to_ignore);
|
|
|
|
LLDB_LOG(log,
|
|
"Signals' version changed. old version={0}, new version={1}, "
|
|
"signals ignored={2}, update result={3}",
|
|
m_last_signals_version, new_signals_version,
|
|
signals_to_ignore.size(), error);
|
|
|
|
if (error.Success())
|
|
m_last_signals_version = new_signals_version;
|
|
|
|
return error;
|
|
}
|
|
|
|
bool ProcessGDBRemote::StartNoticingNewThreads() {
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
if (m_thread_create_bp_sp) {
|
|
if (log && log->GetVerbose())
|
|
LLDB_LOGF(log, "Enabled noticing new thread breakpoint.");
|
|
m_thread_create_bp_sp->SetEnabled(true);
|
|
} else {
|
|
PlatformSP platform_sp(GetTarget().GetPlatform());
|
|
if (platform_sp) {
|
|
m_thread_create_bp_sp =
|
|
platform_sp->SetThreadCreationBreakpoint(GetTarget());
|
|
if (m_thread_create_bp_sp) {
|
|
if (log && log->GetVerbose())
|
|
LLDB_LOGF(
|
|
log, "Successfully created new thread notification breakpoint %i",
|
|
m_thread_create_bp_sp->GetID());
|
|
m_thread_create_bp_sp->SetCallback(
|
|
ProcessGDBRemote::NewThreadNotifyBreakpointHit, this, true);
|
|
} else {
|
|
LLDB_LOGF(log, "Failed to create new thread notification breakpoint.");
|
|
}
|
|
}
|
|
}
|
|
return m_thread_create_bp_sp.get() != nullptr;
|
|
}
|
|
|
|
bool ProcessGDBRemote::StopNoticingNewThreads() {
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
if (log && log->GetVerbose())
|
|
LLDB_LOGF(log, "Disabling new thread notification breakpoint.");
|
|
|
|
if (m_thread_create_bp_sp)
|
|
m_thread_create_bp_sp->SetEnabled(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
DynamicLoader *ProcessGDBRemote::GetDynamicLoader() {
|
|
if (m_dyld_up.get() == nullptr)
|
|
m_dyld_up.reset(DynamicLoader::FindPlugin(this, ""));
|
|
return m_dyld_up.get();
|
|
}
|
|
|
|
Status ProcessGDBRemote::SendEventData(const char *data) {
|
|
int return_value;
|
|
bool was_supported;
|
|
|
|
Status error;
|
|
|
|
return_value = m_gdb_comm.SendLaunchEventDataPacket(data, &was_supported);
|
|
if (return_value != 0) {
|
|
if (!was_supported)
|
|
error = Status::FromErrorString(
|
|
"Sending events is not supported for this process.");
|
|
else
|
|
error = Status::FromErrorStringWithFormat("Error sending event data: %d.",
|
|
return_value);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
DataExtractor ProcessGDBRemote::GetAuxvData() {
|
|
DataBufferSP buf;
|
|
if (m_gdb_comm.GetQXferAuxvReadSupported()) {
|
|
llvm::Expected<std::string> response = m_gdb_comm.ReadExtFeature("auxv", "");
|
|
if (response)
|
|
buf = std::make_shared<DataBufferHeap>(response->c_str(),
|
|
response->length());
|
|
else
|
|
LLDB_LOG_ERROR(GetLog(GDBRLog::Process), response.takeError(), "{0}");
|
|
}
|
|
return DataExtractor(buf, GetByteOrder(), GetAddressByteSize());
|
|
}
|
|
|
|
StructuredData::ObjectSP
|
|
ProcessGDBRemote::GetExtendedInfoForThread(lldb::tid_t tid) {
|
|
StructuredData::ObjectSP object_sp;
|
|
|
|
if (m_gdb_comm.GetThreadExtendedInfoSupported()) {
|
|
StructuredData::ObjectSP args_dict(new StructuredData::Dictionary());
|
|
SystemRuntime *runtime = GetSystemRuntime();
|
|
if (runtime) {
|
|
runtime->AddThreadExtendedInfoPacketHints(args_dict);
|
|
}
|
|
args_dict->GetAsDictionary()->AddIntegerItem("thread", tid);
|
|
|
|
StreamString packet;
|
|
packet << "jThreadExtendedInfo:";
|
|
args_dict->Dump(packet, false);
|
|
|
|
// FIXME the final character of a JSON dictionary, '}', is the escape
|
|
// character in gdb-remote binary mode. lldb currently doesn't escape
|
|
// these characters in its packet output -- so we add the quoted version of
|
|
// the } character here manually in case we talk to a debugserver which un-
|
|
// escapes the characters at packet read time.
|
|
packet << (char)(0x7d ^ 0x20);
|
|
|
|
StringExtractorGDBRemote response;
|
|
response.SetResponseValidatorToJSON();
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
StringExtractorGDBRemote::ResponseType response_type =
|
|
response.GetResponseType();
|
|
if (response_type == StringExtractorGDBRemote::eResponse) {
|
|
if (!response.Empty()) {
|
|
object_sp = StructuredData::ParseJSON(response.GetStringRef());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return object_sp;
|
|
}
|
|
|
|
StructuredData::ObjectSP ProcessGDBRemote::GetLoadedDynamicLibrariesInfos(
|
|
lldb::addr_t image_list_address, lldb::addr_t image_count) {
|
|
|
|
StructuredData::ObjectSP args_dict(new StructuredData::Dictionary());
|
|
args_dict->GetAsDictionary()->AddIntegerItem("image_list_address",
|
|
image_list_address);
|
|
args_dict->GetAsDictionary()->AddIntegerItem("image_count", image_count);
|
|
|
|
return GetLoadedDynamicLibrariesInfos_sender(args_dict);
|
|
}
|
|
|
|
StructuredData::ObjectSP ProcessGDBRemote::GetLoadedDynamicLibrariesInfos() {
|
|
StructuredData::ObjectSP args_dict(new StructuredData::Dictionary());
|
|
|
|
args_dict->GetAsDictionary()->AddBooleanItem("fetch_all_solibs", true);
|
|
|
|
return GetLoadedDynamicLibrariesInfos_sender(args_dict);
|
|
}
|
|
|
|
StructuredData::ObjectSP ProcessGDBRemote::GetLoadedDynamicLibrariesInfos(
|
|
const std::vector<lldb::addr_t> &load_addresses) {
|
|
StructuredData::ObjectSP args_dict(new StructuredData::Dictionary());
|
|
StructuredData::ArraySP addresses(new StructuredData::Array);
|
|
|
|
for (auto addr : load_addresses)
|
|
addresses->AddIntegerItem(addr);
|
|
|
|
args_dict->GetAsDictionary()->AddItem("solib_addresses", addresses);
|
|
|
|
return GetLoadedDynamicLibrariesInfos_sender(args_dict);
|
|
}
|
|
|
|
StructuredData::ObjectSP
|
|
ProcessGDBRemote::GetLoadedDynamicLibrariesInfos_sender(
|
|
StructuredData::ObjectSP args_dict) {
|
|
StructuredData::ObjectSP object_sp;
|
|
|
|
if (m_gdb_comm.GetLoadedDynamicLibrariesInfosSupported()) {
|
|
// Scope for the scoped timeout object
|
|
GDBRemoteCommunication::ScopedTimeout timeout(m_gdb_comm,
|
|
std::chrono::seconds(10));
|
|
|
|
StreamString packet;
|
|
packet << "jGetLoadedDynamicLibrariesInfos:";
|
|
args_dict->Dump(packet, false);
|
|
|
|
// FIXME the final character of a JSON dictionary, '}', is the escape
|
|
// character in gdb-remote binary mode. lldb currently doesn't escape
|
|
// these characters in its packet output -- so we add the quoted version of
|
|
// the } character here manually in case we talk to a debugserver which un-
|
|
// escapes the characters at packet read time.
|
|
packet << (char)(0x7d ^ 0x20);
|
|
|
|
StringExtractorGDBRemote response;
|
|
response.SetResponseValidatorToJSON();
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
StringExtractorGDBRemote::ResponseType response_type =
|
|
response.GetResponseType();
|
|
if (response_type == StringExtractorGDBRemote::eResponse) {
|
|
if (!response.Empty()) {
|
|
object_sp = StructuredData::ParseJSON(response.GetStringRef());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return object_sp;
|
|
}
|
|
|
|
StructuredData::ObjectSP ProcessGDBRemote::GetDynamicLoaderProcessState() {
|
|
StructuredData::ObjectSP object_sp;
|
|
StructuredData::ObjectSP args_dict(new StructuredData::Dictionary());
|
|
|
|
if (m_gdb_comm.GetDynamicLoaderProcessStateSupported()) {
|
|
StringExtractorGDBRemote response;
|
|
response.SetResponseValidatorToJSON();
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse("jGetDyldProcessState",
|
|
response) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
StringExtractorGDBRemote::ResponseType response_type =
|
|
response.GetResponseType();
|
|
if (response_type == StringExtractorGDBRemote::eResponse) {
|
|
if (!response.Empty()) {
|
|
object_sp = StructuredData::ParseJSON(response.GetStringRef());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return object_sp;
|
|
}
|
|
|
|
StructuredData::ObjectSP ProcessGDBRemote::GetSharedCacheInfo() {
|
|
StructuredData::ObjectSP object_sp;
|
|
StructuredData::ObjectSP args_dict(new StructuredData::Dictionary());
|
|
|
|
if (m_gdb_comm.GetSharedCacheInfoSupported()) {
|
|
StreamString packet;
|
|
packet << "jGetSharedCacheInfo:";
|
|
args_dict->Dump(packet, false);
|
|
|
|
// FIXME the final character of a JSON dictionary, '}', is the escape
|
|
// character in gdb-remote binary mode. lldb currently doesn't escape
|
|
// these characters in its packet output -- so we add the quoted version of
|
|
// the } character here manually in case we talk to a debugserver which un-
|
|
// escapes the characters at packet read time.
|
|
packet << (char)(0x7d ^ 0x20);
|
|
|
|
StringExtractorGDBRemote response;
|
|
response.SetResponseValidatorToJSON();
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
StringExtractorGDBRemote::ResponseType response_type =
|
|
response.GetResponseType();
|
|
if (response_type == StringExtractorGDBRemote::eResponse) {
|
|
if (!response.Empty()) {
|
|
object_sp = StructuredData::ParseJSON(response.GetStringRef());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return object_sp;
|
|
}
|
|
|
|
Status ProcessGDBRemote::ConfigureStructuredData(
|
|
llvm::StringRef type_name, const StructuredData::ObjectSP &config_sp) {
|
|
return m_gdb_comm.ConfigureRemoteStructuredData(type_name, config_sp);
|
|
}
|
|
|
|
// Establish the largest memory read/write payloads we should use. If the
|
|
// remote stub has a max packet size, stay under that size.
|
|
//
|
|
// If the remote stub's max packet size is crazy large, use a reasonable
|
|
// largeish default.
|
|
//
|
|
// If the remote stub doesn't advertise a max packet size, use a conservative
|
|
// default.
|
|
|
|
void ProcessGDBRemote::GetMaxMemorySize() {
|
|
const uint64_t reasonable_largeish_default = 128 * 1024;
|
|
const uint64_t conservative_default = 512;
|
|
|
|
if (m_max_memory_size == 0) {
|
|
uint64_t stub_max_size = m_gdb_comm.GetRemoteMaxPacketSize();
|
|
if (stub_max_size != UINT64_MAX && stub_max_size != 0) {
|
|
// Save the stub's claimed maximum packet size
|
|
m_remote_stub_max_memory_size = stub_max_size;
|
|
|
|
// Even if the stub says it can support ginormous packets, don't exceed
|
|
// our reasonable largeish default packet size.
|
|
if (stub_max_size > reasonable_largeish_default) {
|
|
stub_max_size = reasonable_largeish_default;
|
|
}
|
|
|
|
// Memory packet have other overheads too like Maddr,size:#NN Instead of
|
|
// calculating the bytes taken by size and addr every time, we take a
|
|
// maximum guess here.
|
|
if (stub_max_size > 70)
|
|
stub_max_size -= 32 + 32 + 6;
|
|
else {
|
|
// In unlikely scenario that max packet size is less then 70, we will
|
|
// hope that data being written is small enough to fit.
|
|
Log *log(GetLog(GDBRLog::Comm | GDBRLog::Memory));
|
|
if (log)
|
|
log->Warning("Packet size is too small. "
|
|
"LLDB may face problems while writing memory");
|
|
}
|
|
|
|
m_max_memory_size = stub_max_size;
|
|
} else {
|
|
m_max_memory_size = conservative_default;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessGDBRemote::SetUserSpecifiedMaxMemoryTransferSize(
|
|
uint64_t user_specified_max) {
|
|
if (user_specified_max != 0) {
|
|
GetMaxMemorySize();
|
|
|
|
if (m_remote_stub_max_memory_size != 0) {
|
|
if (m_remote_stub_max_memory_size < user_specified_max) {
|
|
m_max_memory_size = m_remote_stub_max_memory_size; // user specified a
|
|
// packet size too
|
|
// big, go as big
|
|
// as the remote stub says we can go.
|
|
} else {
|
|
m_max_memory_size = user_specified_max; // user's packet size is good
|
|
}
|
|
} else {
|
|
m_max_memory_size =
|
|
user_specified_max; // user's packet size is probably fine
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ProcessGDBRemote::GetModuleSpec(const FileSpec &module_file_spec,
|
|
const ArchSpec &arch,
|
|
ModuleSpec &module_spec) {
|
|
Log *log = GetLog(LLDBLog::Platform);
|
|
|
|
const ModuleCacheKey key(module_file_spec.GetPath(),
|
|
arch.GetTriple().getTriple());
|
|
auto cached = m_cached_module_specs.find(key);
|
|
if (cached != m_cached_module_specs.end()) {
|
|
module_spec = cached->second;
|
|
return bool(module_spec);
|
|
}
|
|
|
|
if (!m_gdb_comm.GetModuleInfo(module_file_spec, arch, module_spec)) {
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s - failed to get module info for %s:%s",
|
|
__FUNCTION__, module_file_spec.GetPath().c_str(),
|
|
arch.GetTriple().getTriple().c_str());
|
|
return false;
|
|
}
|
|
|
|
if (log) {
|
|
StreamString stream;
|
|
module_spec.Dump(stream);
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s - got module info for (%s:%s) : %s",
|
|
__FUNCTION__, module_file_spec.GetPath().c_str(),
|
|
arch.GetTriple().getTriple().c_str(), stream.GetData());
|
|
}
|
|
|
|
m_cached_module_specs[key] = module_spec;
|
|
return true;
|
|
}
|
|
|
|
void ProcessGDBRemote::PrefetchModuleSpecs(
|
|
llvm::ArrayRef<FileSpec> module_file_specs, const llvm::Triple &triple) {
|
|
auto module_specs = m_gdb_comm.GetModulesInfo(module_file_specs, triple);
|
|
if (module_specs) {
|
|
for (const FileSpec &spec : module_file_specs)
|
|
m_cached_module_specs[ModuleCacheKey(spec.GetPath(),
|
|
triple.getTriple())] = ModuleSpec();
|
|
for (const ModuleSpec &spec : *module_specs)
|
|
m_cached_module_specs[ModuleCacheKey(spec.GetFileSpec().GetPath(),
|
|
triple.getTriple())] = spec;
|
|
}
|
|
}
|
|
|
|
llvm::VersionTuple ProcessGDBRemote::GetHostOSVersion() {
|
|
return m_gdb_comm.GetOSVersion();
|
|
}
|
|
|
|
llvm::VersionTuple ProcessGDBRemote::GetHostMacCatalystVersion() {
|
|
return m_gdb_comm.GetMacCatalystVersion();
|
|
}
|
|
|
|
namespace {
|
|
|
|
typedef std::vector<std::string> stringVec;
|
|
|
|
typedef std::vector<struct GdbServerRegisterInfo> GDBServerRegisterVec;
|
|
struct RegisterSetInfo {
|
|
ConstString name;
|
|
};
|
|
|
|
typedef std::map<uint32_t, RegisterSetInfo> RegisterSetMap;
|
|
|
|
struct GdbServerTargetInfo {
|
|
std::string arch;
|
|
std::string osabi;
|
|
stringVec includes;
|
|
RegisterSetMap reg_set_map;
|
|
};
|
|
|
|
static FieldEnum::Enumerators ParseEnumEvalues(const XMLNode &enum_node) {
|
|
Log *log(GetLog(GDBRLog::Process));
|
|
// We will use the last instance of each value. Also we preserve the order
|
|
// of declaration in the XML, as it may not be numerical.
|
|
// For example, hardware may intially release with two states that softwware
|
|
// can read from a register field:
|
|
// 0 = startup, 1 = running
|
|
// If in a future hardware release, the designers added a pre-startup state:
|
|
// 0 = startup, 1 = running, 2 = pre-startup
|
|
// Now it makes more sense to list them in this logical order as opposed to
|
|
// numerical order:
|
|
// 2 = pre-startup, 1 = startup, 0 = startup
|
|
// This only matters for "register info" but let's trust what the server
|
|
// chose regardless.
|
|
std::map<uint64_t, FieldEnum::Enumerator> enumerators;
|
|
|
|
enum_node.ForEachChildElementWithName(
|
|
"evalue", [&enumerators, &log](const XMLNode &enumerator_node) {
|
|
std::optional<llvm::StringRef> name;
|
|
std::optional<uint64_t> value;
|
|
|
|
enumerator_node.ForEachAttribute(
|
|
[&name, &value, &log](const llvm::StringRef &attr_name,
|
|
const llvm::StringRef &attr_value) {
|
|
if (attr_name == "name") {
|
|
if (attr_value.size())
|
|
name = attr_value;
|
|
else
|
|
LLDB_LOG(log, "ProcessGDBRemote::ParseEnumEvalues "
|
|
"Ignoring empty name in evalue");
|
|
} else if (attr_name == "value") {
|
|
uint64_t parsed_value = 0;
|
|
if (llvm::to_integer(attr_value, parsed_value))
|
|
value = parsed_value;
|
|
else
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseEnumEvalues "
|
|
"Invalid value \"{0}\" in "
|
|
"evalue",
|
|
attr_value.data());
|
|
} else
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseEnumEvalues Ignoring "
|
|
"unknown attribute "
|
|
"\"{0}\" in evalue",
|
|
attr_name.data());
|
|
|
|
// Keep walking attributes.
|
|
return true;
|
|
});
|
|
|
|
if (value && name)
|
|
enumerators.insert_or_assign(
|
|
*value, FieldEnum::Enumerator(*value, name->str()));
|
|
|
|
// Find all evalue elements.
|
|
return true;
|
|
});
|
|
|
|
FieldEnum::Enumerators final_enumerators;
|
|
for (auto [_, enumerator] : enumerators)
|
|
final_enumerators.push_back(enumerator);
|
|
|
|
return final_enumerators;
|
|
}
|
|
|
|
static void
|
|
ParseEnums(XMLNode feature_node,
|
|
llvm::StringMap<std::unique_ptr<FieldEnum>> ®isters_enum_types) {
|
|
Log *log(GetLog(GDBRLog::Process));
|
|
|
|
// The top level element is "<enum...".
|
|
feature_node.ForEachChildElementWithName(
|
|
"enum", [log, ®isters_enum_types](const XMLNode &enum_node) {
|
|
std::string id;
|
|
|
|
enum_node.ForEachAttribute([&id](const llvm::StringRef &attr_name,
|
|
const llvm::StringRef &attr_value) {
|
|
if (attr_name == "id")
|
|
id = attr_value;
|
|
|
|
// There is also a "size" attribute that is supposed to be the size in
|
|
// bytes of the register this applies to. However:
|
|
// * LLDB doesn't need this information.
|
|
// * It is difficult to verify because you have to wait until the
|
|
// enum is applied to a field.
|
|
//
|
|
// So we will emit this attribute in XML for GDB's sake, but will not
|
|
// bother ingesting it.
|
|
|
|
// Walk all attributes.
|
|
return true;
|
|
});
|
|
|
|
if (!id.empty()) {
|
|
FieldEnum::Enumerators enumerators = ParseEnumEvalues(enum_node);
|
|
if (!enumerators.empty()) {
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseEnums Found enum type \"{0}\"",
|
|
id);
|
|
registers_enum_types.insert_or_assign(
|
|
id, std::make_unique<FieldEnum>(id, enumerators));
|
|
}
|
|
}
|
|
|
|
// Find all <enum> elements.
|
|
return true;
|
|
});
|
|
}
|
|
|
|
static std::vector<RegisterFlags::Field> ParseFlagsFields(
|
|
XMLNode flags_node, unsigned size,
|
|
const llvm::StringMap<std::unique_ptr<FieldEnum>> ®isters_enum_types) {
|
|
Log *log(GetLog(GDBRLog::Process));
|
|
const unsigned max_start_bit = size * 8 - 1;
|
|
|
|
// Process the fields of this set of flags.
|
|
std::vector<RegisterFlags::Field> fields;
|
|
flags_node.ForEachChildElementWithName("field", [&fields, max_start_bit, &log,
|
|
®isters_enum_types](
|
|
const XMLNode
|
|
&field_node) {
|
|
std::optional<llvm::StringRef> name;
|
|
std::optional<unsigned> start;
|
|
std::optional<unsigned> end;
|
|
std::optional<llvm::StringRef> type;
|
|
|
|
field_node.ForEachAttribute([&name, &start, &end, &type, max_start_bit,
|
|
&log](const llvm::StringRef &attr_name,
|
|
const llvm::StringRef &attr_value) {
|
|
// Note that XML in general requires that each of these attributes only
|
|
// appears once, so we don't have to handle that here.
|
|
if (attr_name == "name") {
|
|
LLDB_LOG(
|
|
log,
|
|
"ProcessGDBRemote::ParseFlagsFields Found field node name \"{0}\"",
|
|
attr_value.data());
|
|
name = attr_value;
|
|
} else if (attr_name == "start") {
|
|
unsigned parsed_start = 0;
|
|
if (llvm::to_integer(attr_value, parsed_start)) {
|
|
if (parsed_start > max_start_bit) {
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseFlagsFields Invalid start {0} in "
|
|
"field node, "
|
|
"cannot be > {1}",
|
|
parsed_start, max_start_bit);
|
|
} else
|
|
start = parsed_start;
|
|
} else {
|
|
LLDB_LOG(
|
|
log,
|
|
"ProcessGDBRemote::ParseFlagsFields Invalid start \"{0}\" in "
|
|
"field node",
|
|
attr_value.data());
|
|
}
|
|
} else if (attr_name == "end") {
|
|
unsigned parsed_end = 0;
|
|
if (llvm::to_integer(attr_value, parsed_end))
|
|
if (parsed_end > max_start_bit) {
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseFlagsFields Invalid end {0} in "
|
|
"field node, "
|
|
"cannot be > {1}",
|
|
parsed_end, max_start_bit);
|
|
} else
|
|
end = parsed_end;
|
|
else {
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseFlagsFields Invalid end \"{0}\" in "
|
|
"field node",
|
|
attr_value.data());
|
|
}
|
|
} else if (attr_name == "type") {
|
|
type = attr_value;
|
|
} else {
|
|
LLDB_LOG(
|
|
log,
|
|
"ProcessGDBRemote::ParseFlagsFields Ignoring unknown attribute "
|
|
"\"{0}\" in field node",
|
|
attr_name.data());
|
|
}
|
|
|
|
return true; // Walk all attributes of the field.
|
|
});
|
|
|
|
if (name && start && end) {
|
|
if (*start > *end)
|
|
LLDB_LOG(
|
|
log,
|
|
"ProcessGDBRemote::ParseFlagsFields Start {0} > end {1} in field "
|
|
"\"{2}\", ignoring",
|
|
*start, *end, name->data());
|
|
else {
|
|
if (RegisterFlags::Field::GetSizeInBits(*start, *end) > 64)
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseFlagsFields Ignoring field \"{2}\" "
|
|
"that has "
|
|
"size > 64 bits, this is not supported",
|
|
name->data());
|
|
else {
|
|
// A field's type may be set to the name of an enum type.
|
|
const FieldEnum *enum_type = nullptr;
|
|
if (type && !type->empty()) {
|
|
auto found = registers_enum_types.find(*type);
|
|
if (found != registers_enum_types.end()) {
|
|
enum_type = found->second.get();
|
|
|
|
// No enumerator can exceed the range of the field itself.
|
|
uint64_t max_value =
|
|
RegisterFlags::Field::GetMaxValue(*start, *end);
|
|
for (const auto &enumerator : enum_type->GetEnumerators()) {
|
|
if (enumerator.m_value > max_value) {
|
|
enum_type = nullptr;
|
|
LLDB_LOG(
|
|
log,
|
|
"ProcessGDBRemote::ParseFlagsFields In enum \"{0}\" "
|
|
"evalue \"{1}\" with value {2} exceeds the maximum value "
|
|
"of field \"{3}\" ({4}), ignoring enum",
|
|
type->data(), enumerator.m_name, enumerator.m_value,
|
|
name->data(), max_value);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseFlagsFields Could not find type "
|
|
"\"{0}\" "
|
|
"for field \"{1}\", ignoring",
|
|
type->data(), name->data());
|
|
}
|
|
}
|
|
|
|
fields.push_back(
|
|
RegisterFlags::Field(name->str(), *start, *end, enum_type));
|
|
}
|
|
}
|
|
}
|
|
|
|
return true; // Iterate all "field" nodes.
|
|
});
|
|
return fields;
|
|
}
|
|
|
|
void ParseFlags(
|
|
XMLNode feature_node,
|
|
llvm::StringMap<std::unique_ptr<RegisterFlags>> ®isters_flags_types,
|
|
const llvm::StringMap<std::unique_ptr<FieldEnum>> ®isters_enum_types) {
|
|
Log *log(GetLog(GDBRLog::Process));
|
|
|
|
feature_node.ForEachChildElementWithName(
|
|
"flags",
|
|
[&log, ®isters_flags_types,
|
|
®isters_enum_types](const XMLNode &flags_node) -> bool {
|
|
LLDB_LOG(log, "ProcessGDBRemote::ParseFlags Found flags node \"{0}\"",
|
|
flags_node.GetAttributeValue("id").c_str());
|
|
|
|
std::optional<llvm::StringRef> id;
|
|
std::optional<unsigned> size;
|
|
flags_node.ForEachAttribute(
|
|
[&id, &size, &log](const llvm::StringRef &name,
|
|
const llvm::StringRef &value) {
|
|
if (name == "id") {
|
|
id = value;
|
|
} else if (name == "size") {
|
|
unsigned parsed_size = 0;
|
|
if (llvm::to_integer(value, parsed_size))
|
|
size = parsed_size;
|
|
else {
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseFlags Invalid size \"{0}\" "
|
|
"in flags node",
|
|
value.data());
|
|
}
|
|
} else {
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::ParseFlags Ignoring unknown "
|
|
"attribute \"{0}\" in flags node",
|
|
name.data());
|
|
}
|
|
return true; // Walk all attributes.
|
|
});
|
|
|
|
if (id && size) {
|
|
// Process the fields of this set of flags.
|
|
std::vector<RegisterFlags::Field> fields =
|
|
ParseFlagsFields(flags_node, *size, registers_enum_types);
|
|
if (fields.size()) {
|
|
// Sort so that the fields with the MSBs are first.
|
|
std::sort(fields.rbegin(), fields.rend());
|
|
std::vector<RegisterFlags::Field>::const_iterator overlap =
|
|
std::adjacent_find(fields.begin(), fields.end(),
|
|
[](const RegisterFlags::Field &lhs,
|
|
const RegisterFlags::Field &rhs) {
|
|
return lhs.Overlaps(rhs);
|
|
});
|
|
|
|
// If no fields overlap, use them.
|
|
if (overlap == fields.end()) {
|
|
if (registers_flags_types.contains(*id)) {
|
|
// In theory you could define some flag set, use it with a
|
|
// register then redefine it. We do not know if anyone does
|
|
// that, or what they would expect to happen in that case.
|
|
//
|
|
// LLDB chooses to take the first definition and ignore the rest
|
|
// as waiting until everything has been processed is more
|
|
// expensive and difficult. This means that pointers to flag
|
|
// sets in the register info remain valid if later the flag set
|
|
// is redefined. If we allowed redefinitions, LLDB would crash
|
|
// when you tried to print a register that used the original
|
|
// definition.
|
|
LLDB_LOG(
|
|
log,
|
|
"ProcessGDBRemote::ParseFlags Definition of flags "
|
|
"\"{0}\" shadows "
|
|
"previous definition, using original definition instead.",
|
|
id->data());
|
|
} else {
|
|
registers_flags_types.insert_or_assign(
|
|
*id, std::make_unique<RegisterFlags>(id->str(), *size,
|
|
std::move(fields)));
|
|
}
|
|
} else {
|
|
// If any fields overlap, ignore the whole set of flags.
|
|
std::vector<RegisterFlags::Field>::const_iterator next =
|
|
std::next(overlap);
|
|
LLDB_LOG(
|
|
log,
|
|
"ProcessGDBRemote::ParseFlags Ignoring flags because fields "
|
|
"{0} (start: {1} end: {2}) and {3} (start: {4} end: {5}) "
|
|
"overlap.",
|
|
overlap->GetName().c_str(), overlap->GetStart(),
|
|
overlap->GetEnd(), next->GetName().c_str(), next->GetStart(),
|
|
next->GetEnd());
|
|
}
|
|
} else {
|
|
LLDB_LOG(
|
|
log,
|
|
"ProcessGDBRemote::ParseFlags Ignoring definition of flags "
|
|
"\"{0}\" because it contains no fields.",
|
|
id->data());
|
|
}
|
|
}
|
|
|
|
return true; // Keep iterating through all "flags" elements.
|
|
});
|
|
}
|
|
|
|
bool ParseRegisters(
|
|
XMLNode feature_node, GdbServerTargetInfo &target_info,
|
|
std::vector<DynamicRegisterInfo::Register> ®isters,
|
|
llvm::StringMap<std::unique_ptr<RegisterFlags>> ®isters_flags_types,
|
|
llvm::StringMap<std::unique_ptr<FieldEnum>> ®isters_enum_types) {
|
|
if (!feature_node)
|
|
return false;
|
|
|
|
Log *log(GetLog(GDBRLog::Process));
|
|
|
|
// Enums first because they are referenced by fields in the flags.
|
|
ParseEnums(feature_node, registers_enum_types);
|
|
for (const auto &enum_type : registers_enum_types)
|
|
enum_type.second->DumpToLog(log);
|
|
|
|
ParseFlags(feature_node, registers_flags_types, registers_enum_types);
|
|
for (const auto &flags : registers_flags_types)
|
|
flags.second->DumpToLog(log);
|
|
|
|
feature_node.ForEachChildElementWithName(
|
|
"reg",
|
|
[&target_info, ®isters, ®isters_flags_types,
|
|
log](const XMLNode ®_node) -> bool {
|
|
std::string gdb_group;
|
|
std::string gdb_type;
|
|
DynamicRegisterInfo::Register reg_info;
|
|
bool encoding_set = false;
|
|
bool format_set = false;
|
|
|
|
// FIXME: we're silently ignoring invalid data here
|
|
reg_node.ForEachAttribute([&target_info, &gdb_group, &gdb_type,
|
|
&encoding_set, &format_set, ®_info,
|
|
log](const llvm::StringRef &name,
|
|
const llvm::StringRef &value) -> bool {
|
|
if (name == "name") {
|
|
reg_info.name.SetString(value);
|
|
} else if (name == "bitsize") {
|
|
if (llvm::to_integer(value, reg_info.byte_size))
|
|
reg_info.byte_size =
|
|
llvm::divideCeil(reg_info.byte_size, CHAR_BIT);
|
|
} else if (name == "type") {
|
|
gdb_type = value.str();
|
|
} else if (name == "group") {
|
|
gdb_group = value.str();
|
|
} else if (name == "regnum") {
|
|
llvm::to_integer(value, reg_info.regnum_remote);
|
|
} else if (name == "offset") {
|
|
llvm::to_integer(value, reg_info.byte_offset);
|
|
} else if (name == "altname") {
|
|
reg_info.alt_name.SetString(value);
|
|
} else if (name == "encoding") {
|
|
encoding_set = true;
|
|
reg_info.encoding = Args::StringToEncoding(value, eEncodingUint);
|
|
} else if (name == "format") {
|
|
format_set = true;
|
|
if (!OptionArgParser::ToFormat(value.data(), reg_info.format,
|
|
nullptr)
|
|
.Success())
|
|
reg_info.format =
|
|
llvm::StringSwitch<lldb::Format>(value)
|
|
.Case("vector-sint8", eFormatVectorOfSInt8)
|
|
.Case("vector-uint8", eFormatVectorOfUInt8)
|
|
.Case("vector-sint16", eFormatVectorOfSInt16)
|
|
.Case("vector-uint16", eFormatVectorOfUInt16)
|
|
.Case("vector-sint32", eFormatVectorOfSInt32)
|
|
.Case("vector-uint32", eFormatVectorOfUInt32)
|
|
.Case("vector-float32", eFormatVectorOfFloat32)
|
|
.Case("vector-uint64", eFormatVectorOfUInt64)
|
|
.Case("vector-uint128", eFormatVectorOfUInt128)
|
|
.Default(eFormatInvalid);
|
|
} else if (name == "group_id") {
|
|
uint32_t set_id = UINT32_MAX;
|
|
llvm::to_integer(value, set_id);
|
|
RegisterSetMap::const_iterator pos =
|
|
target_info.reg_set_map.find(set_id);
|
|
if (pos != target_info.reg_set_map.end())
|
|
reg_info.set_name = pos->second.name;
|
|
} else if (name == "gcc_regnum" || name == "ehframe_regnum") {
|
|
llvm::to_integer(value, reg_info.regnum_ehframe);
|
|
} else if (name == "dwarf_regnum") {
|
|
llvm::to_integer(value, reg_info.regnum_dwarf);
|
|
} else if (name == "generic") {
|
|
reg_info.regnum_generic = Args::StringToGenericRegister(value);
|
|
} else if (name == "value_regnums") {
|
|
SplitCommaSeparatedRegisterNumberString(value, reg_info.value_regs,
|
|
0);
|
|
} else if (name == "invalidate_regnums") {
|
|
SplitCommaSeparatedRegisterNumberString(
|
|
value, reg_info.invalidate_regs, 0);
|
|
} else {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::ParseRegisters unhandled reg "
|
|
"attribute %s = %s",
|
|
name.data(), value.data());
|
|
}
|
|
return true; // Keep iterating through all attributes
|
|
});
|
|
|
|
if (!gdb_type.empty()) {
|
|
// gdb_type could reference some flags type defined in XML.
|
|
llvm::StringMap<std::unique_ptr<RegisterFlags>>::iterator it =
|
|
registers_flags_types.find(gdb_type);
|
|
if (it != registers_flags_types.end()) {
|
|
auto flags_type = it->second.get();
|
|
if (reg_info.byte_size == flags_type->GetSize())
|
|
reg_info.flags_type = flags_type;
|
|
else
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::ParseRegisters Size of register "
|
|
"flags %s (%d bytes) for "
|
|
"register %s does not match the register size (%d "
|
|
"bytes). Ignoring this set of flags.",
|
|
flags_type->GetID().c_str(), flags_type->GetSize(),
|
|
reg_info.name.AsCString(), reg_info.byte_size);
|
|
}
|
|
|
|
// There's a slim chance that the gdb_type name is both a flags type
|
|
// and a simple type. Just in case, look for that too (setting both
|
|
// does no harm).
|
|
if (!gdb_type.empty() && !(encoding_set || format_set)) {
|
|
if (llvm::StringRef(gdb_type).starts_with("int")) {
|
|
reg_info.format = eFormatHex;
|
|
reg_info.encoding = eEncodingUint;
|
|
} else if (gdb_type == "data_ptr" || gdb_type == "code_ptr") {
|
|
reg_info.format = eFormatAddressInfo;
|
|
reg_info.encoding = eEncodingUint;
|
|
} else if (gdb_type == "float") {
|
|
reg_info.format = eFormatFloat;
|
|
reg_info.encoding = eEncodingIEEE754;
|
|
} else if (gdb_type == "aarch64v" ||
|
|
llvm::StringRef(gdb_type).starts_with("vec") ||
|
|
gdb_type == "i387_ext" || gdb_type == "uint128" ||
|
|
reg_info.byte_size > 16) {
|
|
// lldb doesn't handle 128-bit uints correctly (for ymm*h), so
|
|
// treat them as vector (similarly to xmm/ymm).
|
|
// We can fall back to handling anything else <= 128 bit as an
|
|
// unsigned integer, more than that, call it a vector of bytes.
|
|
// This can happen if we don't recognise the type for AArc64 SVE
|
|
// registers.
|
|
reg_info.format = eFormatVectorOfUInt8;
|
|
reg_info.encoding = eEncodingVector;
|
|
} else {
|
|
LLDB_LOGF(
|
|
log,
|
|
"ProcessGDBRemote::ParseRegisters Could not determine lldb"
|
|
"format and encoding for gdb type %s",
|
|
gdb_type.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only update the register set name if we didn't get a "reg_set"
|
|
// attribute. "set_name" will be empty if we didn't have a "reg_set"
|
|
// attribute.
|
|
if (!reg_info.set_name) {
|
|
if (!gdb_group.empty()) {
|
|
reg_info.set_name.SetCString(gdb_group.c_str());
|
|
} else {
|
|
// If no register group name provided anywhere,
|
|
// we'll create a 'general' register set
|
|
reg_info.set_name.SetCString("general");
|
|
}
|
|
}
|
|
|
|
if (reg_info.byte_size == 0) {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s Skipping zero bitsize register %s",
|
|
__FUNCTION__, reg_info.name.AsCString());
|
|
} else
|
|
registers.push_back(reg_info);
|
|
|
|
return true; // Keep iterating through all "reg" elements
|
|
});
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// This method fetches a register description feature xml file from
|
|
// the remote stub and adds registers/register groupsets/architecture
|
|
// information to the current process. It will call itself recursively
|
|
// for nested register definition files. It returns true if it was able
|
|
// to fetch and parse an xml file.
|
|
bool ProcessGDBRemote::GetGDBServerRegisterInfoXMLAndProcess(
|
|
ArchSpec &arch_to_use, std::string xml_filename,
|
|
std::vector<DynamicRegisterInfo::Register> ®isters) {
|
|
// request the target xml file
|
|
llvm::Expected<std::string> raw = m_gdb_comm.ReadExtFeature("features", xml_filename);
|
|
if (errorToBool(raw.takeError()))
|
|
return false;
|
|
|
|
XMLDocument xml_document;
|
|
|
|
if (xml_document.ParseMemory(raw->c_str(), raw->size(),
|
|
xml_filename.c_str())) {
|
|
GdbServerTargetInfo target_info;
|
|
std::vector<XMLNode> feature_nodes;
|
|
|
|
// The top level feature XML file will start with a <target> tag.
|
|
XMLNode target_node = xml_document.GetRootElement("target");
|
|
if (target_node) {
|
|
target_node.ForEachChildElement([&target_info, &feature_nodes](
|
|
const XMLNode &node) -> bool {
|
|
llvm::StringRef name = node.GetName();
|
|
if (name == "architecture") {
|
|
node.GetElementText(target_info.arch);
|
|
} else if (name == "osabi") {
|
|
node.GetElementText(target_info.osabi);
|
|
} else if (name == "xi:include" || name == "include") {
|
|
std::string href = node.GetAttributeValue("href");
|
|
if (!href.empty())
|
|
target_info.includes.push_back(href);
|
|
} else if (name == "feature") {
|
|
feature_nodes.push_back(node);
|
|
} else if (name == "groups") {
|
|
node.ForEachChildElementWithName(
|
|
"group", [&target_info](const XMLNode &node) -> bool {
|
|
uint32_t set_id = UINT32_MAX;
|
|
RegisterSetInfo set_info;
|
|
|
|
node.ForEachAttribute(
|
|
[&set_id, &set_info](const llvm::StringRef &name,
|
|
const llvm::StringRef &value) -> bool {
|
|
// FIXME: we're silently ignoring invalid data here
|
|
if (name == "id")
|
|
llvm::to_integer(value, set_id);
|
|
if (name == "name")
|
|
set_info.name = ConstString(value);
|
|
return true; // Keep iterating through all attributes
|
|
});
|
|
|
|
if (set_id != UINT32_MAX)
|
|
target_info.reg_set_map[set_id] = set_info;
|
|
return true; // Keep iterating through all "group" elements
|
|
});
|
|
}
|
|
return true; // Keep iterating through all children of the target_node
|
|
});
|
|
} else {
|
|
// In an included XML feature file, we're already "inside" the <target>
|
|
// tag of the initial XML file; this included file will likely only have
|
|
// a <feature> tag. Need to check for any more included files in this
|
|
// <feature> element.
|
|
XMLNode feature_node = xml_document.GetRootElement("feature");
|
|
if (feature_node) {
|
|
feature_nodes.push_back(feature_node);
|
|
feature_node.ForEachChildElement([&target_info](
|
|
const XMLNode &node) -> bool {
|
|
llvm::StringRef name = node.GetName();
|
|
if (name == "xi:include" || name == "include") {
|
|
std::string href = node.GetAttributeValue("href");
|
|
if (!href.empty())
|
|
target_info.includes.push_back(href);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
// gdbserver does not implement the LLDB packets used to determine host
|
|
// or process architecture. If that is the case, attempt to use
|
|
// the <architecture/> field from target.xml, e.g.:
|
|
//
|
|
// <architecture>i386:x86-64</architecture> (seen from VMWare ESXi)
|
|
// <architecture>arm</architecture> (seen from Segger JLink on unspecified
|
|
// arm board)
|
|
if (!arch_to_use.IsValid() && !target_info.arch.empty()) {
|
|
// We don't have any information about vendor or OS.
|
|
arch_to_use.SetTriple(llvm::StringSwitch<std::string>(target_info.arch)
|
|
.Case("i386:x86-64", "x86_64")
|
|
.Case("riscv:rv64", "riscv64")
|
|
.Case("riscv:rv32", "riscv32")
|
|
.Default(target_info.arch) +
|
|
"--");
|
|
|
|
if (arch_to_use.IsValid())
|
|
GetTarget().MergeArchitecture(arch_to_use);
|
|
}
|
|
|
|
if (arch_to_use.IsValid()) {
|
|
for (auto &feature_node : feature_nodes) {
|
|
ParseRegisters(feature_node, target_info, registers,
|
|
m_registers_flags_types, m_registers_enum_types);
|
|
}
|
|
|
|
for (const auto &include : target_info.includes) {
|
|
GetGDBServerRegisterInfoXMLAndProcess(arch_to_use, include,
|
|
registers);
|
|
}
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ProcessGDBRemote::AddRemoteRegisters(
|
|
std::vector<DynamicRegisterInfo::Register> ®isters,
|
|
const ArchSpec &arch_to_use) {
|
|
std::map<uint32_t, uint32_t> remote_to_local_map;
|
|
uint32_t remote_regnum = 0;
|
|
for (auto it : llvm::enumerate(registers)) {
|
|
DynamicRegisterInfo::Register &remote_reg_info = it.value();
|
|
|
|
// Assign successive remote regnums if missing.
|
|
if (remote_reg_info.regnum_remote == LLDB_INVALID_REGNUM)
|
|
remote_reg_info.regnum_remote = remote_regnum;
|
|
|
|
// Create a mapping from remote to local regnos.
|
|
remote_to_local_map[remote_reg_info.regnum_remote] = it.index();
|
|
|
|
remote_regnum = remote_reg_info.regnum_remote + 1;
|
|
}
|
|
|
|
for (DynamicRegisterInfo::Register &remote_reg_info : registers) {
|
|
auto proc_to_lldb = [&remote_to_local_map](uint32_t process_regnum) {
|
|
auto lldb_regit = remote_to_local_map.find(process_regnum);
|
|
return lldb_regit != remote_to_local_map.end() ? lldb_regit->second
|
|
: LLDB_INVALID_REGNUM;
|
|
};
|
|
|
|
llvm::transform(remote_reg_info.value_regs,
|
|
remote_reg_info.value_regs.begin(), proc_to_lldb);
|
|
llvm::transform(remote_reg_info.invalidate_regs,
|
|
remote_reg_info.invalidate_regs.begin(), proc_to_lldb);
|
|
}
|
|
|
|
// Don't use Process::GetABI, this code gets called from DidAttach, and
|
|
// in that context we haven't set the Target's architecture yet, so the
|
|
// ABI is also potentially incorrect.
|
|
if (ABISP abi_sp = ABI::FindPlugin(shared_from_this(), arch_to_use))
|
|
abi_sp->AugmentRegisterInfo(registers);
|
|
|
|
m_register_info_sp->SetRegisterInfo(std::move(registers), arch_to_use);
|
|
}
|
|
|
|
// query the target of gdb-remote for extended target information returns
|
|
// true on success (got register definitions), false on failure (did not).
|
|
bool ProcessGDBRemote::GetGDBServerRegisterInfo(ArchSpec &arch_to_use) {
|
|
// Make sure LLDB has an XML parser it can use first
|
|
if (!XMLDocument::XMLEnabled())
|
|
return false;
|
|
|
|
// check that we have extended feature read support
|
|
if (!m_gdb_comm.GetQXferFeaturesReadSupported())
|
|
return false;
|
|
|
|
// These hold register type information for the whole of target.xml.
|
|
// target.xml may include further documents that
|
|
// GetGDBServerRegisterInfoXMLAndProcess will recurse to fetch and process.
|
|
// That's why we clear the cache here, and not in
|
|
// GetGDBServerRegisterInfoXMLAndProcess. To prevent it being cleared on every
|
|
// include read.
|
|
m_registers_flags_types.clear();
|
|
m_registers_enum_types.clear();
|
|
std::vector<DynamicRegisterInfo::Register> registers;
|
|
if (GetGDBServerRegisterInfoXMLAndProcess(arch_to_use, "target.xml",
|
|
registers) &&
|
|
// Target XML is not required to include register information.
|
|
!registers.empty())
|
|
AddRemoteRegisters(registers, arch_to_use);
|
|
|
|
return m_register_info_sp->GetNumRegisters() > 0;
|
|
}
|
|
|
|
llvm::Expected<LoadedModuleInfoList> ProcessGDBRemote::GetLoadedModuleList() {
|
|
// Make sure LLDB has an XML parser it can use first
|
|
if (!XMLDocument::XMLEnabled())
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"XML parsing not available");
|
|
|
|
Log *log = GetLog(LLDBLog::Process);
|
|
LLDB_LOGF(log, "ProcessGDBRemote::%s", __FUNCTION__);
|
|
|
|
LoadedModuleInfoList list;
|
|
GDBRemoteCommunicationClient &comm = m_gdb_comm;
|
|
bool can_use_svr4 = GetGlobalPluginProperties().GetUseSVR4();
|
|
|
|
// check that we have extended feature read support
|
|
if (can_use_svr4 && comm.GetQXferLibrariesSVR4ReadSupported()) {
|
|
// request the loaded library list
|
|
llvm::Expected<std::string> raw = comm.ReadExtFeature("libraries-svr4", "");
|
|
if (!raw)
|
|
return raw.takeError();
|
|
|
|
// parse the xml file in memory
|
|
LLDB_LOGF(log, "parsing: %s", raw->c_str());
|
|
XMLDocument doc;
|
|
|
|
if (!doc.ParseMemory(raw->c_str(), raw->size(), "noname.xml"))
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Error reading noname.xml");
|
|
|
|
XMLNode root_element = doc.GetRootElement("library-list-svr4");
|
|
if (!root_element)
|
|
return llvm::createStringError(
|
|
llvm::inconvertibleErrorCode(),
|
|
"Error finding library-list-svr4 xml element");
|
|
|
|
// main link map structure
|
|
std::string main_lm = root_element.GetAttributeValue("main-lm");
|
|
// FIXME: we're silently ignoring invalid data here
|
|
if (!main_lm.empty())
|
|
llvm::to_integer(main_lm, list.m_link_map);
|
|
|
|
root_element.ForEachChildElementWithName(
|
|
"library", [log, &list](const XMLNode &library) -> bool {
|
|
LoadedModuleInfoList::LoadedModuleInfo module;
|
|
|
|
// FIXME: we're silently ignoring invalid data here
|
|
library.ForEachAttribute(
|
|
[&module](const llvm::StringRef &name,
|
|
const llvm::StringRef &value) -> bool {
|
|
uint64_t uint_value = LLDB_INVALID_ADDRESS;
|
|
if (name == "name")
|
|
module.set_name(value.str());
|
|
else if (name == "lm") {
|
|
// the address of the link_map struct.
|
|
llvm::to_integer(value, uint_value);
|
|
module.set_link_map(uint_value);
|
|
} else if (name == "l_addr") {
|
|
// the displacement as read from the field 'l_addr' of the
|
|
// link_map struct.
|
|
llvm::to_integer(value, uint_value);
|
|
module.set_base(uint_value);
|
|
// base address is always a displacement, not an absolute
|
|
// value.
|
|
module.set_base_is_offset(true);
|
|
} else if (name == "l_ld") {
|
|
// the memory address of the libraries PT_DYNAMIC section.
|
|
llvm::to_integer(value, uint_value);
|
|
module.set_dynamic(uint_value);
|
|
}
|
|
|
|
return true; // Keep iterating over all properties of "library"
|
|
});
|
|
|
|
if (log) {
|
|
std::string name;
|
|
lldb::addr_t lm = 0, base = 0, ld = 0;
|
|
bool base_is_offset;
|
|
|
|
module.get_name(name);
|
|
module.get_link_map(lm);
|
|
module.get_base(base);
|
|
module.get_base_is_offset(base_is_offset);
|
|
module.get_dynamic(ld);
|
|
|
|
LLDB_LOGF(log,
|
|
"found (link_map:0x%08" PRIx64 ", base:0x%08" PRIx64
|
|
"[%s], ld:0x%08" PRIx64 ", name:'%s')",
|
|
lm, base, (base_is_offset ? "offset" : "absolute"), ld,
|
|
name.c_str());
|
|
}
|
|
|
|
list.add(module);
|
|
return true; // Keep iterating over all "library" elements in the root
|
|
// node
|
|
});
|
|
|
|
if (log)
|
|
LLDB_LOGF(log, "found %" PRId32 " modules in total",
|
|
(int)list.m_list.size());
|
|
return list;
|
|
} else if (comm.GetQXferLibrariesReadSupported()) {
|
|
// request the loaded library list
|
|
llvm::Expected<std::string> raw = comm.ReadExtFeature("libraries", "");
|
|
|
|
if (!raw)
|
|
return raw.takeError();
|
|
|
|
LLDB_LOGF(log, "parsing: %s", raw->c_str());
|
|
XMLDocument doc;
|
|
|
|
if (!doc.ParseMemory(raw->c_str(), raw->size(), "noname.xml"))
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Error reading noname.xml");
|
|
|
|
XMLNode root_element = doc.GetRootElement("library-list");
|
|
if (!root_element)
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Error finding library-list xml element");
|
|
|
|
// FIXME: we're silently ignoring invalid data here
|
|
root_element.ForEachChildElementWithName(
|
|
"library", [log, &list](const XMLNode &library) -> bool {
|
|
LoadedModuleInfoList::LoadedModuleInfo module;
|
|
|
|
std::string name = library.GetAttributeValue("name");
|
|
module.set_name(name);
|
|
|
|
// The base address of a given library will be the address of its
|
|
// first section. Most remotes send only one section for Windows
|
|
// targets for example.
|
|
const XMLNode §ion =
|
|
library.FindFirstChildElementWithName("section");
|
|
std::string address = section.GetAttributeValue("address");
|
|
uint64_t address_value = LLDB_INVALID_ADDRESS;
|
|
llvm::to_integer(address, address_value);
|
|
module.set_base(address_value);
|
|
// These addresses are absolute values.
|
|
module.set_base_is_offset(false);
|
|
|
|
if (log) {
|
|
std::string name;
|
|
lldb::addr_t base = 0;
|
|
bool base_is_offset;
|
|
module.get_name(name);
|
|
module.get_base(base);
|
|
module.get_base_is_offset(base_is_offset);
|
|
|
|
LLDB_LOGF(log, "found (base:0x%08" PRIx64 "[%s], name:'%s')", base,
|
|
(base_is_offset ? "offset" : "absolute"), name.c_str());
|
|
}
|
|
|
|
list.add(module);
|
|
return true; // Keep iterating over all "library" elements in the root
|
|
// node
|
|
});
|
|
|
|
if (log)
|
|
LLDB_LOGF(log, "found %" PRId32 " modules in total",
|
|
(int)list.m_list.size());
|
|
return list;
|
|
} else {
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Remote libraries not supported");
|
|
}
|
|
}
|
|
|
|
lldb::ModuleSP ProcessGDBRemote::LoadModuleAtAddress(const FileSpec &file,
|
|
lldb::addr_t link_map,
|
|
lldb::addr_t base_addr,
|
|
bool value_is_offset) {
|
|
DynamicLoader *loader = GetDynamicLoader();
|
|
if (!loader)
|
|
return nullptr;
|
|
|
|
return loader->LoadModuleAtAddress(file, link_map, base_addr,
|
|
value_is_offset);
|
|
}
|
|
|
|
llvm::Error ProcessGDBRemote::LoadModules() {
|
|
using lldb_private::process_gdb_remote::ProcessGDBRemote;
|
|
|
|
// request a list of loaded libraries from GDBServer
|
|
llvm::Expected<LoadedModuleInfoList> module_list = GetLoadedModuleList();
|
|
if (!module_list)
|
|
return module_list.takeError();
|
|
|
|
// get a list of all the modules
|
|
ModuleList new_modules;
|
|
|
|
for (LoadedModuleInfoList::LoadedModuleInfo &modInfo : module_list->m_list) {
|
|
std::string mod_name;
|
|
lldb::addr_t mod_base;
|
|
lldb::addr_t link_map;
|
|
bool mod_base_is_offset;
|
|
|
|
bool valid = true;
|
|
valid &= modInfo.get_name(mod_name);
|
|
valid &= modInfo.get_base(mod_base);
|
|
valid &= modInfo.get_base_is_offset(mod_base_is_offset);
|
|
if (!valid)
|
|
continue;
|
|
|
|
if (!modInfo.get_link_map(link_map))
|
|
link_map = LLDB_INVALID_ADDRESS;
|
|
|
|
FileSpec file(mod_name);
|
|
FileSystem::Instance().Resolve(file);
|
|
lldb::ModuleSP module_sp =
|
|
LoadModuleAtAddress(file, link_map, mod_base, mod_base_is_offset);
|
|
|
|
if (module_sp.get())
|
|
new_modules.Append(module_sp);
|
|
}
|
|
|
|
if (new_modules.GetSize() > 0) {
|
|
ModuleList removed_modules;
|
|
Target &target = GetTarget();
|
|
ModuleList &loaded_modules = m_process->GetTarget().GetImages();
|
|
|
|
for (size_t i = 0; i < loaded_modules.GetSize(); ++i) {
|
|
const lldb::ModuleSP loaded_module = loaded_modules.GetModuleAtIndex(i);
|
|
|
|
bool found = false;
|
|
for (size_t j = 0; j < new_modules.GetSize(); ++j) {
|
|
if (new_modules.GetModuleAtIndex(j).get() == loaded_module.get())
|
|
found = true;
|
|
}
|
|
|
|
// The main executable will never be included in libraries-svr4, don't
|
|
// remove it
|
|
if (!found &&
|
|
loaded_module.get() != target.GetExecutableModulePointer()) {
|
|
removed_modules.Append(loaded_module);
|
|
}
|
|
}
|
|
|
|
loaded_modules.Remove(removed_modules);
|
|
m_process->GetTarget().ModulesDidUnload(removed_modules, false);
|
|
|
|
new_modules.ForEach([&target](const lldb::ModuleSP module_sp) -> bool {
|
|
lldb_private::ObjectFile *obj = module_sp->GetObjectFile();
|
|
if (!obj)
|
|
return true;
|
|
|
|
if (obj->GetType() != ObjectFile::Type::eTypeExecutable)
|
|
return true;
|
|
|
|
lldb::ModuleSP module_copy_sp = module_sp;
|
|
target.SetExecutableModule(module_copy_sp, eLoadDependentsNo);
|
|
return false;
|
|
});
|
|
|
|
loaded_modules.AppendIfNeeded(new_modules);
|
|
m_process->GetTarget().ModulesDidLoad(new_modules);
|
|
}
|
|
|
|
return llvm::ErrorSuccess();
|
|
}
|
|
|
|
Status ProcessGDBRemote::GetFileLoadAddress(const FileSpec &file,
|
|
bool &is_loaded,
|
|
lldb::addr_t &load_addr) {
|
|
is_loaded = false;
|
|
load_addr = LLDB_INVALID_ADDRESS;
|
|
|
|
std::string file_path = file.GetPath(false);
|
|
if (file_path.empty())
|
|
return Status::FromErrorString("Empty file name specified");
|
|
|
|
StreamString packet;
|
|
packet.PutCString("qFileLoadAddress:");
|
|
packet.PutStringAsRawHex8(file_path);
|
|
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
|
|
GDBRemoteCommunication::PacketResult::Success)
|
|
return Status::FromErrorString("Sending qFileLoadAddress packet failed");
|
|
|
|
if (response.IsErrorResponse()) {
|
|
if (response.GetError() == 1) {
|
|
// The file is not loaded into the inferior
|
|
is_loaded = false;
|
|
load_addr = LLDB_INVALID_ADDRESS;
|
|
return Status();
|
|
}
|
|
|
|
return Status::FromErrorString(
|
|
"Fetching file load address from remote server returned an error");
|
|
}
|
|
|
|
if (response.IsNormalResponse()) {
|
|
is_loaded = true;
|
|
load_addr = response.GetHexMaxU64(false, LLDB_INVALID_ADDRESS);
|
|
return Status();
|
|
}
|
|
|
|
return Status::FromErrorString(
|
|
"Unknown error happened during sending the load address packet");
|
|
}
|
|
|
|
void ProcessGDBRemote::ModulesDidLoad(ModuleList &module_list) {
|
|
// We must call the lldb_private::Process::ModulesDidLoad () first before we
|
|
// do anything
|
|
Process::ModulesDidLoad(module_list);
|
|
|
|
// After loading shared libraries, we can ask our remote GDB server if it
|
|
// needs any symbols.
|
|
m_gdb_comm.ServeSymbolLookups(this);
|
|
}
|
|
|
|
void ProcessGDBRemote::HandleAsyncStdout(llvm::StringRef out) {
|
|
AppendSTDOUT(out.data(), out.size());
|
|
}
|
|
|
|
static const char *end_delimiter = "--end--;";
|
|
static const int end_delimiter_len = 8;
|
|
|
|
void ProcessGDBRemote::HandleAsyncMisc(llvm::StringRef data) {
|
|
std::string input = data.str(); // '1' to move beyond 'A'
|
|
if (m_partial_profile_data.length() > 0) {
|
|
m_partial_profile_data.append(input);
|
|
input = m_partial_profile_data;
|
|
m_partial_profile_data.clear();
|
|
}
|
|
|
|
size_t found, pos = 0, len = input.length();
|
|
while ((found = input.find(end_delimiter, pos)) != std::string::npos) {
|
|
StringExtractorGDBRemote profileDataExtractor(
|
|
input.substr(pos, found).c_str());
|
|
std::string profile_data =
|
|
HarmonizeThreadIdsForProfileData(profileDataExtractor);
|
|
BroadcastAsyncProfileData(profile_data);
|
|
|
|
pos = found + end_delimiter_len;
|
|
}
|
|
|
|
if (pos < len) {
|
|
// Last incomplete chunk.
|
|
m_partial_profile_data = input.substr(pos);
|
|
}
|
|
}
|
|
|
|
std::string ProcessGDBRemote::HarmonizeThreadIdsForProfileData(
|
|
StringExtractorGDBRemote &profileDataExtractor) {
|
|
std::map<uint64_t, uint32_t> new_thread_id_to_used_usec_map;
|
|
std::string output;
|
|
llvm::raw_string_ostream output_stream(output);
|
|
llvm::StringRef name, value;
|
|
|
|
// Going to assuming thread_used_usec comes first, else bail out.
|
|
while (profileDataExtractor.GetNameColonValue(name, value)) {
|
|
if (name.compare("thread_used_id") == 0) {
|
|
StringExtractor threadIDHexExtractor(value);
|
|
uint64_t thread_id = threadIDHexExtractor.GetHexMaxU64(false, 0);
|
|
|
|
bool has_used_usec = false;
|
|
uint32_t curr_used_usec = 0;
|
|
llvm::StringRef usec_name, usec_value;
|
|
uint32_t input_file_pos = profileDataExtractor.GetFilePos();
|
|
if (profileDataExtractor.GetNameColonValue(usec_name, usec_value)) {
|
|
if (usec_name == "thread_used_usec") {
|
|
has_used_usec = true;
|
|
usec_value.getAsInteger(0, curr_used_usec);
|
|
} else {
|
|
// We didn't find what we want, it is probably an older version. Bail
|
|
// out.
|
|
profileDataExtractor.SetFilePos(input_file_pos);
|
|
}
|
|
}
|
|
|
|
if (has_used_usec) {
|
|
uint32_t prev_used_usec = 0;
|
|
std::map<uint64_t, uint32_t>::iterator iterator =
|
|
m_thread_id_to_used_usec_map.find(thread_id);
|
|
if (iterator != m_thread_id_to_used_usec_map.end())
|
|
prev_used_usec = iterator->second;
|
|
|
|
uint32_t real_used_usec = curr_used_usec - prev_used_usec;
|
|
// A good first time record is one that runs for at least 0.25 sec
|
|
bool good_first_time =
|
|
(prev_used_usec == 0) && (real_used_usec > 250000);
|
|
bool good_subsequent_time =
|
|
(prev_used_usec > 0) &&
|
|
((real_used_usec > 0) || (HasAssignedIndexIDToThread(thread_id)));
|
|
|
|
if (good_first_time || good_subsequent_time) {
|
|
// We try to avoid doing too many index id reservation, resulting in
|
|
// fast increase of index ids.
|
|
|
|
output_stream << name << ":";
|
|
int32_t index_id = AssignIndexIDToThread(thread_id);
|
|
output_stream << index_id << ";";
|
|
|
|
output_stream << usec_name << ":" << usec_value << ";";
|
|
} else {
|
|
// Skip past 'thread_used_name'.
|
|
llvm::StringRef local_name, local_value;
|
|
profileDataExtractor.GetNameColonValue(local_name, local_value);
|
|
}
|
|
|
|
// Store current time as previous time so that they can be compared
|
|
// later.
|
|
new_thread_id_to_used_usec_map[thread_id] = curr_used_usec;
|
|
} else {
|
|
// Bail out and use old string.
|
|
output_stream << name << ":" << value << ";";
|
|
}
|
|
} else {
|
|
output_stream << name << ":" << value << ";";
|
|
}
|
|
}
|
|
output_stream << end_delimiter;
|
|
m_thread_id_to_used_usec_map = new_thread_id_to_used_usec_map;
|
|
|
|
return output;
|
|
}
|
|
|
|
void ProcessGDBRemote::HandleStopReply() {
|
|
if (GetStopID() != 0)
|
|
return;
|
|
|
|
if (GetID() == LLDB_INVALID_PROCESS_ID) {
|
|
lldb::pid_t pid = m_gdb_comm.GetCurrentProcessID();
|
|
if (pid != LLDB_INVALID_PROCESS_ID)
|
|
SetID(pid);
|
|
}
|
|
BuildDynamicRegisterInfo(true);
|
|
}
|
|
|
|
llvm::Expected<bool> ProcessGDBRemote::SaveCore(llvm::StringRef outfile) {
|
|
if (!m_gdb_comm.GetSaveCoreSupported())
|
|
return false;
|
|
|
|
StreamString packet;
|
|
packet.PutCString("qSaveCore;path-hint:");
|
|
packet.PutStringAsRawHex8(outfile);
|
|
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) ==
|
|
GDBRemoteCommunication::PacketResult::Success) {
|
|
// TODO: grab error message from the packet? StringExtractor seems to
|
|
// be missing a method for that
|
|
if (response.IsErrorResponse())
|
|
return llvm::createStringError(
|
|
llvm::inconvertibleErrorCode(),
|
|
llvm::formatv("qSaveCore returned an error"));
|
|
|
|
std::string path;
|
|
|
|
// process the response
|
|
for (auto x : llvm::split(response.GetStringRef(), ';')) {
|
|
if (x.consume_front("core-path:"))
|
|
StringExtractor(x).GetHexByteString(path);
|
|
}
|
|
|
|
// verify that we've gotten what we need
|
|
if (path.empty())
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"qSaveCore returned no core path");
|
|
|
|
// now transfer the core file
|
|
FileSpec remote_core{llvm::StringRef(path)};
|
|
Platform &platform = *GetTarget().GetPlatform();
|
|
Status error = platform.GetFile(remote_core, FileSpec(outfile));
|
|
|
|
if (platform.IsRemote()) {
|
|
// NB: we unlink the file on error too
|
|
platform.Unlink(remote_core);
|
|
if (error.Fail())
|
|
return error.ToError();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Unable to send qSaveCore");
|
|
}
|
|
|
|
static const char *const s_async_json_packet_prefix = "JSON-async:";
|
|
|
|
static StructuredData::ObjectSP
|
|
ParseStructuredDataPacket(llvm::StringRef packet) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
|
|
if (!packet.consume_front(s_async_json_packet_prefix)) {
|
|
if (log) {
|
|
LLDB_LOGF(
|
|
log,
|
|
"GDBRemoteCommunicationClientBase::%s() received $J packet "
|
|
"but was not a StructuredData packet: packet starts with "
|
|
"%s",
|
|
__FUNCTION__,
|
|
packet.slice(0, strlen(s_async_json_packet_prefix)).str().c_str());
|
|
}
|
|
return StructuredData::ObjectSP();
|
|
}
|
|
|
|
// This is an asynchronous JSON packet, destined for a StructuredDataPlugin.
|
|
StructuredData::ObjectSP json_sp = StructuredData::ParseJSON(packet);
|
|
if (log) {
|
|
if (json_sp) {
|
|
StreamString json_str;
|
|
json_sp->Dump(json_str, true);
|
|
json_str.Flush();
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s() "
|
|
"received Async StructuredData packet: %s",
|
|
__FUNCTION__, json_str.GetData());
|
|
} else {
|
|
LLDB_LOGF(log,
|
|
"ProcessGDBRemote::%s"
|
|
"() received StructuredData packet:"
|
|
" parse failure",
|
|
__FUNCTION__);
|
|
}
|
|
}
|
|
return json_sp;
|
|
}
|
|
|
|
void ProcessGDBRemote::HandleAsyncStructuredDataPacket(llvm::StringRef data) {
|
|
auto structured_data_sp = ParseStructuredDataPacket(data);
|
|
if (structured_data_sp)
|
|
RouteAsyncStructuredData(structured_data_sp);
|
|
}
|
|
|
|
class CommandObjectProcessGDBRemoteSpeedTest : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectProcessGDBRemoteSpeedTest(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "process plugin packet speed-test",
|
|
"Tests packet speeds of various sizes to determine "
|
|
"the performance characteristics of the GDB remote "
|
|
"connection. ",
|
|
nullptr),
|
|
m_option_group(),
|
|
m_num_packets(LLDB_OPT_SET_1, false, "count", 'c', 0, eArgTypeCount,
|
|
"The number of packets to send of each varying size "
|
|
"(default is 1000).",
|
|
1000),
|
|
m_max_send(LLDB_OPT_SET_1, false, "max-send", 's', 0, eArgTypeCount,
|
|
"The maximum number of bytes to send in a packet. Sizes "
|
|
"increase in powers of 2 while the size is less than or "
|
|
"equal to this option value. (default 1024).",
|
|
1024),
|
|
m_max_recv(LLDB_OPT_SET_1, false, "max-receive", 'r', 0, eArgTypeCount,
|
|
"The maximum number of bytes to receive in a packet. Sizes "
|
|
"increase in powers of 2 while the size is less than or "
|
|
"equal to this option value. (default 1024).",
|
|
1024),
|
|
m_json(LLDB_OPT_SET_1, false, "json", 'j',
|
|
"Print the output as JSON data for easy parsing.", false, true) {
|
|
m_option_group.Append(&m_num_packets, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
|
|
m_option_group.Append(&m_max_send, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
|
|
m_option_group.Append(&m_max_recv, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
|
|
m_option_group.Append(&m_json, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
|
|
m_option_group.Finalize();
|
|
}
|
|
|
|
~CommandObjectProcessGDBRemoteSpeedTest() override = default;
|
|
|
|
Options *GetOptions() override { return &m_option_group; }
|
|
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
const size_t argc = command.GetArgumentCount();
|
|
if (argc == 0) {
|
|
ProcessGDBRemote *process =
|
|
(ProcessGDBRemote *)m_interpreter.GetExecutionContext()
|
|
.GetProcessPtr();
|
|
if (process) {
|
|
StreamSP output_stream_sp = result.GetImmediateOutputStream();
|
|
if (!output_stream_sp)
|
|
output_stream_sp = m_interpreter.GetDebugger().GetAsyncOutputStream();
|
|
result.SetImmediateOutputStream(output_stream_sp);
|
|
|
|
const uint32_t num_packets =
|
|
(uint32_t)m_num_packets.GetOptionValue().GetCurrentValue();
|
|
const uint64_t max_send = m_max_send.GetOptionValue().GetCurrentValue();
|
|
const uint64_t max_recv = m_max_recv.GetOptionValue().GetCurrentValue();
|
|
const bool json = m_json.GetOptionValue().GetCurrentValue();
|
|
const uint64_t k_recv_amount =
|
|
4 * 1024 * 1024; // Receive amount in bytes
|
|
process->GetGDBRemote().TestPacketSpeed(
|
|
num_packets, max_send, max_recv, k_recv_amount, json,
|
|
output_stream_sp ? *output_stream_sp : result.GetOutputStream());
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return;
|
|
}
|
|
} else {
|
|
result.AppendErrorWithFormat("'%s' takes no arguments",
|
|
m_cmd_name.c_str());
|
|
}
|
|
result.SetStatus(eReturnStatusFailed);
|
|
}
|
|
|
|
protected:
|
|
OptionGroupOptions m_option_group;
|
|
OptionGroupUInt64 m_num_packets;
|
|
OptionGroupUInt64 m_max_send;
|
|
OptionGroupUInt64 m_max_recv;
|
|
OptionGroupBoolean m_json;
|
|
};
|
|
|
|
class CommandObjectProcessGDBRemotePacketHistory : public CommandObjectParsed {
|
|
private:
|
|
public:
|
|
CommandObjectProcessGDBRemotePacketHistory(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "process plugin packet history",
|
|
"Dumps the packet history buffer. ", nullptr) {}
|
|
|
|
~CommandObjectProcessGDBRemotePacketHistory() override = default;
|
|
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
ProcessGDBRemote *process =
|
|
(ProcessGDBRemote *)m_interpreter.GetExecutionContext().GetProcessPtr();
|
|
if (process) {
|
|
process->DumpPluginHistory(result.GetOutputStream());
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return;
|
|
}
|
|
result.SetStatus(eReturnStatusFailed);
|
|
}
|
|
};
|
|
|
|
class CommandObjectProcessGDBRemotePacketXferSize : public CommandObjectParsed {
|
|
private:
|
|
public:
|
|
CommandObjectProcessGDBRemotePacketXferSize(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "process plugin packet xfer-size",
|
|
"Maximum size that lldb will try to read/write one one chunk.",
|
|
nullptr) {
|
|
AddSimpleArgumentList(eArgTypeUnsignedInteger);
|
|
}
|
|
|
|
~CommandObjectProcessGDBRemotePacketXferSize() override = default;
|
|
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
const size_t argc = command.GetArgumentCount();
|
|
if (argc == 0) {
|
|
result.AppendErrorWithFormat("'%s' takes an argument to specify the max "
|
|
"amount to be transferred when "
|
|
"reading/writing",
|
|
m_cmd_name.c_str());
|
|
return;
|
|
}
|
|
|
|
ProcessGDBRemote *process =
|
|
(ProcessGDBRemote *)m_interpreter.GetExecutionContext().GetProcessPtr();
|
|
if (process) {
|
|
const char *packet_size = command.GetArgumentAtIndex(0);
|
|
errno = 0;
|
|
uint64_t user_specified_max = strtoul(packet_size, nullptr, 10);
|
|
if (errno == 0 && user_specified_max != 0) {
|
|
process->SetUserSpecifiedMaxMemoryTransferSize(user_specified_max);
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return;
|
|
}
|
|
}
|
|
result.SetStatus(eReturnStatusFailed);
|
|
}
|
|
};
|
|
|
|
class CommandObjectProcessGDBRemotePacketSend : public CommandObjectParsed {
|
|
private:
|
|
public:
|
|
CommandObjectProcessGDBRemotePacketSend(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "process plugin packet send",
|
|
"Send a custom packet through the GDB remote "
|
|
"protocol and print the answer. "
|
|
"The packet header and footer will automatically "
|
|
"be added to the packet prior to sending and "
|
|
"stripped from the result.",
|
|
nullptr) {
|
|
AddSimpleArgumentList(eArgTypeNone, eArgRepeatStar);
|
|
}
|
|
|
|
~CommandObjectProcessGDBRemotePacketSend() override = default;
|
|
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
const size_t argc = command.GetArgumentCount();
|
|
if (argc == 0) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' takes a one or more packet content arguments",
|
|
m_cmd_name.c_str());
|
|
return;
|
|
}
|
|
|
|
ProcessGDBRemote *process =
|
|
(ProcessGDBRemote *)m_interpreter.GetExecutionContext().GetProcessPtr();
|
|
if (process) {
|
|
for (size_t i = 0; i < argc; ++i) {
|
|
const char *packet_cstr = command.GetArgumentAtIndex(0);
|
|
StringExtractorGDBRemote response;
|
|
process->GetGDBRemote().SendPacketAndWaitForResponse(
|
|
packet_cstr, response, process->GetInterruptTimeout());
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
Stream &output_strm = result.GetOutputStream();
|
|
output_strm.Printf(" packet: %s\n", packet_cstr);
|
|
std::string response_str = std::string(response.GetStringRef());
|
|
|
|
if (strstr(packet_cstr, "qGetProfileData") != nullptr) {
|
|
response_str = process->HarmonizeThreadIdsForProfileData(response);
|
|
}
|
|
|
|
if (response_str.empty())
|
|
output_strm.PutCString("response: \nerror: UNIMPLEMENTED\n");
|
|
else
|
|
output_strm.Printf("response: %s\n", response.GetStringRef().data());
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class CommandObjectProcessGDBRemotePacketMonitor : public CommandObjectRaw {
|
|
private:
|
|
public:
|
|
CommandObjectProcessGDBRemotePacketMonitor(CommandInterpreter &interpreter)
|
|
: CommandObjectRaw(interpreter, "process plugin packet monitor",
|
|
"Send a qRcmd packet through the GDB remote protocol "
|
|
"and print the response."
|
|
"The argument passed to this command will be hex "
|
|
"encoded into a valid 'qRcmd' packet, sent and the "
|
|
"response will be printed.") {}
|
|
|
|
~CommandObjectProcessGDBRemotePacketMonitor() override = default;
|
|
|
|
void DoExecute(llvm::StringRef command,
|
|
CommandReturnObject &result) override {
|
|
if (command.empty()) {
|
|
result.AppendErrorWithFormat("'%s' takes a command string argument",
|
|
m_cmd_name.c_str());
|
|
return;
|
|
}
|
|
|
|
ProcessGDBRemote *process =
|
|
(ProcessGDBRemote *)m_interpreter.GetExecutionContext().GetProcessPtr();
|
|
if (process) {
|
|
StreamString packet;
|
|
packet.PutCString("qRcmd,");
|
|
packet.PutBytesAsRawHex8(command.data(), command.size());
|
|
|
|
StringExtractorGDBRemote response;
|
|
Stream &output_strm = result.GetOutputStream();
|
|
process->GetGDBRemote().SendPacketAndReceiveResponseWithOutputSupport(
|
|
packet.GetString(), response, process->GetInterruptTimeout(),
|
|
[&output_strm](llvm::StringRef output) { output_strm << output; });
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
output_strm.Printf(" packet: %s\n", packet.GetData());
|
|
const std::string &response_str = std::string(response.GetStringRef());
|
|
|
|
if (response_str.empty())
|
|
output_strm.PutCString("response: \nerror: UNIMPLEMENTED\n");
|
|
else
|
|
output_strm.Printf("response: %s\n", response.GetStringRef().data());
|
|
}
|
|
}
|
|
};
|
|
|
|
class CommandObjectProcessGDBRemotePacket : public CommandObjectMultiword {
|
|
private:
|
|
public:
|
|
CommandObjectProcessGDBRemotePacket(CommandInterpreter &interpreter)
|
|
: CommandObjectMultiword(interpreter, "process plugin packet",
|
|
"Commands that deal with GDB remote packets.",
|
|
nullptr) {
|
|
LoadSubCommand(
|
|
"history",
|
|
CommandObjectSP(
|
|
new CommandObjectProcessGDBRemotePacketHistory(interpreter)));
|
|
LoadSubCommand(
|
|
"send", CommandObjectSP(
|
|
new CommandObjectProcessGDBRemotePacketSend(interpreter)));
|
|
LoadSubCommand(
|
|
"monitor",
|
|
CommandObjectSP(
|
|
new CommandObjectProcessGDBRemotePacketMonitor(interpreter)));
|
|
LoadSubCommand(
|
|
"xfer-size",
|
|
CommandObjectSP(
|
|
new CommandObjectProcessGDBRemotePacketXferSize(interpreter)));
|
|
LoadSubCommand("speed-test",
|
|
CommandObjectSP(new CommandObjectProcessGDBRemoteSpeedTest(
|
|
interpreter)));
|
|
}
|
|
|
|
~CommandObjectProcessGDBRemotePacket() override = default;
|
|
};
|
|
|
|
class CommandObjectMultiwordProcessGDBRemote : public CommandObjectMultiword {
|
|
public:
|
|
CommandObjectMultiwordProcessGDBRemote(CommandInterpreter &interpreter)
|
|
: CommandObjectMultiword(
|
|
interpreter, "process plugin",
|
|
"Commands for operating on a ProcessGDBRemote process.",
|
|
"process plugin <subcommand> [<subcommand-options>]") {
|
|
LoadSubCommand(
|
|
"packet",
|
|
CommandObjectSP(new CommandObjectProcessGDBRemotePacket(interpreter)));
|
|
}
|
|
|
|
~CommandObjectMultiwordProcessGDBRemote() override = default;
|
|
};
|
|
|
|
CommandObject *ProcessGDBRemote::GetPluginCommandObject() {
|
|
if (!m_command_sp)
|
|
m_command_sp = std::make_shared<CommandObjectMultiwordProcessGDBRemote>(
|
|
GetTarget().GetDebugger().GetCommandInterpreter());
|
|
return m_command_sp.get();
|
|
}
|
|
|
|
void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints(bool enable) {
|
|
GetBreakpointSiteList().ForEach([this, enable](BreakpointSite *bp_site) {
|
|
if (bp_site->IsEnabled() &&
|
|
(bp_site->GetType() == BreakpointSite::eSoftware ||
|
|
bp_site->GetType() == BreakpointSite::eExternal)) {
|
|
m_gdb_comm.SendGDBStoppointTypePacket(
|
|
eBreakpointSoftware, enable, bp_site->GetLoadAddress(),
|
|
GetSoftwareBreakpointTrapOpcode(bp_site), GetInterruptTimeout());
|
|
}
|
|
});
|
|
}
|
|
|
|
void ProcessGDBRemote::DidForkSwitchHardwareTraps(bool enable) {
|
|
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) {
|
|
GetBreakpointSiteList().ForEach([this, enable](BreakpointSite *bp_site) {
|
|
if (bp_site->IsEnabled() &&
|
|
bp_site->GetType() == BreakpointSite::eHardware) {
|
|
m_gdb_comm.SendGDBStoppointTypePacket(
|
|
eBreakpointHardware, enable, bp_site->GetLoadAddress(),
|
|
GetSoftwareBreakpointTrapOpcode(bp_site), GetInterruptTimeout());
|
|
}
|
|
});
|
|
}
|
|
|
|
for (const auto &wp_res_sp : m_watchpoint_resource_list.Sites()) {
|
|
addr_t addr = wp_res_sp->GetLoadAddress();
|
|
size_t size = wp_res_sp->GetByteSize();
|
|
GDBStoppointType type = GetGDBStoppointType(wp_res_sp);
|
|
m_gdb_comm.SendGDBStoppointTypePacket(type, enable, addr, size,
|
|
GetInterruptTimeout());
|
|
}
|
|
}
|
|
|
|
void ProcessGDBRemote::DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
|
|
lldb::pid_t parent_pid = m_gdb_comm.GetCurrentProcessID();
|
|
// Any valid TID will suffice, thread-relevant actions will set a proper TID
|
|
// anyway.
|
|
lldb::tid_t parent_tid = m_thread_ids.front();
|
|
|
|
lldb::pid_t follow_pid, detach_pid;
|
|
lldb::tid_t follow_tid, detach_tid;
|
|
|
|
switch (GetFollowForkMode()) {
|
|
case eFollowParent:
|
|
follow_pid = parent_pid;
|
|
follow_tid = parent_tid;
|
|
detach_pid = child_pid;
|
|
detach_tid = child_tid;
|
|
break;
|
|
case eFollowChild:
|
|
follow_pid = child_pid;
|
|
follow_tid = child_tid;
|
|
detach_pid = parent_pid;
|
|
detach_tid = parent_tid;
|
|
break;
|
|
}
|
|
|
|
// Switch to the process that is going to be detached.
|
|
if (!m_gdb_comm.SetCurrentThread(detach_tid, detach_pid)) {
|
|
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to set pid/tid");
|
|
return;
|
|
}
|
|
|
|
// Disable all software breakpoints in the forked process.
|
|
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
|
|
DidForkSwitchSoftwareBreakpoints(false);
|
|
|
|
// Remove hardware breakpoints / watchpoints from parent process if we're
|
|
// following child.
|
|
if (GetFollowForkMode() == eFollowChild)
|
|
DidForkSwitchHardwareTraps(false);
|
|
|
|
// Switch to the process that is going to be followed
|
|
if (!m_gdb_comm.SetCurrentThread(follow_tid, follow_pid) ||
|
|
!m_gdb_comm.SetCurrentThreadForRun(follow_tid, follow_pid)) {
|
|
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to reset pid/tid");
|
|
return;
|
|
}
|
|
|
|
LLDB_LOG(log, "Detaching process {0}", detach_pid);
|
|
Status error = m_gdb_comm.Detach(false, detach_pid);
|
|
if (error.Fail()) {
|
|
LLDB_LOG(log, "ProcessGDBRemote::DidFork() detach packet send failed: {0}",
|
|
error.AsCString() ? error.AsCString() : "<unknown error>");
|
|
return;
|
|
}
|
|
|
|
// Hardware breakpoints/watchpoints are not inherited implicitly,
|
|
// so we need to readd them if we're following child.
|
|
if (GetFollowForkMode() == eFollowChild) {
|
|
DidForkSwitchHardwareTraps(true);
|
|
// Update our PID
|
|
SetID(child_pid);
|
|
}
|
|
}
|
|
|
|
void ProcessGDBRemote::DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
|
|
Log *log = GetLog(GDBRLog::Process);
|
|
|
|
LLDB_LOG(
|
|
log,
|
|
"ProcessGDBRemote::DidFork() called for child_pid: {0}, child_tid {1}",
|
|
child_pid, child_tid);
|
|
++m_vfork_in_progress_count;
|
|
|
|
// Disable all software breakpoints for the duration of vfork.
|
|
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
|
|
DidForkSwitchSoftwareBreakpoints(false);
|
|
|
|
lldb::pid_t detach_pid;
|
|
lldb::tid_t detach_tid;
|
|
|
|
switch (GetFollowForkMode()) {
|
|
case eFollowParent:
|
|
detach_pid = child_pid;
|
|
detach_tid = child_tid;
|
|
break;
|
|
case eFollowChild:
|
|
detach_pid = m_gdb_comm.GetCurrentProcessID();
|
|
// Any valid TID will suffice, thread-relevant actions will set a proper TID
|
|
// anyway.
|
|
detach_tid = m_thread_ids.front();
|
|
|
|
// Switch to the parent process before detaching it.
|
|
if (!m_gdb_comm.SetCurrentThread(detach_tid, detach_pid)) {
|
|
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to set pid/tid");
|
|
return;
|
|
}
|
|
|
|
// Remove hardware breakpoints / watchpoints from the parent process.
|
|
DidForkSwitchHardwareTraps(false);
|
|
|
|
// Switch to the child process.
|
|
if (!m_gdb_comm.SetCurrentThread(child_tid, child_pid) ||
|
|
!m_gdb_comm.SetCurrentThreadForRun(child_tid, child_pid)) {
|
|
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to reset pid/tid");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
LLDB_LOG(log, "Detaching process {0}", detach_pid);
|
|
Status error = m_gdb_comm.Detach(false, detach_pid);
|
|
if (error.Fail()) {
|
|
LLDB_LOG(log,
|
|
"ProcessGDBRemote::DidFork() detach packet send failed: {0}",
|
|
error.AsCString() ? error.AsCString() : "<unknown error>");
|
|
return;
|
|
}
|
|
|
|
if (GetFollowForkMode() == eFollowChild) {
|
|
// Update our PID
|
|
SetID(child_pid);
|
|
}
|
|
}
|
|
|
|
void ProcessGDBRemote::DidVForkDone() {
|
|
assert(m_vfork_in_progress_count > 0);
|
|
--m_vfork_in_progress_count;
|
|
|
|
// Reenable all software breakpoints that were enabled before vfork.
|
|
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
|
|
DidForkSwitchSoftwareBreakpoints(true);
|
|
}
|
|
|
|
void ProcessGDBRemote::DidExec() {
|
|
// If we are following children, vfork is finished by exec (rather than
|
|
// vforkdone that is submitted for parent).
|
|
if (GetFollowForkMode() == eFollowChild) {
|
|
if (m_vfork_in_progress_count > 0)
|
|
--m_vfork_in_progress_count;
|
|
}
|
|
Process::DidExec();
|
|
}
|