From 0e0e6ce005c22ad38218c2e1d014871b96cb3045 Mon Sep 17 00:00:00 2001 From: SD Asif Hossein Date: Thu, 8 Aug 2024 23:35:05 +0600 Subject: [PATCH 1/3] New Pallene Tracer pt-run implementation --- .github/workflows/ci.yml | 1 - .gitignore | 1 + .luacheckrc | 36 --- Makefile | 23 +- lib/ptracer.h | 279 ++----------------- pallene-tracer-0.5.0a-1.rockspec | 33 --- spec/tracebacks/anon_lua/main.lua | 6 +- spec/tracebacks/depth_recursion/main.lua | 8 +- spec/tracebacks/dispatch/main.lua | 6 +- spec/tracebacks/ellipsis/Makefile | 12 + spec/tracebacks/ellipsis/main.lua | 12 + spec/tracebacks/ellipsis/module.c | 91 +++++++ spec/tracebacks/multimod/main.lua | 6 +- spec/tracebacks/singular/main.lua | 6 +- spec/tracebacks_spec.lua | 71 +++-- src/bin/pallene-debug | 31 --- src/pt-run/main.c | 330 +++++++++++++++++++++++ src/ptinit/main.c | 20 -- 18 files changed, 529 insertions(+), 443 deletions(-) delete mode 100644 .luacheckrc delete mode 100644 pallene-tracer-0.5.0a-1.rockspec create mode 100644 spec/tracebacks/ellipsis/Makefile create mode 100644 spec/tracebacks/ellipsis/main.lua create mode 100644 spec/tracebacks/ellipsis/module.c delete mode 100755 src/bin/pallene-debug create mode 100644 src/pt-run/main.c delete mode 100644 src/ptinit/main.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f96c051..bbb6607 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,6 @@ jobs: - name: Build run: | sudo make install - luarocks --local make - name: Install Busted run: luarocks --local install busted diff --git a/.gitignore b/.gitignore index d3399f6..4e2aede 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.o *.a *.so +/pt-run diff --git a/.luacheckrc b/.luacheckrc deleted file mode 100644 index 8039c7d..0000000 --- a/.luacheckrc +++ /dev/null @@ -1,36 +0,0 @@ --- vim: set filetype=lua : -- - --- Luacheck configuration file [1] --- --- Please install Luacheck to your system with Luarocks: --- --- luarocks install luacheck --- --- To run Luackeck, please use our linter script, which also runs additional checks: --- --- ./run-lint --- --- For vim integration, I recommend ALE [2]. It supports luacheck out of the box --- For other editors such as Sublime, Atom, Emacs, Brackets, and VS Code, --- consult the instructions in the Luacheck documentation [3]. --- --- [1] https://luacheck.readthedocs.io/en/stable/config.html --- [2] https://github.com/dense-analysis/ale --- [3] https://github.com/mpeterv/luacheck#editor-support - -ignore = { - "212/self", -- Unused argument "self" - - "411/ok", -- Redefining local "ok" - "411/errs?", -- Redefining local "err" or "errs" - - "421/ok", -- Shadowing local "ok" - "421/errs?", -- Shadowing local "err" or "errs" - - "542", -- Empty if branch. - "6..", -- Whitespace warnings -} - -files["spec"] = { - std = "+busted" -} diff --git a/Makefile b/Makefile index 414f613..4a9aed7 100644 --- a/Makefile +++ b/Makefile @@ -3,28 +3,41 @@ # Please refer to the LICENSE and AUTHORS files for details # SPDX-License-Identifier: MIT -CC := gcc +CC := gcc +CFLAGS := -DPT_DEBUG -O2 -std=c99 -pedantic -Wall -Wextra + +LUA_DIR := /usr + INSTALL_DIR := /usr/local INSTALL_INCDIR := $(INSTALL_DIR)/include INSTALL_LIBDIR := $(INSTALL_DIR)/lib +INSTALL_BINDIR := $(INSTALL_DIR)/bin -.PHONY: install ptracer_header libptracer uninstall clean +.PHONY: install ptracer_header pt-run libptracer uninstall clean -install: ptracer_header libptracer +install: ptracer_header pt-run libptracer + strip -s pt-run + cp pt-run $(INSTALL_BINDIR) + strip -s libptracer.so cp libptracer.so $(INSTALL_LIBDIR) # We need the `ptracer.h` header to be installed first. ptracer_header: cp lib/ptracer.h $(INSTALL_INCDIR) +pt-run: + $(CC) $(CFLAGS) src/pt-run/main.c -o pt-run -llua -lm -Wl,-E -L$(LUA_DIR)/lib + libptracer: - $(CC) -fPIC -DPT_DEBUG -O2 -shared src/ptracer/main.c -o libptracer.so + $(CC) -fPIC -shared $(CFLAGS) src/ptracer/main.c -o libptracer.so uninstall: rm -rf $(INSTALL_INCDIR)/ptracer.h rm -rf $(INSTALL_LIBDIR)/libptracer.so clean: - rm -rf ptinit/*.o rm -rf examples/*/*.so + rm -rf spec/tracebacks/*/*.so rm -rf *.so + rm -rf pt-run + diff --git a/lib/ptracer.h b/lib/ptracer.h index 80770f8..fc98009 100644 --- a/lib/ptracer.h +++ b/lib/ptracer.h @@ -43,15 +43,6 @@ /* DO NOT CHANGE EVEN BY MISTAKE. */ #define PALLENE_TRACER_MAX_CALLSTACK 100000 -/* Traceback ellipsis top threshold. How many frames should we print - first to trigger ellipsis? */ -#define PALLENE_TRACEBACK_TOP_THRESHOLD 10 -/* This should always be 2 fewer than top threshold, for symmetry. - Becuase we will always have 2 tail frames lingering around at - at the end which is not captured by '_countlevels'. Lua also - do it like this. */ -#define PALLENE_TRACEBACK_BOTTOM_THRESHOLD 8 - /* API wrapper macros. Using these wrappers instead is raw functions * are highly recommended. */ #ifdef PT_DEBUG @@ -211,18 +202,6 @@ static inline void pallene_tracer_frameexit(pt_fnstack_t *fnstack) { fnstack->count -= (fnstack->count > 0); } -/* Pallene Tracer explicit traceback function to show Pallene call-stack - tracebacks. */ -PT_API int pallene_tracer_debug_traceback(lua_State *L); - -/* When we encounter a runtime error, `pallene_tracer_frameexit()` may not - get called. Therefore, the stack will get corrupted if the previous - call-frames are not removed. The finalizer function makes sure it - does not happen. Its guardian angel. */ -/* The finalizer function will be called from a to-be-closed value (since - Lua 5.4). If you are using Lua version prior 5.4, you are outta luck. */ -PT_API int pallene_tracer_finalizer(lua_State *L); - #ifdef __cplusplus } #endif // __cplusplus @@ -237,120 +216,25 @@ PT_API int pallene_tracer_finalizer(lua_State *L); /* ---------------- PRIVATE ---------------- */ -/* Global table name deduction. Can we find a function name? */ -static bool _pallene_tracer_findfield(lua_State *L, int fn_idx, int level) { - if(level == 0 || !lua_istable(L, -1)) - return false; - - lua_pushnil(L); /* Initial key. */ - - while(lua_next(L, -2)) { - /* We are only interested in String keys. */ - if(lua_type(L, -2) == LUA_TSTRING) { - /* Avoid "_G" recursion in global table. The global table is also part of - global table :). */ - if(!strcmp(lua_tostring(L, -2), "_G")) { - /* Remove value and continue. */ - lua_pop(L, 1); - continue; - } - - /* Is it the function we are looking for? */ - if(lua_rawequal(L, fn_idx, -1)) { - /* Remove value and keep name. */ - lua_pop(L, 1); - return true; - } - /* If not go one level deeper and get the value recursively. */ - else if(_pallene_tracer_findfield(L, fn_idx, level - 1)) { - /* Remove the table but keep name. */ - lua_remove(L, -2); - - /* Add a "." in between. */ - lua_pushliteral(L, "."); - lua_insert(L, -2); - - /* Concatenate last 3 values, resulting "table.some_func". */ - lua_concat(L, 3); - - return true; - } - } - - /* Pop the value. */ - lua_pop(L, 1); - } - - return false; -} - -/* Pushes a function name if found in the global table and returns true. - Returns false otherwise. */ -/* Expects the function to be pushed in the stack. */ -static bool _pallene_tracer_pgf_name(lua_State *L) { - int top = lua_gettop(L); - - /* Start from the global table. */ - lua_pushglobaltable(L); - - if(_pallene_tracer_findfield(L, top, 2)) { - lua_remove(L, -2); - return true; - } - - lua_pop(L, 1); - return false; -} - -/* Returns the maximum number of levels in Lua stack. */ -static int _pallene_tracer_countlevels(lua_State *L) { - lua_Debug ar; - int li = 1, le = 1; - - /* Find an upper bound */ - while (lua_getstack(L, le, &ar)) { - li = le, le *= 2; - } - - /* Do a binary search */ - while (li < le) { - int m = (li + le) / 2; - - if (lua_getstack(L, m, &ar)) li = m + 1; - else le = m; - } - - return le - 1; -} - -/* Counts the number of white and black frames in the Pallene call stack. */ -static void _pallene_tracer_countframes(pt_fnstack_t *fnstack, int *mwhite, int *mblack) { - *mwhite = *mblack = 0; - - for(int i = 0; i < fnstack->count; i++) { - *mwhite += (fnstack->stack[i].type == PALLENE_TRACER_FRAME_TYPE_C); - *mblack += (fnstack->stack[i].type == PALLENE_TRACER_FRAME_TYPE_LUA); - } -} - -/* Responsible for printing and controlling some of the traceback fn parameters. */ -static void _pallene_tracer_dbg_print(const char *buf, bool *ellipsis, int *pframes, int nframes) { - /* We have printed the frame, even tho it might not be visible ;). */ - (*pframes)++; +/* When we encounter a runtime error, `pallene_tracer_frameexit()` may not + get called. Therefore, the stack will get corrupted if the previous + call-frames are not removed. The finalizer function makes sure it + does not happen. Its guardian angel. */ +/* The finalizer function will be called from a to-be-closed value (since + Lua 5.4). If you are using Lua version prior 5.4, you are outta luck. */ +static int _pallene_tracer_finalizer(lua_State *L) { + /* Get the userdata. */ + pt_fnstack_t *fnstack = (pt_fnstack_t *) lua_touserdata(L, lua_upvalueindex(1)); - /* Should we print? Are we at any point in top or bottom printing threshold? */ - bool should_print = (*pframes <= PALLENE_TRACEBACK_TOP_THRESHOLD) - || ((nframes - *pframes) <= PALLENE_TRACEBACK_BOTTOM_THRESHOLD); + /* Remove all the frames until last Lua frame. */ + int idx = fnstack->count - 1; + while(fnstack->stack[idx].type != PALLENE_TRACER_FRAME_TYPE_LUA) + idx--; - if(luai_likely(should_print)) - fprintf(stderr, buf); - else if(*ellipsis) { - fprintf(stderr, "\n ... (Skipped %d frames) ...\n\n", - nframes - (PALLENE_TRACEBACK_TOP_THRESHOLD - + PALLENE_TRACEBACK_BOTTOM_THRESHOLD)); + /* Remove the Lua frame as well. */ + fnstack->count = idx; - *ellipsis = false; - } + return 0; } /* Frees the heap-allocated resources. */ @@ -398,7 +282,7 @@ pt_fnstack_t *pallene_tracer_init(lua_State *L) { lua_pushvalue(L, -3); /* Our finalizer fn. */ - lua_pushcclosure(L, pallene_tracer_finalizer, 1); + lua_pushcclosure(L, _pallene_tracer_finalizer, 1); lua_setfield(L, -2, "__close"); lua_setmetatable(L, -2); @@ -408,9 +292,6 @@ pt_fnstack_t *pallene_tracer_init(lua_State *L) { /* Set stack function stack container to registry .*/ lua_setfield(L, LUA_REGISTRYINDEX, PALLENE_TRACER_CONTAINER_ENTRY); - /* The debug traceback fn. */ - lua_register(L, "pallene_tracer_debug_traceback", pallene_tracer_debug_traceback); - /* Push the finalizer object in the stack. */ lua_getfield(L, LUA_REGISTRYINDEX, PALLENE_TRACER_FINALIZER_ENTRY); } else { @@ -426,132 +307,6 @@ pt_fnstack_t *pallene_tracer_init(lua_State *L) { #endif // PT_DEBUG } -/* Helper macro specific to this function only :). */ -#define DBG_PRINT() _pallene_tracer_dbg_print(buf, &ellipsis, &pframes, nframes) -/* Pallene Tracer explicit traceback function to show Pallene call-stack - tracebacks. */ -int pallene_tracer_debug_traceback(lua_State *L) { - lua_getfield(L, LUA_REGISTRYINDEX, PALLENE_TRACER_CONTAINER_ENTRY); - pt_fnstack_t *fnstack = (pt_fnstack_t *) lua_touserdata(L, -1); - pt_frame_t *stack = fnstack->stack; - /* The point where we are in the Pallene stack. */ - int index = fnstack->count - 1; - lua_pop(L, 1); - - /* Max number of white and black frames. */ - int mwhite, mblack; - _pallene_tracer_countframes(fnstack, &mwhite, &mblack); - /* Max levels of Lua stack. */ - int mlevel = _pallene_tracer_countlevels(L); - - /* Total frames we are going to print. */ - /* Black frames are used for switching and we will start from - Lua stack level 1. */ - int nframes = mlevel + mwhite - mblack - 1; - /* Amount of frames printed. */ - int pframes = 0; - /* Should we print ellipsis? */ - bool ellipsis = nframes > (PALLENE_TRACEBACK_TOP_THRESHOLD - + PALLENE_TRACEBACK_BOTTOM_THRESHOLD); - - /* Buffer to store for a single frame line to be printed. */ - int _BUFSIZ = 1023; - char buf[_BUFSIZ + 1]; - - const char *message = lua_tostring(L, 1); - fprintf(stderr, "Runtime error: %s\nStack traceback:\n", message); - - lua_Debug ar; - int top = lua_gettop(L); - int level = 1; - - while(lua_getstack(L, level++, &ar)) { - /* Get information regarding the frame: name, source, linenumbers etc. */ - lua_getinfo(L, "Slnf", &ar); - - /* If the frame is a C frame. */ - if(lua_iscfunction(L, -1)) { - if(index >= 0) { - /* Check whether this frame is tracked (C interface frames). */ - int check = index; - while(stack[check].type != PALLENE_TRACER_FRAME_TYPE_LUA) - check--; - - /* If the frame matches, we switch to printing Pallene frames. */ - if(lua_tocfunction(L, -1) == stack[check].shared.c_fnptr) { - /* Now print all the frames in Pallene stack. */ - for(; index > check; index--) { - snprintf(buf, _BUFSIZ, " %s:%d: in function '%s'\n", - stack[index].shared.details->filename, - stack[index].line, stack[index].shared.details->fn_name); - DBG_PRINT(); - } - - /* 'check' idx is guaranteed to be a Lua interface frame. - Which is basically our 'stack' index at this point. So, - we simply ignore the Lua interface frame. */ - index--; - - /* We are done. */ - lua_settop(L, top); - continue; - } - } - - /* Then it's an untracked C frame. */ - if(_pallene_tracer_pgf_name(L)) - lua_pushfstring(L, "%s", lua_tostring(L, -1)); - else lua_pushliteral(L, ""); - - snprintf(buf, _BUFSIZ, " C: in function '%s'\n", lua_tostring(L, -1)); - DBG_PRINT(); - } else { - /* It's a Lua frame. */ - - /* Do we have a name? */ - if(*ar.namewhat != '\0') - lua_pushfstring(L, "function '%s'", ar.name); - /* Is it the main chunk? */ - else if(*ar.what == 'm') - lua_pushliteral(L, "
"); - /* Can we deduce the name from the global table? */ - else if(_pallene_tracer_pgf_name(L)) - lua_pushfstring(L, "function '%s'", lua_tostring(L, -1)); - else lua_pushliteral(L, "function ''"); - - snprintf(buf, _BUFSIZ, " %s:%d: in %s\n", ar.short_src, - ar.currentline, lua_tostring(L, -1)); - DBG_PRINT(); - } - - lua_settop(L, top); - } - - return 0; -} -#undef DBG_PRINT - -/* When we encounter a runtime error, `pallene_tracer_frameexit()` may not - get called. Therefore, the stack will get corrupted if the previous - call-frames are not removed. The finalizer function makes sure it - does not happen. Its guardian angel. */ -/* The finalizer function will be called from a to-be-closed value (since - Lua 5.4). If you are using Lua version prior 5.4, you are outta luck. */ -int pallene_tracer_finalizer(lua_State *L) { - /* Get the userdata. */ - pt_fnstack_t *fnstack = (pt_fnstack_t *) lua_touserdata(L, lua_upvalueindex(1)); - - /* Remove all the frames until last Lua frame. */ - int idx = fnstack->count - 1; - while(fnstack->stack[idx].type != PALLENE_TRACER_FRAME_TYPE_LUA) - idx--; - - /* Remove the Lua frame as well. */ - fnstack->count = idx; - - return 0; -} - /* ---------------- DEFINITIONS END ---------------- */ #endif diff --git a/pallene-tracer-0.5.0a-1.rockspec b/pallene-tracer-0.5.0a-1.rockspec deleted file mode 100644 index 3b32fa2..0000000 --- a/pallene-tracer-0.5.0a-1.rockspec +++ /dev/null @@ -1,33 +0,0 @@ -rockspec_format="3.0" -package = "pallene-tracer" -version = "0.5.0a-1" -source = { - url = "http://github.com/pallene-lang/pallene-tracer" -} -description = { - summary = "Pallene Tracer for Traceback", - detailed = [[ - Universal traceback library for Lua C modules - ]], - homepage = "http://github.com/pallene-lang/pallene-tracer", - license = "MIT" -} -dependencies = { - "lua >= 5.4" -} -external_dependencies = { - PTRACER = { - header = "ptracer.h" - } -} -build = { - type = "builtin", - modules = { - ptinit = "src/ptinit/main.c", - }, - install = { - bin = { - ["pallene-debug"] = "src/bin/pallene-debug" - } - } -} diff --git a/spec/tracebacks/anon_lua/main.lua b/spec/tracebacks/anon_lua/main.lua index 89c9ade..438fa61 100644 --- a/spec/tracebacks/anon_lua/main.lua +++ b/spec/tracebacks/anon_lua/main.lua @@ -9,8 +9,4 @@ local function lua_callee_1() module.module_fn_2() end -local function wrapper() - module.module_fn_1(lua_callee_1) -end - -xpcall(wrapper, pallene_tracer_debug_traceback) +module.module_fn_1(lua_callee_1) diff --git a/spec/tracebacks/depth_recursion/main.lua b/spec/tracebacks/depth_recursion/main.lua index de8d4e2..a1cdd7e 100644 --- a/spec/tracebacks/depth_recursion/main.lua +++ b/spec/tracebacks/depth_recursion/main.lua @@ -13,10 +13,4 @@ function lua_fn(depth) module.module_fn(lua_fn, depth - 1) end --- Should be local. --- Making it global so that it is visible in the traceback. -function wrapper() - lua_fn(10) -end - -xpcall(wrapper, pallene_tracer_debug_traceback) +lua_fn(10) diff --git a/spec/tracebacks/dispatch/main.lua b/spec/tracebacks/dispatch/main.lua index 07b46ab..ab9965a 100644 --- a/spec/tracebacks/dispatch/main.lua +++ b/spec/tracebacks/dispatch/main.lua @@ -9,8 +9,4 @@ function lua_callee_1() module.module_fn_2() end -function wrapper() - module.module_fn_1(lua_callee_1) -end - -xpcall(wrapper, pallene_tracer_debug_traceback) +module.module_fn_1(lua_callee_1) diff --git a/spec/tracebacks/ellipsis/Makefile b/spec/tracebacks/ellipsis/Makefile new file mode 100644 index 0000000..15fb481 --- /dev/null +++ b/spec/tracebacks/ellipsis/Makefile @@ -0,0 +1,12 @@ +# Copyright (c) 2024, The Pallene Developers +# Pallene Tracer is licensed under the MIT license. +# Please refer to the LICENSE and AUTHORS files for details +# SPDX-License-Identifier: MIT + +CC := gcc + +.PHONY : all +.SILENT: + +all: + $(CC) -std=c99 -pedantic -Wall -Wextra -fPIC -DPT_DEBUG -shared module.c -o module.so diff --git a/spec/tracebacks/ellipsis/main.lua b/spec/tracebacks/ellipsis/main.lua new file mode 100644 index 0000000..5ed2621 --- /dev/null +++ b/spec/tracebacks/ellipsis/main.lua @@ -0,0 +1,12 @@ +-- Copyright (c) 2024, The Pallene Developers +-- Pallene Tracer is licensed under the MIT license. +-- Please refer to the LICENSE and AUTHORS files for details +-- SPDX-License-Identifier: MIT + +local module = require "module" + +function lua_fn() + module.module_fn(lua_fn) +end + +lua_fn() diff --git a/spec/tracebacks/ellipsis/module.c b/spec/tracebacks/ellipsis/module.c new file mode 100644 index 0000000..2b9ab6c --- /dev/null +++ b/spec/tracebacks/ellipsis/module.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024, The Pallene Developers + * Pallene Tracer is licensed under the MIT license. + * Please refer to the LICENSE and AUTHORS files for details + * SPDX-License-Identifier: MIT + */ + +/* Static use of the library would suffice. */ +#define PT_IMPLEMENTATION +#include + +/* Here goes user specific macros when Pallene Tracer debug mode is active. */ +#ifdef PT_DEBUG +#define MODULE_GET_FNSTACK \ + pt_fnstack_t *fnstack = lua_touserdata(L, \ + lua_upvalueindex(1)) +#else +#define MODULE_GET_FNSTACK +#endif // PT_DEBUG + +/* ---------------- LUA INTERFACE FUNCTIONS ---------------- */ + +#define MODULE_LUA_FRAMEENTER(fnptr) \ + MODULE_GET_FNSTACK; \ + PALLENE_TRACER_LUA_FRAMEENTER(L, fnstack, fnptr, \ + lua_upvalueindex(2), _frame) + +/* ---------------- LUA INTERFACE FUNCTIONS END ---------------- */ + +/* ---------------- FOR C INTERFACE FUNCTIONS ---------------- */ + +#define MODULE_C_FRAMEENTER() \ + MODULE_GET_FNSTACK; \ + PALLENE_TRACER_GENERIC_C_FRAMEENTER(fnstack, _frame) + +#define MODULE_C_SETLINE() \ + PALLENE_TRACER_GENERIC_C_SETLINE(fnstack) + +#define MODULE_C_FRAMEEXIT() \ + PALLENE_TRACER_FRAMEEXIT(fnstack) + +/* ---------------- FOR C INTERFACE FUNCTIONS END ---------------- */ + +void module_fn(lua_State *L) { + MODULE_C_FRAMEENTER(); + + lua_pushvalue(L, 1); + + /* Set line number to current active frame in the Pallene callstack and + call the function which is already in the Lua stack. */ + MODULE_C_SETLINE(); + lua_call(L, 0, 0); + + MODULE_C_FRAMEEXIT(); +} + +int module_fn_lua(lua_State *L) { + int top = lua_gettop(L); + MODULE_LUA_FRAMEENTER(module_fn_lua); + + /* Look at the macro definitions. */ + if(luai_unlikely(top < 1)) + luaL_error(L, "Expected atleast 1 parameters"); + + /* ---- `lua_fn` ---- */ + if(luai_unlikely(lua_isfunction(L, 1) == 0)) + luaL_error(L, "Expected the first parameter to be a function"); + + /* Dispatch. */ + module_fn(L); + + return 0; +} + +int luaopen_module(lua_State *L) { + /* Our stack. */ + pt_fnstack_t *fnstack = pallene_tracer_init(L); + + lua_newtable(L); + + /* One very good way to integrate our stack userdatum and finalizer + object is by using Lua upvalues. */ + /* ---- module_fn_1 ---- */ + lua_pushlightuserdata(L, fnstack); + /* `pallene_tracer_init` function pushes the frameexit finalizer to the stack. */ + lua_pushvalue(L, -3); + lua_pushcclosure(L, module_fn_lua, 2); + lua_setfield(L, -2, "module_fn"); + + return 1; +} diff --git a/spec/tracebacks/multimod/main.lua b/spec/tracebacks/multimod/main.lua index 4897de7..b21f0b5 100644 --- a/spec/tracebacks/multimod/main.lua +++ b/spec/tracebacks/multimod/main.lua @@ -10,8 +10,4 @@ function some_lua_fn() mod_b.another_mod_fn() end -function wrapper() - mod_a.some_mod_fn(some_lua_fn) -end - -xpcall(wrapper, pallene_tracer_debug_traceback) +mod_a.some_mod_fn(some_lua_fn) diff --git a/spec/tracebacks/singular/main.lua b/spec/tracebacks/singular/main.lua index 8d6e5f4..34964a2 100644 --- a/spec/tracebacks/singular/main.lua +++ b/spec/tracebacks/singular/main.lua @@ -9,8 +9,4 @@ function some_lua_fn() module.singular_fn() end -function wrapper() - some_lua_fn() -end - -xpcall(wrapper, pallene_tracer_debug_traceback) +some_lua_fn() diff --git a/spec/tracebacks_spec.lua b/spec/tracebacks_spec.lua index d4dab65..a571c41 100644 --- a/spec/tracebacks_spec.lua +++ b/spec/tracebacks_spec.lua @@ -5,62 +5,53 @@ local util = require "misc.util" -local function assert_example(example, expected_content) +local function assert_test(example, expected_content) local cdir = "spec/tracebacks/"..example.."/"; local ok, err = util.execute("cd "..cdir.."&& make") assert(ok, err) - local ok, err, _, err_content = util.outputs_of_execute("cd "..cdir.." && lua main.lua") - assert(ok, err) + local ok, _, output_content, err_content = util.outputs_of_execute("cd "..cdir.." && pt-run main.lua") + assert(not ok, output_content) assert.are.same(expected_content, err_content) end it("Dispatch", function() - assert_example("dispatch", [[ + assert_test("dispatch", [[ Runtime error: main.lua:9: Error from a C function, which has no trace in Lua callstack! Stack traceback: module.c:48: in function 'some_oblivious_c_function' module.c:92: in function 'module_fn_2' main.lua:9: in function 'lua_callee_1' module.c:61: in function 'module_fn_1' - main.lua:13: in function 'wrapper' - C: in function 'xpcall' - main.lua:16: in
- C: in function '' + main.lua:12: in
]]) end) it("Singular", function() - assert_example("singular", [[ + assert_test("singular", [[ Runtime error: main.lua:9: Life's !good Stack traceback: module.c:49: in function 'lifes_good_fn' module.c:59: in function 'singular_fn' main.lua:9: in function 'some_lua_fn' - main.lua:13: in function 'wrapper' - C: in function 'xpcall' - main.lua:16: in
- C: in function '' + main.lua:12: in
]]) end) it("Multi-module", function() - assert_example("multimod", [[ + assert_test("multimod", [[ Runtime error: main.lua:10: Error from another module! Stack traceback: module_b.c:16: in function 'another_mod_fn' main.lua:10: in function 'some_lua_fn' module_a.c:18: in function 'some_mod_fn' - main.lua:14: in function 'wrapper' - C: in function 'xpcall' - main.lua:17: in
- C: in function '' + main.lua:13: in
]]) end) it("Depth recursion", function() - assert_example("depth_recursion", [[ + assert_test("depth_recursion", [[ Runtime error: main.lua:10: Depth reached 0! Stack traceback: C: in function 'error' @@ -75,24 +66,48 @@ Stack traceback: main.lua:13: in function 'lua_fn' module.c:56: in function 'module_fn' main.lua:13: in function 'lua_fn' - main.lua:19: in function 'wrapper' - C: in function 'xpcall' - main.lua:22: in
- C: in function '' + main.lua:16: in
]]) end) it("Anonymous Lua Fn", function() - assert_example("anon_lua", [[ + assert_test("anon_lua", [[ Runtime error: main.lua:9: Error from a C function, which has no trace in Lua callstack! Stack traceback: module.c:48: in function 'some_oblivious_c_function' module.c:92: in function 'module_fn_2' main.lua:9: in function '' module.c:61: in function 'module_fn_1' - main.lua:13: in function '' - C: in function 'xpcall' - main.lua:16: in
- C: in function '' + main.lua:12: in
+]]) +end) + +it("Traceback Ellipsis", function() + assert_test("ellipsis", [[ +Runtime error: C stack overflow +Stack traceback: + module.c:52: in function 'module_fn' + main.lua:9: in function 'lua_fn' + module.c:52: in function 'module_fn' + main.lua:9: in function 'lua_fn' + module.c:52: in function 'module_fn' + main.lua:9: in function 'lua_fn' + module.c:52: in function 'module_fn' + main.lua:9: in function 'lua_fn' + module.c:52: in function 'module_fn' + main.lua:9: in function 'lua_fn' + + ... (Skipped 380 frames) ... + + main.lua:9: in function 'lua_fn' + module.c:52: in function 'module_fn' + main.lua:9: in function 'lua_fn' + module.c:52: in function 'module_fn' + main.lua:9: in function 'lua_fn' + module.c:52: in function 'module_fn' + main.lua:9: in function 'lua_fn' + module.c:52: in function 'module_fn' + main.lua:9: in function 'lua_fn' + main.lua:12: in
]]) end) diff --git a/src/bin/pallene-debug b/src/bin/pallene-debug deleted file mode 100755 index 0bd02b0..0000000 --- a/src/bin/pallene-debug +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env lua - --- Copyright (c) 2024, The Pallene Developers --- Pallene Tracer is licensed under the MIT license. --- Please refer to the LICENSE and AUTHORS files for details --- SPDX-License-Identifier: MIT - -local argparse = require "argparse" -local _ = require "ptinit" - --- Parse the arguments. -local opts -do - local p = argparse("pallene-debug", "Pallene debugger script for call-stack backtrace") - - p:argument("lua_script", "Lua file to debug with Pallene module") - p:argument("args", "Arguments passed to Lua script") :args("*") - - opts = p:parse() -end - -local fn, err = loadfile(opts.lua_script) - --- Was loading and parsing the file successful? -if not fn then - io.stderr:write("pallene-debug: "..err.."\n") - os.exit(1) -end - --- Moment of truth. -xpcall(fn, pallene_tracer_debug_traceback, table.unpack(opts.args)) diff --git a/src/pt-run/main.c b/src/pt-run/main.c new file mode 100644 index 0000000..824ad37 --- /dev/null +++ b/src/pt-run/main.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2024, The Pallene Developers + * Pallene Tracer is licensed under the MIT license. + * Please refer to the LICENSE and AUTHORS files for details + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include +#include +#include + +#define PT_IMPLEMENTATION +#include + +/* Traceback ellipsis top threshold. How many frames should we print + first to trigger ellipsis? */ +#ifndef PT_RUN_TRACEBACK_TOP_THRESHOLD +#define PT_RUN_TRACEBACK_TOP_THRESHOLD 10 +#endif // PT_RUN_TRACEBACK_TOP_THRESHOLD + +/* This should always be 2 fewer than top threshold, for symmetry. + Becuase we will always have 2 tail frames lingering around at + at the end which is not captured by '_countlevels'. Lua also + do it like this. */ +#ifndef PT_RUN_TRACEBACK_BOTTOM_THRESHOLD +#define PT_RUN_TRACEBACK_BOTTOM_THRESHOLD 8 +#endif // PT_RUN_TRACEBACK_BOTTOM_THRESHOLD + +static void pt_run_usage(FILE *where) { + fprintf(where, "Usage: pt-run [-h | --help] [] ...\n\n"); +} + +static void pt_run_help() { + pt_run_usage(stdout); + printf("Pallene Tracer runner for call-stack backtrace\n\n"); + printf("Arguments:\n" + " lua_script Lua file to debug\n" + " args ... Arguments passed to Lua script\n\n"); + printf("Options:\n" + " -h, --help Show this help message and exit\n"); +} + +/* Global table name deduction. Can we find a function name? */ +static bool pt_run_findfield(lua_State *L, int fn_idx, int level) { + if(level == 0 || !lua_istable(L, -1)) + return false; + + lua_pushnil(L); /* Initial key. */ + + while(lua_next(L, -2)) { + /* We are only interested in String keys. */ + if(lua_type(L, -2) == LUA_TSTRING) { + /* Avoid "_G" recursion in global table. The global table is also part of + global table :). */ + if(!strcmp(lua_tostring(L, -2), "_G")) { + /* Remove value and continue. */ + lua_pop(L, 1); + continue; + } + + /* Is it the function we are looking for? */ + if(lua_rawequal(L, fn_idx, -1)) { + /* Remove value and keep name. */ + lua_pop(L, 1); + return true; + } + /* If not go one level deeper and get the value recursively. */ + else if(pt_run_findfield(L, fn_idx, level - 1)) { + /* Remove the table but keep name. */ + lua_remove(L, -2); + + /* Add a "." in between. */ + lua_pushliteral(L, "."); + lua_insert(L, -2); + + /* Concatenate last 3 values, resulting "table.some_func". */ + lua_concat(L, 3); + + return true; + } + } + + /* Pop the value. */ + lua_pop(L, 1); + } + + return false; +} + +/* Pushes a function name if found in the global table and returns true. + Returns false otherwise. */ +/* Expects the function to be pushed in the stack. */ +static bool pt_run_pgf_name(lua_State *L) { + int top = lua_gettop(L); + + /* Start from the global table. */ + lua_pushglobaltable(L); + + if(pt_run_findfield(L, top, 2)) { + lua_remove(L, -2); + return true; + } + + lua_pop(L, 1); + return false; +} + +/* Returns the maximum number of levels in Lua stack. */ +static int pt_run_countlevels(lua_State *L) { + lua_Debug ar; + int li = 1, le = 1; + + /* Find an upper bound */ + while (lua_getstack(L, le, &ar)) { + li = le, le *= 2; + } + + /* Do a binary search */ + while (li < le) { + int m = (li + le) / 2; + + if (lua_getstack(L, m, &ar)) li = m + 1; + else le = m; + } + + return le - 1; +} + +/* Counts the number of white and black frames in the Pallene call stack. */ +static void pt_run_countframes(pt_fnstack_t *fnstack, int *mwhite, int *mblack) { + *mwhite = *mblack = 0; + + for(int i = 0; i < fnstack->count; i++) { + *mwhite += (fnstack->stack[i].type == PALLENE_TRACER_FRAME_TYPE_C); + *mblack += (fnstack->stack[i].type == PALLENE_TRACER_FRAME_TYPE_LUA); + } +} + +/* Responsible for printing and controlling some of the traceback fn parameters. */ +static void pt_run_dbg_print(const char *buf, bool *ellipsis, int *pframes, int nframes) { + /* We have printed the frame, even tho it might not be visible ;). */ + (*pframes)++; + + /* Should we print? Are we at any point in top or bottom printing threshold? */ + bool should_print = (*pframes <= PT_RUN_TRACEBACK_TOP_THRESHOLD) + || ((nframes - *pframes) <= PT_RUN_TRACEBACK_BOTTOM_THRESHOLD); + + if(luai_likely(should_print)) + fprintf(stderr, buf); + else if(*ellipsis) { + fprintf(stderr, "\n ... (Skipped %d frames) ...\n\n", + nframes - (PT_RUN_TRACEBACK_TOP_THRESHOLD + + PT_RUN_TRACEBACK_BOTTOM_THRESHOLD)); + + *ellipsis = false; + } +} + +#define DBG_PRINT() pt_run_dbg_print(buf, &ellipsis, &pframes, nframes) +/* Pallene Tracer explicit traceback function to show Pallene call-stack + tracebacks. */ +int pt_run_debug_traceback(lua_State *L) { + lua_getfield(L, LUA_REGISTRYINDEX, PALLENE_TRACER_CONTAINER_ENTRY); + pt_fnstack_t *fnstack = (pt_fnstack_t *) lua_touserdata(L, -1); + pt_frame_t *stack = fnstack->stack; + /* The point where we are in the Pallene stack. */ + int index = fnstack->count - 1; + lua_pop(L, 1); + + /* Max number of white and black frames. */ + int mwhite, mblack; + pt_run_countframes(fnstack, &mwhite, &mblack); + /* Max levels of Lua stack. */ + int mlevel = pt_run_countlevels(L); + + /* Total frames we are going to print. */ + /* Black frames are used for switching and we will start from + Lua stack level 1. */ + int nframes = mlevel + mwhite - mblack - 1; + /* Amount of frames printed. */ + int pframes = 0; + /* Should we print ellipsis? */ + bool ellipsis = nframes > (PT_RUN_TRACEBACK_TOP_THRESHOLD + + PT_RUN_TRACEBACK_BOTTOM_THRESHOLD); + + /* Buffer to store for a single frame line to be printed. */ + int _BUFSIZ = 1023; + char buf[_BUFSIZ + 1]; + + const char *message = lua_tostring(L, 1); + fprintf(stderr, "Runtime error: %s\nStack traceback:\n", message); + + lua_Debug ar; + int top = lua_gettop(L); + int level = 1; + + while(lua_getstack(L, level++, &ar)) { + /* Get information regarding the frame: name, source, linenumbers etc. */ + lua_getinfo(L, "Slnf", &ar); + + /* If the frame is a C frame. */ + if(lua_iscfunction(L, -1)) { + if(index >= 0) { + /* Check whether this frame is tracked (C interface frames). */ + int check = index; + while(stack[check].type != PALLENE_TRACER_FRAME_TYPE_LUA) + check--; + + /* If the frame matches, we switch to printing Pallene frames. */ + if(lua_tocfunction(L, -1) == stack[check].shared.c_fnptr) { + /* Now print all the frames in Pallene stack. */ + for(; index > check; index--) { + snprintf(buf, _BUFSIZ, " %s:%d: in function '%s'\n", + stack[index].shared.details->filename, + stack[index].line, stack[index].shared.details->fn_name); + DBG_PRINT(); + } + + /* 'check' idx is guaranteed to be a Lua interface frame. + Which is basically our 'stack' index at this point. So, + we simply ignore the Lua interface frame. */ + index--; + + /* We are done. */ + lua_settop(L, top); + continue; + } + } + + /* Then it's an untracked C frame. */ + if(pt_run_pgf_name(L)) + lua_pushfstring(L, "%s", lua_tostring(L, -1)); + else lua_pushliteral(L, ""); + + snprintf(buf, _BUFSIZ, " C: in function '%s'\n", lua_tostring(L, -1)); + DBG_PRINT(); + } else { + /* It's a Lua frame. */ + + /* Do we have a name? */ + if(*ar.namewhat != '\0') + lua_pushfstring(L, "function '%s'", ar.name); + /* Is it the main chunk? */ + else if(*ar.what == 'm') + lua_pushliteral(L, "
"); + /* Can we deduce the name from the global table? */ + else if(pt_run_pgf_name(L)) + lua_pushfstring(L, "function '%s'", lua_tostring(L, -1)); + else lua_pushliteral(L, "function ''"); + + snprintf(buf, _BUFSIZ, " %s:%d: in %s\n", ar.short_src, + ar.currentline, lua_tostring(L, -1)); + DBG_PRINT(); + } + + lua_settop(L, top); + } + + return 0; +} +#undef DBG_PRINT + +int main(int argc, char **argv) { + int res = EXIT_SUCCESS; + + /* If no argument was passed. */ + if(argc < 2) { + pt_run_usage(stderr); + fprintf(stderr, "pt-run: missing argument , abort...\n"); + + res = EXIT_FAILURE; + goto out; + } + + /* Option parsing. */ + if(!strncmp(argv[1], "-", 1)) { + char *match = argv[1] + 1; + + if(!strcmp(match, "h") || !strcmp(match, "-help")) + pt_run_help(); + else { + pt_run_usage(stderr); + fprintf(stderr, "pt-run: unknown option '%s', abort...\n", argv[1]); + } + + res = EXIT_FAILURE; + goto out; + } + + /* Initalize Lua. */ + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + + /* Initialize Pallene tracer. */ + (void) pallene_tracer_init(L); /* We are not doing anything with the Userdata. */ + /* The init function pushes the finalizer object onto the Lua stack. Which + we also do not need. */ + lua_pop(L, 1); + + /* The traceback function. */ + lua_pushcfunction(L, pt_run_debug_traceback); + int traceback_fn = lua_gettop(L); + + /* Load the Lua file we wish to debug. */ + int status = luaL_loadfile(L, argv[1]); + if(status != LUA_OK) { + fprintf(stderr, "pt-run: %s\n", lua_tostring(L, -1)); + + res = EXIT_FAILURE; + goto finalize; + } + + /* Push arguments to Lua stack. */ + for(int i = 2; i < argc; i++) + lua_pushstring(L, argv[i]); + + /* Moment of truth. */ + status = lua_pcall(L, argc - 2, LUA_MULTRET, traceback_fn); + if(status != LUA_OK) + res = EXIT_FAILURE; + +finalize: + /* Cleanup. */ + lua_close(L); +out: + return res; +} diff --git a/src/ptinit/main.c b/src/ptinit/main.c deleted file mode 100644 index 56e0b52..0000000 --- a/src/ptinit/main.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2024, The Pallene Developers - * Pallene is licensed under the MIT license. - * Please refer to the LICENSE and AUTHORS files for details - * SPDX-License-Identifier: MIT - */ - -#define PT_IMPLEMENTATION -#define PT_DEBUG -#include - -int luaopen_ptinit(lua_State *L) { - /* We are not doing anything with the Userdata. */ - (void) pallene_tracer_init(L); - /* The init function pushes the finalizer object onto the Lua stack. Which - we also do not need. */ - lua_pop(L, 1); - - return 0; -} From bd950373721c1d19b82f1d5cf769a26dbda8f5b8 Mon Sep 17 00:00:00 2001 From: SD Asif Hossein Date: Fri, 9 Aug 2024 19:54:09 +0600 Subject: [PATCH 2/3] Removal of Pallene Tracer dynamic linking --- Makefile | 10 ++-------- spec/tracebacks/multimod/module_a.c | 4 +++- spec/tracebacks/multimod/module_b.c | 3 +++ spec/tracebacks/multimod/module_include.h | 2 -- spec/tracebacks_spec.lua | 4 ++-- src/ptracer/main.c | 12 ------------ 6 files changed, 10 insertions(+), 25 deletions(-) delete mode 100644 src/ptracer/main.c diff --git a/Makefile b/Makefile index 4a9aed7..ba5ed33 100644 --- a/Makefile +++ b/Makefile @@ -15,11 +15,8 @@ INSTALL_BINDIR := $(INSTALL_DIR)/bin .PHONY: install ptracer_header pt-run libptracer uninstall clean -install: ptracer_header pt-run libptracer - strip -s pt-run +install: ptracer_header pt-run cp pt-run $(INSTALL_BINDIR) - strip -s libptracer.so - cp libptracer.so $(INSTALL_LIBDIR) # We need the `ptracer.h` header to be installed first. ptracer_header: @@ -28,12 +25,9 @@ ptracer_header: pt-run: $(CC) $(CFLAGS) src/pt-run/main.c -o pt-run -llua -lm -Wl,-E -L$(LUA_DIR)/lib -libptracer: - $(CC) -fPIC -shared $(CFLAGS) src/ptracer/main.c -o libptracer.so - uninstall: rm -rf $(INSTALL_INCDIR)/ptracer.h - rm -rf $(INSTALL_LIBDIR)/libptracer.so + rm -rf $(INSTALL_BINDIR)/pt-run clean: rm -rf examples/*/*.so diff --git a/spec/tracebacks/multimod/module_a.c b/spec/tracebacks/multimod/module_a.c index de6acd1..cfa910e 100644 --- a/spec/tracebacks/multimod/module_a.c +++ b/spec/tracebacks/multimod/module_a.c @@ -5,7 +5,9 @@ * SPDX-License-Identifier: MIT */ -/* This time we would be doing dynamic linking. */ +#define PT_IMPLEMENTATION +#include + #include "module_include.h" void some_mod_fn(lua_State *L) { diff --git a/spec/tracebacks/multimod/module_b.c b/spec/tracebacks/multimod/module_b.c index 252fe83..2e63519 100644 --- a/spec/tracebacks/multimod/module_b.c +++ b/spec/tracebacks/multimod/module_b.c @@ -5,6 +5,9 @@ * SPDX-License-Identifier: MIT */ +#define PT_IMPLEMENTATION +#include + #include "module_include.h" void another_mod_fn(lua_State *L) { diff --git a/spec/tracebacks/multimod/module_include.h b/spec/tracebacks/multimod/module_include.h index 0920d65..89f4fd7 100644 --- a/spec/tracebacks/multimod/module_include.h +++ b/spec/tracebacks/multimod/module_include.h @@ -1,8 +1,6 @@ #ifndef MODULE_INCLUDE_HEADER #define MODULE_INCLUDE_HEADER -#include - /* Here goes user specific macros when Pallene Tracer debug mode is active. */ #ifdef PT_DEBUG #define MODULE_GET_FNSTACK \ diff --git a/spec/tracebacks_spec.lua b/spec/tracebacks_spec.lua index a571c41..2810312 100644 --- a/spec/tracebacks_spec.lua +++ b/spec/tracebacks_spec.lua @@ -43,9 +43,9 @@ it("Multi-module", function() assert_test("multimod", [[ Runtime error: main.lua:10: Error from another module! Stack traceback: - module_b.c:16: in function 'another_mod_fn' + module_b.c:19: in function 'another_mod_fn' main.lua:10: in function 'some_lua_fn' - module_a.c:18: in function 'some_mod_fn' + module_a.c:20: in function 'some_mod_fn' main.lua:13: in
]]) end) diff --git a/src/ptracer/main.c b/src/ptracer/main.c deleted file mode 100644 index 7ed238d..0000000 --- a/src/ptracer/main.c +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) 2024, The Pallene Developers - * Pallene Tracer is licensed under the MIT license. - * Please refer to the LICENSE and AUTHORS files for details - * SPDX-License-Identifier: MIT - */ - -/* This source will be used to build the shared, dynamically - * linkable library of Pallene Tracer. */ - -#define PT_IMPLEMENTATION -#include From 380b8d11048b3c8ada4e39174704ef6881f79ead Mon Sep 17 00:00:00 2001 From: SD Asif Hossein Date: Fri, 9 Aug 2024 20:15:42 +0600 Subject: [PATCH 3/3] Bug fix in Multimod test --- spec/tracebacks/multimod/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/tracebacks/multimod/Makefile b/spec/tracebacks/multimod/Makefile index c27f433..5669961 100644 --- a/spec/tracebacks/multimod/Makefile +++ b/spec/tracebacks/multimod/Makefile @@ -10,5 +10,5 @@ LIB_DIR := /usr/local/lib .SILENT: all: - $(CC) -std=c99 -pedantic -Wall -Wextra -fPIC -DPT_DEBUG -shared module_a.c -o module_a.so -lptracer -Wl,-rpath=$(LIB_DIR) -L$(LIB_DIR) - $(CC) -std=c99 -pedantic -Wall -Wextra -fPIC -DPT_DEBUG -shared module_b.c -o module_b.so -lptracer -Wl,-rpath=$(LIB_DIR) -L$(LIB_DIR) + $(CC) -std=c99 -pedantic -Wall -Wextra -fPIC -DPT_DEBUG -shared module_a.c -o module_a.so + $(CC) -std=c99 -pedantic -Wall -Wextra -fPIC -DPT_DEBUG -shared module_b.c -o module_b.so