feat(docs): man pages for gasm and every command, guarded against CLI drift
Test / test (push) Successful in 2m4s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-19 21:18:43 +02:00
parent 708d0a0a5e
commit 93c47a312a
19 changed files with 956 additions and 1 deletions
+7
View File
@@ -39,6 +39,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Oracle parity as its own CI step.** The push pipeline already ran
the live go-tool-asm comparison inside the suite; a dedicated step
now names that gate when it fails.
- **Man pages.** docs/man carries gasm(1) and one page per command,
written in roff: synopsis, description, every flag with its default,
exit status, worked examples and cross-references.
`just install-man` compresses them into ~/.local/share/man (MANDIR
overrides) and `just uninstall-man` removes them. A test builds the
binary and compares every command's `-h` output with its page, so the
pages cannot drift from the CLI.
### Changed
+4 -1
View File
@@ -241,8 +241,11 @@ recipe.
## Documentation
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow
- [docs/CLI.md](docs/CLI.md): full command reference
- man pages: `just install-man` installs gasm(1) and one page per command
into ~/.local/share/man (MANDIR overrides); `just uninstall-man` removes
them
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): development setup and recipes
- [CHANGELOG.md](CHANGELOG.md): release history
+157
View File
@@ -0,0 +1,157 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
)
// TestManPagesTrackTheCLI builds the binary once, then compares every
// command's live `-h` output with its docs/man/gasm-<command>.1 page: the
// flag sets must agree both ways, and the page's SYNOPSIS line must carry
// the command's usage line. A flag or a usage change that skips the man
// page fails here, so the pages cannot drift from the binary.
func TestManPagesTrackTheCLI(t *testing.T) {
if testing.Short() {
t.Skip("builds the gasm binary")
}
bin := filepath.Join(t.TempDir(), "gasm")
if out, err := exec.Command("go", "build", "-o", bin, ".").CombinedOutput(); err != nil {
t.Fatalf("build gasm: %v\n%s", err, out)
}
for _, cmd := range []string{
"tokens", "parse", "fmt", "lint", "asm", "dis", "verify",
"debug", "diff", "profile", "audit-instructions", "scaffold", "lsp",
} {
t.Run(cmd, func(t *testing.T) {
raw, err := os.ReadFile(filepath.Join("..", "..", "docs", "man", "gasm-"+cmd+".1"))
if err != nil {
t.Fatalf("read man page: %v", err)
}
page := string(raw)
out, _ := exec.Command(bin, cmd, "-h").CombinedOutput()
help := string(out)
binFlags := helpFlags(help)
pageFlags := roffFlags(page)
for f := range binFlags {
if !pageFlags[f] {
t.Errorf("flag -%s is in the binary's help but missing from the man page", f)
}
}
for f := range pageFlags {
if !binFlags[f] {
t.Errorf("flag -%s is in the man page but the binary does not accept it", f)
}
}
want := helpUsage(help)
got := roffSynopsis(page)
if want != "" && got != want {
t.Errorf("SYNOPSIS drift:\n page: %s\nbinary: %s", got, want)
}
})
}
}
// helpFlags extracts the flag names from a `gasm <cmd> -h` output.
func helpFlags(help string) map[string]bool {
m := map[string]bool{}
inFlags := false
for line := range strings.SplitSeq(help, "\n") {
if strings.TrimRight(line, " \t") == "Flags:" {
inFlags = true
continue
}
if !inFlags {
continue
}
if !strings.HasPrefix(line, " -") {
continue
}
token := strings.FieldsFunc(strings.TrimLeft(line, " "), func(r rune) bool {
return r == ' ' || r == '\t'
})
if len(token) == 0 {
continue
}
m[strings.TrimLeft(token[0], "-")] = true
}
return m
}
var roffEscape = regexp.MustCompile(`\\f[BIRP]`)
// roffFlags extracts the flag names from a man page's OPTIONS section.
func roffFlags(page string) map[string]bool {
m := map[string]bool{}
inOptions := false
for line := range strings.SplitSeq(page, "\n") {
if strings.HasPrefix(line, ".SH ") {
inOptions = strings.HasPrefix(line, ".SH OPTIONS")
continue
}
if !inOptions {
continue
}
// Flag entries are written as either `.B \-flag` or `\fB\-flag`.
var body string
switch {
case strings.HasPrefix(line, `.B \-`):
body = line[3:]
case strings.HasPrefix(line, `\fB\-`):
body = line[1:]
default:
continue
}
name := roffEscape.ReplaceAllString(body, "")
name = strings.ReplaceAll(name, `\-`, "-")
name = strings.TrimSpace(name)
if i := strings.IndexAny(name, " \t"); i >= 0 {
name = name[:i]
}
m[strings.TrimLeft(name, "-")] = true
}
return m
}
// helpUsage returns the command's usage line without the "Usage: " prefix.
func helpUsage(help string) string {
for line := range strings.SplitSeq(help, "\n") {
if strings.HasPrefix(line, "Usage: ") {
return normaliseUsage(line[len("Usage: "):])
}
}
return ""
}
// roffSynopsis returns the page's SYNOPSIS usage line, unescaped.
func roffSynopsis(page string) string {
inSyn := false
for line := range strings.SplitSeq(page, "\n") {
if strings.HasPrefix(line, ".SH ") {
inSyn = strings.HasPrefix(line, ".SH SYNOPSIS")
continue
}
if !inSyn || !strings.HasPrefix(line, ".B ") {
continue
}
return normaliseUsage(strings.ReplaceAll(line[3:], `\-`, "-"))
}
return ""
}
// normaliseUsage flattens whitespace and drops the roff font escapes so that
// the binary's usage line and the page's SYNOPSIS line compare equal.
func normaliseUsage(s string) string {
s = roffEscape.ReplaceAllString(s, "")
return strings.Join(strings.Fields(s), " ")
}
+4
View File
@@ -3,6 +3,10 @@
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
+62
View File
@@ -0,0 +1,62 @@
.TH GASM-ASM 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-asm \- assemble Plan 9 assembly without the Go toolchain
.SH SYNOPSIS
.B gasm asm [\-\-format raw|elf|goobj] [\-p pkg] [\-GOARCH arch] [\-o out] <file>
.SH DESCRIPTION
Assemble FILE without the Go toolchain: every TEXT function is encoded
to machine code and printed as a hex dump. Supported architectures:
amd64 (including VEX/AVX2 and EVEX/AVX-512), arm64 (AArch64 integer,
FP, conditional select, CRC32 and MOV pseudo), riscv64 (RV64IMAFDC and
RVC) and loong64 (LoongArch base ISA).
.PP
With
.B \-o
the output is written to a file instead. The
.B \-\-format
flag selects what is written:
.B raw
(the default) concatenates the functions and the data section into one
self-consistent image;
.B elf
emits a relocatable object (.text/.data sections, a symbol table and
one PC32 relocation per static-symbol reference) that links with the
system toolchain;
.B goobj
emits the Go toolchain's own object format, which cmd/link consumes
directly (it requires
.BR \-p ,
the package path, and the installed Go toolchain).
.PP
Framed functions receive the stack-split guard and the trailing
morestack block, byte-identical to the toolchain's output, so split
functions link too.
.SH OPTIONS
.TP
.B \-\-format \fIraw|elf|goobj\fR
Output format; the default is raw.
.TP
.B \-p \fIpkg\fR
Package path for --format goobj, qualifying the exported symbols.
.TP
.B \-GOARCH \fIarch\fR
Target architecture: amd64, arm64, riscv64 or loong64; overrides the
file-name suffix, which is how the suffix-less majority of GOROOT's
files (cpu_x86.s, stub.s, ...) become assemblable.
.TP
.B \-o \fIfile\fR
Write the output to this file instead of a hex dump on stdout.
.SH EXIT STATUS
Exits 0 on success, 1 when parsing or assembly fails, and 2 on a usage
error.
.SH EXAMPLES
.nf
gasm asm \-o hello.bin hello_amd64.s raw image
gasm asm \-\-format elf \-o k.o k.s linkable ELF object
gasm asm \-\-format goobj \-p pkg/path \-o k.o k.s Go object for go build
gasm asm \-GOARCH amd64 cpu_x86.s arch override
.fi
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-dis (1),
.BR gasm\-verify (1)
+47
View File
@@ -0,0 +1,47 @@
.TH GASM-AUDIT-INSTRUCTIONS 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-audit-instructions \- diff the encoder against the Go toolchain, or measure a corpus
.SH SYNOPSIS
.B gasm audit\-instructions [\-\-corpus [\fIdir\fR]] [amd64|arm64|riscv64|loong64]
.SH DESCRIPTION
Compare the gasm encoder for the given architecture (default amd64)
against
.B go tool asm
and print the diff: superset encodings (gasm-only, shippable via
.BR "gasm asm \-\-format goobj" ),
known-but-unencodable names (the backlog) and go-only names (feature
gaps). The Go side is probed black-box with a battery of bare
mnemonics, so the audit tracks whatever toolchain
.B go env GOROOT
provides.
.PP
With
.BR \-\-corpus ,
the audit changes shape: it assembles every
.I .s
file under the given directory (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, which drive the encodability backlog by
frequency rather than by table order. A run over GOROOT takes under a
second.
.SH OPTIONS
.TP
.B \-\-corpus [\fIdir\fR]
Assemble a corpus of .s files and report pass rates and failure
reasons.
.SH EXIT STATUS
The mnemonic-diff mode reports through its output and exits 0; a failed
probe or an unknown architecture exits non-zero.
.SH EXAMPLES
.nf
gasm audit\-instructions amd64
gasm audit\-instructions \-\-corpus
gasm audit\-instructions \-\-corpus "$(go env GOROOT)/src/crypto"
.fi
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-asm (1)
+113
View File
@@ -0,0 +1,113 @@
.TH GASM-DEBUG 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-debug \- interactive source-level debugger for JIT-assembled functions
.SH SYNOPSIS
.B gasm debug <file.s> \-\-func <name>
.SH DESCRIPTION
Interactive debugger for JIT-assembled functions. Launches the function
in a traced subprocess (ptrace), then provides a REPL for
single-stepping, breakpoints, register and memory inspection.
.PP
With
.B \-\-script
the REPL commands run from a file and the session ends: the headless
mode CI and scripts use.
.B \-\-cover
runs to completion with a breakpoint on every instruction and reports
which executed and how often, the label-level coverage view.
.SH REPL COMMANDS
.TP
.B break \fIlabel|addr\fR [\fBif \fIreg op val\fR]
Set a breakpoint, optionally conditional on a register comparison
(register against register or immediate).
.TP
.B delete \fIlabel|addr\fR
Remove a breakpoint.
.TP
.B info break
List all breakpoints.
.TP
.BR step " [" n ], " s
Single-step n instructions; the default is 1.
.TP
.BR next ", " n
Step over a CALL.
.TP
.BR finish ", " fin
Run until the function returns.
.TP
.BR continue ", " c
Run until a breakpoint, watchpoint or exit.
.TP
.BR disas " [" n ], " u
Disassemble n instructions at PC.
.TP
.B regs
Print general-purpose and vector registers.
.TP
.B where
Show the source line and nearest label at PC.
.TP
.B stack
Show the stack near RSP (return address and ABI0 args).
.TP
.BR bt ", " backtrace
Backtrace: current frame plus return address.
.TP
.B x [\fIaddr\fR] [\fIlen\fR]
Hex-dump memory; the defaults are the current PC and 64 bytes.
.TP
.B w \fIaddr val...\fR
Write bytes to memory.
.TP
.B set \fIreg value\fR
Set a register.
.TP
.B watch \fIaddr\fR [\fBr|w\fR] [\fIsize\fR]
Set a hardware watchpoint; writes are watched by default.
.TP
.B unwatch [\fIslot\fR]
Clear one watchpoint, or all without an argument.
.TP
.BR labels ", " l
List function labels and offsets.
.TP
.BR help ", " h ", " ?
Show command help.
.TP
.BR quit ", " q
Kill the debuggee and exit.
.SH OPTIONS
.TP
.B \-args \fIfile\fR
File containing the ABI0 argument block.
.TP
.B \-buf \fIspec\fR
Buffer specification: name:size:pattern[,name:size:pattern...] where
pattern is zero, ones, seq, or hex.
.TP
.B \-cover
Run to completion with a breakpoint on every instruction and report
which executed and how often.
.TP
.B \-func \fIname\fR
Function to debug.
.TP
.B \-script \fIfile\fR
Run REPL commands from a file (one per line) and exit; - reads stdin.
.TP
.B \-timeout \fIduration\fR
Kill the debuggee after this duration (e.g. 30s); for headless --script
runs.
.SH EXIT STATUS
Exits 0 when the scripted session completes and 1 when the debuggee
crashes or a check fails; the debugger is Linux-only.
.SH EXAMPLES
.nf
gasm debug \-\-func name k.s
gasm debug \-\-func name \-\-script cmds.txt \-\-timeout 30s k.s
gasm debug \-\-func name \-\-cover k.s
.fi
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-verify (1)
+35
View File
@@ -0,0 +1,35 @@
.TH GASM-DIFF 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-diff \- compare the machine code of two assembly files
.SH SYNOPSIS
.B gasm diff [\-GOARCH arch] <file1.s> <file2.s>
.SH DESCRIPTION
Compare the machine code produced by assembling two files. Shows which
functions differ and the byte-level differences. Useful for verifying
that two implementations produce identical code, or for tracking
encoding changes between Go assembler versions.
.PP
Functions are paired by exact name unless
.B \-\-map
says otherwise, so
.B \-\-map wideCopyAVX2=wideCopyAVX512
pairs two variants regardless of suffix.
.SH OPTIONS
.TP
.B \-GOARCH \fIarch\fR
Target architecture for both files: amd64, arm64, riscv64 or loong64;
overrides the file-name suffixes.
.TP
.B \-\-map \fIspec\fR
Comma-separated old=new pairs to match functions with different names.
.SH EXIT STATUS
Exits 0 when every paired function is identical and 1 when anything
differs; a usage error exits 2.
.SH EXAMPLES
.nf
gasm diff hello_amd64.s hello_amd64.s
gasm diff \-\-map wideCopyAVX2=wideCopyAVX512 avx2.s avx512.s
.fi
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-asm (1)
+36
View File
@@ -0,0 +1,36 @@
.TH GASM-DIS 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-dis \- disassemble machine code to instruction text
.SH SYNOPSIS
.B gasm dis [\-a arch] <file>
.SH DESCRIPTION
Disassemble machine code to instruction text, decoded through
golang.org/x/arch.
.PP
With a
.I .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. The architecture comes from the file-name suffix, or from
.BR \-a .
.PP
With any other file, or
.B \-
for standard input, the bytes are disassembled linearly and
.B \-a
selects the architecture (amd64, arm64, riscv64 or loong64).
.SH OPTIONS
.TP
.B \-a \fIarch\fR
Architecture for raw input: amd64, arm64, riscv64 or loong64.
.SH EXIT STATUS
Exits 0 on success, 1 when assembly or decoding fails, and 2 on a usage
error.
.SH EXAMPLES
.nf
gasm dis k.s assemble, then list each function
gasm dis \-a amd64 \- < dump.bin disassemble raw bytes from stdin
.fi
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-asm (1)
+52
View File
@@ -0,0 +1,52 @@
.TH GASM-FMT 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-fmt \- canonicalise the formatting of Plan 9 assembly sources
.SH SYNOPSIS
.B gasm fmt [\-w|\-l|\-d] [path...]
.SH DESCRIPTION
Canonicalise the formatting of Plan 9 assembly sources: indentation,
operand spacing, per-function mnemonic alignment and blank-line layout
(exactly one blank line before each label, TEXT and GLOBL block).
Formatting is idempotent and preserves every line, comments included.
.PP
With no paths, or a directory path, every
.I .s
file below it is reformatted in place and the changed files are listed,
the way
.B go fmt
does;
.B .
and
.B _
directories are skipped. Explicit file paths print to stdout unless
.B \-w
is given.
.PP
.B \-l
and
.B \-d
rewrite nothing:
.B \-l
prints the paths whose formatting differs from gasm's (empty output
means everything is formatted, which is what a CI check wants),
.B \-d
prints the diffs. They are mutually exclusive.
.SH OPTIONS
.TP
.B \-d
Print diffs instead of rewriting files.
.TP
.B \-l
List files whose formatting differs from gasm's.
.TP
.B \-w
Write the result to the source file.
.SH EXAMPLES
.nf
gasm fmt reformat every .s below here
gasm fmt \-w kernel_amd64.s canonicalise one file in place
gasm fmt \-l *.s list files whose formatting differs
gasm fmt \-d kernel_amd64.s print a unified diff instead
.fi
.SH SEE ALSO
.BR gasm (1)
+90
View File
@@ -0,0 +1,90 @@
.TH GASM-LINT 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-lint \- run the static checks over assembly files
.SH SYNOPSIS
.B gasm lint <file...>
.SH DESCRIPTION
Run the static checks over the given files and print diagnostics as
\fIfile:line:col: severity: message [code]\fR. The exit status is
non-zero when an error-severity diagnostic is found; warnings (e.g. the
register-clobber audit) do not affect it.
.PP
The checks are conservative: they report what can be proven wrong and
stay quiet otherwise, so a clean lint run is meaningful without
suppression lists.
.SH RULES
.TP
.B unknown-instruction
The mnemonic is not in the architecture's instruction table.
.TP
.B operand-count
The operand count disagrees with the instruction's declared arity.
.TP
.B undefined-label
A jump target names no label in the function.
.TP
.B duplicate-label
Two labels in one function share a name.
.TP
.B missing-ret
The function can fall off its end without a terminator.
.TP
.B missing-textflag-include
TEXT flags are used without including textflag.h.
.TP
.B abi-argsize
The declared frame or argument size disagrees with the
.B //\ function
signature.
.TP
.B unreachable-code
Code after RET and before the next label is dead; suppressed for
functions with PC-relative or register-indirect control flow.
.TP
.B register-clobber
A register the Go ABI fixes across calls is written without save and
restore, computed by liveness over the control-flow graph.
.TP
.B funcdata-pcdata
FUNCDATA and PCDATA indices are malformed.
.TP
.B unused-label
A label no jump reaches.
.TP
.B invalid-textflag
A TEXT flag combination the toolchain rejects.
.TP
.B stack-imbalance
The function does not restore the stack pointer on every path.
.TP
.B register-width-mismatch
An operand register has the wrong width for the instruction.
.TP
.B abi0-register-args
A call passes arguments in registers where ABI0 expects the stack
frame.
.TP
.B nonportable-register-name
A register spelling that does not exist on the target architecture.
.TP
.B unencodable-instruction
The mnemonic is known to the table but the encoder cannot assemble it
yet (amd64).
.TP
.B reserved-register-write
A write to the register the runtime reserves (arm64 R18).
.SH OPTIONS
.TP
.B \-disable \fIcodes\fR
Comma-separated rule codes to disable.
.SH EXIT STATUS
Exits 0 when no error-severity diagnostic is found, 1 otherwise, and 2
on a usage error.
.SH EXAMPLES
.nf
gasm lint kernel_amd64.s
gasm lint \-disable register-clobber,unused-label *.s
.fi
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-asm (1)
+24
View File
@@ -0,0 +1,24 @@
.TH GASM-LSP 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-lsp \- run the Plan 9 assembly language server
.SH SYNOPSIS
.B gasm lsp
.SH DESCRIPTION
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
.I .s
files; the target architecture is inferred from the file suffix
(_amd64.s, _arm64.s, _riscv64.s, _loong64.s).
.PP
Provides completion, hover, document symbols, push and pull
diagnostics, semantic-token highlighting, go-to-definition, find
references, rename, formatting, inlay hints, code actions, signature
help, document highlights, workspace symbol search, include document
links and folding ranges; definition, references and rename work across
every open document. Syntax highlighting is delivered as LSP semantic
tokens, so no editor-specific grammar is required.
.SH EXIT STATUS
Runs until the client closes the session; exits 0 on a clean shutdown.
.SH SEE ALSO
.BR gasm (1)
+20
View File
@@ -0,0 +1,20 @@
.TH GASM-PARSE 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-parse \- parse an assembly file and report syntax errors
.SH SYNOPSIS
.B gasm parse <file>
.SH DESCRIPTION
Parse FILE and report syntax errors on stderr. The parser is
error-tolerant and line-oriented: a malformed line becomes a diagnostic
and parsing continues, so one run reports every syntax error in the
file rather than the first.
.PP
On success, print how many declarations and TEXT functions the file
contains. FILE may be
.B \-
to read standard input.
.SH EXIT STATUS
Exits 0 when the file parses without errors and 1 otherwise.
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-tokens (1)
+16
View File
@@ -0,0 +1,16 @@
.TH GASM-PROFILE 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-profile \- show the basic-block structure of functions
.SH SYNOPSIS
.B gasm profile <file.s>
.SH DESCRIPTION
Show the basic-block structure of functions in an assembly file: each
function's labels, their offsets, and the block boundaries. This is
the static structure; for runtime execution counts, use
.BR "gasm verify \-fuzz" ,
which exercises the code paths.
.SH EXIT STATUS
Exits 0 on success and 1 when the file cannot be assembled.
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-verify (1)
+22
View File
@@ -0,0 +1,22 @@
.TH GASM-SCAFFOLD 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-scaffold \- generate a differential test skeleton for a kernel file
.SH SYNOPSIS
.B gasm scaffold differential <file.s>
.SH DESCRIPTION
Print a differential test skeleton for every
.B //\ func
signature in FILE. The test seeds random states, drives the kernel and
a portable reference (\fI<name>Portable\fR), and compares outputs
byte-for-byte. Write the reference bodies, place the file in the
kernel's package, and run it in CI.
.SH EXIT STATUS
Exits 0 when the skeleton is written to stdout and 1 when the file
cannot be parsed; a usage error exits 2.
.SH EXAMPLES
.nf
gasm scaffold differential kernel_amd64.s > kernel_differential_test.go
.fi
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-verify (1)
+19
View File
@@ -0,0 +1,19 @@
.TH GASM-TOKENS 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-tokens \- print the lexical token stream of an assembly file
.SH SYNOPSIS
.B gasm tokens <file>
.SH DESCRIPTION
Print the lexical token stream of FILE: position, token kind and text,
one token per line. FILE may be
.B \-
to read standard input.
.PP
This is the front end's raw view, for when the assembler's own
diagnostic is not enough: a mis-scanned operand or a swallowed comment
shows up here as the tokens the parser actually received.
.SH EXIT STATUS
Exits 0 on success and 1 when the file cannot be read.
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-parse (1)
+117
View File
@@ -0,0 +1,117 @@
.TH GASM-VERIFY 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm-verify \- JIT-assemble a file and run dynamic checks against it
.SH SYNOPSIS
.B gasm verify [\-smoke] [\-abi] [\-fuzz] [\-ground\-truth] [\-profile] [\-call] <file.s>
.SH DESCRIPTION
Assemble FILE, map it into executable memory and report the available
functions. This confirms the assembled image is self-consistent (no
unresolved external symbols) and executable, the prerequisite for
dynamic testing.
.PP
With
.BR \-smoke ,
each NOSPLIT function is called with a zeroed argument block to confirm
the JIT trampoline works end-to-end. This is safe only for functions
that tolerate nil pointers and zero lengths in their arguments.
.PP
With
.BR \-abi ,
each function is called with sentinel values in the registers the Go
ABI fixes across calls (the frame pointer and the goroutine pointer)
plus a canary below SP; violations are reported. JIT-based checks run
when the host matches the file's architecture (all but loong64, which
is ground-truth only for now).
.PP
With
.BR \-fuzz ,
each function with a
.B //\ func
signature is differentially fuzzed against the go-tool-asm version in a
subprocess (so a crash on a partial function is reported, not fatal).
.PP
With
.BR \-ground\-truth ,
the assembled machine code is compared byte-for-byte against
.B go tool asm
(relocation sites masked), reporting any encoding drift.
.PP
With
.BR \-profile ,
the static basic-block structure is listed for each function.
.PP
With
.BR \-call ,
a single function is invoked with user-supplied buffers
.RB ( \-buf )
instead of the smoke/abi/fuzz sweeps. Useful for partial functions
(e.g. decoders) that crash on random input but should succeed on valid
data.
.PP
With
.B \-save\-corpus
(and
.BR \-fuzz ),
every input that crashes or mismatches is written to the directory as
replayable JSON.
.B \-replay
re-runs saved entries against the kernel, one child process per entry,
so an input that crashed the original run crashes only the child: the
report says whether each entry reproduces.
.SH OPTIONS
.TP
.B \-abi
Run ABI-checking calls (sentinel registers and red zone).
.TP
.B \-abi\-n \fIn\fR
Number of ABI check iterations with varied inputs; the default is 100.
.TP
.B \-args \fIspec\fR
Scalar args for -call: name=value[,name=value] (decimal or 0x hex).
.TP
.B \-buf \fIspec\fR
Buffer spec for -call: name:size:pattern[,name:size:pattern] where
pattern is zero, ones, seq, or hex.
.TP
.B \-call \fIname\fR
Call a single function with -buf instead of the sweeps.
.TP
.B \-fuzz
Differential fuzz: JIT both the gasm and the go-tool-asm versions and
compare outputs.
.TP
.B \-ground\-truth
Compare machine code byte-for-byte against go tool asm.
.TP
.B \-n \fIn\fR
Number of fuzz iterations per function; the default is 1000.
.TP
.B \-profile
List basic-block structure per function.
.TP
.B \-repeat \fIn\fR
Number of times to repeat a -call invocation; the default is 1.
.TP
.B \-replay \fIdir\fR
Replay saved corpus entries (JSON files in this directory) against the
kernel.
.TP
.B \-save\-corpus \fIdir\fR
With -fuzz: write each failing input to this directory as replayable
JSON.
.TP
.B \-smoke
Call each NOSPLIT function with zeroed args.
.SH EXIT STATUS
Exits 0 when every requested check passes and 1 when any check fails;
a file that cannot be assembled exits 1 and a usage error exits 2.
.SH EXAMPLES
.nf
gasm verify \-\-call add \-\-args a=2,b=3 hello_amd64.s
gasm verify \-\-ground\-truth k.s
gasm verify \-\-fuzz \-n 500 k.s
.fi
.SH SEE ALSO
.BR gasm (1),
.BR gasm\-asm (1),
.BR gasm\-debug (1)
+96
View File
@@ -0,0 +1,96 @@
.TH GASM 1 "2026-09-19" "gasm 0.33.0" "User Commands"
.SH NAME
gasm \- developer tooling for Go's Plan 9 assembler
.SH SYNOPSIS
.B gasm
.I command
.RI [ arguments ]
.br
.B gasm
.BR \-h | \-\-help
.br
.B gasm
.BR \-V | \-\-version
.SH DESCRIPTION
.B gasm
bundles a lexer, parser, formatter, linter, standalone assembler and
language server for Plan 9 assembly into one self-contained binary. It
serves two purposes: it brings developer tooling to the
.I .s
files of Go programs, and it assembles Plan 9 assembly without the Go
toolchain at all, to raw images, linkable ELF objects with DWARF5 debug
sections, or the Go toolchain's own GOOBJ format, which
.B go build
consumes directly.
.PP
Four architectures are covered: amd64 (including VEX/AVX2 and
EVEX/AVX-512), arm64, riscv64 (RV64IMAFDC and RVC) and loong64. The
target architecture is inferred from the file-name suffix
(\fI_amd64.s\fR, \fI_arm64.s\fR, \fI_riscv64.s\fR, \fI_loong64.s\fR) or
named explicitly with \fB\-GOARCH\fR where the commands accept it.
.SH COMMANDS
.TP
.B gasm\-tokens(1)
Print the lexical token stream.
.TP
.B gasm\-parse(1)
Parse a file and report syntax errors.
.TP
.B gasm\-fmt(1)
Canonicalise formatting: gofmt for assembly.
.TP
.B gasm\-lint(1)
Run the static checks.
.TP
.B gasm\-asm(1)
Assemble \fI.s\fR files to machine code, raw images, ELF objects or GOOBJ.
.TP
.B gasm\-dis(1)
Disassemble machine code, raw bytes or an assembled \fI.s\fR file.
.TP
.B gasm\-verify(1)
JIT-assemble and run dynamic checks: smoke calls, ABI checks,
differential fuzzing, ground-truth comparison.
.TP
.B gasm\-debug(1)
Interactive source-level debugger.
.TP
.B gasm\-diff(1)
Compare the machine code of two files byte-for-byte.
.TP
.B gasm\-profile(1)
Show the basic-block structure of functions.
.TP
.B gasm\-audit\-instructions(1)
Diff the encoder against the Go toolchain's name table, or measure a
corpus of \fI.s\fR files.
.TP
.B gasm\-scaffold(1)
Generate a differential test skeleton for a kernel file.
.TP
.B gasm\-lsp(1)
Run the language server over standard input/output.
.TP
.B gasm version
Print the version, the same as \fB\-\-version\fR.
.SH GLOBAL FLAGS
.TP
.BR \-h ", " \-\-help
Show the command overview.
.TP
.BR \-V ", " \-\-version
Print the version the toolchain recorded at build time.
.SH EXIT STATUS
Exits 0 on success, 1 when a command fails, and 2 on a usage error. An
unknown command exits 2.
.SH SEE ALSO
.BR gasm\-asm (1),
.BR gasm\-fmt (1),
.BR gasm\-lint (1),
.BR gasm\-verify (1),
.BR gasm\-debug (1)
.PP
The full command reference, with worked examples and every flag, is in
docs/CLI.md of the repository
.UR https://sourcedock.dev/petrbalvin/gasm-devkit
.UE .
+35
View File
@@ -13,6 +13,10 @@ packages := "./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/..
bindir := env_var_or_default("BINDIR", env_var("HOME") / ".local" / "bin")
# Where `install-man` puts the gzip-compressed pages (man1 below it). Exported because
# the Perl recipes read it from the environment.
export MANDIR := env_var_or_default("MANDIR", env_var("HOME") / ".local" / "share" / "man")
default:
@just --list
@@ -84,6 +88,37 @@ install: build
uninstall:
rm -f "{{bindir}}/{{binary}}"
# Install the man pages under docs/man into mandir/man1, gzip-compressed. Not a gate: a
# convenience for the person at the keyboard; man finds them through ~/.local/share/man.
install-man:
#!/usr/bin/env perl
my $out = $ENV{MANDIR} . q{/man1};
system(q{mkdir}, q{-p}, $out) == 0 or die qq{mkdir $out: $!\n};
for my $p (glob q{docs/man/*.1}) {
open(my $g, q{-|}, q{gzip}, q{-c}, $p) or die qq{gzip $p: $!\n};
my $content = do { local $/; <$g> };
close($g);
my $base = $p;
$base =~ s{docs/man/}{};
open(my $o, q{>}, qq{$out/$base.gz}) or die qq{write $out/$base.gz: $!\n};
print {$o} $content;
close($o);
print qq{$out/$base.gz\n};
}
# Remove the installed man pages.
uninstall-man:
#!/usr/bin/env perl
for my $p (glob q{docs/man/*.1}) {
my $base = $p;
$base =~ s{docs/man/}{};
my $f = $ENV{MANDIR} . q{/man1/} . $base . q{.gz};
if (-f $f) {
unlink($f) or die qq{unlink $f: $!\n};
print qq{removed $f\n};
}
}
# Run the program. The flag is there because `go run` does not stamp the build otherwise.
run:
go run -buildvcs=true {{package}}