From 8f0646e4265821a10801774cc63e4f76d32fdd9c Mon Sep 17 00:00:00 2001 From: aarzilli Date: Tue, 15 Nov 2016 17:16:33 +0100 Subject: [PATCH] proc: load more registers Adds ability to load x87, SSE and AVX registers. Fixes #666 --- Documentation/cli/README.md | 11 ++ _fixtures/fputest/fputest.go | 19 +++ _fixtures/fputest/fputest_amd64.s | 56 ++++++++ proc/disasm.go | 2 +- proc/disasm_amd64.go | 2 +- proc/proc.go | 2 +- proc/proc_test.go | 10 +- proc/ptrace_linux.go | 45 +++++++ proc/registers.go | 207 +++++++++++++++++++++++++++++- proc/registers_darwin_amd64.go | 49 ++++++- proc/registers_linux_amd64.go | 99 ++++++++++++-- proc/registers_windows_amd64.go | 90 ++++++++----- proc/stack.go | 2 +- proc/syscall_windows_amd64.go | 21 ++- proc/test/support.go | 15 ++- proc/threads.go | 4 +- proc/threads_darwin.c | 7 + proc/threads_darwin.h | 3 + proc/threads_linux.go | 2 +- service/api/conversions.go | 12 +- service/api/types.go | 23 ++++ service/client.go | 2 +- service/debugger/debugger.go | 10 +- service/rpc1/server.go | 4 +- service/rpc2/client.go | 8 +- service/rpc2/server.go | 18 ++- service/test/integration2_test.go | 50 +++++++- terminal/command.go | 12 +- terminal/terminal_test.go | 8 +- 29 files changed, 701 insertions(+), 92 deletions(-) create mode 100644 _fixtures/fputest/fputest.go create mode 100644 _fixtures/fputest/fputest_amd64.s diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index 367bce00..4192fb97 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -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] [] 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. + + diff --git a/_fixtures/fputest/fputest.go b/_fixtures/fputest/fputest.go new file mode 100644 index 00000000..aed3d3c5 --- /dev/null +++ b/_fixtures/fputest/fputest.go @@ -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() +} diff --git a/_fixtures/fputest/fputest_amd64.s b/_fixtures/fputest/fputest_amd64.s new file mode 100644 index 00000000..11b06934 --- /dev/null +++ b/_fixtures/fputest/fputest_amd64.s @@ -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 diff --git a/proc/disasm.go b/proc/disasm.go index 20cc5c20..be2db19f 100644 --- a/proc/disasm.go +++ b/proc/disasm.go @@ -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() } diff --git a/proc/disasm_amd64.go b/proc/disasm_amd64.go index df00a30a..11e05522 100644 --- a/proc/disasm_amd64.go +++ b/proc/disasm_amd64.go @@ -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 } diff --git a/proc/proc.go b/proc/proc.go index 7dcd43fd..67886ee7 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -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. diff --git a/proc/proc_test.go b/proc/proc_test.go index 1a126e88..63f3db52 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -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) diff --git a/proc/ptrace_linux.go b/proc/ptrace_linux.go index c18b18e7..402dbb93 100644 --- a/proc/ptrace_linux.go +++ b/proc/ptrace_linux.go @@ -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 Developer’s 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(®set.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 +} diff --git a/proc/registers.go b/proc/registers.go index ebd6a26c..d1032faf 100644 --- a/proc/registers.go +++ b/proc/registers.go @@ -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, " ")) +} diff --git a/proc/registers_darwin_amd64.go b/proc/registers_darwin_amd64.go index 3262ab46..0357c91a 100644 --- a/proc/registers_darwin_amd64.go +++ b/proc/registers_darwin_amd64.go @@ -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 } diff --git a/proc/registers_linux_amd64.go b/proc/registers_linux_amd64.go index ae57ebb3..07b79e25 100644 --- a/proc/registers_linux_amd64.go +++ b/proc/registers_linux_amd64.go @@ -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 + 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{®s}, nil + r := &Regs{®s, 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 } diff --git a/proc/registers_windows_amd64.go b/proc/registers_windows_amd64.go index f5aa9b6a..ac4ed7c0 100644 --- a/proc/registers_windows_amd64.go +++ b/proc/registers_windows_amd64.go @@ -1,7 +1,6 @@ package proc import ( - "bytes" "fmt" "rsc.io/x86/x86asm" "unsafe" @@ -9,32 +8,32 @@ import ( // Regs represents CPU registers on an AMD64 processor. 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 - eflags uint64 - cs uint64 - fs uint64 - gs uint64 - tls 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 + eflags uint64 + cs uint64 + 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 } diff --git a/proc/stack.go b/proc/stack.go index 367a4581..878e90f1 100644 --- a/proc/stack.go +++ b/proc/stack.go @@ -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 } diff --git a/proc/syscall_windows_amd64.go b/proc/syscall_windows_amd64.go index 80d4fdba..1ce592ec 100644 --- a/proc/syscall_windows_amd64.go +++ b/proc/syscall_windows_amd64.go @@ -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 diff --git a/proc/test/support.go b/proc/test/support.go index 53bef738..b9e63d2c 100644 --- a/proc/test/support.go +++ b/proc/test/support.go @@ -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 { diff --git a/proc/threads.go b/proc/threads.go index 77681646..6ed742ae 100644 --- a/proc/threads.go +++ b/proc/threads.go @@ -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 } diff --git a/proc/threads_darwin.c b/proc/threads_darwin.c index ce3a850c..88a92f40 100644 --- a/proc/threads_darwin.c +++ b/proc/threads_darwin.c @@ -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; diff --git a/proc/threads_darwin.h b/proc/threads_darwin.h index 16c2513c..75a82b8d 100644 --- a/proc/threads_darwin.h +++ b/proc/threads_darwin.h @@ -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); diff --git a/proc/threads_linux.go b/proc/threads_linux.go index 983503f5..6f0a8b3a 100644 --- a/proc/threads_linux.go +++ b/proc/threads_linux.go @@ -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) { diff --git a/service/api/conversions.go b/service/api/conversions.go index 2362181a..61a15f7e 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -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 @@ -199,7 +199,7 @@ func ConvertGoroutine(g *proc.G) *Goroutine { CurrentLoc: ConvertLocation(g.CurrentLoc), UserCurrentLoc: ConvertLocation(g.UserCurrent()), GoStatementLoc: ConvertLocation(g.Go()), - ThreadID: tid, + ThreadID: tid, } } @@ -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 +} diff --git a/service/api/types.go b/service/api/types.go index e2abd0b5..733cb37c 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -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() +} diff --git a/service/client.go b/service/client.go index d992e423..e121fdca 100644 --- a/service/client.go +++ b/service/client.go @@ -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) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 95f2e232..028bd05a 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -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 { diff --git a/service/rpc1/server.go b/service/rpc1/server.go index 084e6c75..f2e497bf 100644 --- a/service/rpc1/server.go +++ b/service/rpc1/server.go @@ -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 } diff --git a/service/rpc2/client.go b/service/rpc2/client.go index c6a6f2fe..b47fff56 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -105,7 +105,7 @@ func (c *RPCClient) Step() (*api.DebuggerState, error) { func (c *RPCClient) StepOut() (*api.DebuggerState, error) { var out CommandOut - err := c.call("Command", &api.DebuggerCommand{ Name: api.StepOut}, &out) + err := c.call("Command", &api.DebuggerCommand{Name: api.StepOut}, &out) return &out.State, err } @@ -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) { diff --git a/service/rpc2/server.go b/service/rpc2/server.go index 889ca4d2..841f4141 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -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 { - state, err := s.debugger.State() - if err != nil { - return err + 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 } diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index bd38dde5..4d287b00 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -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) + } + } + }) +} diff --git a/terminal/command.go b/terminal/command.go index ea5c0ed9..342c671b 100644 --- a/terminal/command.go +++ b/terminal/command.go @@ -176,7 +176,11 @@ If regex is specified only local variables with a name matching it will be retur vars [-v] [] 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 } diff --git a/terminal/terminal_test.go b/terminal/terminal_test.go index 04aa75a3..2e7d35dc 100644 --- a/terminal/terminal_test.go +++ b/terminal/terminal_test.go @@ -1,15 +1,15 @@ package terminal import ( - "testing" "runtime" + "testing" "github.com/derekparker/delve/config" ) type tRule struct { from string - to string + to string } type tCase struct { @@ -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)