cbza.org Casa Budista Zapatista Anarquista

An Informal Introduction to LSD

LSD is the LISP System Debugger, a new programmable debugger for 9front written as a library for the StreetLISP (aka sl) programming language.

Installation

LSD is currently included in the StreetLISP repository and can be installed on 9front as follows:

% git/clone https://git.sr.ht/~ft/sl
% cd sl
% mk install lsd

then start lsd by passing it an executable or a pid:

% lsd /bin/cat
/bin/cat:amd64 plan 9 executable

or

% lsd 1234
/proc/1234/text:amd64 plan 9 executable

This will load all the lsd functions and launch the StreetLISP REPL. You are now in the LISP environment and your debugging session will technically be the execution of a StreetLISP program.

Basic Usage

Unless you started lsd with a pid, you will need to create a new process:

#;> (new)
1081

You can also call new with arguments to the executable:

#;> (new "src/plan9/lsd.sl")
1097

This will start the process, halting just inside the execution of main, as the stk or lstk functions will show:

#;> (stk)
main(argc=0x0, argv=0x7fffffffef80)+0x4 /sys/src/cmd/cat.c:18
_callmain+0x38 /sys/src/libc/9sys/callmain.c:21

The output of these functions is the same as acid(1).

You can set and delete breakpoints with bpset and bpdel. These functions are more flexible than their Acid counterparts. They can take as input

  1. A string that refers to a symbol (such as a function) in the process's symbol table,
  2. A string of the form "file:line" used to find a program address corresponding to given line of source code.

thus:

#;> (bpset "cat"); set a break at the function "cat"
T
#;> (bpset "/sys/src/cmd/cat.c:24")
T

then continue execution with cont

#;> (cont)
sys: breakpoint
0x200028

You can also step with the following functions:

  1. (step): advance one line of assembly.
  2. (line): advance one line of source code.
  3. (over): advance one line of source code, stepping over function calls.
  4. (func): advance until the current function returns.

You can also see where you are in the source code with Bsrc which will plumb the current file and line to your editor:

#;> (Bsrc)

and to combine stepping forward and plumbing use B:

#;> (B cont)
#;> (B step)

etc.

I will point out that all these functions are documented within LSD itself. So for more information, use the included help function:

#;> (help cont)
(cont (:print T))

Continue program execution.

Return the next instruction address to be executed or `void` if the
program has exited.  Optionally print any notes that may have caused
the program to stop.
#;> (help step)
(step (n 1))

Step `n` assembly instructions.  Return the next instruction address
to be executed or `NIL` if the program has exited.

You can see all documented LSD functions by checking the help for the LSD documentation group:

#;> (help lsd group)
lsd (group)

Debugging functionality.

Members:

    (@ loc)
    (B stepper)
...

Data Access

There are lots of little functions for accessing data in C programs, essentially one for each basic data type, all of which are prefixed with c-. So to access an integer named i, you can just call (c-int "i"). This will do a lookup first in the local stack frame and then in global data symbols.

For dereferencing, use c-ptr and this can be combined to access values pointed at or even arrays of ints.

(c-int (c-ptr "intp")) ; read the integer value referenced by the pointer.
(c-int "ints" 3) ; read an array of three integers.
(c-double (c-ptr "doublep") 3) ; read three doubles starting from a pointer location.

The previous examples assume the following C data types: int *intp, int ints[3] and double *doublep which points to an array of at least three doubles.

The function c-str can be used to read a null-terminated string: given a C type char *s,

(c-str "s")

will read the string value. To read the argument vector to the program:

(c-str (c-ptr "argv") (c-int "argc"))

Watchpoints

There is rudimentary support for watchpoints on data, only for 386/amd64 architectures. Setting a watchpoint with

(wpset "somedata" 4)

will watch for reads or writes on the four bytes starting at "somedata". The symbol lookup is the same as for the data accessors above, first local stack frame then global symbols.

What's Next?

I plan to complete the following more difficult things for LSD next, not necessarily in this order.

  1. Improve watchpoint support.
  2. Reading arbitrary C structs.
  3. Writing data back to values and structs in C.
  4. Supporting multiple threads/procs.
  5. System calls and memory allocation tracing.
  6. Acid-like REPL.

Any other ideas you have or changes you think make sense, please let me know at spew at cbza dot org or on IRC.