proc.(*Thread).GetG: reading TLS memory directly for g address instead of modifying the executable code
This commit is contained in:
parent
d0f3459efb
commit
0933a681cf
34
proc/arch.go
34
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 {
|
||||
|
@ -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 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
|
||||
}
|
||||
|
@ -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 *);
|
||||
|
Loading…
Reference in New Issue
Block a user