663 lines
17 KiB
Go
663 lines
17 KiB
Go
// 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 (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Label is a named address within the debugged function.
|
|
type Label struct {
|
|
Name string
|
|
Offset int // function-relative offset
|
|
}
|
|
|
|
// SourceLine maps a byte offset to a source line number.
|
|
type SourceLine struct {
|
|
Offset int
|
|
Line int
|
|
}
|
|
|
|
// REPL runs the interactive debugger loop. On entry, the debuggee is
|
|
// stopped in the Go runtime (after PTRACE_TRACEME + SIGSTOP). The REPL
|
|
// sets a temporary breakpoint at the function entry, continues to it, and
|
|
// then presents the prompt — so the user starts debugging at the first
|
|
// instruction of the assembled function.
|
|
func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, argsSize int, labels []Label, lines []SourceLine) {
|
|
entryAddr := codeBase + uint64(funcOffset)
|
|
|
|
// The debuggee is already stopped at the function entry point.
|
|
fmt.Printf("stopped at function entry: %#x (%d bytes)\n", entryAddr, funcSize)
|
|
fmt.Println("commands: break <label|addr> | step [n] | continue | disas [n] | regs | where | x <addr> [len] | w <addr> <val...> | labels | quit")
|
|
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
|
|
for {
|
|
fmt.Print("(gasm) ")
|
|
if !scanner.Scan() {
|
|
break
|
|
}
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
parts := strings.Fields(line)
|
|
cmd := parts[0]
|
|
|
|
switch cmd {
|
|
case "q", "quit":
|
|
s.Kill()
|
|
return
|
|
|
|
case "regs":
|
|
regs, err := s.GetRegs()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
printRegs(®s, codeBase, uint64(funcOffset))
|
|
// Also show vector registers.
|
|
vregs, err := s.GetVectorRegs()
|
|
if err != nil {
|
|
fmt.Printf(" (vector regs unavailable: %v)\n", err)
|
|
} else {
|
|
printVectorRegs(&vregs)
|
|
}
|
|
|
|
case "step", "s":
|
|
n := 1
|
|
if len(parts) > 1 {
|
|
n, _ = strconv.Atoi(parts[1])
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
if s.Exited() {
|
|
fmt.Println("debuggee exited")
|
|
break
|
|
}
|
|
if err := s.Step(); err != nil {
|
|
fmt.Println(err)
|
|
break
|
|
}
|
|
}
|
|
if !s.Exited() {
|
|
regs, _ := s.GetRegs()
|
|
text, _, _ := s.Disassemble(regs.RIP)
|
|
fmt.Printf("=> %#x (func+%#x): %s\n", regs.RIP, regs.RIP-codeBase-uint64(funcOffset), text)
|
|
}
|
|
|
|
case "next", "n":
|
|
// Step over: if the current instruction is a CALL, set a
|
|
// breakpoint after it and continue; otherwise single-step.
|
|
regs, _ := s.GetRegs()
|
|
text, instLen, _ := s.Disassemble(regs.RIP)
|
|
if strings.HasPrefix(strings.ToLower(text), "call") {
|
|
// Set a temporary breakpoint after the CALL.
|
|
afterAddr := regs.RIP + uint64(instLen)
|
|
bp, err := bm.Set(afterAddr, "(next)")
|
|
if err != nil {
|
|
fmt.Printf("cannot set next breakpoint: %v\n", err)
|
|
continue
|
|
}
|
|
// Continue until the breakpoint.
|
|
for _, b := range bm.All() {
|
|
bm.Reinsert(b.Addr)
|
|
}
|
|
if err := s.Continue(); err != nil {
|
|
fmt.Println(err)
|
|
bm.Clear(afterAddr)
|
|
continue
|
|
}
|
|
bm.HandleTrap(®s)
|
|
bm.Clear(afterAddr)
|
|
_ = bp
|
|
} else {
|
|
// Not a CALL — just single-step.
|
|
if err := s.Step(); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
}
|
|
if !s.Exited() {
|
|
regs, _ := s.GetRegs()
|
|
text, _, _ := s.Disassemble(regs.RIP)
|
|
fmt.Printf("=> %#x (func+%#x): %s\n", regs.RIP, regs.RIP-codeBase-uint64(funcOffset), text)
|
|
}
|
|
|
|
case "finish", "fin":
|
|
// Run until the current function returns.
|
|
// For NOSPLIT frame=0: return address is at [RSP].
|
|
regs, _ := s.GetRegs()
|
|
retAddr, err := s.Peek(regs.RSP)
|
|
if err != nil {
|
|
fmt.Printf("cannot read return address: %v\n", err)
|
|
continue
|
|
}
|
|
// Set a temporary breakpoint at the return address.
|
|
bp, err := bm.Set(retAddr, "(finish)")
|
|
if err != nil {
|
|
fmt.Printf("cannot set finish breakpoint: %v\n", err)
|
|
continue
|
|
}
|
|
// Continue until the breakpoint.
|
|
for _, b := range bm.All() {
|
|
bm.Reinsert(b.Addr)
|
|
}
|
|
if err := s.Continue(); err != nil {
|
|
fmt.Println(err)
|
|
bm.Clear(retAddr)
|
|
continue
|
|
}
|
|
if !s.Exited() {
|
|
bm.HandleTrap(®s)
|
|
}
|
|
bm.Clear(retAddr)
|
|
_ = bp
|
|
if s.Exited() {
|
|
fmt.Println("debuggee exited")
|
|
} else {
|
|
regs, _ := s.GetRegs()
|
|
fmt.Printf("finished, now at %#x\n", regs.RIP)
|
|
}
|
|
|
|
case "continue", "c":
|
|
if s.Exited() {
|
|
fmt.Println("debuggee exited")
|
|
continue
|
|
}
|
|
// Loop: continue until a breakpoint fires (condition met) or exit.
|
|
for {
|
|
// Re-insert all breakpoints before continuing.
|
|
for _, bp := range bm.All() {
|
|
bm.Reinsert(bp.Addr)
|
|
}
|
|
if err := s.Continue(); err != nil {
|
|
fmt.Println(err)
|
|
break
|
|
}
|
|
if s.Exited() {
|
|
fmt.Println("debuggee exited")
|
|
break
|
|
}
|
|
// Check for watchpoint hits.
|
|
reason, wpAddr := s.StopInfo()
|
|
if reason == StopWatchpoint {
|
|
fmt.Printf("watchpoint hit at %#x\n", wpAddr)
|
|
break
|
|
}
|
|
regs, _ := s.GetRegs()
|
|
if bp := bm.HandleTrap(®s); bp != nil {
|
|
name := bp.Label
|
|
if name == "" {
|
|
name = fmt.Sprintf("%#x", bp.Addr)
|
|
}
|
|
fmt.Printf("breakpoint hit: %s (func+%#x)\n", name, bp.Addr-codeBase-uint64(funcOffset))
|
|
break
|
|
}
|
|
// Condition not met (or single-step trap) — re-insert and continue.
|
|
}
|
|
|
|
case "break", "b":
|
|
if len(parts) < 2 {
|
|
fmt.Println("usage: break <label|addr|line> [if <reg> <op> <val>]")
|
|
continue
|
|
}
|
|
// Try as a line number first.
|
|
var addr uint64
|
|
var label string
|
|
if lineNum, err := strconv.Atoi(parts[1]); err == nil && lineNum > 0 {
|
|
// Find the byte offset for this line.
|
|
off := offsetForLine(lines, lineNum)
|
|
if off < 0 {
|
|
fmt.Printf("no instruction at line %d\n", lineNum)
|
|
continue
|
|
}
|
|
addr = codeBase + uint64(funcOffset) + uint64(off)
|
|
label = fmt.Sprintf("line %d", lineNum)
|
|
} else {
|
|
addr, label = resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
|
|
}
|
|
if addr == 0 {
|
|
fmt.Printf("unknown label, address, or line: %s\n", parts[1])
|
|
continue
|
|
}
|
|
// Parse optional condition: "if <reg> <op> <value>"
|
|
var cond *Condition
|
|
if len(parts) >= 6 && parts[2] == "if" {
|
|
val, err := strconv.ParseUint(parts[5], 0, 64)
|
|
if err != nil {
|
|
fmt.Printf("invalid condition value: %s\n", parts[5])
|
|
continue
|
|
}
|
|
cond = &Condition{Reg: strings.ToLower(parts[3]), Op: parts[4], Value: val}
|
|
} else if len(parts) >= 4 && parts[2] == "if" {
|
|
fmt.Println("usage: break <label|addr> if <reg> <op> <value>")
|
|
continue
|
|
}
|
|
bp, err := bm.SetWithCond(addr, label, cond)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
condStr := ""
|
|
if cond != nil {
|
|
condStr = fmt.Sprintf(" if %s %s %#x", cond.Reg, cond.Op, cond.Value)
|
|
}
|
|
fmt.Printf("breakpoint set: %s at %#x (func+%#x)%s\n", bp.Label, bp.Addr, bp.Addr-codeBase-uint64(funcOffset), condStr)
|
|
|
|
case "info":
|
|
if len(parts) < 2 {
|
|
fmt.Println("usage: info break")
|
|
continue
|
|
}
|
|
switch parts[1] {
|
|
case "break", "breakpoints", "b":
|
|
fmt.Print(bm.Info())
|
|
default:
|
|
fmt.Printf("unknown info target: %s\n", parts[1])
|
|
}
|
|
|
|
case "delete", "d":
|
|
if len(parts) < 2 {
|
|
fmt.Println("usage: delete <label|addr>")
|
|
continue
|
|
}
|
|
addr, _ := resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
|
|
if addr == 0 {
|
|
fmt.Printf("unknown: %s\n", parts[1])
|
|
continue
|
|
}
|
|
if err := bm.Clear(addr); err != nil {
|
|
fmt.Println(err)
|
|
} else {
|
|
fmt.Println("breakpoint removed")
|
|
}
|
|
|
|
case "x":
|
|
regs, _ := s.GetRegs()
|
|
addr := regs.RIP // default: current PC
|
|
length := 64
|
|
if len(parts) > 1 {
|
|
addr, _ = resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
|
|
}
|
|
if len(parts) > 2 {
|
|
length, _ = strconv.Atoi(parts[2])
|
|
}
|
|
mem, err := s.ReadMemory(addr, length)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
hexDump(addr, mem)
|
|
|
|
case "w":
|
|
if len(parts) < 3 {
|
|
fmt.Println("usage: w <addr> <byte|0x...> [byte...]")
|
|
continue
|
|
}
|
|
addr, _ := resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
|
|
if addr == 0 {
|
|
fmt.Printf("unknown address: %s\n", parts[1])
|
|
continue
|
|
}
|
|
var bytes []byte
|
|
for _, arg := range parts[2:] {
|
|
v, err := strconv.ParseUint(arg, 0, 64)
|
|
if err != nil {
|
|
fmt.Printf("invalid value: %s\n", arg)
|
|
continue
|
|
}
|
|
// Write as 8-byte word if it looks like a large value, else single byte.
|
|
if v > 255 {
|
|
for j := 0; j < 8; j++ {
|
|
bytes = append(bytes, byte(v>>(8*j)))
|
|
}
|
|
} else {
|
|
bytes = append(bytes, byte(v))
|
|
}
|
|
}
|
|
if len(bytes) > 0 {
|
|
if err := s.WriteMemory(addr, bytes); err != nil {
|
|
fmt.Println(err)
|
|
} else {
|
|
fmt.Printf("wrote %d bytes at %#x\n", len(bytes), addr)
|
|
}
|
|
}
|
|
|
|
case "set":
|
|
if len(parts) < 3 {
|
|
fmt.Println("usage: set <reg> <value>")
|
|
continue
|
|
}
|
|
val, err := strconv.ParseUint(parts[2], 0, 64)
|
|
if err != nil {
|
|
fmt.Printf("invalid value: %s\n", parts[2])
|
|
continue
|
|
}
|
|
if err := s.SetReg(strings.ToLower(parts[1]), val); err != nil {
|
|
fmt.Printf("set: %v\n", err)
|
|
} else {
|
|
fmt.Printf("%s = %#x\n", parts[1], val)
|
|
}
|
|
|
|
case "labels", "l":
|
|
sorted := make([]Label, len(labels))
|
|
copy(sorted, labels)
|
|
sort.Slice(sorted, func(i, j int) bool { return sorted[i].Offset < sorted[j].Offset })
|
|
for _, l := range sorted {
|
|
fmt.Printf(" func+%#04x %s\n", l.Offset, l.Name)
|
|
}
|
|
|
|
case "disas", "u":
|
|
n := 5
|
|
if len(parts) > 1 {
|
|
n, _ = strconv.Atoi(parts[1])
|
|
if n <= 0 {
|
|
n = 5
|
|
}
|
|
}
|
|
regs, _ := s.GetRegs()
|
|
fmt.Print(s.DisassembleN(regs.RIP, n))
|
|
|
|
case "where":
|
|
regs, _ := s.GetRegs()
|
|
funcOff := int(regs.RIP - codeBase - uint64(funcOffset))
|
|
line := lineAt(lines, funcOff)
|
|
label := nearestLabel(labels, funcOff)
|
|
fmt.Printf(" func+%#x", funcOff)
|
|
if label != "" {
|
|
fmt.Printf(" (near %s)", label)
|
|
}
|
|
if line > 0 {
|
|
fmt.Printf(" line %d", line)
|
|
}
|
|
fmt.Println()
|
|
|
|
case "help", "h", "?":
|
|
fmt.Println(` break <label|addr> [if <reg> <op> <val>] set a breakpoint
|
|
delete <label|addr> remove a breakpoint
|
|
info break list all breakpoints
|
|
watch <addr> [r|w] [size] set a hardware watchpoint (write by default)
|
|
unwatch [<slot>] clear one or all watchpoints
|
|
step [n], s single-step n instructions
|
|
next, n step over CALL
|
|
continue, c run until breakpoint or exit
|
|
disas [n], u disassemble n instructions at PC
|
|
regs print registers and RFLAGS
|
|
where show source line and nearest label
|
|
stack show stack near RSP (args + return address)
|
|
x [addr] [len] hex-dump memory
|
|
w <addr> <val...> write bytes to memory
|
|
labels, l list function labels
|
|
help, h, ? this help
|
|
quit, q kill debuggee and exit`)
|
|
|
|
case "stack":
|
|
regs, _ := s.GetRegs()
|
|
// For NOSPLIT frame=0: [RSP] = return address, [RSP+8..] = args.
|
|
retAddr, _ := s.Peek(regs.RSP)
|
|
fmt.Printf(" [RSP] return addr = %#x\n", retAddr)
|
|
if argsSize > 0 {
|
|
fmt.Printf(" args (%d bytes at RSP+8):\n", argsSize)
|
|
argBytes, err := s.ReadMemory(regs.RSP+8, argsSize)
|
|
if err == nil {
|
|
for i := 0; i < argsSize; i += 8 {
|
|
var v uint64
|
|
for j := 0; j < 8 && i+j < len(argBytes); j++ {
|
|
v |= uint64(argBytes[i+j]) << (8 * j)
|
|
}
|
|
fmt.Printf(" [%+3d] %#016x\n", i+8, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
case "bt", "backtrace":
|
|
regs, _ := s.GetRegs()
|
|
funcOff := int(regs.RIP - codeBase - uint64(funcOffset))
|
|
line := lineAt(lines, funcOff)
|
|
label := nearestLabel(labels, funcOff)
|
|
fmt.Printf(" #0 func+%#x", funcOff)
|
|
if label != "" {
|
|
fmt.Printf(" (%s)", label)
|
|
}
|
|
if line > 0 {
|
|
fmt.Printf(" [line %d]", line)
|
|
}
|
|
fmt.Println()
|
|
retAddr, _ := s.Peek(regs.RSP)
|
|
fmt.Printf(" #1 return to %#x\n", retAddr)
|
|
|
|
case "watch":
|
|
if len(parts) < 2 {
|
|
fmt.Println("usage: watch <addr> [r|w] [size]")
|
|
continue
|
|
}
|
|
addr, _ := resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
|
|
if addr == 0 {
|
|
fmt.Printf("unknown address: %s\n", parts[1])
|
|
continue
|
|
}
|
|
typ := WatchWrite
|
|
size := 8
|
|
if len(parts) > 2 {
|
|
switch parts[2] {
|
|
case "r":
|
|
typ = WatchRead
|
|
case "w":
|
|
typ = WatchWrite
|
|
}
|
|
}
|
|
if len(parts) > 3 {
|
|
size, _ = strconv.Atoi(parts[3])
|
|
}
|
|
slot := s.FindFreeWatchpointSlot()
|
|
if slot < 0 {
|
|
fmt.Println("no free watchpoint slots (use 'unwatch <slot>' to clear one)")
|
|
continue
|
|
}
|
|
if err := s.SetWatchpoint(slot, addr, typ, size); err != nil {
|
|
fmt.Printf("watch: %v\n", err)
|
|
} else {
|
|
typStr := "w"
|
|
if typ == WatchRead {
|
|
typStr = "r"
|
|
}
|
|
fmt.Printf("watchpoint %d set: %#x (%s, %d bytes)\n", slot, addr, typStr, size)
|
|
}
|
|
|
|
case "unwatch":
|
|
if len(parts) >= 2 {
|
|
slot, err := strconv.Atoi(parts[1])
|
|
if err != nil || slot < 0 || slot > 3 {
|
|
fmt.Println("usage: unwatch [<slot>]")
|
|
continue
|
|
}
|
|
if err := s.ClearWatchpoint(slot); err != nil {
|
|
fmt.Printf("unwatch: %v\n", err)
|
|
} else {
|
|
fmt.Printf("watchpoint %d cleared\n", slot)
|
|
}
|
|
} else {
|
|
if err := s.ClearAllWatchpoints(); err != nil {
|
|
fmt.Printf("unwatch: %v\n", err)
|
|
} else {
|
|
fmt.Println("all watchpoints cleared")
|
|
}
|
|
}
|
|
|
|
default:
|
|
fmt.Printf("unknown command: %s\n", cmd)
|
|
}
|
|
}
|
|
s.Kill()
|
|
}
|
|
|
|
func printRegs(regs *Regs, codeBase, funcOff uint64) {
|
|
fmt.Printf(" RIP = %#016x (func+%#x)\n", regs.RIP, regs.RIP-codeBase-funcOff)
|
|
fmt.Printf(" RSP = %#016x RBP = %#016x\n", regs.RSP, regs.RBP)
|
|
fmt.Printf(" RAX = %#016x RBX = %#016x\n", regs.RAX, regs.RBX)
|
|
fmt.Printf(" RCX = %#016x RDX = %#016x\n", regs.RCX, regs.RDX)
|
|
fmt.Printf(" RSI = %#016x RDI = %#016x\n", regs.RSI, regs.RDI)
|
|
fmt.Printf(" R8 = %#016x R9 = %#016x\n", regs.R8, regs.R9)
|
|
fmt.Printf(" R10 = %#016x R11 = %#016x\n", regs.R10, regs.R11)
|
|
fmt.Printf(" R12 = %#016x R13 = %#016x\n", regs.R12, regs.R13)
|
|
fmt.Printf(" R14 = %#016x R15 = %#016x\n", regs.R14, regs.R15)
|
|
fmt.Printf(" RFLAGS = %#x [%s]\n", regs.RFLAGS, decodeRflags(regs.RFLAGS))
|
|
}
|
|
|
|
// printVectorRegs displays the YMM registers.
|
|
func printVectorRegs(v *VectorRegs) {
|
|
fmt.Println("\n Vector registers (YMM):")
|
|
for i := 0; i < 16; i += 2 {
|
|
fmt.Printf(" YMM%-2d = ", i)
|
|
printYMM(v.YMM[i][:])
|
|
fmt.Printf(" YMM%-2d = ", i+1)
|
|
printYMM(v.YMM[i+1][:])
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
func printYMM(b []byte) {
|
|
// Show as 8 32-bit values.
|
|
for j := 0; j < 32; j += 4 {
|
|
v := uint32(b[j]) | uint32(b[j+1])<<8 | uint32(b[j+2])<<16 | uint32(b[j+3])<<24
|
|
fmt.Printf("%08x ", v)
|
|
}
|
|
}
|
|
|
|
func decodeRflags(f uint64) string {
|
|
var flags string
|
|
if f&1 != 0 {
|
|
flags += "CF "
|
|
}
|
|
if f&(1<<2) != 0 {
|
|
flags += "PF "
|
|
}
|
|
if f&(1<<4) != 0 {
|
|
flags += "AF "
|
|
}
|
|
if f&(1<<6) != 0 {
|
|
flags += "ZF "
|
|
}
|
|
if f&(1<<7) != 0 {
|
|
flags += "SF "
|
|
}
|
|
if f&(1<<8) != 0 {
|
|
flags += "TF "
|
|
}
|
|
if f&(1<<9) != 0 {
|
|
flags += "IF "
|
|
}
|
|
if f&(1<<10) != 0 {
|
|
flags += "DF "
|
|
}
|
|
if f&(1<<11) != 0 {
|
|
flags += "OF "
|
|
}
|
|
if flags == "" {
|
|
return "none"
|
|
}
|
|
return flags[:len(flags)-1] // trim trailing space
|
|
}
|
|
|
|
func hexDump(addr uint64, data []byte) {
|
|
for i := 0; i < len(data); i += 16 {
|
|
end := i + 16
|
|
if end > len(data) {
|
|
end = len(data)
|
|
}
|
|
fmt.Printf(" %#08x:", addr+uint64(i))
|
|
for j := i; j < i+16; j++ {
|
|
if j < end {
|
|
fmt.Printf(" %02x", data[j])
|
|
} else {
|
|
fmt.Print(" ")
|
|
}
|
|
}
|
|
fmt.Print(" ")
|
|
for j := i; j < end; j++ {
|
|
if data[j] >= 0x20 && data[j] < 0x7f {
|
|
fmt.Printf("%c", data[j])
|
|
} else {
|
|
fmt.Print(".")
|
|
}
|
|
}
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
func resolveAddr(s string, codeBase, funcOff uint64, labels []Label) (uint64, string) {
|
|
// Try as a hex address.
|
|
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
|
|
v, err := strconv.ParseUint(s, 0, 64)
|
|
if err == nil {
|
|
return v, ""
|
|
}
|
|
}
|
|
// Try as func+offset.
|
|
if strings.HasPrefix(s, "+") {
|
|
off, err := strconv.ParseUint(s[1:], 0, 64)
|
|
if err == nil {
|
|
return codeBase + funcOff + off, fmt.Sprintf("func+%#x", off)
|
|
}
|
|
}
|
|
// Try as a label name.
|
|
for _, l := range labels {
|
|
if l.Name == s {
|
|
return codeBase + funcOff + uint64(l.Offset), l.Name
|
|
}
|
|
}
|
|
return 0, ""
|
|
}
|
|
|
|
// lineAt returns the source line for a given function-relative offset.
|
|
func lineAt(lines []SourceLine, offset int) int {
|
|
if len(lines) == 0 {
|
|
return 0
|
|
}
|
|
lo, hi := 0, len(lines)-1
|
|
for lo < hi {
|
|
mid := (lo + hi + 1) / 2
|
|
if lines[mid].Offset <= offset {
|
|
lo = mid
|
|
} else {
|
|
hi = mid - 1
|
|
}
|
|
}
|
|
if lines[lo].Offset <= offset {
|
|
return lines[lo].Line
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// offsetForLine returns the byte offset for a given source line number.
|
|
// Returns -1 if no instruction is at that line.
|
|
func offsetForLine(lines []SourceLine, line int) int {
|
|
for _, le := range lines {
|
|
if le.Line == line {
|
|
return le.Offset
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// nearestLabel returns the name of the label at or just before the offset.
|
|
func nearestLabel(labels []Label, offset int) string {
|
|
best := ""
|
|
bestOff := -1
|
|
for _, l := range labels {
|
|
if l.Offset <= offset && l.Offset > bestOff {
|
|
best = l.Name
|
|
bestOff = l.Offset
|
|
}
|
|
}
|
|
return best
|
|
}
|