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:
Alessandro Arzilli 2021-02-23 21:38:52 +01:00 committed by GitHub
parent d196b7ddfc
commit a6685247e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 34 deletions

@ -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(&regs, 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(&regs, 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