MDB: Monoglot Debugger

Command Syntax

mdb [--ui] [--port=PORT] -- ...DEBUGTARGET...

Example

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.

Usage

The debugger acts as a web server. If the --ui option is given, it will launch a browser to point to the debugger UI.

Keyboard Shortcuts

Type ? to see a list of keyboard shortcuts.

Interactive Console

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.

Debugger Functions

When a program is running in the debugger, the following fields of the debug library will be populated:

Error Handling

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

Coroutines

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.

Internals

A debugging session involves multiple communicating processes:

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.

spawns
Browser
MDB
HTTP
Target
MDB
Protocol
mdbagent

Modes

The agent switches between two different modes:

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:

MDB Protocol

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.

Client Messages

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.

Server Messages

Observable Items

"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:

Value Descriptions

A “Value Description” is a string that describes a Lua value.

Examples:

Type

Example Value Descriptions

nil

nil

boolean

true, false

number

1, -1.234e20

string

"short string", "long string ..." 12

table

table 14

function

function 15

userdata

userdata 17

thread

thread 201

Console Entries

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:

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.