|
| 1 | +🐞 debugger.lua 🌖 |
| 2 | += |
| 3 | + |
| 4 | +A simple, embedabble debugger for Lua 5.x, and LuaJIT 2.x. |
| 5 | + |
| 6 | + |
| 7 | + |
| 8 | +debugger.lua is a simple, single file, pure Lua debugger that is easy to integrate with any project. The lua-users wiki lists a [number of debuggers](http://lua-users.org/wiki/DebuggingLuaCode). clidebugger was closest to what I was looking for, but I ran into several compatibility issues, and the rest are pretty big libraries with a whole lot of dependencies. I just wanted something simple to integrate that would work through stdin/stdout. I also decided that it sounded fun to try and make my own! |
| 9 | + |
| 10 | +✅ Features |
| 11 | +- |
| 12 | + |
| 13 | +- Trivial to "install". Can be integrated as a single .lua _or_ .h file. |
| 14 | +- The regular assortment of commands you'd expect from a debugger: continue, step, next, finish, print/eval expression, move up/down the stack, backtrace, print locals, inline help. |
| 15 | +- Evaluate expressions, call functions interactively, and get/set variables. |
| 16 | +- Pretty printed output so you see `{1 = 3, "a" = 5}` instead of `table: 0x10010cfa0` |
| 17 | +- Speed! The debugger hooks are only set during the step/next/finish commands. |
| 18 | +- Conditional, assert-style breakpoints. |
| 19 | +- Colored output and line editing support when possible. |
| 20 | +- Drop in replacements for Lua's `assert()`, `error()`, and `pcall()` functions that trigger the debugger. |
| 21 | +- When using the C API, `dbg_call()` works as a drop-in replacement for `lua_pcall()`. |
| 22 | +- IO can easily be remapped to a socket or window by overwriting the `dbg.write()` and `dbg.read()` functions. |
| 23 | +- Permissive MIT license. |
| 24 | + |
| 25 | +🙌 Easy to use from C too! |
| 26 | +- |
| 27 | + |
| 28 | +debugger.lua can be easily integrated into an embedded project with just a .h file. First though, you'll need to run `lua make_c_header.lua`. This generates debugger_lua.h by inserting the lua code into a template .h file. |
| 29 | + |
| 30 | +```c |
| 31 | +// You need to define this in one of your C files. (and only one!) |
| 32 | +#define DEBUGGER_LUA_IMPLEMENTATION |
| 33 | +#include "debugger_lua.h" |
| 34 | + |
| 35 | +int main(int argc, char **argv){ |
| 36 | + // Do normal Lua init stuff. |
| 37 | + lua_State *lua = luaL_newstate(); |
| 38 | + luaL_openlibs(lua); |
| 39 | + |
| 40 | + // Load up the debugger module as "debugger". |
| 41 | + // Also stores it in a global variable "dbg". |
| 42 | + // Use dbg_setup() to change these or use custom I/O. |
| 43 | + dbg_setup_default(lua); |
| 44 | + |
| 45 | + // Load some buggy Lua code. |
| 46 | + luaL_loadstring(lua, |
| 47 | + "local num = 1 \n" |
| 48 | + "local str = 'one' \n" |
| 49 | + "local res = num + str \n" |
| 50 | + ); |
| 51 | + |
| 52 | + // Run it in the debugger. This function works just like lua_pcall() otherwise. |
| 53 | + // Note that setting your own custom message handler disables the debugger. |
| 54 | + if(dbg_pcall(lua, 0, 0, 0)){ |
| 55 | + fprintf(stderr, "Lua Error: %s\n", lua_tostring(lua, -1)); |
| 56 | + } |
| 57 | +} |
| 58 | +``` |
| 59 | +
|
| 60 | +Now in your Lua code you can access `dbg` or use `require 'debugger'`. |
| 61 | +
|
| 62 | +🐝 Debugger Commands: |
| 63 | +- |
| 64 | +
|
| 65 | +If you have used other CLI debuggers, debugger.lua shouldn't be surprising. I didn't make a fancy parser, so the commands are just single letters. Since the debugger is pretty simple there are only a small handful of commands anwyay. |
| 66 | +
|
| 67 | + [return] - re-run last command |
| 68 | + c(ontinue) - contiue execution |
| 69 | + s(tep) - step forward by one line (into functions) |
| 70 | + n(ext) - step forward by one line (skipping over functions) |
| 71 | + p(rint) [expression] - execute the expression and print the result |
| 72 | + f(inish) - step forward until exiting the current function |
| 73 | + u(p) - move up the stack by one frame |
| 74 | + d(own) - move down the stack by one frame |
| 75 | + w(here) [line count] - print source code around the current line |
| 76 | + t(race) - print the stack trace |
| 77 | + l(ocals) - print the function arguments, locals and upvalues. |
| 78 | + h(elp) - print this message |
| 79 | + q(uit) - halt execution |
| 80 | +
|
| 81 | +If you've never used a command line debugger before, start a nice warm cozy fire, run tutorial.lua, and open it up in your favorite editor so you can follow along. |
| 82 | +
|
| 83 | +🦋 Debugger API |
| 84 | +- |
| 85 | +
|
| 86 | +There are several overloadable functions you can use to customize debugger.lua. |
| 87 | +* `dbg.read(prompt)` - Show the prompt and block for user input. (Defaults to read from stdin) |
| 88 | +* `dbg.write(str)` - Write a string to the output. (Defaults to write to stdout) |
| 89 | +* `dbg.shorten_path(path)` - Return a shortened version of a path. (Defaults to simply return `path`) |
| 90 | +* `dbg.exit(err)` - Stop debugging. (Defaults to `os.exit(err)`) |
| 91 | +* `dbg.pretty(obj)` - Output a pretty print string for an object. (Defaults to a reasonable version using __tostring metamethods and such) |
| 92 | +
|
| 93 | +Using these you can customize the debugger to work in your environment. For instance, you can divert the I/O over a network socket or to a GUI window. |
| 94 | +
|
| 95 | +There are also some goodies you can use to make debugging easier. |
| 96 | +* `dbg.writeln(format, ...)` - Basically the same as `dbg.write(string.format(format.."\n", ...))` |
| 97 | +* `dbg.pretty_depth = int` - Set how deep `dbg.pretty()` formats tables. |
| 98 | +* `dbg.pretty(obj)` - Will return a pretty print string of an object. |
| 99 | +* `dbg.pp(obj)` - Basically the same as `dbg.writeln(dbg.pretty(obj))` |
| 100 | +* `dbg.auto_where = int_or_false` - Set the where command to run automatically when the active line changes. The value is the number of context lines. |
| 101 | +* `dbg.error(error, [level])` - Drop in replacement for `error()` that breaks in the debugger. |
| 102 | +* `dbg.assert(error, [message])` - Drop in replacement for `assert()` that breaks in the debugger. |
| 103 | +* `dbg.call(f, ...)` - Drop in replacement for `pcall()` that breaks in the debugger. |
| 104 | +
|
| 105 | +🪲 Environment Variables: |
| 106 | +- |
| 107 | +
|
| 108 | +Want to disable ANSI color support or disable GNU readline? Set the `DBG_NOCOLOR` and/or `DBG_NOREADLINE` environment variables. |
| 109 | +
|
| 110 | +🕷️ Known Issues: |
| 111 | +- |
| 112 | +
|
| 113 | +- Lua 5.1 lacks the API to access varargs. The workaround is to do something like `local args = {...}` and then use `unpack(args)` when you want to access them. In Lua 5.2+ and LuaJIT, you can simply use `...` in your expressions with the print command. |
| 114 | +- You can't add breakpoints to a running program or remove them. Currently the only way to set them is by explicitly calling the `dbg()` function explicitly in your code. (This is sort of by design and sort of because it's difficult/slow otherwise.) |
| 115 | +- Different interpreters (and versions) print out slightly different stack trace information. |
| 116 | +- Tail calls are handled silghtly differently in different interpreters. You may find that 1.) stepping into a function that does nothing but a tail call steps you into the tail called function. 2.) The interpreter gives you the wrong name of a tail called function (watch the line numbers). 3.) Stepping out of a tail called function also steps out of the function that performed the tail call. Mostly this is never a problem, but it is a little confusing if you don't know what is going on. |
| 117 | +- Coroutine support has not been tested extensively yet, and Lua vs. LuaJIT handle them differently anyway. -_- |
| 118 | +
|
| 119 | +🪰 License: |
| 120 | +- |
| 121 | +
|
| 122 | + Copyright (c) 2024 Scott Lembcke and Howling Moon Software |
| 123 | + |
| 124 | + Permission is hereby granted, free of charge, to any person obtaining a copy |
| 125 | + of this software and associated documentation files (the "Software"), to deal |
| 126 | + in the Software without restriction, including without limitation the rights |
| 127 | + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 128 | + copies of the Software, and to permit persons to whom the Software is |
| 129 | + furnished to do so, subject to the following conditions: |
| 130 | + |
| 131 | + The above copyright notice and this permission notice shall be included in |
| 132 | + all copies or substantial portions of the Software. |
| 133 | + |
| 134 | + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 135 | + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 136 | + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 137 | + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 138 | + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 139 | + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 140 | + SOFTWARE. |
0 commit comments