docs: state the validation status and correct claims the material contradicts

Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
2026-09-20 01:40:51 +02:00
parent 2931bbd6b2
commit f0d5238c47
22 changed files with 356 additions and 191 deletions
+44 -35
View File
@@ -16,8 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
JALR; loong64 accepts the raw `JIRL rd, rj, off` spelling the Go
assembler cannot express. A frameless amd64 function containing a
CALL now receives the toolchain's forced base-pointer frame. The
verify trampolines join the ground-truth lists, and a lint check for
control flow through registers and memory extends to the new forms.
riscv64 and loong64 verify trampolines join their ground-truth lists,
and a lint check for control flow through registers and memory extends
to the new forms.
- **`gasm asm -GOARCH` and `gasm diff -GOARCH`.** The target
architecture can be named explicitly instead of inferred from the
file-name suffix, which is how the suffix-less majority of GOROOT's
@@ -26,37 +27,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
file under a directory (default GOROOT/src) with the gasm encoder
only: suffixed files for their architecture, suffix-less files for
all four, as a GOARCH build would. Reports the headline number (127
of 627 GOROOT files, 20.3 %, assemble for every target architecture,
against 23 in the previous release), the per-architecture pass rates
and the most common failure reasons with a representative file each,
of 627 GOROOT files, 20.3 %, assemble for every target architecture),
the per-architecture pass rates and the most common failure reasons
with a representative file each,
which drive the encodability backlog by frequency.
- **Fuzz targets for the parser and the formatter.** FuzzParse (no
panic, always a usable file) and FuzzFormatIdempotency (formatting
twice equals formatting once; clean input stays clean) seed
themselves from the repository's kernels, so the plain test suite
replays every seed in CI and `just fuzz` runs the mutation engine on
demand.
- **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.
- **The parser and the formatter are fuzzed.** Two targets carry the
guarantee: no input makes the parser panic, and every input yields a
file the rest of the toolkit can work on; formatting twice equals
formatting once, and clean input stays clean. They seed from the
repository's own kernels, and `just fuzz` drives the mutation engine
on demand.
- **Man pages.** docs/man carries gasm(1) and a page for every command
except `version`, which gasm(1) documents itself, 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.
binary and compares each page's flags and synopsis with its own `-h`
output, so the pages cannot drift from the CLI.
### Changed
- **Go 1.27.1 required.** The module declares `go 1.27.1`, so building
from source needs that patch release or newer.
- **Canonical just recipes.** `just gates` is the definition of done
(build, fmt-check, vet, test, race). `install` now builds and copies
the binary into `~/.local/bin` (`BINDIR` overrides) instead of
downloading module dependencies, and `install-bin` is gone. The test
gate sweeps the logic packages (arch through verify; the hardware-bound
`debug` and the thin `cmd/gasm` sit outside it), so the coverage floor
is computed over the product code and the number is identical locally
and in CI. `fuzz` requires its target package.
gate sweeps the logic packages (arch through verify; the ptrace-bound
`debug` and the thin `cmd/gasm` sit outside the coverage profile), so
the coverage floor is computed over the product code and the number is
identical locally and in CI; the two excluded packages' own tests run
in the gate and in the pipelines, outside the floor. `fuzz` requires
its target package.
- **The reported version comes from the build.** `gasm --version`
prints the version the toolchain recorded: the tag on a tagged
checkout, a pseudo-version naming the commit below one, `+dirty` on a
@@ -78,8 +81,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
synopsis, the commands, every flag with its default, the exit codes and
worked examples; `CONTRIBUTING.md` carries the Contributor terms and
states the commit trailer form, the one-logical-change rule and the
licence header rule. The repository's own assembly (the `verify`
trampolines and the test kernels) is in `gasm fmt` canonical form.
licence header rule; `SECURITY.md` states how a vulnerability is
reported and what to expect. The repository's own assembly (the
`verify` trampolines and the test kernels) is in `gasm fmt` canonical
form.
- **The README states the project's purpose and status.** It opens with
a warning that the tool is an experiment under active development,
version 0.x.x, free to change without warning, with 1.0.0 far off,
@@ -88,7 +93,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Go toolchain), argues the case for the syntax in a new Why Plan 9
assembly section, and carries a Direction section: extended
instruction support, full GOOBJ and ELF compilation, Linux and
FreeBSD, and the four architectures.
FreeBSD, and the four architectures. A Validation status section
states what has been executed where: amd64 on real hardware, the other
three architectures under qemu-user emulation, the encoding parity on
the host for all four, and the debugger's ptrace path on amd64 only.
### Fixed
@@ -125,13 +133,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(everything after it was dropped); it is an illegal token now, invalid
UTF-8 no longer inflates byte offsets, and CRLF files format to
uniform LF.
- **A frameless amd64 function containing a CALL read its arguments from
the wrong stack slot.** The forced base-pointer frame shifted
FP references by eight bytes (`x+0(FP)` resolved to SP+0x18 where the
toolchain emits SP+0x10), so such functions loaded garbage. The
class-2 stack guard had the sibling defect: whenever the underflow
branch relaxed to its 32-bit form, its displacement ran four bytes
past the target and into the morestack CALL.
- **The class-2 stack guard branched four bytes past its target.** When
the underflow branch relaxed to its 32-bit form, its displacement was
still computed as if the branch were two bytes long, so it landed
inside the morestack CALL instead of the compare that decides it.
The long form is reachable once a large frame carries a body of roughly
a hundred bytes.
- **Immediate operands wrapped silently on amd64.** Shift counts,
immediates beyond the operand's width and displacements beyond int32
truncated without a diagnostic (`SHLQ $300` assembled as `$44`); they
@@ -290,8 +297,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
the other architectures, and the abi kernels use it; every verify
kernel is now ground-truth checkable (the numeric `X27` spelling the
kernels used is one `go tool asm` rejects).
- The GOROOT corpus number rose to 127 of 627 files (20.3 %) assembling
for every target architecture, from 108.
- **Two more spellings GOROOT uses now assemble.** riscv64 `FCLASSD`
(classify a float64 into an integer mask) is encodable, and a
displacement written as a product (`0*8(X5)`, the toolchain's own
spelling in several kernels) parses instead of being rejected.
- **loong64 JIT execution enabled.** The loong64 trampoline is now
validated end to end under qemu-user emulation (plain and ABI-checked
calls, goroutine-clobber detection), so `gasm verify` runs the JIT
+15 -9
View File
@@ -24,7 +24,9 @@ below; submitting one means you accept them.
## Development setup
Requirements: Go 1.27.1, the exact version the `go` directive in `go.mod`
declares, and [just](https://github.com/casey/just) for the recipes.
declares, [just](https://github.com/casey/just) for the recipes, and a C
compiler (gcc), because `just gates` includes `just race` and the race
detector needs cgo.
```sh
git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
@@ -54,16 +56,20 @@ workflow builds the assets and publishes the release and its notes.
## Code style
`gofmt` and `go vet` run through `just fmt` and `just vet`, with zero diff and zero
warnings tolerated. `just gates` is the definition of done in one command, and the recipe
file names what it contains. Errors are checked explicitly, wrapped as
`fmt.Errorf("context: %w", err)`, and nothing panics outside `main`. The recipe file holds
the commands, and the language and standard-library surface is the one the `go` directive
in `go.mod` pins.
warnings tolerated. `just vet` is two gates, `go vet ./...` and `go fix -diff ./...`,
so the modernisation rewrites are enforced too. `just gates` is the definition of done in
one command, and the recipe file names what it contains. Errors are checked explicitly,
wrapped as `fmt.Errorf("context: %w", err)`, and nothing panics outside `main`. The
recipe file holds the commands, and the language and standard-library surface is the one
the `go` directive in `go.mod` pins.
- `golang.org/x/arch` is the one module dependency, and it is linked into the binary:
`gasm dis` and the debugger's listings decode through it. Everything else is the
standard library.
- No cgo, no C, no external toolchain at runtime.
- No cgo and no C. The standalone encoder paths (`gasm asm --format raw` and `--format
elf`) need no Go installation; `gasm verify --ground-truth`, `gasm verify --fuzz`,
`gasm audit-instructions` and `gasm asm --format goobj` resolve through the installed
Go toolchain.
- The parser, lexer and formatter are hand-written; the `arch` instruction tables are
generated only by `_gen/gen.go` (`just gen`) and never edited by hand.
- Assembly committed to the repository goes through `gasm fmt` and `gasm lint`, so a
@@ -111,8 +117,8 @@ Workflows live in `.gitea/workflows/` and run on the project's own runners:
| Workflow | Trigger | What it does |
|---|---|---|
| Test | push or pull request to `development` | build, format check, vet, modernisation, the test suite with the coverage floor |
| Release | a `v*` tag | the same gates as Test, then the matrix build, the proven version and the release itself; the race detector runs locally in `just gates` before the tag is cut |
| Test | push or pull request to `development` | build, format check, vet, modernisation, the test suite with the coverage floor, the CLI and debugger tests outside the profile, then the oracle-parity rerun against `go tool asm` |
| Release | a `v*` tag | the same gates as Test minus the oracle-parity step, then the matrix build, the version smoke test and the release itself; the race detector runs locally in `just gates` before the tag is cut |
The local equivalent is `just gates`, which is the same set plus the race detector. The
race detector also has its own workflow, dispatched by hand; it never runs on a push or a
+53 -18
View File
@@ -5,23 +5,25 @@
> output formats and behaviour can change without warning at any time.
> A 1.0.0 release is light years away. Nothing in this document is a
> stability promise. For all of that, this is not a paper project: gasm
> is already in active use and is tested on real assembly work.
> is already in active use and is tested on real assembly work. Only
> amd64 is validated on real hardware; the other three architectures run
> under emulation ([Validation status](#validation-status)).
**GAsm** is Go's Plan 9 assembler, and Go ships it without tooling:
there is no formatter, no linter, no static analyser, no standalone
assembler and no debugger for `.s` files. Developers write assembly
blind, validate it by benchmark, and debug it by print statement.
gasm-devkit is the missing toolkit: a single, self-contained binary,
`gasm`, that serves both purposes.
there is no formatter, no linter and no debugger for `.s` files, and no
assembler that works without a Go installation. Developers write
assembly blind, validate it by benchmark, and debug it by print
statement. gasm-devkit is the missing toolkit: a single, self-contained
binary, `gasm`, that serves both purposes.
- **Help develop Plan 9 assembly.** Formatting, linting, disassembly,
dynamic verification, a source-level debugger and a language server,
for `.s` files in Go programs.
- **Use Plan 9 assembly outside the Go toolchain.** `gasm asm` encodes
on its own, with no Go installation in the loop, and writes raw
images, linkable ELF objects with DWARF5 debug sections, or the Go
on its own and writes raw images or linkable ELF objects with DWARF5
debug sections, with no Go installation in the loop; the Go
toolchain's own GOOBJ format, which `go build` consumes in place of
the toolchain's output.
the toolchain's output, needs the installed toolchain.
## Why Plan 9 assembly
@@ -69,12 +71,14 @@ to give that syntax the tooling it deserves.
operating recursively on directories the way `go fmt` does. `-l` lists
files whose formatting differs and `-d` prints a unified diff.
- **Linter.** `gasm lint` runs 18 conservative static checks, among them
`undefined-label`, `abi-argsize` (declared frame vs the `// func` signature),
`register-clobber` (Go ABI register liveness over the control-flow graph),
`stack-imbalance`, `abi0-register-args` and `unencodable-instruction`.
`undefined-label`, `abi-argsize` (declared argument area vs the `// func`
signature), `register-clobber` (Go ABI register liveness over the
control-flow graph), `stack-imbalance`, `abi0-register-args` and
`unencodable-instruction`.
- **Standalone assembler.** `gasm asm` encodes all four architectures without
the Go toolchain and writes raw images, linkable ELF objects (with DWARF5
debug sections) or the Go toolchain's own GOOBJ format, which `go build`
the Go toolchain and writes raw images or linkable ELF objects (with DWARF5
debug sections) with no Go installation needed, or the Go toolchain's own
GOOBJ format, which needs the installed toolchain and which `go build`
consumes in place of the toolchain's output. Framed functions get the
stack-split guard and the morestack block, byte-identical to the
toolchain's, so split functions link too.
@@ -86,7 +90,8 @@ to give that syntax the tooling it deserves.
byte-for-byte ground-truth comparison of the machine code.
- **Debugger.** `gasm debug` is a source-level ptrace debugger with
breakpoints (optionally conditional), hardware watchpoints, register and
memory inspection, and headless script runs with label-level coverage.
memory inspection, and headless script runs that report instruction and
label coverage.
- **Language server.** `gasm lsp` serves completion, hover, document symbols,
push and pull diagnostics, semantic-token highlighting, go-to-definition,
find references, rename, formatting, inlay hints, code actions, signature
@@ -123,6 +128,34 @@ The same measurement runs over GOROOT's whole assembly corpus:
assembling for every target architecture today, with the top failure
reasons per architecture; the number moves with every release.
### Validation status
**Only amd64 is validated on real hardware.** The other three
architectures are validated under qemu-user emulation, because the
project owns no arm64, riscv64 or loong64 machine, and emulation is the
only substitute available for the hardware. The distinction matters and
is stated rather than implied: everything below is a claim about what has
actually been executed.
| Layer | amd64 | arm64, riscv64, loong64 |
|---|---|---|
| Encoding: byte-for-byte against `go tool asm` | native hardware | native hardware (the toolchain cross-assembles any GOARCH on any host) |
| Execution: JIT calls, ABI checks, differential fuzzing | native hardware | qemu-user emulation |
| Debugger: ptrace tracing, breakpoints, watchpoints, coverage | native hardware | emulation cannot run ptrace; the layer compiles and its architecture-neutral units run under `go test ./...`, nothing more |
Consequences, stated plainly. An emulator is a model of a CPU, not the
CPU: instruction semantics are implemented in software and can differ
from silicon in ways a test suite does not reveal. A kernel that passes
under qemu-user is therefore not proven correct on real hardware, and a
discrepancy found on real hardware is a defect in gasm, reported like any
other. Encoding parity is the exception: the byte comparison against the
toolchain runs on the host for every architecture, so no emulator stands
between the claim and the evidence. The debugger is the weakest case: on
the three emulated architectures its per-architecture ptrace code has
been compiled and read, never executed. Its architecture-neutral units
run under `go test ./...`, which the race workflow and a manual run
perform; the default `just test` gate does not sweep `./debug/...`.
## Direction
The plan, in the order it is being worked:
@@ -134,7 +167,8 @@ The plan, in the order it is being worked:
an extended instruction set the toolchain does not know at all. The
toolchain-derived tables stay generated and untouched; only the
extended instructions are hand-maintained, with their own spellings
and encoders, verified by execution on real hardware because the
and encoders, verified by execution (on real hardware for amd64, under
emulation for the rest, per the validation status above) because the
toolchain offers no ground truth to compare against. The gaps exist
on every architecture, amd64 included.
- **Full GOOBJ and ELF compilation.** The destination is a complete,
@@ -205,7 +239,7 @@ gasm verify --ground-truth k.s # byte-for-byte vs go tool asm
gasm verify --fuzz k.s # differential fuzz vs the go tool asm build
gasm debug --func name k.s # interactive debugger
gasm debug --func name --script cmds.txt --timeout 30s k.s # headless run
gasm debug --func name --cover k.s # which labels did execution reach?
gasm debug --func name --cover k.s # instruction and label coverage
gasm diff a.s b.s # compare machine code byte-for-byte
gasm diff --map wideCopyAVX2=wideCopyAVX512 avx2.s avx512.s
gasm profile k.s # show basic-block structure
@@ -243,7 +277,8 @@ recipe.
- [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
except `version`, which is documented inside gasm(1) instead, 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
+2 -1
View File
@@ -29,7 +29,8 @@ Include:
- You are kept informed while the fix is being made, and told when it ships.
- The fix is released before the details are published, and the timing is agreed with
you.
- The reporter is credited in the release notes unless they ask otherwise.
- The fix ships without naming you: the project keeps no credits list, so the release
notes, the changelog and the commits name no reporter.
## Out of scope
+8 -5
View File
@@ -40,10 +40,13 @@ func cmdAuditInstructions(args []string) error {
fs := newCommand("audit-instructions", "gasm audit-instructions [--corpus [dir]] [amd64|arm64|riscv64|loong64]", `
Compare the gasm encoder for the given architecture (default amd64) against
go tool asm and print the diff: superset encodings (gasm-only, shippable via
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 `+"`go env GOROOT`"+`
provides.
gasm asm --format goobj) and known-but-unencodable names (the backlog). The
Go side is probed black-box one bare mnemonic at a time, 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 elsewhere. Names go tool asm knows and gasm does not cannot be
enumerated by probing, because Go's table is visible only through names
already in the gasm table; the report closes with a note saying so.
With --corpus the audit changes shape: it assembles every .s file under the
given directory (default GOROOT/src) with the gasm encoder only, no
@@ -109,7 +112,7 @@ the encodability backlog by frequency rather than by table order.
w := os.Stdout
fmt.Fprintf(w, "gasm table (%s, families excluded): %d mnemonics\n", archName, len(names))
fmt.Fprintf(w, "gasm encodable: %d go tool asm recognized: %d\n", len(shared)+len(superset), countTrue(goKnown))
fmt.Fprintf(w, "gasm encodable: %d go tool asm recognised: %d\n", len(shared)+len(superset), countTrue(goKnown))
fmt.Fprintf(w, "shared: %d\n", len(shared))
fmt.Fprintf(w, "\nSuperset encodings (gasm-only; ship via gasm asm --format goobj):\n")
for _, n := range superset {
+3 -2
View File
@@ -24,9 +24,10 @@ function in a traced subprocess (ptrace), then provides a REPL for
single-stepping, breakpoints, register and memory inspection.
REPL commands:
break <label|addr> [if <reg> <op> <val>]
break <label|addr|line> [if <reg> <op> <val|reg|*addr>]
set a breakpoint, optionally conditional on a
register comparison (reg-reg or reg-immediate)
comparison of one register against a constant,
another register, or the 8-byte word at *addr
delete <label|addr> remove a breakpoint
info break list all breakpoints
step [n], s single-step n instructions (default 1)
+11 -7
View File
@@ -486,10 +486,13 @@ func cmdAsm(args []string) int {
With -o the output is written to a file instead. The --format flag selects
what is written: raw (the default) concatenates the functions and the data
section into one self-consistent image; 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; goobj emits
the Go toolchain's own object format, which cmd/link consumes directly (it
requires -p, the package path, and the installed Go toolchain).
(.text/.data sections, a symbol table and one relocation per static-symbol
reference, in the architecture's own form: R_X86_64_PC32 on amd64,
R_AARCH64_*, R_RISCV_* or R_LARCH_* on the others) that links with the
system toolchain; goobj emits the Go toolchain's own object format, which
cmd/link consumes directly (it requires -p, the package path, and the
installed Go toolchain: the object preamble is captured from go tool asm
and the format version from go version).
`)
out := fs.String("o", "", "write the output to this file")
format := fs.String("format", "raw", "output format: raw (concatenated image), elf or goobj (Go object)")
@@ -793,8 +796,8 @@ func cmdProfile(args []string) int {
flagSet := newCommand("profile", "gasm profile <file.s>", `
Show the basic-block structure of functions in an assembly file.
Lists each function's labels, their offsets, and the block boundaries.
This is the static structure; for runtime execution counts, use
gasm verify --fuzz which exercises the code paths.
This is the static structure; for runtime execution counts use
gasm debug --cover, and for input coverage gasm verify --fuzz.
`)
flagSet.Parse(args)
if flagSet.NArg() != 1 {
@@ -1033,7 +1036,8 @@ With -profile, the static basic-block structure is listed for each function.
With -call, a single function is invoked with user-supplied buffers (-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.
decoders) that crash on random input but should succeed on valid data. The
function named must be NOSPLIT: a function with a stack frame is refused.
With -save-corpus (and -fuzz), every input that crashes or mismatches is
written to the directory as replayable JSON. -replay re-runs saved
+4 -4
View File
@@ -33,7 +33,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
entryAddr := codeBase + uint64(funcOffset)
fmt.Printf("stopped at function entry: %#x (%d bytes)\n", entryAddr, funcSize)
fmt.Println("commands: break <label|addr> | step [n] | continue | disas [n] | regs | where | x <addr> [len] | w <addr> <val...> | labels | quit")
fmt.Println("commands: break <label|addr|line> | step [n] | continue | disas [n] | regs | where | x <addr> [len] | w <addr> <val...> | labels | quit")
scanner := bufio.NewScanner(in)
@@ -233,7 +233,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
case "break", "b":
if len(parts) < 2 {
fmt.Println("usage: break <label|addr|line> [if <reg> <op> <val>]")
fmt.Println("usage: break <label|addr|line> [if <reg> <op> <val|reg|*addr>]")
continue
}
var addr uint64
@@ -278,7 +278,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
}
}
} else if len(parts) >= 4 && parts[2] == "if" {
fmt.Println("usage: break <label|addr> if <reg> <op> <value|reg|*addr>")
fmt.Println("usage: break <label|addr|line> if <reg> <op> <value|reg|*addr>")
continue
}
bp, err := bm.SetWithCond(addr, label, cond)
@@ -422,7 +422,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
fmt.Println()
case "help", "h", "?":
fmt.Printf(` break <label|addr> [if <reg> <op> <val|reg|*addr>]
fmt.Printf(` break <label|addr|line> [if <reg> <op> <val|reg|*addr>]
set a breakpoint, optionally conditional on a
register compared to a constant, a register, or the
8-byte word at *addr
+67 -39
View File
@@ -13,11 +13,13 @@ Three design goals shape everything below.
So the centre of the toolkit is a hand-written lexer and a parser that
produce a typed AST with source positions on every node.
2. **Architecture as data, not code.** Per-architecture differences (amd64,
arm64, riscv64, loong64) live in register and instruction *tables* (`arch`),
never in `if arch == …` branches scattered through the logic. The
instruction tables are generated from the Go toolchain's own assembler
source (`just gen`), so adding or refreshing an architecture is a data
operation, not a coding one.
arm64, riscv64, loong64) live in register and instruction *tables* (`arch`)
and per-architecture encoders, rather than in `if arch == …` branches
threaded through the analysis; the arch tests that remain are dispatch and
policy points, such as which encoder a file name selects and which
registers the liveness pass audits. The instruction tables are generated
from the Go toolchain's own assembler source (`just gen`), so refreshing an
architecture is a data operation, not a coding one.
3. **Open integration surface.** Everything the toolkit can do is reachable
through two vendor-neutral interfaces: a CLI and an LSP server. No editor
owns the toolkit; the toolkit is offered to editors on standard terms.
@@ -35,10 +37,19 @@ flowchart TD
ARCH["arch tables<br/>amd64 / arm64 / riscv64 / loong64"] --> LINT
ARCH --> LSP
LINT --> LSP
PAR --> ASM["asm<br/>encoders, image, object emitters"]
ASM --> VER["verify<br/>JIT mapping, ABI checks, fuzzing"]
ASM --> DBG["debug<br/>ptrace session"]
VER --> DBG
DIS["disasm<br/>golang.org/x/arch"] --> DBG
FMT --> CLI["gasm CLI"]
LINT --> CLI
PAR --> CLI
LEX --> CLI
ASM --> CLI
VER --> CLI
DBG --> CLI
DIS --> CLI
LSP --> EDITOR["any LSP editor"]
```
@@ -70,9 +81,11 @@ assembler provides.
The boundaries matter as much as the responsibilities: `ast` records syntax
only, and whether a name is a register or a label is left to `arch`, so the
parser stays architecture-agnostic. `asm` and `verify` are the only packages
that touch machine code and executable memory, and `cmd/gasm` owns no logic
beyond flags and output.
parser stays architecture-agnostic. `asm` produces the machine code, `verify`
and `debug` are the two packages that map it executable (read-execute in
`verify`, read-write-execute in the debuggee), and `cmd/gasm` is the CLI, with
the verify sweep orchestration and the audit, scaffold and unified-diff
helpers beside its flags and output.
### `token` and `lexer`
@@ -112,14 +125,17 @@ Register files are generated programmatically (the regular `R8`-`R15`,
`X0`-`X15`, `Y0`-`Y15`, `Z0`-`Z31`, `K0`-`K7` ranges) plus the irregularly
named registers listed explicitly. Instruction names are **generated from the
Go toolchain's own assembler source** (`cmd/internal/obj/<arch>/anames.go`,
plus the common opcodes and the per-architecture front-end aliases such as the
arm64 `B`/`BL` branches and the `.P`/`.W` load-store addressing suffixes) by
`just gen`, so the tables always match what the real assembler accepts. Each
mnemonic maps to a summary and an optional operand-count range; counts are
recorded only where unambiguous (`-1` disables the operand-count lint for that
instruction) so the linter stays silent rather than guess. For architectures
with highly variable operand forms (arm64, riscv64, loong64) only a few
fixed-arity instructions (`RET`, `NOP`, `JMP`, `CALL`) carry counts at all.
plus the common opcodes in `cmd/internal/obj/util.go`) by `just gen`, so the
tables always match what the real assembler accepts. The spellings the
toolchain's tables do not carry are hand-maintained instead: the front-end
alias lists in `arch/arm64.go`, `arch/amd64.go` and `arch/loong64.go` (the
arm64 `B`/`BL` branches among them), and the arm64 `.P`/`.W` load-store suffix
stripping in `arch/arch.go`. Each mnemonic maps to a summary and an optional
operand-count range; counts are recorded only where unambiguous (`-1`
disables the operand-count lint for that instruction) so the linter stays
silent rather than guess. For architectures with highly variable operand
forms (arm64, riscv64, loong64) `relaxCounts` clears those counts, leaving
`RET` and `NOP` with a range (`RET` alone on riscv64).
### `lint`
@@ -291,11 +307,11 @@ registers are translated onto the hardware stack pointer: `x+N(FP)` becomes
pointer is set up, with the matching Go prologue/epilogue generated, so the
output is byte-identical to the Go assembler for these cases. SIMD is handled
by a VEX (AVX/AVX2) encoder (the two- and three-byte VEX prefixes with XMM/YMM
registers) across eight operand forms: the three-operand NDS form, the
two-operand reg/rm form, the immediate-shift form (plus the variable-count
shifts, which share the NDS shape with the count in an XMM register or
memory), the immediate shuffle form (`VPSHUFD`, `VPERMQ`), the
three-operand-plus-immediate form (`VSHUFPD`,
registers) over nine operand forms plus a dedicated move encoder: the
three-operand NDS form, the two-operand reg/rm form, the immediate-shift form
(plus the variable-count shifts, which share the NDS shape with the count in
an XMM register or memory), the immediate shuffle form (`VPSHUFD`, `VPERMQ`),
the three-operand-plus-immediate form (`VSHUFPD`,
`VPERM2I128`, `VINSERTI128`), the lane-extract form (`VEXTRACTI128`,
`VEXTRACTF128`, where the YMM source occupies the reg field and the XMM or
memory destination r/m), the direction-sensitive moves (`VMOVDQU`, `VMOVUPD`,
@@ -340,10 +356,10 @@ b bit and the L'L rounding-control field (broadcast keeps the vector length
and scales disp8 by the element size), and combine with the .Z zeroing
suffix. Every encoding is validated two ways: by
round-trip decoding through `golang.org/x/arch`, and byte-for-byte against
the machine code the real Go assembler emits, a comparison that holds for
whole functions: all 27 functions of both kernels assemble to exactly the Go
toolchain's bytes, the lone exception being the displacements of the
static-constant loads, which the Go linker fills at link time.
the machine code the real Go assembler emits; the parity suites carry that
comparison over whole kernel files on all four architectures, with the
relocation fields masked because the Go linker fills those displacements at
link time.
File-level assembly (`AssembleFile`) goes beyond single functions: it
materialises the file's static symbols (`GLOBL`/`DATA`) in a data section
@@ -426,10 +442,13 @@ The `gasm verify` CLI subcommand exposes this: it loads a file, reports the
available functions and (with `-smoke`) calls each NOSPLIT function with zeroed
arguments to confirm the trampoline round-trips. The `-smoke` and `-abi`
sweeps run in parallel and each inside a child process, so a function that
faults is reported without ending the sweep. `gasm verify --fuzz` combines
ABI checks (sentinel registers, canary, stack bounds) with differential fuzz
testing, comparing the JIT-assembled kernel against the portable Go reference
bit-for-bit while verifying the ABI contract on every iteration. When a fuzz
faults is reported without ending the sweep; `-abi` is where the ABI check
lives, fuzzing each function with sentinel values in the registers the Go ABI
fixes across calls and a canary below `SP`, and reporting a violation on any
iteration. `gasm verify --fuzz` is the differential campaign instead: it
JIT-loads the kernel and the `go tool asm` build of the same kernel and
compares the output argument areas bit-for-bit, one child process per function
so a crash on a partial function is reported rather than fatal. When a fuzz
iteration crashes or mismatches, `FuzzResult.CrashInput` stores the exact input
for reproducibility. `gasm verify --call <func> --buf name:size:pattern`
invokes a single function with user-supplied buffers (patterns: zero, ones,
@@ -449,16 +468,25 @@ masked), reporting any encoding drift.
The interactive debugger (all four architectures). It launches the target
function in a child process that maps the JIT code, calls
`PTRACE_TRACEME`, and stops; the parent attaches via ptrace and controls
execution. Breakpoints are patched as INT3 bytes through `/proc/pid/mem`
(PTRACE_PEEKTEXT is unreliable with Go's multi-threaded runtime).
execution. Breakpoints are patched through `/proc/pid/mem`: the one-byte
`INT3` on amd64, the four-byte break instruction on the other three (arm64
`BRK #0`, riscv64 `ebreak`, loong64 `break 0`).
The child pins its goroutine to the OS thread with `runtime.LockOSThread`
so the traced thread is the one executing JIT code. The REPL provides
single-step, register inspection (GPR + YMM/XMM via `PTRACE_GETFPREGS`),
single-step, register inspection (the GPRs on every architecture; on amd64 the
XMM set through `PTRACE_GETFPREGS` and the YMM set through `PTRACE_GETREGSET`
on `NT_X86_XSTATE`; on the other three the FP/SIMD regset through
`PTRACE_GETREGSET` on `NT_PRFPREG`),
label resolution, named buffer allocation with pattern filling
(`--buf name:size:pattern`: zero, ones, seq, or hex), and breakpoint
management. Breakpoints accept conditions
(`break <label> if <reg> <op> <val>`, including register-against-register
comparisons), and hardware watchpoints work on all four architectures.
comparisons), and hardware watchpoints work on amd64 (the DR0-DR3 debug
registers), arm64 (`NT_ARM_HW_WATCH`) and loong64 (`NT_LOONGARCH_HW_WATCH`);
riscv64 reports that its kernel ptrace interface exposes no trigger regset.
The ptrace path is validated at run time on amd64, where the session tests are
built; arm64, riscv64 and loong64 compile and are covered by the
architecture-neutral units (label and line tables, the breakpoint manager).
For non-interactive use, `--script` runs REPL commands from a file (or
stdin) and exits, `--timeout` kills the debuggee when a run hangs (the
watchdog is armed before the ptrace attach, so a sandboxed debuggee cannot
@@ -499,9 +527,10 @@ sequenceDiagram
Errors are produced where the parse or the encoding fails and become values at
the CLI boundary: the parser returns a diagnostic list and never aborts a file,
`AssembleFile` returns an error, and `cmd/gasm` prints what it has to stderr
and returns a non-zero exit code. The formatter and the linter take the same
AST by a different route: `gasm fmt` re-spaces the token stream and `gasm lint`
walks the parsed file, so neither depends on an encoding.
and returns a non-zero exit code. The formatter and the linter take different
inputs from the assembler: `gasm fmt` re-spaces the token stream
(`format.Source` lexes the source text itself) and `gasm lint` walks the parsed
AST, so neither depends on an encoding.
## State and lifetime
@@ -522,9 +551,8 @@ walks the parsed file, so neither depends on an encoding.
## Dependencies
- **`golang.org/x/arch`** (v0.30.0) is the one module dependency: it is the
disassembler backend (`gasm dis` and the debugger's listings) and the source
of the register metadata the encoder consults (`asm/reg.go`, `asm/vex.go`).
The tests additionally decode through it to validate the encodings.
disassembler backend (`gasm dis` and the debugger's listings). The tests
additionally decode through it to validate the encodings.
- **The Go toolchain**, as an oracle and never as a library: `go tool asm`
supplies the object preamble and the ground truth for `gasm verify
--ground-truth`, `go list -json -export` locates the archives of the packages
+41 -26
View File
@@ -3,9 +3,11 @@
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.
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
@@ -58,7 +60,8 @@ Usage: gasm parse <file>
```
Parse FILE and report syntax errors on stderr. On success, print how many
declarations and TEXT functions the file contains.
declarations and TEXT functions the file contains. FILE may be `-` to read
standard input.
```sh
gasm parse hello_amd64.s
@@ -155,7 +158,9 @@ 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.
`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.
```sh
gasm asm hello_amd64.s
@@ -208,7 +213,7 @@ Usage: gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] <f
| `--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 |
| `--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 |
@@ -219,7 +224,8 @@ 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.
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
@@ -262,17 +268,18 @@ Usage: gasm debug <file.s> --func <name>
| `-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.
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> [if <reg> <op> <val>]` | set a breakpoint, optionally conditional |
| `delete <label\|addr>` | remove a breakpoint |
| `info break` | list the breakpoints |
| `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 |
@@ -346,13 +353,18 @@ 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.
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
@@ -360,8 +372,9 @@ gasm audit-instructions amd64
```text
gasm table (amd64, families excluded): 1542 mnemonics
gasm encodable: 580 go tool asm recognized: 1542
shared: 580
gasm encodable: 587 go tool asm recognised: 1542
shared: 587
...
```
With `--corpus` the audit changes shape: it assembles every `.s` file under
@@ -381,10 +394,12 @@ 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
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
...
```
@@ -468,5 +483,5 @@ 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
gasm debug --func decodeBlockAVX2 --cover --timeout 30s kernel_amd64.s
```
+24 -7
View File
@@ -6,9 +6,15 @@ Repository: [sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrb
- **Go** 1.27.1, the exact version the `go` directive in `go.mod` declares
- **just**, the command runner; every task below is a just recipe
- **A C compiler** (`gcc`): `just race` runs the suite under the race detector,
which needs cgo
- **Perl**: the `test`, `fmt-check`, `install-man` and `uninstall-man` recipes
are Perl programs
- **`gzip`**: `install-man` compresses the man pages with it
- A Linux host on amd64, arm64, riscv64 or loong64: `gasm debug` needs ptrace
and the JIT checks of `gasm verify` need executable memory
- No external dependencies beyond the Go toolchain
- **`golang.org/x/arch`**, the one module dependency, which the Go toolchain
fetches; nothing else sits outside the standard library
## Setup
@@ -25,8 +31,9 @@ Every recipe in the `justfile`, and what it does.
| Recipe | What it does |
|---|---|
| `default` (bare `just`) | prints the recipe list (`@just --list`) |
| `just build` | compiles `bin/gasm` with `CGO_ENABLED=0` and stripped symbols; zero errors and zero warnings |
| `just test` | the test gate: the suite with `-count=1`, the coverage profile and the 80 % floor |
| `just test` | the test gate: the suite with `-count=1`, the coverage profile and the 80 % floor, then the CLI and debugger tests outside the profile |
| `just race` | the same suite under the race detector; the expensive one, so it runs once, inside `gates` |
| `just unit [packages] [run]` | fast, cached, scoped run for iterating: no race and no coverage, so an unchanged package reports instantly |
| `just fuzz <target> <pkg> [fuzztime]` | time-boxed fuzz of one target; the package is required, because `go test -fuzz` refuses more than one |
@@ -36,7 +43,7 @@ Every recipe in the `justfile`, and what it does.
| `just vet` | both static gates: `go vet` and `go fix -diff` |
| `just gates` | `build`, `fmt-check`, `vet`, `test` and `race`, in that order: the definition of done |
| `just clean` | removes the build artefacts, `bin/` and `coverage.out` |
| `just install` | builds, then copies the binary into `bindir` (`~/.local/bin`); `gasm debug` needs an installed binary, because it spawns the debuggee from `$PATH` |
| `just install` | builds, then copies the binary into `bindir` (`~/.local/bin`) |
| `just uninstall` | removes the installed binary from `bindir` |
| `just install-man` | installs the man pages under `docs/man` into `~/.local/share/man/man1` (`MANDIR` overrides), gzip-compressed; not a gate |
| `just uninstall-man` | removes the installed man pages |
@@ -55,9 +62,18 @@ go test -count=1 -timeout 10m -coverprofile=coverage.out \
The suite runs over the logic packages (`-count=1`, so no cached pass
counts): arch, asm, ast, disasm, format, lexer, lint, lsp, parser,
token, verify. `debug` traces a live process and `cmd/gasm` is thin CLI
glue, so both sit outside the sweep, and a thin `cmd/` in it would drag
the coverage total under the floor. The floor fails if the total is
below 80 %. CI runs the same command with the same ten-minute bound, so
glue, so both sit outside the profile sweep, and a thin `cmd/` in it
would drag the coverage total under the floor. Their tests still run, in
a second invocation without a profile:
```sh
go test -count=1 -timeout 10m ./cmd/... ./debug/...
```
That covers the CLI's exit codes and the guard that compares the manual
pages with the binary's own help, and the debugger's architecture-neutral
units. The floor fails if the total is below 80 %. CI runs the same two
commands with the same ten-minute bound, so
the number is the same everywhere.
### `just run`
@@ -131,7 +147,8 @@ therefore the fastest way to a green pipeline.
Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`,
which triggers the release workflow: it builds the portable Linux targets,
takes the notes from the matching `CHANGELOG.md` section and uploads the
assets.
assets. `SECURITY.md` carries the supported-versions table, so that table
moves with the release; the pipeline refuses a tag the policy does not name.
The version is never injected. `gasm --version` prints what the
toolchain recorded in the build information: the tag on a tagged
+15 -4
View File
@@ -20,13 +20,24 @@ flag selects what is written:
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
one relocation per static-symbol reference, in the architecture's own
form: R_X86_64_PC32 on amd64, R_AARCH64_*, R_RISCV_* or R_LARCH_* on the
others) 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).
the package path, and the installed Go toolchain: the object preamble is
captured from
.B go tool asm
and the format version from
.BR "go version" ).
.PP
.B raw
and
.B elf
need no toolchain at all.
.PP
Framed functions receive the stack-split guard and the trailing
morestack block, byte-identical to the toolchain's output, so split
@@ -52,8 +63,8 @@ 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 \-\-format elf \-o k.o k_amd64.s linkable ELF object
gasm asm \-\-format goobj \-p pkg/path \-o k.o k_amd64.s Go object for go build
gasm asm \-GOARCH amd64 cpu_x86.s arch override
.fi
.SH SEE ALSO
+10 -5
View File
@@ -8,12 +8,17 @@ 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
.BR "gasm asm \-\-format goobj" )
and known-but-unencodable names (the backlog). The Go side is probed
black-box one bare mnemonic at a time, so the audit tracks whatever
toolchain
.B go env GOROOT
provides.
provides; the gasm side answers from the encoder table on amd64 and from
trial assembly over a battery of operand shapes elsewhere. Names
.B go tool asm
knows and gasm does not cannot be enumerated by probing, 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.
.PP
With
.BR \-\-corpus ,
+7 -5
View File
@@ -17,14 +17,16 @@ 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).
.B break \fIlabel|addr|line\fR [\fBif \fIreg op val|reg|*addr\fR], b
Set a breakpoint at a label, an address or a source line number, optionally
conditional on a register comparison: against a constant, against another
register, or against the 8-byte word at
.BR *addr .
.TP
.B delete \fIlabel|addr\fR
.B delete \fIlabel|addr\fR, d
Remove a breakpoint.
.TP
.B info break
.B info break, info breakpoints, info b
List all breakpoints.
.TP
.BR step " [" n ], " s
+1 -1
View File
@@ -28,7 +28,7 @@ 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
gasm diff \-\-map wideCopyAVX2=wideCopyAVX512 avx2_amd64.s avx512_amd64.s
.fi
.SH SEE ALSO
.BR gasm (1),
+1 -1
View File
@@ -28,7 +28,7 @@ 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 k_amd64.s assemble, then list each function
gasm dis \-a amd64 \- < dump.bin disassemble raw bytes from stdin
.fi
.SH SEE ALSO
+7
View File
@@ -41,6 +41,13 @@ List files whose formatting differs from gasm's.
.TP
.B \-w
Write the result to the source file.
.SH EXIT STATUS
Exits 0 on success, 1 when a path cannot be read or written, and 2 on a
usage error (combining
.B \-l
and
.BR \-d ,
or an unknown flag).
.SH EXAMPLES
.nf
gasm fmt reformat every .s below here
+17 -5
View File
@@ -33,7 +33,11 @@ The function can fall off its end without a terminator.
TEXT flags are used without including textflag.h.
.TP
.B abi-argsize
The declared frame or argument size disagrees with the
The declared argument area (the
.I \-args
part of
.IR $frame\-args )
disagrees with the
.B //\ function
signature.
.TP
@@ -52,7 +56,8 @@ FUNCDATA and PCDATA indices are malformed.
A label no jump reaches.
.TP
.B invalid-textflag
A TEXT flag combination the toolchain rejects.
An unknown TEXT or GLOBL flag, reported one flag at a time; numeric flags
are accepted as textflag.h constants.
.TP
.B stack-imbalance
The function does not restore the stack pointer on every path.
@@ -61,11 +66,18 @@ The function does not restore the stack pointer on every path.
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.
A function whose
.B //\ function
parameters are never read from their
.IR name+offset(FP)
frame slots, which usually means the body takes its arguments from
registers instead.
.TP
.B nonportable-register-name
A register spelling that does not exist on the target architecture.
An amd64 register alias gasm accepts but
.B go tool asm
rejects (the RAX/EAX family); the canonical spelling is named in the
diagnostic.
.TP
.B unencodable-instruction
The mnemonic is known to the table but the encoder cannot assemble it
+4 -3
View File
@@ -6,9 +6,10 @@ gasm-profile \- show the basic-block structure of functions
.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.
the static structure; for runtime execution counts use
.BR "gasm debug \-\-cover" ,
and for input coverage
.BR "gasm verify \-\-fuzz" .
.SH EXIT STATUS
Exits 0 on success and 1 when the file cannot be assembled.
.SH SEE ALSO
+5 -4
View File
@@ -45,7 +45,8 @@ 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.
data. The function named must be NOSPLIT: a function with a stack frame
is refused with a diagnostic and exits 1.
.PP
With
.B \-save\-corpus
@@ -73,7 +74,7 @@ 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.
Call a single NOSPLIT function with -buf instead of the sweeps.
.TP
.B \-fuzz
Differential fuzz: JIT both the gasm and the go-tool-asm versions and
@@ -107,8 +108,8 @@ 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
gasm verify \-\-ground\-truth k_amd64.s
gasm verify \-\-fuzz \-n 500 k_amd64.s
.fi
.SH SEE ALSO
.BR gasm (1),
+8 -4
View File
@@ -17,11 +17,15 @@ 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
files of Go programs, and it assembles Plan 9 assembly to raw images or
linkable ELF objects with DWARF5 debug sections without the Go toolchain at
all, plus the Go toolchain's own GOOBJ format, which
.B go build
consumes directly.
consumes directly. GOOBJ is the one format that needs the toolchain
installed: the object preamble is captured from
.B go tool asm
and the format version from
.BR "go version" .
.PP
Four architectures are covered: amd64 (including VEX/AVX2 and
EVEX/AVX-512), arm64, riscv64 (RV64IMAFDC and RVC) and loong64. The
+7 -4
View File
@@ -2,13 +2,16 @@
// SPDX-License-Identifier: BSD-3-Clause
// Package verify provides the dynamic-analysis substrate for gasm: it
// JIT-assembles Plan 9 amd64 kernels into executable memory and calls them
// JIT-assembles Plan 9 kernels for all four supported architectures
// (amd64, arm64, riscv64, loong64) into executable memory and calls them
// directly, enabling differential testing against portable Go references,
// runtime ABI checks and basic-block coverage profiling.
//
// The execution model is pure Go (stdlib only): machine code is mapped with
// syscall.Mmap and invoked through an assembly trampoline that switches to a
// prepared ABI0 stack. No cgo, no external toolchain.
// The execution model is pure Go, stdlib only, with no cgo: machine code is
// mapped with syscall.Mmap and invoked through an assembly trampoline that
// switches to a prepared ABI0 stack. The toolchain-comparison helpers in
// this package (groundtruth.go) are the one exception, shelling out to the
// installed Go toolchain and using its assembler as the differential oracle.
package verify
import (