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 }