midenc-debug 0.0.5

An interactive debugger for Miden VM programs
Documentation

Miden Debugger

This crate implements a TUI-based interactive debugger for the Miden VM, designed to interoperate with midenc.

Usage

The easiest way to use the debugger, is via midenc debug, and giving it a path to a program compiled by midenc compile. See Program Inputs for information on how to provide inputs to the program you wish to debug. Run midenc help debug for more detailed usage documentation.

The debugger may also be used as a library, but that is left as an exercise for the reader for now.

Example

# Compile a program to MAST from a rustc-generated Wasm module
midenc compile foo.wasm -o foo.masl

# Load that program into the debugger and start executing it
midenc debug foo.masl

Program Inputs

To pass arguments to the program on the operand stack, or via the advice provider, you have two options, depending on the needs of the program:

  1. Pass arguments to midenc debug in the same order you wish them to appear on the stack. That is, the first argument you specify will be on top of the stack, and so on.
  2. Specify a configuration file from which to load inputs for the program, via the --inputs option.

Via Command Line

To specify the contents of the operand stack, you can do so following the raw arguments separator --. Each operand must be a valid field element value, in either decimal or hexadecimal format. For example:

midenc debug foo.masl -- 1 2 0xdeadbeef

If you pass arguments via the command line in conjunction with --inputs, then the command line arguments will be used instead of the contents of the inputs.stack option (if set). This lets you specify a baseline set of inputs, and then try out different arguments using the command line.

Via Inputs Config

While simply passing operands to the midenc debug command is useful, it only allows you to specify inputs to be passed via operand stack. To provide inputs via the advice provider, you will need to use the --inputs option. The configuration file expected by --inputs also lets you tweak the execution options for the VM, such as the maximum and expected cycle counts.

An example configuration file looks like so:

# This section is used for execution options
[options]
max_cycles = 5000
expected_cycles = 4000

# This section is the root table for all inputs
[inputs]
# Specify elements to place on the operand stack, leftmost element will be on top of the stack
stack = [1, 2, 0xdeadbeef]

# This section contains input options for the advice provider
[inputs.advice]
# Specify elements to place on the advice stack, leftmost element will be on top
stack = [1, 2, 3, 4]

# The `inputs.advice.map` section is a list of advice map entries that should be
# placed in the advice map before the program is executed. Entries with duplicate
# keys are handled on a last-write-wins basis.
[[inputs.advice.map]]
# The key for this entry in the advice map
digest = '0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63'
# The values to be stored under this key
values = [1, 2, 3, 4]

[[inputs.advice.map]]
digest = '0x20234ee941e53a15886e733cc8e041198c6e90d2a16ea18ce1030e8c3596dd38''
values = [5, 6, 7, 8]

Debugger Usage

Once started, you will be dropped into the main debugger UI, stopped at the first cycle of the program. The UI is organized into pages and panes, with the main/home page being the one you get dropped into when the debugger starts. The home page contains the following panes:

  • Source Code - displays source code for the current instruction, if available, with the relevant line and span highlighted, with syntax highlighting (when available)
  • Disassembly - displays the 5 most recently executed VM instructions, and the current cycle count
  • Stack Trace - displays a stack trace for the current instruction, if the program was compiled with tracing enabled. If frames are unavailable, this pane may be empty.
  • Operand Stack - displays the contents of the operand stack and its current depth
  • Breakpoints - displays the set of current breakpoints, along with how many were hit at the current instruction, when relevant

On the home page, the following keyboard shortcuts are available:

  • q (quit) - exit the debugger
  • h,l (pane movement) - cycle focus to the next pane (h) or previous pane (l)
  • s (step) - advance the VM one cycle
  • n (step next) - advance the VM to the next instruction (i.e. skip over all the cycles of a multi-cycle instructions)
  • c (continue) - advance the VM to the next breakpoint, or until execution terminates
  • e (exit current frame) - advance the VM until we exit the current call frame, or until another breakpoint is triggered, or execution terminates, whichever happens first
  • d (delete) - delete an item (where applicable, for example, the breakpoints pane)
  • : (command prompt) - bring up the command prompt (described further below)

When various panes have focus, additional keyboard shortcuts are available, in any pane with a list of items, or multiple lines (e.g. source code), j and k (or the up and down arrows) will select the next item up and down, respectively. As more features are added, I will document their keyboard shortcuts below.

Commands

From the home page, typing : will bring up the command prompt in the footer pane.

You will know the prompt is active because the keyboard shortcuts normally shown there will no longer appear, and instead you will see the prompt, starting with :. It supports any of the following commands:

  • q or quit (quit) - exit the debugger
  • debug (debug log) - display internal debug log for the debugger itself
  • reload (reload current program) - reloads the program from disk, and resets the UI, with the exception of breakpoints, which are retained across reloads
  • b or break or breakpoint (breakpoints) - manage breakpoints (see Breakpoints)
  • r or read (read memory) - read values from linear memory (see Reading Memory)

Breakpoints

One of the most common things you will want to do with the debugger is set and manage breakpoints. Using the command prompt, you can create breakpoints by typing b (or break or breakpoint), followed by a space, and then the desired breakpoint expression to do any of the following:

  • Break at an instruction which corresponds to a source file (or file and line) whose name/path matches a pattern
  • Break at the first instruction which causes a call frame to be pushed for a procedure whose name matches a pattern
  • Break any time a specific opcode is executed
  • Break at the next instruction
  • Break after N cycles
  • Break at CYCLE

The syntax for each of these can be found below, in the same order (shown using b as the command):

  • b FILE[:LINE] - where FILE is a glob pattern matched against the source file path. The :LINE part is optional, as indicated by the brackets. If specified, only instructions with source locations in FILE and that occur on LINE, will cause a hit.
  • b in NAME - where NAME is a glob pattern matched against the fully-qualified procedure name
  • b for OPCODE - where OPCODE is the exact opcode you want to break on (including immediates)
  • b next
  • b after N
  • b at CYCLE - if CYCLE is in the past, this breakpoint will have no effect

When a breakpoint is hit, it will be highlighted, and the breakpoint window will display the number of hit breakpoints in the lower right.

After a breakpoint is hit, it expires if it is one of the following types:

  • Break after N
  • Break at CYCLE
  • Break next

When a breakpoint expires, it is removed from the breakpoint list on the next cycle.

Read Memory

Another useful diagnostic task is examining the contents of linear memory, to verify that expected data has been written. You can do this via the command prompt, using r (or read), followed by a space, and then the desired memory address and options:

The format for read expressions is :r ADDR [OPTIONS..], where ADDR is a memory address in decimal or hexadecimal format (the latter requires the 0x prefix). The read command supports the following for OPTIONS:

  • -m MODE or -mode MODE, specify a memory addressing mode, either words or bytes (aliases w/b, word/byte, or miden/rust are permitted). This determines whether ADDR is an address in units of words or bytes. (default words)
  • -f FORMAT or -format FORMAT, specify the format used to print integral values (default decimal):
    • d, decimal: print as decimal/base-10
    • x, hex, hexadecimal: print as hexadecimal/base-16
    • b, bin, binary, bits: print as binary/base-2
  • -c N or -count N, specify the number of units to read (default 1)
  • -t TYPE or -type TYPE, specify the type of value to read. In addition to modifying the default for -format, and the unit size for -count, this will also attempt to interpret the memory as a value of the specified type, and notify you if the value is invalid. The default type is word. Available types are listed below:
    • iN and uN: integer of N bits, with the i or u prefix determining its signedness. N must be a power of two.
    • felt: a field element
    • word: a word, i.e. an array of four felt
    • ptr or pointer: a 32-bit memory address (defaults -format hex)
    • In the future, more types will be supported, namely structs/arrays

Any invalid combination of options, or invalid syntax, will display an error in the status bar.

Roadmap

The following are some features planned for the near future:

  • Watchpoints, i.e. cause execution to break when a memory store touches a specific address
  • Conditional breakpoints, i.e. only trigger a breakpoint when an expression attached to it evaluates to true
  • More DYIM-style breakpoints, i.e. when breaking on first hitting a match for a file or procedure, we probably shouldn't continue to break for every instruction to which that breakpoint technically applies. Instead, it would make sense to break and then temporarily disable that breakpoint until something changes that would make breaking again useful. This will rely on the ability to disable breakpoints, not delete them, which we don't yet support.
  • More robust type support in the read command
  • Display procedure locals and their contents in a dedicated pane