18 KiB
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
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
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.
gasm tokens hello_amd64.s
1:1 # "#"
1:2 IDENT "include"
1:10 STRING "\"textflag.h\""
parse
Usage: gasm parse <file>
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.
gasm parse hello_amd64.s
hello_amd64.s: OK, 2 declarations, 1 functions
fmt
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.
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:
gasm fmt -d ugly_amd64.s
--- 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
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.
gasm lint kernel_amd64.s
asm
Usage: gasm asm [--format raw|elf|goobj] [-I dir] [-p pkg] [-GOARCH arch] [-GOOS os] [-o out] <file>
| 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.
gasm asm hello_amd64.s
add: 16 bytes
0000: 48 8b 44 24 08 48 03 44 24 10 48 89 44 24 18 c3
dis
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).
gasm dis hello_amd64.s
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
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 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.
gasm verify --ground-truth hello_amd64.s
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
gasm verify --call add --args a=2,b=3 hello_amd64.s
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
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 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 <label|addr|line> [if <reg> <op> <val|reg|*addr>], b |
set a breakpoint; the condition compares a register with a constant, another register or the 8-byte word at *addr |
delete <label|addr>, 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 <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 |
gasm debug --func add --cover hello_amd64.s
diff
Usage: gasm diff [-GOARCH arch] [-I dir] <file1.s> <file2.s>
| 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.
gasm diff hello_amd64.s hello_amd64.s
add: identical (16 bytes)
all functions identical
profile
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.
gasm profile hello_amd64.s
add: 16 bytes, args=24, frame=0 NOSPLIT
basic blocks: 1
audit-instructions
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.
gasm audit-instructions amd64
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.
gasm audit-instructions --corpus
gasm audit-instructions --corpus "$(go env GOROOT)/src/crypto"
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
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.
gasm scaffold differential kernel_amd64.s > kernel_differential_test.go
lsp
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 and the wider workspace on disk: the server indexes the .s files
under the workspace root that the editor has never opened, an open buffer
always shadows its disk copy, and watched-file events together with a
per-query freshness check keep the index current.
version
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:
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:
gasm asm --format goobj -p example.com/kernel -o kernel.o kernel_amd64.s
Find which labels a failing kernel reaches, headlessly:
gasm debug --func decodeBlockAVX2 --cover --timeout 30s kernel_amd64.s