From a6685247e03b6e1a8ae4703f0b7fab866db1a899 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 23 Feb 2021 21:38:52 +0100 Subject: [PATCH] proc/native: correctly read g address on linux/arm64 (#2343) When cgo is used the address of the g struct is saved on the special register TPIDR_EL0. Because executing C code could overwrite the contents of R28 that normally contains the address of g we should read it from TPIDR_EL0 instead when runtime.iscgo is set. --- pkg/proc/bininfo.go | 74 ++++++++++++++++-------- pkg/proc/linutil/regs_arm64_arch.go | 19 +++--- pkg/proc/native/proc.go | 11 +++- pkg/proc/native/registers_linux_arm64.go | 19 +++++- pkg/proc/target.go | 19 ++++++ 5 files changed, 108 insertions(+), 34 deletions(-) diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 594b12a1..d31cc4eb 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -1227,19 +1227,9 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync. // - Otherwise, Go asks the external linker to place the G pointer by // emitting runtime.tlsg, a TLS symbol, which is relocated to the chosen // offset in libc's TLS block. - symbols, err := exe.Symbols() - if err != nil { - image.setLoadError("could not parse ELF symbols: %v", err) - return - } - var tlsg *elf.Symbol - for _, symbol := range symbols { - if symbol.Name == "runtime.tlsg" { - s := symbol - tlsg = &s - break - } - } + // - On ARM64 (but really, any architecture other than i386 and 86x64) the + // offset is calculate using runtime.tls_g and the formula is different. + var tls *elf.Prog for _, prog := range exe.Progs { if prog.Type == elf.PT_TLS { @@ -1247,20 +1237,54 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync. break } } - if tlsg == nil || tls == nil { - bi.gStructOffset = ^uint64(bi.Arch.PtrSize()) + 1 //-ptrSize - return + + switch exe.Machine { + case elf.EM_X86_64, elf.EM_386: + tlsg := getSymbol(image, exe, "runtime.tlsg") + if tlsg == nil || tls == nil { + bi.gStructOffset = ^uint64(bi.Arch.PtrSize()) + 1 //-ptrSize + return + } + + // According to https://reviews.llvm.org/D61824, linkers must pad the actual + // size of the TLS segment to ensure that (tlsoffset%align) == (vaddr%align). + // This formula, copied from the lld code, matches that. + // https://github.com/llvm-mirror/lld/blob/9aef969544981d76bea8e4d1961d3a6980980ef9/ELF/InputSection.cpp#L643 + memsz := tls.Memsz + (-tls.Vaddr-tls.Memsz)&(tls.Align-1) + + // The TLS register points to the end of the TLS block, which is + // tls.Memsz long. runtime.tlsg is an offset from the beginning of that block. + bi.gStructOffset = ^(memsz) + 1 + tlsg.Value // -tls.Memsz + tlsg.Value + + case elf.EM_AARCH64: + tlsg := getSymbol(image, exe, "runtime.tls_g") + if tlsg == nil || tls == nil { + bi.gStructOffset = 2 * uint64(bi.Arch.PtrSize()) + return + } + + bi.gStructOffset = tlsg.Value + uint64(bi.Arch.PtrSize()*2) + ((tls.Vaddr - uint64(bi.Arch.PtrSize()*2)) & (tls.Align - 1)) + + default: + // we should never get here + panic("architecture not supported") + } +} + +func getSymbol(image *Image, exe *elf.File, name string) *elf.Symbol { + symbols, err := exe.Symbols() + if err != nil { + image.setLoadError("could not parse ELF symbols: %v", err) + return nil } - // According to https://reviews.llvm.org/D61824, linkers must pad the actual - // size of the TLS segment to ensure that (tlsoffset%align) == (vaddr%align). - // This formula, copied from the lld code, matches that. - // https://github.com/llvm-mirror/lld/blob/9aef969544981d76bea8e4d1961d3a6980980ef9/ELF/InputSection.cpp#L643 - memsz := tls.Memsz + (-tls.Vaddr-tls.Memsz)&(tls.Align-1) - - // The TLS register points to the end of the TLS block, which is - // tls.Memsz long. runtime.tlsg is an offset from the beginning of that block. - bi.gStructOffset = ^(memsz) + 1 + tlsg.Value // -tls.Memsz + tlsg.Value + for _, symbol := range symbols { + if symbol.Name == name { + s := symbol + return &s + } + } + return nil } // PE //////////////////////////////////////////////////////////////// diff --git a/pkg/proc/linutil/regs_arm64_arch.go b/pkg/proc/linutil/regs_arm64_arch.go index a82b0b7d..5141e579 100644 --- a/pkg/proc/linutil/regs_arm64_arch.go +++ b/pkg/proc/linutil/regs_arm64_arch.go @@ -9,15 +9,17 @@ import ( // Regs is a wrapper for sys.PtraceRegs. type ARM64Registers struct { - Regs *ARM64PtraceRegs //general-purpose registers - Fpregs []proc.Register //Formatted floating point registers - Fpregset []byte //holding all floating point register values + Regs *ARM64PtraceRegs //general-purpose registers + iscgo bool + tpidr_el0 uint64 + Fpregs []proc.Register //Formatted floating point registers + Fpregset []byte //holding all floating point register values loadFpRegs func(*ARM64Registers) error } -func NewARM64Registers(regs *ARM64PtraceRegs, loadFpRegs func(*ARM64Registers) error) *ARM64Registers { - return &ARM64Registers{Regs: regs, loadFpRegs: loadFpRegs} +func NewARM64Registers(regs *ARM64PtraceRegs, iscgo bool, tpidr_el0 uint64, loadFpRegs func(*ARM64Registers) error) *ARM64Registers { + return &ARM64Registers{Regs: regs, iscgo: iscgo, tpidr_el0: tpidr_el0, loadFpRegs: loadFpRegs} } // ARM64PtraceRegs is the struct used by the linux kernel to return the @@ -102,13 +104,16 @@ func (r *ARM64Registers) BP() uint64 { // TLS returns the address of the thread local storage memory segment. func (r *ARM64Registers) TLS() uint64 { - return 0 + if !r.iscgo { + return 0 + } + return r.tpidr_el0 } // GAddr returns the address of the G variable if it is known, 0 and false // otherwise. func (r *ARM64Registers) GAddr() (uint64, bool) { - return r.Regs.Regs[28], true + return r.Regs.Regs[28], !r.iscgo } // Get returns the value of the n-th register (in arm64asm order). diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index 00db889a..511b2772 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -41,6 +41,8 @@ type nativeProcess struct { // this process. ctty *os.File + iscgo bool + exited, detached bool } @@ -281,12 +283,19 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc if !dbp.childProcess { stopReason = proc.StopAttached } - return proc.NewTarget(dbp, dbp.memthread, proc.NewTargetConfig{ + tgt, err := proc.NewTarget(dbp, dbp.memthread, proc.NewTargetConfig{ Path: path, DebugInfoDirs: debugInfoDirs, DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd", StopReason: stopReason, CanDump: runtime.GOOS == "linux"}) + if err != nil { + return nil, err + } + if dbp.bi.Arch.Name == "arm64" { + dbp.iscgo = tgt.IsCgo() + } + return tgt, nil } func (dbp *nativeProcess) handlePtraceFuncs() { diff --git a/pkg/proc/native/registers_linux_arm64.go b/pkg/proc/native/registers_linux_arm64.go index 19ee8aba..19971298 100644 --- a/pkg/proc/native/registers_linux_arm64.go +++ b/pkg/proc/native/registers_linux_arm64.go @@ -15,6 +15,7 @@ import ( const ( _AARCH64_GREGS_SIZE = 34 * 8 _AARCH64_FPREGS_SIZE = 32*16 + 8 + _NT_ARM_TLS = 0x401 // used in PTRACE_GETREGSET on ARM64 to retrieve the value of TPIDR_EL0, see source/include/uapi/linux/elf.h and source/arch/arm64/kernel/ptrace.c ) func ptraceGetGRegs(pid int, regs *linutil.ARM64PtraceRegs) (err error) { @@ -26,6 +27,15 @@ func ptraceGetGRegs(pid int, regs *linutil.ARM64PtraceRegs) (err error) { return } +func ptraceGetTpidr_el0(pid int, tpidr_el0 *uint64) (err error) { + iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(tpidr_el0)), Len: uint64(unsafe.Sizeof(*tpidr_el0))} + _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(pid), uintptr(_NT_ARM_TLS), uintptr(unsafe.Pointer(&iov)), 0, 0) + if err == syscall.Errno(0) { + err = nil + } + return +} + func ptraceSetGRegs(pid int, regs *linutil.ARM64PtraceRegs) (err error) { iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(regs)), Len: _AARCH64_GREGS_SIZE} _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(pid), uintptr(elf.NT_PRSTATUS), uintptr(unsafe.Pointer(&iov)), 0, 0) @@ -92,7 +102,14 @@ func registers(thread *nativeThread) (proc.Registers, error) { if err != nil { return nil, err } - r := linutil.NewARM64Registers(®s, func(r *linutil.ARM64Registers) error { + var tpidr_el0 uint64 + if thread.dbp.iscgo { + thread.dbp.execPtraceFunc(func() { err = ptraceGetTpidr_el0(thread.ID, &tpidr_el0) }) + if err != nil { + return nil, err + } + } + r := linutil.NewARM64Registers(®s, thread.dbp.iscgo, tpidr_el0, func(r *linutil.ARM64Registers) error { var floatLoadError error r.Fpregs, r.Fpregset, floatLoadError = thread.fpRegisters() return floatLoadError diff --git a/pkg/proc/target.go b/pkg/proc/target.go index 1bda30f3..9f5235bd 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -61,6 +61,7 @@ type Target struct { // have read and parsed from the targets memory. // This must be cleared whenever the target is resumed. gcache goroutineCache + iscgo *bool } // ErrProcessExited indicates that the process has exited and contains both @@ -181,6 +182,24 @@ func NewTarget(p Process, currentThread Thread, cfg NewTargetConfig) (*Target, e return t, nil } +// IsCgo returns the value of runtime.iscgo +func (t *Target) IsCgo() bool { + if t.iscgo != nil { + return *t.iscgo + } + scope := globalScope(t.BinInfo(), t.BinInfo().Images[0], t.Memory()) + iscgov, err := scope.findGlobal("runtime", "iscgo") + if err == nil { + iscgov.loadValue(loadFullValue) + if iscgov.Unreadable == nil { + t.iscgo = new(bool) + *t.iscgo = constant.BoolVal(iscgov.Value) + return constant.BoolVal(iscgov.Value) + } + } + return false +} + // SupportsFunctionCalls returns whether or not the backend supports // calling functions during a debug session. // Currently only non-recorded processes running on AMD64 support