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"
|
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
|
||||||
}
|
}
|
||||||
|
@ -8,27 +8,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Regs struct {
|
type Regs struct {
|
||||||
rax uint64
|
rax uint64
|
||||||
rbx uint64
|
rbx uint64
|
||||||
rcx uint64
|
rcx uint64
|
||||||
rdx uint64
|
rdx uint64
|
||||||
rdi uint64
|
rdi uint64
|
||||||
rsi uint64
|
rsi uint64
|
||||||
rbp uint64
|
rbp uint64
|
||||||
rsp uint64
|
rsp uint64
|
||||||
r8 uint64
|
r8 uint64
|
||||||
r9 uint64
|
r9 uint64
|
||||||
r10 uint64
|
r10 uint64
|
||||||
r11 uint64
|
r11 uint64
|
||||||
r12 uint64
|
r12 uint64
|
||||||
r13 uint64
|
r13 uint64
|
||||||
r14 uint64
|
r14 uint64
|
||||||
r15 uint64
|
r15 uint64
|
||||||
rip uint64
|
rip uint64
|
||||||
rflags uint64
|
rflags uint64
|
||||||
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,32 +93,50 @@ 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 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{
|
regs := &Regs{
|
||||||
rax: uint64(state.__rax),
|
rax: uint64(state.__rax),
|
||||||
rbx: uint64(state.__rbx),
|
rbx: uint64(state.__rbx),
|
||||||
rcx: uint64(state.__rcx),
|
rcx: uint64(state.__rcx),
|
||||||
rdx: uint64(state.__rdx),
|
rdx: uint64(state.__rdx),
|
||||||
rdi: uint64(state.__rdi),
|
rdi: uint64(state.__rdi),
|
||||||
rsi: uint64(state.__rsi),
|
rsi: uint64(state.__rsi),
|
||||||
rbp: uint64(state.__rbp),
|
rbp: uint64(state.__rbp),
|
||||||
rsp: uint64(state.__rsp),
|
rsp: uint64(state.__rsp),
|
||||||
r8: uint64(state.__r8),
|
r8: uint64(state.__r8),
|
||||||
r9: uint64(state.__r9),
|
r9: uint64(state.__r9),
|
||||||
r10: uint64(state.__r10),
|
r10: uint64(state.__r10),
|
||||||
r11: uint64(state.__r11),
|
r11: uint64(state.__r11),
|
||||||
r12: uint64(state.__r12),
|
r12: uint64(state.__r12),
|
||||||
r13: uint64(state.__r13),
|
r13: uint64(state.__r13),
|
||||||
r14: uint64(state.__r14),
|
r14: uint64(state.__r14),
|
||||||
r15: uint64(state.__r15),
|
r15: uint64(state.__r15),
|
||||||
rip: uint64(state.__rip),
|
rip: uint64(state.__rip),
|
||||||
rflags: uint64(state.__rflags),
|
rflags: uint64(state.__rflags),
|
||||||
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 *);
|
||||||
|
Loading…
Reference in New Issue
Block a user