docs: state the validation status and correct claims the material contradicts
Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
+67
-39
@@ -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
@@ -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
@@ -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
|
||||
|
||||
+17
-6
@@ -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
|
||||
@@ -51,10 +62,10 @@ 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
|
||||
gasm asm \-o hello.bin hello_amd64.s raw image
|
||||
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
|
||||
.BR gasm (1),
|
||||
|
||||
@@ -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 ,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user