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
a.gStructOffset = 0xfffffffffffffff0
}
} 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 {
@ -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
}

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