Files
gasm-sdk/docs/CLI.md
T
2026-09-23 20:21:58 +02:00

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