2026-08-01 09:47:00 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
//go:build linux && amd64
|
|
|
|
|
|
|
|
|
|
package debug
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2026-08-29 15:40:31 +02:00
|
|
|
"strings"
|
2026-08-01 09:47:00 +02:00
|
|
|
|
2026-09-26 11:08:43 +02:00
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/disasm"
|
2026-08-01 09:47:00 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Disassemble decodes the instruction at the given address in the debuggee's
|
2026-10-02 00:40:54 +02:00
|
|
|
// memory and returns its text representation and length in bytes. An amd64
|
|
|
|
|
// instruction is up to 15 bytes long, but the read must not reach past the
|
|
|
|
|
// end of the mapping: when the full 15-byte window crosses into unmapped
|
|
|
|
|
// memory the window shrinks, because an instruction at the mapping's end is
|
|
|
|
|
// by construction no longer than the readable bytes that hold it.
|
2026-08-01 09:47:00 +02:00
|
|
|
func (s *Session) Disassemble(addr uint64) (string, int, error) {
|
2026-10-02 00:40:54 +02:00
|
|
|
var lastErr error
|
|
|
|
|
for _, n := range []int{15, 8, 4, 2, 1} {
|
|
|
|
|
mem, err := s.ReadMemory(addr, n)
|
|
|
|
|
if err != nil {
|
|
|
|
|
lastErr = err
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
ins, derr := disasm.Decode(arch.AMD64, mem, addr)
|
|
|
|
|
if derr != nil {
|
|
|
|
|
return "", 0, derr
|
|
|
|
|
}
|
|
|
|
|
return ins.Text, ins.Len, nil
|
2026-08-01 09:47:00 +02:00
|
|
|
}
|
2026-10-02 00:40:54 +02:00
|
|
|
return "", 0, lastErr
|
2026-08-01 09:47:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DisassembleN decodes up to n instructions starting at addr and returns
|
|
|
|
|
// them as a formatted string with addresses and byte offsets.
|
|
|
|
|
func (s *Session) DisassembleN(addr uint64, n int) string {
|
2026-08-29 15:40:31 +02:00
|
|
|
var result strings.Builder
|
2026-08-01 09:47:00 +02:00
|
|
|
pc := addr
|
2026-08-29 15:25:15 +02:00
|
|
|
for range n {
|
2026-08-01 09:47:00 +02:00
|
|
|
text, length, err := s.Disassemble(pc)
|
|
|
|
|
if err != nil {
|
2026-08-29 15:40:31 +02:00
|
|
|
result.WriteString(fmt.Sprintf(" %#08x: <error: %v>\n", pc, err))
|
2026-08-01 09:47:00 +02:00
|
|
|
break
|
|
|
|
|
}
|
2026-08-29 15:40:31 +02:00
|
|
|
result.WriteString(fmt.Sprintf(" %#08x: %s\n", pc, text))
|
2026-08-01 09:47:00 +02:00
|
|
|
if length == 0 {
|
|
|
|
|
length = 1
|
|
|
|
|
}
|
|
|
|
|
pc += uint64(length)
|
|
|
|
|
}
|
2026-08-29 15:40:31 +02:00
|
|
|
return result.String()
|
2026-08-01 09:47:00 +02:00
|
|
|
}
|
2026-09-19 23:49:19 +02:00
|
|
|
|
|
|
|
|
// isCallInsn reports whether disassembled text (x86asm.IntelSyntax) is a
|
|
|
|
|
// call. The first token must match exactly: a prefix test would also catch
|
|
|
|
|
// unrelated mnemonics.
|
|
|
|
|
func isCallInsn(text string) bool {
|
|
|
|
|
m, _, _ := strings.Cut(text, " ")
|
|
|
|
|
return strings.ToLower(m) == "call"
|
|
|
|
|
}
|