Files
gasm-sdk/disasm/disasm.go
T
petrbalvin dd356b9e6a fix(disasm): name the amd64 families x/arch decodes to the zero opcode
x/arch reports the ADCX, ADOX, RDSEED, RDPID, TPAUSE, UMONITOR, UMWAIT
and ENDBR families with no error but the degenerate zero instruction,
which GoSyntax renders as Op(0) under its prefix decoration and with a
length of one.  A supplementary naming table keyed by the opcode
pattern restores the toolchain's own spellings and lengths; the parity
fixtures pin all 41 corpus rows (ENDBR32 alone, which the toolchain
cannot spell, pins as bytes and text in the focused naming test).

Assisted-by: GLM 5.3 Flash
2026-10-07 02:27:51 +02:00

106 lines
3.5 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
// Package disasm decodes machine code back to instruction text for the four
// architectures gasm assembles. It is a thin, platform-independent wrapper
// over golang.org/x/arch and backs both the `gasm dis` command and the live
// debugger views.
package disasm
import (
"fmt"
"golang.org/x/arch/arm64/arm64asm"
"golang.org/x/arch/loong64/loong64asm"
"golang.org/x/arch/riscv64/riscv64asm"
"golang.org/x/arch/x86/x86asm"
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
)
// Instruction is one decoded instruction: its text form, its length in bytes
// and the address it was decoded at.
type Instruction struct {
Addr uint64
Text string
Len int
}
// Decode decodes the instruction at the start of code, located at addr.
// code needs to hold at least the one instruction being decoded (amd64 may
// consume up to 15 bytes). Undecodable bytes yield the placeholder text "???"
// and a length of one word (four bytes, one on amd64) so that a listing can
// keep making progress, mirroring the debugger's behaviour.
func Decode(a arch.Arch, code []byte, addr uint64) (Instruction, error) {
if len(code) == 0 {
return Instruction{}, fmt.Errorf("disasm: empty input")
}
switch a {
case arch.ARM64:
if len(code) < 4 {
return Instruction{}, fmt.Errorf("disasm: need 4 bytes, have %d", len(code))
}
inst, err := arm64asm.Decode(code)
if err != nil {
return Instruction{Addr: addr, Text: "???", Len: 4}, nil
}
return Instruction{Addr: addr, Text: arm64asm.GoSyntax(inst, addr, nil, nil), Len: 4}, nil
case arch.RISCV:
// The compressed extensions are decoded transparently; a 16-bit
// instruction only needs its two bytes.
inst, err := riscv64asm.Decode(code)
if err != nil {
return Instruction{Addr: addr, Text: "???", Len: 2}, nil
}
return Instruction{Addr: addr, Text: riscv64asm.GoSyntax(inst, addr, nil, nil), Len: inst.Len}, nil
case arch.LOONG64:
if len(code) < 4 {
return Instruction{}, fmt.Errorf("disasm: need 4 bytes, have %d", len(code))
}
inst, err := loong64asm.Decode(code)
if err != nil {
return Instruction{Addr: addr, Text: "???", Len: 4}, nil
}
return Instruction{Addr: addr, Text: loong64asm.GoSyntax(inst, addr, nil), Len: 4}, nil
default: // amd64
inst, err := x86asm.Decode(code, 64)
if err != nil {
return Instruction{Addr: addr, Text: "???", Len: 1}, nil
}
if inst.Op == 0 {
// x/arch reports a few opcode families with no error but the
// degenerate zero instruction: no opcode, no operands and a
// length of one, which GoSyntax renders as "Op(0)". The
// supplementary naming table restores the families the Go
// toolchain names; anything else keeps the placeholder.
if text, n, ok := nameAMD64Degenerate(code); ok {
return Instruction{Addr: addr, Text: text, Len: n}, nil
}
}
return Instruction{Addr: addr, Text: x86asm.GoSyntax(inst, addr, nil), Len: inst.Len}, nil
}
}
// Block decodes up to max instructions from code starting at addr and returns
// them in order. Decoding stops at the end of code or once an instruction
// would run past it.
func Block(a arch.Arch, code []byte, addr uint64, max int) []Instruction {
var out []Instruction
pc := 0
for len(out) < max && pc < len(code) {
ins, err := Decode(a, code[pc:], addr+uint64(pc))
if err != nil {
break
}
if ins.Len <= 0 || pc+ins.Len > len(code) {
break
}
out = append(out, ins)
pc += ins.Len
}
return out
}