From 46de2b469f384d7ce86644c7f1f4d2a675b79b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20S=C3=A1ez?= Date: Wed, 19 Jan 2022 10:19:33 +0100 Subject: [PATCH] WIP: Add ppc64le support --- pkg/dwarf/regnum/ppc64le.go | 73 +++++++ pkg/proc/bininfo.go | 7 + pkg/proc/linutil/regs_ppc64le_arch.go | 171 +++++++++++++++ pkg/proc/native/hwbreak_ppc64le.go | 23 +++ pkg/proc/native/proc.go | 2 + pkg/proc/native/proc_linux.go | 4 +- pkg/proc/native/ptrace_linux_64bit.go | 4 +- pkg/proc/native/registers_linux_ppc64le.go | 105 ++++++++++ pkg/proc/native/support_sentinel.go | 4 +- pkg/proc/native/threads_linux_ppc64le.go | 24 +++ pkg/proc/ppc64le_arch.go | 229 +++++++++++++++++++++ pkg/proc/ppc64le_disasm.go | 154 ++++++++++++++ pkg/proc/stack.go | 2 + pkg/proc/threads.go | 1 - 14 files changed, 796 insertions(+), 7 deletions(-) create mode 100644 pkg/dwarf/regnum/ppc64le.go create mode 100644 pkg/proc/linutil/regs_ppc64le_arch.go create mode 100644 pkg/proc/native/hwbreak_ppc64le.go create mode 100644 pkg/proc/native/registers_linux_ppc64le.go create mode 100644 pkg/proc/native/threads_linux_ppc64le.go create mode 100644 pkg/proc/ppc64le_arch.go create mode 100644 pkg/proc/ppc64le_disasm.go diff --git a/pkg/dwarf/regnum/ppc64le.go b/pkg/dwarf/regnum/ppc64le.go new file mode 100644 index 0000000000..555fd99f6a --- /dev/null +++ b/pkg/dwarf/regnum/ppc64le.go @@ -0,0 +1,73 @@ +package regnum + +import "fmt" + +const ( + PPC64LE_R0 = 0 // General Purpose Registers: from R0 to R31 + PPC64LE_F0 = 0 // Floating point registers: from F0 to F31 + PPC64LE_V0 = 0 // Vector (Altivec/VMX) registers: from V0 to V31 + PPC64LE_VS0 = 0 // Vector Scalar (VSX) registers: from VS0 to VS63 + PPC64LE_CR0 = 0 // Condition Registers: from CR0 to CR7 + PPC64LE_SP = 1 // Stack frame pointer: Gpr[1] + PPC64LE_PC = 12 // The documentation refers to this as the CIA (Current Instruction Address) + PPC64LE_BP = 30 // TODO(alexsaezm) No idea where the BP is + PPC64LE_LR = 65 // TODO(alexsaezm) What is this? +) + +// PPC64LEToName TODO(alexsaezm) Verify why I have this method, do I need it? +func PPC64LEToName(num uint64) string { + switch { + case num <= 31: + return fmt.Sprintf("R%d", num) + case num <= 62: + return fmt.Sprintf("F%d", num) + case num == PPC64LE_SP: + return "SP" + case num == PPC64LE_PC: + return "PC" + case num >= PPC64LE_V0 && num <= 108: + return fmt.Sprintf("V%d", num-64) + default: + return fmt.Sprintf("unknown%d", num) + } +} + +func PPC64LEMaxRegNum() uint64 { + // TODO(alexsaezm) What is this thing? + return 65 +} + +var PPC64LENameToDwarf = func() map[string]int { + r := make(map[string]int) + + r["nip"] = PPC64LE_PC + r["sp"] = PPC64LE_SP + r["bp"] = PPC64LE_BP + r["lr"] = PPC64LE_LR + + // General Purpose Registers: from R0 to R31 + for i := 0; i <= 31; i++ { + r[fmt.Sprintf("r%d", i)] = PPC64LE_R0 + i + } + + // Floating point registers: from F0 to F31 + for i := 0; i <= 31; i++ { + r[fmt.Sprintf("f%d", i)] = PPC64LE_F0 + i + } + + // Vector (Altivec/VMX) registers: from V0 to V31 + for i := 0; i <= 31; i++ { + r[fmt.Sprintf("v%d", i)] = PPC64LE_V0 + i + } + + // Vector Scalar (VSX) registers: from VS0 to VS63 + for i := 0; i <= 63; i++ { + r[fmt.Sprintf("vs%d", i)] = PPC64LE_VS0 + i + } + + // Condition Registers: from CR0 to CR7 + for i := 0; i <= 7; i++ { + r[fmt.Sprintf("cr%d", i)] = PPC64LE_CR0 + i + } + return r +}() diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 736a8dc6ca..91df3e2a03 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -131,6 +131,7 @@ var ( elf.EM_X86_64: true, elf.EM_AARCH64: true, elf.EM_386: true, + elf.EM_PPC64: true, } supportedWindowsArch = map[_PEMachine]bool{ @@ -646,6 +647,8 @@ func NewBinaryInfo(goos, goarch string) *BinaryInfo { r.Arch = AMD64Arch(goos) case "arm64": r.Arch = ARM64Arch(goos) + case "ppc64le": + r.Arch = PPC64LEArch(goos) } return r } @@ -1451,6 +1454,10 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync. bi.gStructOffset = tlsg.Value + uint64(bi.Arch.PtrSize()*2) + ((tls.Vaddr - uint64(bi.Arch.PtrSize()*2)) & (tls.Align - 1)) + case elf.EM_PPC64: + // TODO(alexsaezm) Complete this + _ = getSymbol(image, bi.logger, exe, "runtime.tls_g") + default: // we should never get here panic("architecture not supported") diff --git a/pkg/proc/linutil/regs_ppc64le_arch.go b/pkg/proc/linutil/regs_ppc64le_arch.go new file mode 100644 index 0000000000..1328d2da4c --- /dev/null +++ b/pkg/proc/linutil/regs_ppc64le_arch.go @@ -0,0 +1,171 @@ +package linutil + +import ( + "fmt" + "github.com/go-delve/delve/pkg/proc" +) + +// PPC64LERegisters implements the proc.Registers interface for the native/linux +// backend and core/linux backends, on PPC64LE. +type PPC64LERegisters struct { + Regs *PPC64LEPtraceRegs + Fpregs []proc.Register //Formatted floating point registers + Fpregset []byte //holding all floating point register values + loadFpRegs func(*PPC64LERegisters) error +} + +func NewPPC64LERegisters(regs *PPC64LEPtraceRegs, loadFpRegs func(*PPC64LERegisters) error) *PPC64LERegisters { + return &PPC64LERegisters{Regs: regs, loadFpRegs: loadFpRegs} +} + +// PPC64LEPtraceRegs is the struct used by the linux kernel to return the +// general purpose registers for PPC64LE CPUs. +// TODO(alexsaezm) Remove the next line: +// Copied from src/syscall/ztypes_linux_ppc64le.go#L518-L532 +type PPC64LEPtraceRegs struct { + Gpr [32]uint64 // 32 general-purpose registers, each 64 bits wide + Nip uint64 + Msr uint64 + Orig_gpr3 uint64 + Ctr uint64 + Link uint64 + Xer uint64 + Ccr uint64 + Softe uint64 + Trap uint64 + Dar uint64 + Dsisr uint64 + Result uint64 + //SP uint64 // SP pointer r[1] + //TOC uint64 // TOC pointer -- r[2] + //TLS uint64 // Thread pointer -- r[13] + //BP uint64 // Base pointer -- r[30] // TODO(alexsaezm) Check this + //LR uint64 // Link register -> TODO(alexsaezm) LLDB dwarf_lr_ppc64le = 65 + //CTR uint64 // Loop count register // TODO(alexsaezm) LLDB dwarf_ctr_ppc64le = 66 + //XER uint64 // Fixed point exception register // TODO(alexsaezm) LLDB dwarf_xer_ppc64le = 76 +} + +func (regs PPC64LEPtraceRegs) String() string { + gprs := "\n" + for i, r := range regs.Gpr { + gprs += fmt.Sprintf("\t\t - gpr[%d]: %d\n", i, r) + } + return fmt.Sprintf("Registers:\n"+ + "\t - Gpr: %s"+ + "\t - Nip: %d\n"+ + "\t - Msr: %d\n"+ + "\t - Orig_gpr3: %d\n"+ + "\t - Ctr: %d\n"+ + "\t - Link: %d\n"+ + "\t - Xer: %d\n"+ + "\t - Ccr: %d\n"+ + "\t - Softe: %d\n"+ + "\t - Trap: %d\n"+ + "\t - Dar: %d\n"+ + "\t - Dsisr: %d\n"+ + "\t - Result: %d\n", gprs, regs.Nip, regs.Msr, regs.Orig_gpr3, regs.Ctr, regs.Link, regs.Xer, regs.Ccr, regs.Softe, regs.Trap, regs.Dar, regs.Dsisr, regs.Result) +} + +// PC returns the value of the NIP register +// Also called the IAR/Instruction Address Register or NIP/Next Instruction Pointer +func (r *PPC64LERegisters) PC() uint64 { + return r.Regs.Nip +} + +// SP returns the value of Stack frame pointer stored in Gpr[1]. +func (r *PPC64LERegisters) SP() uint64 { + return r.Regs.Gpr[1] +} + +func (r *PPC64LERegisters) BP() uint64 { + return r.Regs.Gpr[30] +} + +// TLS returns the value of the thread pointer stored in Gpr[13] +func (r *PPC64LERegisters) TLS() uint64 { + return r.Regs.Gpr[13] +} + +// GAddr returns the address of the G variable +func (r *PPC64LERegisters) GAddr() (uint64, bool) { + return r.Regs.Gpr[30], true +} + +// Slice returns the registers as a list of (name, value) pairs. +func (r *PPC64LERegisters) Slice(floatingPoint bool) ([]proc.Register, error) { + var regs = []struct { + k string + v uint64 + }{ + {"R0", r.Regs.Gpr[0]}, + {"R1", r.Regs.Gpr[1]}, + {"R2", r.Regs.Gpr[2]}, + {"R3", r.Regs.Gpr[3]}, + {"R4", r.Regs.Gpr[4]}, + {"R5", r.Regs.Gpr[5]}, + {"R6", r.Regs.Gpr[6]}, + {"R7", r.Regs.Gpr[7]}, + {"R8", r.Regs.Gpr[8]}, + {"R9", r.Regs.Gpr[9]}, + {"R10", r.Regs.Gpr[10]}, + {"R11", r.Regs.Gpr[11]}, + {"R12", r.Regs.Gpr[12]}, + {"R13", r.Regs.Gpr[13]}, + {"R14", r.Regs.Gpr[14]}, + {"R15", r.Regs.Gpr[15]}, + {"R16", r.Regs.Gpr[16]}, + {"R17", r.Regs.Gpr[17]}, + {"R18", r.Regs.Gpr[18]}, + {"R19", r.Regs.Gpr[19]}, + {"R20", r.Regs.Gpr[20]}, + {"R21", r.Regs.Gpr[21]}, + {"R22", r.Regs.Gpr[22]}, + {"R23", r.Regs.Gpr[23]}, + {"R24", r.Regs.Gpr[24]}, + {"R25", r.Regs.Gpr[25]}, + {"R26", r.Regs.Gpr[26]}, + {"R27", r.Regs.Gpr[27]}, + {"R28", r.Regs.Gpr[28]}, + {"R29", r.Regs.Gpr[29]}, + {"R30", r.Regs.Gpr[30]}, + {"R31", r.Regs.Gpr[31]}, + {"NIP", r.Regs.Nip}, + //{"SP", r.Regs.SP}, + //{"TOC", r.Regs.TOC}, + //{"TLS", r.Regs.TLS}, + //{"BR", r.Regs.BP}, + //{"LR", r.Regs.LR}, + //{"CTR", r.Regs.CTR}, + } + out := make([]proc.Register, 0, len(regs)+len(r.Fpregs)) + for _, reg := range regs { + out = proc.AppendUint64Register(out, reg.k, reg.v) + } + var floatLoadError error + if floatingPoint { + if r.loadFpRegs != nil { + floatLoadError = r.loadFpRegs(r) + r.loadFpRegs = nil + } + out = append(out, r.Fpregs...) + } + return out, floatLoadError +} + +// Copy returns a copy of these registers that is guaranteed not to change. +func (r *PPC64LERegisters) Copy() (proc.Registers, error) { + //TODO implement me + panic("implement me: Copy") +} + +type PPC64LEPtraceFpRegs struct { + // TODO(alexsaezm) Not quite sure about this, review in the future + Fp []byte +} + +func (fpregs *PPC64LEPtraceFpRegs) Decode() (regs []proc.Register) { + for i := 0; i < len(fpregs.Fp); i += 16 { + regs = proc.AppendBytesRegister(regs, fmt.Sprintf("V%d", i/16), fpregs.Fp[i:i+16]) + } + return +} diff --git a/pkg/proc/native/hwbreak_ppc64le.go b/pkg/proc/native/hwbreak_ppc64le.go new file mode 100644 index 0000000000..c751eaa2d0 --- /dev/null +++ b/pkg/proc/native/hwbreak_ppc64le.go @@ -0,0 +1,23 @@ +package native + +import ( + "github.com/go-delve/delve/pkg/proc" +) + +func (thread *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) { + // TODO(alexsaezm) Implement this method + panic("Unimplemented findHardwareBreakpoint method in threads_linux_ppc64le.go") + return nil, nil +} + +func (thread *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error { + // TODO(alexsaezm) Implement this method + panic("Unimplemented writeHardwareBreakpoint method in threads_linux_ppc64le.go") + return nil +} + +func (thread *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error { + // TODO(alexsaezm) Implement this method + panic("Unimplemented clearHardwareBreakpoint method in threads_linux_ppc64le.go") + return nil +} diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index 500fc46a16..6a8452c3dd 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -251,6 +251,7 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc // look like the breakpoint was hit twice when it was "logically" only // executed once. // See: https://go-review.googlesource.com/c/go/+/208126 + // TODO(alexsaezm) Verify if I need to disable it for ppc64le too DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"), StopReason: stopReason, @@ -259,6 +260,7 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc if err != nil { return nil, err } + // TODO(alexsaezm) Verify if I need to run IsCgo for ppc64le too if dbp.bi.Arch.Name == "arm64" { dbp.iscgo = tgt.IsCgo() } diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index a7f0dd6328..6fa51a33fa 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -414,9 +414,9 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n // Sometimes we get an unknown thread, ignore it? continue } - if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) { + if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) || (status.StopSignal() == sys.SIGSEGV) { th.os.running = false - if status.StopSignal() == sys.SIGTRAP { + if status.StopSignal() == sys.SIGTRAP || status.StopSignal() == sys.SIGSEGV { th.os.setbp = true } return th, nil diff --git a/pkg/proc/native/ptrace_linux_64bit.go b/pkg/proc/native/ptrace_linux_64bit.go index f4ab69c8b2..e3a0c60752 100644 --- a/pkg/proc/native/ptrace_linux_64bit.go +++ b/pkg/proc/native/ptrace_linux_64bit.go @@ -1,5 +1,5 @@ -//go:build (linux && amd64) || (linux && arm64) -// +build linux,amd64 linux,arm64 +//go:build (linux && amd64) || (linux && arm64) || (linux && ppc64le) +// +build linux,amd64 linux,arm64 linux,ppc64le package native diff --git a/pkg/proc/native/registers_linux_ppc64le.go b/pkg/proc/native/registers_linux_ppc64le.go new file mode 100644 index 0000000000..e3ffe62e38 --- /dev/null +++ b/pkg/proc/native/registers_linux_ppc64le.go @@ -0,0 +1,105 @@ +package native + +import ( + "debug/elf" + "github.com/go-delve/delve/pkg/dwarf/op" + "github.com/go-delve/delve/pkg/dwarf/regnum" + "github.com/go-delve/delve/pkg/proc" + "github.com/go-delve/delve/pkg/proc/linutil" + sys "golang.org/x/sys/unix" + "syscall" + "unsafe" +) + +const ( + _PPC64LE_GPREGS_SIZE = 44 * 8 // TODO(alexsaezm) Review _PPC64LE_GPREGS_SIZE's value + _PPC64LE_FPREGS_SIZE = 34 + 8*8 // TODO(alexsaezm) Review _PPC64LE_FPREGS_SIZE's value +) + +func ptraceGetGRegs(pid int, regs *linutil.PPC64LEPtraceRegs) (err error) { + iov0 := sys.Iovec{Base: (*byte)(unsafe.Pointer(regs)), Len: _PPC64LE_GPREGS_SIZE} + _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(pid), uintptr(elf.NT_PRSTATUS), uintptr(unsafe.Pointer(&iov0)), 0, 0) + if err == syscall.Errno(0) { + err = nil + } + return +} + +func ptraceSetGRegs(pid int, regs *linutil.PPC64LEPtraceRegs) (err error) { + iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(regs)), Len: _PPC64LE_GPREGS_SIZE} + _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGS, uintptr(pid), uintptr(elf.NT_PRSTATUS), uintptr(unsafe.Pointer(&iov)), 0, 0) + if err == syscall.Errno(0) { + err = nil + } + return +} + +func ptraceGetFpRegset(tid int) (fpregset []byte, err error) { + var ppc64leFpregs [_PPC64LE_FPREGS_SIZE]byte + iov := sys.Iovec{Base: &ppc64leFpregs[0], Len: _PPC64LE_FPREGS_SIZE} + _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), uintptr(elf.NT_FPREGSET), uintptr(unsafe.Pointer(&iov)), 0, 0) + if err != syscall.Errno(0) { + if err == syscall.ENODEV { + err = nil + } + return + } else { + err = nil + } + + fpregset = ppc64leFpregs[:iov.Len-8] + return fpregset, err +} + +// SetPC sets PC to the value specified by 'pc'. +func (t *nativeThread) setPC(pc uint64) error { + ir, err := registers(t) + if err != nil { + return err + } + r := ir.(*linutil.PPC64LERegisters) + r.Regs.Nip = pc + t.dbp.execPtraceFunc(func() { err = ptraceSetGRegs(t.ID, r.Regs) }) + return err +} + +// SetReg changes the value of the specified register. +func (t *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error { + ir, err := registers(t) + if err != nil { + return err + } + r := ir.(*linutil.PPC64LERegisters) + + switch regNum { + case regnum.PPC64LE_PC: + r.Regs.Nip = reg.Uint64Val + case regnum.PPC64LE_SP: + r.Regs.Gpr[1] = reg.Uint64Val + //case regnum.PPC64LE_LR: + // r.Regs.Gpr[= reg.Uint64Val + default: + panic("SetReg") + } + + t.dbp.execPtraceFunc(func() { err = ptraceSetGRegs(t.ID, r.Regs) }) + return err +} + +func registers(thread *nativeThread) (proc.Registers, error) { + var ( + regs linutil.PPC64LEPtraceRegs + err error + ) + + thread.dbp.execPtraceFunc(func() { err = ptraceGetGRegs(thread.ID, ®s) }) + if err != nil { + return nil, err + } + r := linutil.NewPPC64LERegisters(®s, func(r *linutil.PPC64LERegisters) error { + var floatLoadError error + r.Fpregs, r.Fpregset, floatLoadError = thread.fpRegisters() + return floatLoadError + }) + return r, nil +} diff --git a/pkg/proc/native/support_sentinel.go b/pkg/proc/native/support_sentinel.go index bfad9ad21f..a2048554fb 100644 --- a/pkg/proc/native/support_sentinel.go +++ b/pkg/proc/native/support_sentinel.go @@ -1,6 +1,6 @@ // This file is used to detect build on unsupported GOOS/GOARCH combinations. -//go:build (!linux && !darwin && !windows && !freebsd) || (linux && !amd64 && !arm64 && !386) || (darwin && !amd64 && !arm64) || (windows && !amd64) || (freebsd && !amd64) -// +build !linux,!darwin,!windows,!freebsd linux,!amd64,!arm64,!386 darwin,!amd64,!arm64 windows,!amd64 freebsd,!amd64 +//go:build (!linux && !darwin && !windows && !freebsd) || (linux && !amd64 && !arm64 && !386 && !ppc64le) || (darwin && !amd64 && !arm64) || (windows && !amd64) || (freebsd && !amd64) +// +build !linux,!darwin,!windows,!freebsd linux,!amd64,!arm64,!386,!ppc64le darwin,!amd64,!arm64 windows,!amd64 freebsd,!amd64 package your_operating_system_and_architecture_combination_is_not_supported_by_delve diff --git a/pkg/proc/native/threads_linux_ppc64le.go b/pkg/proc/native/threads_linux_ppc64le.go new file mode 100644 index 0000000000..8376fc2c6e --- /dev/null +++ b/pkg/proc/native/threads_linux_ppc64le.go @@ -0,0 +1,24 @@ +package native + +import ( + "fmt" + "github.com/go-delve/delve/pkg/proc" + "github.com/go-delve/delve/pkg/proc/linutil" +) + +func (t *nativeThread) fpRegisters() ([]proc.Register, []byte, error) { + var err error + var ppc64leFpregs linutil.PPC64LEPtraceFpRegs + t.dbp.execPtraceFunc(func() { ppc64leFpregs.Fp, err = ptraceGetFpRegset(t.ID) }) + fpregs := ppc64leFpregs.Decode() + if err != nil { + err = fmt.Errorf("could not get floating point registers: %v", err.Error()) + } + return fpregs, ppc64leFpregs.Fp, err +} + +func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error { + // TODO(alexsaezm) Implement me + panic("Unimplemented restoreRegisters method in threads_linux_ppc64le.go") + return nil +} diff --git a/pkg/proc/ppc64le_arch.go b/pkg/proc/ppc64le_arch.go new file mode 100644 index 0000000000..5b7102b4b4 --- /dev/null +++ b/pkg/proc/ppc64le_arch.go @@ -0,0 +1,229 @@ +package proc + +import ( + "encoding/binary" + "github.com/go-delve/delve/pkg/dwarf/frame" + "strings" + + "github.com/go-delve/delve/pkg/dwarf/op" + "github.com/go-delve/delve/pkg/dwarf/regnum" +) + +//var ppc64leBreakInstruction = []byte{0xfc, 0x00, 0x07, 0xfe} + +var ppc64leBreakInstruction = []byte{0x0, 0x0, 0x20, 0xd4} // TODO(alexsaezm) Verify if this is the correct break instruction + +func PPC64LEArch(goos string) *Arch { + return &Arch{ + Name: "ppc64le", + ptrSize: 8, + maxInstructionLength: 4, + breakpointInstruction: ppc64leBreakInstruction, + breakInstrMovesPC: false, // TODO(alexsaezm) Verify if the break instruction moves the PC + derefTLS: false, // TODO(alexsaezm) Verify how the TLS behaves + prologues: prologuesPPC64LE, + fixFrameUnwindContext: ppc64leFixFrameUnwindContext, + switchStack: ppc64leSwitchStack, + regSize: ppc64leRegSize, + RegistersToDwarfRegisters: ppc64leRegistersToDwarfRegisters, + addrAndStackRegsToDwarfRegisters: ppc64leAddrAndStackRegsToDwarfRegisters, + DwarfRegisterToString: ppc64leDwarfRegisterToString, + inhibitStepInto: func(*BinaryInfo, uint64) bool { return false }, + asmDecode: ppc64leAsmDecode, + usesLR: true, + PCRegNum: regnum.PPC64LE_PC, + SPRegNum: regnum.PPC64LE_SP, + BPRegNum: regnum.PPC64LE_BP, + asmRegisters: ppc64leAsmRegisters, + RegisterNameToDwarf: nameToDwarfFunc(regnum.PPC64LENameToDwarf), + } +} + +func ppc64leFixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext { + a := bi.Arch + if a.sigreturnfn == nil { + a.sigreturnfn = bi.LookupFunc["runtime.sigreturn"] + } + if fctxt == nil || (a.sigreturnfn != nil && pc >= a.sigreturnfn.Entry && pc < a.sigreturnfn.End) { + return &frame.FrameContext{ + RetAddrReg: regnum.PPC64LE_PC, + Regs: map[uint64]frame.DWRule{ + regnum.PPC64LE_PC: { + Rule: frame.RuleOffset, + Offset: int64(-a.PtrSize()), + }, + regnum.PPC64LE_LR: { + Rule: frame.RuleOffset, + Offset: int64(-2 * a.PtrSize()), + }, + regnum.PPC64LE_SP: { + Rule: frame.RuleValOffset, + Offset: 0, + }, + }, + CFA: frame.DWRule{ + Rule: frame.RuleCFA, + Reg: regnum.PPC64LE_BP, + Offset: int64(2 * a.PtrSize()), + }, + } + } + if a.crosscall2fn == nil { + // This is used to fix issues with the c calling frames + a.crosscall2fn = bi.LookupFunc["crosscall2"] + } + + // Checks if we marked the function as a crosscall and if we are currently in it + if a.crosscall2fn != nil && pc >= a.crosscall2fn.Entry && pc < a.crosscall2fn.End { + rule := fctxt.CFA + if rule.Offset == crosscall2SPOffsetBad { + // Linux support only + rule.Offset += crosscall2SPOffsetNonWindows + } + fctxt.CFA = rule + } + //We assume that RBP 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 a rule already we emit one. + //FIXME(alexsaezm) I think this section of code doesn't apply to ppc64le, but not sure why + if fctxt.Regs[regnum.PPC64LE_BP].Rule == frame.RuleUndefined { + fctxt.Regs[regnum.PPC64LE_BP] = frame.DWRule{ + Rule: frame.RuleFramePointer, + Reg: regnum.PPC64LE_BP, + Offset: 0, + } + } + if fctxt.Regs[regnum.PPC64LE_LR].Rule == frame.RuleUndefined { + fctxt.Regs[regnum.PPC64LE_LR] = frame.DWRule{ + Rule: frame.RuleFramePointer, + Reg: regnum.PPC64LE_LR, + Offset: 0, + } + } + return fctxt +} + +const ppc64cgocallSPOffsetSaveSlot = 0x8 +const ppc64prevG0schedSPOffsetSaveSlot = 0x10 + +// TODO(alexsaezm) Review this method +func ppc64leSwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool { + if it.frame.Current.Fn != nil { + switch it.frame.Current.Fn.Name { + case "runtime.asmcgocall", "runtime.cgocallback_gofunc", "runtime.sigpanic", "runtime.cgocallback": + //do nothing + case "runtime.goexit", "runtime.rt0_go", "runtime.mcall": + // Look for "top of stack" functions. + it.atend = true + return true + case "crosscall2": + //The offsets get from runtime/cgo/asm_ppc64.s:10 + newsp, _ := readUintRaw(it.mem, it.regs.SP()+8*24, int64(it.bi.Arch.PtrSize())) + newbp, _ := readUintRaw(it.mem, it.regs.SP()+8*14, int64(it.bi.Arch.PtrSize())) + newlr, _ := readUintRaw(it.mem, it.regs.SP()+8*15, int64(it.bi.Arch.PtrSize())) + if it.regs.Reg(it.regs.BPRegNum) != nil { + it.regs.Reg(it.regs.BPRegNum).Uint64Val = newbp + } else { + reg, _ := it.readRegisterAt(it.regs.BPRegNum, it.regs.SP()+8*14) + it.regs.AddReg(it.regs.BPRegNum, reg) + } + it.regs.Reg(it.regs.LRRegNum).Uint64Val = newlr + it.regs.Reg(it.regs.SPRegNum).Uint64Val = newsp + it.pc = newlr + 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. + it.switchToGoroutineStack() + return true + } + } + } + fn := it.bi.PCToFunc(it.frame.Ret) + if fn == nil { + return false + } + switch fn.Name { + case "runtime.asmcgocall": + if !it.systemstack { + return false + } + + // This function is called by a goroutine to execute a C function and + // switches from the goroutine stack to the system stack. + // Since we are unwinding the stack from callee to caller we have to switch + // from the system stack to the goroutine stack. + off, _ := readIntRaw(it.mem, callFrameRegs.SP()+ppc64cgocallSPOffsetSaveSlot, int64(it.bi.Arch.PtrSize())) + oldsp := callFrameRegs.SP() + newsp := uint64(int64(it.stackhi) - off) + + // runtime.asmcgocall can also be called from inside the system stack, + // in that case no stack switch actually happens + if newsp == oldsp { + return false + } + it.systemstack = false + callFrameRegs.Reg(callFrameRegs.SPRegNum).Uint64Val = uint64(int64(newsp)) + return false + + case "runtime.cgocallback_gofunc", "runtime.cgocallback": + // For a detailed description of how this works read the long comment at + // the start of $GOROOT/src/runtime/cgocall.go and the source code of + // runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_ppc64.s + // + // When a C functions calls back into go it will eventually call into + // runtime.cgocallback_gofunc which is the function that does the stack + // switch from the system stack back into the goroutine stack + // Since we are going backwards on the stack here we see the transition + // as goroutine stack -> system stack. + if it.systemstack { + return false + } + + it.loadG0SchedSP() + if it.g0_sched_sp <= 0 { + return false + } + // entering the system stack + callFrameRegs.Reg(callFrameRegs.SPRegNum).Uint64Val = it.g0_sched_sp + // reads the previous value of g0.sched.sp that runtime.cgocallback_gofunc saved on the stack + + // TODO: is this save slot correct? + it.g0_sched_sp, _ = readUintRaw(it.mem, callFrameRegs.SP()+ppc64prevG0schedSPOffsetSaveSlot, int64(it.bi.Arch.PtrSize())) + it.systemstack = true + return false + } + + return false +} + +// ppc64leRegSize returns the size (in bytes) of register regnum. +func ppc64leRegSize(rn uint64) int { + // FIXME(alexsaezm) Fix this, this is clearly not correct + if rn == regnum.PPC64LE_PC { + return 8 + } + return 8 +} + +func ppc64leRegistersToDwarfRegisters(staticBase uint64, regs Registers) *op.DwarfRegisters { + dregs := initDwarfRegistersFromSlice(int(regnum.PPC64LEMaxRegNum()), regs, regnum.PPC64LENameToDwarf) + dr := op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, regnum.PPC64LE_PC, regnum.PPC64LE_SP, regnum.PPC64LE_BP, regnum.PPC64LE_LR) + dr.SetLoadMoreCallback(loadMoreDwarfRegistersFromSliceFunc(dr, regs, regnum.PPC64LENameToDwarf)) + return dr +} + +func ppc64leAddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint64) op.DwarfRegisters { + panic("ppc64leAddrAndStackRegsToDwarfRegisters") +} + +func ppc64leDwarfRegisterToString(i int, reg *op.DwarfRegister) (name string, floatingPoint bool, repr string) { + panic("ppc64leDwarfRegisterToString") +} diff --git a/pkg/proc/ppc64le_disasm.go b/pkg/proc/ppc64le_disasm.go new file mode 100644 index 0000000000..7ba07972ef --- /dev/null +++ b/pkg/proc/ppc64le_disasm.go @@ -0,0 +1,154 @@ +package proc + +import ( + "encoding/binary" + + "github.com/go-delve/delve/pkg/dwarf/op" + "github.com/go-delve/delve/pkg/dwarf/regnum" + "golang.org/x/arch/ppc64/ppc64asm" +) + +// Possible stacksplit prologues are inserted by stacksplit in +// $GOROOT/src/cmd/internal/obj/ppc64/obj9.go. +var prologuesPPC64LE []opcodeSeq + +func init() { + // Note: these will be the gnu opcodes and not the Go opcodes. Verify the sequences are as expected. + var tinyStacksplit = opcodeSeq{uint64(ppc64asm.LDX), uint64(ppc64asm.CMPD), uint64(ppc64asm.B)} + var smallStacksplit = opcodeSeq{uint64(ppc64asm.SUBF), uint64(ppc64asm.CMPD), uint64(ppc64asm.B)} + var bigStacksplit = opcodeSeq{uint64(ppc64asm.CMPD), uint64(ppc64asm.B), uint64(ppc64asm.ADD), uint64(ppc64asm.SUBF), uint64(ppc64asm.LDX), uint64(ppc64asm.CMPD), uint64(ppc64asm.B)} + + // Don't know what GetG is for. I don't see it in the split stack prologues. + var unixGetG = opcodeSeq{uint64(ppc64asm.LDX)} + + prologuesPPC64LE = make([]opcodeSeq, 0, 3) + for _, getG := range []opcodeSeq{unixGetG} { + for _, stacksplit := range []opcodeSeq{tinyStacksplit, smallStacksplit, bigStacksplit} { + prologue := make(opcodeSeq, 0, len(getG)+len(stacksplit)) + prologue = append(prologue, getG...) + prologue = append(prologue, stacksplit...) + prologuesPPC64LE = append(prologuesPPC64LE, prologue) + } + } +} + +func ppc64leAsmDecode(asmInst *AsmInstruction, mem []byte, regs *op.DwarfRegisters, memrw MemoryReadWriter, bi *BinaryInfo) error { + asmInst.Size = 4 + asmInst.Bytes = mem[:asmInst.Size] + + inst, err := ppc64asm.Decode(mem, binary.LittleEndian) + if err != nil { + asmInst.Inst = (*ppc64ArchInst)(nil) + return err + } + asmInst.Inst = (*ppc64ArchInst)(&inst) + asmInst.Kind = OtherInstruction + + // TODO(alexsaezm) Review the opcodes, there's a lot more + switch inst.Op { + case ppc64asm.BL, ppc64asm.BLA, ppc64asm.BCL, ppc64asm.BCLA, ppc64asm.BCLRL, ppc64asm.BCCTRL, ppc64asm.BCTARL: + // Pages 38-40 Book I v3.0 + asmInst.Kind = CallInstruction + case ppc64asm.BCLR, ppc64asm.RFEBB, ppc64asm.RFID, ppc64asm.HRFID, ppc64asm.RFI, ppc64asm.RFCI, ppc64asm.RFDI, ppc64asm.RFMCI, ppc64asm.RFGI: + asmInst.Kind = RetInstruction + case ppc64asm.B, ppc64asm.BA, ppc64asm.BC, ppc64asm.BCA, ppc64asm.BCCTR, ppc64asm.BCTAR: + // Pages 38-40 Book I v3.0 + asmInst.Kind = JmpInstruction + case ppc64asm.TD, ppc64asm.TDI, ppc64asm.TW, ppc64asm.TWI: + asmInst.Kind = HardBreakInstruction + } + + asmInst.DestLoc = resolveCallArgPPC64LE(&inst, asmInst.Loc.PC, asmInst.AtPC, regs, memrw, bi) + return nil +} + +func resolveCallArgPPC64LE(inst *ppc64asm.Inst, instAddr uint64, currentGoroutine bool, regs *op.DwarfRegisters, mem MemoryReadWriter, bininfo *BinaryInfo) *Location { + switch inst.Op { + case ppc64asm.B, ppc64asm.BL, ppc64asm.BLA, ppc64asm.BCL, ppc64asm.BCLA, ppc64asm.BCLRL, ppc64asm.BCCTRL, ppc64asm.BCTARL: + // ok + default: + return nil + } + + var pc uint64 + var err error + + switch arg := inst.Args[0].(type) { + case ppc64asm.Imm: + pc = uint64(arg) + case ppc64asm.Reg: + if !currentGoroutine || regs == nil { + return nil + } + pc, err = bininfo.Arch.getAsmRegister(regs, int(arg)) + if err != nil { + return nil + } + case ppc64asm.PCRel: + pc = instAddr + uint64(arg) + default: + return nil + } + + file, line, fn := bininfo.PCToLine(pc) + if fn == nil { + return &Location{PC: pc} + } + return &Location{PC: pc, File: file, Line: line, Fn: fn} +} + +type ppc64ArchInst ppc64asm.Inst + +func (inst *ppc64ArchInst) Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string { + if inst == nil { + return "?" + } + + var text string + + switch flavour { + case GNUFlavour: + text = ppc64asm.GNUSyntax(ppc64asm.Inst(*inst), pc) + default: + text = ppc64asm.GoSyntax(ppc64asm.Inst(*inst), pc, symLookup) + } + + return text +} + +func (inst *ppc64ArchInst) OpcodeEquals(op uint64) bool { + if inst == nil { + return false + } + return uint64(inst.Op) == op +} + +var ppc64leAsmRegisters = func() map[int]asmRegister { + r := make(map[int]asmRegister) + + // General Purpose Registers: from R0 to R31 + for i := ppc64asm.R0; i <= ppc64asm.R31; i++ { + r[int(i)] = asmRegister{regnum.PPC64LE_R0 + uint64(i-ppc64asm.R0), 0, 0} + } + + // Floating point registers: from F0 to F31 + for i := ppc64asm.F0; i <= ppc64asm.F31; i++ { + r[int(i)] = asmRegister{regnum.PPC64LE_F0 + uint64(i-ppc64asm.F0), 0, 0} + } + + // Vector (Altivec/VMX) registers: from V0 to V31 + for i := ppc64asm.V0; i <= ppc64asm.V31; i++ { + r[int(i)] = asmRegister{regnum.PPC64LE_V0 + uint64(i-ppc64asm.V0), 0, 0} + } + + // Vector Scalar (VSX) registers: from VS0 to VS63 + for i := ppc64asm.VS0; i <= ppc64asm.VS63; i++ { + r[int(i)] = asmRegister{regnum.PPC64LE_VS0 + uint64(i-ppc64asm.VS0), 0, 0} + } + + // Condition Registers: from CR0 to CR7 + for i := ppc64asm.CR0; i <= ppc64asm.CR7; i++ { + r[int(i)] = asmRegister{regnum.PPC64LE_CR0 + uint64(i-ppc64asm.CR0), 0, 0} + } + return r +}() diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index 9d847f4980..932682ced0 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -227,6 +227,7 @@ func (it *stackIterator) Next() bool { it.top = false it.pc = it.frame.Ret it.regs = callFrameRegs + return true } @@ -313,6 +314,7 @@ func (it *stackIterator) stacktrace(depth int) ([]Stackframe, error) { break } } + if err := it.Err(); err != nil { if len(frames) == 0 { return nil, err diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index f191598edb..e078966b24 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -2,7 +2,6 @@ package proc import ( "errors" - "github.com/go-delve/delve/pkg/dwarf/op" )