370 lines
11 KiB
Go
370 lines
11 KiB
Go
![]() |
package proc
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"github.com/go-delve/delve/pkg/dwarf/frame"
|
||
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// I386 represents the Intel386 CPU architecture.
|
||
|
type I386 struct {
|
||
|
gStructOffset uint64
|
||
|
goos string
|
||
|
|
||
|
// crosscall2fn is the DIE of crosscall2, a function used by the go runtime
|
||
|
// to call C functions. This function in go 1.9 (and previous versions) had
|
||
|
// a bad frame descriptor which needs to be fixed to generate good stack
|
||
|
// traces.
|
||
|
crosscall2fn *Function
|
||
|
|
||
|
// sigreturnfn is the DIE of runtime.sigreturn, the return trampoline for
|
||
|
// the signal handler. See comment in FixFrameUnwindContext for a
|
||
|
// description of why this is needed.
|
||
|
sigreturnfn *Function
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
i386DwarfIPRegNum uint64 = 8
|
||
|
i386DwarfSPRegNum uint64 = 4
|
||
|
i386DwarfBPRegNum uint64 = 5
|
||
|
)
|
||
|
|
||
|
var i386BreakInstruction = []byte{0xCC}
|
||
|
|
||
|
// I386Arch returns an initialized I386Arch
|
||
|
// struct.
|
||
|
func I386Arch(goos string) *I386 {
|
||
|
return &I386{
|
||
|
goos: goos,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// PtrSize returns the size of a pointer
|
||
|
// on this architecture.
|
||
|
func (i *I386) PtrSize() int {
|
||
|
return 4
|
||
|
}
|
||
|
|
||
|
// MaxInstructionLength returns the maximum length of an instruction.
|
||
|
func (i *I386) MaxInstructionLength() int {
|
||
|
return 15
|
||
|
}
|
||
|
|
||
|
// BreakpointInstruction returns the Breakpoint
|
||
|
// instruction for this architecture.
|
||
|
func (i *I386) BreakpointInstruction() []byte {
|
||
|
return i386BreakInstruction
|
||
|
}
|
||
|
|
||
|
// BreakInstrMovesPC returns whether the
|
||
|
// breakpoint instruction will change the value
|
||
|
// of PC after being executed
|
||
|
func (i *I386) BreakInstrMovesPC() bool {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// BreakpointSize returns the size of the
|
||
|
// breakpoint instruction on this architecture.
|
||
|
func (i *I386) BreakpointSize() int {
|
||
|
return len(i386BreakInstruction)
|
||
|
}
|
||
|
|
||
|
// TODO, Not sure, always return false for now. Need to test on windows.
|
||
|
func (i *I386) DerefTLS() bool {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// FixFrameUnwindContext adds default architecture rules to fctxt or returns
|
||
|
// the default frame unwind context if fctxt is nil.
|
||
|
func (i *I386) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext {
|
||
|
if i.sigreturnfn == nil {
|
||
|
i.sigreturnfn = bi.LookupFunc["runtime.sigreturn"]
|
||
|
}
|
||
|
|
||
|
if fctxt == nil || (i.sigreturnfn != nil && pc >= i.sigreturnfn.Entry && pc < i.sigreturnfn.End) {
|
||
|
// When there's no frame descriptor entry use BP (the frame pointer) instead
|
||
|
// - return register is [bp + i.PtrSize()] (i.e. [cfa-i.PtrSize()])
|
||
|
// - cfa is bp + i.PtrSize()*2
|
||
|
// - bp is [bp] (i.e. [cfa-i.PtrSize()*2])
|
||
|
// - sp is cfa
|
||
|
|
||
|
// When the signal handler runs it will move the execution to the signal
|
||
|
// handling stack (installed using the sigaltstack system call).
|
||
|
// This isn't i proper stack switch: the pointer to g in TLS will still
|
||
|
// refer to whatever g was executing on that thread before the signal was
|
||
|
// received.
|
||
|
// Since go did not execute i stack switch the previous value of sp, pc
|
||
|
// and bp is not saved inside g.sched, as it normally would.
|
||
|
// The only way to recover is to either read sp/pc from the signal context
|
||
|
// parameter (the ucontext_t* parameter) or to unconditionally follow the
|
||
|
// frame pointer when we get to runtime.sigreturn (which is what we do
|
||
|
// here).
|
||
|
|
||
|
return &frame.FrameContext{
|
||
|
RetAddrReg: i386DwarfIPRegNum,
|
||
|
Regs: map[uint64]frame.DWRule{
|
||
|
i386DwarfIPRegNum: frame.DWRule{
|
||
|
Rule: frame.RuleOffset,
|
||
|
Offset: int64(-i.PtrSize()),
|
||
|
},
|
||
|
i386DwarfBPRegNum: frame.DWRule{
|
||
|
Rule: frame.RuleOffset,
|
||
|
Offset: int64(-2 * i.PtrSize()),
|
||
|
},
|
||
|
i386DwarfSPRegNum: frame.DWRule{
|
||
|
Rule: frame.RuleValOffset,
|
||
|
Offset: 0,
|
||
|
},
|
||
|
},
|
||
|
CFA: frame.DWRule{
|
||
|
Rule: frame.RuleCFA,
|
||
|
Reg: i386DwarfBPRegNum,
|
||
|
Offset: int64(2 * i.PtrSize()),
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if i.crosscall2fn == nil {
|
||
|
i.crosscall2fn = bi.LookupFunc["crosscall2"]
|
||
|
}
|
||
|
|
||
|
// TODO(chainhelen), need to check whether there is a bad frame descriptor like amd64.
|
||
|
// crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_386.s.
|
||
|
if i.crosscall2fn != nil && pc >= i.crosscall2fn.Entry && pc < i.crosscall2fn.End {
|
||
|
rule := fctxt.CFA
|
||
|
fctxt.CFA = rule
|
||
|
}
|
||
|
|
||
|
// We assume that EBP is the frame pointer and we want to keep it updated,
|
||
|
// so that we can use it to unwind the stack even when we encounter frames
|
||
|
// without descriptor entries.
|
||
|
// If there isn't i rule already we emit one.
|
||
|
if fctxt.Regs[i386DwarfBPRegNum].Rule == frame.RuleUndefined {
|
||
|
fctxt.Regs[i386DwarfBPRegNum] = frame.DWRule{
|
||
|
Rule: frame.RuleFramePointer,
|
||
|
Reg: i386DwarfBPRegNum,
|
||
|
Offset: 0,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fctxt
|
||
|
}
|
||
|
|
||
|
// SwitchStack will use the current frame to determine if it's time to
|
||
|
// switch between the system stack and the goroutine stack or vice versa.
|
||
|
// Sets it.atend when the top of the stack is reached.
|
||
|
func (i *I386) SwitchStack(it *stackIterator, _ *op.DwarfRegisters) bool {
|
||
|
if it.frame.Current.Fn == nil {
|
||
|
return false
|
||
|
}
|
||
|
switch it.frame.Current.Fn.Name {
|
||
|
case "runtime.asmcgocall", "runtime.cgocallback_gofunc": // TODO(chainhelen), need to support cgo stacktraces.
|
||
|
return false
|
||
|
case "runtime.goexit", "runtime.rt0_go", "runtime.mcall":
|
||
|
// Look for "top of stack" functions.
|
||
|
it.atend = true
|
||
|
return true
|
||
|
|
||
|
case "runtime.mstart":
|
||
|
// Calls to runtime.systemstack will switch to the systemstack then:
|
||
|
// 1. alter the goroutine stack so that it looks like systemstack_switch
|
||
|
// was called
|
||
|
// 2. alter the system stack so that it looks like the bottom-most frame
|
||
|
// belongs to runtime.mstart
|
||
|
// If we find a runtime.mstart frame on the system stack of a goroutine
|
||
|
// parked on runtime.systemstack_switch we assume runtime.systemstack was
|
||
|
// called and continue tracing from the parked position.
|
||
|
|
||
|
if it.top || !it.systemstack || it.g == nil {
|
||
|
return false
|
||
|
}
|
||
|
if fn := it.bi.PCToFunc(it.g.PC); fn == nil || fn.Name != "runtime.systemstack_switch" {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
it.switchToGoroutineStack()
|
||
|
return true
|
||
|
|
||
|
default:
|
||
|
if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") && it.frame.Current.Fn.Name != "runtime.fatalthrow" {
|
||
|
// The runtime switches to the system stack in multiple places.
|
||
|
// This usually happens through a call to runtime.systemstack but there
|
||
|
// are functions that switch to the system stack manually (for example
|
||
|
// runtime.morestack).
|
||
|
// Since we are only interested in printing the system stack for cgo
|
||
|
// calls we switch directly to the goroutine stack if we detect that the
|
||
|
// function at the top of the stack is a runtime function.
|
||
|
//
|
||
|
// The function "runtime.fatalthrow" is deliberately excluded from this
|
||
|
// because it can end up in the stack during a cgo call and switching to
|
||
|
// the goroutine stack will exclude all the C functions from the stack
|
||
|
// trace.
|
||
|
it.switchToGoroutineStack()
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// RegSize returns the size (in bytes) of register regnum.
|
||
|
// The mapping between hardware registers and DWARF registers is specified
|
||
|
// in the System V ABI Intel386 Architecture Processor Supplement page 25,
|
||
|
// table 2.14
|
||
|
// https://www.uclibc.org/docs/psABI-i386.pdf
|
||
|
func (i *I386) RegSize(regnum uint64) int {
|
||
|
// XMM registers
|
||
|
if regnum >= 21 && regnum <= 36 {
|
||
|
return 16
|
||
|
}
|
||
|
// x87 registers
|
||
|
if regnum >= 11 && regnum <= 18 {
|
||
|
return 10
|
||
|
}
|
||
|
return 4
|
||
|
}
|
||
|
|
||
|
// The mapping between hardware registers and DWARF registers is specified
|
||
|
// in the System V ABI Intel386 Architecture Processor Supplement page 25,
|
||
|
// table 2.14
|
||
|
// https://www.uclibc.org/docs/psABI-i386.pdf
|
||
|
|
||
|
var i386DwarfToName = map[int]string{
|
||
|
0: "Eax",
|
||
|
1: "Ecx",
|
||
|
2: "Edx",
|
||
|
3: "Ebx",
|
||
|
4: "Esp",
|
||
|
5: "Ebp",
|
||
|
6: "Esi",
|
||
|
7: "Edi",
|
||
|
8: "Eip",
|
||
|
9: "Eflags",
|
||
|
11: "ST(0)",
|
||
|
12: "ST(1)",
|
||
|
13: "ST(2)",
|
||
|
14: "ST(3)",
|
||
|
15: "ST(4)",
|
||
|
16: "ST(5)",
|
||
|
17: "ST(6)",
|
||
|
18: "ST(7)",
|
||
|
21: "XMM0",
|
||
|
22: "XMM1",
|
||
|
23: "XMM2",
|
||
|
24: "XMM3",
|
||
|
25: "XMM4",
|
||
|
26: "XMM5",
|
||
|
27: "XMM6",
|
||
|
28: "XMM7",
|
||
|
40: "Es",
|
||
|
41: "Cs",
|
||
|
42: "Ss",
|
||
|
43: "Ds",
|
||
|
44: "Fs",
|
||
|
45: "Gs",
|
||
|
}
|
||
|
|
||
|
var i386NameToDwarf = func() map[string]int {
|
||
|
r := make(map[string]int)
|
||
|
for regNum, regName := range i386DwarfToName {
|
||
|
r[strings.ToLower(regName)] = regNum
|
||
|
}
|
||
|
r["eflags"] = 9
|
||
|
r["st0"] = 11
|
||
|
r["st1"] = 12
|
||
|
r["st2"] = 13
|
||
|
r["st3"] = 14
|
||
|
r["st4"] = 15
|
||
|
r["st5"] = 16
|
||
|
r["st6"] = 17
|
||
|
r["st7"] = 18
|
||
|
return r
|
||
|
}()
|
||
|
|
||
|
func maxI386DwarfRegister() int {
|
||
|
max := int(i386DwarfIPRegNum)
|
||
|
for i := range i386DwarfToName {
|
||
|
if i > max {
|
||
|
max = i
|
||
|
}
|
||
|
}
|
||
|
return max
|
||
|
}
|
||
|
|
||
|
func (i *I386) RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.DwarfRegisters {
|
||
|
dregs := make([]*op.DwarfRegister, maxI386DwarfRegister()+1)
|
||
|
|
||
|
for _, reg := range regs.Slice(true) {
|
||
|
if dwarfReg, ok := i386NameToDwarf[strings.ToLower(reg.Name)]; ok {
|
||
|
dregs[dwarfReg] = reg.Reg
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return op.DwarfRegisters{
|
||
|
StaticBase: staticBase,
|
||
|
Regs: dregs,
|
||
|
ByteOrder: binary.LittleEndian,
|
||
|
PCRegNum: i386DwarfIPRegNum,
|
||
|
SPRegNum: i386DwarfSPRegNum,
|
||
|
BPRegNum: i386DwarfBPRegNum,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AddrAndStackRegsToDwarfRegisters returns DWARF registers from the passed in
|
||
|
// PC, SP, and BP registers in the format used by the DWARF expression interpreter.
|
||
|
func (i *I386) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint64) op.DwarfRegisters {
|
||
|
dregs := make([]*op.DwarfRegister, i386DwarfIPRegNum+1)
|
||
|
dregs[i386DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc)
|
||
|
dregs[i386DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp)
|
||
|
dregs[i386DwarfBPRegNum] = op.DwarfRegisterFromUint64(bp)
|
||
|
|
||
|
return op.DwarfRegisters{
|
||
|
StaticBase: staticBase,
|
||
|
Regs: dregs,
|
||
|
ByteOrder: binary.LittleEndian,
|
||
|
PCRegNum: i386DwarfIPRegNum,
|
||
|
SPRegNum: i386DwarfSPRegNum,
|
||
|
BPRegNum: i386DwarfBPRegNum,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (i *I386) DwarfRegisterToString(j int, reg *op.DwarfRegister) (name string, floatingPoint bool, repr string) {
|
||
|
name, ok := i386DwarfToName[j]
|
||
|
if !ok {
|
||
|
name = fmt.Sprintf("unknown%d", j)
|
||
|
}
|
||
|
|
||
|
switch n := strings.ToLower(name); n {
|
||
|
case "eflags":
|
||
|
return name, false, eflagsDescription.Describe(reg.Uint64Val, 32)
|
||
|
|
||
|
case "tw", "fop":
|
||
|
return name, true, fmt.Sprintf("%#04x", reg.Uint64Val)
|
||
|
|
||
|
default:
|
||
|
if reg.Bytes != nil && strings.HasPrefix(name, "xmm") {
|
||
|
return name, true, formatSSEReg(reg.Bytes)
|
||
|
} else if reg.Bytes != nil && strings.HasPrefix(name, "st(") {
|
||
|
return name, true, formatX87Reg(reg.Bytes)
|
||
|
} else if reg.Bytes == nil || (reg.Bytes != nil && len(reg.Bytes) <= 8) {
|
||
|
return name, false, fmt.Sprintf("%#016x", reg.Uint64Val)
|
||
|
} else {
|
||
|
return name, false, fmt.Sprintf("%#x", reg.Bytes)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// InhibitStepInto returns whether StepBreakpoint can be set at pc.
|
||
|
// When cgo or pie on 386 linux, compiler will insert more instructions (ex: call __x86.get_pc_thunk.).
|
||
|
// StepBreakpoint shouldn't be set on __x86.get_pc_thunk and skip it.
|
||
|
// See comments on stacksplit in $GOROOT/src/cmd/internal/obj/x86/obj6.go for generated instructions details.
|
||
|
func (i *I386) InhibitStepInto(bi *BinaryInfo, pc uint64) bool {
|
||
|
if bi.SymNames != nil && bi.SymNames[pc] != nil &&
|
||
|
strings.HasPrefix(bi.SymNames[pc].Name, "__x86.get_pc_thunk.") {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|