# Command line The reference below is taken from the program's own `--help`. If the two disagree, the program is right and this file is a defect. The same reference is installed as man pages: `just install-man` puts gasm(1) and a page for every command except `version` (which gasm(1) itself documents) into ~/.local/share/man (`MANDIR` overrides). A test in `cmd/gasm` keeps the two from drifting: it compares each page's flag set and SYNOPSIS line with the binary's own `-h` output, and gasm(1)'s COMMANDS list with the top-level help. The prose is not compared. ## Synopsis ```sh gasm [global flags] [command flags] [arguments] ``` ## Commands | Command | Purpose | |---|---| | `tokens` | print the lexical token stream | | `parse` | parse a file and report syntax errors | | `fmt` | canonicalise the formatting of `.s` files | | `lint` | run the static checks | | `asm` | assemble `.s` files to machine code | | `dis` | disassemble machine code or an assembled file | | `verify` | JIT-assemble and run the dynamic checks | | `debug` | interactive source-level debugger | | `diff` | compare the machine code of two `.s` files | | `profile` | show the basic-block structure of the functions | | `audit-instructions` | diff the encoder against the toolchain's name table | | `scaffold` | generate a differential test skeleton for a kernel | | `lsp` | run the language server over stdio | | `version` | print the version | ## tokens ```text Usage: gasm tokens ``` Print the lexical token stream of FILE: position, token kind and text, one token per line. FILE may be `-` to read standard input. ```sh gasm tokens hello_amd64.s ``` ```text 1:1 # "#" 1:2 IDENT "include" 1:10 STRING "\"textflag.h\"" ``` ## parse ```text Usage: gasm parse ``` Parse FILE and report syntax errors on stderr. On success, print how many declarations and TEXT functions the file contains. FILE may be `-` to read standard input. ```sh gasm parse hello_amd64.s ``` ```text hello_amd64.s: OK, 2 declarations, 1 functions ``` ## fmt ```text Usage: gasm fmt [-w|-l|-d] [path...] ``` | Flag | Default | Effect | |---|---|---| | `-w` | off | write the result back to the source file | | `-l` | off | list the files whose formatting differs; write nothing | | `-d` | off | print a unified diff of the canonical formatting instead | `-l` and `-d` are mutually exclusive. With no arguments, or with a directory argument, every `.s` file below it is reformatted in place and the names of the changed files are listed, the way `go fmt` does; `.` and `_` directories are skipped. Explicit file arguments print to stdout unless `-w` is given. ```sh gasm fmt -l kernel_amd64.s ``` Empty output means every file is formatted, which is the shape a CI check wants; `-d` shows what would change: ```sh gasm fmt -d ugly_amd64.s ``` ```text --- ugly_amd64.s +++ ugly_amd64.s @@ -2,8 +2,8 @@ // func add(a, b int) int TEXT ·add(SB), NOSPLIT, $0-24 - MOVQ a+0(FP), AX - ADDQ b+8(FP), AX + MOVQ a+0(FP), AX + ADDQ b+8(FP), AX ``` ## lint ```text Usage: gasm lint ``` | Flag | Default | Effect | |---|---|---| | `-disable` | empty | comma-separated rule codes to disable | Diagnostics are printed as `file:line:col: severity: message [code]`. The exit status is non-zero when an error-severity diagnostic is found; warnings (the register-clobber audit, for example) do not affect it. Rules: `unknown-instruction`, `operand-count`, `undefined-label`, `duplicate-label`, `missing-ret`, `missing-textflag-include`, `abi-argsize`, `unreachable-code`, `register-clobber`, `funcdata-pcdata`, `unused-label`, `invalid-textflag`, `stack-imbalance`, `register-width-mismatch`, `abi0-register-args`, `nonportable-register-name`, `unencodable-instruction` and `reserved-register-write`. ```sh gasm lint kernel_amd64.s ``` ## asm ```text Usage: gasm asm [--format raw|elf|goobj] [-I dir] [-p pkg] [-GOARCH arch] [-GOOS os] [-o out] ``` | Flag | Default | Effect | |---|---|---| | `-format` | `raw` | output format: `raw` (concatenated image), `elf` or `goobj` (Go object) | | `-I` | empty | directory to search for `#include` files; may be repeated, searched in order after the source directory | | `-p` | empty | package path for `--format goobj`, qualifying the exported symbols | | `-GOARCH` | empty | target architecture: `amd64`, `arm64`, `riscv64` or `loong64`; overrides the file-name suffix | | `-GOOS` | empty | operating system for the generated `go_asm.h`: any GOOS `go/build` recognises in file names; default is the host's | | `-o` | empty | write the output to this file instead of a hex dump on stdout | Supported architectures: amd64 (VEX/AVX2 and EVEX/AVX-512 included), arm64, riscv64 (RV64IMAFDC and RVC) and loong64, taken from the file's `_arch.s` suffix or from `-GOARCH`, which is how files whose names carry no recognisable suffix (most of GOROOT's, for example `cpu_x86.s`) are assembled. `raw` concatenates the functions and the data section into one self-consistent image; `elf` emits a relocatable object that links with the system toolchain; `goobj` emits the Go toolchain's own object format, which `cmd/link` consumes directly, and is the one format that needs the toolchain installed: the object preamble is captured from `go tool asm` and the format version from `go version`. `raw` and `elf` need no toolchain at all. A file that includes `go_asm.h` gets that header generated from the Go files beside it, type-checked for the target. `-GOOS` selects the type-checking GOOS for that header, because a GOOS-specific file needs its platform's defines: `sys_darwin_arm64.s` fails against the ambient GOOS (`machTimebaseInfo_numer` is missing from a linux type-check) and assembles with `-GOOS darwin`. Assembly preprocessing matches the toolchain's: `#define` macros (object and parameterised) expand at the point of use, `#undef`, `#ifdef`, `#ifndef`, `#else` and `#endif` behave as in `go tool asm`, `;` separates statements, and `#include "file"` splices the named file in, resolved against the source directory and then each `-I` directory in order. `textflag.h` is the one header that is not spliced: gasm consumes its flag names natively. ```sh gasm asm hello_amd64.s ``` ```text add: 16 bytes 0000: 48 8b 44 24 08 48 03 44 24 10 48 89 44 24 18 c3 ``` ## dis ```text Usage: gasm dis [-a arch] ``` | Flag | Default | Effect | |---|---|---| | `-a` | empty | architecture for raw input without a `_arch.s` name | With a `.s` file the file is assembled first and the listing follows the real layout: one block per `TEXT` function, local labels printed at their offsets. With any other file, or `-` for standard input, the bytes are disassembled linearly and `-a` selects the architecture (amd64, arm64, riscv64 or loong64). ```sh gasm dis hello_amd64.s ``` ```text add: 16 bytes 0000: 48 8b 44 24 08 mov rax, qword ptr [rsp+0x8] 0005: 48 03 44 24 10 add rax, qword ptr [rsp+0x10] 000a: 48 89 44 24 18 mov qword ptr [rsp+0x18], rax 000f: c3 ret ``` ## verify ```text Usage: gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] ``` | Flag | Default | Effect | |---|---|---| | `--ground-truth` | off | compare the machine code byte-for-byte against `go tool asm` | | `--fuzz` | off | differential fuzz against the `go tool asm` build | | `-n` | 1000 | fuzz iterations per function | | `--abi` | off | ABI-checking calls: sentinel registers and a red-zone canary | | `--abi-n` | 100 | ABI check iterations with varied inputs | | `--profile` | off | list the basic-block structure per function | | `--smoke` | off | call each NOSPLIT function with zeroed arguments | | `--call` | empty | invoke a single NOSPLIT function with `--buf` instead of the sweeps | | `--buf` | empty | buffer spec for `--call`: `name:size:pattern[,name:size:pattern]` | | `--args` | empty | scalar args for `--call`: `name=value[,name=value]` (decimal or `0x` hex) | | `--repeat` | 1 | number of times to repeat a `--call` invocation | | `--save-corpus` | empty | with `--fuzz`: write each failing input to this directory as replayable JSON | | `--replay` | empty | re-run saved corpus entries, one child process per entry | The JIT checks run when the host matches the file's architecture; the toolchain comparison works everywhere. `--fuzz`, `--smoke` and `--abi` run each function in its own child process, so a partial function that faults on random input is reported as `CRASH` instead of ending the sweep; `--call` with `--buf` invokes such a function with valid data. The function named by `--call` must be NOSPLIT: a function with a stack frame is refused with a diagnostic and exits 1. ```sh gasm verify --ground-truth hello_amd64.s ``` ```text hello_amd64.s: 1 functions JIT-loaded add: MATCH (16 bytes) ground truth: 1/1 functions byte-identical add: 16 bytes, args=24, frame=0 NOSPLIT ``` ```sh gasm verify --call add --args a=2,b=3 hello_amd64.s ``` ```text add: 16 bytes, args=24 signature: func add(a int, b int) int scalars: a = 2 b = 3 args before: 02 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (24 bytes) args after: 02 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 (24 bytes) call 1: OK ``` ## debug ```text Usage: gasm debug --func ``` | Flag | Default | Effect | |---|---|---| | `-func` | empty | the function to debug, required | | `-buf` | empty | buffer spec: `name:size:pattern[,name:size:pattern]` (zero, ones, seq or hex) | | `-args` | empty | file containing the ABI0 argument block | | `-script` | empty | run REPL commands from a file, one per line, and exit; `-` reads stdin | | `-timeout` | 0 | kill the debuggee after this duration, for headless `-script` runs; a timeout exits 3 | | `-cover` | off | run to completion with a breakpoint on every instruction and report which executed | The debugger re-executes the binary it is running as (`os.Executable()`) for the traced child, so the child is the same `gasm`, whether it is installed on `$PATH` or run with `go run ./cmd/gasm`; nothing has to be installed first. Requires Linux (ptrace), and all four architectures are supported. REPL commands: | Command | Effect | |---|---| | `break [if ]`, `b` | set a breakpoint; the condition compares a register with a constant, another register or the 8-byte word at `*addr` | | `delete `, `d` | remove a breakpoint | | `info break`, `info breakpoints`, `info b` | list the breakpoints | | `step [n]`, `s` | single-step n instructions | | `next`, `n` | step over a CALL | | `finish`, `fin` | run until the function returns | | `continue`, `c` | run until a breakpoint, watchpoint or exit | | `disas [n]`, `u` | disassemble n instructions at the PC | | `regs` | print the general-purpose and vector/FP registers | | `where` | show the source line and the nearest label at the PC | | `stack` | show the stack near RSP, the return address and the ABI0 args | | `bt`, `backtrace` | backtrace: the current frame and the return address | | `x [addr] [len]` | hex-dump memory | | `w ` | write bytes to memory | | `set ` | set a register | | `watch [r\|w] [size]` | set a hardware watchpoint, write by default | | `unwatch []` | clear one watchpoint or all of them | | `labels`, `l` | list the function's labels and offsets | | `help`, `h`, `?` | show the command help | | `quit`, `q` | kill the debuggee and exit | ```sh gasm debug --func add --cover hello_amd64.s ``` ## diff ```text Usage: gasm diff [-GOARCH arch] [-I dir] ``` | Flag | Default | Effect | |---|---|---| | `-GOARCH` | empty | target architecture for both files, overriding the file-name suffixes | | `-I` | empty | directory to search for `#include` files; may be repeated, searched in order after the source directory | | `-map` | empty | comma-separated `old=new` pairs to match functions with different names | Functions are paired by exact name unless `--map` says otherwise, so `--map wideCopyAVX2=wideCopyAVX512` pairs two variants regardless of suffix. The exit status is non-zero when anything differs. ```sh gasm diff hello_amd64.s hello_amd64.s ``` ```text add: identical (16 bytes) all functions identical ``` ## profile ```text Usage: gasm profile ``` Show the basic-block structure of each function: its labels, their offsets and the block boundaries. This is the static structure; for runtime execution counts use `gasm debug --cover`, and for input coverage `gasm verify --fuzz`. ```sh gasm profile hello_amd64.s ``` ```text add: 16 bytes, args=24, frame=0 NOSPLIT basic blocks: 1 ``` ## audit-instructions ```text Usage: gasm audit-instructions [--corpus [dir]] [--list] [-I dir] [amd64|arm64|riscv64|loong64] ``` Compare the gasm encoder for the given architecture (default amd64) against the installed `go tool asm` and print the diff: superset encodings (gasm-only spellings, shippable via `gasm asm --format goobj`) and known-but-unencodable names (the encoder backlog). The Go side is probed black-box one bare mnemonic at a time, classified by the toolchain's diagnostic for an instruction it does not know, so the audit tracks whatever toolchain `go env GOROOT` provides; the gasm side answers from the encoder table on amd64 and from trial assembly over a battery of operand shapes on the other architectures. On non-amd64 architectures the backlog is therefore an over-approximation: a name counts as encodable only when a probe shape assembles cleanly, so a name whose real forms the battery misses lands in the backlog. Names the toolchain knows and gasm does not cannot be enumerated by probing at all, because Go's table is visible only through names already in the gasm table; the report closes with a note saying so rather than listing them. ```sh gasm audit-instructions amd64 ``` ```text gasm table (amd64, families excluded): 1542 mnemonics gasm encodable: 587 go tool asm recognised: 1542 shared: 587 ... ``` With `--corpus` the audit changes shape: it assembles every `.s` file under DIR (default `GOROOT/src`) with the gasm encoder only, no toolchain probing. A file whose name carries a recognisable `_arch` suffix is attempted for that architecture; a file without one is attempted for all four, exactly as a `GOARCH` build would compile it, and a name that names a GOOS (`sys_darwin_arm64.s`) type-checks its generated `go_asm.h` for that GOOS. The report gives the headline number (files that assemble for every target architecture), the per-architecture pass rates and the most common failure reasons with one representative file each, which drive the encodability backlog by frequency rather than by table order. With `--list` the report additionally prints every failing file with its failure reason, per architecture. A run over GOROOT takes under a second. ```sh gasm audit-instructions --corpus gasm audit-instructions --corpus "$(go env GOROOT)/src/crypto" ``` ```text corpus /usr/local/go/src: 627 files (365 generic, attempted for all architectures) assemble for every target architecture: 127 (20.3%) amd64: 82/464 attempted 165 unsupported operand form e.g. /usr/local/go/src/cmd/asm/internal/asm/testdata/386enc.s 109 instruction not encodable e.g. /usr/local/go/src/cmd/asm/internal/asm/testdata/386.s ... ``` ## scaffold ```text Usage: gasm scaffold differential ``` Print a differential test skeleton for every `// func` signature in FILE. The generated test seeds random states, drives the kernel and a portable reference (`Portable`), and compares the outputs byte-for-byte. Write the reference bodies, place the file in the kernel's package, and run it in CI. ```sh gasm scaffold differential kernel_amd64.s > kernel_differential_test.go ``` ## lsp ```text Usage: gasm lsp ``` Run the language server over standard input/output, JSON-RPC 2.0 with `Content-Length` framing. Point an LSP-capable editor at the binary and associate it with `.s` files; the target architecture is inferred from the file-name suffix (`_amd64.s`, `_arm64.s`, `_riscv64.s`, `_loong64.s`). Provides: completion, hover, document symbols, push and pull diagnostics, semantic tokens, go-to-definition, find references, rename, document formatting, inlay hints, code actions, signature help, document highlights, workspace symbol search, #include document links, and folding ranges for function bodies. Definition, references and rename work across every open document. ## version ```text Usage: gasm version ``` Print the version the toolchain recorded for the build, the same string as `gasm --version`: the tag on a tagged checkout, a pseudo-version naming the commit below one, with `+dirty` appended on a dirty tree and `(devel)` outside version control. ## Global flags | Flag | Default | Effect | |---|---|---| | `-h`, `--help` | off | print the usage | | `-V`, `--version` | off | print the version | ## Exit codes | Code | Meaning | |---|---| | `0` | success | | `1` | a failure the program detected: a parse or assembly error, an error-severity lint diagnostic, a mismatch in `verify`, a file that cannot be read | | `2` | the arguments were wrong: a missing or extra argument, an unknown command or format, an invalid `--map` pair | | `3` | `debug --timeout` killed the debuggee | ## Examples Assemble a kernel, check it, and run it: ```sh gasm lint kernel_amd64.s gasm fmt -l kernel_amd64.s gasm asm -o kernel.bin kernel_amd64.s gasm verify --ground-truth kernel_amd64.s ``` Link the kernel into a Go program through the toolchain's own object format: ```sh gasm asm --format goobj -p example.com/kernel -o kernel.o kernel_amd64.s ``` Find which labels a failing kernel reaches, headlessly: ```sh gasm debug --func decodeBlockAVX2 --cover --timeout 30s kernel_amd64.s ```