diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Plugins/Process/scripted')
4 files changed, 1126 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp b/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp new file mode 100644 index 000000000000..66f861350d14 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp @@ -0,0 +1,540 @@ +//===-- ScriptedProcess.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 "ScriptedProcess.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" + +#include "lldb/Host/OptionParser.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupBoolean.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/Queue.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/ScriptedMetadata.h" +#include "lldb/Utility/State.h" + +#include <mutex> + +LLDB_PLUGIN_DEFINE(ScriptedProcess) + +using namespace lldb; +using namespace lldb_private; + +llvm::StringRef ScriptedProcess::GetPluginDescriptionStatic() { + return "Scripted Process plug-in."; +} + +static constexpr lldb::ScriptLanguage g_supported_script_languages[] = { + ScriptLanguage::eScriptLanguagePython, +}; + +bool ScriptedProcess::IsScriptLanguageSupported(lldb::ScriptLanguage language) { + llvm::ArrayRef<lldb::ScriptLanguage> supported_languages = + llvm::ArrayRef(g_supported_script_languages); + + return llvm::is_contained(supported_languages, language); +} + +lldb::ProcessSP ScriptedProcess::CreateInstance(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const FileSpec *file, + bool can_connect) { + if (!target_sp || + !IsScriptLanguageSupported(target_sp->GetDebugger().GetScriptLanguage())) + return nullptr; + + ScriptedMetadata scripted_metadata(target_sp->GetProcessLaunchInfo()); + + Status error; + auto process_sp = std::shared_ptr<ScriptedProcess>( + new ScriptedProcess(target_sp, listener_sp, scripted_metadata, error)); + + if (error.Fail() || !process_sp || !process_sp->m_interface_up) { + LLDB_LOGF(GetLog(LLDBLog::Process), "%s", error.AsCString()); + return nullptr; + } + + return process_sp; +} + +bool ScriptedProcess::CanDebug(lldb::TargetSP target_sp, + bool plugin_specified_by_name) { + return true; +} + +ScriptedProcess::ScriptedProcess(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const ScriptedMetadata &scripted_metadata, + Status &error) + : Process(target_sp, listener_sp), m_scripted_metadata(scripted_metadata) { + + if (!target_sp) { + error.SetErrorStringWithFormat("ScriptedProcess::%s () - ERROR: %s", + __FUNCTION__, "Invalid target"); + return; + } + + ScriptInterpreter *interpreter = + target_sp->GetDebugger().GetScriptInterpreter(); + + if (!interpreter) { + error.SetErrorStringWithFormat("ScriptedProcess::%s () - ERROR: %s", + __FUNCTION__, + "Debugger has no Script Interpreter"); + return; + } + + // Create process instance interface + m_interface_up = interpreter->CreateScriptedProcessInterface(); + if (!m_interface_up) { + error.SetErrorStringWithFormat( + "ScriptedProcess::%s () - ERROR: %s", __FUNCTION__, + "Script interpreter couldn't create Scripted Process Interface"); + return; + } + + ExecutionContext exe_ctx(target_sp, /*get_process=*/false); + + // Create process script object + auto obj_or_err = GetInterface().CreatePluginObject( + m_scripted_metadata.GetClassName(), exe_ctx, + m_scripted_metadata.GetArgsSP()); + + if (!obj_or_err) { + llvm::consumeError(obj_or_err.takeError()); + error.SetErrorString("Failed to create script object."); + return; + } + + StructuredData::GenericSP object_sp = *obj_or_err; + + if (!object_sp || !object_sp->IsValid()) { + error.SetErrorStringWithFormat("ScriptedProcess::%s () - ERROR: %s", + __FUNCTION__, + "Failed to create valid script object"); + return; + } +} + +ScriptedProcess::~ScriptedProcess() { + Clear(); + // If the interface is not valid, we can't call Finalize(). When that happens + // it means that the Scripted Process instanciation failed and the + // CreateProcess function returns a nullptr, so no one besides this class + // should have access to that bogus process object. + if (!m_interface_up) + return; + // 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 */); +} + +void ScriptedProcess::Initialize() { + static llvm::once_flag g_once_flag; + + llvm::call_once(g_once_flag, []() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance); + }); +} + +void ScriptedProcess::Terminate() { + PluginManager::UnregisterPlugin(ScriptedProcess::CreateInstance); +} + +Status ScriptedProcess::DoLoadCore() { + ProcessLaunchInfo launch_info = GetTarget().GetProcessLaunchInfo(); + + return DoLaunch(nullptr, launch_info); +} + +Status ScriptedProcess::DoLaunch(Module *exe_module, + ProcessLaunchInfo &launch_info) { + LLDB_LOGF(GetLog(LLDBLog::Process), "ScriptedProcess::%s launching process", __FUNCTION__); + + /* MARK: This doesn't reflect how lldb actually launches a process. + In reality, it attaches to debugserver, then resume the process. + That's not true in all cases. If debugserver is remote, lldb + asks debugserver to launch the process for it. */ + Status error = GetInterface().Launch(); + SetPrivateState(eStateStopped); + return error; +} + +void ScriptedProcess::DidLaunch() { m_pid = GetInterface().GetProcessID(); } + +void ScriptedProcess::DidResume() { + // Update the PID again, in case the user provided a placeholder pid at launch + m_pid = GetInterface().GetProcessID(); +} + +Status ScriptedProcess::DoResume() { + LLDB_LOGF(GetLog(LLDBLog::Process), "ScriptedProcess::%s resuming process", __FUNCTION__); + + return GetInterface().Resume(); +} + +Status ScriptedProcess::DoAttach(const ProcessAttachInfo &attach_info) { + Status error = GetInterface().Attach(attach_info); + SetPrivateState(eStateRunning); + SetPrivateState(eStateStopped); + if (error.Fail()) + return error; + // NOTE: We need to set the PID before finishing to attach otherwise we will + // hit an assert when calling the attach completion handler. + DidLaunch(); + + return {}; +} + +Status +ScriptedProcess::DoAttachToProcessWithID(lldb::pid_t pid, + const ProcessAttachInfo &attach_info) { + return DoAttach(attach_info); +} + +Status ScriptedProcess::DoAttachToProcessWithName( + const char *process_name, const ProcessAttachInfo &attach_info) { + return DoAttach(attach_info); +} + +void ScriptedProcess::DidAttach(ArchSpec &process_arch) { + process_arch = GetArchitecture(); +} + +Status ScriptedProcess::DoDestroy() { return Status(); } + +bool ScriptedProcess::IsAlive() { return GetInterface().IsAlive(); } + +size_t ScriptedProcess::DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) { + lldb::DataExtractorSP data_extractor_sp = + GetInterface().ReadMemoryAtAddress(addr, size, error); + + if (!data_extractor_sp || !data_extractor_sp->GetByteSize() || error.Fail()) + return 0; + + offset_t bytes_copied = data_extractor_sp->CopyByteOrderedData( + 0, data_extractor_sp->GetByteSize(), buf, size, GetByteOrder()); + + if (!bytes_copied || bytes_copied == LLDB_INVALID_OFFSET) + return ScriptedInterface::ErrorWithMessage<size_t>( + LLVM_PRETTY_FUNCTION, "Failed to copy read memory to buffer.", error); + + // FIXME: We should use the diagnostic system to report a warning if the + // `bytes_copied` is different from `size`. + + return bytes_copied; +} + +size_t ScriptedProcess::DoWriteMemory(lldb::addr_t vm_addr, const void *buf, + size_t size, Status &error) { + lldb::DataExtractorSP data_extractor_sp = std::make_shared<DataExtractor>( + buf, size, GetByteOrder(), GetAddressByteSize()); + + if (!data_extractor_sp || !data_extractor_sp->GetByteSize()) + return 0; + + lldb::offset_t bytes_written = + GetInterface().WriteMemoryAtAddress(vm_addr, data_extractor_sp, error); + + if (!bytes_written || bytes_written == LLDB_INVALID_OFFSET) + return ScriptedInterface::ErrorWithMessage<size_t>( + LLVM_PRETTY_FUNCTION, "Failed to copy write buffer to memory.", error); + + // FIXME: We should use the diagnostic system to report a warning if the + // `bytes_written` is different from `size`. + + return bytes_written; +} + +Status ScriptedProcess::EnableBreakpointSite(BreakpointSite *bp_site) { + assert(bp_site != nullptr); + + if (bp_site->IsEnabled()) { + return {}; + } + + if (bp_site->HardwareRequired()) { + return Status("Scripted Processes don't support hardware breakpoints"); + } + + Status error; + GetInterface().CreateBreakpoint(bp_site->GetLoadAddress(), error); + + return error; +} + +ArchSpec ScriptedProcess::GetArchitecture() { + return GetTarget().GetArchitecture(); +} + +Status ScriptedProcess::DoGetMemoryRegionInfo(lldb::addr_t load_addr, + MemoryRegionInfo ®ion) { + Status error; + if (auto region_or_err = + GetInterface().GetMemoryRegionContainingAddress(load_addr, error)) + region = *region_or_err; + + return error; +} + +Status ScriptedProcess::GetMemoryRegions(MemoryRegionInfos ®ion_list) { + Status error; + lldb::addr_t address = 0; + + while (auto region_or_err = + GetInterface().GetMemoryRegionContainingAddress(address, error)) { + if (error.Fail()) + break; + + MemoryRegionInfo &mem_region = *region_or_err; + auto range = mem_region.GetRange(); + address += range.GetRangeBase() + range.GetByteSize(); + region_list.push_back(mem_region); + } + + return error; +} + +void ScriptedProcess::Clear() { Process::m_thread_list.Clear(); } + +bool ScriptedProcess::DoUpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) { + // TODO: Implement + // This is supposed to get the current set of threads, if any of them are in + // old_thread_list then they get copied to new_thread_list, and then any + // actually new threads will get added to new_thread_list. + m_thread_plans.ClearThreadCache(); + + Status error; + StructuredData::DictionarySP thread_info_sp = GetInterface().GetThreadsInfo(); + + if (!thread_info_sp) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, + "Couldn't fetch thread list from Scripted Process.", error); + + // Because `StructuredData::Dictionary` uses a `std::map<ConstString, + // ObjectSP>` for storage, each item is sorted based on the key alphabetical + // order. Since `GetThreadsInfo` provides thread indices as the key element, + // thread info comes ordered alphabetically, instead of numerically, so we + // need to sort the thread indices before creating thread. + + StructuredData::ArraySP keys = thread_info_sp->GetKeys(); + + std::map<size_t, StructuredData::ObjectSP> sorted_threads; + auto sort_keys = [&sorted_threads, + &thread_info_sp](StructuredData::Object *item) -> bool { + if (!item) + return false; + + llvm::StringRef key = item->GetStringValue(); + size_t idx = 0; + + // Make sure the provided index is actually an integer + if (!llvm::to_integer(key, idx)) + return false; + + sorted_threads[idx] = thread_info_sp->GetValueForKey(key); + return true; + }; + + size_t thread_count = thread_info_sp->GetSize(); + + if (!keys->ForEach(sort_keys) || sorted_threads.size() != thread_count) + // Might be worth showing the unsorted thread list instead of return early. + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, "Couldn't sort thread list.", error); + + auto create_scripted_thread = + [this, &error, &new_thread_list]( + const std::pair<size_t, StructuredData::ObjectSP> pair) -> bool { + size_t idx = pair.first; + StructuredData::ObjectSP object_sp = pair.second; + + if (!object_sp) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, "Invalid thread info object", error); + + auto thread_or_error = + ScriptedThread::Create(*this, object_sp->GetAsGeneric()); + + if (!thread_or_error) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, toString(thread_or_error.takeError()), error); + + ThreadSP thread_sp = thread_or_error.get(); + lldbassert(thread_sp && "Couldn't initialize scripted thread."); + + RegisterContextSP reg_ctx_sp = thread_sp->GetRegisterContext(); + if (!reg_ctx_sp) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, + llvm::Twine("Invalid Register Context for thread " + llvm::Twine(idx)) + .str(), + error); + + new_thread_list.AddThread(thread_sp); + + return true; + }; + + llvm::for_each(sorted_threads, create_scripted_thread); + + return new_thread_list.GetSize(false) > 0; +} + +void ScriptedProcess::RefreshStateAfterStop() { + // Let all threads recover from stopping and do any clean up based on the + // previous thread state (if any). + m_thread_list.RefreshStateAfterStop(); +} + +bool ScriptedProcess::GetProcessInfo(ProcessInstanceInfo &info) { + info.Clear(); + info.SetProcessID(GetID()); + info.SetArchitecture(GetArchitecture()); + lldb::ModuleSP module_sp = GetTarget().GetExecutableModule(); + if (module_sp) { + const bool add_exe_file_as_first_arg = false; + info.SetExecutableFile(GetTarget().GetExecutableModule()->GetFileSpec(), + add_exe_file_as_first_arg); + } + return true; +} + +lldb_private::StructuredData::ObjectSP +ScriptedProcess::GetLoadedDynamicLibrariesInfos() { + Status error; + auto error_with_message = [&error](llvm::StringRef message) { + return ScriptedInterface::ErrorWithMessage<bool>(LLVM_PRETTY_FUNCTION, + message.data(), error); + }; + + StructuredData::ArraySP loaded_images_sp = GetInterface().GetLoadedImages(); + + if (!loaded_images_sp || !loaded_images_sp->GetSize()) + return ScriptedInterface::ErrorWithMessage<StructuredData::ObjectSP>( + LLVM_PRETTY_FUNCTION, "No loaded images.", error); + + ModuleList module_list; + Target &target = GetTarget(); + + auto reload_image = [&target, &module_list, &error_with_message]( + StructuredData::Object *obj) -> bool { + StructuredData::Dictionary *dict = obj->GetAsDictionary(); + + if (!dict) + return error_with_message("Couldn't cast image object into dictionary."); + + ModuleSpec module_spec; + llvm::StringRef value; + + bool has_path = dict->HasKey("path"); + bool has_uuid = dict->HasKey("uuid"); + if (!has_path && !has_uuid) + return error_with_message("Dictionary should have key 'path' or 'uuid'"); + if (!dict->HasKey("load_addr")) + return error_with_message("Dictionary is missing key 'load_addr'"); + + if (has_path) { + dict->GetValueForKeyAsString("path", value); + module_spec.GetFileSpec().SetPath(value); + } + + if (has_uuid) { + dict->GetValueForKeyAsString("uuid", value); + module_spec.GetUUID().SetFromStringRef(value); + } + module_spec.GetArchitecture() = target.GetArchitecture(); + + ModuleSP module_sp = + target.GetOrCreateModule(module_spec, true /* notify */); + + if (!module_sp) + return error_with_message("Couldn't create or get module."); + + lldb::addr_t load_addr = LLDB_INVALID_ADDRESS; + lldb::offset_t slide = LLDB_INVALID_OFFSET; + dict->GetValueForKeyAsInteger("load_addr", load_addr); + dict->GetValueForKeyAsInteger("slide", slide); + if (load_addr == LLDB_INVALID_ADDRESS) + return error_with_message( + "Couldn't get valid load address or slide offset."); + + if (slide != LLDB_INVALID_OFFSET) + load_addr += slide; + + bool changed = false; + module_sp->SetLoadAddress(target, load_addr, false /*=value_is_offset*/, + changed); + + if (!changed && !module_sp->GetObjectFile()) + return error_with_message("Couldn't set the load address for module."); + + dict->GetValueForKeyAsString("path", value); + FileSpec objfile(value); + module_sp->SetFileSpecAndObjectName(objfile, objfile.GetFilename()); + + return module_list.AppendIfNeeded(module_sp); + }; + + if (!loaded_images_sp->ForEach(reload_image)) + return ScriptedInterface::ErrorWithMessage<StructuredData::ObjectSP>( + LLVM_PRETTY_FUNCTION, "Couldn't reload all images.", error); + + target.ModulesDidLoad(module_list); + + return loaded_images_sp; +} + +lldb_private::StructuredData::DictionarySP ScriptedProcess::GetMetadata() { + StructuredData::DictionarySP metadata_sp = GetInterface().GetMetadata(); + + Status error; + if (!metadata_sp || !metadata_sp->GetSize()) + return ScriptedInterface::ErrorWithMessage<StructuredData::DictionarySP>( + LLVM_PRETTY_FUNCTION, "No metadata.", error); + + return metadata_sp; +} + +void ScriptedProcess::UpdateQueueListIfNeeded() { + CheckScriptedInterface(); + for (ThreadSP thread_sp : Threads()) { + if (const char *queue_name = thread_sp->GetQueueName()) { + QueueSP queue_sp = std::make_shared<Queue>( + m_process->shared_from_this(), thread_sp->GetQueueID(), queue_name); + m_queue_list.AddQueue(queue_sp); + } + } +} + +ScriptedProcessInterface &ScriptedProcess::GetInterface() const { + CheckScriptedInterface(); + return *m_interface_up; +} + +void *ScriptedProcess::GetImplementation() { + StructuredData::GenericSP object_instance_sp = + GetInterface().GetScriptObjectInstance(); + if (object_instance_sp && + object_instance_sp->GetType() == eStructuredDataTypeGeneric) + return object_instance_sp->GetAsGeneric()->GetValue(); + return nullptr; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedProcess.h b/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedProcess.h new file mode 100644 index 000000000000..0335364b4010 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedProcess.h @@ -0,0 +1,136 @@ +//===-- ScriptedProcess.h ------------------------------------- -*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_SCRIPTED_PROCESS_H +#define LLDB_SOURCE_PLUGINS_SCRIPTED_PROCESS_H + +#include "lldb/Target/Process.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/ScriptedMetadata.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/Status.h" + +#include "ScriptedThread.h" + +#include <mutex> + +namespace lldb_private { +class ScriptedProcess : public Process { +public: + static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const FileSpec *crash_file_path, + bool can_connect); + + static void Initialize(); + + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "ScriptedProcess"; } + + static llvm::StringRef GetPluginDescriptionStatic(); + + ~ScriptedProcess() override; + + bool CanDebug(lldb::TargetSP target_sp, + bool plugin_specified_by_name) override; + + DynamicLoader *GetDynamicLoader() override { return nullptr; } + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + Status DoLoadCore() override; + + Status DoLaunch(Module *exe_module, ProcessLaunchInfo &launch_info) override; + + void DidLaunch() override; + + void DidResume() override; + + Status DoResume() override; + + Status DoAttachToProcessWithID(lldb::pid_t pid, + const ProcessAttachInfo &attach_info) override; + + Status + DoAttachToProcessWithName(const char *process_name, + const ProcessAttachInfo &attach_info) override; + + void DidAttach(ArchSpec &process_arch) override; + + Status DoDestroy() override; + + void RefreshStateAfterStop() override; + + bool IsAlive() override; + + size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) override; + + size_t DoWriteMemory(lldb::addr_t vm_addr, const void *buf, size_t size, + Status &error) override; + + Status EnableBreakpointSite(BreakpointSite *bp_site) override; + + ArchSpec GetArchitecture(); + + Status + GetMemoryRegions(lldb_private::MemoryRegionInfos ®ion_list) override; + + bool GetProcessInfo(ProcessInstanceInfo &info) override; + + lldb_private::StructuredData::ObjectSP + GetLoadedDynamicLibrariesInfos() override; + + lldb_private::StructuredData::DictionarySP GetMetadata() override; + + void UpdateQueueListIfNeeded() override; + + void *GetImplementation() override; + + void ForceScriptedState(lldb::StateType state) override { + // If we're about to stop, we should fetch the loaded dynamic libraries + // dictionary before emitting the private stop event to avoid having the + // module loading happen while the process state is changing. + if (StateIsStoppedState(state, true)) + GetLoadedDynamicLibrariesInfos(); + SetPrivateState(state); + } + +protected: + ScriptedProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, + const ScriptedMetadata &scripted_metadata, Status &error); + + void Clear(); + + bool DoUpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) override; + + Status DoGetMemoryRegionInfo(lldb::addr_t load_addr, + MemoryRegionInfo &range_info) override; + + Status DoAttach(const ProcessAttachInfo &attach_info); + +private: + friend class ScriptedThread; + + inline void CheckScriptedInterface() const { + lldbassert(m_interface_up && "Invalid scripted process interface."); + } + + ScriptedProcessInterface &GetInterface() const; + static bool IsScriptLanguageSupported(lldb::ScriptLanguage language); + + // Member variables. + const ScriptedMetadata m_scripted_metadata; + lldb::ScriptedProcessInterfaceUP m_interface_up; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_SCRIPTED_PROCESS_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp b/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp new file mode 100644 index 000000000000..88a4ca3b0389 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp @@ -0,0 +1,370 @@ +//===-- ScriptedThread.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 "ScriptedThread.h" + +#include "Plugins/Process/Utility/RegisterContextThreadMemory.h" +#include "Plugins/Process/Utility/StopInfoMachException.h" +#include "lldb/Target/OperatingSystem.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Unwind.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/LLDBLog.h" +#include <memory> +#include <optional> + +using namespace lldb; +using namespace lldb_private; + +void ScriptedThread::CheckInterpreterAndScriptObject() const { + lldbassert(m_script_object_sp && "Invalid Script Object."); + lldbassert(GetInterface() && "Invalid Scripted Thread Interface."); +} + +llvm::Expected<std::shared_ptr<ScriptedThread>> +ScriptedThread::Create(ScriptedProcess &process, + StructuredData::Generic *script_object) { + if (!process.IsValid()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Invalid scripted process."); + + process.CheckScriptedInterface(); + + auto scripted_thread_interface = + process.GetInterface().CreateScriptedThreadInterface(); + if (!scripted_thread_interface) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Failed to create scripted thread interface."); + + llvm::StringRef thread_class_name; + if (!script_object) { + std::optional<std::string> class_name = + process.GetInterface().GetScriptedThreadPluginName(); + if (!class_name || class_name->empty()) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Failed to get scripted thread class name."); + thread_class_name = *class_name; + } + + ExecutionContext exe_ctx(process); + auto obj_or_err = scripted_thread_interface->CreatePluginObject( + thread_class_name, exe_ctx, process.m_scripted_metadata.GetArgsSP(), + script_object); + + if (!obj_or_err) { + llvm::consumeError(obj_or_err.takeError()); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Failed to create script object."); + } + + StructuredData::GenericSP owned_script_object_sp = *obj_or_err; + + if (!owned_script_object_sp->IsValid()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Created script object is invalid."); + + lldb::tid_t tid = scripted_thread_interface->GetThreadID(); + + return std::make_shared<ScriptedThread>(process, scripted_thread_interface, + tid, owned_script_object_sp); +} + +ScriptedThread::ScriptedThread(ScriptedProcess &process, + ScriptedThreadInterfaceSP interface_sp, + lldb::tid_t tid, + StructuredData::GenericSP script_object_sp) + : Thread(process, tid), m_scripted_process(process), + m_scripted_thread_interface_sp(interface_sp), + m_script_object_sp(script_object_sp) {} + +ScriptedThread::~ScriptedThread() { DestroyThread(); } + +const char *ScriptedThread::GetName() { + CheckInterpreterAndScriptObject(); + std::optional<std::string> thread_name = GetInterface()->GetName(); + if (!thread_name) + return nullptr; + return ConstString(thread_name->c_str()).AsCString(); +} + +const char *ScriptedThread::GetQueueName() { + CheckInterpreterAndScriptObject(); + std::optional<std::string> queue_name = GetInterface()->GetQueue(); + if (!queue_name) + return nullptr; + return ConstString(queue_name->c_str()).AsCString(); +} + +void ScriptedThread::WillResume(StateType resume_state) {} + +void ScriptedThread::ClearStackFrames() { Thread::ClearStackFrames(); } + +RegisterContextSP ScriptedThread::GetRegisterContext() { + if (!m_reg_context_sp) + m_reg_context_sp = CreateRegisterContextForFrame(nullptr); + return m_reg_context_sp; +} + +RegisterContextSP +ScriptedThread::CreateRegisterContextForFrame(StackFrame *frame) { + const uint32_t concrete_frame_idx = + frame ? frame->GetConcreteFrameIndex() : 0; + + if (concrete_frame_idx) + return GetUnwinder().CreateRegisterContextForFrame(frame); + + lldb::RegisterContextSP reg_ctx_sp; + Status error; + + std::optional<std::string> reg_data = GetInterface()->GetRegisterContext(); + if (!reg_data) + return ScriptedInterface::ErrorWithMessage<lldb::RegisterContextSP>( + LLVM_PRETTY_FUNCTION, "Failed to get scripted thread registers data.", + error, LLDBLog::Thread); + + DataBufferSP data_sp( + std::make_shared<DataBufferHeap>(reg_data->c_str(), reg_data->size())); + + if (!data_sp->GetByteSize()) + return ScriptedInterface::ErrorWithMessage<lldb::RegisterContextSP>( + LLVM_PRETTY_FUNCTION, "Failed to copy raw registers data.", error, + LLDBLog::Thread); + + std::shared_ptr<RegisterContextMemory> reg_ctx_memory = + std::make_shared<RegisterContextMemory>( + *this, 0, *GetDynamicRegisterInfo(), LLDB_INVALID_ADDRESS); + if (!reg_ctx_memory) + return ScriptedInterface::ErrorWithMessage<lldb::RegisterContextSP>( + LLVM_PRETTY_FUNCTION, "Failed to create a register context.", error, + LLDBLog::Thread); + + reg_ctx_memory->SetAllRegisterData(data_sp); + m_reg_context_sp = reg_ctx_memory; + + return m_reg_context_sp; +} + +bool ScriptedThread::LoadArtificialStackFrames() { + StructuredData::ArraySP arr_sp = GetInterface()->GetStackFrames(); + + Status error; + if (!arr_sp) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, "Failed to get scripted thread stackframes.", + error, LLDBLog::Thread); + + size_t arr_size = arr_sp->GetSize(); + if (arr_size > std::numeric_limits<uint32_t>::max()) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, + llvm::Twine( + "StackFrame array size (" + llvm::Twine(arr_size) + + llvm::Twine( + ") is greater than maximum authorized for a StackFrameList.")) + .str(), + error, LLDBLog::Thread); + + StackFrameListSP frames = GetStackFrameList(); + + for (size_t idx = 0; idx < arr_size; idx++) { + std::optional<StructuredData::Dictionary *> maybe_dict = + arr_sp->GetItemAtIndexAsDictionary(idx); + if (!maybe_dict) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, + llvm::Twine( + "Couldn't get artificial stackframe dictionary at index (" + + llvm::Twine(idx) + llvm::Twine(") from stackframe array.")) + .str(), + error, LLDBLog::Thread); + StructuredData::Dictionary *dict = *maybe_dict; + + lldb::addr_t pc; + if (!dict->GetValueForKeyAsInteger("pc", pc)) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, + "Couldn't find value for key 'pc' in stackframe dictionary.", error, + LLDBLog::Thread); + + Address symbol_addr; + symbol_addr.SetLoadAddress(pc, &this->GetProcess()->GetTarget()); + + lldb::addr_t cfa = LLDB_INVALID_ADDRESS; + bool cfa_is_valid = false; + const bool behaves_like_zeroth_frame = false; + SymbolContext sc; + symbol_addr.CalculateSymbolContext(&sc); + + StackFrameSP synth_frame_sp = std::make_shared<StackFrame>( + this->shared_from_this(), idx, idx, cfa, cfa_is_valid, pc, + StackFrame::Kind::Artificial, behaves_like_zeroth_frame, &sc); + + if (!frames->SetFrameAtIndex(static_cast<uint32_t>(idx), synth_frame_sp)) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, + llvm::Twine("Couldn't add frame (" + llvm::Twine(idx) + + llvm::Twine(") to ScriptedThread StackFrameList.")) + .str(), + error, LLDBLog::Thread); + } + + return true; +} + +bool ScriptedThread::CalculateStopInfo() { + StructuredData::DictionarySP dict_sp = GetInterface()->GetStopReason(); + + Status error; + if (!dict_sp) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, "Failed to get scripted thread stop info.", error, + LLDBLog::Thread); + + lldb::StopInfoSP stop_info_sp; + lldb::StopReason stop_reason_type; + + if (!dict_sp->GetValueForKeyAsInteger("type", stop_reason_type)) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, + "Couldn't find value for key 'type' in stop reason dictionary.", error, + LLDBLog::Thread); + + StructuredData::Dictionary *data_dict; + if (!dict_sp->GetValueForKeyAsDictionary("data", data_dict)) + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, + "Couldn't find value for key 'data' in stop reason dictionary.", error, + LLDBLog::Thread); + + switch (stop_reason_type) { + case lldb::eStopReasonNone: + return true; + case lldb::eStopReasonBreakpoint: { + lldb::break_id_t break_id; + data_dict->GetValueForKeyAsInteger("break_id", break_id, + LLDB_INVALID_BREAK_ID); + stop_info_sp = + StopInfo::CreateStopReasonWithBreakpointSiteID(*this, break_id); + } break; + case lldb::eStopReasonSignal: { + uint32_t signal; + llvm::StringRef description; + if (!data_dict->GetValueForKeyAsInteger("signal", signal)) { + signal = LLDB_INVALID_SIGNAL_NUMBER; + return false; + } + data_dict->GetValueForKeyAsString("desc", description); + stop_info_sp = + StopInfo::CreateStopReasonWithSignal(*this, signal, description.data()); + } break; + case lldb::eStopReasonTrace: { + stop_info_sp = StopInfo::CreateStopReasonToTrace(*this); + } break; + case lldb::eStopReasonException: { +#if defined(__APPLE__) + StructuredData::Dictionary *mach_exception; + if (data_dict->GetValueForKeyAsDictionary("mach_exception", + mach_exception)) { + llvm::StringRef value; + mach_exception->GetValueForKeyAsString("type", value); + auto exc_type = + StopInfoMachException::MachException::ExceptionCode(value.data()); + + if (!exc_type) + return false; + + uint32_t exc_data_size = 0; + llvm::SmallVector<uint64_t, 3> raw_codes; + + StructuredData::Array *exc_rawcodes; + mach_exception->GetValueForKeyAsArray("rawCodes", exc_rawcodes); + if (exc_rawcodes) { + auto fetch_data = [&raw_codes](StructuredData::Object *obj) { + if (!obj) + return false; + raw_codes.push_back(obj->GetUnsignedIntegerValue()); + return true; + }; + + exc_rawcodes->ForEach(fetch_data); + exc_data_size = raw_codes.size(); + } + + stop_info_sp = StopInfoMachException::CreateStopReasonWithMachException( + *this, *exc_type, exc_data_size, + exc_data_size >= 1 ? raw_codes[0] : 0, + exc_data_size >= 2 ? raw_codes[1] : 0, + exc_data_size >= 3 ? raw_codes[2] : 0); + + break; + } +#endif + stop_info_sp = + StopInfo::CreateStopReasonWithException(*this, "EXC_BAD_ACCESS"); + } break; + default: + return ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, + llvm::Twine("Unsupported stop reason type (" + + llvm::Twine(stop_reason_type) + llvm::Twine(").")) + .str(), + error, LLDBLog::Thread); + } + + if (!stop_info_sp) + return false; + + SetStopInfo(stop_info_sp); + return true; +} + +void ScriptedThread::RefreshStateAfterStop() { + GetRegisterContext()->InvalidateIfNeeded(/*force=*/false); + LoadArtificialStackFrames(); +} + +lldb::ScriptedThreadInterfaceSP ScriptedThread::GetInterface() const { + return m_scripted_thread_interface_sp; +} + +std::shared_ptr<DynamicRegisterInfo> ScriptedThread::GetDynamicRegisterInfo() { + CheckInterpreterAndScriptObject(); + + if (!m_register_info_sp) { + StructuredData::DictionarySP reg_info = GetInterface()->GetRegisterInfo(); + + Status error; + if (!reg_info) + return ScriptedInterface::ErrorWithMessage< + std::shared_ptr<DynamicRegisterInfo>>( + LLVM_PRETTY_FUNCTION, "Failed to get scripted thread registers info.", + error, LLDBLog::Thread); + + m_register_info_sp = DynamicRegisterInfo::Create( + *reg_info, m_scripted_process.GetTarget().GetArchitecture()); + } + + return m_register_info_sp; +} + +StructuredData::ObjectSP ScriptedThread::FetchThreadExtendedInfo() { + CheckInterpreterAndScriptObject(); + + Status error; + StructuredData::ArraySP extended_info_sp = GetInterface()->GetExtendedInfo(); + + if (!extended_info_sp || !extended_info_sp->GetSize()) + return ScriptedInterface::ErrorWithMessage<StructuredData::ObjectSP>( + LLVM_PRETTY_FUNCTION, "No extended information found", error); + + return extended_info_sp; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedThread.h b/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedThread.h new file mode 100644 index 000000000000..cd224d60ceef --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Process/scripted/ScriptedThread.h @@ -0,0 +1,80 @@ +//===-- ScriptedThread.h ----------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_SCRIPTED_THREAD_H +#define LLDB_SOURCE_PLUGINS_SCRIPTED_THREAD_H + +#include <string> + +#include "ScriptedProcess.h" + +#include "Plugins/Process/Utility/RegisterContextMemory.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Target//DynamicRegisterInfo.h" +#include "lldb/Target/Thread.h" + +namespace lldb_private { +class ScriptedProcess; +} + +namespace lldb_private { + +class ScriptedThread : public lldb_private::Thread { + +public: + ScriptedThread(ScriptedProcess &process, + lldb::ScriptedThreadInterfaceSP interface_sp, lldb::tid_t tid, + StructuredData::GenericSP script_object_sp = nullptr); + + ~ScriptedThread() override; + + static llvm::Expected<std::shared_ptr<ScriptedThread>> + Create(ScriptedProcess &process, + StructuredData::Generic *script_object = nullptr); + + lldb::RegisterContextSP GetRegisterContext() override; + + lldb::RegisterContextSP + CreateRegisterContextForFrame(lldb_private::StackFrame *frame) override; + + bool LoadArtificialStackFrames(); + + bool CalculateStopInfo() override; + + const char *GetInfo() override { return nullptr; } + + const char *GetName() override; + + const char *GetQueueName() override; + + void WillResume(lldb::StateType resume_state) override; + + void RefreshStateAfterStop() override; + + void ClearStackFrames() override; + + StructuredData::ObjectSP FetchThreadExtendedInfo() override; + +private: + void CheckInterpreterAndScriptObject() const; + lldb::ScriptedThreadInterfaceSP GetInterface() const; + + ScriptedThread(const ScriptedThread &) = delete; + const ScriptedThread &operator=(const ScriptedThread &) = delete; + + std::shared_ptr<DynamicRegisterInfo> GetDynamicRegisterInfo(); + + const ScriptedProcess &m_scripted_process; + lldb::ScriptedThreadInterfaceSP m_scripted_thread_interface_sp = nullptr; + lldb_private::StructuredData::GenericSP m_script_object_sp = nullptr; + std::shared_ptr<DynamicRegisterInfo> m_register_info_sp = nullptr; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_SCRIPTED_THREAD_H |