ACID(1)

๐Ÿ“ฆ version 0_git20260208, in alpine 3.24, arch rolling, nixos 26.05, nixpkgs unstable

๐Ÿ’… ACID(1) โ€” like, the debugger page, bestie

okay so real talk: this page is for acid and acidtypes, and it's literally the debugger page. buckle up ๐ŸŽ€


โœจ NAME โœจ

acid, acidtypes โ€” debugger ๐Ÿ’€ (yes that's the whole vibe, it debugs things)


๐ŸŒธ SYNOPSIS ๐ŸŒธ

acid [ -l library ] [ -wq ] [ -m machine ] [ pid | core ] [ textfile ]

acidtypes [ -p prefix ] file ...

๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

So Acid is a programmable symbolic debugger โ€” like, you can script it, which is honestly iconic. It can peek inside one or more processes that share an address space (plural, bestie, it does the most ๐Ÿ’…).

You tell it what to debug in one of two ways, no cap:

  • the process id of a running or defunct process, or
  • the name of the program's text file (a.out by default, obviously).

Once you're at the prompt, acid will happily store function definitions for you, or print the value of expressions. Main character energy โญ

The options are these ones:

๐Ÿงš OPTIONS ๐Ÿงš

  • -w โ€” let the textfile be modified. yeah. it's giving chaotic, use with intent ๐Ÿ™ˆ
  • -q โ€” print the variable renamings at startup. (more on why those happen in a sec ๐Ÿ‘€)
  • -l library โ€” load from library at startup; see below, there's a whole section for it ๐Ÿซถ
  • -m machine โ€” assume the instructions are for the given CPU type (check mach(3)) instead of, like, reading the executable header to figure out the CPU type on its own
  • -k โ€” debug the kernel state for the process instead of the user state ๐Ÿ”ฅ

๐ŸŽ€ At startup (the lore) ๐ŸŽ€

When it boots up, acid grabs its standard function definitions from the library file /acid/port, then the architecture-dependent functions from /acid/$objtype, then any user-specified functions from $HOME/lib/acid, and then even more functions from whatever -l files you gave it.

Definitions in any of those files can override functions that were defined earlier โ€” yes, it's a whole inheritance drama and everyone's invited ๐Ÿ’…

Then:

  1. If the function acidinit() is defined, it gets invoked after all modules have been loaded.
  2. After that, the function acidmap() gets invoked if it's defined.

And plot twist: /acid/port provides a definition of acidmap that attaches all the shared libraries the target process is using, and then runs acidtypes (q.v.) to create acid functions for examining data structures. slay ๐Ÿ’–


๐ŸŒˆ Language ๐ŸŒˆ

Symbols of the program you're debugging become integer variables whose values are addresses. Contents of addresses? You get those by indirection, periodt.

Local variables are qualified by their function name โ€” for example, main:argv โ€” so everyone knows whose variable it is, iykyk.

When program symbols conflict with acid's own words, acid prefixes distinguishing $ signs. Those renamings get reported at startup, and the -q option suppresses them. (That's the whole -q thing from up top, you're welcome ๐Ÿซถ)

Variable types (integer, float, list, string) and formats are inferred from assignments. No declarations, she just knows, it's giving psychic.

Truth values false/true are attributed to:

  • zero / nonzero integers or floats, and
  • empty / nonempty lists or strings

Lists are sequences of expressions surrounded by {} and separated by commas. cute.

Expressions are much as in C, but they yield both a value and a format. Casts to complex types are allowed. And lists admit the following operators, with subscripts counted from 0 (zero-indexed besties ๐ŸŽ€):

head list
tail list
append list, element
delete list, subscript

Format codes are the same as in db(1). Formats can be attached to (unary) expressions with \, e.g. (32*7)\D.

There are two indirection operators:

  • * โ€” to address a core image
  • @ โ€” to address a text file

The type and format of the result are determined by the format of the operand, whose type must be integer. no exceptions, the operand's gotta be an integer fr fr.


๐Ÿ‘‘ Statements ๐Ÿ‘‘

if expr then statement [ else statement ]
while expr do statement
loop expr, expr do statement
defn name(args) { statement }
defn name
name(args)
builtin name(args)
local name
return expr
whatis [  name ]

Okay important detail: the statement defn name clears the definition for name.

A defn may override a built-in function โ€” but if you prefix a function call with builtin, it ignores any overriding defn, forcing the use of the built-in function. so petty, so powerful ๐Ÿ’…


๐Ÿ“ Functions (partial list) ๐Ÿ“

Here's a partial list of functions; see the manual for the complete list, bestie, there are more:

  • stk() โ€” print a stack trace for the current process
  • lstk() โ€” print a stack trace with the values of local variables
  • gpr() โ€” print general registers. Registers can also be accessed by name, for example *R0
  • spr() โ€” print special registers such as the program counter and the stack pointer
  • fpr() โ€” print floating-point registers
  • regs() โ€” same as spr();gpr();
  • fmt(expr,format) โ€” expression expr with the format given by the character value of expression format
  • src(address) โ€” print 10 lines of source around the program address
  • Bsrc(address) โ€” get the source line for the program address into a window of a running sam(1) and select it โœจ
  • line(address) โ€” print the source line nearest to the program address
  • source() โ€” list current source directories
  • addsrcdir(string) โ€” add a source directory to the list
  • filepc(where) โ€” convert a string of the form sourcefile:linenumber to a machine address
  • pcfile(address) โ€” convert a machine address to a source file name
  • pcline(address) โ€” convert a machine address to a source line number
  • bptab() โ€” list breakpoints set in the current process
  • bpset(address) โ€” set a breakpoint in the current process at the given address. (โš ๏ธ Doesn't work on Unix yet.)
  • bpdel(address) โ€” delete a breakpoint from the current process
  • cont() โ€” continue execution of the current process and wait for it to stop
  • step() โ€” execute a single machine instruction in the current process. (โš ๏ธ Doesn't work on Unix yet.)
  • func() โ€” step repeatedly until after a function return
  • stopped(pid) โ€” this replaceable function is called automatically when the given process stops. It normally prints the program counter and returns to the prompt
  • asm(address) โ€” disassemble 30 machine instructions beginning at the given address
  • mem(address,string) โ€” print a block of memory interpreted according to a string of format codes
  • dump(address,n,string) โ€” like mem(), repeated for n consecutive blocks
  • print(expr,...) โ€” print the values of the expressions
  • newproc(arguments) โ€” start a new process with arguments given as a string and halt at the first instruction
  • new() โ€” like newproc(), but take arguments (except argv[0]) from the string variable progargs
  • win() โ€” like new(), but run the process in a separate window ๐ŸชŸ
  • start(pid) โ€” start a stopped process
  • kill(pid) โ€” kill the given process ๐Ÿ’€
  • setproc(pid) โ€” make the given process current
  • rc(string) โ€” escape to the shell, rc(1), to execute the command string
  • include(string) โ€” read acid commands from the named file
  • includepipe(string) โ€” run the command string, reading its standard output as acid commands

๐Ÿงต Shared library segments ๐Ÿงต

When you give a pid or a core file on the command line, acid will โ€” as part of its startup โ€” figure out the set of shared libraries in use by the process image, and map those at appropriate locations. she just does it, no notes ๐Ÿซถ

But if acid is started without a pid or core file and then gets subsequently attached to a process via setproc, the shared library maps can be initialized by calling dynamicmap(). good to know, fr fr.


๐Ÿ” Type information ๐Ÿ”

Unix compilers conventionally include detailed type information in the debugging symbol section of binaries. The external program acidtypes extracts this info and formats it as acid program text.

Once the shared libraries have been mapped, the default acid startup invokes acidtypes (via includepipe) on the set of currently mapped text files. And if you change the set of mapped text files, you can call the function acidtypes() to rerun the command. easy ๐Ÿ’…


๐Ÿ“š Acid Libraries ๐Ÿ“š

There are a number of acid libraries that provide higher-level debugging facilities. One notable example is trump, which uses acid to trace memory allocation. ๐Ÿ‘‘

Trump requires starting acid on the program, either by:

  • attaching to a running process, or
  • executing new() on a binary (perhaps after setting progargs), then stopping the process, and then running trump() to execute the program under the scaffolding

The output will be a trace of the memory allocation and free calls executed by the program. And when you're finished tracing: stop the process, execute untrump(), and then cont() to resume execution. periodt ๐ŸŽ€


๐Ÿ”ฅ EXAMPLES ๐Ÿ”ฅ

Example 1: debug /bin/ls

Start to debug /bin/ls; set some breakpoints; run up to the first one. (โš ๏ธ this example doesn't work on Unix yet):

% acid /bin/ls
/bin/ls: mips plan 9 executable
/sys/lib/acid/port
/sys/lib/acid/mips
acid: new()
70094: system call  _main     ADD  $-0x14,R29
70094: breakpoint   main+0x4  MOVW R31,0x0(R29)
acid: pid
70094
acid: argv0 = **main:argv\s
acid: whatis argv0
integer variable format s
acid: *argv0
/bin/ls
acid: bpset(ls)
acid: cont()
70094: breakpoint  ls    ADD  $-0x16c8,R29
acid:

Example 2: a linked list of structures ๐Ÿ’œ

Display elements of a linked list of structures:

complex Str { 'D' 0 val; 'X' 4 next; };
s = *headstr;
while s != 0 do{
     complex Str s;
     print(s.val, "\n");
     s = s.next;
}

Note the use of the . operator instead of ->. (yes really, that's the note ๐Ÿซถ)

Example 3: an array of bytes ๐ŸŒธ

Display an array of bytes declared in C as char array[].

*(array\s)

This example gives array string format, then prints the string beginning at the address (in acid notation) *array.

Example 4: truss ๐Ÿฆ‹

Trace the system calls executed by ls(1) (โš ๏ธ neither does this one):

% acid -l truss /bin/ls
/bin/ls:386 plan 9 executable

/sys/lib/acid/port
/sys/lib/acid/kernel
/sys/lib/acid/truss
/sys/lib/acid/386
acid: progargs = "-l lib/profile"
acid: new()
acid: truss()
open("#c/pid", 0)
     return value: 3
pread(3, 0x7fffeeac, 20, -1)
     return value: 12
     data: "        166 "
...
stat("lib/profile", 0x0000f8cc, 113)
     return value: 65
open("/env/timezone", 0)
     return value: 3
pread(3, 0x7fffd7c4, 1680, -1)
     return value: 1518
     data: "EST -18000 EDT -14400
   9943200   25664400   41392800   57718800   73447200   89168400
 104896800  ..."
close(3)
     return value: 0
pwrite(1, "--rw-rw-r-- M 9 rob rob 2519 Mar 22 10:29 lib/profile
", 54, -1)
--rw-rw-r-- M 9 rob rob 2519 Mar 22 10:29 lib/profile
     return value: 54
...
166: breakpoint     _exits+0x5     INTB $0x40
acid: cont()

๐ŸŽ€ FILES ๐ŸŽ€

/acid/$objtype
/acid/port
/acid/kernel
/acid/trump
/acid/truss
$HOME/lib/acid

โญ SOURCE โญ

/src/cmd/acid

๐ŸŒˆ SEE ALSO ๐ŸŒˆ

mk(1), db(1)

Phil Winterbottom, ``Acid Manual''.


๐Ÿ’€ DIAGNOSTICS ๐Ÿ’€

At termination, kill commands are proposed for processes that are still active. like, she's not gonna do it behind your back, she's gonna tell you ๐Ÿ‘€

๐Ÿ’€ BUGS ๐Ÿ’€

okay bestie, listen up โ€” even icons have their off days, and ACID is very much an icon, but there are some things it literally cannot do yet. Not trying to be dramatic, just keeping it real with you ๐Ÿซถ

Here's the full receipts, no cap:

  • There is no way to redirect the standard input and standard output of a new process. ๐Ÿ˜ญ like... none. zero. the vibes are not immaculate here.
  • Source line selection near the beginning of a file may pick an adjacent file. yeah... it's giving "close enough" energy and honestly that's a little sketchy, so watch out near the top of files ๐Ÿ’€
  • With the extant stepping commands, one cannot step through instructions outside the text segment and it is hard to debug across process forks. so like, stepping around outside the text segment? not happening. and debugging across process forks? it's a struggle, fr fr ๐Ÿ˜ฎโ€๐Ÿ’จ
  • Breakpoints do not work yet. i know, i know โ€” breakpoints!! the main character of debugging!! and they do not work yet ๐Ÿ™ˆ which means...
  • Therefore, commands such as step, new, and truss do not work either. yeah they're all caught in the crossfire ๐ŸŽ€
  • new in particular will need some help to cope with dynamic libraries. like, it's gonna need a little support system to deal with dynamic libraries. we believe in you, new ๐Ÿ’œ
no lies detected in this section โ€” these are real limitations, not a vibe check you can just skip. go in knowing them ๐Ÿ’…

and that's a wrap on ACID(1), bestie!! this footer is literally just ACID(1) hanging out at the bottom โ€” no fancy version drama, just the page name doing its little signature pose โœจ take care of yourself, hydrate, and may your breakpoints one day actually work ๐ŸŒธ๐Ÿ’–๐Ÿฆ‹