1 //===-- ScriptInterpreterLua.cpp ------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "ScriptInterpreterLua.h" 10 #include "Lua.h" 11 #include "lldb/Breakpoint/StoppointCallbackContext.h" 12 #include "lldb/Core/Debugger.h" 13 #include "lldb/Core/PluginManager.h" 14 #include "lldb/Core/StreamFile.h" 15 #include "lldb/Interpreter/CommandReturnObject.h" 16 #include "lldb/Target/ExecutionContext.h" 17 #include "lldb/Utility/Stream.h" 18 #include "lldb/Utility/StringList.h" 19 #include "lldb/Utility/Timer.h" 20 #include "llvm/Support/FormatAdapters.h" 21 #include <memory> 22 23 using namespace lldb; 24 using namespace lldb_private; 25 26 LLDB_PLUGIN_DEFINE(ScriptInterpreterLua) 27 28 class IOHandlerLuaInterpreter : public IOHandlerDelegate, 29 public IOHandlerEditline { 30 public: 31 IOHandlerLuaInterpreter(Debugger &debugger, 32 ScriptInterpreterLua &script_interpreter) 33 : IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua", 34 ">>> ", "..> ", true, debugger.GetUseColor(), 0, 35 *this, nullptr), 36 m_script_interpreter(script_interpreter) { 37 llvm::cantFail(m_script_interpreter.GetLua().ChangeIO( 38 debugger.GetOutputFile().GetStream(), 39 debugger.GetErrorFile().GetStream())); 40 llvm::cantFail(m_script_interpreter.EnterSession(debugger.GetID())); 41 } 42 43 ~IOHandlerLuaInterpreter() override { 44 llvm::cantFail(m_script_interpreter.LeaveSession()); 45 } 46 47 void IOHandlerInputComplete(IOHandler &io_handler, 48 std::string &data) override { 49 if (llvm::StringRef(data).rtrim() == "quit") { 50 io_handler.SetIsDone(true); 51 return; 52 } 53 54 if (llvm::Error error = m_script_interpreter.GetLua().Run(data)) { 55 *GetOutputStreamFileSP() << llvm::toString(std::move(error)); 56 } 57 } 58 59 private: 60 ScriptInterpreterLua &m_script_interpreter; 61 }; 62 63 ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger) 64 : ScriptInterpreter(debugger, eScriptLanguageLua), 65 m_lua(std::make_unique<Lua>()) {} 66 67 ScriptInterpreterLua::~ScriptInterpreterLua() {} 68 69 bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command, 70 CommandReturnObject *result, 71 const ExecuteScriptOptions &options) { 72 if (command.empty()) { 73 if (result) 74 result->AppendError("empty command passed to lua\n"); 75 return false; 76 } 77 78 llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>> 79 io_redirect_or_error = ScriptInterpreterIORedirect::Create( 80 options.GetEnableIO(), m_debugger, result); 81 if (!io_redirect_or_error) { 82 if (result) 83 result->AppendErrorWithFormatv( 84 "failed to redirect I/O: {0}\n", 85 llvm::fmt_consume(io_redirect_or_error.takeError())); 86 else 87 llvm::consumeError(io_redirect_or_error.takeError()); 88 return false; 89 } 90 91 ScriptInterpreterIORedirect &io_redirect = **io_redirect_or_error; 92 93 if (llvm::Error e = 94 m_lua->ChangeIO(io_redirect.GetOutputFile()->GetStream(), 95 io_redirect.GetErrorFile()->GetStream())) { 96 result->AppendErrorWithFormatv("lua failed to redirect I/O: {0}\n", 97 llvm::toString(std::move(e))); 98 return false; 99 } 100 101 if (llvm::Error e = m_lua->Run(command)) { 102 result->AppendErrorWithFormatv( 103 "lua failed attempting to evaluate '{0}': {1}\n", command, 104 llvm::toString(std::move(e))); 105 return false; 106 } 107 108 io_redirect.Flush(); 109 return true; 110 } 111 112 void ScriptInterpreterLua::ExecuteInterpreterLoop() { 113 static Timer::Category func_cat(LLVM_PRETTY_FUNCTION); 114 Timer scoped_timer(func_cat, LLVM_PRETTY_FUNCTION); 115 116 // At the moment, the only time the debugger does not have an input file 117 // handle is when this is called directly from lua, in which case it is 118 // both dangerous and unnecessary (not to mention confusing) to try to embed 119 // a running interpreter loop inside the already running lua interpreter 120 // loop, so we won't do it. 121 if (!m_debugger.GetInputFile().IsValid()) 122 return; 123 124 IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(m_debugger, *this)); 125 m_debugger.RunIOHandlerAsync(io_handler_sp); 126 } 127 128 bool ScriptInterpreterLua::LoadScriptingModule( 129 const char *filename, bool init_session, lldb_private::Status &error, 130 StructuredData::ObjectSP *module_sp, FileSpec extra_search_dir) { 131 132 FileSystem::Instance().Collect(filename); 133 if (llvm::Error e = m_lua->LoadModule(filename)) { 134 error.SetErrorStringWithFormatv("lua failed to import '{0}': {1}\n", 135 filename, llvm::toString(std::move(e))); 136 return false; 137 } 138 return true; 139 } 140 141 void ScriptInterpreterLua::Initialize() { 142 static llvm::once_flag g_once_flag; 143 144 llvm::call_once(g_once_flag, []() { 145 PluginManager::RegisterPlugin(GetPluginNameStatic(), 146 GetPluginDescriptionStatic(), 147 lldb::eScriptLanguageLua, CreateInstance); 148 }); 149 } 150 151 void ScriptInterpreterLua::Terminate() {} 152 153 llvm::Error ScriptInterpreterLua::EnterSession(user_id_t debugger_id) { 154 if (m_session_is_active) 155 return llvm::Error::success(); 156 157 const char *fmt_str = 158 "lldb.debugger = lldb.SBDebugger.FindDebuggerWithID({0}); " 159 "lldb.target = lldb.debugger:GetSelectedTarget(); " 160 "lldb.process = lldb.target:GetProcess(); " 161 "lldb.thread = lldb.process:GetSelectedThread(); " 162 "lldb.frame = lldb.thread:GetSelectedFrame()"; 163 return m_lua->Run(llvm::formatv(fmt_str, debugger_id).str()); 164 } 165 166 llvm::Error ScriptInterpreterLua::LeaveSession() { 167 if (!m_session_is_active) 168 return llvm::Error::success(); 169 170 m_session_is_active = false; 171 172 llvm::StringRef str = "lldb.debugger = nil; " 173 "lldb.target = nil; " 174 "lldb.process = nil; " 175 "lldb.thread = nil; " 176 "lldb.frame = nil"; 177 return m_lua->Run(str); 178 } 179 180 bool ScriptInterpreterLua::BreakpointCallbackFunction( 181 void *baton, StoppointCallbackContext *context, user_id_t break_id, 182 user_id_t break_loc_id) { 183 assert(context); 184 185 ExecutionContext exe_ctx(context->exe_ctx_ref); 186 Target *target = exe_ctx.GetTargetPtr(); 187 if (target == nullptr) 188 return true; 189 190 StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP()); 191 BreakpointSP breakpoint_sp = target->GetBreakpointByID(break_id); 192 BreakpointLocationSP bp_loc_sp(breakpoint_sp->FindLocationByID(break_loc_id)); 193 194 Debugger &debugger = target->GetDebugger(); 195 ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>( 196 debugger.GetScriptInterpreter(true, eScriptLanguageLua)); 197 Lua &lua = lua_interpreter->GetLua(); 198 199 llvm::Expected<bool> BoolOrErr = 200 lua.CallBreakpointCallback(baton, stop_frame_sp, bp_loc_sp); 201 if (llvm::Error E = BoolOrErr.takeError()) { 202 debugger.GetErrorStream() << toString(std::move(E)); 203 return true; 204 } 205 206 return *BoolOrErr; 207 } 208 209 Status ScriptInterpreterLua::SetBreakpointCommandCallback( 210 BreakpointOptions *bp_options, const char *command_body_text) { 211 Status error; 212 auto data_up = std::make_unique<CommandDataLua>(); 213 error = m_lua->RegisterBreakpointCallback(data_up.get(), command_body_text); 214 if (error.Fail()) 215 return error; 216 auto baton_sp = 217 std::make_shared<BreakpointOptions::CommandBaton>(std::move(data_up)); 218 bp_options->SetCallback(ScriptInterpreterLua::BreakpointCallbackFunction, 219 baton_sp); 220 return error; 221 } 222 223 lldb::ScriptInterpreterSP 224 ScriptInterpreterLua::CreateInstance(Debugger &debugger) { 225 return std::make_shared<ScriptInterpreterLua>(debugger); 226 } 227 228 lldb_private::ConstString ScriptInterpreterLua::GetPluginNameStatic() { 229 static ConstString g_name("script-lua"); 230 return g_name; 231 } 232 233 const char *ScriptInterpreterLua::GetPluginDescriptionStatic() { 234 return "Lua script interpreter"; 235 } 236 237 lldb_private::ConstString ScriptInterpreterLua::GetPluginName() { 238 return GetPluginNameStatic(); 239 } 240 241 uint32_t ScriptInterpreterLua::GetPluginVersion() { return 1; } 242 243 Lua &ScriptInterpreterLua::GetLua() { return *m_lua; } 244