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

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

@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
@ -617,6 +618,11 @@ func TestGetG(t *testing.T) {
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) {
testGSupportFunc("cgo", t, p, fixture)
})

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

@ -8,27 +8,28 @@ import (
)
type Regs struct {
rax uint64
rbx uint64
rcx uint64
rdx uint64
rdi uint64
rsi uint64
rbp uint64
rsp uint64
r8 uint64
r9 uint64
r10 uint64
r11 uint64
r12 uint64
r13 uint64
r14 uint64
r15 uint64
rip uint64
rflags uint64
cs uint64
fs uint64
gs uint64
rax uint64
rbx uint64
rcx uint64
rdx uint64
rdi uint64
rsi uint64
rbp uint64
rsp uint64
r8 uint64
r9 uint64
r10 uint64
r11 uint64
r12 uint64
r13 uint64
r14 uint64
r15 uint64
rip uint64
rflags uint64
cs uint64
fs uint64
gs uint64
gs_base uint64
}
func (r *Regs) String() string {
@ -58,6 +59,7 @@ func (r *Regs) String() string {
{"Cs", r.cs},
{"Fs", r.fs},
{"Gs", r.gs},
{"Gs_base", r.gs_base},
}
for _, reg := range regs {
fmt.Fprintf(&buf, "%s = 0x%x\n", reg.k, reg.v)
@ -77,6 +79,10 @@ func (r *Regs) CX() uint64 {
return r.rcx
}
func (r *Regs) TLS() uint64 {
return r.gs_base
}
func (r *Regs) SetPC(thread *Thread, pc uint64) error {
kret := C.set_pc(thread.os.thread_act, C.uint64_t(pc))
if kret != C.KERN_SUCCESS {
@ -87,32 +93,50 @@ func (r *Regs) SetPC(thread *Thread, pc uint64) error {
func registers(thread *Thread) (Registers, error) {
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)
if kret != C.KERN_SUCCESS {
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{
rax: uint64(state.__rax),
rbx: uint64(state.__rbx),
rcx: uint64(state.__rcx),
rdx: uint64(state.__rdx),
rdi: uint64(state.__rdi),
rsi: uint64(state.__rsi),
rbp: uint64(state.__rbp),
rsp: uint64(state.__rsp),
r8: uint64(state.__r8),
r9: uint64(state.__r9),
r10: uint64(state.__r10),
r11: uint64(state.__r11),
r12: uint64(state.__r12),
r13: uint64(state.__r13),
r14: uint64(state.__r14),
r15: uint64(state.__r15),
rip: uint64(state.__rip),
rflags: uint64(state.__rflags),
cs: uint64(state.__cs),
fs: uint64(state.__fs),
gs: uint64(state.__gs),
rax: uint64(state.__rax),
rbx: uint64(state.__rbx),
rcx: uint64(state.__rcx),
rdx: uint64(state.__rdx),
rdi: uint64(state.__rdi),
rsi: uint64(state.__rsi),
rbp: uint64(state.__rbp),
rsp: uint64(state.__rsp),
r8: uint64(state.__r8),
r9: uint64(state.__r9),
r10: uint64(state.__r10),
r11: uint64(state.__r11),
r12: uint64(state.__r12),
r13: uint64(state.__r13),
r14: uint64(state.__r14),
r15: uint64(state.__r15),
rip: uint64(state.__rip),
rflags: uint64(state.__rflags),
cs: uint64(state.__cs),
fs: uint64(state.__fs),
gs: uint64(state.__gs),
gs_base: uint64(identity.thread_handle),
}
return regs, nil
}

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

@ -2,6 +2,7 @@ package proc
import (
"debug/gosym"
"encoding/binary"
"fmt"
"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
// any thread, regardless of OS.
//
// In order to get around all this craziness, we write the instructions to retrieve the G
// structure running on this thread (which is stored in thread local memory) into the
// 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.
// In order to get around all this craziness, we read the address of the G structure for
// the current thread from the thread local storage area.
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()
if err != nil {
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 {
g.thread = thread
}

@ -46,11 +46,16 @@ kern_return_t
get_registers(mach_port_name_t task, x86_thread_state64_t *state) {
kern_return_t kret;
mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT;
// TODO(dp) - possible memory leak - vm_deallocate state
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
set_registers(mach_port_name_t task, x86_thread_state64_t *state) {
mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT;

@ -27,3 +27,6 @@ resume_thread(thread_act_t);
kern_return_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 *);