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/Host/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 llvm::StringRef(">>> "), llvm::StringRef("..> "), 44 true, debugger.GetUseColor(), 0, *this), 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 break; 62 case eIOHandlerWatchpoint: 63 instructions = "Enter your Lua command(s). Type 'quit' to end.\n" 64 "The commands are compiled as the body of the following " 65 "Lua function\n" 66 "function (frame, wp) end\n"; 67 SetPrompt(llvm::StringRef("..> ")); 68 break; 69 case eIOHandlerBreakpoint: 70 instructions = "Enter your Lua command(s). Type 'quit' to end.\n" 71 "The commands are compiled as the body of the following " 72 "Lua function\n" 73 "function (frame, bp_loc, ...) end\n"; 74 SetPrompt(llvm::StringRef("..> ")); 75 break; 76 } 77 if (instructions == nullptr) 78 return; 79 if (interactive) 80 *io_handler.GetOutputStreamFileSP() << instructions; 81 } 82 83 bool IOHandlerIsInputComplete(IOHandler &io_handler, 84 StringList &lines) override { 85 size_t last = lines.GetSize() - 1; 86 if (IsQuitCommand(lines.GetStringAtIndex(last))) { 87 if (m_active_io_handler == eIOHandlerBreakpoint || 88 m_active_io_handler == eIOHandlerWatchpoint) 89 lines.DeleteStringAtIndex(last); 90 return true; 91 } 92 StreamString str; 93 lines.Join("\n", str); 94 if (llvm::Error E = 95 m_script_interpreter.GetLua().CheckSyntax(str.GetString())) { 96 std::string error_str = toString(std::move(E)); 97 // Lua always errors out to incomplete code with '<eof>' 98 return error_str.find("<eof>") == std::string::npos; 99 } 100 // The breakpoint and watchpoint handler only exits with a explicit 'quit' 101 return m_active_io_handler != eIOHandlerBreakpoint && 102 m_active_io_handler != eIOHandlerWatchpoint; 103 } 104 105 void IOHandlerInputComplete(IOHandler &io_handler, 106 std::string &data) override { 107 switch (m_active_io_handler) { 108 case eIOHandlerBreakpoint: { 109 auto *bp_options_vec = 110 static_cast<std::vector<std::reference_wrapper<BreakpointOptions>> *>( 111 io_handler.GetUserData()); 112 for (BreakpointOptions &bp_options : *bp_options_vec) { 113 Status error = m_script_interpreter.SetBreakpointCommandCallback( 114 bp_options, data.c_str(), /*is_callback=*/false); 115 if (error.Fail()) 116 *io_handler.GetErrorStreamFileSP() << error.AsCString() << '\n'; 117 } 118 io_handler.SetIsDone(true); 119 } break; 120 case eIOHandlerWatchpoint: { 121 auto *wp_options = 122 static_cast<WatchpointOptions *>(io_handler.GetUserData()); 123 m_script_interpreter.SetWatchpointCommandCallback(wp_options, 124 data.c_str(), 125 /*is_callback=*/false); 126 io_handler.SetIsDone(true); 127 } break; 128 case eIOHandlerNone: 129 if (IsQuitCommand(data)) { 130 io_handler.SetIsDone(true); 131 return; 132 } 133 if (llvm::Error error = m_script_interpreter.GetLua().Run(data)) 134 *io_handler.GetErrorStreamFileSP() << toString(std::move(error)); 135 break; 136 } 137 } 138 139 private: 140 ScriptInterpreterLua &m_script_interpreter; 141 ActiveIOHandler m_active_io_handler; 142 143 bool IsQuitCommand(llvm::StringRef cmd) { return cmd.rtrim() == "quit"; } 144 }; 145 146 ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger) 147 : ScriptInterpreter(debugger, eScriptLanguageLua), 148 m_lua(std::make_unique<Lua>()) {} 149 150 ScriptInterpreterLua::~ScriptInterpreterLua() = default; 151 152 StructuredData::DictionarySP ScriptInterpreterLua::GetInterpreterInfo() { 153 auto info = std::make_shared<StructuredData::Dictionary>(); 154 info->AddStringItem("language", "lua"); 155 return info; 156 } 157 158 bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command, 159 CommandReturnObject *result, 160 const ExecuteScriptOptions &options) { 161 if (command.empty()) { 162 if (result) 163 result->AppendError("empty command passed to lua\n"); 164 return false; 165 } 166 167 llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>> 168 io_redirect_or_error = ScriptInterpreterIORedirect::Create( 169 options.GetEnableIO(), m_debugger, result); 170 if (!io_redirect_or_error) { 171 if (result) 172 result->AppendErrorWithFormatv( 173 "failed to redirect I/O: {0}\n", 174 llvm::fmt_consume(io_redirect_or_error.takeError())); 175 else 176 llvm::consumeError(io_redirect_or_error.takeError()); 177 return false; 178 } 179 180 ScriptInterpreterIORedirect &io_redirect = **io_redirect_or_error; 181 182 if (llvm::Error e = 183 m_lua->ChangeIO(io_redirect.GetOutputFile()->GetStream(), 184 io_redirect.GetErrorFile()->GetStream())) { 185 result->AppendErrorWithFormatv("lua failed to redirect I/O: {0}\n", 186 llvm::toString(std::move(e))); 187 return false; 188 } 189 190 if (llvm::Error e = m_lua->Run(command)) { 191 result->AppendErrorWithFormatv( 192 "lua failed attempting to evaluate '{0}': {1}\n", command, 193 llvm::toString(std::move(e))); 194 return false; 195 } 196 197 io_redirect.Flush(); 198 return true; 199 } 200 201 void ScriptInterpreterLua::ExecuteInterpreterLoop() { 202 LLDB_SCOPED_TIMER(); 203 204 // At the moment, the only time the debugger does not have an input file 205 // handle is when this is called directly from lua, in which case it is 206 // both dangerous and unnecessary (not to mention confusing) to try to embed 207 // a running interpreter loop inside the already running lua interpreter 208 // loop, so we won't do it. 209 if (!m_debugger.GetInputFile().IsValid()) 210 return; 211 212 IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(m_debugger, *this)); 213 m_debugger.RunIOHandlerAsync(io_handler_sp); 214 } 215 216 bool ScriptInterpreterLua::LoadScriptingModule( 217 const char *filename, const LoadScriptOptions &options, 218 lldb_private::Status &error, StructuredData::ObjectSP *module_sp, 219 FileSpec extra_search_dir) { 220 221 if (llvm::Error e = m_lua->LoadModule(filename)) { 222 error = Status::FromErrorStringWithFormatv( 223 "lua failed to import '{0}': {1}\n", filename, 224 llvm::toString(std::move(e))); 225 return false; 226 } 227 return true; 228 } 229 230 void ScriptInterpreterLua::Initialize() { 231 static llvm::once_flag g_once_flag; 232 233 llvm::call_once(g_once_flag, []() { 234 PluginManager::RegisterPlugin(GetPluginNameStatic(), 235 GetPluginDescriptionStatic(), 236 lldb::eScriptLanguageLua, CreateInstance); 237 }); 238 } 239 240 void ScriptInterpreterLua::Terminate() {} 241 242 llvm::Error ScriptInterpreterLua::EnterSession(user_id_t debugger_id) { 243 if (m_session_is_active) 244 return llvm::Error::success(); 245 246 const char *fmt_str = 247 "lldb.debugger = lldb.SBDebugger.FindDebuggerWithID({0}); " 248 "lldb.target = lldb.debugger:GetSelectedTarget(); " 249 "lldb.process = lldb.target:GetProcess(); " 250 "lldb.thread = lldb.process:GetSelectedThread(); " 251 "lldb.frame = lldb.thread:GetSelectedFrame()"; 252 return m_lua->Run(llvm::formatv(fmt_str, debugger_id).str()); 253 } 254 255 llvm::Error ScriptInterpreterLua::LeaveSession() { 256 if (!m_session_is_active) 257 return llvm::Error::success(); 258 259 m_session_is_active = false; 260 261 llvm::StringRef str = "lldb.debugger = nil; " 262 "lldb.target = nil; " 263 "lldb.process = nil; " 264 "lldb.thread = nil; " 265 "lldb.frame = nil"; 266 return m_lua->Run(str); 267 } 268 269 bool ScriptInterpreterLua::BreakpointCallbackFunction( 270 void *baton, StoppointCallbackContext *context, user_id_t break_id, 271 user_id_t break_loc_id) { 272 assert(context); 273 274 ExecutionContext exe_ctx(context->exe_ctx_ref); 275 Target *target = exe_ctx.GetTargetPtr(); 276 if (target == nullptr) 277 return true; 278 279 StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP()); 280 BreakpointSP breakpoint_sp = target->GetBreakpointByID(break_id); 281 BreakpointLocationSP bp_loc_sp(breakpoint_sp->FindLocationByID(break_loc_id)); 282 283 Debugger &debugger = target->GetDebugger(); 284 ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>( 285 debugger.GetScriptInterpreter(true, eScriptLanguageLua)); 286 Lua &lua = lua_interpreter->GetLua(); 287 288 CommandDataLua *bp_option_data = static_cast<CommandDataLua *>(baton); 289 llvm::Expected<bool> BoolOrErr = lua.CallBreakpointCallback( 290 baton, stop_frame_sp, bp_loc_sp, bp_option_data->m_extra_args_sp); 291 if (llvm::Error E = BoolOrErr.takeError()) { 292 debugger.GetErrorStream() << toString(std::move(E)); 293 return true; 294 } 295 296 return *BoolOrErr; 297 } 298 299 bool ScriptInterpreterLua::WatchpointCallbackFunction( 300 void *baton, StoppointCallbackContext *context, user_id_t watch_id) { 301 assert(context); 302 303 ExecutionContext exe_ctx(context->exe_ctx_ref); 304 Target *target = exe_ctx.GetTargetPtr(); 305 if (target == nullptr) 306 return true; 307 308 StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP()); 309 WatchpointSP wp_sp = target->GetWatchpointList().FindByID(watch_id); 310 311 Debugger &debugger = target->GetDebugger(); 312 ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>( 313 debugger.GetScriptInterpreter(true, eScriptLanguageLua)); 314 Lua &lua = lua_interpreter->GetLua(); 315 316 llvm::Expected<bool> BoolOrErr = 317 lua.CallWatchpointCallback(baton, stop_frame_sp, wp_sp); 318 if (llvm::Error E = BoolOrErr.takeError()) { 319 debugger.GetErrorStream() << toString(std::move(E)); 320 return true; 321 } 322 323 return *BoolOrErr; 324 } 325 326 void ScriptInterpreterLua::CollectDataForBreakpointCommandCallback( 327 std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec, 328 CommandReturnObject &result) { 329 IOHandlerSP io_handler_sp( 330 new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerBreakpoint)); 331 io_handler_sp->SetUserData(&bp_options_vec); 332 m_debugger.RunIOHandlerAsync(io_handler_sp); 333 } 334 335 void ScriptInterpreterLua::CollectDataForWatchpointCommandCallback( 336 WatchpointOptions *wp_options, CommandReturnObject &result) { 337 IOHandlerSP io_handler_sp( 338 new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerWatchpoint)); 339 io_handler_sp->SetUserData(wp_options); 340 m_debugger.RunIOHandlerAsync(io_handler_sp); 341 } 342 343 Status ScriptInterpreterLua::SetBreakpointCommandCallbackFunction( 344 BreakpointOptions &bp_options, const char *function_name, 345 StructuredData::ObjectSP extra_args_sp) { 346 const char *fmt_str = "return {0}(frame, bp_loc, ...)"; 347 std::string oneliner = llvm::formatv(fmt_str, function_name).str(); 348 return RegisterBreakpointCallback(bp_options, oneliner.c_str(), 349 extra_args_sp); 350 } 351 352 Status ScriptInterpreterLua::SetBreakpointCommandCallback( 353 BreakpointOptions &bp_options, const char *command_body_text, 354 bool is_callback) { 355 return RegisterBreakpointCallback(bp_options, command_body_text, {}); 356 } 357 358 Status ScriptInterpreterLua::RegisterBreakpointCallback( 359 BreakpointOptions &bp_options, const char *command_body_text, 360 StructuredData::ObjectSP extra_args_sp) { 361 auto data_up = std::make_unique<CommandDataLua>(extra_args_sp); 362 llvm::Error err = 363 m_lua->RegisterBreakpointCallback(data_up.get(), command_body_text); 364 if (err) 365 return Status::FromError(std::move(err)); 366 auto baton_sp = 367 std::make_shared<BreakpointOptions::CommandBaton>(std::move(data_up)); 368 bp_options.SetCallback(ScriptInterpreterLua::BreakpointCallbackFunction, 369 baton_sp); 370 return {}; 371 } 372 373 void ScriptInterpreterLua::SetWatchpointCommandCallback( 374 WatchpointOptions *wp_options, const char *command_body_text, 375 bool is_callback) { 376 RegisterWatchpointCallback(wp_options, command_body_text, {}); 377 } 378 379 Status ScriptInterpreterLua::RegisterWatchpointCallback( 380 WatchpointOptions *wp_options, const char *command_body_text, 381 StructuredData::ObjectSP extra_args_sp) { 382 auto data_up = std::make_unique<WatchpointOptions::CommandData>(); 383 llvm::Error err = 384 m_lua->RegisterWatchpointCallback(data_up.get(), command_body_text); 385 if (err) 386 return Status::FromError(std::move(err)); 387 auto baton_sp = 388 std::make_shared<WatchpointOptions::CommandBaton>(std::move(data_up)); 389 wp_options->SetCallback(ScriptInterpreterLua::WatchpointCallbackFunction, 390 baton_sp); 391 return {}; 392 } 393 394 lldb::ScriptInterpreterSP 395 ScriptInterpreterLua::CreateInstance(Debugger &debugger) { 396 return std::make_shared<ScriptInterpreterLua>(debugger); 397 } 398 399 llvm::StringRef ScriptInterpreterLua::GetPluginDescriptionStatic() { 400 return "Lua script interpreter"; 401 } 402 403 Lua &ScriptInterpreterLua::GetLua() { return *m_lua; } 404