474 lines
15 KiB
Markdown
474 lines
15 KiB
Markdown
# 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 one
|
|
page per command into ~/.local/share/man (`MANDIR` overrides), and a test compares each
|
|
page against the binary so the two cannot drift apart.
|
|
|
|
## Synopsis
|
|
|
|
```sh
|
|
gasm [global flags] <command> [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 <file>
|
|
```
|
|
|
|
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 <file>
|
|
```
|
|
|
|
Parse FILE and report syntax errors on stderr. On success, print how many
|
|
declarations and TEXT functions the file contains.
|
|
|
|
```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 <file...>
|
|
```
|
|
|
|
| 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] [-p pkg] [-GOARCH arch] [-o out] <file>
|
|
```
|
|
|
|
| Flag | Default | Effect |
|
|
|---|---|---|
|
|
| `-format` | `raw` | output format: `raw` (concatenated image), `elf` or `goobj` (Go object) |
|
|
| `-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 |
|
|
| `-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.
|
|
|
|
```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] <file>
|
|
```
|
|
|
|
| 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] <file.s>
|
|
```
|
|
|
|
| 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 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. loong64 stays on the ground-truth path
|
|
until hardware validation.
|
|
|
|
```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 <file.s> --func <name>
|
|
```
|
|
|
|
| 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 spawns the debuggee from the `gasm` binary on `$PATH`, so install
|
|
it first with `just install`; `go run` does not work for the traced child.
|
|
Requires Linux (ptrace) and all four architectures are supported.
|
|
|
|
REPL commands:
|
|
|
|
| Command | Effect |
|
|
|---|---|
|
|
| `break <label\|addr> [if <reg> <op> <val>]` | set a breakpoint, optionally conditional |
|
|
| `delete <label\|addr>` | remove a breakpoint |
|
|
| `info break` | 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 <addr> <val...>` | write bytes to memory |
|
|
| `set <reg> <value>` | set a register |
|
|
| `watch <addr> [r\|w] [size]` | set a hardware watchpoint, write by default |
|
|
| `unwatch [<slot>]` | 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] <file1.s> <file2.s>
|
|
```
|
|
|
|
| Flag | Default | Effect |
|
|
|---|---|---|
|
|
| `-GOARCH` | empty | target architecture for both files, overriding the file-name suffixes |
|
|
| `-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 <file.s>
|
|
```
|
|
|
|
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]] [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`), known-but-unencodable
|
|
names (the encoder backlog) and go-only names (feature gaps). The Go side is
|
|
probed black-box with a battery of operand shapes per mnemonic, so the audit
|
|
tracks whatever toolchain `go env GOROOT` provides. On non-amd64
|
|
architectures the backlog is 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.
|
|
|
|
```sh
|
|
gasm audit-instructions amd64
|
|
```
|
|
|
|
```text
|
|
gasm table (amd64, families excluded): 1542 mnemonics
|
|
gasm encodable: 580 go tool asm recognized: 1542
|
|
shared: 580
|
|
```
|
|
|
|
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. 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. 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: 108 (17.2%)
|
|
amd64: 77/464 attempted
|
|
148 instruction not encodable
|
|
e.g. /usr/local/go/src/cmd/asm/internal/asm/testdata/386enc.s
|
|
...
|
|
```
|
|
|
|
## scaffold
|
|
|
|
```text
|
|
Usage: gasm scaffold differential <file.s>
|
|
```
|
|
|
|
Print a differential test skeleton for every `// func` signature in FILE. The
|
|
generated test seeds random states, drives the kernel and a portable reference
|
|
(`<name>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 --script cmds.txt --timeout 30s kernel_amd64.s
|
|
```
|