// Copyright (c) 2026 Petr BalvĂ­n (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 }