Path: blob/main/contrib/llvm-project/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp
39642 views
//===-- ScriptInterpreterLua.cpp ------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "ScriptInterpreterLua.h"9#include "Lua.h"10#include "lldb/Breakpoint/StoppointCallbackContext.h"11#include "lldb/Core/Debugger.h"12#include "lldb/Core/PluginManager.h"13#include "lldb/Host/StreamFile.h"14#include "lldb/Interpreter/CommandReturnObject.h"15#include "lldb/Target/ExecutionContext.h"16#include "lldb/Utility/Stream.h"17#include "lldb/Utility/StringList.h"18#include "lldb/Utility/Timer.h"19#include "llvm/ADT/StringRef.h"20#include "llvm/Support/FormatAdapters.h"21#include <memory>22#include <vector>2324using namespace lldb;25using namespace lldb_private;2627LLDB_PLUGIN_DEFINE(ScriptInterpreterLua)2829enum ActiveIOHandler {30eIOHandlerNone,31eIOHandlerBreakpoint,32eIOHandlerWatchpoint33};3435class IOHandlerLuaInterpreter : public IOHandlerDelegate,36public IOHandlerEditline {37public:38IOHandlerLuaInterpreter(Debugger &debugger,39ScriptInterpreterLua &script_interpreter,40ActiveIOHandler active_io_handler = eIOHandlerNone)41: IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua",42llvm::StringRef(">>> "), llvm::StringRef("..> "),43true, debugger.GetUseColor(), 0, *this),44m_script_interpreter(script_interpreter),45m_active_io_handler(active_io_handler) {46llvm::cantFail(m_script_interpreter.GetLua().ChangeIO(47debugger.GetOutputFile().GetStream(),48debugger.GetErrorFile().GetStream()));49llvm::cantFail(m_script_interpreter.EnterSession(debugger.GetID()));50}5152~IOHandlerLuaInterpreter() override {53llvm::cantFail(m_script_interpreter.LeaveSession());54}5556void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {57const char *instructions = nullptr;58switch (m_active_io_handler) {59case eIOHandlerNone:60break;61case eIOHandlerWatchpoint:62instructions = "Enter your Lua command(s). Type 'quit' to end.\n"63"The commands are compiled as the body of the following "64"Lua function\n"65"function (frame, wp) end\n";66SetPrompt(llvm::StringRef("..> "));67break;68case eIOHandlerBreakpoint:69instructions = "Enter your Lua command(s). Type 'quit' to end.\n"70"The commands are compiled as the body of the following "71"Lua function\n"72"function (frame, bp_loc, ...) end\n";73SetPrompt(llvm::StringRef("..> "));74break;75}76if (instructions == nullptr)77return;78if (interactive)79*io_handler.GetOutputStreamFileSP() << instructions;80}8182bool IOHandlerIsInputComplete(IOHandler &io_handler,83StringList &lines) override {84size_t last = lines.GetSize() - 1;85if (IsQuitCommand(lines.GetStringAtIndex(last))) {86if (m_active_io_handler == eIOHandlerBreakpoint ||87m_active_io_handler == eIOHandlerWatchpoint)88lines.DeleteStringAtIndex(last);89return true;90}91StreamString str;92lines.Join("\n", str);93if (llvm::Error E =94m_script_interpreter.GetLua().CheckSyntax(str.GetString())) {95std::string error_str = toString(std::move(E));96// Lua always errors out to incomplete code with '<eof>'97return error_str.find("<eof>") == std::string::npos;98}99// The breakpoint and watchpoint handler only exits with a explicit 'quit'100return m_active_io_handler != eIOHandlerBreakpoint &&101m_active_io_handler != eIOHandlerWatchpoint;102}103104void IOHandlerInputComplete(IOHandler &io_handler,105std::string &data) override {106switch (m_active_io_handler) {107case eIOHandlerBreakpoint: {108auto *bp_options_vec =109static_cast<std::vector<std::reference_wrapper<BreakpointOptions>> *>(110io_handler.GetUserData());111for (BreakpointOptions &bp_options : *bp_options_vec) {112Status error = m_script_interpreter.SetBreakpointCommandCallback(113bp_options, data.c_str(), /*is_callback=*/false);114if (error.Fail())115*io_handler.GetErrorStreamFileSP() << error.AsCString() << '\n';116}117io_handler.SetIsDone(true);118} break;119case eIOHandlerWatchpoint: {120auto *wp_options =121static_cast<WatchpointOptions *>(io_handler.GetUserData());122m_script_interpreter.SetWatchpointCommandCallback(wp_options,123data.c_str(),124/*is_callback=*/false);125io_handler.SetIsDone(true);126} break;127case eIOHandlerNone:128if (IsQuitCommand(data)) {129io_handler.SetIsDone(true);130return;131}132if (llvm::Error error = m_script_interpreter.GetLua().Run(data))133*io_handler.GetErrorStreamFileSP() << toString(std::move(error));134break;135}136}137138private:139ScriptInterpreterLua &m_script_interpreter;140ActiveIOHandler m_active_io_handler;141142bool IsQuitCommand(llvm::StringRef cmd) { return cmd.rtrim() == "quit"; }143};144145ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger)146: ScriptInterpreter(debugger, eScriptLanguageLua),147m_lua(std::make_unique<Lua>()) {}148149ScriptInterpreterLua::~ScriptInterpreterLua() = default;150151StructuredData::DictionarySP ScriptInterpreterLua::GetInterpreterInfo() {152auto info = std::make_shared<StructuredData::Dictionary>();153info->AddStringItem("language", "lua");154return info;155}156157bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command,158CommandReturnObject *result,159const ExecuteScriptOptions &options) {160if (command.empty()) {161if (result)162result->AppendError("empty command passed to lua\n");163return false;164}165166llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>>167io_redirect_or_error = ScriptInterpreterIORedirect::Create(168options.GetEnableIO(), m_debugger, result);169if (!io_redirect_or_error) {170if (result)171result->AppendErrorWithFormatv(172"failed to redirect I/O: {0}\n",173llvm::fmt_consume(io_redirect_or_error.takeError()));174else175llvm::consumeError(io_redirect_or_error.takeError());176return false;177}178179ScriptInterpreterIORedirect &io_redirect = **io_redirect_or_error;180181if (llvm::Error e =182m_lua->ChangeIO(io_redirect.GetOutputFile()->GetStream(),183io_redirect.GetErrorFile()->GetStream())) {184result->AppendErrorWithFormatv("lua failed to redirect I/O: {0}\n",185llvm::toString(std::move(e)));186return false;187}188189if (llvm::Error e = m_lua->Run(command)) {190result->AppendErrorWithFormatv(191"lua failed attempting to evaluate '{0}': {1}\n", command,192llvm::toString(std::move(e)));193return false;194}195196io_redirect.Flush();197return true;198}199200void ScriptInterpreterLua::ExecuteInterpreterLoop() {201LLDB_SCOPED_TIMER();202203// At the moment, the only time the debugger does not have an input file204// handle is when this is called directly from lua, in which case it is205// both dangerous and unnecessary (not to mention confusing) to try to embed206// a running interpreter loop inside the already running lua interpreter207// loop, so we won't do it.208if (!m_debugger.GetInputFile().IsValid())209return;210211IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(m_debugger, *this));212m_debugger.RunIOHandlerAsync(io_handler_sp);213}214215bool ScriptInterpreterLua::LoadScriptingModule(216const char *filename, const LoadScriptOptions &options,217lldb_private::Status &error, StructuredData::ObjectSP *module_sp,218FileSpec extra_search_dir) {219220if (llvm::Error e = m_lua->LoadModule(filename)) {221error.SetErrorStringWithFormatv("lua failed to import '{0}': {1}\n",222filename, llvm::toString(std::move(e)));223return false;224}225return true;226}227228void ScriptInterpreterLua::Initialize() {229static llvm::once_flag g_once_flag;230231llvm::call_once(g_once_flag, []() {232PluginManager::RegisterPlugin(GetPluginNameStatic(),233GetPluginDescriptionStatic(),234lldb::eScriptLanguageLua, CreateInstance);235});236}237238void ScriptInterpreterLua::Terminate() {}239240llvm::Error ScriptInterpreterLua::EnterSession(user_id_t debugger_id) {241if (m_session_is_active)242return llvm::Error::success();243244const char *fmt_str =245"lldb.debugger = lldb.SBDebugger.FindDebuggerWithID({0}); "246"lldb.target = lldb.debugger:GetSelectedTarget(); "247"lldb.process = lldb.target:GetProcess(); "248"lldb.thread = lldb.process:GetSelectedThread(); "249"lldb.frame = lldb.thread:GetSelectedFrame()";250return m_lua->Run(llvm::formatv(fmt_str, debugger_id).str());251}252253llvm::Error ScriptInterpreterLua::LeaveSession() {254if (!m_session_is_active)255return llvm::Error::success();256257m_session_is_active = false;258259llvm::StringRef str = "lldb.debugger = nil; "260"lldb.target = nil; "261"lldb.process = nil; "262"lldb.thread = nil; "263"lldb.frame = nil";264return m_lua->Run(str);265}266267bool ScriptInterpreterLua::BreakpointCallbackFunction(268void *baton, StoppointCallbackContext *context, user_id_t break_id,269user_id_t break_loc_id) {270assert(context);271272ExecutionContext exe_ctx(context->exe_ctx_ref);273Target *target = exe_ctx.GetTargetPtr();274if (target == nullptr)275return true;276277StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP());278BreakpointSP breakpoint_sp = target->GetBreakpointByID(break_id);279BreakpointLocationSP bp_loc_sp(breakpoint_sp->FindLocationByID(break_loc_id));280281Debugger &debugger = target->GetDebugger();282ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>(283debugger.GetScriptInterpreter(true, eScriptLanguageLua));284Lua &lua = lua_interpreter->GetLua();285286CommandDataLua *bp_option_data = static_cast<CommandDataLua *>(baton);287llvm::Expected<bool> BoolOrErr = lua.CallBreakpointCallback(288baton, stop_frame_sp, bp_loc_sp, bp_option_data->m_extra_args_sp);289if (llvm::Error E = BoolOrErr.takeError()) {290debugger.GetErrorStream() << toString(std::move(E));291return true;292}293294return *BoolOrErr;295}296297bool ScriptInterpreterLua::WatchpointCallbackFunction(298void *baton, StoppointCallbackContext *context, user_id_t watch_id) {299assert(context);300301ExecutionContext exe_ctx(context->exe_ctx_ref);302Target *target = exe_ctx.GetTargetPtr();303if (target == nullptr)304return true;305306StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP());307WatchpointSP wp_sp = target->GetWatchpointList().FindByID(watch_id);308309Debugger &debugger = target->GetDebugger();310ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>(311debugger.GetScriptInterpreter(true, eScriptLanguageLua));312Lua &lua = lua_interpreter->GetLua();313314llvm::Expected<bool> BoolOrErr =315lua.CallWatchpointCallback(baton, stop_frame_sp, wp_sp);316if (llvm::Error E = BoolOrErr.takeError()) {317debugger.GetErrorStream() << toString(std::move(E));318return true;319}320321return *BoolOrErr;322}323324void ScriptInterpreterLua::CollectDataForBreakpointCommandCallback(325std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,326CommandReturnObject &result) {327IOHandlerSP io_handler_sp(328new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerBreakpoint));329io_handler_sp->SetUserData(&bp_options_vec);330m_debugger.RunIOHandlerAsync(io_handler_sp);331}332333void ScriptInterpreterLua::CollectDataForWatchpointCommandCallback(334WatchpointOptions *wp_options, CommandReturnObject &result) {335IOHandlerSP io_handler_sp(336new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerWatchpoint));337io_handler_sp->SetUserData(wp_options);338m_debugger.RunIOHandlerAsync(io_handler_sp);339}340341Status ScriptInterpreterLua::SetBreakpointCommandCallbackFunction(342BreakpointOptions &bp_options, const char *function_name,343StructuredData::ObjectSP extra_args_sp) {344const char *fmt_str = "return {0}(frame, bp_loc, ...)";345std::string oneliner = llvm::formatv(fmt_str, function_name).str();346return RegisterBreakpointCallback(bp_options, oneliner.c_str(),347extra_args_sp);348}349350Status ScriptInterpreterLua::SetBreakpointCommandCallback(351BreakpointOptions &bp_options, const char *command_body_text,352bool is_callback) {353return RegisterBreakpointCallback(bp_options, command_body_text, {});354}355356Status ScriptInterpreterLua::RegisterBreakpointCallback(357BreakpointOptions &bp_options, const char *command_body_text,358StructuredData::ObjectSP extra_args_sp) {359Status error;360auto data_up = std::make_unique<CommandDataLua>(extra_args_sp);361error = m_lua->RegisterBreakpointCallback(data_up.get(), command_body_text);362if (error.Fail())363return error;364auto baton_sp =365std::make_shared<BreakpointOptions::CommandBaton>(std::move(data_up));366bp_options.SetCallback(ScriptInterpreterLua::BreakpointCallbackFunction,367baton_sp);368return error;369}370371void ScriptInterpreterLua::SetWatchpointCommandCallback(372WatchpointOptions *wp_options, const char *command_body_text,373bool is_callback) {374RegisterWatchpointCallback(wp_options, command_body_text, {});375}376377Status ScriptInterpreterLua::RegisterWatchpointCallback(378WatchpointOptions *wp_options, const char *command_body_text,379StructuredData::ObjectSP extra_args_sp) {380Status error;381auto data_up = std::make_unique<WatchpointOptions::CommandData>();382error = m_lua->RegisterWatchpointCallback(data_up.get(), command_body_text);383if (error.Fail())384return error;385auto baton_sp =386std::make_shared<WatchpointOptions::CommandBaton>(std::move(data_up));387wp_options->SetCallback(ScriptInterpreterLua::WatchpointCallbackFunction,388baton_sp);389return error;390}391392lldb::ScriptInterpreterSP393ScriptInterpreterLua::CreateInstance(Debugger &debugger) {394return std::make_shared<ScriptInterpreterLua>(debugger);395}396397llvm::StringRef ScriptInterpreterLua::GetPluginDescriptionStatic() {398return "Lua script interpreter";399}400401Lua &ScriptInterpreterLua::GetLua() { return *m_lua; }402403404