| //===-- 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 "ScriptedFrame.h" | |
| #include "Plugins/Process/Utility/RegisterContextThreadMemory.h" | |
| #include "Plugins/Process/Utility/StopInfoMachException.h" | |
| #include "lldb/Core/Debugger.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); | |
| // The legacy thread-spawn path (no script_object) needs to instantiate a | |
| // *thread* Python class whose name comes from the process plugin, not the | |
| // process's own class name. Build a thread-specific metadata for that case; | |
| // when script_object is non-null the class name is unused so we just forward | |
| // the process's metadata. | |
| ScriptedMetadata thread_metadata = | |
| script_object ? process.m_scripted_metadata | |
| : ScriptedMetadata(thread_class_name, | |
| process.m_scripted_metadata.GetArgsSP()); | |
| auto obj_or_err = scripted_thread_interface->CreatePluginObject( | |
| thread_metadata, exe_ctx, script_object); | |
| if (!obj_or_err) | |
| return obj_or_err.takeError(); | |
| 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).AsCString(nullptr); | |
| } | |
| const char *ScriptedThread::GetQueueName() { | |
| CheckInterpreterAndScriptObject(); | |
| std::optional<std::string> queue_name = GetInterface()->GetQueue(); | |
| if (!queue_name) | |
| return nullptr; | |
| return ConstString(*queue_name).AsCString(nullptr); | |
| } | |
| 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); | |
| DynamicRegisterInfoSP register_info_sp = GetDynamicRegisterInfo(); | |
| if (!register_info_sp) | |
| return ScriptedInterface::ErrorWithMessage<lldb::RegisterContextSP>( | |
| LLVM_PRETTY_FUNCTION, | |
| "Failed to create scripted thread registers info.", error, | |
| LLDBLog::Thread); | |
| std::shared_ptr<RegisterContextMemory> reg_ctx_memory = | |
| std::make_shared<RegisterContextMemory>( | |
| *this, 0, std::move(register_info_sp), 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) | |
| return ScriptedInterface::ErrorWithMessage<bool>( | |
| LLVM_PRETTY_FUNCTION, "StackFrame array is empty.", error, | |
| LLDBLog::Thread); | |
| 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) + | |
| ") is greater than maximum authorized for a StackFrameList.") | |
| .str(), | |
| error, LLDBLog::Thread); | |
| auto create_frame_from_dict = | |
| [this, arr_sp](size_t idx, | |
| uint32_t frame_list_idx) -> llvm::Expected<StackFrameSP> { | |
| Status error; | |
| std::optional<StructuredData::Dictionary *> maybe_dict = | |
| arr_sp->GetItemAtIndexAsDictionary(idx); | |
| if (!maybe_dict) { | |
| 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); | |
| return error.ToError(); | |
| } | |
| StructuredData::Dictionary *dict = *maybe_dict; | |
| lldb::addr_t pc; | |
| if (!dict->GetValueForKeyAsInteger("pc", pc)) { | |
| ScriptedInterface::ErrorWithMessage<bool>( | |
| LLVM_PRETTY_FUNCTION, | |
| "Couldn't find value for key 'pc' in stackframe dictionary.", error, | |
| LLDBLog::Thread); | |
| return error.ToError(); | |
| } | |
| Address symbol_addr; | |
| pc = this->GetProcess()->FixCodeAddress(pc); | |
| symbol_addr.SetLoadAddress(pc, &this->GetProcess()->GetTarget()); | |
| lldb::addr_t cfa = LLDB_INVALID_ADDRESS; | |
| bool cfa_is_valid = false; | |
| const bool artificial = false; | |
| const bool behaves_like_zeroth_frame = (frame_list_idx == 0); | |
| SymbolContext sc; | |
| symbol_addr.CalculateSymbolContext(&sc); | |
| return std::make_shared<StackFrame>(shared_from_this(), frame_list_idx, idx, | |
| cfa, cfa_is_valid, pc, | |
| StackFrame::Kind::Synthetic, artificial, | |
| behaves_like_zeroth_frame, &sc); | |
| }; | |
| auto create_frame_from_script_object = | |
| [this, arr_sp](size_t idx) -> llvm::Expected<StackFrameSP> { | |
| Status error; | |
| StructuredData::ObjectSP object_sp = arr_sp->GetItemAtIndex(idx); | |
| if (!object_sp || !object_sp->GetAsGeneric()) { | |
| ScriptedInterface::ErrorWithMessage<bool>( | |
| LLVM_PRETTY_FUNCTION, | |
| llvm::Twine("Couldn't get artificial stackframe object at index (" + | |
| llvm::Twine(idx) + | |
| llvm::Twine(") from stackframe array.")) | |
| .str(), | |
| error, LLDBLog::Thread); | |
| return error.ToError(); | |
| } | |
| auto frame_or_error = ScriptedFrame::Create( | |
| shared_from_this(), GetInterface(), nullptr, object_sp->GetAsGeneric()); | |
| if (!frame_or_error) { | |
| ScriptedInterface::ErrorWithMessage<bool>( | |
| LLVM_PRETTY_FUNCTION, toString(frame_or_error.takeError()), error); | |
| return error.ToError(); | |
| } | |
| StackFrameSP frame_sp = frame_or_error.get(); | |
| lldbassert(frame_sp && "Couldn't initialize scripted frame."); | |
| return frame_sp; | |
| }; | |
| StackFrameListSP frames = GetStackFrameList(); | |
| uint32_t frame_list_idx = 0; | |
| for (size_t idx = 0; idx < arr_size; idx++) { | |
| StackFrameSP synth_frame_sp = nullptr; | |
| auto frame_from_dict_or_err = create_frame_from_dict(idx, frame_list_idx); | |
| if (!frame_from_dict_or_err) { | |
| auto frame_from_script_obj_or_err = create_frame_from_script_object(idx); | |
| if (!frame_from_script_obj_or_err) { | |
| llvm::consumeError(frame_from_dict_or_err.takeError()); | |
| Debugger::ReportError( | |
| llvm::formatv( | |
| "couldn't add artificial frame ({0}) to ScriptedThread " | |
| "StackFrameList: {1}", | |
| idx, llvm::toString(frame_from_script_obj_or_err.takeError())) | |
| .str(), | |
| GetProcess()->GetTarget().GetDebugger().GetID()); | |
| return false; | |
| } else { | |
| llvm::consumeError(frame_from_dict_or_err.takeError()); | |
| synth_frame_sp = *frame_from_script_obj_or_err; | |
| } | |
| } else { | |
| synth_frame_sp = *frame_from_dict_or_err; | |
| } | |
| if (!frames->SetFrameAtIndex(frame_list_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); | |
| frame_list_idx++; | |
| // Synthesize inline frames, mirroring StackFrameList::FetchFramesUpTo(). | |
| frame_list_idx += frames->SynthesizeInlineFrames( | |
| synth_frame_sp, /*cfa=*/LLDB_INVALID_ADDRESS); | |
| } | |
| // Mark the stack as fully unwound so the regular unwinder doesn't try to | |
| // extend it beyond the artificial frames (e.g. by reading lr/fp from the | |
| // register context). | |
| frames->SetAllFramesFetched(); | |
| 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); | |
| // If we're at a BreakpointSite, mark that we stopped there and | |
| // need to hit the breakpoint when we resume. This will be cleared | |
| // if we CreateStopReasonWithBreakpointSiteID. | |
| if (RegisterContextSP reg_ctx_sp = GetRegisterContext()) { | |
| addr_t pc = reg_ctx_sp->GetPC(); | |
| ProcessSP proc = GetProcess(); | |
| if (BreakpointSiteSP bp_site_sp = | |
| proc->GetBreakpointSiteList().FindByAddress(pc)) | |
| if (proc->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) | |
| SetThreadStoppedAtUnexecutedBP(pc); | |
| } | |
| 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; | |
| } | |
| DynamicRegisterInfoSP ScriptedThread::GetDynamicRegisterInfo() { | |
| CheckInterpreterAndScriptObject(); | |
| StructuredData::DictionarySP reg_info = GetInterface()->GetRegisterInfo(); | |
| Status error; | |
| if (!reg_info) | |
| return ScriptedInterface::ErrorWithMessage<DynamicRegisterInfoSP>( | |
| LLVM_PRETTY_FUNCTION, "Failed to get scripted thread registers info.", | |
| error, LLDBLog::Thread); | |
| return DynamicRegisterInfo::Create( | |
| *reg_info, m_scripted_process.GetTarget().GetArchitecture()); | |
| } | |
| 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; | |
| } |