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/ADT/StringRef.h" 21 #include "llvm/Support/FormatAdapters.h" 22 #include <memory> 23 #include <vector> 24 25 using namespace lldb; 26 using namespace lldb_private; 27 28 LLDB_PLUGIN_DEFINE(ScriptInterpreterLua) 29 30 enum ActiveIOHandler { 31 eIOHandlerNone, 32 eIOHandlerBreakpoint, 33 eIOHandlerWatchpoint 34 }; 35 36 class IOHandlerLuaInterpreter : public IOHandlerDelegate, 37 public IOHandlerEditline { 38 public: 39 IOHandlerLuaInterpreter(Debugger &debugger, 40 ScriptInterpreterLua &script_interpreter, 41 ActiveIOHandler active_io_handler = eIOHandlerNone) 42 : IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua", 43 ">>> ", "..> ", true, debugger.GetUseColor(), 0, 44 *this, nullptr), 45 m_script_interpreter(script_interpreter), 46 m_active_io_handler(active_io_handler) { 47 llvm::cantFail(m_script_interpreter.GetLua().ChangeIO( 48 debugger.GetOutputFile().GetStream(), 49 debugger.GetErrorFile().GetStream())); 50 llvm::cantFail(m_script_interpreter.EnterSession(debugger.GetID())); 51 } 52 53 ~IOHandlerLuaInterpreter() override { 54 llvm::cantFail(m_script_interpreter.LeaveSession()); 55 } 56 57 void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { 58 const char *instructions = nullptr; 59 switch (m_active_io_handler) { 60 case eIOHandlerNone: 61 case eIOHandlerWatchpoint: 62 break; 63 case eIOHandlerBreakpoint: 64 instructions = "Enter your Lua command(s). Type 'quit' to end.\n" 65 "The commands are compiled as the body of the following " 66 "Lua function\n" 67 "function (frame, bp_loc, ...) end\n"; 68 SetPrompt(llvm::StringRef("..> ")); 69 break; 70 } 71 if (instructions == nullptr) 72 return; 73 if (interactive) 74 *io_handler.GetOutputStreamFileSP() << instructions; 75 } 76 77 bool IOHandlerIsInputComplete(IOHandler &io_handler, 78 StringList &lines) override { 79 size_t last = lines.GetSize() - 1; 80 if (IsQuitCommand(lines.GetStringAtIndex(last))) { 81 if (m_active_io_handler == eIOHandlerBreakpoint) 82 lines.DeleteStringAtIndex(last); 83 return true; 84 } 85 StreamString str; 86 lines.Join("\n", str); 87 if (llvm::Error E = 88 m_script_interpreter.GetLua().CheckSyntax(str.GetString())) { 89 std::string error_str = toString(std::move(E)); 90 // Lua always errors out to incomplete code with '<eof>' 91 return error_str.find("<eof>") == std::string::npos; 92 } 93 // The breakpoint handler only exits with a explicit 'quit' 94 return m_active_io_handler != eIOHandlerBreakpoint; 95 } 96 97 void IOHandlerInputComplete(IOHandler &io_handler, 98 std::string &data) override { 99 switch (m_active_io_handler) { 100 case eIOHandlerBreakpoint: { 101 auto *bp_options_vec = 102 static_cast<std::vector<std::reference_wrapper<BreakpointOptions>> *>( 103 io_handler.GetUserData()); 104 for (BreakpointOptions &bp_options : *bp_options_vec) { 105 Status error = m_script_interpreter.SetBreakpointCommandCallback( 106 bp_options, data.c_str()); 107 if (error.Fail()) 108 *io_handler.GetErrorStreamFileSP() << error.AsCString() << '\n'; 109 } 110 io_handler.SetIsDone(true); 111 } break; 112 case eIOHandlerWatchpoint: 113 io_handler.SetIsDone(true); 114 break; 115 case eIOHandlerNone: 116 if (IsQuitCommand(data)) { 117 io_handler.SetIsDone(true); 118 return; 119 } 120 if (llvm::Error error = m_script_interpreter.GetLua().Run(data)) 121 *io_handler.GetErrorStreamFileSP() << toString(std::move(error)); 122 break; 123 } 124 } 125 126 private: 127 ScriptInterpreterLua &m_script_interpreter; 128 ActiveIOHandler m_active_io_handler; 129 130 bool IsQuitCommand(llvm::StringRef cmd) { return cmd.rtrim() == "quit"; } 131 }; 132 133 ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger) 134 : ScriptInterpreter(debugger, eScriptLanguageLua), 135 m_lua(std::make_unique<Lua>()) {} 136 137 ScriptInterpreterLua::~ScriptInterpreterLua() = default; 138 139 bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command, 140 CommandReturnObject *result, 141 const ExecuteScriptOptions &options) { 142 if (command.empty()) { 143 if (result) 144 result->AppendError("empty command passed to lua\n"); 145 return false; 146 } 147 148 llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>> 149 io_redirect_or_error = ScriptInterpreterIORedirect::Create( 150 options.GetEnableIO(), m_debugger, result); 151 if (!io_redirect_or_error) { 152 if (result) 153 result->AppendErrorWithFormatv( 154 "failed to redirect I/O: {0}\n", 155 llvm::fmt_consume(io_redirect_or_error.takeError())); 156 else 157 llvm::consumeError(io_redirect_or_error.takeError()); 158 return false; 159 } 160 161 ScriptInterpreterIORedirect &io_redirect = **io_redirect_or_error; 162 163 if (llvm::Error e = 164 m_lua->ChangeIO(io_redirect.GetOutputFile()->GetStream(), 165 io_redirect.GetErrorFile()->GetStream())) { 166 result->AppendErrorWithFormatv("lua failed to redirect I/O: {0}\n", 167 llvm::toString(std::move(e))); 168 return false; 169 } 170 171 if (llvm::Error e = m_lua->Run(command)) { 172 result->AppendErrorWithFormatv( 173 "lua failed attempting to evaluate '{0}': {1}\n", command, 174 llvm::toString(std::move(e))); 175 return false; 176 } 177 178 io_redirect.Flush(); 179 return true; 180 } 181 182 void ScriptInterpreterLua::ExecuteInterpreterLoop() { 183 LLDB_SCOPED_TIMER(); 184 185 // At the moment, the only time the debugger does not have an input file 186 // handle is when this is called directly from lua, in which case it is 187 // both dangerous and unnecessary (not to mention confusing) to try to embed 188 // a running interpreter loop inside the already running lua interpreter 189 // loop, so we won't do it. 190 if (!m_debugger.GetInputFile().IsValid()) 191 return; 192 193 IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(m_debugger, *this)); 194 m_debugger.RunIOHandlerAsync(io_handler_sp); 195 } 196 197 bool ScriptInterpreterLua::LoadScriptingModule( 198 const char *filename, bool init_session, lldb_private::Status &error, 199 StructuredData::ObjectSP *module_sp, FileSpec extra_search_dir) { 200 201 FileSystem::Instance().Collect(filename); 202 if (llvm::Error e = m_lua->LoadModule(filename)) { 203 error.SetErrorStringWithFormatv("lua failed to import '{0}': {1}\n", 204 filename, llvm::toString(std::move(e))); 205 return false; 206 } 207 return true; 208 } 209 210 void ScriptInterpreterLua::Initialize() { 211 static llvm::once_flag g_once_flag; 212 213 llvm::call_once(g_once_flag, []() { 214 PluginManager::RegisterPlugin(GetPluginNameStatic(), 215 GetPluginDescriptionStatic(), 216 lldb::eScriptLanguageLua, CreateInstance); 217 }); 218 } 219 220 void ScriptInterpreterLua::Terminate() {} 221 222 llvm::Error ScriptInterpreterLua::EnterSession(user_id_t debugger_id) { 223 if (m_session_is_active) 224 return llvm::Error::success(); 225 226 const char *fmt_str = 227 "lldb.debugger = lldb.SBDebugger.FindDebuggerWithID({0}); " 228 "lldb.target = lldb.debugger:GetSelectedTarget(); " 229 "lldb.process = lldb.target:GetProcess(); " 230 "lldb.thread = lldb.process:GetSelectedThread(); " 231 "lldb.frame = lldb.thread:GetSelectedFrame()"; 232 return m_lua->Run(llvm::formatv(fmt_str, debugger_id).str()); 233 } 234 235 llvm::Error ScriptInterpreterLua::LeaveSession() { 236 if (!m_session_is_active) 237 return llvm::Error::success(); 238 239 m_session_is_active = false; 240 241 llvm::StringRef str = "lldb.debugger = nil; " 242 "lldb.target = nil; " 243 "lldb.process = nil; " 244 "lldb.thread = nil; " 245 "lldb.frame = nil"; 246 return m_lua->Run(str); 247 } 248 249 bool ScriptInterpreterLua::BreakpointCallbackFunction( 250 void *baton, StoppointCallbackContext *context, user_id_t break_id, 251 user_id_t break_loc_id) { 252 assert(context); 253 254 ExecutionContext exe_ctx(context->exe_ctx_ref); 255 Target *target = exe_ctx.GetTargetPtr(); 256 if (target == nullptr) 257 return true; 258 259 StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP()); 260 BreakpointSP breakpoint_sp = target->GetBreakpointByID(break_id); 261 BreakpointLocationSP bp_loc_sp(breakpoint_sp->FindLocationByID(break_loc_id)); 262 263 Debugger &debugger = target->GetDebugger(); 264 ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>( 265 debugger.GetScriptInterpreter(true, eScriptLanguageLua)); 266 Lua &lua = lua_interpreter->GetLua(); 267 268 CommandDataLua *bp_option_data = static_cast<CommandDataLua *>(baton); 269 llvm::Expected<bool> BoolOrErr = lua.CallBreakpointCallback( 270 baton, stop_frame_sp, bp_loc_sp, bp_option_data->m_extra_args_sp); 271 if (llvm::Error E = BoolOrErr.takeError()) { 272 debugger.GetErrorStream() << toString(std::move(E)); 273 return true; 274 } 275 276 return *BoolOrErr; 277 } 278 279 void ScriptInterpreterLua::CollectDataForBreakpointCommandCallback( 280 std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec, 281 CommandReturnObject &result) { 282 IOHandlerSP io_handler_sp( 283 new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerBreakpoint)); 284 io_handler_sp->SetUserData(&bp_options_vec); 285 m_debugger.RunIOHandlerAsync(io_handler_sp); 286 } 287 288 Status ScriptInterpreterLua::SetBreakpointCommandCallbackFunction( 289 BreakpointOptions &bp_options, const char *function_name, 290 StructuredData::ObjectSP extra_args_sp) { 291 const char *fmt_str = "return {0}(frame, bp_loc, ...)"; 292 std::string oneliner = llvm::formatv(fmt_str, function_name).str(); 293 return RegisterBreakpointCallback(bp_options, oneliner.c_str(), 294 extra_args_sp); 295 } 296 297 Status ScriptInterpreterLua::SetBreakpointCommandCallback( 298 BreakpointOptions &bp_options, const char *command_body_text) { 299 return RegisterBreakpointCallback(bp_options, command_body_text, {}); 300 } 301 302 Status ScriptInterpreterLua::RegisterBreakpointCallback( 303 BreakpointOptions &bp_options, const char *command_body_text, 304 StructuredData::ObjectSP extra_args_sp) { 305 Status error; 306 auto data_up = std::make_unique<CommandDataLua>(extra_args_sp); 307 error = m_lua->RegisterBreakpointCallback(data_up.get(), command_body_text); 308 if (error.Fail()) 309 return error; 310 auto baton_sp = 311 std::make_shared<BreakpointOptions::CommandBaton>(std::move(data_up)); 312 bp_options.SetCallback(ScriptInterpreterLua::BreakpointCallbackFunction, 313 baton_sp); 314 return error; 315 } 316 317 lldb::ScriptInterpreterSP 318 ScriptInterpreterLua::CreateInstance(Debugger &debugger) { 319 return std::make_shared<ScriptInterpreterLua>(debugger); 320 } 321 322 lldb_private::ConstString ScriptInterpreterLua::GetPluginNameStatic() { 323 static ConstString g_name("script-lua"); 324 return g_name; 325 } 326 327 const char *ScriptInterpreterLua::GetPluginDescriptionStatic() { 328 return "Lua script interpreter"; 329 } 330 331 lldb_private::ConstString ScriptInterpreterLua::GetPluginName() { 332 return GetPluginNameStatic(); 333 } 334 335 uint32_t ScriptInterpreterLua::GetPluginVersion() { return 1; } 336 337 Lua &ScriptInterpreterLua::GetLua() { return *m_lua; } 338