// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause //go:build linux && amd64 package debug import "fmt" 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)) } 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) { 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] } // archReturnAddr reads the return address of the current frame (amd64 // ABI0 convention). A function that contains a CALL (or has a frame) is // assembled with the prologue PUSHQ BP; MOVQ SP, BP, so mid-function the // word at SP is the saved caller BP, a stack address, and the return // address sits further up. Walk the stack from SP and take the first word // that lies in an executable mapping: stack and data words never do, a // return address always does. func archReturnAddr(s *Session, regs *Regs) (uint64, error) { ranges := execRanges(s.pid) for off := uint64(0); off < 512; off += 8 { word, err := s.Peek(regs.RSP + off) if err != nil { break } for _, r := range ranges { if word >= r.lo && word < r.hi { return word, nil } } } // No mapping available or nothing code-like on the stack: fall back to // the raw entry convention, [SP] before any push. return s.Peek(regs.RSP) } // archSPLabel returns the SP register name for display. func archSPLabel() string { return "RSP" }