From 0933a681cfd9b3a199fd8c2be1224a54faa96b9f Mon Sep 17 00:00:00 2001 From: aarzilli Date: Tue, 28 Jul 2015 07:33:07 +0200 Subject: [PATCH] proc.(*Thread).GetG: reading TLS memory directly for g address instead of modifying the executable code --- proc/arch.go | 34 +++------ proc/{version.go => go_version.go} | 0 proc/proc.go | 7 +- proc/proc_test.go | 6 ++ proc/registers.go | 1 + proc/registers_darwin_amd64.go | 108 ++++++++++++++++++----------- proc/registers_linux_amd64.go | 4 ++ proc/threads.go | 66 ++++-------------- proc/threads_darwin.c | 7 +- proc/threads_darwin.h | 3 + 10 files changed, 111 insertions(+), 125 deletions(-) rename proc/{version.go => go_version.go} (100%) diff --git a/proc/arch.go b/proc/arch.go index 6fb7482c..48f15531 100644 --- a/proc/arch.go +++ b/proc/arch.go @@ -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 { diff --git a/proc/version.go b/proc/go_version.go similarity index 100% rename from proc/version.go rename to proc/go_version.go diff --git a/proc/proc.go b/proc/proc.go index 61748f4b..b9b3e780 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -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 } diff --git a/proc/proc_test.go b/proc/proc_test.go index 8e09ca0f..145bcd2d 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -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) }) diff --git a/proc/registers.go b/proc/registers.go index e37dc9d1..add88988 100644 --- a/proc/registers.go +++ b/proc/registers.go @@ -10,6 +10,7 @@ type Registers interface { PC() uint64 SP() uint64 CX() uint64 + TLS() uint64 SetPC(*Thread, uint64) error String() string } diff --git a/proc/registers_darwin_amd64.go b/proc/registers_darwin_amd64.go index 411bac2f..50933b0c 100644 --- a/proc/registers_darwin_amd64.go +++ b/proc/registers_darwin_amd64.go @@ -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 thread’s 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 } diff --git a/proc/registers_linux_amd64.go b/proc/registers_linux_amd64.go index 69821afe..85ef8bff 100644 --- a/proc/registers_linux_amd64.go +++ b/proc/registers_linux_amd64.go @@ -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) }) diff --git a/proc/threads.go b/proc/threads.go index c109f357..68ba135c 100644 --- a/proc/threads.go +++ b/proc/threads.go @@ -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 } diff --git a/proc/threads_darwin.c b/proc/threads_darwin.c index 3879ac8e..a0988b68 100644 --- a/proc/threads_darwin.c +++ b/proc/threads_darwin.c @@ -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; diff --git a/proc/threads_darwin.h b/proc/threads_darwin.h index 93310f71..156c410f 100644 --- a/proc/threads_darwin.h +++ b/proc/threads_darwin.h @@ -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 *);