mdb [--ui] [--port=PORT] -- ...DEBUGTARGET...
--ui: start the browser (this is done by issuing an open command).
--port=PORT: specify the HTTP port (and optionally IP address) on which to serve the UI. It can take the following forms:
“N” : a single decimal integer names the port on which to accept connections from the browser. This will bind only to the localhost (127.0.0.1) interface.
“A:N : an IP address followed by a colon and a decimal integer specifies the interface and port number on which to accept connections. An address of ”0“ causes MDB to listen on all interfaces.
...DEBUGTARGET... is a sequence of words constituting the command line to invoke the target process.
In order for the debugger to work, the target process must load mdbagent.lua at startup. This can be done by passing the -l option to the Lua interpreter (as long as mdbagent.lua is found in the Lua search path).
The xpio module (exported by the monoglot package) is required by the mdbagent package, and must be available in the target program's search paths (as specified by the LUA_PATH and LUA_CPATH environment variables).
There are two ways to run a program under the debugger.
The first loads mdbagent using -l:
mdb --ui --port=8000 -- lua -l mdbagent myprogram.lua ...args...
Similarly, Lua-based programs other than the Lua interpreter can include the mdbagent package.
If we run mdbagent as a source file, it loads and runs the file listed as the next argument:
mdb --ui --port=8000 -- lua mdbagent.lua myprogram.lua ...args...
This second form allows MDB to trap uncaught errors that occur in the target program.
The debugger acts as a web server. If the --ui option is given, it will launch a browser to point to the debugger UI.
Type ? to see a list of keyboard shortcuts.
The interactive console allows you to type Lua code and have it immediately executed. The Lua code will be executed in the context where execution is currently paused — all local variables, up-values, and globals are in scope.
When an expression is typed, its value will be displayed in the console. When a chunk is typed, its return values (zero or more) will be displayed in the console.
Keep in mind that print and io.write will write to stdout of the target process — not to the debug console. Use debug.log or debug.printf (see Debugger Functions) to output to the interactive console.
When a program is running in the debugger, the following fields of the debug library will be populated:
debug.log(...)
Write values to the console output window.
debug.printf(...)
Write a string to the console output window. This uses string.format plus the %Q formatting option, which displays the value in an explorable form.
debug.pause()
Pause execution at the current line of code.
The debugger pauses execution at any error, whether generated by error, assert, or other runtime errors (e.g. attempt to call a non-function value).
The call stack shows the location of the error, prior to handling by an xpcall handler (if one is in effect) and prior to returning to the nearest protected call boundary (e.g. pcall, xpcall, or coroutine.resume).
Stepping “in” to coroutine.yield or coroutine.resume will single-step into the other coroutine.
Stepping “over” a coroutine.yield or coroutine.resume will cause the debugger to pause again after the function returns to the current coroutine. Stepping “out” similarly sets a “pause again” condition.
Whenever the debugger pauses, as it does when a breakpoint is hit, it clears the “pause again” conditions for the current coroutine (the one in which the breakpoint was hit). Any other coroutines retain their “pause” again conditions.
A debugging session involves multiple communicating processes:
The target is a program that contains the code being debugger.
MDB is the “debugger”.
A browser, through which the UI is viewed.
MDB is the web server to which the browser connects. The target process is typically a child process of the debugger. The mdbagent Lua module implements the server-side of the protocol and controls the software being debugged in the target process, staying mostly invisible to the target software.
The agent switches between two different modes:
In pause mode the target program is suspended at a point of execution. The agent waits for messages from the debugger.
In run mode the target program is executing. The agent watches for the target to reach breakpoints and also watches for commands from the debugger.
The debugger transitions from pause mode to run mode in response to run message from the server.
The debugger transitions from run mode to pause mode as a result of one of the following:
a pause message from the server
a breakpoint is reached
a run limit is reached (“stepping” in, over, or out of functions)
an error triggered by the target program
the target program exiting
The MDB protocol describes the communication between MDB (the “client”) and the target process (the “server”).
Communication in each direction is a sequence of messages. Each message has an ID (a short ASCII string) and zero or more values (Lua values). The concrete syntax is defined in terms of how it is parsed, using PEG notation:
Message <- ID Value* NL Value <- ( !SP !NL . )+ SP* ID <- ( !SP !NL . )+ SP* SP <- " " NL <- "\n"
In the above syntax, “.” matches any byte.
Note that each Value non-terminal contains neither spaces nor newline characters. These contain encoded Lua values that can be decoded using mdbser.decode.
Messages can be thought of as remote procedure invocations without return values. These are documented below as Lua functions. The function name is the message ID, and the function parameters are the message values.
bp(breakpoints)
Update the active set of breakpoints. breakpoints is a table mapping file names to arrays of line numbers.
eval(code)
Compile and execute code. code is Lua source code to be treated as an expression (if a valid expression) or a Lua chunk (function body).
The evaluated code and its results and/or errors, if any, will be logged to the console, as described in Console Entries.
run(limit)
Resume execution, and optionally set a run limit as described by limit:
"over" : step over function calls. "in" : step into function calls. "out" : step out of the current function.
If any other value is supplied, no run limit will be in effect.
sub(name): subscribe to an observable item. The server updates the server whenever the item's current value differs from the value most recently sent to the client. The server checks for updates before acknowledging each client message and before sending a “pause” message.
unsub(name): unsubscribe to updates on an item.
pause()
This is sent when the target transitions to “pause” mode from “run” mode. It is sent after subscribed values have been updated to reflect the new pause state.
run()
This is sent when the target transitions to “run” mode from “pause” mode.
exit()
This is sent when the the target returns from its main function. At this point the agent ceases communication with the server.
log(entry)
This appends an entry onto the debugging console. See Console Entries, below.
set(name, value)
This delivers a new value for a subscribed item. See sub(name), above.
ack()
This acknowledges a client message. The server sends one ack for each message received from the client, after the documented actions have been performed.
"stack" is the contents of the stack as of the last time the target was in pause mode. This value is an array of tables describing stack frames, with the most deeply nested stack frame first. See getStack() in mdbagent.lua for the fields in each frame.
Items named "pairs/DESC", where DESC is a value description, describe the contents of the table DESC. The value is an array of {name=DESC, value=DESC}, one for each key/value pair in the table.
Items named "vars/INDEX", where INDEX is an integer, describe the local variables and upvalues visible in the function at a given activation record. The first index, 1, refers to the most deeply-nested activation record.
In error conditions the observed value will be a table with an error field with one of the following values:
"stale" => the value description is no longer valid.
"unk" => the subscribed item name is unknown/unsupported.
A “Value Description” is a string that describes a Lua value.
Simple data types (nil, boolean, number, and short strings) are represented as they appear in Lua source code. Strings are encoded using string.format("%q", value), and control characters (\000 ... \031) are encoded as numeric escape sequences.
Reference data types (table, function, userdata, and thread) are encoded as the type name followed by a space and a numeric ID.
When strings exceed a certain length, an initial portion of the string is returned, followed by a numeric ID.
Examples:
Type | Example Value Descriptions |
|---|---|
nil |
|
boolean |
|
number |
|
string |
|
table |
|
function |
|
userdata |
|
thread |
|
Each console entry is a string. The first byte of the string identifies the type of entry, and the remainder of the string is the payload, which is usually text to be displayed, but sometimes a value description.
The following entry types are defined:
C = code that was executed using eval()
E = an error encountered during eval()
R = a value returned after executing a command; payload is a value description.
S = status message (from the debugger, not the target process)
P = text output using debug.printf()
V = a value logged via debug.log(); payload is a value description.
W = warning [internal condition]
The content of a P entry uses an encoding to intersperse “%Q”-generated value descriptions. !2 signifies the beginning of a value description and !1 signifies the end. !0 signifies a literal “!”, whether it occurs inside or outside of a value description.