A dependency free, embeddable debugger for Lua in a single file (.lua or .c)



A simple, embedabble debugger for Lua 5.x, and LuaJIT 2.x.


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. 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!


  • Trivial to "install". Can be integrated as a single .lua or .c file.
  • 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.
  • Evaluate expressions, call functions interactively, and get/set variables.
  • Pretty printed output so you see {1 = 3, "a" = 5} instead of table: 0x10010cfa0
  • Speed! The debugger hooks are only set during the step/next/finish commands.
  • Conditional, assert-style breakpoints.
  • Colored output and line editing support when possible.
  • Drop in replacements for Lua's assert(), error(), and pcall() functions that trigger the debugger.
  • When using the C API, dbg_call() works as a drop-in replacement for lua_pcall().
  • IO can easily be remapped to a socket or window by overwriting the dbg.write() and dbg.read() functions.
  • Permissive MIT license.

Easy to use from C too!

debugger.lua can be easily integrated into an embedded project with just a .c and .h file. First though, you'll need to run lua embed/debugger.c.lua. This generates embed/debugger.c by inserting the lua code into a template .c file.

int main(int argc, char **argv){
	lua_State *lua = luaL_newstate();

	// The 2nd parameter is the module name. (Ex: require("debugger") )
	// The 3rd parameter is the name of a global variable to bind it to, or NULL if you don't want one.
	// The last two are lua_CFunctions for overriding the I/O functions.
	// A NULL I/O function  means to use standard input or output respectively.
	dbg_setup(lua, "debugger", "dbg", NULL, NULL);

	// Load some lua code and prepare to call the MyBuggyFunction() defined below...

	// dbg_pcall() is called exactly like lua_pcall().
	// Although note that using a custom message handler disables the debugger.
	if(dbg_pcall(lua, nargs, nresults, 0)){
		fprintf(stderr, "Lua Error: %s\n", lua_tostring(lua, -1));

Now in your Lua code you can just use the global variable or require the module name you passed to the dbg_setup() call.

Debugger Commands:

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.

[return] - re-run last command
c(ontinue) - contiue execution
s(tep) - step forward by one line (into functions)
n(ext) - step forward by one line (skipping over functions)
p(rint) [expression] - execute the expression and print the result
f(inish) - step forward until exiting the current function
u(p) - move up the stack by one frame
d(own) - move down the stack by one frame
w(here) [line count] - print source code around the current line
t(race) - print the stack trace
l(ocals) - print the function arguments, locals and upvalues.
h(elp) - print this message
q(uit) - halt execution

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.

Debugger API

There are several overloadable functions you can use to customize debugger.lua.

  • dbg.read(prompt) - Show the prompt and block for user input. (Defaults to read from stdin)
  • dbg.write(str) - Write a string to the output. (Defaults to write to stdout)
  • dbg.shorten_path(path) - Return a shortened version of a path. (Defaults to simply return path)
  • dbg.exit(err) - Stop debugging. (Defaults to os.exit(err))

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.

There are also some goodies you can use to make debugging easier.

  • dbg.writeln(format, ...) - Basically the same as dbg.write(string.format(format.."\n", ...))
  • dbg.pretty_depth = int - Set how deep dbg.pretty() formats tables.
  • dbg.pretty(obj) - Will return a pretty print string of an object.
  • dbg.pp(obj) - Basically the same as dbg.writeln(dbg.pretty(obj))
  • 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.
  • dbg.error(error, [level]) - Drop in replacement for error() that breaks in the debugger.
  • dbg.assert(error, [message]) - Drop in replacement for assert() that breaks in the debugger.
  • dbg.call(f, ...) - Drop in replacement for pcall() that breaks in the debugger.

Environment Variables:

Want to disable ANSI color support or disable GNU readline? Set the DBG_NOCOLOR and/or DBG_NOREADLINE environment variables.

Known Issues:

  • 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.
  • 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.)
  • Different interpreters (and versions) print out slightly different stack trace information.
  • 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.
  • Coroutine support has not been tested extensively yet, and Lua vs. LuaJIT handle them differently anyway. -_-


Copyright (c) 2021 Scott Lembcke and Howling Moon Software

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

  • Variable Assignment

    Variable Assignment

    Thanks a lot for this great tool! Until now I haven't been able to figure out how to do variable assignment e.g. a=5 in the debugger environment. Is this a valid functionality?

    opened by yueatsprograms 14
  • Debugger UX Improvements

    Debugger UX Improvements

    Quick highlight of the proposed changes:

    • New q[uit] command to stop executing the program immediately.
    • New S[top] command to disable all breakpoints.
    • New pretty printing functionality redesigned for inspecting long/complex tables and subtables; handles recursion and custom __tostring metamethods gracefully.
    • New o[ption] <name> <val> command to set debugger options. Primarily used to control the new pretty printing.
    • l[ocals] view no longer expands tables, for readability reasons. Using the 'le' form re-enables this. (Note: the printing depth will have to be set to 2 or greater (via o depth 2 for this.)
    • New dbg.on/dbg.off functions, used to enable/disable breakpoints.
    • New dbg.opt function, used as an alternative to 'o ' every time the debugger is loaded.
    • New dbg.install function, places the debugger object in the global namespace as dbg.
    • Chunk compilation has been patched to allow expressions that cannot be prefixed with a return statement.
    • Any non-command statement is now treated as an implicit p[rint] <exp> statement. This results in a much improved debugging workflow.

    It also now supports using Antirez's Linenoise, via my bindings.

    Any comments?

    opened by Web-eWorks 11
  • fix: account for `error` calls in hook_factory

    fix: account for `error` calls in hook_factory

    Previously, an error call would prevent calls to repl because the stack offset wasn't being reset properly.

    This commit fixes that by setting the offset to -1 when an error call is encountered, which allows hook_factory instances to call repl on the next encountered Lua line.

    Also, the repl function is now called only when a "line" event is for a Lua function. This means the repl is never used to inspect a C function (which is pointless).

    opened by aleclarson 8
  • feat: support variable assignment

    feat: support variable assignment

    Assignment is done as you would expect: foo = bar + 2

    • No command required
    • Locals, upvalues, and globals can be used in the expression
    • Locals and upvalues are mutable
    • You can do tuple assignment: x, y = 1, 2

    Assignment of an undeclared or global variable does the following:

    • declare the variable in the REPL environment
    • that variable is only accessible via the REPL

    You can always mutate a global variable with _G.foo = 2

    This commits removes cmd_eval, but you may have a good reason to keep it around. Let me know!

    opened by aleclarson 6
  • dbg_writeln() error.

    dbg_writeln() error.

    From an email:

    Thanks for writing the Lua debugger. I use it in my control program for my model trains.

    It was trivial to install and by overwriting the dbg.* functions, I was able to seamless integrate it into my program.

    I did come across one odd behavior. I had this function in my code:

       function cleanup(Engine, TIU)
                -- this function is called once when the user presses the [STOP] button
                --print(os.date("cleanup()  %d %b %Y   %X"));
                return true;            -- false=cleanup failed, true=cleanup succeeded

    A commented out "print()" command that I had for testing only.

    I entered the debugger with "dbg()" and then used the "s(tep)" command to step into this function and through it.As I stepped through lines 68 an then line 69 I got this error message:

    68 -- this function is called once when the user presses the [STOP] button Error 2 running function cleanup() : E:\Projects\RTC\lua\debugger.lua:110: bad argument #3 to 'format' (no value)

    Here is the dbg_writeln() in debugger.lua that I overwrote (I don't need the "\n"):

       local function dbg_writeln(str, ...)
          if select("#", ...) == 0 then
                -- mcd change for RTC Debugging
                --dbg.write((str or "<NULL>").."\n")
                dbg.write((str or "<NULL>"))
                -- mcd change for RTC Debugging
                -- mcd change for RTC Debugging
                --dbg.write(string.format(str.."\n", ...))
                dbg.write(string.format(str, ...))                      ------- this is line 110 refered to in the error       
                -- mcd change for RTC Debugging

    I appears that the "%" in the commented out print() statement is confusing the string.format() call inside of dbg_writeln().

    I set "dbg.auto_where = 2" for the line tracing.

    This can be worked around but you might want to fix it.

    Thanks again, Mark

    opened by slembcke 5
  • Could not get it to work with love2d

    Could not get it to work with love2d

    Probably this debugger was not even meant to work with love2d, but I'm reporting here because it would be great to be able to get it working there!

    When running dbg(), I get the Loaded for LuaJIT 2.0.5 message in the terminal and after that the program keeps running instead of the debugger kicking in. Also, the program runs with very poor performance after this, as if it's doing something in the background.

    Edit: forgot to mention love version: 11.1

    bug LuaJIT 
    opened by eduardomezencio 5
  • (n)ext and (s)tep should behave different after using (u)p

    (n)ext and (s)tep should behave different after using (u)p

    Let's say I moved up the stack by a frame. Now, I want to skip to the (n)ext line of the frame I'm inspecting. The current behavior simply skips the line of the bottom frame (even if I moved up a frame).

    Would this be easy to implement? 😄

    opened by aleclarson 4
  • License


    Hi, could you choose and publish a license for this project? I want to use it in my game, but our packaging requirements require that all files have an explicit license from the author.

    If you don't particularly care, I'd suggest the MIT license since it's compatible with nearly every organization's licensing requirements.

    Great work on the library, hope to get a response soon.

    opened by bkconrad 4
  • Problem with arrow keys

    Problem with arrow keys

    This bug is not related to your code but maybe you know how to fix it.

    It happens on Xubuntu 20.04, xfce4-terminal, on any lua version (5.1-5.4), including luajit.

    In debugger.lua prompt I can't use arrow keys. When I press them, it types annoying bullshit like this: "^[[D^[[A^[[C^[[B".

    opened by lua-rocks 3
  • feat: make up/down ignore stack frames of C functions

    feat: make up/down ignore stack frames of C functions

    With this commit, the up and down commands hop over any C function calls. This provides a better experience, since you can't do anything on C frames anyway.

    The trace command will continue to show C function calls, though.

    opened by aleclarson 3
  • feat: add where command

    feat: add where command

    I squashed the commits by @churay that add a where command that print the lines of code surrounding the current breakpoint.

    debugger.lua> w
    58	             stream.headers = headers
    59	           else
    60	             return stream:shutdown(), app:onerror(err)
    61	           end
    62	           dbg()
    63	=>         local res = setmetatable({
    64	             headers = http_headers.new()
    65	           }, res_mt)
    66	           local _list_0 = app.pipes
    67	           for _index_0 = 1, #_list_0 do
    68	             local _continue_0 = false
    debugger.lua> w 7
    56	           local headers, err = stream:get_headers()
    57	           if headers then
    58	             stream.headers = headers
    59	           else
    60	             return stream:shutdown(), app:onerror(err)
    61	           end
    62	           dbg()
    63	=>         local res = setmetatable({
    64	             headers = http_headers.new()
    65	           }, res_mt)
    66	           local _list_0 = app.pipes
    67	           for _index_0 = 1, #_list_0 do
    68	             local _continue_0 = false
    69	             repeat
    70	               local pipe = _list_0[_index_0]
    debugger.lua> w1
    62	           dbg()
    63	=>         local res = setmetatable({
    64	             headers = http_headers.new()

    As you can see, the default line count is 5 on each side of the breakpoint.

    You can customize the line count by adding a number after the command. The space between is optional.

    It would be really cool to add syntax highlighting in the future.

    opened by aleclarson 3
  • add hint how to run the debugger_lua.c.lua file

    add hint how to run the debugger_lua.c.lua file

    trying to run the debugger_lua.c.lua file from within the embed folder will result in "attempt to index a nil value" errors, which are not obvious, as to why they happen. by reading through the readme, I've then found out that this file needs to be run from the main folder, which will allow "io.open('debugger.lua')" to not return nil.

    I've added a comment at the top of the file to make that obvious.

    opened by karstenBriksoft 0
  • [UX Suggestion] Add

    [UX Suggestion] Add "command not found" error message?

    I come from a Ruby background and am used to pry and byebug as my main debuggers, and spent a good 20mins wondering why I couldn't inspect variables with this debugger when stepping/moving worked fine.

    I was doing variations of this:

    debugger.lua> transitions
    debugger.lua> src_state
    debugger.lua> print src_state
    debugger.lua> locals
    debugger.lua> l
    	dest_state => "Ready"
    	src_state => "Not Ready"
    	transitions => {"Not Ready" = {}}

    I realize that l prints all the locals out, but I'm wondering how I can inspect individual variables...

    And then out of frustration I type in:

    debugger.lua> asdfasdf

    And notice that the prompt doesn't react and then I'm like ohhh when it doesn't understand a command it just pretends like nothing happened.

    Cue these attempts:

    debugger.lua> pp(transitions)
    debugger.lua> p
    Error: command 'p' not recognized.
    Type 'h' and press return for a command list.
    debugger.lua> pp
    debugger.lua> h
    [.. help output ..]
    debugger.lua> p transitions
    transitions => {"Not Ready" = {}}

    I was expecting the output of p and pp to tell me what arguments they were expecting.

    Even more confusing is that when you do type in the right single character command but with the wrong arguments, it says Error: command 'p' not recognized - which is arguably even more misleading 🤣

    Anyway. Just throwing my 2 cents in here. ~I'm still too much of a lua noob to attempt to fix this myself, but if you think it's a worthy addition, I can make an attempt.~

    Edit: Turned out to be an easy fix - I created a PR.

    opened by vyder 0
  • Windows 10 colored terminal

    Windows 10 colored terminal

    Windows 10 anniversary update added ENABLE_VIRTUAL_TERMINAL_PROCESSING flag to console mode, which can be used to enable ANSI color codes. This can be enabled for LuaJIT environment (where FFI is available). Below is my way to enable it.


    opened by MikuAuahDark 0
  • Truncate long string properties

    Truncate long string properties

    When inspecting a table, it sometimes has a property whose value is a long string that fills up the terminal. This is often too noisy. If I wanted to see the entire string, I would inspect that specific property.

    Truncation would replace the end of the string with ... (# = 1000). This could be colored so it's clear this isn't the string's real value.

    What would be a good length for truncation?

    opened by aleclarson 1
  • Coroutines


    It would be great if using finish or next within a coroutine would avoid triggering the REPL outside that coroutine (unless the coroutine dies). You can always use step if you want to follow yields.

    opened by aleclarson 0
Scott Lembcke
Scott Lembcke
RV-Debugger-BL702 is an opensource project that implement a JTAG+UART debugger with BL702C-A0.

BL702 is highly integrated BLE and Zigbee combo chipset for IoT applications, contains 32-bit RISC-V CPU with FPU, frequency up to 144MHz, with 132KB RAM and 192 KB ROM, 1Kb eFuse, 512KB embedded Flash, USB2.0 FS device interface, and many other features.

Sipeed 73 Jun 21, 2022
LuaZDF - Lua Zero Dependency Functions

LuaZDF - Lua Zero Dependency Functions LuaZDF is a collection of independet functions that have zero dependencies among themselves. A function in LuaZ

null 46 Jun 4, 2022
A small, dependency-free node editor extension for dear imgui.

imnodes A small, dependency-free node editor extension for dear imgui. Features: Create nodes, links, and pins in an immediate-mode style Single heade

Johann Muszynski 1.1k Jun 24, 2022
Single-chip solution for Hi-speed USB2.0(480Mbps) JTAG/SPI Debugger based on RISC-V MCU CH32V30x/CH32V20x

480Mbps High Speed USB2.0 JTAG Debugger Open source information |-- bin |--------MCU: MCU target program |--------WIN APP |------------------USB20Jtag

RISC-V 38 Jun 28, 2022
A high performance, shared memory, lock free, cross platform, single file, no dependencies, C++11 key-value store

SimDB A high performance, shared memory, lock free, cross platform, single file, no dependencies, C++11 key-value store. SimDB is part of LAVA (Live A

null 442 Jun 25, 2022
Elk is a tiny embeddable JavaScript engine that implements a small but usable subset of ES6

Elk is a tiny embeddable JavaScript engine that implements a small but usable subset of ES6. It is designed for microcontroller development. Instead of writing firmware code in C/C++, Elk allows to develop in JavaScript. Another use case is providing customers with a secure, protected scripting environment for product customisation.

Cesanta Software 1.4k Jun 27, 2022
Golang bindings of Sciter: the Embeddable HTML/CSS/script engine for modern UI development

Go bindings for Sciter Check this page for other language bindings (Delphi / D / Go / .NET / Python / Rust). Attention The ownership of project is tra

Terra Informatica Software, Inc 2.4k Jun 22, 2022
Zep - An embeddable editor, with optional support for using vim keystrokes.

Zep - A Mini Editor Zep is a simple embeddable editor, with a rendering agnostic design and optional Vim mode. It is built as a shared modern-cmake li

Rezonality - 630 Jun 27, 2022
A single file, single function, header to make notifications on the PS4 easier

Notifi Synopsis Adds a single function notifi(). It functions like printf however the first arg is the image to use (NULL and any invalid input should

Al Azif 7 Mar 24, 2022
Hobbyist Operating System targeting x86_64 systems. Includes userspace, Virtual File System, An InitFS (tarfs), Lua port, easy porting, a decent LibC and LibM, and a shell that supports: piping, file redirection, and more.

SynnixOS Epic Hobby OS targeting x86_64 CPUs, it includes some hacked together functionality for most essential OSs although, with interactivity via Q

RaidTheWeb 40 May 27, 2022
"Sigma File Manager" is a free, open-source, quickly evolving, modern file manager (explorer / finder) app for Windows, MacOS, and Linux.

"Sigma File Manager" is a free, open-source, quickly evolving, modern file manager (explorer / finder) app for Windows, MacOS, and Linux.

Aleksey Hoffman 892 Jun 27, 2022
🦘 A dependency injection container for C++11, C++14 and later

kangaru ?? Kangaru is an inversion of control container for C++11, C++14 and later. It provides many features to automate dependency injection and red

Guillaume Racicot 353 Jun 16, 2022
built-in CMSIS-DAP debugger tailored especially for the RP2040 “Raspberry Pi Pico”

RP2040 has two ARM Cortex-M0+ cores, and the second core normally remains dormant. pico-debug runs on one core in a RP2040 and provides a USB CMSIS-DAP interface to debug the other core. No hardware is added; it is as if there were a virtual debug pod built-in.

null 189 Jun 25, 2022
GameBoy emulator with debugger written in C++ using Win32 and SDL2

脳(のう)腐(ふ) DMG-01 emulator written in modern C++ using Win32 API and SDL2. It is intended as a hobby project to sharpen my C++ skills. The emulator is

Jimmy Yang 19 Jun 15, 2022
IDA Debugger Module to Dynamically Synchronize Memory and Registers with third-party Backends (Tenet, Unicorn, GDB, etc.)

IDA Debug Bridge IDA Debugger Module to Dynamically Synchronize Memory and Registers with third-party Backends (Tenet, Unicorn, GDB, etc.) By synchron

null 7 Jun 30, 2022
CHIP-8 Emulator/Debugger made with C++ 17, OpenGL & ImGui.

Description (Some frames were deleted to make the GIF smaller) CHIP-8 interpreter/debugger made with C++ 17. SDL 2.0.16 for window creation, event han

Alexsander Bispo 6 Jan 7, 2022
A keystone engine powered Windows Debugger extension

DbgKeystone A Keystone engine powered Windows Debugger extension Reasoning WinDbg's default assember (the a command) can't handle instructions involvi

Michael B. 9 May 19, 2022
An open-source x64/x32 debugger for windows.

x64dbg An open-source binary debugger for Windows, aimed at malware analysis and reverse engineering of executables you do not have the source code fo

x64dbg 38.7k Jun 29, 2022
Little driver for detect UM/KM debugger and Hypervisor

MAJESTY-technologies Little driver for protecthion. The driver is still under development, so you can submit your ideas! I write it's for manual map d

null 49 May 29, 2022