ACID(1)
๐ 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.outby 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 ๐)-llibrary โ load from library at startup; see below, there's a whole section for it ๐ซถ-mmachine โ assume the instructions are for the given CPU type (checkmach(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:
- If the function
acidinit()is defined, it gets invoked after all modules have been loaded. - 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 processlstk()โ print a stack trace with the values of local variablesgpr()โ print general registers. Registers can also be accessed by name, for example*R0spr()โ print special registers such as the program counter and the stack pointerfpr()โ print floating-point registersregs()โ same asspr();gpr();fmt(expr,format)โ expression expr with the format given by the character value of expression formatsrc(address)โ print 10 lines of source around the program addressBsrc(address)โ get the source line for the program address into a window of a runningsam(1)and select it โจline(address)โ print the source line nearest to the program addresssource()โ list current source directoriesaddsrcdir(string)โ add a source directory to the listfilepc(where)โ convert a string of the form sourcefile:linenumber to a machine addresspcfile(address)โ convert a machine address to a source file namepcline(address)โ convert a machine address to a source line numberbptab()โ list breakpoints set in the current processbpset(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 processcont()โ continue execution of the current process and wait for it to stopstep()โ execute a single machine instruction in the current process. (โ ๏ธ Doesn't work on Unix yet.)func()โ step repeatedly until after a function returnstopped(pid)โ this replaceable function is called automatically when the given process stops. It normally prints the program counter and returns to the promptasm(address)โ disassemble 30 machine instructions beginning at the given addressmem(address,string)โ print a block of memory interpreted according to a string of format codesdump(address,n,string)โ likemem(), repeated for n consecutive blocksprint(expr,...)โ print the values of the expressionsnewproc(arguments)โ start a new process with arguments given as a string and halt at the first instructionnew()โ likenewproc(), but take arguments (exceptargv[0]) from the string variableprogargswin()โ likenew(), but run the process in a separate window ๐ชstart(pid)โ start a stopped processkill(pid)โ kill the given process ๐setproc(pid)โ make the given process currentrc(string)โ escape to the shell,rc(1), to execute the command stringinclude(string)โ read acid commands from the named fileincludepipe(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 settingprogargs), then stopping the process, and then runningtrump()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 ๐
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, andtrussdo not work either. yeah they're all caught in the crossfire ๐ newin 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 ๐ธ๐๐ฆ
๐ฆ where this page comes from
- alpine 3.24: plan9port-doc 0_git20260208-r0 ยท LPL-1.02
- arch rolling: plan9port 20261021-2 ยท custom
- nixos 26.05: plan9port 0-unstable-2025-11-10
- nixos 26.05: plan9port-wayland 0-unstable-2025-11-26
- nixpkgs unstable: plan9port 0-unstable-2025-11-10
- nixpkgs unstable: plan9port-wayland 0-unstable-2025-11-26
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms