Files
gasm-sdk/disasm/disasm.go
T

96 lines
3.0 KiB
Go
Raw Permalink Normal View History

// 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-devkit/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
}
return Instruction{Addr: addr, Text: x86asm.IntelSyntax(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
}