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.
This commit is contained in:
parent
d196b7ddfc
commit
a6685247e0
@ -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 ////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user