Lua Debugging
Lua - Debugging
Section titled “Lua - Debugging”Lua includes a debug library that provides powerful introspection capabilities. It’s primarily used for building debuggers or for advanced introspection tasks, rather than for typical everyday debugging by application developers (where print statements or dedicated debuggers are more common).
The debug library allows you to inspect the execution stack, get information about functions (source, line numbers, variable names), access local and upvalue variables, set hooks for tracing, and more.
Key Functions in the debug Library
Section titled “Key Functions in the debug Library”Here are some of the most commonly used functions:
| Function Signature | Purpose |
|---|---|
debug.debug() | Enters an interactive debug mode. You can type Lua statements to inspect variables. Type cont to exit. |
debug.getfenv(object) | Returns the environment table of the given object (function, thread, or userdata). Deprecated in Lua 5.2+; use _ENV and upvalues for lexical scoping. |
debug.setfenv(object, table) | Sets the environment of the given object. Deprecated in Lua 5.2+. |
debug.getinfo([thread,] func_or_stacklevel [, what_string]) | Returns a table with information about a function. func_or_stacklevel can be a function or a stack level number. what_string specifies which fields to retrieve (e.g., ‘S’ for source, ‘l’ for current line, ‘n’ for name, ‘f’ for function object, ‘u’ for number of upvalues). |
debug.getlocal([thread,] stacklevel, local_index) | Returns the name and value of the local variable at local_index of the function at stacklevel. Returns nil if no such variable exists. |
debug.setlocal([thread,] stacklevel, local_index, value) | Assigns value to the local variable. Returns the local variable name or nil. |
debug.getupvalue(func, upvalue_index) | Returns the name and value of the upvalue at upvalue_index for the function func. |
debug.setupvalue(func, upvalue_index, value) | Assigns value to the upvalue. Returns the upvalue name or nil. |
debug.getmetatable(value) | Returns the metatable of the given value. |
debug.setmetatable(value, metatable) | Sets the metatable for the given value. |
debug.traceback([thread,] [message_string,] [level]) | Returns a string containing a stack traceback. Useful for constructing custom error messages. |
debug.sethook([thread,] hook_func, mask_string, count) | Sets a hook function that is called on certain events (call, return, line, count instructions) specified by mask_string (‘c’, ‘r’, ‘l’) and count. |
Example: Stack Traceback and Function Info
Section titled “Example: Stack Traceback and Function Info”local function level_two() print("--- Inside level_two ---") -- Get info about the current function (level_two) local info_current = debug.getinfo(1, "Snl") -- S: source, n: name, l: currentline print(string.format("Current function name: %s, source: %s, line: %d", info_current.name or "?", info_current.short_src, info_current.linedefined))
-- Get info about the caller (level_one) local info_caller = debug.getinfo(2, "Snl") if info_caller then print(string.format("Caller function name: %s, source: %s, line defined: %d", info_caller.name or "?", info_caller.short_src, info_caller.linedefined)) end
print("Stack traceback from level_two:") print(debug.traceback("Custom message from level_two", 2)) -- Start traceback from callerend
local function level_one() print("--- Inside level_one ---") level_two()end
level_one()
print("--- Back in main chunk ---")local main_info = debug.getinfo(1, "S")print("Main chunk source:", main_info.short_src)The output will show information about the functions and a stack traceback. The exact line numbers and source paths will depend on how you save and run the file.
Example: Inspecting and Modifying Upvalues
Section titled “Example: Inspecting and Modifying Upvalues”Upvalues are external local variables used by a closure. The debug library can inspect and even modify them.
local function create_counter() local n = 0 -- This is an upvalue for the returned function return function() n = n + 1 return n endend
local counter1 = create_counter()print("Counter1:", counter1()) -- Output: 1print("Counter1:", counter1()) -- Output: 2
-- Inspect and modify counter1's upvalue 'n'local i = 1repeat local name, value = debug.getupvalue(counter1, i) if name then print(string.format("Upvalue index %d: name='%s', value=%s", i, name, tostring(value))) if name == "n" then print("Modifying upvalue 'n' to 100") debug.setupvalue(counter1, i, 100) end i = i + 1 enduntil not name
print("Counter1 after modification:", counter1()) -- Output: 101Output of the upvalue example:
Counter1: 1Counter1: 2Upvalue index 1: name='n', value=2Modifying upvalue 'n' to 100Counter1 after modification: 101Debugging Approaches
Section titled “Debugging Approaches”While the debug library is powerful, for most day-to-day debugging, developers use other techniques:
- Print Debugging: The simplest form, using
print(variable)orprint(require('inspect')(variable))(with a suitableinspect.lualibrary for pretty-printing tables) to check values at various points. - Command-Line Debuggers: Tools that allow stepping through code, setting breakpoints, and inspecting variables from the command line.
mobdebugis a well-known remote debugger library that can be used to build or integrate with command-line debuggers. - Graphical Debuggers / IDE Integration: Modern IDEs and text editors often have Lua debugging extensions. These provide a visual interface for breakpoints, stepping, variable inspection, call stack view, etc.
Modern Debugging Tools
Section titled “Modern Debugging Tools”- VS Code with Lua Extensions: Editors like Visual Studio Code, with extensions such as
lua-language-server(by sumneko/LuaLS) orEmmyLua, offer excellent debugging support (often usingmobdebugor similar protocols). This is a very popular choice. - ZeroBrane Studio: A lightweight Lua IDE with a built-in debugger that supports local and remote debugging for various Lua engines (LuaJIT, LÖVE, Moai, etc.). Open source.
- Other IDE Plugins: IntelliJ IDEA (with EmmyLua/IntelliJ-Lua plugin), Eclipse (e.g., with LDT - Lua Development Tools, though LDT’s activity has waned), and others may offer Lua debugging capabilities.
Historical Debugging Tools
Section titled “Historical Debugging Tools”- RemDebug: An early remote debugger for Lua 5.0/5.1.
- clidebugger: A simple command-line debugger for Lua 5.1.
- SciTE: The default Windows IDE for Lua included some basic debugging features.
- Decoda: A graphical debugger with remote debugging (status less clear in recent years).
Common Learning Obstacles:
- Understanding Stack Levels: Stack levels in
debug.getinfoanddebug.getlocalare 1-based from the currently executing function. Level 0 isgetinfoitself, level 1 is the function that calledgetinfo, etc. debugLibrary is Low-Level: It’s a tool for building debuggers, not an end-user debugger itself. For application debugging, use dedicated debugger tools or IDEs.- Scope and Environments: Understanding how environments (
_ENVin Lua 5.2+) and upvalues work is crucial for effective use of related debug functions.
Further Resources: Consult the official Lua Reference Manual for the debug library. For specific debugger tools, refer to their respective documentation (e.g., ZeroBrane Studio documentation, VS Code extension marketplaces).