proc: load more registers

Adds ability to load x87, SSE and AVX registers.

Fixes #666
This commit is contained in:
aarzilli
2016-11-15 17:16:33 +01:00
parent 63b8fa8269
commit 8f0646e426
29 changed files with 701 additions and 92 deletions

View File

@ -29,6 +29,7 @@ Command | Description
[stack](#stack) | Print stack trace.
[step](#step) | Single step through program.
[step-instruction](#step-instruction) | Single step a single cpu instruction.
[stepout](#stepout) | Step out of the current function.
[thread](#thread) | Switch to the specified thread.
[threads](#threads) | Print out info for every traced thread.
[trace](#trace) | Set tracepoint.
@ -195,6 +196,10 @@ Aliases: p
## regs
Print contents of CPU registers.
regs [-a]
Argument -a shows more registers.
## restart
Restart process.
@ -242,6 +247,10 @@ Single step a single cpu instruction.
Aliases: si
## stepout
Step out of the current function.
## thread
Switch to the specified thread.
@ -278,3 +287,5 @@ Print package variables.
vars [-v] [<regex>]
If regex is specified only package variables with a name matching it will be returned. If -v is specified more information about each package variable will be shown.

View File

@ -0,0 +1,19 @@
package main
import "runtime"
func fputestsetup(f64a, f64b, f64c, f64d float64, f32a, f32b, f32c, f32d float32)
func main() {
var f64a float64 = 1.1
var f64b float64 = 1.2
var f64c float64 = 1.3
var f64d float64 = 1.4
var f32a float32 = 1.5
var f32b float32 = 1.6
var f32c float32 = 1.7
var f32d float32 = 1.8
fputestsetup(f64a, f64b, f64c, f64d, f32a, f32b, f32c, f32d)
runtime.Breakpoint()
}

View File

@ -0,0 +1,56 @@
TEXT ·fputestsetup(SB),$0-48
// setup x87 stack
FMOVD f64a+0(FP), F0
FMOVD f64b+8(FP), F0
FMOVD f64c+16(FP), F0
FMOVD f64d+24(FP), F0
FMOVF f32a+32(FP), F0
FMOVF f32b+36(FP), F0
FMOVF f32c+40(FP), F0
FMOVF f32d+44(FP), F0
// setup SSE registers
// XMM0 = { f64b, f64a } = { 1.2, 1.1 }
MOVLPS f64a+0(FP), X0
MOVHPS f64b+8(FP), X0
// XMM1 = { f64d, f64c } = { 1.4, 1.3 }
MOVLPS f64c+16(FP), X1
MOVHPS f64d+24(FP), X1
// XMM2 = { f32d, f32c, f32b, f32a } = { 1.8, 1.7, 1.6, 1.5 }
MOVQ f32a+32(FP), AX
MOVQ AX, X2
MOVQ f32c+40(FP), AX
MOVQ AX, X3
PUNPCKLQDQ X3, X2
// XMM3 = { f64a, f64b } = { 1.1, 1.2 }
MOVLPS f64b+8(FP), X3
MOVHPS f64a+0(FP), X3
// XMM4 = { f64c, f64d } = { 1.3, 1.4 }
MOVLPS f64d+24(FP), X4
MOVHPS f64c+16(FP), X4
// XMM5 = { f32b, f32a, f32d, f32c } = { 1.6, 1.5, 1.8, 1.7 }
MOVQ f32c+40(FP), AX
MOVQ AX, X5
MOVQ f32a+32(FP), AX
MOVQ AX, X6
PUNPCKLQDQ X6, X5
// XMM6 = XMM0 + XMM1 = { f64b+f64d, f64a+f64c } = { 2.6, 2.4 }
MOVAPS X0,X6
ADDPD X1, X6
// XMM7 = XMM0 + XMM3 = { f64b+f64a, f64a+f64b } = { 2.3, 2.3 }
MOVAPS X0, X7
ADDPD X3, X7
// XMM8 = XMM2 + XMM5 = { f32d+f32b, f32c+f32a, f32b+f32d, f32a+f32c } = { 3.4, 3.2, 3.4, 3.2 }
MOVAPS X2, X8
ADDPS X5, X8
RET

View File

@ -34,7 +34,7 @@ func (thread *Thread) Disassemble(startPC, endPC uint64, currentGoroutine bool)
var curpc uint64
var regs Registers
if currentGoroutine {
regs, _ = thread.Registers()
regs, _ = thread.Registers(false)
if regs != nil {
curpc = regs.PC()
}

View File

@ -88,7 +88,7 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs
if arg.Segment != 0 {
return nil
}
regs, err := thread.Registers()
regs, err := thread.Registers(false)
if err != nil {
return nil
}

View File

@ -689,7 +689,7 @@ func (dbp *Process) Halt() (err error) {
// Registers obtains register values from the
// "current" thread of the traced process.
func (dbp *Process) Registers() (Registers, error) {
return dbp.CurrentThread.Registers()
return dbp.CurrentThread.Registers(false)
}
// PC returns the PC of the current thread.

View File

@ -163,7 +163,7 @@ func TestHalt(t *testing.T) {
if th.running != false {
t.Fatal("expected running = false for thread", th.ID)
}
_, err := th.Registers()
_, err := th.Registers(false)
assertNoError(err, t, "Registers")
}
go func() {
@ -189,7 +189,7 @@ func TestHalt(t *testing.T) {
if th.running != false {
t.Fatal("expected running = false for thread", th.ID)
}
_, err := th.Registers()
_, err := th.Registers(false)
assertNoError(err, t, "Registers")
}
})
@ -676,6 +676,9 @@ func TestCGONext(t *testing.T) {
if runtime.GOOS == "darwin" && strings.Contains(runtime.Version(), "1.4") {
return
}
if os.Getenv("CGO_ENABLED") == "" {
return
}
withTestProcess("cgotest", t, func(p *Process, fixture protest.Fixture) {
pc, err := p.FindFunctionLocation("main.main", true, 0)
@ -895,6 +898,9 @@ func TestGetG(t *testing.T) {
if runtime.GOOS == "darwin" && strings.Contains(runtime.Version(), "1.4") {
return
}
if os.Getenv("CGO_ENABLED") == "" {
return
}
withTestProcess("cgotest", t, func(p *Process, fixture protest.Fixture) {
testGSupportFunc("cgo", t, p, fixture)

View File

@ -1,6 +1,7 @@
package proc
import (
"encoding/binary"
"syscall"
"unsafe"
@ -49,3 +50,47 @@ func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
}
return val, nil
}
// PtraceGetRegset returns floating point registers of the specified thread
// using PTRACE.
// See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html
// and amd64_supply_xsave in gdb/amd64-tdep.c.html
// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developers Manual, Volume 1: Basic Architecture
func PtraceGetRegset(tid int) (regset PtraceXsave, err error) {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(&regset.PtraceFpRegs)), 0, 0)
if err == syscall.Errno(0) {
err = nil
}
var xstateargs [_X86_XSTATE_MAX_SIZE]byte
iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE}
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
if err != syscall.Errno(0) {
return
} else {
err = nil
}
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= iov.Len {
return
}
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
if xcomp_bv&(1<<63) != 0 {
// compact format not supported
return
}
if xstate_bv&(1<<2) == 0 {
// AVX state not present
return
}
avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:iov.Len]
regset.AvxState = true
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
return
}

View File

@ -1,6 +1,14 @@
package proc
import "errors"
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"os"
"strings"
)
// Registers is an interface for a generic register type. The
// interface encapsulates the generic values / actions
@ -13,21 +21,210 @@ type Registers interface {
TLS() uint64
Get(int) (uint64, error)
SetPC(*Thread, uint64) error
String() string
Slice() []Register
}
type Register struct {
Name string
Value string
}
func appendWordReg(regs []Register, name string, value uint16) []Register {
return append(regs, Register{name, fmt.Sprintf("%#04x", value)})
}
func appendDwordReg(regs []Register, name string, value uint32) []Register {
return append(regs, Register{name, fmt.Sprintf("%#08x", value)})
}
func appendQwordReg(regs []Register, name string, value uint64) []Register {
return append(regs, Register{name, fmt.Sprintf("%#016x", value)})
}
func appendFlagReg(regs []Register, name string, value uint64, descr flagRegisterDescr, size int) []Register {
return append(regs, Register{name, descr.Describe(value, size)})
}
func appendX87Reg(regs []Register, index int, exponent uint16, mantissa uint64) []Register {
var f float64
fset := false
const (
_SIGNBIT = 1 << 15
_EXP_BIAS = (1 << 14) - 1 // 2^(n-1) - 1 = 16383
_SPECIALEXP = (1 << 15) - 1 // all bits set
_HIGHBIT = 1 << 63
_QUIETBIT = 1 << 62
)
sign := 1.0
if exponent&_SIGNBIT != 0 {
sign = -1.0
}
exponent &= ^uint16(_SIGNBIT)
NaN := math.NaN()
Inf := math.Inf(+1)
switch exponent {
case 0:
switch {
case mantissa == 0:
f = sign * 0.0
fset = true
case mantissa&_HIGHBIT != 0:
f = NaN
fset = true
}
case _SPECIALEXP:
switch {
case mantissa&_HIGHBIT == 0:
f = sign * Inf
fset = true
default:
f = NaN // signaling NaN
fset = true
}
default:
if mantissa&_HIGHBIT == 0 {
f = NaN
fset = true
}
}
if !fset {
significand := float64(mantissa) / (1 << 63)
f = sign * math.Ldexp(significand, int(exponent-_EXP_BIAS))
}
return append(regs, Register{fmt.Sprintf("ST(%d)", index), fmt.Sprintf("%#04x%016x\t%g", exponent, mantissa, f)})
}
func appendSSEReg(regs []Register, name string, xmm []byte) []Register {
buf := bytes.NewReader(xmm)
var out bytes.Buffer
var vi [16]uint8
for i := range vi {
binary.Read(buf, binary.LittleEndian, &vi[i])
}
fmt.Fprintf(&out, "0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8], vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0])
fmt.Fprintf(&out, "\tv2_int={ %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x%02x%02x%02x%02x }", vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0], vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8])
fmt.Fprintf(&out, "\tv4_int={ %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x }", vi[3], vi[2], vi[1], vi[0], vi[7], vi[6], vi[5], vi[4], vi[11], vi[10], vi[9], vi[8], vi[15], vi[14], vi[13], vi[12])
fmt.Fprintf(&out, "\tv8_int={ %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x }", vi[1], vi[0], vi[3], vi[2], vi[5], vi[4], vi[7], vi[6], vi[9], vi[8], vi[11], vi[10], vi[13], vi[12], vi[15], vi[14])
fmt.Fprintf(&out, "\tv16_int={ %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x }", vi[0], vi[1], vi[2], vi[3], vi[4], vi[5], vi[6], vi[7], vi[8], vi[9], vi[10], vi[11], vi[12], vi[13], vi[14], vi[15])
buf.Seek(0, os.SEEK_SET)
var v2 [2]float64
for i := range v2 {
binary.Read(buf, binary.LittleEndian, &v2[i])
}
fmt.Fprintf(&out, "\tv2_float={ %g %g }", v2[0], v2[1])
buf.Seek(0, os.SEEK_SET)
var v4 [4]float32
for i := range v4 {
binary.Read(buf, binary.LittleEndian, &v4[i])
}
fmt.Fprintf(&out, "\tv4_float={ %g %g %g %g }", v4[0], v4[1], v4[2], v4[3])
return append(regs, Register{name, out.String()})
}
var UnknownRegisterError = errors.New("unknown register")
// Registers obtains register values from the debugged process.
func (t *Thread) Registers() (Registers, error) {
return registers(t)
func (t *Thread) Registers(floatingPoint bool) (Registers, error) {
return registers(t, floatingPoint)
}
// PC returns the current PC for this thread.
func (t *Thread) PC() (uint64, error) {
regs, err := t.Registers()
regs, err := t.Registers(false)
if err != nil {
return 0, err
}
return regs.PC(), nil
}
type flagRegisterDescr []flagDescr
type flagDescr struct {
name string
mask uint64
}
var mxcsrDescription flagRegisterDescr = []flagDescr{
{"FZ", 1 << 15},
{"RZ/RN", 1<<14 | 1<<13},
{"PM", 1 << 12},
{"UM", 1 << 11},
{"OM", 1 << 10},
{"ZM", 1 << 9},
{"DM", 1 << 8},
{"IM", 1 << 7},
{"DAZ", 1 << 6},
{"PE", 1 << 5},
{"UE", 1 << 4},
{"OE", 1 << 3},
{"ZE", 1 << 2},
{"DE", 1 << 1},
{"IE", 1 << 0},
}
var eflagsDescription flagRegisterDescr = []flagDescr{
{"CF", 1 << 0},
{"", 1 << 1},
{"PF", 1 << 2},
{"AF", 1 << 4},
{"ZF", 1 << 6},
{"SF", 1 << 7},
{"TF", 1 << 8},
{"IF", 1 << 9},
{"DF", 1 << 10},
{"OF", 1 << 11},
{"IOPL", 1<<12 | 1<<13},
{"NT", 1 << 14},
{"RF", 1 << 16},
{"VM", 1 << 17},
{"AC", 1 << 18},
{"VIF", 1 << 19},
{"VIP", 1 << 20},
{"ID", 1 << 21},
}
func (descr flagRegisterDescr) Mask() uint64 {
var r uint64
for _, f := range descr {
r = r | f.mask
}
return r
}
func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string {
var r []string
for _, f := range descr {
if f.name == "" {
continue
}
// rbm is f.mask with only the right-most bit set:
// 0001 1100 -> 0000 0100
rbm := f.mask & -f.mask
if rbm == f.mask {
if reg&f.mask != 0 {
r = append(r, f.name)
}
} else {
x := (reg & f.mask) >> uint64(math.Log2(float64(rbm)))
r = append(r, fmt.Sprintf("%s=%x", f.name, x))
}
}
if reg & ^descr.Mask() != 0 {
r = append(r, fmt.Sprintf("unknown_flags=%x", reg&^descr.Mask()))
}
return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " "))
}

View File

@ -3,9 +3,10 @@ package proc
// #include "threads_darwin.h"
import "C"
import (
"bytes"
"encoding/binary"
"fmt"
"rsc.io/x86/x86asm"
"unsafe"
)
// Regs represents CPU registers on an AMD64 processor.
@ -32,10 +33,10 @@ type Regs struct {
fs uint64
gs uint64
gsBase uint64
fpregs []Register
}
func (r *Regs) String() string {
var buf bytes.Buffer
func (r *Regs) Slice() []Register {
var regs = []struct {
k string
v uint64
@ -63,10 +64,16 @@ func (r *Regs) String() string {
{"Gs", r.gs},
{"Gs_base", r.gsBase},
}
out := make([]Register, 0, len(regs)+len(r.fpregs))
for _, reg := range regs {
fmt.Fprintf(&buf, "%8s = %0#16x\n", reg.k, reg.v)
if reg.k == "Rflags" {
out = appendFlagReg(out, reg.k, reg.v, eflagsDescription, 64)
} else {
out = appendQwordReg(out, reg.k, reg.v)
}
return buf.String()
}
out = append(out, r.fpregs...)
return out
}
// PC returns the current program counter
@ -259,7 +266,7 @@ func (r *Regs) Get(n int) (uint64, error) {
return 0, UnknownRegisterError
}
func registers(thread *Thread) (Registers, error) {
func registers(thread *Thread, floatingPoint bool) (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.threadAct), &state)
@ -306,6 +313,36 @@ func registers(thread *Thread) (Registers, error) {
gs: uint64(state.__gs),
gsBase: uint64(identity.thread_handle),
}
if floatingPoint {
// https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/thread_status.h?txt
var fpstate C.x86_float_state64_t
kret = C.get_fpu_registers(C.mach_port_name_t(thread.os.threadAct), &fpstate)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not get floating point registers")
}
regs.fpregs = appendWordReg(regs.fpregs, "CW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fcw))))
regs.fpregs = appendWordReg(regs.fpregs, "SW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fsw))))
regs.fpregs = appendWordReg(regs.fpregs, "TW", uint16(fpstate.__fpu_ftw))
regs.fpregs = appendWordReg(regs.fpregs, "FOP", uint16(fpstate.__fpu_fop))
regs.fpregs = appendQwordReg(regs.fpregs, "FIP", uint64(fpstate.__fpu_cs)<<32|uint64(fpstate.__fpu_ip))
regs.fpregs = appendQwordReg(regs.fpregs, "FDP", uint64(fpstate.__fpu_ds)<<32|uint64(fpstate.__fpu_dp))
for i, st := range []*C.char{&fpstate.__fpu_stmm0.__mmst_reg[0], &fpstate.__fpu_stmm1.__mmst_reg[0], &fpstate.__fpu_stmm2.__mmst_reg[0], &fpstate.__fpu_stmm3.__mmst_reg[0], &fpstate.__fpu_stmm4.__mmst_reg[0], &fpstate.__fpu_stmm5.__mmst_reg[0], &fpstate.__fpu_stmm6.__mmst_reg[0], &fpstate.__fpu_stmm7.__mmst_reg[0]} {
stb := C.GoBytes(unsafe.Pointer(st), 10)
mantissa := binary.LittleEndian.Uint64(stb[:8])
exponent := binary.LittleEndian.Uint16(stb[8:])
regs.fpregs = appendX87Reg(regs.fpregs, i, exponent, mantissa)
}
regs.fpregs = appendFlagReg(regs.fpregs, "MXCSR", uint64(fpstate.__fpu_mxcsr), mxcsrDescription, 32)
regs.fpregs = appendDwordReg(regs.fpregs, "MXCSR_MASK", uint32(fpstate.__fpu_mxcsrmask))
for i, xmm := range []*C.char{&fpstate.__fpu_xmm0.__xmm_reg[0], &fpstate.__fpu_xmm1.__xmm_reg[0], &fpstate.__fpu_xmm2.__xmm_reg[0], &fpstate.__fpu_xmm3.__xmm_reg[0], &fpstate.__fpu_xmm4.__xmm_reg[0], &fpstate.__fpu_xmm5.__xmm_reg[0], &fpstate.__fpu_xmm6.__xmm_reg[0], &fpstate.__fpu_xmm7.__xmm_reg[0], &fpstate.__fpu_xmm8.__xmm_reg[0], &fpstate.__fpu_xmm9.__xmm_reg[0], &fpstate.__fpu_xmm10.__xmm_reg[0], &fpstate.__fpu_xmm11.__xmm_reg[0], &fpstate.__fpu_xmm12.__xmm_reg[0], &fpstate.__fpu_xmm13.__xmm_reg[0], &fpstate.__fpu_xmm14.__xmm_reg[0], &fpstate.__fpu_xmm15.__xmm_reg[0]} {
regs.fpregs = appendSSEReg(regs.fpregs, fmt.Sprintf("XMM%d", i), C.GoBytes(unsafe.Pointer(xmm), 16))
}
}
return regs, nil
}

View File

@ -1,17 +1,20 @@
package proc
import "fmt"
import "bytes"
import sys "golang.org/x/sys/unix"
import "rsc.io/x86/x86asm"
import (
"fmt"
"rsc.io/x86/x86asm"
sys "golang.org/x/sys/unix"
)
// Regs is a wrapper for sys.PtraceRegs.
type Regs struct {
regs *sys.PtraceRegs
fpregs []Register
}
func (r *Regs) String() string {
var buf bytes.Buffer
func (r *Regs) Slice() []Register {
var regs = []struct {
k string
v uint64
@ -44,10 +47,16 @@ func (r *Regs) String() string {
{"Fs", r.regs.Fs},
{"Gs", r.regs.Gs},
}
out := make([]Register, 0, len(regs)+len(r.fpregs))
for _, reg := range regs {
fmt.Fprintf(&buf, "%8s = %0#16x\n", reg.k, reg.v)
if reg.k == "Eflags" {
out = appendFlagReg(out, reg.k, reg.v, eflagsDescription, 64)
} else {
out = appendQwordReg(out, reg.k, reg.v)
}
return buf.String()
}
out = append(out, r.fpregs...)
return out
}
// PC returns the value of RIP register.
@ -235,7 +244,7 @@ func (r *Regs) Get(n int) (uint64, error) {
return 0, UnknownRegisterError
}
func registers(thread *Thread) (Registers, error) {
func registers(thread *Thread, floatingPoint bool) (Registers, error) {
var (
regs sys.PtraceRegs
err error
@ -244,5 +253,73 @@ func registers(thread *Thread) (Registers, error) {
if err != nil {
return nil, err
}
return &Regs{&regs}, nil
r := &Regs{&regs, nil}
if floatingPoint {
r.fpregs, err = thread.fpRegisters()
if err != nil {
return nil, err
}
}
return r, nil
}
// tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
type PtraceFpRegs struct {
Cwd uint16
Swd uint16
Ftw uint16
Fop uint16
Rip uint64
Rdp uint64
Mxcsr uint32
MxcrMask uint32
StSpace [32]uint32
XmmSpace [256]byte
padding [24]uint32
}
type PtraceXsave struct {
PtraceFpRegs
AvxState bool // contains AVX state
YmmSpace [256]byte
}
const (
_X86_XSTATE_MAX_SIZE = 2688
_NT_X86_XSTATE = 0x202
_XSAVE_HEADER_START = 512
_XSAVE_HEADER_LEN = 64
_XSAVE_EXTENDED_REGION_START = 576
_XSAVE_SSE_REGION_LEN = 416
)
func (thread *Thread) fpRegisters() (regs []Register, err error) {
var fpregs PtraceXsave
thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
// x87 registers
regs = appendWordReg(regs, "CW", fpregs.Cwd)
regs = appendWordReg(regs, "SW", fpregs.Swd)
regs = appendWordReg(regs, "TW", fpregs.Ftw)
regs = appendWordReg(regs, "FOP", fpregs.Fop)
regs = appendQwordReg(regs, "FIP", fpregs.Rip)
regs = appendQwordReg(regs, "FDP", fpregs.Rdp)
for i := 0; i < len(fpregs.StSpace); i += 4 {
regs = appendX87Reg(regs, i/4, uint16(fpregs.StSpace[i+2]), uint64(fpregs.StSpace[i+1])<<32|uint64(fpregs.StSpace[i]))
}
// SSE registers
regs = appendFlagReg(regs, "MXCSR", uint64(fpregs.Mxcsr), mxcsrDescription, 32)
regs = appendDwordReg(regs, "MXCSR_MASK", fpregs.MxcrMask)
for i := 0; i < len(fpregs.XmmSpace); i += 16 {
regs = appendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), fpregs.XmmSpace[i:i+16])
if fpregs.AvxState {
regs = appendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), fpregs.YmmSpace[i:i+16])
}
}
return
}

View File

@ -1,7 +1,6 @@
package proc
import (
"bytes"
"fmt"
"rsc.io/x86/x86asm"
"unsafe"
@ -31,10 +30,10 @@ type Regs struct {
fs uint64
gs uint64
tls uint64
fltSave *_XMM_SAVE_AREA32
}
func (r *Regs) String() string {
var buf bytes.Buffer
func (r *Regs) Slice() []Register {
var regs = []struct {
k string
v uint64
@ -62,10 +61,38 @@ func (r *Regs) String() string {
{"Gs", r.gs},
{"TLS", r.tls},
}
for _, reg := range regs {
fmt.Fprintf(&buf, "%8s = %0#16x\n", reg.k, reg.v)
outlen := len(regs)
if r.fltSave != nil {
outlen += 6 + 8 + 2 + 16
}
return buf.String()
out := make([]Register, 0, outlen)
for _, reg := range regs {
if reg.k == "Eflags" {
out = append(out, Register{reg.k, eflagsDescription.Describe(reg.v, 64)})
} else {
out = appendQwordReg(out, reg.k, reg.v)
}
}
if r.fltSave != nil {
out = appendWordReg(out, "CW", r.fltSave.ControlWord)
out = appendWordReg(out, "SW", r.fltSave.StatusWord)
out = appendWordReg(out, "TW", uint16(r.fltSave.TagWord))
out = appendWordReg(out, "FOP", r.fltSave.ErrorOpcode)
out = appendQwordReg(out, "FIP", uint64(r.fltSave.ErrorSelector)<<32|uint64(r.fltSave.ErrorOffset))
out = appendQwordReg(out, "FDP", uint64(r.fltSave.DataSelector)<<32|uint64(r.fltSave.DataOffset))
for i := range r.fltSave.FloatRegisters {
out = appendX87Reg(out, i, uint16(r.fltSave.FloatRegisters[i].High), r.fltSave.FloatRegisters[i].Low)
}
out = appendFlagReg(out, "MXCSR", uint64(r.fltSave.MxCsr), mxcsrDescription, 32)
out = appendDwordReg(out, "MXCSR_MASK", r.fltSave.MxCsr_Mask)
for i := 0; i < len(r.fltSave.XmmRegisters); i += 16 {
out = appendSSEReg(out, fmt.Sprintf("XMM%d", i/16), r.fltSave.XmmRegisters[i:i+16])
}
}
return out
}
// PC returns the current program counter
@ -264,7 +291,7 @@ func (r *Regs) Get(n int) (uint64, error) {
return 0, UnknownRegisterError
}
func registers(thread *Thread) (Registers, error) {
func registers(thread *Thread, floatingPoint bool) (Registers, error) {
context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL
@ -303,6 +330,11 @@ func registers(thread *Thread) (Registers, error) {
gs: uint64(context.SegGs),
tls: uint64(threadInfo.TebBaseAddress),
}
if floatingPoint {
regs.fltSave = &context.FltSave
}
return regs, nil
}

View File

@ -50,7 +50,7 @@ func (t *Thread) ReturnAddress() (uint64, error) {
}
func (t *Thread) stackIterator() (*stackIterator, error) {
regs, err := t.Registers()
regs, err := t.Registers(false)
if err != nil {
return nil, err
}

View File

@ -22,6 +22,25 @@ type _M128A struct {
High int64
}
type _XMM_SAVE_AREA32 struct {
ControlWord uint16
StatusWord uint16
TagWord byte
Reserved1 byte
ErrorOpcode uint16
ErrorOffset uint32
ErrorSelector uint16
Reserved2 uint16
DataOffset uint32
DataSelector uint16
Reserved3 uint16
MxCsr uint32
MxCsr_Mask uint32
FloatRegisters [8]_M128A
XmmRegisters [256]byte
Reserved4 [96]byte
}
type _CONTEXT struct {
P1Home uint64
P2Home uint64
@ -67,7 +86,7 @@ type _CONTEXT struct {
Rip uint64
FltSave [512]byte
FltSave _XMM_SAVE_AREA32
VectorRegister [26]_M128A
VectorControl uint64

View File

@ -46,7 +46,13 @@ func BuildFixture(name string) Fixture {
// Make a (good enough) random temporary file name
r := make([]byte, 4)
rand.Read(r)
dir := fixturesDir
path := filepath.Join(fixturesDir, name+".go")
if name[len(name)-1] == '/' {
dir = filepath.Join(dir, name)
path = ""
name = name[:len(name)-1]
}
tmpfile := filepath.Join(os.TempDir(), fmt.Sprintf("%s.%s", name, hex.EncodeToString(r)))
buildFlags := []string{"build"}
@ -54,10 +60,13 @@ func BuildFixture(name string) Fixture {
// Work-around for https://github.com/golang/go/issues/13154
buildFlags = append(buildFlags, "-ldflags=-linkmode internal")
}
buildFlags = append(buildFlags, "-gcflags=-N -l", "-o", tmpfile, name+".go")
buildFlags = append(buildFlags, "-gcflags=-N -l", "-o", tmpfile)
if path != "" {
buildFlags = append(buildFlags, name+".go")
}
cmd := exec.Command("go", buildFlags...)
cmd.Dir = fixturesDir
cmd.Dir = dir
// Build the test binary
if err := cmd.Run(); err != nil {

View File

@ -316,7 +316,7 @@ func (dbp *Process) setInternalBreakpoints(curpc uint64, pcs []uint64, kind Brea
// SetPC sets the PC for this thread.
func (thread *Thread) SetPC(pc uint64) error {
regs, err := thread.Registers()
regs, err := thread.Registers(false)
if err != nil {
return err
}
@ -324,7 +324,7 @@ func (thread *Thread) SetPC(pc uint64) error {
}
func (thread *Thread) getGVariable() (*Variable, error) {
regs, err := thread.Registers()
regs, err := thread.Registers(false)
if err != nil {
return nil, err
}

View File

@ -50,6 +50,13 @@ get_registers(mach_port_name_t task, x86_thread_state64_t *state) {
return thread_get_state(task, x86_THREAD_STATE64, (thread_state_t)state, &stateCount);
}
kern_return_t
get_fpu_registers(mach_port_name_t task, x86_float_state64_t *state) {
kern_return_t kret;
mach_msg_type_number_t stateCount = x86_FLOAT_STATE64_COUNT;
return thread_get_state(task, x86_FLOAT_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;

View File

@ -13,6 +13,9 @@ read_memory(task_t, mach_vm_address_t, void *, mach_msg_type_number_t);
kern_return_t
get_registers(mach_port_name_t, x86_thread_state64_t*);
kern_return_t
get_fpu_registers(mach_port_name_t, x86_float_state64_t *);
kern_return_t
set_pc(thread_act_t, uint64_t);

View File

@ -82,7 +82,7 @@ func (t *Thread) saveRegisters() (Registers, error) {
if err != nil {
return nil, fmt.Errorf("could not save register contents")
}
return &Regs{&t.os.registers}, nil
return &Regs{&t.os.registers, nil}, nil
}
func (t *Thread) restoreRegisters() (err error) {

View File

@ -9,8 +9,8 @@ import (
"reflect"
"strconv"
"golang.org/x/debug/dwarf"
"github.com/derekparker/delve/proc"
"golang.org/x/debug/dwarf"
)
// ConvertBreakpoint converts from a proc.Breakpoint to
@ -254,3 +254,11 @@ func LoadConfigFromProc(cfg *proc.LoadConfig) *LoadConfig {
cfg.MaxStructFields,
}
}
func ConvertRegisters(in []proc.Register) (out []Register) {
out = make([]Register, len(in))
for i := range in {
out[i] = Register{in[i].Name, in[i].Value}
}
return
}

View File

@ -1,6 +1,7 @@
package api
import (
"bytes"
"errors"
"fmt"
"reflect"
@ -290,3 +291,25 @@ type SetAPIVersionIn struct {
type SetAPIVersionOut struct {
}
type Register struct {
Name string
Value string
}
type Registers []Register
func (regs Registers) String() string {
maxlen := 0
for _, reg := range regs {
if n := len(reg.Name); n > maxlen {
maxlen = n
}
}
var buf bytes.Buffer
for _, reg := range regs {
fmt.Fprintf(&buf, "%*s = %s\n", maxlen, reg.Name, reg.Value)
}
return buf.String()
}

View File

@ -79,7 +79,7 @@ type Client interface {
// ListFunctionArgs lists all arguments to the current function.
ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error)
// ListRegisters lists registers and their values.
ListRegisters() (string, error)
ListRegisters(threadID int, includeFp bool) (api.Registers, error)
// ListGoroutines lists all goroutines.
ListGoroutines() ([]*api.Goroutine, error)

View File

@ -602,19 +602,19 @@ func (d *Debugger) PackageVariables(threadID int, filter string, cfg proc.LoadCo
}
// Registers returns string representation of the CPU registers.
func (d *Debugger) Registers(threadID int) (string, error) {
func (d *Debugger) Registers(threadID int, floatingPoint bool) (api.Registers, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
thread, found := d.process.Threads[threadID]
if !found {
return "", fmt.Errorf("couldn't find thread %d", threadID)
return nil, fmt.Errorf("couldn't find thread %d", threadID)
}
regs, err := thread.Registers()
regs, err := thread.Registers(floatingPoint)
if err != nil {
return "", err
return nil, err
}
return regs.String(), err
return api.ConvertRegisters(regs.Slice()), err
}
func convertVars(pv []*proc.Variable) []api.Variable {

View File

@ -199,11 +199,11 @@ func (s *RPCServer) ListRegisters(arg interface{}, registers *string) error {
return err
}
regs, err := s.debugger.Registers(state.CurrentThread.ID)
regs, err := s.debugger.Registers(state.CurrentThread.ID, false)
if err != nil {
return err
}
*registers = regs
*registers = regs.String()
return nil
}

View File

@ -241,10 +241,10 @@ func (c *RPCClient) ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig)
return out.Variables, err
}
func (c *RPCClient) ListRegisters() (string, error) {
func (c *RPCClient) ListRegisters(threadID int, includeFp bool) (api.Registers, error) {
out := new(ListRegistersOut)
err := c.call("ListRegisters", ListRegistersIn{}, out)
return out.Registers, err
err := c.call("ListRegisters", ListRegistersIn{ThreadID: threadID, IncludeFp: includeFp}, out)
return out.Regs, err
}
func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) {

View File

@ -312,24 +312,32 @@ func (s *RPCServer) ListPackageVars(arg ListPackageVarsIn, out *ListPackageVarsO
}
type ListRegistersIn struct {
ThreadID int
IncludeFp bool
}
type ListRegistersOut struct {
Registers string
Regs api.Registers
}
// ListRegisters lists registers and their values.
func (s *RPCServer) ListRegisters(arg ListRegistersIn, out *ListRegistersOut) error {
if arg.ThreadID == 0 {
state, err := s.debugger.State()
if err != nil {
return err
}
arg.ThreadID = state.CurrentThread.ID
}
regs, err := s.debugger.Registers(state.CurrentThread.ID)
regs, err := s.debugger.Registers(arg.ThreadID, arg.IncludeFp)
if err != nil {
return err
}
out.Registers = regs
out.Regs = regs
out.Registers = out.Regs.String()
return nil
}

View File

@ -457,11 +457,11 @@ func TestClientServer_infoArgs(t *testing.T) {
if state.Err != nil {
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
}
regs, err := c.ListRegisters()
regs, err := c.ListRegisters(0, false)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if regs == "" {
if len(regs) == 0 {
t.Fatal("Expected string showing registers values, got empty string")
}
locals, err := c.ListFunctionArgs(api.EvalScope{-1, 0}, normalLoadConfig)
@ -853,7 +853,7 @@ func TestIssue355(t *testing.T) {
assertError(err, t, "ListLocalVariables()")
_, err = c.ListFunctionArgs(api.EvalScope{gid, 0}, normalLoadConfig)
assertError(err, t, "ListFunctionArgs()")
_, err = c.ListRegisters()
_, err = c.ListRegisters(0, false)
assertError(err, t, "ListRegisters()")
_, err = c.ListGoroutines()
assertError(err, t, "ListGoroutines()")
@ -1149,3 +1149,47 @@ func TestClientServer_Issue528(t *testing.T) {
findLocationHelper(t, c, "State.Close", false, 1, 0)
})
}
func TestClientServer_FpRegisters(t *testing.T) {
regtests := []struct{ name, value string }{
{"ST(0)", "0x3fffe666660000000000"},
{"ST(1)", "0x3fffd9999a0000000000"},
{"ST(2)", "0x3fffcccccd0000000000"},
{"ST(3)", "0x3fffc000000000000000"},
{"ST(4)", "0x3fffb333333333333000"},
{"ST(5)", "0x3fffa666666666666800"},
{"ST(6)", "0x3fff9999999999999800"},
{"ST(7)", "0x3fff8cccccccccccd000"},
{"XMM0", "0x3ff33333333333333ff199999999999a v2_int={ 3ff199999999999a 3ff3333333333333 } v4_int={ 9999999a 3ff19999 33333333 3ff33333 } v8_int={ 999a 9999 9999 3ff1 3333 3333 3333 3ff3 } v16_int={ 9a 99 99 99 99 99 f1 3f 33 33 33 33 33 33 f3 3f }"},
{"XMM1", "0x3ff66666666666663ff4cccccccccccd"},
{"XMM2", "0x3fe666663fd9999a3fcccccd3fc00000"},
{"XMM3", "0x3ff199999999999a3ff3333333333333"},
{"XMM4", "0x3ff4cccccccccccd3ff6666666666666"},
{"XMM5", "0x3fcccccd3fc000003fe666663fd9999a"},
{"XMM6", "0x4004cccccccccccc4003333333333334"},
{"XMM7", "0x40026666666666664002666666666666"},
{"XMM8", "0x4059999a404ccccd4059999a404ccccd"},
}
withTestClient2("fputest/", t, func(c service.Client) {
<-c.Continue()
regs, err := c.ListRegisters(0, true)
assertNoError(err, t, "ListRegisters()")
t.Logf("%s", regs.String())
for _, regtest := range regtests {
found := false
for _, reg := range regs {
if reg.Name == regtest.name {
found = true
if !strings.HasPrefix(reg.Value, regtest.value) {
t.Fatalf("register %s expected %q got %q", reg.Name, regtest.value, reg.Value)
}
}
}
if !found {
t.Fatalf("register %s not found: %v", regtest.name, regs)
}
}
})
}

View File

@ -176,7 +176,11 @@ If regex is specified only local variables with a name matching it will be retur
vars [-v] [<regex>]
If regex is specified only package variables with a name matching it will be returned. If -v is specified more information about each package variable will be shown.`},
{aliases: []string{"regs"}, cmdFn: regs, helpMsg: "Print contents of CPU registers."},
{aliases: []string{"regs"}, cmdFn: regs, helpMsg: `Print contents of CPU registers.
regs [-a]
Argument -a shows more registers.`},
{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
{aliases: []string{"list", "ls"}, allowedPrefixes: scopePrefix, cmdFn: listCommand, helpMsg: `Show source code.
@ -963,7 +967,11 @@ func vars(t *Term, ctx callContext, args string) error {
}
func regs(t *Term, ctx callContext, args string) error {
regs, err := t.client.ListRegisters()
includeFp := false
if args == "-a" {
includeFp = true
}
regs, err := t.client.ListRegisters(0, includeFp)
if err != nil {
return err
}

View File

@ -1,8 +1,8 @@
package terminal
import (
"testing"
"runtime"
"testing"
"github.com/derekparker/delve/config"
)
@ -66,9 +66,9 @@ func platformCases() []tCase {
}
func TestSubstitutePath(t *testing.T) {
for _, c := range(platformCases()) {
for _, c := range platformCases() {
var subRules config.SubstitutePathRules
for _, r := range(c.rules) {
for _, r := range c.rules {
subRules = append(subRules, config.SubstitutePathRule{From: r.from, To: r.to})
}
res := New(nil, &config.Config{SubstitutePath: subRules}).substitutePath(c.path)