321 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			321 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===-- ScriptInterpreterLua.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 "ScriptInterpreterLua.h"
 | |
| #include "Lua.h"
 | |
| #include "lldb/Breakpoint/StoppointCallbackContext.h"
 | |
| #include "lldb/Core/Debugger.h"
 | |
| #include "lldb/Core/PluginManager.h"
 | |
| #include "lldb/Core/StreamFile.h"
 | |
| #include "lldb/Interpreter/CommandReturnObject.h"
 | |
| #include "lldb/Target/ExecutionContext.h"
 | |
| #include "lldb/Utility/Stream.h"
 | |
| #include "lldb/Utility/StringList.h"
 | |
| #include "lldb/Utility/Timer.h"
 | |
| #include "llvm/ADT/StringRef.h"
 | |
| #include "llvm/Support/FormatAdapters.h"
 | |
| #include <memory>
 | |
| #include <vector>
 | |
| 
 | |
| using namespace lldb;
 | |
| using namespace lldb_private;
 | |
| 
 | |
| LLDB_PLUGIN_DEFINE(ScriptInterpreterLua)
 | |
| 
 | |
| enum ActiveIOHandler {
 | |
|   eIOHandlerNone,
 | |
|   eIOHandlerBreakpoint,
 | |
|   eIOHandlerWatchpoint
 | |
| };
 | |
| 
 | |
| class IOHandlerLuaInterpreter : public IOHandlerDelegate,
 | |
|                                 public IOHandlerEditline {
 | |
| public:
 | |
|   IOHandlerLuaInterpreter(Debugger &debugger,
 | |
|                           ScriptInterpreterLua &script_interpreter,
 | |
|                           ActiveIOHandler active_io_handler = eIOHandlerNone)
 | |
|       : IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua",
 | |
|                           ">>> ", "..> ", true, debugger.GetUseColor(), 0,
 | |
|                           *this, nullptr),
 | |
|         m_script_interpreter(script_interpreter),
 | |
|         m_active_io_handler(active_io_handler) {
 | |
|     llvm::cantFail(m_script_interpreter.GetLua().ChangeIO(
 | |
|         debugger.GetOutputFile().GetStream(),
 | |
|         debugger.GetErrorFile().GetStream()));
 | |
|     llvm::cantFail(m_script_interpreter.EnterSession(debugger.GetID()));
 | |
|   }
 | |
| 
 | |
|   ~IOHandlerLuaInterpreter() override {
 | |
|     llvm::cantFail(m_script_interpreter.LeaveSession());
 | |
|   }
 | |
| 
 | |
|   void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
 | |
|     const char *instructions = nullptr;
 | |
|     switch (m_active_io_handler) {
 | |
|     case eIOHandlerNone:
 | |
|     case eIOHandlerWatchpoint:
 | |
|       break;
 | |
|     case eIOHandlerBreakpoint:
 | |
|       instructions = "Enter your Lua command(s). Type 'quit' to end.\n"
 | |
|                      "The commands are compiled as the body of the following "
 | |
|                      "Lua function\n"
 | |
|                      "function (frame, bp_loc, ...) end\n";
 | |
|       SetPrompt(llvm::StringRef("..> "));
 | |
|       break;
 | |
|     }
 | |
|     if (instructions == nullptr)
 | |
|       return;
 | |
|     if (interactive)
 | |
|       *io_handler.GetOutputStreamFileSP() << instructions;
 | |
|   }
 | |
| 
 | |
|   bool IOHandlerIsInputComplete(IOHandler &io_handler,
 | |
|                                 StringList &lines) override {
 | |
|     size_t last = lines.GetSize() - 1;
 | |
|     if (IsQuitCommand(lines.GetStringAtIndex(last))) {
 | |
|       if (m_active_io_handler == eIOHandlerBreakpoint)
 | |
|         lines.DeleteStringAtIndex(last);
 | |
|       return true;
 | |
|     }
 | |
|     StreamString str;
 | |
|     lines.Join("\n", str);
 | |
|     if (llvm::Error E =
 | |
|             m_script_interpreter.GetLua().CheckSyntax(str.GetString())) {
 | |
|       std::string error_str = toString(std::move(E));
 | |
|       // Lua always errors out to incomplete code with '<eof>'
 | |
|       return error_str.find("<eof>") == std::string::npos;
 | |
|     }
 | |
|     // The breakpoint handler only exits with a explicit 'quit'
 | |
|     return m_active_io_handler != eIOHandlerBreakpoint;
 | |
|   }
 | |
| 
 | |
|   void IOHandlerInputComplete(IOHandler &io_handler,
 | |
|                               std::string &data) override {
 | |
|     switch (m_active_io_handler) {
 | |
|     case eIOHandlerBreakpoint: {
 | |
|       auto *bp_options_vec = static_cast<std::vector<BreakpointOptions *> *>(
 | |
|           io_handler.GetUserData());
 | |
|       for (auto *bp_options : *bp_options_vec) {
 | |
|         Status error = m_script_interpreter.SetBreakpointCommandCallback(
 | |
|             bp_options, data.c_str());
 | |
|         if (error.Fail())
 | |
|           *io_handler.GetErrorStreamFileSP() << error.AsCString() << '\n';
 | |
|       }
 | |
|       io_handler.SetIsDone(true);
 | |
|     } break;
 | |
|     case eIOHandlerWatchpoint:
 | |
|       io_handler.SetIsDone(true);
 | |
|       break;
 | |
|     case eIOHandlerNone:
 | |
|       if (IsQuitCommand(data)) {
 | |
|         io_handler.SetIsDone(true);
 | |
|         return;
 | |
|       }
 | |
|       if (llvm::Error error = m_script_interpreter.GetLua().Run(data))
 | |
|         *io_handler.GetErrorStreamFileSP() << toString(std::move(error));
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| private:
 | |
|   ScriptInterpreterLua &m_script_interpreter;
 | |
|   ActiveIOHandler m_active_io_handler;
 | |
| 
 | |
|   bool IsQuitCommand(llvm::StringRef cmd) { return cmd.rtrim() == "quit"; }
 | |
| };
 | |
| 
 | |
| ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger)
 | |
|     : ScriptInterpreter(debugger, eScriptLanguageLua),
 | |
|       m_lua(std::make_unique<Lua>()) {}
 | |
| 
 | |
| ScriptInterpreterLua::~ScriptInterpreterLua() {}
 | |
| 
 | |
| bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command,
 | |
|                                           CommandReturnObject *result,
 | |
|                                           const ExecuteScriptOptions &options) {
 | |
|   if (command.empty()) {
 | |
|     if (result)
 | |
|       result->AppendError("empty command passed to lua\n");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>>
 | |
|       io_redirect_or_error = ScriptInterpreterIORedirect::Create(
 | |
|           options.GetEnableIO(), m_debugger, result);
 | |
|   if (!io_redirect_or_error) {
 | |
|     if (result)
 | |
|       result->AppendErrorWithFormatv(
 | |
|           "failed to redirect I/O: {0}\n",
 | |
|           llvm::fmt_consume(io_redirect_or_error.takeError()));
 | |
|     else
 | |
|       llvm::consumeError(io_redirect_or_error.takeError());
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   ScriptInterpreterIORedirect &io_redirect = **io_redirect_or_error;
 | |
| 
 | |
|   if (llvm::Error e =
 | |
|           m_lua->ChangeIO(io_redirect.GetOutputFile()->GetStream(),
 | |
|                           io_redirect.GetErrorFile()->GetStream())) {
 | |
|     result->AppendErrorWithFormatv("lua failed to redirect I/O: {0}\n",
 | |
|                                    llvm::toString(std::move(e)));
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (llvm::Error e = m_lua->Run(command)) {
 | |
|     result->AppendErrorWithFormatv(
 | |
|         "lua failed attempting to evaluate '{0}': {1}\n", command,
 | |
|         llvm::toString(std::move(e)));
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   io_redirect.Flush();
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void ScriptInterpreterLua::ExecuteInterpreterLoop() {
 | |
|   LLDB_SCOPED_TIMER();
 | |
| 
 | |
|   // At the moment, the only time the debugger does not have an input file
 | |
|   // handle is when this is called directly from lua, in which case it is
 | |
|   // both dangerous and unnecessary (not to mention confusing) to try to embed
 | |
|   // a running interpreter loop inside the already running lua interpreter
 | |
|   // loop, so we won't do it.
 | |
|   if (!m_debugger.GetInputFile().IsValid())
 | |
|     return;
 | |
| 
 | |
|   IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(m_debugger, *this));
 | |
|   m_debugger.RunIOHandlerAsync(io_handler_sp);
 | |
| }
 | |
| 
 | |
| bool ScriptInterpreterLua::LoadScriptingModule(
 | |
|     const char *filename, bool init_session, lldb_private::Status &error,
 | |
|     StructuredData::ObjectSP *module_sp, FileSpec extra_search_dir) {
 | |
| 
 | |
|   FileSystem::Instance().Collect(filename);
 | |
|   if (llvm::Error e = m_lua->LoadModule(filename)) {
 | |
|     error.SetErrorStringWithFormatv("lua failed to import '{0}': {1}\n",
 | |
|                                     filename, llvm::toString(std::move(e)));
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void ScriptInterpreterLua::Initialize() {
 | |
|   static llvm::once_flag g_once_flag;
 | |
| 
 | |
|   llvm::call_once(g_once_flag, []() {
 | |
|     PluginManager::RegisterPlugin(GetPluginNameStatic(),
 | |
|                                   GetPluginDescriptionStatic(),
 | |
|                                   lldb::eScriptLanguageLua, CreateInstance);
 | |
|   });
 | |
| }
 | |
| 
 | |
| void ScriptInterpreterLua::Terminate() {}
 | |
| 
 | |
| llvm::Error ScriptInterpreterLua::EnterSession(user_id_t debugger_id) {
 | |
|   if (m_session_is_active)
 | |
|     return llvm::Error::success();
 | |
| 
 | |
|   const char *fmt_str =
 | |
|       "lldb.debugger = lldb.SBDebugger.FindDebuggerWithID({0}); "
 | |
|       "lldb.target = lldb.debugger:GetSelectedTarget(); "
 | |
|       "lldb.process = lldb.target:GetProcess(); "
 | |
|       "lldb.thread = lldb.process:GetSelectedThread(); "
 | |
|       "lldb.frame = lldb.thread:GetSelectedFrame()";
 | |
|   return m_lua->Run(llvm::formatv(fmt_str, debugger_id).str());
 | |
| }
 | |
| 
 | |
| llvm::Error ScriptInterpreterLua::LeaveSession() {
 | |
|   if (!m_session_is_active)
 | |
|     return llvm::Error::success();
 | |
| 
 | |
|   m_session_is_active = false;
 | |
| 
 | |
|   llvm::StringRef str = "lldb.debugger = nil; "
 | |
|                         "lldb.target = nil; "
 | |
|                         "lldb.process = nil; "
 | |
|                         "lldb.thread = nil; "
 | |
|                         "lldb.frame = nil";
 | |
|   return m_lua->Run(str);
 | |
| }
 | |
| 
 | |
| bool ScriptInterpreterLua::BreakpointCallbackFunction(
 | |
|     void *baton, StoppointCallbackContext *context, user_id_t break_id,
 | |
|     user_id_t break_loc_id) {
 | |
|   assert(context);
 | |
| 
 | |
|   ExecutionContext exe_ctx(context->exe_ctx_ref);
 | |
|   Target *target = exe_ctx.GetTargetPtr();
 | |
|   if (target == nullptr)
 | |
|     return true;
 | |
| 
 | |
|   StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP());
 | |
|   BreakpointSP breakpoint_sp = target->GetBreakpointByID(break_id);
 | |
|   BreakpointLocationSP bp_loc_sp(breakpoint_sp->FindLocationByID(break_loc_id));
 | |
| 
 | |
|   Debugger &debugger = target->GetDebugger();
 | |
|   ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>(
 | |
|       debugger.GetScriptInterpreter(true, eScriptLanguageLua));
 | |
|   Lua &lua = lua_interpreter->GetLua();
 | |
| 
 | |
|   llvm::Expected<bool> BoolOrErr =
 | |
|       lua.CallBreakpointCallback(baton, stop_frame_sp, bp_loc_sp);
 | |
|   if (llvm::Error E = BoolOrErr.takeError()) {
 | |
|     debugger.GetErrorStream() << toString(std::move(E));
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return *BoolOrErr;
 | |
| }
 | |
| 
 | |
| void ScriptInterpreterLua::CollectDataForBreakpointCommandCallback(
 | |
|     std::vector<BreakpointOptions *> &bp_options_vec,
 | |
|     CommandReturnObject &result) {
 | |
|   IOHandlerSP io_handler_sp(
 | |
|       new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerBreakpoint));
 | |
|   io_handler_sp->SetUserData(&bp_options_vec);
 | |
|   m_debugger.RunIOHandlerAsync(io_handler_sp);
 | |
| }
 | |
| 
 | |
| Status ScriptInterpreterLua::SetBreakpointCommandCallback(
 | |
|     BreakpointOptions *bp_options, const char *command_body_text) {
 | |
|   Status error;
 | |
|   auto data_up = std::make_unique<CommandDataLua>();
 | |
|   error = m_lua->RegisterBreakpointCallback(data_up.get(), command_body_text);
 | |
|   if (error.Fail())
 | |
|     return error;
 | |
|   auto baton_sp =
 | |
|       std::make_shared<BreakpointOptions::CommandBaton>(std::move(data_up));
 | |
|   bp_options->SetCallback(ScriptInterpreterLua::BreakpointCallbackFunction,
 | |
|                           baton_sp);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| lldb::ScriptInterpreterSP
 | |
| ScriptInterpreterLua::CreateInstance(Debugger &debugger) {
 | |
|   return std::make_shared<ScriptInterpreterLua>(debugger);
 | |
| }
 | |
| 
 | |
| lldb_private::ConstString ScriptInterpreterLua::GetPluginNameStatic() {
 | |
|   static ConstString g_name("script-lua");
 | |
|   return g_name;
 | |
| }
 | |
| 
 | |
| const char *ScriptInterpreterLua::GetPluginDescriptionStatic() {
 | |
|   return "Lua script interpreter";
 | |
| }
 | |
| 
 | |
| lldb_private::ConstString ScriptInterpreterLua::GetPluginName() {
 | |
|   return GetPluginNameStatic();
 | |
| }
 | |
| 
 | |
| uint32_t ScriptInterpreterLua::GetPluginVersion() { return 1; }
 | |
| 
 | |
| Lua &ScriptInterpreterLua::GetLua() { return *m_lua; }
 |