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
} }

@ -29,6 +29,7 @@ type Regs struct {
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,10 +93,27 @@ 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),
@ -113,6 +136,7 @@ func registers(thread *Thread) (Registers, error) {
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 *);