diff options
author | 2020-08-03 14:33:06 +0000 | |
---|---|---|
committer | 2020-08-03 14:33:06 +0000 | |
commit | 061da546b983eb767bad15e67af1174fb0bcf31c (patch) | |
tree | 83c78b820819d70aa40c36d90447978b300078c5 /gnu/llvm/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp | |
parent | Import LLVM 10.0.0 release including clang, lld and lldb. (diff) | |
download | wireguard-openbsd-061da546b983eb767bad15e67af1174fb0bcf31c.tar.xz wireguard-openbsd-061da546b983eb767bad15e67af1174fb0bcf31c.zip |
Import LLVM 10.0.0 release including clang, lld and lldb.
ok hackroom
tested by plenty
Diffstat (limited to 'gnu/llvm/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp')
-rw-r--r-- | gnu/llvm/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/gnu/llvm/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp b/gnu/llvm/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp new file mode 100644 index 00000000000..491c0b012b0 --- /dev/null +++ b/gnu/llvm/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp @@ -0,0 +1,1030 @@ +//===-- ProcessKDP.cpp ------------------------------------------*- C++ -*-===// +// +// 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 <errno.h> +#include <stdlib.h> + +#include <memory> +#include <mutex> + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/ConnectionFileDescriptor.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Host/common/TCPSocket.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionGroupString.h" +#include "lldb/Interpreter/OptionGroupUInt64.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Symbol/LocateSymbolFile.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/StringExtractor.h" +#include "lldb/Utility/UUID.h" + +#include "llvm/Support/Threading.h" + +#define USEC_PER_SEC 1000000 + +#include "Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.h" +#include "Plugins/DynamicLoader/Static/DynamicLoaderStatic.h" +#include "ProcessKDP.h" +#include "ProcessKDPLog.h" +#include "ThreadKDP.h" + +using namespace lldb; +using namespace lldb_private; + +namespace { + +#define LLDB_PROPERTIES_processkdp +#include "ProcessKDPProperties.inc" + +enum { +#define LLDB_PROPERTIES_processkdp +#include "ProcessKDPPropertiesEnum.inc" +}; + +class PluginProperties : public Properties { +public: + static ConstString GetSettingName() { + return ProcessKDP::GetPluginNameStatic(); + } + + PluginProperties() : Properties() { + m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName()); + m_collection_sp->Initialize(g_processkdp_properties); + } + + virtual ~PluginProperties() {} + + uint64_t GetPacketTimeout() { + const uint32_t idx = ePropertyKDPPacketTimeout; + return m_collection_sp->GetPropertyAtIndexAsUInt64( + NULL, idx, g_processkdp_properties[idx].default_uint_value); + } +}; + +typedef std::shared_ptr<PluginProperties> ProcessKDPPropertiesSP; + +static const ProcessKDPPropertiesSP &GetGlobalPluginProperties() { + static ProcessKDPPropertiesSP g_settings_sp; + if (!g_settings_sp) + g_settings_sp = std::make_shared<PluginProperties>(); + return g_settings_sp; +} + +} // anonymous namespace end + +static const lldb::tid_t g_kernel_tid = 1; + +ConstString ProcessKDP::GetPluginNameStatic() { + static ConstString g_name("kdp-remote"); + return g_name; +} + +const char *ProcessKDP::GetPluginDescriptionStatic() { + return "KDP Remote protocol based debugging plug-in for darwin kernel " + "debugging."; +} + +void ProcessKDP::Terminate() { + PluginManager::UnregisterPlugin(ProcessKDP::CreateInstance); +} + +lldb::ProcessSP ProcessKDP::CreateInstance(TargetSP target_sp, + ListenerSP listener_sp, + const FileSpec *crash_file_path) { + lldb::ProcessSP process_sp; + if (crash_file_path == NULL) + process_sp = std::make_shared<ProcessKDP>(target_sp, listener_sp); + return process_sp; +} + +bool ProcessKDP::CanDebug(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) { + const llvm::Triple &triple_ref = target_sp->GetArchitecture().GetTriple(); + switch (triple_ref.getOS()) { + case llvm::Triple::Darwin: // Should use "macosx" for desktop and "ios" for + // iOS, but accept darwin just in case + case llvm::Triple::MacOSX: // For desktop targets + case llvm::Triple::IOS: // For arm targets + case llvm::Triple::TvOS: + case llvm::Triple::WatchOS: + if (triple_ref.getVendor() == llvm::Triple::Apple) { + ObjectFile *exe_objfile = exe_module->GetObjectFile(); + if (exe_objfile->GetType() == ObjectFile::eTypeExecutable && + exe_objfile->GetStrata() == ObjectFile::eStrataKernel) + return true; + } + break; + + default: + break; + } + } + return false; +} + +// ProcessKDP constructor +ProcessKDP::ProcessKDP(TargetSP target_sp, ListenerSP listener_sp) + : Process(target_sp, listener_sp), + m_comm("lldb.process.kdp-remote.communication"), + m_async_broadcaster(NULL, "lldb.process.kdp-remote.async-broadcaster"), + m_dyld_plugin_name(), m_kernel_load_addr(LLDB_INVALID_ADDRESS), + m_command_sp(), m_kernel_thread_wp() { + m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit, + "async thread should exit"); + m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue, + "async thread continue"); + const uint64_t timeout_seconds = + GetGlobalPluginProperties()->GetPacketTimeout(); + if (timeout_seconds > 0) + m_comm.SetPacketTimeout(std::chrono::seconds(timeout_seconds)); +} + +// Destructor +ProcessKDP::~ProcessKDP() { + 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(); +} + +// PluginInterface +lldb_private::ConstString ProcessKDP::GetPluginName() { + return GetPluginNameStatic(); +} + +uint32_t ProcessKDP::GetPluginVersion() { return 1; } + +Status ProcessKDP::WillLaunch(Module *module) { + Status error; + error.SetErrorString("launching not supported in kdp-remote plug-in"); + return error; +} + +Status ProcessKDP::WillAttachToProcessWithID(lldb::pid_t pid) { + Status error; + error.SetErrorString( + "attaching to a by process ID not supported in kdp-remote plug-in"); + return error; +} + +Status ProcessKDP::WillAttachToProcessWithName(const char *process_name, + bool wait_for_launch) { + Status error; + error.SetErrorString( + "attaching to a by process name not supported in kdp-remote plug-in"); + return error; +} + +bool ProcessKDP::GetHostArchitecture(ArchSpec &arch) { + uint32_t cpu = m_comm.GetCPUType(); + if (cpu) { + uint32_t sub = m_comm.GetCPUSubtype(); + arch.SetArchitecture(eArchTypeMachO, cpu, sub); + // Leave architecture vendor as unspecified unknown + arch.GetTriple().setVendor(llvm::Triple::UnknownVendor); + arch.GetTriple().setVendorName(llvm::StringRef()); + return true; + } + arch.Clear(); + return false; +} + +Status ProcessKDP::DoConnectRemote(Stream *strm, llvm::StringRef remote_url) { + Status error; + + // Don't let any JIT happen when doing KDP as we can't allocate memory and we + // don't want to be mucking with threads that might already be handling + // exceptions + SetCanJIT(false); + + if (remote_url.empty()) { + error.SetErrorStringWithFormat("empty connection URL"); + return error; + } + + std::unique_ptr<ConnectionFileDescriptor> conn_up( + new ConnectionFileDescriptor()); + if (conn_up) { + // Only try once for now. + // TODO: check if we should be retrying? + const uint32_t max_retry_count = 1; + for (uint32_t retry_count = 0; retry_count < max_retry_count; + ++retry_count) { + if (conn_up->Connect(remote_url, &error) == eConnectionStatusSuccess) + break; + usleep(100000); + } + } + + if (conn_up->IsConnected()) { + const TCPSocket &socket = + static_cast<const TCPSocket &>(*conn_up->GetReadObject()); + const uint16_t reply_port = socket.GetLocalPortNumber(); + + if (reply_port != 0) { + m_comm.SetConnection(conn_up.release()); + + if (m_comm.SendRequestReattach(reply_port)) { + if (m_comm.SendRequestConnect(reply_port, reply_port, + "Greetings from LLDB...")) { + m_comm.GetVersion(); + + Target &target = GetTarget(); + ArchSpec kernel_arch; + // The host architecture + GetHostArchitecture(kernel_arch); + ArchSpec target_arch = target.GetArchitecture(); + // Merge in any unspecified stuff into the target architecture in + // case the target arch isn't set at all or incompletely. + target_arch.MergeFrom(kernel_arch); + target.SetArchitecture(target_arch); + + /* Get the kernel's UUID and load address via KDP_KERNELVERSION + * packet. */ + /* An EFI kdp session has neither UUID nor load address. */ + + UUID kernel_uuid = m_comm.GetUUID(); + addr_t kernel_load_addr = m_comm.GetLoadAddress(); + + if (m_comm.RemoteIsEFI()) { + // Select an invalid plugin name for the dynamic loader so one + // doesn't get used since EFI does its own manual loading via + // python scripting + static ConstString g_none_dynamic_loader("none"); + m_dyld_plugin_name = g_none_dynamic_loader; + + if (kernel_uuid.IsValid()) { + // If EFI passed in a UUID= try to lookup UUID The slide will not + // be provided. But the UUID lookup will be used to launch EFI + // debug scripts from the dSYM, that can load all of the symbols. + ModuleSpec module_spec; + module_spec.GetUUID() = kernel_uuid; + module_spec.GetArchitecture() = target.GetArchitecture(); + + // Lookup UUID locally, before attempting dsymForUUID like action + FileSpecList search_paths = + Target::GetDefaultDebugFileSearchPaths(); + module_spec.GetSymbolFileSpec() = + Symbols::LocateExecutableSymbolFile(module_spec, + search_paths); + if (module_spec.GetSymbolFileSpec()) { + ModuleSpec executable_module_spec = + Symbols::LocateExecutableObjectFile(module_spec); + if (FileSystem::Instance().Exists( + executable_module_spec.GetFileSpec())) { + module_spec.GetFileSpec() = + executable_module_spec.GetFileSpec(); + } + } + if (!module_spec.GetSymbolFileSpec() || + !module_spec.GetSymbolFileSpec()) + Symbols::DownloadObjectAndSymbolFile(module_spec, true); + + if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) { + ModuleSP module_sp(new Module(module_spec)); + if (module_sp.get() && module_sp->GetObjectFile()) { + // Get the current target executable + ModuleSP exe_module_sp(target.GetExecutableModule()); + + // Make sure you don't already have the right module loaded + // and they will be uniqued + if (exe_module_sp.get() != module_sp.get()) + target.SetExecutableModule(module_sp, eLoadDependentsNo); + } + } + } + } else if (m_comm.RemoteIsDarwinKernel()) { + m_dyld_plugin_name = + DynamicLoaderDarwinKernel::GetPluginNameStatic(); + if (kernel_load_addr != LLDB_INVALID_ADDRESS) { + m_kernel_load_addr = kernel_load_addr; + } + } + + // Set the thread ID + UpdateThreadListIfNeeded(); + SetID(1); + GetThreadList(); + SetPrivateState(eStateStopped); + StreamSP async_strm_sp(target.GetDebugger().GetAsyncOutputStream()); + if (async_strm_sp) { + const char *cstr; + if ((cstr = m_comm.GetKernelVersion()) != NULL) { + async_strm_sp->Printf("Version: %s\n", cstr); + async_strm_sp->Flush(); + } + // if ((cstr = m_comm.GetImagePath ()) != NULL) + // { + // async_strm_sp->Printf ("Image Path: + // %s\n", cstr); + // async_strm_sp->Flush(); + // } + } + } else { + error.SetErrorString("KDP_REATTACH failed"); + } + } else { + error.SetErrorString("KDP_REATTACH failed"); + } + } else { + error.SetErrorString("invalid reply port from UDP connection"); + } + } else { + if (error.Success()) + error.SetErrorStringWithFormat("failed to connect to '%s'", + remote_url.str().c_str()); + } + if (error.Fail()) + m_comm.Disconnect(); + + return error; +} + +// Process Control +Status ProcessKDP::DoLaunch(Module *exe_module, + ProcessLaunchInfo &launch_info) { + Status error; + error.SetErrorString("launching not supported in kdp-remote plug-in"); + return error; +} + +Status +ProcessKDP::DoAttachToProcessWithID(lldb::pid_t attach_pid, + const ProcessAttachInfo &attach_info) { + Status error; + error.SetErrorString( + "attach to process by ID is not supported in kdp remote debugging"); + return error; +} + +Status +ProcessKDP::DoAttachToProcessWithName(const char *process_name, + const ProcessAttachInfo &attach_info) { + Status error; + error.SetErrorString( + "attach to process by name is not supported in kdp remote debugging"); + return error; +} + +void ProcessKDP::DidAttach(ArchSpec &process_arch) { + Process::DidAttach(process_arch); + + Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS)); + LLDB_LOGF(log, "ProcessKDP::DidAttach()"); + if (GetID() != LLDB_INVALID_PROCESS_ID) { + GetHostArchitecture(process_arch); + } +} + +addr_t ProcessKDP::GetImageInfoAddress() { return m_kernel_load_addr; } + +lldb_private::DynamicLoader *ProcessKDP::GetDynamicLoader() { + if (m_dyld_up.get() == NULL) + m_dyld_up.reset(DynamicLoader::FindPlugin( + this, + m_dyld_plugin_name.IsEmpty() ? NULL : m_dyld_plugin_name.GetCString())); + return m_dyld_up.get(); +} + +Status ProcessKDP::WillResume() { return Status(); } + +Status ProcessKDP::DoResume() { + Status error; + Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS)); + // Only start the async thread if we try to do any process control + if (!m_async_thread.IsJoinable()) + StartAsyncThread(); + + bool resume = false; + + // With KDP there is only one thread we can tell what to do + ThreadSP kernel_thread_sp(m_thread_list.FindThreadByProtocolID(g_kernel_tid)); + + if (kernel_thread_sp) { + const StateType thread_resume_state = + kernel_thread_sp->GetTemporaryResumeState(); + + LLDB_LOGF(log, "ProcessKDP::DoResume() thread_resume_state = %s", + StateAsCString(thread_resume_state)); + switch (thread_resume_state) { + case eStateSuspended: + // Nothing to do here when a thread will stay suspended we just leave the + // CPU mask bit set to zero for the thread + LLDB_LOGF(log, "ProcessKDP::DoResume() = suspended???"); + break; + + case eStateStepping: { + lldb::RegisterContextSP reg_ctx_sp( + kernel_thread_sp->GetRegisterContext()); + + if (reg_ctx_sp) { + LLDB_LOGF( + log, + "ProcessKDP::DoResume () reg_ctx_sp->HardwareSingleStep (true);"); + reg_ctx_sp->HardwareSingleStep(true); + resume = true; + } else { + error.SetErrorStringWithFormat( + "KDP thread 0x%llx has no register context", + kernel_thread_sp->GetID()); + } + } break; + + case eStateRunning: { + lldb::RegisterContextSP reg_ctx_sp( + kernel_thread_sp->GetRegisterContext()); + + if (reg_ctx_sp) { + LLDB_LOGF(log, "ProcessKDP::DoResume () reg_ctx_sp->HardwareSingleStep " + "(false);"); + reg_ctx_sp->HardwareSingleStep(false); + resume = true; + } else { + error.SetErrorStringWithFormat( + "KDP thread 0x%llx has no register context", + kernel_thread_sp->GetID()); + } + } break; + + default: + // The only valid thread resume states are listed above + llvm_unreachable("invalid thread resume state"); + } + } + + if (resume) { + LLDB_LOGF(log, "ProcessKDP::DoResume () sending resume"); + + if (m_comm.SendRequestResume()) { + m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue); + SetPrivateState(eStateRunning); + } else + error.SetErrorString("KDP resume failed"); + } else { + error.SetErrorString("kernel thread is suspended"); + } + + return error; +} + +lldb::ThreadSP ProcessKDP::GetKernelThread() { + // KDP only tells us about one thread/core. Any other threads will usually + // be the ones that are read from memory by the OS plug-ins. + + ThreadSP thread_sp(m_kernel_thread_wp.lock()); + if (!thread_sp) { + thread_sp = std::make_shared<ThreadKDP>(*this, g_kernel_tid); + m_kernel_thread_wp = thread_sp; + } + return thread_sp; +} + +bool ProcessKDP::UpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) { + // locker will keep a mutex locked until it goes out of scope + Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_THREAD)); + LLDB_LOGV(log, "pid = {0}", GetID()); + + // Even though there is a CPU mask, it doesn't mean we can see each CPU + // individually, there is really only one. Lets call this thread 1. + ThreadSP thread_sp( + old_thread_list.FindThreadByProtocolID(g_kernel_tid, false)); + if (!thread_sp) + thread_sp = GetKernelThread(); + new_thread_list.AddThread(thread_sp); + + return new_thread_list.GetSize(false) > 0; +} + +void ProcessKDP::RefreshStateAfterStop() { + // Let all threads recover from stopping and do any clean up based on the + // previous thread state (if any). + m_thread_list.RefreshStateAfterStop(); +} + +Status ProcessKDP::DoHalt(bool &caused_stop) { + Status error; + + if (m_comm.IsRunning()) { + if (m_destroy_in_process) { + // If we are attempting to destroy, we need to not return an error to Halt + // or DoDestroy won't get called. We are also currently running, so send + // a process stopped event + SetPrivateState(eStateStopped); + } else { + error.SetErrorString("KDP cannot interrupt a running kernel"); + } + } + return error; +} + +Status ProcessKDP::DoDetach(bool keep_stopped) { + Status error; + Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS)); + LLDB_LOGF(log, "ProcessKDP::DoDetach(keep_stopped = %i)", keep_stopped); + + if (m_comm.IsRunning()) { + // We are running and we can't interrupt a running kernel, so we need to + // just close the connection to the kernel and hope for the best + } else { + // If we are going to keep the target stopped, then don't send the + // disconnect message. + if (!keep_stopped && m_comm.IsConnected()) { + const bool success = m_comm.SendRequestDisconnect(); + if (log) { + if (success) + log->PutCString( + "ProcessKDP::DoDetach() detach packet sent successfully"); + else + log->PutCString( + "ProcessKDP::DoDetach() connection channel shutdown failed"); + } + m_comm.Disconnect(); + } + } + StopAsyncThread(); + m_comm.Clear(); + + SetPrivateState(eStateDetached); + ResumePrivateStateThread(); + + // KillDebugserverProcess (); + return error; +} + +Status ProcessKDP::DoDestroy() { + // For KDP there really is no difference between destroy and detach + bool keep_stopped = false; + return DoDetach(keep_stopped); +} + +// Process Queries + +bool ProcessKDP::IsAlive() { + return m_comm.IsConnected() && Process::IsAlive(); +} + +// Process Memory +size_t ProcessKDP::DoReadMemory(addr_t addr, void *buf, size_t size, + Status &error) { + uint8_t *data_buffer = (uint8_t *)buf; + if (m_comm.IsConnected()) { + const size_t max_read_size = 512; + size_t total_bytes_read = 0; + + // Read the requested amount of memory in 512 byte chunks + while (total_bytes_read < size) { + size_t bytes_to_read_this_request = size - total_bytes_read; + if (bytes_to_read_this_request > max_read_size) { + bytes_to_read_this_request = max_read_size; + } + size_t bytes_read = m_comm.SendRequestReadMemory( + addr + total_bytes_read, data_buffer + total_bytes_read, + bytes_to_read_this_request, error); + total_bytes_read += bytes_read; + if (error.Fail() || bytes_read == 0) { + return total_bytes_read; + } + } + + return total_bytes_read; + } + error.SetErrorString("not connected"); + return 0; +} + +size_t ProcessKDP::DoWriteMemory(addr_t addr, const void *buf, size_t size, + Status &error) { + if (m_comm.IsConnected()) + return m_comm.SendRequestWriteMemory(addr, buf, size, error); + error.SetErrorString("not connected"); + return 0; +} + +lldb::addr_t ProcessKDP::DoAllocateMemory(size_t size, uint32_t permissions, + Status &error) { + error.SetErrorString( + "memory allocation not supported in kdp remote debugging"); + return LLDB_INVALID_ADDRESS; +} + +Status ProcessKDP::DoDeallocateMemory(lldb::addr_t addr) { + Status error; + error.SetErrorString( + "memory deallocation not supported in kdp remote debugging"); + return error; +} + +Status ProcessKDP::EnableBreakpointSite(BreakpointSite *bp_site) { + if (m_comm.LocalBreakpointsAreSupported()) { + Status error; + if (!bp_site->IsEnabled()) { + if (m_comm.SendRequestBreakpoint(true, bp_site->GetLoadAddress())) { + bp_site->SetEnabled(true); + bp_site->SetType(BreakpointSite::eExternal); + } else { + error.SetErrorString("KDP set breakpoint failed"); + } + } + return error; + } + return EnableSoftwareBreakpoint(bp_site); +} + +Status ProcessKDP::DisableBreakpointSite(BreakpointSite *bp_site) { + if (m_comm.LocalBreakpointsAreSupported()) { + Status error; + if (bp_site->IsEnabled()) { + BreakpointSite::Type bp_type = bp_site->GetType(); + if (bp_type == BreakpointSite::eExternal) { + if (m_destroy_in_process && m_comm.IsRunning()) { + // We are trying to destroy our connection and we are running + bp_site->SetEnabled(false); + } else { + if (m_comm.SendRequestBreakpoint(false, bp_site->GetLoadAddress())) + bp_site->SetEnabled(false); + else + error.SetErrorString("KDP remove breakpoint failed"); + } + } else { + error = DisableSoftwareBreakpoint(bp_site); + } + } + return error; + } + return DisableSoftwareBreakpoint(bp_site); +} + +Status ProcessKDP::EnableWatchpoint(Watchpoint *wp, bool notify) { + Status error; + error.SetErrorString( + "watchpoints are not supported in kdp remote debugging"); + return error; +} + +Status ProcessKDP::DisableWatchpoint(Watchpoint *wp, bool notify) { + Status error; + error.SetErrorString( + "watchpoints are not supported in kdp remote debugging"); + return error; +} + +void ProcessKDP::Clear() { m_thread_list.Clear(); } + +Status ProcessKDP::DoSignal(int signo) { + Status error; + error.SetErrorString( + "sending signals is not supported in kdp remote debugging"); + return error; +} + +void ProcessKDP::Initialize() { + static llvm::once_flag g_once_flag; + + llvm::call_once(g_once_flag, []() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance, + DebuggerInitialize); + + ProcessKDPLog::Initialize(); + }); +} + +void ProcessKDP::DebuggerInitialize(lldb_private::Debugger &debugger) { + if (!PluginManager::GetSettingForProcessPlugin( + debugger, PluginProperties::GetSettingName())) { + const bool is_global_setting = true; + PluginManager::CreateSettingForProcessPlugin( + debugger, GetGlobalPluginProperties()->GetValueProperties(), + ConstString("Properties for the kdp-remote process plug-in."), + is_global_setting); + } +} + +bool ProcessKDP::StartAsyncThread() { + Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS)); + + LLDB_LOGF(log, "ProcessKDP::StartAsyncThread ()"); + + if (m_async_thread.IsJoinable()) + return true; + + llvm::Expected<HostThread> async_thread = ThreadLauncher::LaunchThread( + "<lldb.process.kdp-remote.async>", ProcessKDP::AsyncThread, this); + if (!async_thread) { + LLDB_LOG(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST), + "failed to launch host thread: {}", + llvm::toString(async_thread.takeError())); + return false; + } + m_async_thread = *async_thread; + return m_async_thread.IsJoinable(); +} + +void ProcessKDP::StopAsyncThread() { + Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS)); + + LLDB_LOGF(log, "ProcessKDP::StopAsyncThread ()"); + + m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncThreadShouldExit); + + // Stop the stdio thread + if (m_async_thread.IsJoinable()) + m_async_thread.Join(nullptr); +} + +void *ProcessKDP::AsyncThread(void *arg) { + ProcessKDP *process = (ProcessKDP *)arg; + + const lldb::pid_t pid = process->GetID(); + + Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS)); + LLDB_LOGF(log, + "ProcessKDP::AsyncThread (arg = %p, pid = %" PRIu64 + ") thread starting...", + arg, pid); + + ListenerSP listener_sp(Listener::MakeListener("ProcessKDP::AsyncThread")); + EventSP event_sp; + const uint32_t desired_event_mask = + eBroadcastBitAsyncContinue | eBroadcastBitAsyncThreadShouldExit; + + if (listener_sp->StartListeningForEvents(&process->m_async_broadcaster, + desired_event_mask) == + desired_event_mask) { + bool done = false; + while (!done) { + LLDB_LOGF(log, + "ProcessKDP::AsyncThread (pid = %" PRIu64 + ") listener.WaitForEvent (NULL, event_sp)...", + pid); + if (listener_sp->GetEvent(event_sp, llvm::None)) { + uint32_t event_type = event_sp->GetType(); + LLDB_LOGF(log, + "ProcessKDP::AsyncThread (pid = %" PRIu64 + ") Got an event of type: %d...", + pid, event_type); + + // When we are running, poll for 1 second to try and get an exception + // to indicate the process has stopped. If we don't get one, check to + // make sure no one asked us to exit + bool is_running = false; + DataExtractor exc_reply_packet; + do { + switch (event_type) { + case eBroadcastBitAsyncContinue: { + is_running = true; + if (process->m_comm.WaitForPacketWithTimeoutMicroSeconds( + exc_reply_packet, 1 * USEC_PER_SEC)) { + ThreadSP thread_sp(process->GetKernelThread()); + if (thread_sp) { + lldb::RegisterContextSP reg_ctx_sp( + thread_sp->GetRegisterContext()); + if (reg_ctx_sp) + reg_ctx_sp->InvalidateAllRegisters(); + static_cast<ThreadKDP *>(thread_sp.get()) + ->SetStopInfoFrom_KDP_EXCEPTION(exc_reply_packet); + } + + // TODO: parse the stop reply packet + is_running = false; + process->SetPrivateState(eStateStopped); + } else { + // Check to see if we are supposed to exit. There is no way to + // interrupt a running kernel, so all we can do is wait for an + // exception or detach... + if (listener_sp->GetEvent(event_sp, + std::chrono::microseconds(0))) { + // We got an event, go through the loop again + event_type = event_sp->GetType(); + } + } + } break; + + case eBroadcastBitAsyncThreadShouldExit: + LLDB_LOGF(log, + "ProcessKDP::AsyncThread (pid = %" PRIu64 + ") got eBroadcastBitAsyncThreadShouldExit...", + pid); + done = true; + is_running = false; + break; + + default: + LLDB_LOGF(log, + "ProcessKDP::AsyncThread (pid = %" PRIu64 + ") got unknown event 0x%8.8x", + pid, event_type); + done = true; + is_running = false; + break; + } + } while (is_running); + } else { + LLDB_LOGF(log, + "ProcessKDP::AsyncThread (pid = %" PRIu64 + ") listener.WaitForEvent (NULL, event_sp) => false", + pid); + done = true; + } + } + } + + LLDB_LOGF(log, + "ProcessKDP::AsyncThread (arg = %p, pid = %" PRIu64 + ") thread exiting...", + arg, pid); + + process->m_async_thread.Reset(); + return NULL; +} + +class CommandObjectProcessKDPPacketSend : public CommandObjectParsed { +private: + OptionGroupOptions m_option_group; + OptionGroupUInt64 m_command_byte; + OptionGroupString m_packet_data; + + virtual Options *GetOptions() { return &m_option_group; } + +public: + CommandObjectProcessKDPPacketSend(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process plugin packet send", + "Send a custom packet through the KDP protocol by " + "specifying the command byte and the packet " + "payload data. A packet will be sent with a " + "correct header and payload, and the raw result " + "bytes will be displayed as a string value. ", + NULL), + m_option_group(), + m_command_byte(LLDB_OPT_SET_1, true, "command", 'c', 0, eArgTypeNone, + "Specify the command byte to use when sending the KDP " + "request packet.", + 0), + m_packet_data(LLDB_OPT_SET_1, false, "payload", 'p', 0, eArgTypeNone, + "Specify packet payload bytes as a hex ASCII string with " + "no spaces or hex prefixes.", + NULL) { + m_option_group.Append(&m_command_byte, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Append(&m_packet_data, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_option_group.Finalize(); + } + + ~CommandObjectProcessKDPPacketSend() {} + + bool DoExecute(Args &command, CommandReturnObject &result) { + const size_t argc = command.GetArgumentCount(); + if (argc == 0) { + if (!m_command_byte.GetOptionValue().OptionWasSet()) { + result.AppendError( + "the --command option must be set to a valid command byte"); + result.SetStatus(eReturnStatusFailed); + } else { + const uint64_t command_byte = + m_command_byte.GetOptionValue().GetUInt64Value(0); + if (command_byte > 0 && command_byte <= UINT8_MAX) { + ProcessKDP *process = + (ProcessKDP *)m_interpreter.GetExecutionContext().GetProcessPtr(); + if (process) { + const StateType state = process->GetState(); + + if (StateIsStoppedState(state, true)) { + std::vector<uint8_t> payload_bytes; + const char *ascii_hex_bytes_cstr = + m_packet_data.GetOptionValue().GetCurrentValue(); + if (ascii_hex_bytes_cstr && ascii_hex_bytes_cstr[0]) { + StringExtractor extractor(ascii_hex_bytes_cstr); + const size_t ascii_hex_bytes_cstr_len = + extractor.GetStringRef().size(); + if (ascii_hex_bytes_cstr_len & 1) { + result.AppendErrorWithFormat("payload data must contain an " + "even number of ASCII hex " + "characters: '%s'", + ascii_hex_bytes_cstr); + result.SetStatus(eReturnStatusFailed); + return false; + } + payload_bytes.resize(ascii_hex_bytes_cstr_len / 2); + if (extractor.GetHexBytes(payload_bytes, '\xdd') != + payload_bytes.size()) { + result.AppendErrorWithFormat("payload data must only contain " + "ASCII hex characters (no " + "spaces or hex prefixes): '%s'", + ascii_hex_bytes_cstr); + result.SetStatus(eReturnStatusFailed); + return false; + } + } + Status error; + DataExtractor reply; + process->GetCommunication().SendRawRequest( + command_byte, + payload_bytes.empty() ? NULL : payload_bytes.data(), + payload_bytes.size(), reply, error); + + if (error.Success()) { + // Copy the binary bytes into a hex ASCII string for the result + StreamString packet; + packet.PutBytesAsRawHex8( + reply.GetDataStart(), reply.GetByteSize(), + endian::InlHostByteOrder(), endian::InlHostByteOrder()); + result.AppendMessage(packet.GetString()); + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } else { + const char *error_cstr = error.AsCString(); + if (error_cstr && error_cstr[0]) + result.AppendError(error_cstr); + else + result.AppendErrorWithFormat("unknown error 0x%8.8x", + error.GetError()); + result.SetStatus(eReturnStatusFailed); + return false; + } + } else { + result.AppendErrorWithFormat("process must be stopped in order " + "to send KDP packets, state is %s", + StateAsCString(state)); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendError("invalid process"); + result.SetStatus(eReturnStatusFailed); + } + } else { + result.AppendErrorWithFormat("invalid command byte 0x%" PRIx64 + ", valid values are 1 - 255", + command_byte); + result.SetStatus(eReturnStatusFailed); + } + } + } else { + result.AppendErrorWithFormat("'%s' takes no arguments, only options.", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + } + return false; + } +}; + +class CommandObjectProcessKDPPacket : public CommandObjectMultiword { +private: +public: + CommandObjectProcessKDPPacket(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "process plugin packet", + "Commands that deal with KDP remote packets.", + NULL) { + LoadSubCommand( + "send", + CommandObjectSP(new CommandObjectProcessKDPPacketSend(interpreter))); + } + + ~CommandObjectProcessKDPPacket() {} +}; + +class CommandObjectMultiwordProcessKDP : public CommandObjectMultiword { +public: + CommandObjectMultiwordProcessKDP(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "process plugin", + "Commands for operating on a ProcessKDP process.", + "process plugin <subcommand> [<subcommand-options>]") { + LoadSubCommand("packet", CommandObjectSP(new CommandObjectProcessKDPPacket( + interpreter))); + } + + ~CommandObjectMultiwordProcessKDP() {} +}; + +CommandObject *ProcessKDP::GetPluginCommandObject() { + if (!m_command_sp) + m_command_sp = std::make_shared<CommandObjectMultiwordProcessKDP>( + GetTarget().GetDebugger().GetCommandInterpreter()); + return m_command_sp.get(); +} |