2026-09-14 18:47:08 +02:00
|
|
|
// 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"
|
|
|
|
|
|
2026-09-26 11:08:43 +02:00
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
2026-09-14 18:47:08 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|