proc.(*Thread).GetG: reading TLS memory directly for g address instead of modifying the executable code

This commit is contained in:
aarzilli 2015-07-28 07:33:07 +02:00
parent d0f3459efb
commit 0933a681cf
10 changed files with 111 additions and 125 deletions

@ -3,11 +3,11 @@ package proc
import "runtime" import "runtime"
type Arch interface { type Arch interface {
SetCurGInstructions(ver GoVersion, iscgo bool) SetGStructOffset(ver GoVersion, iscgo bool)
PtrSize() int PtrSize() int
BreakpointInstruction() []byte BreakpointInstruction() []byte
BreakpointSize() int BreakpointSize() int
CurgInstructions() []byte GStructOffset() uint64
HardwareBreakpointUsage() []bool HardwareBreakpointUsage() []bool
SetHardwareBreakpointUsage(int, bool) SetHardwareBreakpointUsage(int, bool)
} }
@ -16,7 +16,7 @@ type AMD64 struct {
ptrSize int ptrSize int
breakInstruction []byte breakInstruction []byte
breakInstructionLen int breakInstructionLen int
curgInstructions []byte gStructOffset uint64
hardwareBreakpointUsage []bool hardwareBreakpointUsage []bool
} }
@ -31,29 +31,17 @@ func AMD64Arch() *AMD64 {
} }
} }
func (a *AMD64) SetCurGInstructions(ver GoVersion, isextld bool) { func (a *AMD64) SetGStructOffset(ver GoVersion, isextld bool) {
var curg []byte
switch runtime.GOOS { switch runtime.GOOS {
case "darwin": case "darwin":
curg = []byte{ a.gStructOffset = 0x8a0
0x65, 0x48, 0x8b, 0x0C, 0x25, 0xA0, 0x08, // mov %gs:0x8a0,%rcx
0x0, 0x0,
}
case "linux": case "linux":
if isextld || ver.After(GoVersion{1, 5, 0}) { a.gStructOffset = 0xfffffffffffffff0
curg = []byte{
0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, // mov %fs:0xfffffffffffffff8,%rcx
}
} else {
curg = []byte{
0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf0, 0xff, 0xff, 0xff, // mov %fs:0xfffffffffffffff0,%rcx
}
}
} }
curg = append(curg, a.breakInstruction...)
a.curgInstructions = curg if isextld || ver.After(GoVersion{1, 5, 0}) {
a.gStructOffset += 8
}
} }
func (a *AMD64) PtrSize() int { func (a *AMD64) PtrSize() int {
@ -68,8 +56,8 @@ func (a *AMD64) BreakpointSize() int {
return a.breakInstructionLen return a.breakInstructionLen
} }
func (a *AMD64) CurgInstructions() []byte { func (a *AMD64) GStructOffset() uint64 {
return a.curgInstructions return a.gStructOffset
} }
func (a *AMD64) HardwareBreakpointUsage() []bool { func (a *AMD64) HardwareBreakpointUsage() []bool {

@ -601,7 +601,7 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e
return nil, err return nil, err
} }
dbp.arch.SetCurGInstructions(ver, isextld) dbp.arch.SetGStructOffset(ver, isextld)
return dbp, nil return dbp, nil
} }
@ -683,9 +683,7 @@ func (dbp *Process) execPtraceFunc(fn func()) {
} }
func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) { func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) {
th := dbp.Threads[dbp.Pid] vv, err := dbp.CurrentThread.EvalPackageVariable("runtime.buildVersion")
vv, err := th.EvalPackageVariable("runtime.buildVersion")
if err != nil { if err != nil {
err = fmt.Errorf("Could not determine version number: %v\n", err) err = fmt.Errorf("Could not determine version number: %v\n", err)
return return
@ -709,6 +707,5 @@ func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error)
break break
} }
} }
return return
} }

@ -9,6 +9,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"testing" "testing"
"time" "time"
@ -617,6 +618,11 @@ func TestGetG(t *testing.T) {
testGSupportFunc("nocgo", t, p, fixture) testGSupportFunc("nocgo", t, p, fixture)
}) })
// On OSX with Go < 1.5 CGO is not supported due to: https://github.com/golang/go/issues/8973
if runtime.GOOS == "darwin" && strings.Contains(runtime.Version(), "1.4") {
return
}
withTestProcess("cgotest", t, func(p *Process, fixture protest.Fixture) { withTestProcess("cgotest", t, func(p *Process, fixture protest.Fixture) {
testGSupportFunc("cgo", t, p, fixture) testGSupportFunc("cgo", t, p, fixture)
}) })

@ -10,6 +10,7 @@ type Registers interface {
PC() uint64 PC() uint64
SP() uint64 SP() uint64
CX() uint64 CX() uint64
TLS() uint64
SetPC(*Thread, uint64) error SetPC(*Thread, uint64) error
String() string String() string
} }

@ -8,27 +8,28 @@ import (
) )
type Regs struct { type Regs struct {
rax uint64 rax uint64
rbx uint64 rbx uint64
rcx uint64 rcx uint64
rdx uint64 rdx uint64
rdi uint64 rdi uint64
rsi uint64 rsi uint64
rbp uint64 rbp uint64
rsp uint64 rsp uint64
r8 uint64 r8 uint64
r9 uint64 r9 uint64
r10 uint64 r10 uint64
r11 uint64 r11 uint64
r12 uint64 r12 uint64
r13 uint64 r13 uint64
r14 uint64 r14 uint64
r15 uint64 r15 uint64
rip uint64 rip uint64
rflags uint64 rflags uint64
cs uint64 cs uint64
fs uint64 fs uint64
gs uint64 gs uint64
gs_base uint64
} }
func (r *Regs) String() string { func (r *Regs) String() string {
@ -58,6 +59,7 @@ func (r *Regs) String() string {
{"Cs", r.cs}, {"Cs", r.cs},
{"Fs", r.fs}, {"Fs", r.fs},
{"Gs", r.gs}, {"Gs", r.gs},
{"Gs_base", r.gs_base},
} }
for _, reg := range regs { for _, reg := range regs {
fmt.Fprintf(&buf, "%s = 0x%x\n", reg.k, reg.v) fmt.Fprintf(&buf, "%s = 0x%x\n", reg.k, reg.v)
@ -77,6 +79,10 @@ func (r *Regs) CX() uint64 {
return r.rcx return r.rcx
} }
func (r *Regs) TLS() uint64 {
return r.gs_base
}
func (r *Regs) SetPC(thread *Thread, pc uint64) error { func (r *Regs) SetPC(thread *Thread, pc uint64) error {
kret := C.set_pc(thread.os.thread_act, C.uint64_t(pc)) kret := C.set_pc(thread.os.thread_act, C.uint64_t(pc))
if kret != C.KERN_SUCCESS { if kret != C.KERN_SUCCESS {
@ -87,32 +93,50 @@ func (r *Regs) SetPC(thread *Thread, pc uint64) error {
func registers(thread *Thread) (Registers, error) { func registers(thread *Thread) (Registers, error) {
var state C.x86_thread_state64_t var state C.x86_thread_state64_t
var identity C.thread_identifier_info_data_t
kret := C.get_registers(C.mach_port_name_t(thread.os.thread_act), &state) kret := C.get_registers(C.mach_port_name_t(thread.os.thread_act), &state)
if kret != C.KERN_SUCCESS { if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not get registers") return nil, fmt.Errorf("could not get registers")
} }
kret = C.get_identity(C.mach_port_name_t(thread.os.thread_act), &identity)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not get thread identity informations")
}
/*
thread_identifier_info::thread_handle contains the base of the
thread-specific data area, which on x86 and x86_64 is the threads base
address of the %gs segment. 10.9.2 xnu-2422.90.20/osfmk/kern/thread.c
thread_info_internal() gets the value from
machine_thread::cthread_self, which is the same value used to set the
%gs base in xnu-2422.90.20/osfmk/i386/pcb_native.c
act_machine_switch_pcb().
--
comment copied from chromium's crashpad
https://chromium.googlesource.com/crashpad/crashpad/+/master/snapshot/mac/process_reader.cc
*/
regs := &Regs{ regs := &Regs{
rax: uint64(state.__rax), rax: uint64(state.__rax),
rbx: uint64(state.__rbx), rbx: uint64(state.__rbx),
rcx: uint64(state.__rcx), rcx: uint64(state.__rcx),
rdx: uint64(state.__rdx), rdx: uint64(state.__rdx),
rdi: uint64(state.__rdi), rdi: uint64(state.__rdi),
rsi: uint64(state.__rsi), rsi: uint64(state.__rsi),
rbp: uint64(state.__rbp), rbp: uint64(state.__rbp),
rsp: uint64(state.__rsp), rsp: uint64(state.__rsp),
r8: uint64(state.__r8), r8: uint64(state.__r8),
r9: uint64(state.__r9), r9: uint64(state.__r9),
r10: uint64(state.__r10), r10: uint64(state.__r10),
r11: uint64(state.__r11), r11: uint64(state.__r11),
r12: uint64(state.__r12), r12: uint64(state.__r12),
r13: uint64(state.__r13), r13: uint64(state.__r13),
r14: uint64(state.__r14), r14: uint64(state.__r14),
r15: uint64(state.__r15), r15: uint64(state.__r15),
rip: uint64(state.__rip), rip: uint64(state.__rip),
rflags: uint64(state.__rflags), rflags: uint64(state.__rflags),
cs: uint64(state.__cs), cs: uint64(state.__cs),
fs: uint64(state.__fs), fs: uint64(state.__fs),
gs: uint64(state.__gs), gs: uint64(state.__gs),
gs_base: uint64(identity.thread_handle),
} }
return regs, nil return regs, nil
} }

@ -60,6 +60,10 @@ func (r *Regs) CX() uint64 {
return r.regs.Rcx return r.regs.Rcx
} }
func (r *Regs) TLS() uint64 {
return r.regs.Fs_base
}
func (r *Regs) SetPC(thread *Thread, pc uint64) (err error) { func (r *Regs) SetPC(thread *Thread, pc uint64) (err error) {
r.regs.SetPC(pc) r.regs.SetPC(pc)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.Id, r.regs) }) thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.Id, r.regs) })

@ -2,6 +2,7 @@ package proc
import ( import (
"debug/gosym" "debug/gosym"
"encoding/binary"
"fmt" "fmt"
"path/filepath" "path/filepath"
@ -257,65 +258,22 @@ func (thread *Thread) SetPC(pc uint64) error {
// opposed to the runtime version. This has the consequence of not setting M.id for // opposed to the runtime version. This has the consequence of not setting M.id for
// any thread, regardless of OS. // any thread, regardless of OS.
// //
// In order to get around all this craziness, we write the instructions to retrieve the G // In order to get around all this craziness, we read the address of the G structure for
// structure running on this thread (which is stored in thread local memory) into the // the current thread from the thread local storage area.
// current instruction stream. The instructions are obviously arch/os dependant, as they
// vary on how thread local storage is implemented, which MMU register is used and
// what the offset into thread local storage is.
func (thread *Thread) GetG() (g *G, err error) { func (thread *Thread) GetG() (g *G, err error) {
var pcInt uint64
pcInt, err = thread.PC()
if err != nil {
return
}
pc := uintptr(pcInt)
// Read original instructions.
originalInstructions := make([]byte, len(thread.dbp.arch.CurgInstructions()))
if _, err = readMemory(thread, pc, originalInstructions); err != nil {
return
}
// Write new instructions.
if _, err = writeMemory(thread, pc, thread.dbp.arch.CurgInstructions()); err != nil {
return
}
// We're going to be intentionally modifying the registers
// once we execute the code we inject into the instruction stream,
// so save them off here so we can restore them later.
if _, err = thread.saveRegisters(); err != nil {
return
}
// Ensure original instructions and PC are both restored.
defer func() {
// Do not shadow previous error, if there was one.
originalErr := err
// Restore the original instructions and register contents.
if _, err = writeMemory(thread, pc, originalInstructions); err != nil {
return
}
if err = thread.restoreRegisters(); err != nil {
return
}
err = originalErr
return
}()
// Execute new instructions.
if err = thread.resume(); err != nil {
return
}
// Set the halt flag so that trapWait will ignore the fact that
// we hit a breakpoint that isn't captured in our list of
// known breakpoints.
thread.dbp.halt = true
defer func(dbp *Process) { dbp.halt = false }(thread.dbp)
if _, err = thread.dbp.trapWait(-1); err != nil {
return
}
// Grab *G from RCX.
regs, err := thread.Registers() regs, err := thread.Registers()
if err != nil { if err != nil {
return nil, err return nil, err
} }
g, err = parseG(thread, regs.CX(), false)
gaddrbs := make([]byte, 8)
_, err = readMemory(thread, uintptr(regs.TLS()+thread.dbp.arch.GStructOffset()), gaddrbs)
if err != nil {
return nil, err
}
gaddr := binary.LittleEndian.Uint64(gaddrbs)
g, err = parseG(thread, gaddr, false)
if err == nil { if err == nil {
g.thread = thread g.thread = thread
} }

@ -46,11 +46,16 @@ kern_return_t
get_registers(mach_port_name_t task, x86_thread_state64_t *state) { get_registers(mach_port_name_t task, x86_thread_state64_t *state) {
kern_return_t kret; kern_return_t kret;
mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT; mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT;
// TODO(dp) - possible memory leak - vm_deallocate state // TODO(dp) - possible memory leak - vm_deallocate state
return thread_get_state(task, x86_THREAD_STATE64, (thread_state_t)state, &stateCount); return thread_get_state(task, x86_THREAD_STATE64, (thread_state_t)state, &stateCount);
} }
kern_return_t
get_identity(mach_port_name_t task, thread_identifier_info_data_t *idinfo) {
mach_msg_type_number_t idinfoCount = THREAD_IDENTIFIER_INFO_COUNT;
return thread_info(task, THREAD_IDENTIFIER_INFO, (thread_info_t)idinfo, &idinfoCount);
}
kern_return_t kern_return_t
set_registers(mach_port_name_t task, x86_thread_state64_t *state) { set_registers(mach_port_name_t task, x86_thread_state64_t *state) {
mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT; mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT;

@ -27,3 +27,6 @@ resume_thread(thread_act_t);
kern_return_t kern_return_t
set_registers(mach_port_name_t, x86_thread_state64_t*); set_registers(mach_port_name_t, x86_thread_state64_t*);
kern_return_t
get_identity(mach_port_name_t, thread_identifier_info_data_t *);