diff --git a/lldb/bindings/lua/lua-swigsafecast.swig b/lldb/bindings/lua/lua-swigsafecast.swig new file mode 100644 --- /dev/null +++ b/lldb/bindings/lua/lua-swigsafecast.swig @@ -0,0 +1,17 @@ +template +void +SBTypeToSWIGWrapper (lua_State *L, SBClass* sb_object); + +template<> +void +SBTypeToSWIGWrapper (lua_State* L, lldb::SBFrame* frame_sb) +{ + SWIG_NewPointerObj(L, (void *) frame_sb, SWIGTYPE_p_lldb__SBFrame, 0); +} + +template<> +void +SBTypeToSWIGWrapper (lua_State* L, lldb::SBBreakpointLocation* breakpoint_location_sb) +{ + SWIG_NewPointerObj(L, (void *) breakpoint_location_sb, SWIGTYPE_p_lldb__SBBreakpointLocation, 0); +} diff --git a/lldb/bindings/lua/lua-wrapper.swig b/lldb/bindings/lua/lua-wrapper.swig new file mode 100644 --- /dev/null +++ b/lldb/bindings/lua/lua-wrapper.swig @@ -0,0 +1,35 @@ +%header %{ + +template +void +SBTypeToSWIGWrapper (lua_State* L, T* obj); + +%} + +%wrapper %{ + +SWIGEXPORT bool +LLDBSwigLuaBreakpointCallbackFunction +( + lua_State *L, + void* baton, + const lldb::StackFrameSP& frame_sp, + const lldb::BreakpointLocationSP& bp_loc_sp +) +{ + lldb::SBFrame sb_frame (frame_sp); + lldb::SBBreakpointLocation sb_bp_loc(bp_loc_sp); + + lua_pushlightuserdata(L, baton); + lua_gettable(L, LUA_REGISTRYINDEX); + + SBTypeToSWIGWrapper(L, &sb_frame); + SBTypeToSWIGWrapper(L, &sb_bp_loc); + + lua_call(L, 2, 1); + + return lua_toboolean(L, -1); +} + + +%} diff --git a/lldb/bindings/lua/lua.swig b/lldb/bindings/lua/lua.swig --- a/lldb/bindings/lua/lua.swig +++ b/lldb/bindings/lua/lua.swig @@ -14,8 +14,10 @@ %include "headers.swig" %{ +#include "../bindings/lua/lua-swigsafecast.swig" using namespace lldb_private; using namespace lldb; %} %include "interfaces.swig" +%include "lua-wrapper.swig" diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h --- a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h @@ -15,6 +15,7 @@ #include "lua.hpp" +#include #include namespace lldb_private { @@ -23,12 +24,15 @@ int luaopen_lldb(lua_State *L); } +using LuaCallback = std::function; + class Lua { public: Lua(); ~Lua(); llvm::Error Run(llvm::StringRef buffer); + llvm::Error Run(LuaCallback &callback); llvm::Error LoadModule(llvm::StringRef filename); llvm::Error ChangeIO(FILE *out, FILE *err); diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp @@ -57,6 +57,26 @@ return e; } +static int runCallback(lua_State *L) { + LuaCallback *callback = static_cast(lua_touserdata(L, -1)); + return (*callback)(L); +} + +llvm::Error Lua::Run(LuaCallback &callback) { + lua_pushcfunction(m_lua_state, runCallback); + lua_pushlightuserdata(m_lua_state, &callback); + int error = lua_pcall(m_lua_state, 1, 0, 0); + if (error == LUA_OK) + return llvm::Error::success(); + + llvm::Error e = llvm::make_error( + llvm::formatv("{0}\n", lua_tostring(m_lua_state, -1)), + llvm::inconvertibleErrorCode()); + // Pop error message from the stack. + lua_pop(m_lua_state, 1); + return e; +} + llvm::Error Lua::LoadModule(llvm::StringRef filename) { FileSpec file(filename); if (!FileSystem::Instance().Exists(file)) { diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h --- a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h @@ -10,11 +10,20 @@ #define liblldb_ScriptInterpreterLua_h_ #include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-enumerations.h" namespace lldb_private { class Lua; class ScriptInterpreterLua : public ScriptInterpreter { public: + class CommandDataLua : public BreakpointOptions::CommandData { + public: + CommandDataLua() : BreakpointOptions::CommandData() { + interpreter = lldb::eScriptLanguageLua; + } + }; + ScriptInterpreterLua(Debugger &debugger); ~ScriptInterpreterLua() override; @@ -41,6 +50,11 @@ static const char *GetPluginDescriptionStatic(); + static bool BreakpointCallbackFunction(void *baton, + StoppointCallbackContext *context, + lldb::user_id_t break_id, + lldb::user_id_t break_loc_id); + // PluginInterface protocol lldb_private::ConstString GetPluginName() override; @@ -51,6 +65,9 @@ llvm::Error EnterSession(lldb::user_id_t debugger_id); llvm::Error LeaveSession(); + Status SetBreakpointCommandCallback(BreakpointOptions *bp_options, + const char *command_body_text) override; + private: std::unique_ptr m_lua; bool m_session_is_active = false; diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp @@ -8,18 +8,29 @@ #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/StreamString.h" #include "lldb/Utility/StringList.h" #include "lldb/Utility/Timer.h" +#include "lldb/lldb-forward.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FormatAdapters.h" +#include +#include using namespace lldb; using namespace lldb_private; +extern "C" bool LLDBSwigLuaBreakpointCallbackFunction( + lua_State *L, void *baton, const lldb::StackFrameSP &sb_frame, + const lldb::BreakpointLocationSP &sb_bp_loc); + LLDB_PLUGIN_DEFINE(ScriptInterpreterLua) class IOHandlerLuaInterpreter : public IOHandlerDelegate, @@ -174,6 +185,68 @@ 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(debugger.GetScriptInterpreter()); + + Lua &lua = lua_interpreter->GetLua(); + bool stop = true; + LuaCallback callback = [&](lua_State *L) -> int { + stop = LLDBSwigLuaBreakpointCallbackFunction(L, baton, stop_frame_sp, + bp_loc_sp); + return 0; + }; + + llvm::handleAllErrors(lua.Run(callback), [&](const llvm::StringError &E) { + debugger.GetErrorStream() << E.message(); + stop = true; + }); + + return stop; +} + +Status ScriptInterpreterLua::SetBreakpointCommandCallback( + BreakpointOptions *bp_options, const char *command_body_text) { + Status error; + auto data_up = std::make_unique(); + LuaCallback callback = [&](lua_State *L) -> int { + StreamString sstr; + m_debugger.GetErrorStream() << data_up.get() << '\n'; + lua_pushlightuserdata(L, data_up.get()); + sstr.Printf("return function(frame, bp_loc, ...) %s end", + command_body_text); + if (luaL_dostring(L, sstr.GetData()) != LUA_OK) + return lua_error(L); + lua_settable(L, LUA_REGISTRYINDEX); + return 0; + }; + + error = m_lua->Run(callback); + if (error.Fail()) + return error; + + auto baton_sp = + std::make_shared(std::move(data_up)); + bp_options->SetCallback(ScriptInterpreterLua::BreakpointCallbackFunction, + baton_sp); + + return error; +} + lldb::ScriptInterpreterSP ScriptInterpreterLua::CreateInstance(Debugger &debugger) { return std::make_shared(debugger); diff --git a/lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_oneline_callback.test b/lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_oneline_callback.test new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_oneline_callback.test @@ -0,0 +1,5 @@ +# RUN: %lldb -s %s --script-language lua 2>&1 | FileCheck %s +b main +breakpoint command add -s lua -o 'print(1)' +run +# CHECK: 1 diff --git a/lldb/unittests/ScriptInterpreter/Lua/LuaTests.cpp b/lldb/unittests/ScriptInterpreter/Lua/LuaTests.cpp --- a/lldb/unittests/ScriptInterpreter/Lua/LuaTests.cpp +++ b/lldb/unittests/ScriptInterpreter/Lua/LuaTests.cpp @@ -26,3 +26,27 @@ EXPECT_EQ(llvm::toString(std::move(error)), "[string \"buffer\"]:1: unexpected symbol near 'nil'\n"); } + +TEST(LuaTest, RunCallbackValid) { + Lua lua; + LuaCallback callback = [](lua_State *L) -> int { + if (luaL_dostring(L, "foo = 1") != LUA_OK) + return lua_error(L); + return 0; + }; + llvm::Error error = lua.Run(callback); + EXPECT_FALSE(static_cast(error)); +} + +TEST(LuaTest, RunCallbackInvalid) { + Lua lua; + LuaCallback callback = [](lua_State *L) -> int { + if (luaL_dostring(L, "nil = foo") != LUA_OK) + return lua_error(L); + return 0; + }; + llvm::Error error = lua.Run(callback); + EXPECT_TRUE(static_cast(error)); + EXPECT_EQ(llvm::toString(std::move(error)), + "[string \"nil = foo\"]:1: unexpected symbol near 'nil'\n"); +} diff --git a/lldb/unittests/ScriptInterpreter/Lua/ScriptInterpreterTests.cpp b/lldb/unittests/ScriptInterpreter/Lua/ScriptInterpreterTests.cpp --- a/lldb/unittests/ScriptInterpreter/Lua/ScriptInterpreterTests.cpp +++ b/lldb/unittests/ScriptInterpreter/Lua/ScriptInterpreterTests.cpp @@ -43,6 +43,13 @@ }; } // namespace +struct lua_State; +extern "C" bool LLDBSwigLuaBreakpointCallbackFunction( + lua_State *L, void *baton, const lldb::StackFrameSP &sb_frame, + const lldb::BreakpointLocationSP &sb_bp_loc) { + return false; +} + TEST_F(ScriptInterpreterTest, Plugin) { EXPECT_EQ(ScriptInterpreterLua::GetPluginNameStatic(), "script-lua"); EXPECT_EQ(ScriptInterpreterLua::GetPluginDescriptionStatic(),