proc,service: allow printing registers for arbitrary frames (#1875)

Adds an optional scope prefix to the `regs` command which allows
printing registers for any stack frame (as long as they were somehow
saved). Issue #1838 is not yet to be closed since we are still not
recovering the registers of a segfaulting frame.

Updates #1838
This commit is contained in:
Alessandro Arzilli 2020-02-24 19:47:02 +01:00 committed by GitHub
parent 186786235f
commit d925f6b719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 187 additions and 61 deletions

@ -45,7 +45,7 @@ goroutines(Start, Count) | Equivalent to API call [ListGoroutines](https://godoc
local_vars(Scope, Cfg) | Equivalent to API call [ListLocalVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListLocalVars)
package_vars(Filter, Cfg) | Equivalent to API call [ListPackageVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackageVars)
packages_build_info(IncludeFiles) | Equivalent to API call [ListPackagesBuildInfo](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackagesBuildInfo)
registers(ThreadID, IncludeFp) | Equivalent to API call [ListRegisters](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListRegisters)
registers(ThreadID, IncludeFp, Scope) | Equivalent to API call [ListRegisters](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListRegisters)
sources(Filter) | Equivalent to API call [ListSources](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListSources)
threads() | Equivalent to API call [ListThreads](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListThreads)
types(Filter) | Equivalent to API call [ListTypes](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListTypes)

@ -426,30 +426,34 @@ func (a *AMD64) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint
}
}
func (a *AMD64) DwarfRegisterToString(name string, reg *op.DwarfRegister) string {
name = strings.ToLower(name)
switch name {
func (a *AMD64) DwarfRegisterToString(i int, reg *op.DwarfRegister) (name string, floatingPoint bool, repr string) {
name, ok := amd64DwarfToName[i]
if !ok {
name = fmt.Sprintf("unknown%d", i)
}
switch n := strings.ToLower(name); n {
case "rflags":
return eflagsDescription.Describe(reg.Uint64Val, 64)
return name, false, eflagsDescription.Describe(reg.Uint64Val, 64)
case "cw", "sw", "tw", "fop":
return fmt.Sprintf("%#04x", reg.Uint64Val)
return name, true, fmt.Sprintf("%#04x", reg.Uint64Val)
case "mxcsr_mask":
return fmt.Sprintf("%#08x", reg.Uint64Val)
return name, true, fmt.Sprintf("%#08x", reg.Uint64Val)
case "mxcsr":
return mxcsrDescription.Describe(reg.Uint64Val, 32)
return name, true, mxcsrDescription.Describe(reg.Uint64Val, 32)
default:
if reg.Bytes != nil && strings.HasPrefix(name, "xmm") {
return formatSSEReg(reg.Bytes)
} else if reg.Bytes != nil && strings.HasPrefix(name, "st(") {
return formatX87Reg(reg.Bytes)
if reg.Bytes != nil && strings.HasPrefix(n, "xmm") {
return name, true, formatSSEReg(reg.Bytes)
} else if reg.Bytes != nil && strings.HasPrefix(n, "st(") {
return name, true, formatX87Reg(reg.Bytes)
} else if reg.Bytes == nil || (reg.Bytes != nil && len(reg.Bytes) <= 8) {
return fmt.Sprintf("%#016x", reg.Uint64Val)
return name, false, fmt.Sprintf("%#016x", reg.Uint64Val)
} else {
return fmt.Sprintf("%#x", reg.Bytes)
return name, false, fmt.Sprintf("%#x", reg.Bytes)
}
}
}

@ -21,7 +21,7 @@ type Arch interface {
RegSize(uint64) int
RegistersToDwarfRegisters(uint64, Registers) op.DwarfRegisters
AddrAndStackRegsToDwarfRegisters(uint64, uint64, uint64, uint64, uint64) op.DwarfRegisters
DwarfRegisterToString(string, *op.DwarfRegister) string
DwarfRegisterToString(int, *op.DwarfRegister) (string, bool, string)
}
const (

@ -411,8 +411,22 @@ func (a *ARM64) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint
}
}
func (a *ARM64) DwarfRegisterToString(name string, reg *op.DwarfRegister) string {
if reg.Bytes != nil && (name[0] == 'v' || name[0] == 'V') {
func (a *ARM64) DwarfRegisterToString(i int, reg *op.DwarfRegister) (name string, floatingPoint bool, repr string) {
// see arm64DwarfToHardware table for explanation
switch {
case i <= 30:
name = fmt.Sprintf("X%d", i)
case i == 31:
name = "SP"
case i == 32:
name = "PC"
case i >= 64 && i <= 95:
name = fmt.Sprintf("V%d", i-64)
default:
name = fmt.Sprintf("unknown%d", i)
}
if reg.Bytes != nil && name[0] == 'V' {
buf := bytes.NewReader(reg.Bytes)
var out bytes.Buffer
@ -445,9 +459,9 @@ func (a *ARM64) DwarfRegisterToString(name string, reg *op.DwarfRegister) string
}
fmt.Fprintf(&out, "\tv4_float={ %g %g %g %g }", v4[0], v4[1], v4[2], v4[3])
return out.String()
return name, true, out.String()
} else if reg.Bytes == nil || (reg.Bytes != nil && len(reg.Bytes) < 16) {
return fmt.Sprintf("%#016x", reg.Uint64Val)
return name, false, fmt.Sprintf("%#016x", reg.Uint64Val)
}
return fmt.Sprintf("%#x", reg.Bytes)
return name, false, fmt.Sprintf("%#x", reg.Bytes)
}

@ -185,6 +185,17 @@ func withCoreFile(t *testing.T, name, args string) *proc.Target {
return p
}
func logRegisters(t *testing.T, regs proc.Registers, arch proc.Arch) {
dregs := arch.RegistersToDwarfRegisters(0, regs)
for i, reg := range dregs.Regs {
if reg == nil {
continue
}
name, _, value := arch.DwarfRegisterToString(i, reg)
t.Logf("%s = %s", name, value)
}
}
func TestCore(t *testing.T) {
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
return
@ -243,11 +254,7 @@ func TestCore(t *testing.T) {
if err != nil {
t.Fatalf("Couldn't get current thread registers: %v", err)
}
regslice := regs.Slice(true)
arch := p.BinInfo().Arch
for _, reg := range regslice {
t.Logf("%s = %s", reg.Name, arch.DwarfRegisterToString(reg.Name, reg.Reg))
}
logRegisters(t, regs, p.BinInfo().Arch)
}
func TestCoreFpRegisters(t *testing.T) {
@ -314,18 +321,17 @@ func TestCoreFpRegisters(t *testing.T) {
}
arch := p.BinInfo().Arch
for _, reg := range regs.Slice(true) {
t.Logf("%s = %s", reg.Name, arch.DwarfRegisterToString(reg.Name, reg.Reg))
}
logRegisters(t, regs, arch)
dregs := arch.RegistersToDwarfRegisters(0, regs)
for _, regtest := range regtests {
found := false
for _, reg := range regs.Slice(true) {
if reg.Name == regtest.name {
for i, reg := range dregs.Regs {
regname, _, regval := arch.DwarfRegisterToString(i, reg)
if reg != nil && regname == regtest.name {
found = true
regval := arch.DwarfRegisterToString(reg.Name, reg.Reg)
if !strings.HasPrefix(regval, regtest.value) {
t.Fatalf("register %s expected %q got %q", reg.Name, regtest.value, regval)
t.Fatalf("register %s expected %q got %q", regname, regtest.value, regval)
}
}
}

@ -1638,7 +1638,13 @@ func regs(t *Term, ctx callContext, args string) error {
if args == "-a" {
includeFp = true
}
regs, err := t.client.ListRegisters(0, includeFp)
var regs api.Registers
var err error
if ctx.Scope.GoroutineID < 0 && ctx.Scope.Frame == 0 {
regs, err = t.client.ListThreadRegisters(0, includeFp)
} else {
regs, err = t.client.ListScopeRegisters(ctx.Scope, includeFp)
}
if err != nil {
return err
}

@ -967,7 +967,7 @@ func TestIssue1493(t *testing.T) {
ra := term.MustExec("regs -a")
nra := len(strings.Split(ra, "\n"))
t.Logf("regs -a: %s", ra)
if nr > nra/2 {
if nr > nra/2+1 {
t.Fatalf("'regs' returned too many registers (%d) compared to 'regs -a' (%d)", nr, nra)
}
})

@ -934,6 +934,15 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
return starlark.None, decorateError(thread, err)
}
}
if len(args) > 2 && args[2] != starlark.None {
err := unmarshalStarlarkValue(args[2], &rpcArgs.Scope, "Scope")
if err != nil {
return starlark.None, decorateError(thread, err)
}
} else {
scope := env.ctx.Scope()
rpcArgs.Scope = &scope
}
for _, kv := range kwargs {
var err error
switch kv[0].(starlark.String) {
@ -941,6 +950,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
err = unmarshalStarlarkValue(kv[1], &rpcArgs.ThreadID, "ThreadID")
case "IncludeFp":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.IncludeFp, "IncludeFp")
case "Scope":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Scope, "Scope")
default:
err = fmt.Errorf("unknown argument %q", kv[0])
}

@ -10,6 +10,7 @@ import (
"strconv"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/proc"
)
@ -328,10 +329,18 @@ func LoadConfigFromProc(cfg *proc.LoadConfig) *LoadConfig {
}
// ConvertRegisters converts proc.Register to api.Register for a slice.
func ConvertRegisters(in []proc.Register, arch proc.Arch) (out []Register) {
out = make([]Register, len(in))
for i := range in {
out[i] = Register{in[i].Name, arch.DwarfRegisterToString(in[i].Name, in[i].Reg)}
func ConvertRegisters(in op.DwarfRegisters, arch proc.Arch, floatingPoint bool) (out []Register) {
out = make([]Register, 0, len(in.Regs))
for i := range in.Regs {
reg := in.Reg(uint64(i))
if reg == nil {
continue
}
name, fp, repr := arch.DwarfRegisterToString(i, reg)
if !floatingPoint && fp {
continue
}
out = append(out, Register{name, repr, i})
}
return
}

@ -442,8 +442,9 @@ type SetAPIVersionOut struct {
// Register holds information on a CPU register.
type Register struct {
Name string
Value string
Name string
Value string
DwarfNumber int
}
// Registers is a list of CPU registers.

@ -93,8 +93,10 @@ type Client interface {
ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error)
// 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(threadID int, includeFp bool) (api.Registers, error)
// ListThreadRegisters lists registers and their values, for the given thread.
ListThreadRegisters(threadID int, includeFp bool) (api.Registers, error)
// ListScopeRegisters lists registers and their values, for the given scope.
ListScopeRegisters(scope api.EvalScope, includeFp bool) (api.Registers, error)
// ListGoroutines lists all goroutines.
ListGoroutines(start, count int) ([]*api.Goroutine, int, error)

@ -13,6 +13,7 @@ import (
"sync"
"time"
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc"
@ -949,19 +950,65 @@ 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, floatingPoint bool) (api.Registers, error) {
func (d *Debugger) Registers(threadID int, scope *api.EvalScope, floatingPoint bool) (api.Registers, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
thread, found := d.target.FindThread(threadID)
if !found {
return nil, fmt.Errorf("couldn't find thread %d", threadID)
var dregs op.DwarfRegisters
if scope != nil {
s, err := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame, scope.DeferredCall)
if err != nil {
return nil, err
}
dregs = s.Regs
} else {
thread, found := d.target.FindThread(threadID)
if !found {
return nil, fmt.Errorf("couldn't find thread %d", threadID)
}
regs, err := thread.Registers(floatingPoint)
if err != nil {
return nil, err
}
dregs = d.target.BinInfo().Arch.RegistersToDwarfRegisters(0, regs)
}
regs, err := thread.Registers(floatingPoint)
if err != nil {
return nil, err
}
return api.ConvertRegisters(regs.Slice(floatingPoint), d.target.BinInfo().Arch), err
r := api.ConvertRegisters(dregs, d.target.BinInfo().Arch, floatingPoint)
// Sort the registers in a canonical order we prefer, this is mostly
// because the DWARF register numbering for AMD64 is weird.
sort.Slice(r, func(i, j int) bool {
a, b := r[i], r[j]
an, aok := canonicalRegisterOrder[strings.ToLower(a.Name)]
bn, bok := canonicalRegisterOrder[strings.ToLower(b.Name)]
// Registers that don't appear in canonicalRegisterOrder sort after registers that do.
if !aok {
an = 1000
}
if !bok {
bn = 1000
}
if an == bn {
// keep registers that don't appear in canonicalRegisterOrder in DWARF order
return a.DwarfNumber < b.DwarfNumber
}
return an < bn
})
return r, nil
}
var canonicalRegisterOrder = map[string]int{
// amd64
"rip": 0,
"rsp": 1,
"rax": 2,
"rbx": 3,
"rcx": 4,
"rdx": 5,
// arm64
"pc": 0,
"sp": 1,
}
func convertVars(pv []*proc.Variable) []api.Variable {

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

@ -292,9 +292,15 @@ func (c *RPCClient) ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig)
return out.Variables, err
}
func (c *RPCClient) ListRegisters(threadID int, includeFp bool) (api.Registers, error) {
func (c *RPCClient) ListThreadRegisters(threadID int, includeFp bool) (api.Registers, error) {
out := new(ListRegistersOut)
err := c.call("ListRegisters", ListRegistersIn{ThreadID: threadID, IncludeFp: includeFp}, out)
err := c.call("ListRegisters", ListRegistersIn{ThreadID: threadID, IncludeFp: includeFp, Scope: nil}, out)
return out.Regs, err
}
func (c *RPCClient) ListScopeRegisters(scope api.EvalScope, includeFp bool) (api.Registers, error) {
out := new(ListRegistersOut)
err := c.call("ListRegisters", ListRegistersIn{ThreadID: 0, IncludeFp: includeFp, Scope: &scope}, out)
return out.Regs, err
}

@ -368,6 +368,7 @@ func (s *RPCServer) ListPackageVars(arg ListPackageVarsIn, out *ListPackageVarsO
type ListRegistersIn struct {
ThreadID int
IncludeFp bool
Scope *api.EvalScope
}
type ListRegistersOut struct {
@ -376,8 +377,10 @@ type ListRegistersOut struct {
}
// ListRegisters lists registers and their values.
// If ListRegistersIn.Scope is not nil the registers of that eval scope will
// be returned, otherwise ListRegistersIn.ThreadID will be used.
func (s *RPCServer) ListRegisters(arg ListRegistersIn, out *ListRegistersOut) error {
if arg.ThreadID == 0 {
if arg.ThreadID == 0 && arg.Scope == nil {
state, err := s.debugger.State(false)
if err != nil {
return err
@ -385,7 +388,7 @@ func (s *RPCServer) ListRegisters(arg ListRegistersIn, out *ListRegistersOut) er
arg.ThreadID = state.CurrentThread.ID
}
regs, err := s.debugger.Registers(arg.ThreadID, arg.IncludeFp)
regs, err := s.debugger.Registers(arg.ThreadID, arg.Scope, arg.IncludeFp)
if err != nil {
return err
}

@ -510,13 +510,28 @@ func TestClientServer_infoArgs(t *testing.T) {
if state.Err != nil {
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
}
regs, err := c.ListRegisters(0, false)
regs, err := c.ListThreadRegisters(0, false)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(regs) == 0 {
t.Fatal("Expected string showing registers values, got empty string")
}
regs, err = c.ListScopeRegisters(api.EvalScope{GoroutineID: -1, Frame: 0}, false)
assertNoError(err, t, "ListScopeRegisters(-1, 0)")
if len(regs) == 0 {
t.Fatal("Expected string showing registers values, got empty string")
}
t.Logf("GoroutineID: -1, Frame: 0\n%s", regs.String())
regs, err = c.ListScopeRegisters(api.EvalScope{GoroutineID: -1, Frame: 1}, false)
assertNoError(err, t, "ListScopeRegisters(-1, 1)")
if len(regs) == 0 {
t.Fatal("Expected string showing registers values, got empty string")
}
t.Logf("GoroutineID: -1, Frame: 1\n%s", regs.String())
locals, err := c.ListFunctionArgs(api.EvalScope{-1, 0, 0}, normalLoadConfig)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
@ -925,8 +940,10 @@ func TestIssue355(t *testing.T) {
assertError(err, t, "ListLocalVariables()")
_, err = c.ListFunctionArgs(api.EvalScope{gid, 0, 0}, normalLoadConfig)
assertError(err, t, "ListFunctionArgs()")
_, err = c.ListRegisters(0, false)
assertError(err, t, "ListRegisters()")
_, err = c.ListThreadRegisters(0, false)
assertError(err, t, "ListThreadRegisters()")
_, err = c.ListScopeRegisters(api.EvalScope{gid, 0, 0}, false)
assertError(err, t, "ListScopeRegisters()")
_, _, err = c.ListGoroutines(0, 0)
assertError(err, t, "ListGoroutines()")
_, err = c.Stacktrace(gid, 10, 0, &normalLoadConfig)
@ -1279,8 +1296,8 @@ func TestClientServer_FpRegisters(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("fputest/", t, func(c service.Client) {
<-c.Continue()
regs, err := c.ListRegisters(0, true)
assertNoError(err, t, "ListRegisters()")
regs, err := c.ListThreadRegisters(0, true)
assertNoError(err, t, "ListThreadRegisters()")
t.Logf("%s", regs.String())