| //===-- 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/ioctl.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 | |
| #ifdef _WIN32 | |
| #include "lldb/Host/windows/windows.h" | |
| #endif | |
| #include <ctime> | |
| #include <sys/types.h> | |
| #include "lldb/Breakpoint/Breakpoint.h" | |
| #include "lldb/Breakpoint/StoppointCallbackContext.h" | |
| #include "lldb/Breakpoint/Watchpoint.h" | |
| #include "lldb/Breakpoint/WatchpointAlgorithms.h" | |
| #include "lldb/Breakpoint/WatchpointResource.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Core/Diagnostics.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/HostInfo.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/OptionGroupPlatform.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/Symbol/Symbol.h" | |
| #include "lldb/Symbol/SymbolContext.h" | |
| #include "lldb/Target/ABI.h" | |
| #include "lldb/Target/DynamicLoader.h" | |
| #include "lldb/Target/MemoryRegionInfo.h" | |
| #include "lldb/Target/ProcessIOHandler.h" | |
| #include "lldb/Target/SystemRuntime.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Target/TargetGroup.h" | |
| #include "lldb/Target/TargetGroupList.h" | |
| #include "lldb/Target/TargetList.h" | |
| #include "lldb/Target/ThreadPlanCallFunction.h" | |
| #include "lldb/Utility/Args.h" | |
| #include "lldb/Utility/Baton.h" | |
| #include "lldb/Utility/FileSpec.h" | |
| #include "lldb/Utility/FileSpecList.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/RegisterType.h" | |
| #include "lldb/Utility/RegisterTypeFlags.h" | |
| #include "lldb/Utility/RegisterValue.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/STLExtras.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| #include "llvm/ADT/StringMap.h" | |
| #include "llvm/ADT/StringSwitch.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/Chrono.h" | |
| #include "llvm/Support/ErrorExtras.h" | |
| #include "llvm/Support/FormatAdapters.h" | |
| #include "llvm/Support/Threading.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #if defined(__APPLE__) | |
| #define DEBUGSERVER_BASENAME "debugserver" | |
| #elif defined(_WIN32) | |
| #define DEBUGSERVER_BASENAME "lldb-server.exe" | |
| #else | |
| #define DEBUGSERVER_BASENAME "lldb-server" | |
| #endif | |
| 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_def); | |
| } | |
| ~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); | |
| } | |
| uint64_t GetPacketTestDelay() const { | |
| const uint32_t idx = ePropertyPacketTestDelay; | |
| return GetPropertyAtIndexAs<uint64_t>( | |
| idx, g_processgdbremote_properties[idx].default_uint_value); | |
| } | |
| }; | |
| std::chrono::seconds ResumeTimeout() { return std::chrono::seconds(5); } | |
| static std::pair<uint16_t, uint16_t> GetClientTerminalSize() { | |
| #ifdef _WIN32 | |
| CONSOLE_SCREEN_BUFFER_INFO csbi{}; | |
| HANDLE h = ::GetStdHandle(STD_OUTPUT_HANDLE); | |
| if (h != INVALID_HANDLE_VALUE && ::GetConsoleScreenBufferInfo(h, &csbi)) { | |
| int cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; | |
| int rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; | |
| if (cols > 0 && rows > 0) | |
| return {static_cast<uint16_t>(cols), static_cast<uint16_t>(rows)}; | |
| } | |
| #elif LLDB_ENABLE_POSIX | |
| struct winsize ws{}; | |
| if (::ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0 && | |
| ws.ws_row > 0) | |
| return {ws.ws_col, ws.ws_row}; | |
| #endif | |
| return {0, 0}; | |
| } | |
| } // 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) { | |
| if (crash_file_path) | |
| return nullptr; // Cannot create a GDBRemote process from a crash_file. | |
| return lldb::ProcessSP(new ProcessGDBRemote(target_sp, listener_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()); | |
| } | |
| std::chrono::milliseconds ProcessGDBRemote::GetPacketTestDelay() { | |
| return std::chrono::milliseconds( | |
| GetGlobalPluginProperties().GetPacketTestDelay()); | |
| } | |
| 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_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(); | |
| // Contribute the packet history to diagnostics bundles, named with the | |
| // creation timestamp so files from different processes stay distinguishable. | |
| if (Diagnostics::Enabled()) { | |
| llvm::sys::TimePoint<> now = std::chrono::system_clock::now(); | |
| std::string name = llvm::formatv( | |
| "gdb-remote-packet-history-{0:%Y-%m-%dT%H-%M-%S}.txt", now); | |
| m_diagnostics_artifact_id = Diagnostics::Instance().AddArtifactProvider( | |
| std::move(name), [this]() -> std::string { | |
| StreamString stream; | |
| DumpPluginHistory(stream); | |
| return stream.GetString().str(); | |
| }); | |
| } | |
| } | |
| // Destructor | |
| ProcessGDBRemote::~ProcessGDBRemote() { | |
| // Unregister before teardown so a concurrent collection can't run the | |
| // provider on a half-destroyed process. | |
| if (m_diagnostics_artifact_id && Diagnostics::Enabled()) | |
| Diagnostics::Instance().RemoveArtifactProvider(*m_diagnostics_artifact_id); | |
| // 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(); | |
| } | |
| std::shared_ptr<ThreadGDBRemote> | |
| ProcessGDBRemote::CreateThread(lldb::tid_t tid) { | |
| return std::make_shared<ThreadGDBRemote>(*this, tid); | |
| } | |
| 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<DynamicRegisterInfo>(); | |
| // 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; | |
| llvm::Error register_info_err = GetGDBServerRegisterInfo(arch_to_use); | |
| if (!register_info_err) { | |
| // We got the registers from target XML. | |
| return; | |
| } | |
| Log *log = GetLog(GDBRLog::Process); | |
| LLDB_LOG_ERROR(log, std::move(register_info_err), | |
| "Failed to read register information from target XML: {0}"); | |
| LLDB_LOG(log, "Now trying to use qRegisterInfo instead."); | |
| 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(BASE_10, reg_info.byte_size)) | |
| reg_info.byte_size /= CHAR_BIT; | |
| } else if (name == "offset") { | |
| value.getAsInteger(BASE_10, 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("boolean", eFormatBoolean) | |
| .Case("binary", eFormatBinary) | |
| .Case("bytes", eFormatBytes) | |
| .Case("bytes-with-ascii", eFormatBytesWithASCII) | |
| .Case("char", eFormatChar) | |
| .Case("char-printable", eFormatCharPrintable) | |
| .Case("complex", eFormatComplex) | |
| .Case("cstring", eFormatCString) | |
| .Case("decimal", eFormatDecimal) | |
| .Case("enum", eFormatEnum) | |
| .Case("hex", eFormatHex) | |
| .Case("hex-uppercase", eFormatHexUppercase) | |
| .Case("float", eFormatFloat) | |
| .Case("octal", eFormatOctal) | |
| .Case("ostype", eFormatOSType) | |
| .Case("unicode16", eFormatUnicode16) | |
| .Case("unicode32", eFormatUnicode32) | |
| .Case("unsigned", eFormatUnsigned) | |
| .Case("pointer", eFormatPointer) | |
| .Case("vector-char", eFormatVectorOfChar) | |
| .Case("vector-sint64", eFormatVectorOfSInt64) | |
| .Case("vector-float16", eFormatVectorOfFloat16) | |
| .Case("vector-float64", eFormatVectorOfFloat64) | |
| .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) | |
| .Case("complex-integer", eFormatComplexInteger) | |
| .Case("char-array", eFormatCharArray) | |
| .Case("address-info", eFormatAddressInfo) | |
| .Case("hex-float", eFormatHexFloat) | |
| .Case("instruction", eFormatInstruction) | |
| .Case("void", eFormatVoid) | |
| .Case("unicode8", eFormatUnicode8) | |
| .Case("float128", eFormatFloat128) | |
| .Default(eFormatInvalid); | |
| } else if (name == "set") { | |
| reg_info.set_name.SetString(value); | |
| } else if (name == "gcc" || name == "ehframe") { | |
| value.getAsInteger(BASE_AUTOSENSE, reg_info.regnum_ehframe); | |
| } else if (name == "dwarf") { | |
| value.getAsInteger(BASE_AUTOSENSE, 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 { | |
| // Only warn if we were offered Target XML and could not use it, and | |
| // the qRegisterInfo fallback failed. This is something a user could | |
| // take action on by getting an lldb with libxml2. | |
| // | |
| // It's possible we weren't offered Target XML and qRegisterInfo failed, | |
| // but there's no much a user can do about that. It may be the intended | |
| // way the debug stub works, so we do not warn for that case. | |
| if (response_type == StringExtractorGDBRemote::eUnsupported && | |
| m_gdb_comm.GetQXferFeaturesReadSupported() && | |
| !XMLDocument::XMLEnabled()) { | |
| Debugger::ReportWarning( | |
| "the debug server supports Target Description XML but LLDB does " | |
| "not have XML parsing enabled. Using \"qRegisterInfo\" was also " | |
| "not possible. Register information may be incorrect or missing", | |
| GetTarget().GetDebugger().GetID()); | |
| } | |
| break; | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| if (registers.empty()) { | |
| registers = GetFallbackRegisters(arch_to_use); | |
| if (!registers.empty()) | |
| LLDB_LOG( | |
| log, | |
| "All other methods failed, using fallback register information."); | |
| } | |
| 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 (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); | |
| if (launch_flags & eLaunchFlagUsePipes) { | |
| m_gdb_comm.SetSTDIOWindowSize(0, 0); | |
| } else { | |
| auto [terminal_cols, terminal_rows] = GetClientTerminalSize(); | |
| m_gdb_comm.SetSTDIOWindowSize(terminal_cols, terminal_rows); | |
| } | |
| 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()) { | |
| const llvm::Triple &remote_triple = | |
| GetTarget().GetArchitecture().GetTriple(); | |
| if (remote_triple.getOS() != llvm::Triple::UnknownOS) { | |
| FileSpec remote_exe_file(exe_file.GetPath(/*denormalize=*/false), | |
| remote_triple); | |
| args.ReplaceArgumentAtIndex( | |
| 0, remote_exe_file.GetPath(/*denormalize=*/true)); | |
| } else { | |
| args.ReplaceArgumentAtIndex(0, | |
| exe_file.GetPath(/*denormalize=*/true)); | |
| } | |
| } | |
| 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) { | |
| #ifdef _WIN32 | |
| SetSTDIOFileDescriptor(pty.ReleasePrimaryFileDescriptor()); | |
| #else | |
| SetSTDIOPseudoTerminal(pty); | |
| #endif | |
| } | |
| #ifdef _WIN32 | |
| else if (m_stdin_forward) { | |
| // No client-side PTY FD on Windows. | |
| std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); | |
| if (!m_process_input_reader) | |
| m_process_input_reader = | |
| std::make_shared<IOHandlerProcessSTDIOWindows>(this); | |
| } | |
| #endif | |
| } | |
| } | |
| } 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(); | |
| // Empty unless the server advertised "address-spaces+" in qSupported. | |
| m_address_spaces = m_gdb_comm.GetAddressSpaces(); | |
| // 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())); | |
| } | |
| // Ask any accelerator plugins installed in lldb-server for their initial | |
| // actions (e.g. breakpoints to set in the native process). | |
| llvm::Expected<std::vector<AcceleratorActions>> init_actions = | |
| m_gdb_comm.GetAcceleratorInitializeActions(); | |
| if (!init_actions) { | |
| LLDB_LOG_ERROR(log, init_actions.takeError(), | |
| "failed to get accelerator initialize actions: {0}"); | |
| } else { | |
| for (const AcceleratorActions &actions : *init_actions) { | |
| if (llvm::Error error = HandleAcceleratorActions(actions)) | |
| LLDB_LOG_ERROR(log, std::move(error), | |
| "failed to handle accelerator actions: {0}"); | |
| } | |
| } | |
| } | |
| 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)) { | |
| if (standalone_uuid.IsValid()) { | |
| DynamicLoader::BinarySpec bin_spec; | |
| bin_spec.uuid = standalone_uuid; | |
| bin_spec.value = standalone_value; | |
| bin_spec.value_is_offset = standalone_value_is_offset; | |
| bin_spec.force_symbol_search = true; | |
| bin_spec.notify = true; | |
| bin_spec.set_address_in_target = true; | |
| bin_spec.is_main_executable = true; | |
| llvm::Expected<ModuleSP> module = | |
| DynamicLoader::LocateAndLoadBinary(this, bin_spec); | |
| if (!module) | |
| *GetTarget().GetDebugger().GetAsyncErrorStream() | |
| << llvm::toString(module.takeError()) << "\n"; | |
| } | |
| } | |
| // 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()) { | |
| 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; | |
| // Second manually load this binary into the Target. | |
| DynamicLoader::BinarySpec bin_spec; | |
| bin_spec.value = addr; | |
| bin_spec.force_symbol_search = true; | |
| bin_spec.notify = notify; | |
| bin_spec.set_address_in_target = true; | |
| llvm::Expected<ModuleSP> module = | |
| DynamicLoader::LocateAndLoadBinary(this, bin_spec); | |
| if (!module) | |
| *GetTarget().GetDebugger().GetAsyncErrorStream() | |
| << llvm::toString(module.takeError()) << "\n"; | |
| } | |
| } | |
| } | |
| 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.Lock()->reset(); | |
| m_jthreadsinfo.Lock()->reset(); | |
| m_shared_cache_info.Lock()->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 execution of processes"); | |
| } | |
| 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()); | |
| StructuredData::ObjectSP threads_info_sp = *m_jthreadsinfo.Lock(); | |
| if (threads_info_sp) { | |
| // If we have the JSON threads info, we can get the thread list from that | |
| StructuredData::Array *thread_infos = threads_info_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 llvm::StringRef stop_info_str = stop_info.GetStringRef(); | |
| m_thread_pcs.clear(); | |
| const size_t thread_pcs_pos = stop_info_str.find(";thread-pcs:"); | |
| if (thread_pcs_pos != llvm::StringRef::npos) { | |
| const size_t start = thread_pcs_pos + strlen(";thread-pcs:"); | |
| const size_t end = stop_info_str.find(';', start); | |
| if (end != llvm::StringRef::npos) { | |
| llvm::StringRef value = stop_info_str.substr(start, end - start); | |
| UpdateThreadPCsFromStopReplyThreadsValue(value); | |
| } | |
| } | |
| const size_t threads_pos = stop_info_str.find(";threads:"); | |
| if (threads_pos != llvm::StringRef::npos) { | |
| const size_t start = threads_pos + strlen(";threads:"); | |
| const size_t end = stop_info_str.find(';', start); | |
| if (end != llvm::StringRef::npos) { | |
| llvm::StringRef 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_LOG_VERBOSE(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 = CreateThread(tid); | |
| LLDB_LOG_VERBOSE(log, "Making new thread: {0} for thread ID: {1:x}.", | |
| thread_sp.get(), thread_sp->GetID()); | |
| } else { | |
| LLDB_LOG_VERBOSE(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]); | |
| } | |
| } | |
| } | |
| } | |
| std::optional<size_t> ProcessGDBRemote::FindThreadIDIndex(lldb::tid_t tid) { | |
| // This runs for every thread at a stop, so searching m_thread_ids would make | |
| // the stop quadratic in the number of threads. m_thread_ids is rewritten in | |
| // several places that leave m_thread_id_positions alone, so trust a cached | |
| // position only if m_thread_ids still has tid there, and rebuild otherwise. | |
| std::lock_guard<std::recursive_mutex> guard(m_thread_list_real.GetMutex()); | |
| auto it = m_thread_id_positions.find(tid); | |
| if (it != m_thread_id_positions.end() && it->second < m_thread_ids.size() && | |
| m_thread_ids[it->second] == tid) | |
| return it->second; | |
| m_thread_id_positions.clear(); | |
| for (auto [i, id] : llvm::enumerate(m_thread_ids)) | |
| m_thread_id_positions.try_emplace(id, i); | |
| it = m_thread_id_positions.find(tid); | |
| if (it == m_thread_id_positions.end()) | |
| return std::nullopt; | |
| return it->second; | |
| } | |
| 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, | |
| thread->shared_from_this()); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| bool ProcessGDBRemote::CalculateThreadStopInfo(ThreadGDBRemote *thread) { | |
| // See if we got thread stop infos for all threads via the "jThreadsInfo" | |
| // packet (we're at a public stop). | |
| StructuredData::ObjectSP threads_info_sp = *m_jthreadsinfo.Lock(); | |
| if (GetThreadStopInfoFromJSON(thread, threads_info_sp)) | |
| return true; | |
| // See if the stop-reply packet (T05 etc) included a `jstopinfo` key | |
| // with a mach exception description for any thread that has a stop reason. | |
| StructuredData::ObjectSP stop_info_sp = *m_jstopinfo.Lock(); | |
| if (stop_info_sp) { | |
| // Any thread not described in `jstopinfo` has no stop reason. | |
| // If a no-stop-reason thread is stopped at a breakpoint site (but | |
| // hasn't yet hit the breakpoint instruction), note that in the | |
| // Thread state so we will hit the breakpoint when we resume execution. | |
| if (!GetThreadStopInfoFromJSON(thread, stop_info_sp)) { | |
| addr_t pc = thread->GetRegisterContext()->GetPC(); | |
| BreakpointSiteSP bp_site_sp = | |
| thread->GetProcess()->GetBreakpointSiteList().FindByAddress(pc); | |
| if (bp_site_sp && IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) | |
| thread->SetThreadStoppedAtUnexecutedBP(pc); | |
| 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) { | |
| uint32_t lldb_regnum = gdb_reg_ctx_sp->ConvertRegisterKindToRegisterNumber( | |
| eRegisterKindProcessPlugin, pair.first); | |
| if (lldb_regnum != LLDB_INVALID_REGNUM) { | |
| StringExtractor reg_value_extractor(pair.second); | |
| if (reg_value_extractor.GetStringRef().empty()) { | |
| gdb_thread->PrivateSetRegisterUnavailable(lldb_regnum); | |
| continue; | |
| } | |
| WritableDataBufferSP buffer_sp( | |
| new DataBufferHeap(reg_value_extractor.GetStringRef().size() / 2, 0)); | |
| reg_value_extractor.GetHexBytes(buffer_sp->GetData(), '\xcc'); | |
| 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, | |
| std::vector<lldb::addr_t> &added_binaries, | |
| StructuredData::ObjectSP &detailed_binaries_info, ThreadSP thread_sp) { | |
| if (tid == LLDB_INVALID_THREAD_ID) | |
| return nullptr; | |
| // Stop info is set for every thread at a stop, so a search of the thread | |
| // list for each would take time quadratic in the number of threads. | |
| if (!thread_sp || thread_sp->GetProtocolID() != tid) { | |
| // 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 = CreateThread(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); | |
| if (std::optional<size_t> index = FindThreadIDIndex(tid)) | |
| SetThreadPc(thread_sp, *index); | |
| 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); | |
| gdb_thread->SetNewlyAddedBinaries(added_binaries); | |
| gdb_thread->SetDetailedBinariesInfo(detailed_binaries_info); | |
| // 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 && IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) | |
| 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 (IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) | |
| 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, | |
| lldb::ThreadSP thread_sp) { | |
| 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"); | |
| static constexpr llvm::StringLiteral g_key_added_binaries("added-binaries"); | |
| static constexpr llvm::StringLiteral g_key_detailed_binaries_info( | |
| "detailed-binaries-info"); | |
| // Stop with signal and thread info | |
| lldb::tid_t tid = LLDB_INVALID_THREAD_ID; | |
| uint8_t signo = 0; | |
| 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; | |
| std::vector<addr_t> added_binaries; | |
| StructuredData::ObjectSP detailed_binaries_info; | |
| // 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, | |
| &added_binaries, &detailed_binaries_info]( | |
| 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) | |
| AddCacheData(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); | |
| else if (key == g_key_added_binaries) { | |
| StructuredData::Array *array = object->GetAsArray(); | |
| if (array) { | |
| array->ForEach([&added_binaries]( | |
| StructuredData::Object *object) -> bool { | |
| StructuredData::UnsignedInteger *addr = | |
| object->GetAsUnsignedInteger(); | |
| if (addr) { | |
| addr_t value = addr->GetUnsignedIntegerValue(LLDB_INVALID_ADDRESS); | |
| if (value != LLDB_INVALID_ADDRESS) | |
| added_binaries.push_back(value); | |
| } | |
| return true; // Keep iterating through all array items | |
| }); | |
| } | |
| } else if (key == g_key_detailed_binaries_info) { | |
| // Get a string representation and then parse it into | |
| // StructuredData to get a separate copy of this part of | |
| // the response. We only have an Object* here, not the | |
| // original shared pointer, to increase the ref count. | |
| if (object->GetAsDictionary()) { | |
| StreamString json_str; | |
| object->Dump(json_str); | |
| detailed_binaries_info = | |
| StructuredData::ParseJSON(json_str.GetString()); | |
| } | |
| } | |
| 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, added_binaries, detailed_binaries_info, | |
| std::move(thread_sp)); | |
| } | |
| 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; | |
| std::vector<addr_t> added_binaries; | |
| StructuredData::ObjectSP detailed_binaries_info; | |
| 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(BASE_16, exc_type); | |
| } else if (key.compare("medata") == 0) { | |
| // exception data in big endian hex | |
| uint64_t x; | |
| value.getAsInteger(BASE_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(BASE_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.Lock() = StructuredData::ParseJSON(json); | |
| } else if (key.compare("hexname") == 0) { | |
| StringExtractor name_extractor(value); | |
| // 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(BASE_16, thread_dispatch_qaddr); | |
| } else if (key.compare("dispatch_queue_t") == 0) { | |
| queue_vars_valid = true; | |
| value.getAsInteger(BASE_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(BASE_10, 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(BASE_AUTOSENSE, 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) | |
| AddCacheData(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(BASE_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(BASE_10, addressing_bits)) { | |
| addressable_bits.SetAddressableBits(addressing_bits); | |
| } | |
| } else if (key.compare("low_mem_addressing_bits") == 0) { | |
| uint64_t addressing_bits; | |
| if (!value.getAsInteger(BASE_10, addressing_bits)) { | |
| addressable_bits.SetLowmemAddressableBits(addressing_bits); | |
| } | |
| } else if (key.compare("high_mem_addressing_bits") == 0) { | |
| uint64_t addressing_bits; | |
| if (!value.getAsInteger(BASE_10, addressing_bits)) { | |
| addressable_bits.SetHighmemAddressableBits(addressing_bits); | |
| } | |
| } else if (key == "added-binaries") { | |
| // 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(BASE_16, pc)) | |
| pc = LLDB_INVALID_ADDRESS; | |
| added_binaries.push_back(pc); | |
| } | |
| } else if (key == "detailed-binaries-info") { | |
| StringExtractor json_extractor(value); | |
| std::string json; | |
| // Now convert the HEX bytes into a string value. | |
| json_extractor.GetHexByteString(json); | |
| // This JSON contains detailed information about binares. | |
| detailed_binaries_info = StructuredData::ParseJSON(json); | |
| } else if (key.size() == 2 && ::isxdigit(key[0]) && ::isxdigit(key[1])) { | |
| uint32_t reg = UINT32_MAX; | |
| if (!key.getAsInteger(BASE_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); | |
| m_last_stop_primary_tid = tid; | |
| 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, added_binaries, | |
| detailed_binaries_info); | |
| 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; | |
| } else if (m_last_stop_primary_tid != LLDB_INVALID_THREAD_ID && | |
| StateIsRunningState(m_last_broadcast_state)) { | |
| if (ThreadSP primary_thread_sp = m_thread_list.FindThreadByProtocolID( | |
| m_last_stop_primary_tid, /*can_update=*/false)) { | |
| ThreadSP selected_thread_sp = m_thread_list.GetSelectedThread(); | |
| if (!selected_thread_sp || | |
| selected_thread_sp->GetID() != primary_thread_sp->GetID()) | |
| m_thread_list.SetSelectedThreadByID(primary_thread_sp->GetID()); | |
| } | |
| } | |
| m_last_stop_primary_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 (GetPublicState() == 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 (GetPublicState() != 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. | |
| StructuredData::ObjectSP threads_info_sp = m_gdb_comm.GetThreadsInfo(); | |
| *m_jthreadsinfo.Lock() = threads_info_sp; | |
| if (threads_info_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 = threads_info_sp->GetAsArray(); | |
| if (thread_infos) { | |
| std::unordered_map<lldb::tid_t, ThreadSP> threads; | |
| for (const ThreadSP &thread_sp : m_thread_list_real.Threads()) | |
| threads.try_emplace(thread_sp->GetProtocolID(), thread_sp); | |
| 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) | |
| continue; | |
| lldb::tid_t tid = LLDB_INVALID_THREAD_ID; | |
| thread_dict->GetValueForKeyAsInteger("tid", tid); | |
| auto thread = threads.find(tid); | |
| SetThreadStopInfo(thread_dict, | |
| thread == threads.end() ? nullptr : thread->second); | |
| } | |
| } | |
| } | |
| } | |
| // Process Memory | |
| size_t ProcessGDBRemote::DoReadMemory(const ProcessAddress &process_addr, | |
| void *buf, size_t size, Status &error) { | |
| using xPacketState = GDBRemoteCommunicationClient::xPacketState; | |
| lldb::addr_t addr = process_addr.GetValue(); | |
| lldb::addr_space_t addr_space = process_addr.GetAddressSpace(); | |
| if (addr_space != LLDB_DEFAULT_ADDRESS_SPACE_ID && | |
| !m_gdb_comm.GetAddressSpacesSupported()) { | |
| error = Status::FromErrorString("address spaces are not supported"); | |
| return 0; | |
| } | |
| 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; | |
| } | |
| // A non-default address space rides on an "address_space:<hex-id>;" suffix, | |
| // followed by "thread:<hex-tid>;" when that space is thread specific. | |
| std::string suffix; | |
| if (addr_space != LLDB_DEFAULT_ADDRESS_SPACE_ID) { | |
| llvm::Expected<AddressSpaceInfo> info = GetAddressSpaceInfo(addr_space); | |
| if (!info) { | |
| error = Status::FromError(info.takeError()); | |
| return 0; | |
| } | |
| suffix = | |
| llvm::formatv(";address_space:{0};", llvm::utohexstr(addr_space, true)); | |
| if (info->is_thread_specific) { | |
| std::optional<lldb::tid_t> tid = process_addr.GetThreadID(); | |
| if (!tid) { | |
| error = Status::FromErrorStringWithFormat( | |
| "address space \"%s\" is thread specific, but no thread was " | |
| "specified", | |
| info->name.c_str()); | |
| return 0; | |
| } | |
| suffix += llvm::formatv("thread:{0};", llvm::utohexstr(*tid, true)); | |
| } | |
| } | |
| char packet[128]; | |
| int packet_len = | |
| ::snprintf(packet, sizeof(packet), "%c%" PRIx64 ",%" PRIx64 "%s", | |
| x_state != xPacketState::Unimplemented ? 'x' : 'm', | |
| (uint64_t)addr, (uint64_t)size, suffix.c_str()); | |
| 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; | |
| } | |
| /// Returns the number of ranges that is safe to request using MultiMemRead | |
| /// while respecting max_packet_size. | |
| static uint64_t ComputeNumRangesMultiMemRead( | |
| uint64_t max_packet_size, | |
| llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges) { | |
| // Each range is specified by two numbers (up to 16 ASCII characters) and one | |
| // comma. | |
| constexpr uint64_t range_overhead = 33; | |
| uint64_t current_size = 0; | |
| for (auto [idx, range] : llvm::enumerate(ranges)) { | |
| uint64_t potential_size = current_size + range.size + range_overhead; | |
| if (potential_size > max_packet_size) { | |
| if (idx == 0) | |
| LLDB_LOG(GetLog(GDBRLog::Process), | |
| "MultiMemRead input has a range (base = {0:x}, size = {1}) " | |
| "bigger than the maximum allowed by remote", | |
| range.base, range.size); | |
| return idx; | |
| } | |
| } | |
| return ranges.size(); | |
| } | |
| llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> | |
| ProcessGDBRemote::DoReadMemoryRanges( | |
| llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges, | |
| llvm::MutableArrayRef<uint8_t> buffer) { | |
| if (!m_gdb_comm.GetMultiMemReadSupported()) | |
| return Process::DoReadMemoryRanges(ranges, buffer); | |
| const llvm::ArrayRef<Range<lldb::addr_t, size_t>> original_ranges = ranges; | |
| llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> memory_regions; | |
| while (!ranges.empty()) { | |
| uint64_t num_ranges = | |
| ComputeNumRangesMultiMemRead(m_max_memory_size, ranges); | |
| if (num_ranges == 0) | |
| return Process::DoReadMemoryRanges(original_ranges, buffer); | |
| auto ranges_for_request = ranges.take_front(num_ranges); | |
| ranges = ranges.drop_front(num_ranges); | |
| llvm::Expected<StringExtractorGDBRemote> response = | |
| SendMultiMemReadPacket(ranges_for_request); | |
| if (!response) { | |
| LLDB_LOG_ERROR(GetLog(GDBRLog::Process), response.takeError(), | |
| "MultiMemRead error response: {0}"); | |
| return Process::DoReadMemoryRanges(original_ranges, buffer); | |
| } | |
| llvm::StringRef response_str = response->GetStringRef(); | |
| const unsigned expected_num_ranges = ranges_for_request.size(); | |
| if (llvm::Error error = ParseMultiMemReadPacket( | |
| response_str, buffer, expected_num_ranges, memory_regions)) { | |
| LLDB_LOG_ERROR(GetLog(GDBRLog::Process), std::move(error), | |
| "MultiMemRead error parsing response: {0}"); | |
| return Process::DoReadMemoryRanges(original_ranges, buffer); | |
| } | |
| } | |
| return memory_regions; | |
| } | |
| llvm::Expected<StringExtractorGDBRemote> | |
| ProcessGDBRemote::SendMultiMemReadPacket( | |
| llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges) { | |
| std::string packet_str; | |
| llvm::raw_string_ostream stream(packet_str); | |
| stream << "MultiMemRead:ranges:"; | |
| auto range_to_stream = [&](auto range) { | |
| // the "-" marker omits the '0x' prefix. | |
| stream << llvm::formatv("{0:x-},{1:x-}", range.base, range.size); | |
| }; | |
| llvm::interleave(ranges, stream, range_to_stream, ","); | |
| stream << ";"; | |
| StringExtractorGDBRemote response; | |
| GDBRemoteCommunication::PacketResult packet_result = | |
| m_gdb_comm.SendPacketAndWaitForResponse(packet_str.data(), response, | |
| GetInterruptTimeout()); | |
| if (packet_result != GDBRemoteCommunication::PacketResult::Success) | |
| return llvm::createStringErrorV("MultiMemRead failed to send packet: '{0}'", | |
| packet_str); | |
| if (response.IsErrorResponse()) | |
| return llvm::createStringErrorV("MultiMemRead failed: '{0}'", | |
| response.GetStringRef()); | |
| if (!response.IsNormalResponse()) | |
| return llvm::createStringErrorV("MultiMemRead unexpected response: '{0}'", | |
| response.GetStringRef()); | |
| return response; | |
| } | |
| llvm::Error ProcessGDBRemote::ParseMultiMemReadPacket( | |
| llvm::StringRef response_str, llvm::MutableArrayRef<uint8_t> buffer, | |
| unsigned expected_num_ranges, | |
| llvm::SmallVectorImpl<llvm::MutableArrayRef<uint8_t>> &memory_regions) { | |
| // The sizes and the data are separated by a `;`. | |
| auto [sizes_str, memory_data] = response_str.split(';'); | |
| if (sizes_str.size() == response_str.size()) | |
| return llvm::createStringErrorV( | |
| "MultiMemRead response missing field separator ';' in: '{0}'", | |
| response_str); | |
| // Sizes are separated by a `,`. | |
| unsigned num_sizes = 0; | |
| for (llvm::StringRef size_str : llvm::split(sizes_str, ',')) { | |
| uint64_t read_size; | |
| if (size_str.getAsInteger(BASE_16, read_size)) | |
| return llvm::createStringErrorV( | |
| "MultiMemRead response has invalid size string: {0}", size_str); | |
| if (memory_data.size() < read_size) | |
| return llvm::createStringErrorV("MultiMemRead response did not have " | |
| "enough data, requested sizes: {0}", | |
| sizes_str); | |
| if (read_size > buffer.size()) | |
| return llvm::createStringErrorV( | |
| "MultiMemRead response size {0} exceeds remaining buffer {1}", | |
| read_size, buffer.size()); | |
| llvm::StringRef region_to_read = memory_data.take_front(read_size); | |
| memory_data = memory_data.drop_front(read_size); | |
| llvm::MutableArrayRef<uint8_t> region_to_write = | |
| buffer.take_front(read_size); | |
| buffer = buffer.drop_front(read_size); | |
| memcpy(region_to_write.data(), region_to_read.data(), read_size); | |
| memory_regions.push_back(region_to_write); | |
| ++num_sizes; | |
| } | |
| if (num_sizes != expected_num_ranges) | |
| return llvm::createStringErrorV( | |
| "MultiMemRead response had {0} sizes, expected {1}", num_sizes, | |
| expected_num_ranges); | |
| return llvm::Error::success(); | |
| } | |
| 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. | |
| llvm::stable_sort(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() == eLazyBoolYes; | |
| 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; | |
| } | |
| bool ProcessGDBRemote::DoCanAllocateMemory() { | |
| // Probe the _M packet. Falling back to mmap() only needs its symbol. | |
| if (m_gdb_comm.SupportsAllocDeallocMemory() == eLazyBoolCalculate) { | |
| addr_t addr = m_gdb_comm.AllocateMemory(8, ePermissionsReadable | | |
| ePermissionsWritable | | |
| ePermissionsExecutable); | |
| if (addr != LLDB_INVALID_ADDRESS) | |
| m_gdb_comm.DeallocateMemory(addr); | |
| } | |
| if (m_gdb_comm.SupportsAllocDeallocMemory() == eLazyBoolYes) | |
| return true; | |
| ModuleFunctionSearchOptions options; | |
| options.include_symbols = true; | |
| options.include_inlines = false; | |
| SymbolContextList sc_list; | |
| GetTarget().GetImages().FindFunctions( | |
| ConstString("mmap"), eFunctionNameTypeFull, options, sc_list); | |
| return !sc_list.IsEmpty(); | |
| } | |
| 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, GetInterruptTimeout()); | |
| } | |
| return 0; | |
| } | |
| /// Enable a single breakpoint site by trying Z0 (software), then Z1 | |
| /// (hardware), then manual memory write as a last resort. | |
| llvm::Error ProcessGDBRemote::DoEnableBreakpointSite(BreakpointSite &bp_site) { | |
| Log *log = GetLog(GDBRLog::Breakpoints); | |
| const addr_t addr = bp_site.GetLoadAddress(); | |
| const size_t bp_op_size = GetSoftwareBreakpointTrapOpcode(&bp_site); | |
| auto &gdb_comm = GetGDBRemote(); | |
| // SupportsGDBStoppointPacket always returns true unless a previously sent | |
| // packet failed. As such, query the function before AND after sending the | |
| // packet. | |
| if (gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware) && | |
| !bp_site.HardwareRequired()) { | |
| uint8_t error_no = gdb_comm.SendGDBStoppointTypePacket( | |
| eBreakpointSoftware, true, addr, bp_op_size, GetInterruptTimeout()); | |
| if (error_no == 0) { | |
| SetBreakpointSiteEnabled(bp_site); | |
| bp_site.SetType(BreakpointSite::eExternal); | |
| return llvm::Error::success(); | |
| } | |
| if (gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware)) { | |
| if (error_no != UINT8_MAX) | |
| return llvm::createStringErrorV( | |
| "error sending the breakpoint request: {0}", error_no); | |
| return llvm::createStringError("error sending the breakpoint request"); | |
| } | |
| LLDB_LOG(log, "Software breakpoints are unsupported"); | |
| } | |
| // Like above, this is also queried twice. | |
| if (gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) { | |
| uint8_t error_no = gdb_comm.SendGDBStoppointTypePacket( | |
| eBreakpointHardware, true, addr, bp_op_size, GetInterruptTimeout()); | |
| if (error_no == 0) { | |
| SetBreakpointSiteEnabled(bp_site); | |
| bp_site.SetType(BreakpointSite::eHardware); | |
| return llvm::Error::success(); | |
| } | |
| if (gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) { | |
| if (error_no != UINT8_MAX) | |
| return llvm::createStringErrorV( | |
| "error sending the hardware breakpoint request: {0} " | |
| "(hardware breakpoint resources might be exhausted or unavailable)", | |
| error_no); | |
| return llvm::createStringError( | |
| "error sending the hardware breakpoint request " | |
| "(hardware breakpoint resources might be exhausted or unavailable)"); | |
| } | |
| LLDB_LOG(log, "Hardware breakpoints are unsupported"); | |
| } | |
| if (bp_site.HardwareRequired()) | |
| return llvm::createStringError("hardware breakpoints are not supported"); | |
| return EnableSoftwareBreakpoint(&bp_site).takeError(); | |
| } | |
| /// Disable a single breakpoint site directly by sending the appropriate | |
| /// z packet or restoring the original instruction. | |
| llvm::Error ProcessGDBRemote::DoDisableBreakpointSite(BreakpointSite &bp_site) { | |
| const addr_t addr = bp_site.GetLoadAddress(); | |
| const size_t bp_op_size = GetSoftwareBreakpointTrapOpcode(&bp_site); | |
| auto &gdb_comm = GetGDBRemote(); | |
| switch (bp_site.GetType()) { | |
| case BreakpointSite::eSoftware: { | |
| Status error = DisableSoftwareBreakpoint(&bp_site); | |
| if (error.Fail()) | |
| return error.takeError(); | |
| break; | |
| } | |
| case BreakpointSite::eHardware: | |
| if (gdb_comm.SendGDBStoppointTypePacket(eBreakpointHardware, false, addr, | |
| bp_op_size, GetInterruptTimeout())) | |
| return llvm::createStringError("unknown error"); | |
| break; | |
| case BreakpointSite::eExternal: | |
| if (gdb_comm.SendGDBStoppointTypePacket(eBreakpointSoftware, false, addr, | |
| bp_op_size, GetInterruptTimeout())) | |
| return llvm::createStringError("unknown error"); | |
| break; | |
| } | |
| SetBreakpointSiteEnabled(bp_site, false); | |
| return llvm::Error::success(); | |
| } | |
| Status ProcessGDBRemote::EnableBreakpointSite(BreakpointSite *bp_site) { | |
| 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 (IsBreakpointSitePhysicallyEnabled(*bp_site)) { | |
| LLDB_LOGF(log, | |
| "ProcessGDBRemote::EnableBreakpointSite (size_id = %" PRIu64 | |
| ") address = 0x%" PRIx64 " -- SUCCESS (already enabled)", | |
| site_id, (uint64_t)addr); | |
| return Status(); | |
| } | |
| return Status::FromError(DoEnableBreakpointSite(*bp_site)); | |
| } | |
| Status ProcessGDBRemote::DisableBreakpointSite(BreakpointSite *bp_site) { | |
| 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 (!IsBreakpointSitePhysicallyEnabled(*bp_site)) { | |
| LLDB_LOGF(log, | |
| "ProcessGDBRemote::DisableBreakpointSite (site_id = %" PRIu64 | |
| ") addr = 0x%8.8" PRIx64 " -- SUCCESS (already disabled)", | |
| site_id, (uint64_t)addr); | |
| return Status(); | |
| } | |
| return Status::FromError(DoDisableBreakpointSite(*bp_site)); | |
| } | |
| // 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; | |
| } | |
| static FileSpec GetDebugserverPath(Platform &platform) { | |
| Log *log = GetLog(GDBRLog::Process); | |
| // If we locate debugserver, keep that located version around | |
| static FileSpec g_debugserver_file_spec; | |
| FileSpec debugserver_file_spec; | |
| Environment host_env = Host::GetEnvironment(); | |
| // Always check to see if we have an environment override for the path to the | |
| // debugserver to use and use it if we do. | |
| std::string env_debugserver_path = host_env.lookup("LLDB_DEBUGSERVER_PATH"); | |
| if (!env_debugserver_path.empty()) { | |
| debugserver_file_spec.SetFile(env_debugserver_path, | |
| FileSpec::Style::native); | |
| LLDB_LOG(log, "gdb-remote stub exe path set from environment variable: {0}", | |
| env_debugserver_path); | |
| } else | |
| debugserver_file_spec = g_debugserver_file_spec; | |
| if (FileSystem::Instance().Exists(debugserver_file_spec)) | |
| return debugserver_file_spec; | |
| // The debugserver binary is in the LLDB.framework/Resources directory. | |
| debugserver_file_spec = HostInfo::GetSupportExeDir(); | |
| if (debugserver_file_spec) { | |
| debugserver_file_spec.AppendPathComponent(DEBUGSERVER_BASENAME); | |
| if (FileSystem::Instance().Exists(debugserver_file_spec)) { | |
| LLDB_LOG(log, "found gdb-remote stub exe '{0}'", debugserver_file_spec); | |
| g_debugserver_file_spec = debugserver_file_spec; | |
| } else { | |
| debugserver_file_spec = platform.LocateExecutable(DEBUGSERVER_BASENAME); | |
| if (!debugserver_file_spec) { | |
| // Platform::LocateExecutable() wouldn't return a path if it doesn't | |
| // exist | |
| LLDB_LOG(log, "could not find gdb-remote stub exe '{0}'", | |
| debugserver_file_spec); | |
| } | |
| // Don't cache the platform specific GDB server binary as it could | |
| // change from platform to platform | |
| g_debugserver_file_spec.Clear(); | |
| } | |
| } | |
| return debugserver_file_spec; | |
| } | |
| Status ProcessGDBRemote::LaunchAndConnectToDebugserver( | |
| const ProcessInfo &process_info) { | |
| using namespace std::placeholders; // For _1, _2, etc. | |
| if (m_debugserver_pid != LLDB_INVALID_PROCESS_ID) | |
| return Status(); | |
| 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()); | |
| FileSpec debugserver_path = GetDebugserverPath(*GetTarget().GetPlatform()); | |
| #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) { | |
| debugserver_path = FileSpec("/Library/Apple/usr/libexec/oah/debugserver"); | |
| } | |
| } | |
| #endif | |
| if (!FileSystem::Instance().Exists(debugserver_path)) | |
| return Status::FromErrorString("could not find '" DEBUGSERVER_BASENAME | |
| "'. Please ensure it is properly installed " | |
| "and available in your PATH"); | |
| debugserver_launch_info.SetExecutableFile(debugserver_path, | |
| /*add_exe_file_as_first_arg=*/true); | |
| llvm::Expected<Socket::Pair> socket_pair = Socket::CreatePair(); | |
| if (!socket_pair) | |
| return Status::FromError(socket_pair.takeError()); | |
| Status error; | |
| SharedSocket shared_socket(socket_pair->first.get(), error); | |
| if (error.Fail()) | |
| return error; | |
| error = m_gdb_comm.StartDebugserverProcess(shared_socket.GetSendableFD(), | |
| debugserver_launch_info, nullptr); | |
| if (error.Fail()) { | |
| Log *log = GetLog(GDBRLog::Process); | |
| LLDB_LOGF(log, "failed to start debugserver process: %s", | |
| error.AsCString()); | |
| return error; | |
| } | |
| m_debugserver_pid = debugserver_launch_info.GetProcessID(); | |
| shared_socket.CompleteSending(m_debugserver_pid); | |
| // Our process spawned correctly, we can now set our connection to use | |
| // our end of the socket pair | |
| m_gdb_comm.SetConnection(std::make_unique<ConnectionFileDescriptor>( | |
| std::move(socket_pair->second))); | |
| StartAsyncThread(); | |
| 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() { | |
| 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; | |
| } | |
| namespace { | |
| /// Baton that carries the breakpoint hit arguments to the accelerator plugin | |
| /// breakpoint callback. | |
| class AcceleratorBreakpointCallbackBaton | |
| : public TypedBaton<AcceleratorBreakpointHitArgs> { | |
| public: | |
| explicit AcceleratorBreakpointCallbackBaton( | |
| std::unique_ptr<AcceleratorBreakpointHitArgs> data) | |
| : TypedBaton(std::move(data)) {} | |
| }; | |
| } // namespace | |
| llvm::Error | |
| ProcessGDBRemote::HandleAcceleratorActions(const AcceleratorActions &actions) { | |
| Log *log = GetLog(GDBRLog::Process); | |
| // The same set of actions can be delivered to the client more than once: a | |
| // plugin may keep reporting the same actions (with the same identifier) on | |
| // subsequent native stops until its state advances. The identifier uniquely | |
| // names a set of actions for a plugin, so skip any set we have already | |
| // processed to avoid re-running its side effects (e.g. setting the same | |
| // breakpoints again). | |
| auto it = m_processed_accelerator_actions.find(actions.plugin_name); | |
| if (it != m_processed_accelerator_actions.end() && | |
| it->second == actions.identifier) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::HandleAcceleratorActions skipping already " | |
| "processed actions for plugin '{0}' with identifier {1}", | |
| actions.plugin_name, actions.identifier); | |
| return llvm::Error::success(); | |
| } | |
| m_processed_accelerator_actions[actions.plugin_name] = actions.identifier; | |
| // Handle each kind of action. More action kinds will be handled here in the | |
| // future, so only return early on error; otherwise fall through so the next | |
| // kind of action still gets a chance to run. | |
| if (!actions.breakpoints.empty()) { | |
| if (llvm::Error error = HandleAcceleratorBreakpoints(actions)) | |
| return error; | |
| } | |
| if (actions.connect_info) { | |
| if (llvm::Error error = HandleAcceleratorConnection(actions)) | |
| return error; | |
| } | |
| return llvm::Error::success(); | |
| } | |
| llvm::Error ProcessGDBRemote::HandleAcceleratorConnection( | |
| const AcceleratorActions &actions) { | |
| const AcceleratorConnectionInfo &connect_info = *actions.connect_info; | |
| Debugger &debugger = GetTarget().GetDebugger(); | |
| OptionGroupPlatform platform_options(/*include_platform_option=*/false); | |
| platform_options.SetPlatformName(connect_info.platform_name.c_str()); | |
| std::string exe_path = connect_info.exe_path.value_or(""); | |
| TargetSP accelerator_target_sp; | |
| Status error = debugger.GetTargetList().CreateTarget( | |
| debugger, exe_path, connect_info.triple, eLoadDependentsNo, | |
| &platform_options, accelerator_target_sp); | |
| if (error.Fail()) | |
| return error.takeError(); | |
| if (!accelerator_target_sp) | |
| return llvm::createStringError("failed to create accelerator target"); | |
| llvm::scope_exit delete_accelerator_target([&] { | |
| debugger.GetTargetList().DeleteTarget(accelerator_target_sp); | |
| accelerator_target_sp->Destroy(); | |
| }); | |
| PlatformSP platform_sp = accelerator_target_sp->GetPlatform(); | |
| if (!platform_sp) | |
| return llvm::createStringErrorV( | |
| "no platform '{0}' compatible with triple '{1}' for the accelerator " | |
| "target", | |
| connect_info.platform_name, connect_info.triple); | |
| TargetSP native_target_sp = GetTarget().shared_from_this(); | |
| std::shared_ptr<TargetGroupCPUAndAccelerators> group_sp = | |
| native_target_sp | |
| ->GetFirstTargetGroupOfType<TargetGroupCPUAndAccelerators>(); | |
| bool created_group = !group_sp; | |
| if (created_group) { | |
| group_sp = debugger.GetTargetGroupList() | |
| .CreateTargetGroup<TargetGroupCPUAndAccelerators>( | |
| "CPU and accelerator targets"); | |
| } | |
| llvm::scope_exit restore_target_group([&] { | |
| group_sp->RemoveTarget(accelerator_target_sp); | |
| if (created_group) { | |
| group_sp->RemoveTarget(native_target_sp); | |
| debugger.GetTargetGroupList().DeleteTargetGroup(group_sp); | |
| } | |
| }); | |
| if (llvm::Error membership_error = | |
| group_sp->AddTarget(native_target_sp, TargetGroup::Role::CPU)) | |
| return membership_error; | |
| if (llvm::Error membership_error = group_sp->AddTarget( | |
| accelerator_target_sp, TargetGroup::Role::Accelerator)) | |
| return membership_error; | |
| ProcessSP process_sp = | |
| connect_info.synchronous | |
| ? platform_sp->ConnectProcessSynchronous( | |
| connect_info.connect_url, GetPluginNameStatic(), debugger, | |
| *debugger.GetAsyncOutputStream(), accelerator_target_sp.get(), | |
| error) | |
| : platform_sp->ConnectProcess(connect_info.connect_url, | |
| GetPluginNameStatic(), debugger, | |
| accelerator_target_sp.get(), error); | |
| if (error.Fail()) | |
| return error.takeError(); | |
| if (!process_sp) | |
| return llvm::createStringError("failed to connect to the accelerator"); | |
| restore_target_group.release(); | |
| delete_accelerator_target.release(); | |
| accelerator_target_sp->SetTargetSessionName(actions.session_name); | |
| // Broadcast the new-target event so API clients can detect it. | |
| auto event_sp = std::make_shared<Event>( | |
| Target::eBroadcastBitNewTargetCreated, | |
| new Target::TargetEventData(GetTarget().shared_from_this(), | |
| accelerator_target_sp)); | |
| GetTarget().BroadcastEvent(event_sp); | |
| return llvm::Error::success(); | |
| } | |
| llvm::Error ProcessGDBRemote::HandleAcceleratorBreakpoints( | |
| const AcceleratorActions &actions) { | |
| Target &target = GetTarget(); | |
| llvm::Error error = llvm::Error::success(); | |
| for (const AcceleratorBreakpointInfo &bp : actions.breakpoints) { | |
| // Carry data with the breakpoint so the callback can notify the plugin | |
| // when the breakpoint is hit. | |
| auto args_up = std::make_unique<AcceleratorBreakpointHitArgs>(); | |
| args_up->plugin_name = actions.plugin_name; | |
| args_up->breakpoint = bp; | |
| // Each breakpoint must specify exactly one of by_name or by_address. Bad | |
| // breakpoints are collected as errors but don't stop the remaining ones | |
| // from being set. | |
| BreakpointSP bp_sp; | |
| if (bp.by_name && bp.by_address) { | |
| error = llvm::joinErrors( | |
| std::move(error), | |
| llvm::createStringErrorV( | |
| "accelerator breakpoint {0} specifies both a by_name and a " | |
| "by_address specification", | |
| bp.identifier)); | |
| continue; | |
| } else if (bp.by_name) { | |
| FileSpecList bp_modules; | |
| if (bp.by_name->shlib && !bp.by_name->shlib->empty()) | |
| bp_modules.Append(FileSpec(*bp.by_name->shlib)); | |
| bp_sp = target.CreateBreakpoint( | |
| bp_modules.GetSize() ? &bp_modules : nullptr, // Containing modules. | |
| nullptr, // Containing source. | |
| bp.by_name->function_name.c_str(), // Function name. | |
| eFunctionNameTypeFull, // Function name type. | |
| eLanguageTypeUnknown, // Language type. | |
| 0, // Byte offset. | |
| false, // Offset is insn count. | |
| eLazyBoolNo, // Skip prologue. | |
| true, // Internal breakpoint. | |
| false); // Request hardware. | |
| } else if (bp.by_address) { | |
| bp_sp = target.CreateBreakpoint(bp.by_address->load_address, | |
| /*internal=*/true, | |
| /*request_hardware=*/false); | |
| } else { | |
| error = llvm::joinErrors( | |
| std::move(error), | |
| llvm::createStringErrorV( | |
| "accelerator breakpoint {0} has neither a by_name nor a " | |
| "by_address specification", | |
| bp.identifier)); | |
| continue; | |
| } | |
| if (!bp_sp) { | |
| error = llvm::joinErrors( | |
| std::move(error), | |
| llvm::createStringErrorV("failed to set accelerator breakpoint {0}", | |
| bp.identifier)); | |
| continue; | |
| } | |
| // Give the internal breakpoint a meaningful description for stop reasons, | |
| // including the plugin that requested it. | |
| std::string kind = | |
| llvm::formatv("accelerator-plugin ({0})", actions.plugin_name); | |
| bp_sp->SetBreakpointKind(kind.c_str()); | |
| auto baton_sp = std::make_shared<AcceleratorBreakpointCallbackBaton>( | |
| std::move(args_up)); | |
| bp_sp->SetCallback(AcceleratorBreakpointHitCallback, baton_sp, | |
| /*is_synchronous=*/true); | |
| } | |
| return error; | |
| } | |
| bool ProcessGDBRemote::AcceleratorBreakpointHitCallback( | |
| void *baton, StoppointCallbackContext *context, lldb::user_id_t break_id, | |
| lldb::user_id_t break_loc_id) { | |
| ProcessSP process_sp = context->exe_ctx_ref.GetProcessSP(); | |
| ProcessGDBRemote *process = static_cast<ProcessGDBRemote *>(process_sp.get()); | |
| return process->AcceleratorBreakpointHit(baton, context, break_id, | |
| break_loc_id); | |
| } | |
| bool ProcessGDBRemote::AcceleratorBreakpointHit( | |
| void *baton, StoppointCallbackContext *context, lldb::user_id_t break_id, | |
| lldb::user_id_t break_loc_id) { | |
| AcceleratorBreakpointHitArgs *callback_data = | |
| static_cast<AcceleratorBreakpointHitArgs *>(baton); | |
| // Copy the args so we can fill in requested symbol values before notifying | |
| // lldb-server. | |
| AcceleratorBreakpointHitArgs args = *callback_data; | |
| Target &target = GetTarget(); | |
| const std::vector<std::string> &symbol_names = args.breakpoint.symbol_names; | |
| args.symbol_values.resize(symbol_names.size()); | |
| for (size_t i = 0; i < symbol_names.size(); ++i) { | |
| args.symbol_values[i].name = symbol_names[i]; | |
| SymbolContextList sc_list; | |
| target.GetImages().FindSymbolsWithNameAndType(ConstString(symbol_names[i]), | |
| eSymbolTypeAny, sc_list); | |
| for (const SymbolContext &sc : sc_list) { | |
| if (!sc.symbol) | |
| continue; | |
| addr_t load_addr = sc.symbol->GetAddress().GetLoadAddress(&target); | |
| if (load_addr != LLDB_INVALID_ADDRESS) { | |
| args.symbol_values[i].value = load_addr; | |
| break; | |
| } | |
| } | |
| } | |
| Log *log = GetLog(GDBRLog::Process); | |
| llvm::Expected<AcceleratorBreakpointHitResponse> response = | |
| m_gdb_comm.AcceleratorBreakpointHit(args); | |
| if (!response) { | |
| LLDB_LOG_ERROR(log, response.takeError(), | |
| "accelerator breakpoint hit notification failed: {0}"); | |
| // We could not reach the plugin, so auto-resume rather than stopping the | |
| // native process at an internal breakpoint the user can't see. | |
| return false; | |
| } | |
| // Disable the breakpoint if requested, but keep it around so its hit count | |
| // and other stats remain visible. | |
| if (response->disable_bp) { | |
| if (BreakpointSP bp_sp = target.GetBreakpointByID(break_id)) | |
| bp_sp->SetEnabled(false); | |
| } | |
| // The plugin may request new actions (e.g. additional breakpoints) in | |
| // response to this breakpoint being hit. | |
| if (response->actions) { | |
| if (llvm::Error error = HandleAcceleratorActions(*response->actions)) { | |
| // Also print the failure to the user; during a stop, logging alone is | |
| // invisible. | |
| std::string message = llvm::toString(std::move(error)); | |
| LLDB_LOG(log, "failed to handle accelerator actions: {0}", message); | |
| target.GetDebugger().GetAsyncErrorStream()->Printf( | |
| "error: accelerator plugin '%s': %s\n", | |
| response->actions->plugin_name.c_str(), message.c_str()); | |
| } | |
| } | |
| // Returning true stops the native process; false auto-resumes it. | |
| return !response->auto_resume_native; | |
| } | |
| 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) { | |
| LLDB_LOGF_VERBOSE(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) { | |
| LLDB_LOGF_VERBOSE( | |
| 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); | |
| LLDB_LOGF_VERBOSE(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); | |
| } | |
| static std::string | |
| BinaryInformationLevelToJSONKey(BinaryInformationLevel info_level) { | |
| std::string info_level_str; | |
| if (info_level == eBinaryInformationLevelAddrOnly) | |
| info_level_str = "address-only"; | |
| else if (info_level == eBinaryInformationLevelAddrName) | |
| info_level_str = "address-name"; | |
| else if (info_level == eBinaryInformationLevelAddrNameUUID) | |
| info_level_str = "address-name-uuid"; | |
| else if (info_level == eBinaryInformationLevelFull) | |
| info_level_str = "full"; | |
| return info_level_str; | |
| } | |
| StructuredData::ObjectSP ProcessGDBRemote::GetLoadedDynamicLibrariesInfos( | |
| BinaryInformationLevel info_level) { | |
| StructuredData::ObjectSP args_dict(new StructuredData::Dictionary()); | |
| args_dict->GetAsDictionary()->AddBooleanItem("fetch_all_solibs", true); | |
| if (info_level != eBinaryInformationLevelFull) | |
| args_dict->GetAsDictionary()->AddBooleanItem("report_load_commands", false); | |
| std::string info_level_str = BinaryInformationLevelToJSONKey(info_level); | |
| if (!info_level_str.empty()) | |
| args_dict->GetAsDictionary()->AddStringItem("information-level", | |
| info_level_str.c_str()); | |
| return GetLoadedDynamicLibrariesInfos_sender(args_dict); | |
| } | |
| StructuredData::ObjectSP ProcessGDBRemote::GetLoadedDynamicLibrariesInfos( | |
| BinaryInformationLevel info_level, | |
| 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); | |
| std::string info_level_str = BinaryInformationLevelToJSONKey(info_level); | |
| if (!info_level_str.empty()) | |
| args_dict->GetAsDictionary()->AddStringItem("information-level", | |
| info_level_str.c_str()); | |
| 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() { | |
| // Held across the query so a second caller waits for the answer instead of | |
| // sending the packet again. | |
| auto shared_cache_info = m_shared_cache_info.Lock(); | |
| StructuredData::ObjectSP args_dict(new StructuredData::Dictionary()); | |
| if (*shared_cache_info || !m_gdb_comm.GetSharedCacheInfoSupported()) | |
| return *shared_cache_info; | |
| StreamString packet; | |
| packet << "jGetSharedCacheInfo:"; | |
| args_dict->Dump(packet, false); | |
| 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()) | |
| return {}; | |
| StructuredData::ObjectSP response_sp = | |
| StructuredData::ParseJSON(response.GetStringRef()); | |
| if (!response_sp) | |
| return {}; | |
| StructuredData::Dictionary *dict = response_sp->GetAsDictionary(); | |
| if (!dict) | |
| return {}; | |
| if (!dict->HasKey("shared_cache_uuid")) | |
| return {}; | |
| llvm::StringRef uuid_str; | |
| if (!dict->GetValueForKeyAsString("shared_cache_uuid", uuid_str, "") || | |
| uuid_str == "00000000-0000-0000-0000-000000000000") | |
| return {}; | |
| if (dict->HasKey("shared_cache_path")) { | |
| UUID uuid; | |
| uuid.SetFromStringRef(uuid_str); | |
| FileSpec sc_path( | |
| dict->GetValueForKey("shared_cache_path")->GetStringValue()); | |
| SymbolSharedCacheUse sc_mode = | |
| ModuleList::GetGlobalModuleListProperties() | |
| .GetSharedCacheBinaryLoading(); | |
| if (sc_mode == eSymbolSharedCacheUseHostAndInferiorSharedCache || | |
| sc_mode == eSymbolSharedCacheUseInferiorSharedCacheOnly) { | |
| // Attempt to open the shared cache at sc_path, and | |
| // if the uuid matches, index all the files. | |
| HostInfo::SharedCacheIndexFiles(sc_path, uuid, sc_mode); | |
| } | |
| } | |
| *shared_cache_info = response_sp; | |
| } | |
| } | |
| return *shared_cache_info; | |
| } | |
| 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)); | |
| LLDB_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; | |
| }; | |
| using RegisterTypeMap = llvm::StringMap<const RegisterType *>; | |
| static RegisterTypeEnum::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 initially release with two states that software | |
| // 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, RegisterTypeEnum::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, RegisterTypeEnum::Enumerator(*value, name->str())); | |
| // Find all evalue elements. | |
| return true; | |
| }); | |
| RegisterTypeEnum::Enumerators final_enumerators; | |
| for (auto [_, enumerator] : enumerators) | |
| final_enumerators.push_back(enumerator); | |
| return final_enumerators; | |
| } | |
| static void | |
| ParseEnums(XMLNode feature_node, RegisterTypeMap &feature_register_types, | |
| std::vector<std::unique_ptr<RegisterType>> &owned_register_types) { | |
| Log *log(GetLog(GDBRLog::Process)); | |
| // The top level element is "<enum...". | |
| feature_node.ForEachChildElementWithName( | |
| "enum", [log, &feature_register_types, | |
| &owned_register_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()) { | |
| RegisterTypeEnum::Enumerators enumerators = | |
| ParseEnumEvalues(enum_node); | |
| if (!enumerators.empty()) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseEnums Found enum type \"{0}\"", | |
| id); | |
| auto enum_type = | |
| std::make_unique<RegisterTypeEnum>(id, enumerators); | |
| const RegisterTypeEnum *enum_type_ptr = enum_type.get(); | |
| auto [it, inserted] = | |
| feature_register_types.try_emplace(id, enum_type_ptr); | |
| if (inserted) { | |
| owned_register_types.push_back(std::move(enum_type)); | |
| } else if (llvm::isa<RegisterTypeEnum>(it->second)) { | |
| // Preserve the existing behavior where the last valid enum with | |
| // a repeated ID wins. All enums are parsed before flags, so no | |
| // fields can reference the enum being replaced yet. The earlier | |
| // object remains owned; only the feature lookup is updated. | |
| owned_register_types.push_back(std::move(enum_type)); | |
| it->second = enum_type_ptr; | |
| } else { | |
| LLDB_LOG( | |
| log, | |
| "ProcessGDBRemote::ParseEnums Ignoring enum type \"{0}\" " | |
| "because another type with that id already exists", | |
| id); | |
| } | |
| } | |
| } | |
| // Find all <enum> elements. | |
| return true; | |
| }); | |
| } | |
| static std::vector<RegisterTypeFlags::Field> | |
| ParseFlagsFields(XMLNode flags_node, unsigned size, | |
| const RegisterTypeMap &feature_register_types) { | |
| Log *log(GetLog(GDBRLog::Process)); | |
| const unsigned max_start_bit = size * 8 - 1; | |
| // Process the fields of this set of flags. | |
| std::vector<RegisterTypeFlags::Field> fields; | |
| flags_node.ForEachChildElementWithName("field", [&fields, max_start_bit, &log, | |
| &feature_register_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 (RegisterTypeFlags::Field::GetSizeInBits(*start, *end) > 64) | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseFlagsFields Ignoring field \"{}\" " | |
| "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 RegisterTypeEnum *enum_type = nullptr; | |
| if (type && !type->empty()) { | |
| auto found = feature_register_types.find(*type); | |
| if (found != feature_register_types.end()) { | |
| enum_type = llvm::dyn_cast<RegisterTypeEnum>(found->second); | |
| if (!enum_type) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseFlagsFields Type \"{0}\" for " | |
| "field \"{1}\" is not an enum, ignoring", | |
| type->data(), name->data()); | |
| } | |
| // No enumerator can exceed the range of the field itself. | |
| if (enum_type) { | |
| uint64_t max_value = | |
| RegisterTypeFlags::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( | |
| RegisterTypeFlags::Field(name->str(), *start, *end, enum_type)); | |
| } | |
| } | |
| } | |
| return true; // Iterate all "field" nodes. | |
| }); | |
| return fields; | |
| } | |
| void ParseFlags( | |
| XMLNode feature_node, RegisterTypeMap &feature_register_types, | |
| std::vector<std::unique_ptr<RegisterType>> &owned_register_types) { | |
| Log *log(GetLog(GDBRLog::Process)); | |
| feature_node.ForEachChildElementWithName( | |
| "flags", | |
| [&log, &feature_register_types, | |
| &owned_register_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<RegisterTypeFlags::Field> fields = | |
| ParseFlagsFields(flags_node, *size, feature_register_types); | |
| if (fields.size()) { | |
| // Sort so that the fields with the MSBs are first. | |
| std::sort(fields.rbegin(), fields.rend()); | |
| std::vector<RegisterTypeFlags::Field>::const_iterator overlap = | |
| std::adjacent_find(fields.begin(), fields.end(), | |
| [](const RegisterTypeFlags::Field &lhs, | |
| const RegisterTypeFlags::Field &rhs) { | |
| return lhs.Overlaps(rhs); | |
| }); | |
| // If no fields overlap, use them. | |
| if (overlap == fields.end()) { | |
| if (feature_register_types.contains(*id)) { | |
| // Type IDs must be unique within a feature. Keep the type that | |
| // was already registered by the enum and flags parsing passes. | |
| LLDB_LOG( | |
| log, | |
| "ProcessGDBRemote::ParseFlags Definition of flags \"{0}\" " | |
| "conflicts with an existing type, ignoring this " | |
| "definition.", | |
| id->data()); | |
| } else { | |
| auto flags_type = std::make_unique<RegisterTypeFlags>( | |
| id->str(), *size, std::move(fields)); | |
| feature_register_types.try_emplace(*id, flags_type.get()); | |
| owned_register_types.push_back(std::move(flags_type)); | |
| } | |
| } else { | |
| // If any fields overlap, ignore the whole set of flags. | |
| std::vector<RegisterTypeFlags::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. | |
| }); | |
| } | |
| static const RegisterTypeBuiltin * | |
| ResolveGDBBuiltinType(llvm::StringRef type_name) { | |
| // These names and sizes follow GDB's predefined target-description types in | |
| // gdbsupport/tdesc.cc. ARM FPA is intentionally omitted because GCC removed | |
| // support for it in 2012. | |
| static const RegisterTypeBuiltin bool_type("bool", eEncodingUint, | |
| eFormatBoolean, 1); | |
| static const RegisterTypeBuiltin int8_type("int8", eEncodingSint, | |
| eFormatDecimal, 1); | |
| static const RegisterTypeBuiltin int16_type("int16", eEncodingSint, | |
| eFormatDecimal, 2); | |
| static const RegisterTypeBuiltin int32_type("int32", eEncodingSint, | |
| eFormatDecimal, 4); | |
| static const RegisterTypeBuiltin int64_type("int64", eEncodingSint, | |
| eFormatDecimal, 8); | |
| static const RegisterTypeBuiltin int128_type("int128", eEncodingSint, | |
| eFormatDecimal, 16); | |
| static const RegisterTypeBuiltin uint8_type("uint8", eEncodingUint, | |
| eFormatHex, 1); | |
| static const RegisterTypeBuiltin uint16_type("uint16", eEncodingUint, | |
| eFormatHex, 2); | |
| static const RegisterTypeBuiltin uint32_type("uint32", eEncodingUint, | |
| eFormatHex, 4); | |
| static const RegisterTypeBuiltin uint64_type("uint64", eEncodingUint, | |
| eFormatHex, 8); | |
| static const RegisterTypeBuiltin uint128_type("uint128", eEncodingUint, | |
| eFormatHex, 16); | |
| static const RegisterTypeBuiltin code_ptr_type( | |
| "code_ptr", eEncodingUint, eFormatAddressInfo, std::nullopt); | |
| static const RegisterTypeBuiltin data_ptr_type( | |
| "data_ptr", eEncodingUint, eFormatAddressInfo, std::nullopt); | |
| static const RegisterTypeBuiltin ieee_half_type("ieee_half", eEncodingIEEE754, | |
| eFormatFloat, 2); | |
| static const RegisterTypeBuiltin ieee_single_type( | |
| "ieee_single", eEncodingIEEE754, eFormatFloat, 4); | |
| static const RegisterTypeBuiltin ieee_double_type( | |
| "ieee_double", eEncodingIEEE754, eFormatFloat, 8); | |
| static const RegisterTypeBuiltin i387_ext_type("i387_ext", eEncodingIEEE754, | |
| eFormatFloat, 10); | |
| static const RegisterTypeBuiltin bfloat16_type("bfloat16", eEncodingIEEE754, | |
| eFormatFloat, 2); | |
| return llvm::StringSwitch<const RegisterTypeBuiltin *>(type_name) | |
| .Case("bool", &bool_type) | |
| .Case("int8", &int8_type) | |
| .Case("int16", &int16_type) | |
| .Case("int32", &int32_type) | |
| .Case("int64", &int64_type) | |
| .Case("int128", &int128_type) | |
| .Case("uint8", &uint8_type) | |
| .Case("uint16", &uint16_type) | |
| .Case("uint32", &uint32_type) | |
| .Case("uint64", &uint64_type) | |
| .Case("uint128", &uint128_type) | |
| .Case("code_ptr", &code_ptr_type) | |
| .Case("data_ptr", &data_ptr_type) | |
| .Case("ieee_half", &ieee_half_type) | |
| .Case("ieee_single", &ieee_single_type) | |
| .Case("ieee_double", &ieee_double_type) | |
| .Case("i387_ext", &i387_ext_type) | |
| .Case("bfloat16", &bfloat16_type) | |
| .Default(nullptr); | |
| } | |
| static const RegisterType * | |
| ResolveGDBType(llvm::StringRef type_name, | |
| const RegisterTypeMap &feature_register_types) { | |
| auto type_it = feature_register_types.find(type_name); | |
| if (type_it != feature_register_types.end()) | |
| return type_it->second; | |
| return ResolveGDBBuiltinType(type_name); | |
| } | |
| static void | |
| ParseVector(const XMLNode &vector_node, RegisterTypeMap &feature_register_types, | |
| std::vector<std::unique_ptr<RegisterType>> &owned_register_types) { | |
| Log *log(GetLog(GDBRLog::Process)); | |
| std::optional<llvm::StringRef> id; | |
| std::optional<llvm::StringRef> element_type_name; | |
| std::optional<uint32_t> count; | |
| vector_node.ForEachAttribute( | |
| [&id, &element_type_name, &count, log](llvm::StringRef name, | |
| llvm::StringRef value) { | |
| if (name == "id") { | |
| id = value; | |
| } else if (name == "type") { | |
| element_type_name = value; | |
| } else if (name == "count") { | |
| uint32_t parsed_count = 0; | |
| if (llvm::to_integer(value, parsed_count)) | |
| count = parsed_count; | |
| else | |
| LLDB_LOG(log, "ProcessGDBRemote::ParseVector Invalid count \"{0}\"", | |
| value); | |
| } else { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseVector Ignoring unknown attribute " | |
| "\"{0}\"", | |
| name); | |
| } | |
| return true; | |
| }); | |
| // GDB limits vectors to 65536 elements. This is also LLDB's maximum | |
| // register size in bytes, so use the existing named limit. | |
| constexpr uint32_t max_vector_count = RegisterValue::kMaxRegisterByteSize; | |
| if (!id || id->empty() || !element_type_name || element_type_name->empty() || | |
| !count || *count == 0 || *count > max_vector_count) { | |
| LLDB_LOG(log, "ProcessGDBRemote::ParseVector Ignoring vector with invalid " | |
| "id, type, or count"); | |
| return; | |
| } | |
| if (feature_register_types.contains(*id)) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseVector Ignoring duplicate type \"{0}\"", | |
| *id); | |
| return; | |
| } | |
| const RegisterType *element_type = | |
| ResolveGDBType(*element_type_name, feature_register_types); | |
| if (!element_type) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseVector Could not resolve element type " | |
| "\"{0}\" for vector \"{1}\"", | |
| *element_type_name, *id); | |
| return; | |
| } | |
| if (!llvm::isa<RegisterTypeBuiltin, RegisterTypeVector, RegisterTypeUnion>( | |
| element_type)) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseVector Found element type \"{0}\" for " | |
| "vector \"{1}\", but it is not a builtin, vector, or union " | |
| "type", | |
| *element_type_name, *id); | |
| return; | |
| } | |
| std::optional<uint64_t> element_size = element_type->GetByteSize(); | |
| if (element_size && | |
| *element_size > RegisterValue::kMaxRegisterByteSize / *count) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseVector Size of vector \"{0}\" is too " | |
| "large", | |
| *id); | |
| return; | |
| } | |
| auto vector_type = | |
| std::make_unique<RegisterTypeVector>(id->str(), element_type, *count); | |
| feature_register_types.try_emplace(*id, vector_type.get()); | |
| owned_register_types.push_back(std::move(vector_type)); | |
| } | |
| static std::vector<RegisterTypeUnion::Field> | |
| ParseUnionFields(const XMLNode &union_node, llvm::StringRef union_id, | |
| const RegisterTypeMap &feature_register_types) { | |
| Log *log(GetLog(GDBRLog::Process)); | |
| std::vector<RegisterTypeUnion::Field> fields; | |
| bool invalid_field = false; | |
| union_node.ForEachChildElementWithName( | |
| "field", [&fields, &invalid_field, log, &feature_register_types, | |
| union_id](const XMLNode &field_node) { | |
| std::optional<llvm::StringRef> name; | |
| std::optional<llvm::StringRef> type_name; | |
| field_node.ForEachAttribute( | |
| [&name, &type_name, log, union_id](llvm::StringRef attribute, | |
| llvm::StringRef value) { | |
| if (attribute == "name") | |
| name = value; | |
| else if (attribute == "type") | |
| type_name = value; | |
| else | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseUnionFields Ignoring unknown " | |
| "attribute \"{0}\" in a field of union \"{1}\"", | |
| attribute, union_id); | |
| return true; | |
| }); | |
| if (!name || name->empty() || !type_name || type_name->empty()) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseUnionFields Union \"{0}\" has a " | |
| "field missing a non-empty name or type", | |
| union_id); | |
| invalid_field = true; | |
| return true; | |
| } | |
| const RegisterType *field_type = | |
| ResolveGDBType(*type_name, feature_register_types); | |
| if (!field_type) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseUnionFields Could not resolve type " | |
| "\"{0}\" for field \"{1}\" of union \"{2}\"", | |
| *type_name, *name, union_id); | |
| invalid_field = true; | |
| return true; | |
| } | |
| if (!llvm::isa<RegisterTypeBuiltin, RegisterTypeVector, | |
| RegisterTypeUnion>(field_type)) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseUnionFields Found type \"{0}\" " | |
| "for field \"{1}\", but it is not a builtin, vector, or " | |
| "union type. Union \"{2}\" will be ignored.", | |
| *type_name, *name, union_id); | |
| invalid_field = true; | |
| return true; | |
| } | |
| fields.emplace_back(name->str(), field_type); | |
| return true; | |
| }); | |
| // Reject the whole union if any field is invalid. Retaining only valid fields | |
| // would misrepresent the target's type definition. | |
| if (invalid_field) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseUnionFields Ignoring union \"{0}\" " | |
| "because it contains an invalid field", | |
| union_id); | |
| fields.clear(); | |
| } else if (fields.empty()) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseUnionFields Ignoring union \"{0}\" " | |
| "because it has no fields", | |
| union_id); | |
| } | |
| return fields; | |
| } | |
| static void | |
| ParseUnion(const XMLNode &union_node, RegisterTypeMap &feature_register_types, | |
| std::vector<std::unique_ptr<RegisterType>> &owned_register_types) { | |
| Log *log(GetLog(GDBRLog::Process)); | |
| std::optional<llvm::StringRef> id; | |
| union_node.ForEachAttribute( | |
| [&id, log](llvm::StringRef name, llvm::StringRef value) { | |
| if (name == "id") | |
| id = value; | |
| else | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseUnion Ignoring unknown attribute " | |
| "\"{0}\"", | |
| name); | |
| return true; | |
| }); | |
| if (!id || id->empty()) { | |
| LLDB_LOG(log, "ProcessGDBRemote::ParseUnion Ignoring union without an id"); | |
| return; | |
| } | |
| if (feature_register_types.contains(*id)) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseUnion Ignoring duplicate type \"{0}\"", | |
| *id); | |
| return; | |
| } | |
| std::vector<RegisterTypeUnion::Field> fields = | |
| ParseUnionFields(union_node, *id, feature_register_types); | |
| if (fields.empty()) | |
| return; | |
| auto union_type = | |
| std::make_unique<RegisterTypeUnion>(id->str(), std::move(fields)); | |
| feature_register_types.try_emplace(*id, union_type.get()); | |
| owned_register_types.push_back(std::move(union_type)); | |
| } | |
| static void ParseCompositeTypes( | |
| XMLNode feature_node, RegisterTypeMap &feature_register_types, | |
| std::vector<std::unique_ptr<RegisterType>> &owned_register_types) { | |
| feature_node.ForEachChildElement( | |
| [&feature_register_types, | |
| &owned_register_types](const XMLNode &type_node) { | |
| if (type_node.NameIs("vector")) | |
| ParseVector(type_node, feature_register_types, owned_register_types); | |
| else if (type_node.NameIs("union")) | |
| ParseUnion(type_node, feature_register_types, owned_register_types); | |
| return true; | |
| }); | |
| } | |
| bool ParseRegisters( | |
| XMLNode feature_node, GdbServerTargetInfo &target_info, | |
| std::vector<DynamicRegisterInfo::Register> ®isters, | |
| std::vector<std::unique_ptr<RegisterType>> &owned_register_types) { | |
| if (!feature_node) | |
| return false; | |
| Log *log(GetLog(GDBRLog::Process)); | |
| RegisterTypeMap feature_register_types; | |
| // Enums first because they are referenced by fields in the flags. | |
| ParseEnums(feature_node, feature_register_types, owned_register_types); | |
| for (const auto ®ister_type : feature_register_types) | |
| if (const auto *enum_type = | |
| llvm::dyn_cast<RegisterTypeEnum>(register_type.second)) | |
| enum_type->DumpToLog(log); | |
| ParseFlags(feature_node, feature_register_types, owned_register_types); | |
| for (const auto ®ister_type : feature_register_types) | |
| if (const auto *flags_type = | |
| llvm::dyn_cast<RegisterTypeFlags>(register_type.second)) | |
| flags_type->DumpToLog(log); | |
| // Enums and flags retain their dedicated passes above. Vectors and unions | |
| // can reference one another, so parse them together in document order. A | |
| // referenced composite type must precede its user. | |
| ParseCompositeTypes(feature_node, feature_register_types, | |
| owned_register_types); | |
| for (const auto ®ister_type : feature_register_types) | |
| if (const auto *vector_type = | |
| llvm::dyn_cast<RegisterTypeVector>(register_type.second)) | |
| vector_type->DumpToLog(log); | |
| for (const auto ®ister_type : feature_register_types) | |
| if (const auto *union_type = | |
| llvm::dyn_cast<RegisterTypeUnion>(register_type.second)) | |
| union_type->DumpToLog(log); | |
| feature_node.ForEachChildElementWithName( | |
| "reg", | |
| [&target_info, ®isters, &feature_register_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 a type defined in this feature. | |
| auto it = feature_register_types.find(gdb_type); | |
| if (it != feature_register_types.end()) { | |
| if (const auto *vector_type = | |
| llvm::dyn_cast<RegisterTypeVector>(it->second)) { | |
| std::optional<uint64_t> type_size = vector_type->GetByteSize(); | |
| if (reg_info.byte_size > RegisterValue::kMaxRegisterByteSize) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseRegisters Register {0} is " | |
| "too large for vector type {1}", | |
| reg_info.name, vector_type->GetID()); | |
| } else if (!vector_type->IsByteSizeCompatible( | |
| reg_info.byte_size)) { | |
| if (!type_size) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseRegisters Size of register " | |
| "{0} is incompatible with vector type {1}", | |
| reg_info.name, vector_type->GetID()); | |
| } else { | |
| LLDB_LOG( | |
| log, | |
| "ProcessGDBRemote::ParseRegisters Size of register type " | |
| "{0} ({1} bytes) for register {2} does not match the " | |
| "register size ({3} bytes). Ignoring this type.", | |
| vector_type->GetID(), *type_size, reg_info.name, | |
| reg_info.byte_size); | |
| } | |
| } else { | |
| reg_info.register_type = vector_type; | |
| if (!encoding_set) { | |
| reg_info.encoding = eEncodingVector; | |
| encoding_set = true; | |
| } | |
| if (!format_set) { | |
| reg_info.format = eFormatVectorOfUInt8; | |
| format_set = true; | |
| } | |
| } | |
| } else if (const auto *union_type = | |
| llvm::dyn_cast<RegisterTypeUnion>(it->second)) { | |
| if (reg_info.byte_size > RegisterValue::kMaxRegisterByteSize) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseRegisters Register {0} is " | |
| "too large for union type {1}", | |
| reg_info.name, union_type->GetID()); | |
| } else if (!union_type->IsByteSizeCompatible( | |
| reg_info.byte_size)) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::ParseRegisters Size of register " | |
| "{0} is incompatible with union type {1}", | |
| reg_info.name, union_type->GetID()); | |
| } else { | |
| reg_info.register_type = union_type; | |
| if (!encoding_set) { | |
| reg_info.encoding = eEncodingUint; | |
| encoding_set = true; | |
| } | |
| if (!format_set) { | |
| reg_info.format = eFormatHex; | |
| format_set = true; | |
| } | |
| } | |
| } else if (const auto *flags_type = | |
| llvm::dyn_cast<RegisterTypeFlags>(it->second)) { | |
| if (reg_info.byte_size == flags_type->GetSize()) | |
| reg_info.register_type = flags_type; | |
| else | |
| LLDB_LOG( | |
| log, | |
| "ProcessGDBRemote::ParseRegisters Size of register flags " | |
| "{0} ({1} bytes) for register {2} does not match the " | |
| "register size ({3} bytes). Ignoring this set of flags.", | |
| flags_type->GetID().c_str(), flags_type->GetSize(), | |
| reg_info.name, 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" || gdb_type == "ieee_single" || | |
| gdb_type == "ieee_double") { | |
| 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_LOG(log, | |
| "ProcessGDBRemote::{0} Skipping zero bitsize register {1}", | |
| __FUNCTION__, reg_info.name); | |
| } 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_register_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). | |
| llvm::Error ProcessGDBRemote::GetGDBServerRegisterInfo(ArchSpec &arch_to_use) { | |
| // If the remote does not offer XML, does not matter if we would have been | |
| // able to parse it. | |
| if (!m_gdb_comm.GetQXferFeaturesReadSupported()) | |
| return llvm::createStringError( | |
| llvm::inconvertibleErrorCode(), | |
| "the debug server does not support \"qXfer:features:read\""); | |
| if (!XMLDocument::XMLEnabled()) | |
| return llvm::createStringError( | |
| llvm::inconvertibleErrorCode(), | |
| "the debug server supports \"qXfer:features:read\", but LLDB does not " | |
| "have XML parsing enabled (check LLLDB_ENABLE_LIBXML2)"); | |
| 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::ErrorSuccess() | |
| : llvm::createStringError( | |
| llvm::inconvertibleErrorCode(), | |
| "the debug server did not describe any registers"); | |
| } | |
| 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 | |
| }); | |
| 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 | |
| }); | |
| 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) { | |
| lldb_private::ObjectFile *obj = module_sp->GetObjectFile(); | |
| if (!obj) | |
| return IterationAction::Continue; | |
| if (obj->GetType() != ObjectFile::Type::eTypeExecutable) | |
| return IterationAction::Continue; | |
| if (target.GetExecutableModulePointer() == module_sp.get()) | |
| return IterationAction::Stop; | |
| lldb::ModuleSP module_copy_sp = module_sp; | |
| target.RebuildModuleListWithExecutable(module_copy_sp, eLoadDependentsNo); | |
| return IterationAction::Stop; | |
| }); | |
| 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(BASE_10, 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(), | |
| "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)) { | |
| 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, bool is_expression_fork) { | |
| Log *log = GetLog(GDBRLog::Process); | |
| // Resolve the expression-return sentinel address (_start) once. This is | |
| // the same address ThreadPlanCallFunction uses as the return trap. | |
| lldb::addr_t entry_addr = LLDB_INVALID_ADDRESS; | |
| if (!enable && is_expression_fork) { | |
| if (auto entry = GetTarget().GetEntryPointAddress()) | |
| entry_addr = entry->GetOpcodeLoadAddress(&GetTarget()); | |
| } | |
| GetBreakpointSiteList().ForEach([this, enable, entry_addr, | |
| log](BreakpointSite *bp_site) { | |
| if (IsBreakpointSitePhysicallyEnabled(*bp_site) && | |
| (bp_site->GetType() == BreakpointSite::eSoftware || | |
| bp_site->GetType() == BreakpointSite::eExternal)) { | |
| // During expression evaluation, retain the expression-return trap | |
| // at _start in the forked child so it dies deterministically on | |
| // SIGTRAP rather than executing _start with a corrupted stack. | |
| if (entry_addr != LLDB_INVALID_ADDRESS && | |
| bp_site->GetLoadAddress() == entry_addr) { | |
| LLDB_LOG(log, | |
| "DidForkSwitchSoftwareBreakpoints: retaining expression-" | |
| "return trap at {0:x} in forked child", | |
| bp_site->GetLoadAddress()); | |
| return; | |
| } | |
| 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 (IsBreakpointSitePhysicallyEnabled(*bp_site) && | |
| 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, | |
| bool is_expression_fork) { | |
| Log *log = GetLog(GDBRLog::Process); | |
| // During expression evaluation, force follow-parent regardless of which | |
| // thread forked. The expression is running on the parent and following the | |
| // child would cause the expression thread to vanish (the child has different | |
| // thread IDs). Even if a *different* thread forks, switching to the child | |
| // would destroy the expression thread's process context. | |
| FollowForkMode follow_fork_mode = GetFollowForkMode(); | |
| bool overrode_follow_mode = false; | |
| if (follow_fork_mode == eFollowChild && | |
| GetModIDRef().IsRunningExpression()) { | |
| if (is_expression_fork) { | |
| LLDB_LOG(log, "ProcessGDBRemote::DidFork() overriding follow-fork-mode " | |
| "to parent during expression evaluation"); | |
| } else { | |
| LLDB_LOG(log, "ProcessGDBRemote::DidFork() overriding follow-fork-mode " | |
| "to parent during expression evaluation. Child process " | |
| "{0} is available for manual attachment.", | |
| child_pid); | |
| } | |
| follow_fork_mode = eFollowParent; | |
| overrode_follow_mode = true; | |
| } | |
| 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 (follow_fork_mode) { | |
| 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, is_expression_fork); | |
| // Remove hardware breakpoints / watchpoints from parent process if we're | |
| // following child. | |
| if (follow_fork_mode == 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); | |
| // When we overrode follow-child because of a concurrent expression, try to | |
| // keep the child stopped so the user can attach to it manually. | |
| bool keep_stopped = overrode_follow_mode && !is_expression_fork; | |
| Status error = m_gdb_comm.Detach(keep_stopped, detach_pid); | |
| if (error.Fail() && keep_stopped) { | |
| LLDB_LOG(log, "ProcessGDBRemote::DidFork() detach-and-stay-stopped not " | |
| "supported, falling back to normal detach"); | |
| keep_stopped = false; | |
| 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; | |
| } | |
| // Notify the user via the async output channel when we overrode | |
| // follow-fork-mode for a non-expression fork during expression evaluation. | |
| if (overrode_follow_mode && !is_expression_fork) { | |
| StreamUP output_up = | |
| GetTarget().GetDebugger().GetAsyncOutputStream(); | |
| if (output_up) { | |
| output_up->Printf("warning: follow-fork-mode 'child' was overridden to " | |
| "'parent' because an expression is being evaluated.\n" | |
| "Child process %" PRIu64 | |
| " has been detached%s.\n" | |
| "You can attach to it with: process attach -p %" PRIu64 | |
| "\n", | |
| child_pid, | |
| keep_stopped ? " and stopped" : " (running)", | |
| child_pid); | |
| output_up->Flush(); | |
| } | |
| } | |
| // Hardware breakpoints/watchpoints are not inherited implicitly, | |
| // so we need to readd them if we're following child. | |
| if (follow_fork_mode == eFollowChild) { | |
| DidForkSwitchHardwareTraps(true); | |
| // Update our PID | |
| SetID(child_pid); | |
| } | |
| } | |
| void ProcessGDBRemote::DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid, | |
| bool is_expression_fork) { | |
| Log *log = GetLog(GDBRLog::Process); | |
| LLDB_LOG( | |
| log, | |
| "ProcessGDBRemote::DidVFork() called for child_pid: {0}, child_tid {1}", | |
| child_pid, child_tid); | |
| ++m_vfork_in_progress_count; | |
| // See comment in DidFork(): force follow-parent during expression evaluation | |
| // regardless of which thread triggered the vfork. | |
| FollowForkMode follow_fork_mode = GetFollowForkMode(); | |
| bool overrode_follow_mode = false; | |
| if (follow_fork_mode == eFollowChild && | |
| GetModIDRef().IsRunningExpression()) { | |
| if (is_expression_fork) { | |
| LLDB_LOG(log, "ProcessGDBRemote::DidVFork() overriding follow-fork-mode " | |
| "to parent during expression evaluation"); | |
| } else { | |
| LLDB_LOG(log, "ProcessGDBRemote::DidVFork() overriding follow-fork-mode " | |
| "to parent during expression evaluation. Child process " | |
| "{0} is available for manual attachment.", | |
| child_pid); | |
| } | |
| follow_fork_mode = eFollowParent; | |
| overrode_follow_mode = true; | |
| } | |
| // Disable all software breakpoints for the duration of vfork. | |
| if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware)) | |
| DidForkSwitchSoftwareBreakpoints(false, is_expression_fork); | |
| lldb::pid_t detach_pid; | |
| lldb::tid_t detach_tid; | |
| switch (follow_fork_mode) { | |
| 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::DidVFork() 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::DidVFork() unable to reset pid/tid"); | |
| return; | |
| } | |
| break; | |
| } | |
| LLDB_LOG(log, "Detaching process {0}", detach_pid); | |
| bool keep_stopped = overrode_follow_mode && !is_expression_fork; | |
| Status error = m_gdb_comm.Detach(keep_stopped, detach_pid); | |
| if (error.Fail() && keep_stopped) { | |
| LLDB_LOG(log, "ProcessGDBRemote::DidVFork() detach-and-stay-stopped not " | |
| "supported, falling back to normal detach"); | |
| keep_stopped = false; | |
| error = m_gdb_comm.Detach(false, detach_pid); | |
| } | |
| if (error.Fail()) { | |
| LLDB_LOG(log, | |
| "ProcessGDBRemote::DidVFork() detach packet send failed: {0}", | |
| error.AsCString() ? error.AsCString() : "<unknown error>"); | |
| return; | |
| } | |
| if (overrode_follow_mode && !is_expression_fork) { | |
| StreamUP output_up = | |
| GetTarget().GetDebugger().GetAsyncOutputStream(); | |
| if (output_up) { | |
| output_up->Printf("warning: follow-fork-mode 'child' was overridden to " | |
| "'parent' because an expression is being evaluated.\n" | |
| "Child process %" PRIu64 | |
| " has been detached%s.\n" | |
| "You can attach to it with: process attach -p %" PRIu64 | |
| "\n", | |
| child_pid, | |
| keep_stopped ? " and stopped" : " (running)", | |
| child_pid); | |
| output_up->Flush(); | |
| } | |
| } | |
| if (follow_fork_mode == 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(); | |
| } | |
| llvm::Error ProcessGDBRemote::UpdateBreakpointSitesNotBatched( | |
| const BreakpointSiteToActionMap &site_to_action) { | |
| llvm::Error joined = llvm::Error::success(); | |
| for (auto &[site, action] : site_to_action) { | |
| llvm::Error error = action == Process::BreakpointAction::Enable | |
| ? DoEnableBreakpointSite(*site) | |
| : DoDisableBreakpointSite(*site); | |
| joined = llvm::joinErrors(std::move(joined), std::move(error)); | |
| } | |
| return joined; | |
| } | |
| /// Parse a MultiBreakpoint response into per-request results. | |
| /// Returns a vector of results: std::nullopt means OK, a uint8_t value is the | |
| /// error code from an Exx response. | |
| static llvm::SmallVector<std::optional<uint8_t>> | |
| ParseMultiBreakpointResponse(llvm::StringRef response_str) { | |
| llvm::SmallVector<std::optional<uint8_t>> results; | |
| StructuredData::ObjectSP parsed = StructuredData::ParseJSON(response_str); | |
| StructuredData::Dictionary *dict = | |
| parsed ? parsed->GetAsDictionary() : nullptr; | |
| StructuredData::Array *array = nullptr; | |
| if (dict) | |
| dict->GetValueForKeyAsArray("results", array); | |
| if (!array) | |
| return results; | |
| array->ForEach([&results](StructuredData::Object *object) -> bool { | |
| llvm::StringRef token; | |
| if (auto *string = object->GetAsString()) | |
| token = string->GetValue(); | |
| if (token == "OK") { | |
| results.push_back(std::nullopt); | |
| return true; | |
| } | |
| if (token.size() != 3 || !token.starts_with("E")) { | |
| results.push_back(uint8_t(0xff)); | |
| return true; | |
| } | |
| uint8_t error_code = 0; | |
| if (token.drop_front(1).getAsInteger(BASE_16, error_code)) | |
| results.push_back(0xff); | |
| else | |
| results.push_back(error_code); | |
| return true; | |
| }); | |
| return results; | |
| } | |
| /// Determine the GDB stoppoint type for a breakpoint site by checking which | |
| /// packet types the remote supports (for insertions), or by checking the site | |
| /// type (for deletions). | |
| static std::optional<GDBStoppointType> | |
| GetStoppointType(BreakpointSite &site, bool insert, | |
| GDBRemoteCommunicationClient &gdb_comm) { | |
| if (insert) { | |
| if (!site.HardwareRequired() && | |
| gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware)) | |
| return eBreakpointSoftware; | |
| if (gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) | |
| return eBreakpointHardware; | |
| return std::nullopt; | |
| } | |
| switch (site.GetType()) { | |
| case BreakpointSite::eExternal: | |
| return eBreakpointSoftware; | |
| case BreakpointSite::eHardware: | |
| return eBreakpointHardware; | |
| case BreakpointSite::eSoftware: | |
| return std::nullopt; | |
| } | |
| llvm_unreachable("unhandled BreakpointSite type"); | |
| } | |
| namespace { | |
| struct BreakpointPacketInfo { | |
| BreakpointSite &site; | |
| size_t trap_opcode_size; | |
| GDBStoppointType type; | |
| bool is_enable; | |
| }; | |
| std::string to_string(const BreakpointPacketInfo &info) { | |
| char packet = info.is_enable ? 'Z' : 'z'; | |
| return llvm::formatv("{0}{1},{2:x-},{3:x-}", packet, | |
| static_cast<int>(info.type), info.site.GetLoadAddress(), | |
| info.trap_opcode_size) | |
| .str(); | |
| } | |
| } // namespace | |
| llvm::Error ProcessGDBRemote::UpdateBreakpointSites( | |
| const BreakpointSiteToActionMap &site_to_action) { | |
| if (site_to_action.empty()) | |
| return llvm::Error::success(); | |
| if (!m_gdb_comm.GetMultiBreakpointSupported()) | |
| return UpdateBreakpointSitesNotBatched(site_to_action); | |
| Log *log = GetLog(GDBRLog::Breakpoints); | |
| std::vector<BreakpointPacketInfo> breakpoint_infos; | |
| for (auto [site, action] : site_to_action) { | |
| size_t trap_opcode_size = GetSoftwareBreakpointTrapOpcode(site.get()); | |
| std::optional<GDBStoppointType> type = | |
| GetStoppointType(*site, action == BreakpointAction::Enable, m_gdb_comm); | |
| if (!type) { | |
| LLDB_LOG(log, "MultiBreakpoint: site {0} at {1:x} can't be batched", | |
| site->GetID(), site->GetLoadAddress()); | |
| return UpdateBreakpointSitesNotBatched(site_to_action); | |
| } | |
| breakpoint_infos.push_back( | |
| {*site, trap_opcode_size, *type, action == BreakpointAction::Enable}); | |
| } | |
| StreamString stream; | |
| stream << "jMultiBreakpoint:"; | |
| auto args_array = std::make_shared<StructuredData::Array>(); | |
| for (auto &bp_info : breakpoint_infos) | |
| args_array->AddStringItem(to_string(bp_info)); | |
| StructuredData::Dictionary packet_dict; | |
| packet_dict.AddItem("breakpoint_requests", args_array); | |
| packet_dict.Dump(stream, false); | |
| StreamGDBRemote escaped_stream; | |
| escaped_stream.PutEscapedBytes(stream.GetString()); | |
| llvm::Expected<StringExtractorGDBRemote> response = | |
| m_gdb_comm.SendPacketAndExpectResponse(escaped_stream.GetString(), | |
| GetInterruptTimeout()); | |
| if (!response) { | |
| LLDB_LOG_ERROR(log, response.takeError(), "jMultiBreakpoint failed: {0}"); | |
| return UpdateBreakpointSitesNotBatched(site_to_action); | |
| } | |
| llvm::SmallVector<std::optional<uint8_t>> results = | |
| ParseMultiBreakpointResponse(response->GetStringRef()); | |
| // This is a protocol violation, do nothing. | |
| if (results.size() != breakpoint_infos.size()) | |
| return llvm::createStringErrorV( | |
| "MultiBreakpoint response count mismatch (expected {0}, got {1})", | |
| site_to_action.size(), results.size()); | |
| llvm::Error joined = llvm::Error::success(); | |
| for (auto [error_code, bp_info] : | |
| llvm::zip_equal(results, breakpoint_infos)) { | |
| BreakpointSite &site = bp_info.site; | |
| if (error_code) { | |
| auto error = llvm::createStringErrorV( | |
| "MultiBreakpoint: site {0} at {1:x} failed with E{2}", | |
| bp_info.site.GetID(), bp_info.site.GetLoadAddress(), error_code); | |
| joined = llvm::joinErrors(std::move(joined), std::move(error)); | |
| continue; | |
| } | |
| SetBreakpointSiteEnabled(site, bp_info.is_enable); | |
| if (bp_info.is_enable) | |
| site.SetType(bp_info.type == eBreakpointHardware | |
| ? BreakpointSite::eHardware | |
| : BreakpointSite::eExternal); | |
| } | |
| return joined; | |
| } |