arm64asm rejects three exception-space words the toolchain's corpus assembles: the hypervisor call HVC, the secure monitor call SMC and the speculation barrier SB. The supplementary naming table reads the raw word in the error branch and renders the corpus spellings; the corpus rows are pinned in the parity fixture and the boundary test holds the unallocated neighbours on the placeholder. Assisted-by: GLM 5.3
118 lines
4.0 KiB
Go
118 lines
4.0 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 {
|
|
// A few exception-space words the decoder refuses outright
|
|
// (HVC, SMC, SB) live in the supplementary naming table;
|
|
// anything else keeps the placeholder.
|
|
if text, n, ok := nameARM64Rejected(code); ok {
|
|
return Instruction{Addr: addr, Text: text, Len: n}, 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 {
|
|
// A few named families the decoder refuses outright live in
|
|
// the same supplementary table; anything else keeps the
|
|
// placeholder.
|
|
if text, n, ok := nameAMD64Rejected(code); ok {
|
|
return Instruction{Addr: addr, Text: text, Len: n}, 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
|
|
}
|