Skip to content

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.

Here are some of the most commonly used functions:

Function SignaturePurpose
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 caller
end
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
end
end
local counter1 = create_counter()
print("Counter1:", counter1()) -- Output: 1
print("Counter1:", counter1()) -- Output: 2
-- Inspect and modify counter1's upvalue 'n'
local i = 1
repeat
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
end
until not name
print("Counter1 after modification:", counter1()) -- Output: 101

Output of the upvalue example:

Counter1: 1
Counter1: 2
Upvalue index 1: name='n', value=2
Modifying upvalue 'n' to 100
Counter1 after modification: 101

While the debug library is powerful, for most day-to-day debugging, developers use other techniques:

  • Print Debugging: The simplest form, using print(variable) or print(require('inspect')(variable)) (with a suitable inspect.lua library 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. mobdebug is 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.
  • VS Code with Lua Extensions: Editors like Visual Studio Code, with extensions such as lua-language-server (by sumneko/LuaLS) or EmmyLua, offer excellent debugging support (often using mobdebug or 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.
  • 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.getinfo and debug.getlocal are 1-based from the currently executing function. Level 0 is getinfo itself, level 1 is the function that called getinfo, etc.
  • debug Library 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 (_ENV in 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).