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,40 @@ +template +llvm::Error +PushSBClass (lua_State* L, SBClass* obj); + +llvm::Error +DoLuaPcall(lua_State *L, int nargs, int nresults) { + if (lua_pcall(L, nargs, nresults, 0) != LUA_OK) { + llvm::Error E = llvm::make_error( + llvm::formatv("{0}\n", lua_tostring(L, -1)), + llvm::inconvertibleErrorCode()); + // Pop error message from the stack. + lua_pop(L, 1); + return std::move(E); + } + return llvm::Error::success(); +} + +llvm::Error +PushSBClass (lua_State* L, lldb::SBFrame* frame_sb) +{ + lua_pushcfunction(L, [](lua_State *L) -> int { + void *frame_sb = lua_touserdata(L, -1); + SWIG_NewPointerObj(L, frame_sb, SWIGTYPE_p_lldb__SBFrame, 0); + return 1; // swig wrapper + }); + lua_pushlightuserdata(L, (void *)frame_sb); + return DoLuaPcall(L, 1, 1); +} + +llvm::Error +PushSBClass (lua_State* L, lldb::SBBreakpointLocation* breakpoint_location_sb) +{ + lua_pushcfunction(L, [](lua_State *L) { + void *breakpoint_location_sb = lua_touserdata(L, -1); + SWIG_NewPointerObj(L, breakpoint_location_sb, SWIGTYPE_p_lldb__SBBreakpointLocation, 0); + return 1; // swig wrapper + }); + lua_pushlightuserdata(L, breakpoint_location_sb); + return DoLuaPcall(L, 1, 1); +} 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,59 @@ +%header %{ + +template +llvm::Error +PushSBClass(lua_State* L, T* obj); + +llvm::Error +DoLuaPcall(lua_State *L, int nargs, int nresults); + +%} + +%wrapper %{ + +// This function is called from Lua::CallBreakpointCallback +SWIGEXPORT llvm::Expected +LLDBSwigLuaBreakpointCallbackFunction +( + lua_State *L, + void *baton, + lldb::StackFrameSP &stop_frame_sp, + lldb::BreakpointLocationSP &bp_loc_sp +) +{ + lldb::SBFrame sb_frame(stop_frame_sp); + lldb::SBBreakpointLocation sb_bp_loc(bp_loc_sp); + + if (!lua_checkstack(L, 4)) { + return llvm::make_error( + "Lua cannot fulfill this request", llvm::inconvertibleErrorCode()); + } + + // Get the Lua callback + lua_pushlightuserdata(L, baton); + lua_gettable(L, LUA_REGISTRYINDEX); + + // Push the Lua wrappers + if (llvm::Error E = PushSBClass(L, &sb_frame)) { + return std::move(E); + } + + if (llvm::Error E = PushSBClass(L, &sb_bp_loc)) { + return std::move(E); + } + + // Call into the Lua callback passing 'sb_frame' and 'sb_bp_loc'. + // Expects a boolean return. + if (llvm::Error E = DoLuaPcall(L, 2, 1)) { + return std::move(E); + } + + // Boolean return from the callback + bool stop = lua_toboolean(L, -1); + lua_pop(L, 1); + + return stop; +} + + +%} 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,12 @@ %include "headers.swig" %{ +#include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" +#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 @@ -9,6 +9,8 @@ #ifndef liblldb_Lua_h_ #define liblldb_Lua_h_ +#include "lldb/API/SBBreakpointLocation.h" +#include "lldb/API/SBFrame.h" #include "lldb/lldb-types.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" @@ -29,6 +31,10 @@ ~Lua(); llvm::Error Run(llvm::StringRef buffer); + llvm::Error RegisterBreakpointCallback(void *baton, const char *body); + llvm::Expected + CallBreakpointCallback(void *baton, lldb::StackFrameSP &stop_frame_sp, + lldb::BreakpointLocationSP &bp_loc_sp); 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 @@ -9,11 +9,34 @@ #include "Lua.h" #include "lldb/Host/FileSystem.h" #include "lldb/Utility/FileSpec.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" using namespace lldb_private; using namespace lldb; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" + +// Disable warning C4190: 'LLDBSwigPythonBreakpointCallbackFunction' has +// C-linkage specified, but returns UDT 'llvm::Expected' which is +// incompatible with C +#if _MSC_VER +#pragma warning (push) +#pragma warning (disable : 4190) +#endif + +extern "C" llvm::Expected +LLDBSwigLuaBreakpointCallbackFunction(lua_State *L, void *baton, + lldb::StackFrameSP &stop_frame_sp, + lldb::BreakpointLocationSP &bp_loc_sp); + +#if _MSC_VER +#pragma warning (pop) +#endif + +#pragma clang diagnostic pop + static int lldb_print(lua_State *L) { int n = lua_gettop(L); lua_getglobal(L, "io"); @@ -57,6 +80,33 @@ return e; } +llvm::Error Lua::RegisterBreakpointCallback(void *baton, const char *body) { + if (!lua_checkstack(m_lua_state, 2)) { + return llvm::make_error( + "Lua cannot fulfill this request", llvm::inconvertibleErrorCode()); + } + lua_pushlightuserdata(m_lua_state, baton); + const char *fmt_str = "return function(frame, bp_loc, ...) {0} end"; + std::string func_str = llvm::formatv(fmt_str, body).str(); + if (luaL_dostring(m_lua_state, func_str.c_str()) != LUA_OK) { + 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; + } + lua_settable(m_lua_state, LUA_REGISTRYINDEX); + return llvm::Error::success(); +} + +llvm::Expected +Lua::CallBreakpointCallback(void *baton, lldb::StackFrameSP &stop_frame_sp, + lldb::BreakpointLocationSP &bp_loc_sp) { + return LLDBSwigLuaBreakpointCallbackFunction(m_lua_state, baton, + stop_frame_sp, bp_loc_sp); +} + 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,14 +8,17 @@ #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/Support/FormatAdapters.h" +#include using namespace lldb; using namespace lldb_private; @@ -174,6 +177,49 @@ 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(true, eScriptLanguageLua)); + Lua &lua = lua_interpreter->GetLua(); + + llvm::Expected 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; +} + +Status ScriptInterpreterLua::SetBreakpointCommandCallback( + BreakpointOptions *bp_options, const char *command_body_text) { + Status error; + auto data_up = std::make_unique(); + error = m_lua->RegisterBreakpointCallback(data_up.get(), command_body_text); + 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,7 @@ +# REQUIRES: lua +# RUN: echo "int main() { return 0; }" | %clang_host -x c - -o %t +# RUN: %lldb -s %s --script-language lua %t 2>&1 | FileCheck %s +b main +breakpoint command add -s lua -o 'print(frame)' +run +# CHECK: frame #0 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 @@ -13,6 +13,30 @@ extern "C" int luaopen_lldb(lua_State *L) { return 0; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" + +// Disable warning C4190: 'LLDBSwigPythonBreakpointCallbackFunction' has +// C-linkage specified, but returns UDT 'llvm::Expected' which is +// incompatible with C +#if _MSC_VER +#pragma warning (push) +#pragma warning (disable : 4190) +#endif + +extern "C" llvm::Expected +LLDBSwigLuaBreakpointCallbackFunction(lua_State *L, void *baton, + lldb::StackFrameSP &stop_frame_sp, + lldb::BreakpointLocationSP &bp_loc_sp) { + return false; +} + +#if _MSC_VER +#pragma warning (pop) +#endif + +#pragma clang diagnostic pop + TEST(LuaTest, RunValid) { Lua lua; llvm::Error error = lua.Run("foo = 1");