| //===-- ScriptedThreadPlan.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/Target/ThreadPlan.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Interpreter/CommandInterpreter.h" | |
| #include "lldb/Interpreter/Interfaces/ScriptedThreadPlanInterface.h" | |
| #include "lldb/Interpreter/ScriptInterpreter.h" | |
| #include "lldb/Target/Process.h" | |
| #include "lldb/Target/RegisterContext.h" | |
| #include "lldb/Target/ScriptedThreadPlan.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Target/Thread.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/State.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| ScriptedThreadPlan::ScriptedThreadPlan( | |
| Thread &thread, const ScriptedMetadata &scripted_metadata) | |
| : ThreadPlan(ThreadPlan::eKindPython, "Script based Thread Plan", thread, | |
| eVoteNoOpinion, eVoteNoOpinion), | |
| m_scripted_metadata(scripted_metadata), m_did_push(false), | |
| m_stop_others(false) { | |
| ScriptInterpreter *interpreter = GetScriptInterpreter(); | |
| if (!interpreter) { | |
| SetPlanComplete(false); | |
| // FIXME: error handling | |
| // error = Status::FromErrorStringWithFormat( | |
| // "ScriptedThreadPlan::%s () - ERROR: %s", __FUNCTION__, | |
| // "Couldn't get script interpreter"); | |
| return; | |
| } | |
| m_interface = interpreter->CreateScriptedThreadPlanInterface(); | |
| if (!m_interface) { | |
| SetPlanComplete(false); | |
| // FIXME: error handling | |
| // error = Status::FromErrorStringWithFormat( | |
| // "ScriptedThreadPlan::%s () - ERROR: %s", __FUNCTION__, | |
| // "Script interpreter couldn't create Scripted Thread Plan Interface"); | |
| return; | |
| } | |
| SetIsControllingPlan(true); | |
| SetOkayToDiscard(true); | |
| SetPrivate(false); | |
| } | |
| bool ScriptedThreadPlan::ValidatePlan(Stream *error) { | |
| if (!m_did_push) | |
| return true; | |
| if (!m_implementation_sp) { | |
| if (error) | |
| error->Printf("Error constructing Python ThreadPlan: %s", | |
| m_error_str.empty() ? "<unknown error>" | |
| : m_error_str.c_str()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| ScriptInterpreter *ScriptedThreadPlan::GetScriptInterpreter() { | |
| return m_process.GetTarget().GetDebugger().GetScriptInterpreter(); | |
| } | |
| llvm::StringRef ScriptedThreadPlan::GetScriptClassName() const { | |
| if (m_interface && m_interface->GetScriptedMetadata()) | |
| return m_interface->GetScriptedMetadata()->GetClassName(); | |
| return "<unknown>"; | |
| } | |
| void ScriptedThreadPlan::DidPush() { | |
| // We set up the script side in DidPush, so that it can push other plans in | |
| // the constructor, and doesn't have to care about the details of DidPush. | |
| m_did_push = true; | |
| if (m_interface) { | |
| auto obj_or_err = m_interface->CreatePluginObject(m_scripted_metadata, | |
| this->shared_from_this()); | |
| if (!obj_or_err) { | |
| m_error_str = llvm::toString(obj_or_err.takeError()); | |
| Debugger::ReportError( | |
| llvm::formatv("failed to create ScriptedThreadPlan: {0}", m_error_str) | |
| .str(), | |
| m_process.GetTarget().GetDebugger().GetID()); | |
| SetPlanComplete(false); | |
| } else | |
| m_implementation_sp = *obj_or_err; | |
| } | |
| } | |
| bool ScriptedThreadPlan::ShouldStop(Event *event_ptr) { | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOG(log, "called on Scripted Thread Plan: {0}", GetScriptClassName()); | |
| bool should_stop = true; | |
| if (m_implementation_sp) { | |
| auto should_stop_or_err = m_interface->ShouldStop(event_ptr); | |
| if (!should_stop_or_err) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), should_stop_or_err.takeError(), | |
| "Can't call ScriptedThreadPlan::ShouldStop: {0}"); | |
| SetPlanComplete(false); | |
| } else | |
| should_stop = *should_stop_or_err; | |
| } | |
| return should_stop; | |
| } | |
| bool ScriptedThreadPlan::IsPlanStale() { | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOG(log, "called on Scripted Thread Plan: {0}", GetScriptClassName()); | |
| bool is_stale = true; | |
| if (m_implementation_sp) { | |
| auto is_stale_or_err = m_interface->IsStale(); | |
| if (!is_stale_or_err) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), is_stale_or_err.takeError(), | |
| "Can't call ScriptedThreadPlan::IsStale: {0}"); | |
| SetPlanComplete(false); | |
| } else | |
| is_stale = *is_stale_or_err; | |
| } | |
| return is_stale; | |
| } | |
| bool ScriptedThreadPlan::DoPlanExplainsStop(Event *event_ptr) { | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOG(log, "called on Scripted Thread Plan: {0}", GetScriptClassName()); | |
| bool explains_stop = true; | |
| if (m_implementation_sp) { | |
| auto explains_stop_or_error = m_interface->ExplainsStop(event_ptr); | |
| if (!explains_stop_or_error) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), | |
| explains_stop_or_error.takeError(), | |
| "Can't call ScriptedThreadPlan::ExplainsStop: {0}"); | |
| SetPlanComplete(false); | |
| } else | |
| explains_stop = *explains_stop_or_error; | |
| } | |
| return explains_stop; | |
| } | |
| bool ScriptedThreadPlan::MischiefManaged() { | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOG(log, "called on Scripted Thread Plan: {0}", GetScriptClassName()); | |
| bool mischief_managed = true; | |
| if (m_implementation_sp) { | |
| // I don't really need mischief_managed, since it's simpler to just call | |
| // SetPlanComplete in should_stop. | |
| mischief_managed = IsPlanComplete(); | |
| if (mischief_managed) { | |
| // We need to cache the stop reason here we'll need it in GetDescription. | |
| GetDescription(&m_stop_description, eDescriptionLevelBrief); | |
| m_implementation_sp.reset(); | |
| } | |
| } | |
| return mischief_managed; | |
| } | |
| lldb::StateType ScriptedThreadPlan::GetPlanRunState() { | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOG(log, "called on Scripted Thread Plan: {0}", GetScriptClassName()); | |
| lldb::StateType run_state = eStateRunning; | |
| if (m_implementation_sp) { | |
| llvm::Expected<lldb::StateType> run_state_or_err = | |
| m_interface->GetRunState(); | |
| if (!run_state_or_err) { | |
| LLDB_LOG_ERROR(log, run_state_or_err.takeError(), | |
| "Can't call ScriptedThreadPlan::GetRunState: {0}"); | |
| return run_state; | |
| } | |
| run_state = *run_state_or_err; | |
| } | |
| return run_state; | |
| } | |
| void ScriptedThreadPlan::GetDescription(Stream *s, | |
| lldb::DescriptionLevel level) { | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOG(log, "called on Scripted Thread Plan: {0}", GetScriptClassName()); | |
| if (m_implementation_sp) { | |
| ScriptInterpreter *script_interp = GetScriptInterpreter(); | |
| if (script_interp) { | |
| lldb::StreamSP stream = std::make_shared<lldb_private::StreamString>(); | |
| if (llvm::Error err = m_interface->GetStopDescription(stream)) | |
| LLDB_LOG_ERROR( | |
| GetLog(LLDBLog::Thread), std::move(err), | |
| "Can't call ScriptedThreadPlan::GetStopDescription: {0}"); | |
| // Fall back on the class name whenever the plan didn't describe itself, | |
| // whether that's because `stop_description` failed or because the plan | |
| // doesn't implement it at all. A plan with no description at all is | |
| // never the right answer. | |
| StreamString *description = | |
| reinterpret_cast<StreamString *>(stream.get()); | |
| if (description->GetString().empty()) | |
| s->Format("Scripted thread plan implemented by class {0}.", | |
| GetScriptClassName()); | |
| else | |
| s->PutCString(description->GetData()); | |
| } | |
| return; | |
| } | |
| // It's an error not to have a description, so if we get here, we should | |
| // add something. | |
| if (m_stop_description.Empty()) | |
| s->Format("Scripted thread plan implemented by class {0}.", | |
| GetScriptClassName()); | |
| s->PutCString(m_stop_description.GetData()); | |
| } | |
| // The ones below are not currently exported to Python. | |
| bool ScriptedThreadPlan::WillStop() { | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOG(log, "called on Scripted Thread Plan: {0}", GetScriptClassName()); | |
| return true; | |
| } | |
| bool ScriptedThreadPlan::DoWillResume(lldb::StateType resume_state, | |
| bool current_plan) { | |
| m_stop_description.Clear(); | |
| return true; | |
| } |