mirror of
https://github.com/go-delve/delve.git
synced 2025-10-28 04:35:19 +08:00
service/dap: add registers configuration for variables response (#2742)
This commit is contained in:
@ -25,6 +25,7 @@ const (
|
||||
UnableToGetExceptionInfo = 2011
|
||||
UnableToSetVariable = 2012
|
||||
UnableToDisassemble = 2013
|
||||
UnableToListRegisters = 2014
|
||||
// Add more codes as we support more requests
|
||||
NoDebugIsRunning = 3000
|
||||
DebuggeeIsRunning = 4000
|
||||
|
||||
@ -185,6 +185,8 @@ type launchAttachArgs struct {
|
||||
stackTraceDepth int
|
||||
// showGlobalVariables indicates if global package variables should be loaded.
|
||||
showGlobalVariables bool
|
||||
// showRegisters indicates if register values should be loaded.
|
||||
showRegisters bool
|
||||
// substitutePathClientToServer indicates rules for converting file paths between client and debugger.
|
||||
// These must be directory paths.
|
||||
substitutePathClientToServer [][2]string
|
||||
@ -200,6 +202,7 @@ var defaultArgs = launchAttachArgs{
|
||||
stopOnEntry: false,
|
||||
stackTraceDepth: 50,
|
||||
showGlobalVariables: false,
|
||||
showRegisters: false,
|
||||
substitutePathClientToServer: [][2]string{},
|
||||
substitutePathServerToClient: [][2]string{},
|
||||
}
|
||||
@ -302,6 +305,7 @@ func (s *Session) setLaunchAttachArgs(args LaunchAttachCommonConfig) error {
|
||||
s.args.stackTraceDepth = depth
|
||||
}
|
||||
s.args.showGlobalVariables = args.ShowGlobalVariables
|
||||
s.args.showRegisters = args.ShowRegisters
|
||||
if paths := args.SubstitutePath; len(paths) > 0 {
|
||||
clientToServer := make([][2]string, 0, len(paths))
|
||||
serverToClient := make([][2]string, 0, len(paths))
|
||||
@ -2011,6 +2015,27 @@ func (s *Session) onScopesRequest(request *dap.ScopesRequest) {
|
||||
scopeGlobals := dap.Scope{Name: globScope.Name, VariablesReference: s.variableHandles.create(globScope)}
|
||||
scopes = append(scopes, scopeGlobals)
|
||||
}
|
||||
|
||||
if s.args.showRegisters {
|
||||
// Retrieve registers
|
||||
regs, err := s.debugger.ScopeRegisters(goid, frame, 0, false)
|
||||
if err != nil {
|
||||
s.sendErrorResponse(request.Request, UnableToListRegisters, "Unable to list registers", err.Error())
|
||||
return
|
||||
}
|
||||
outRegs := api.ConvertRegisters(regs, s.debugger.DwarfRegisterToString, false)
|
||||
regsVar := make([]proc.Variable, len(outRegs))
|
||||
for i, r := range outRegs {
|
||||
regsVar[i] = proc.Variable{
|
||||
Name: r.Name,
|
||||
Value: constant.MakeString(r.Value),
|
||||
Kind: reflect.Kind(proc.VariableConstant),
|
||||
}
|
||||
}
|
||||
regsScope := &fullyQualifiedVariable{&proc.Variable{Name: "Registers", Children: regsVar}, "", true, 0}
|
||||
scopeRegisters := dap.Scope{Name: regsScope.Name, VariablesReference: s.variableHandles.create(regsScope)}
|
||||
scopes = append(scopes, scopeRegisters)
|
||||
}
|
||||
response := &dap.ScopesResponse{
|
||||
Response: *newResponse(request.Request),
|
||||
Body: dap.ScopesResponseBody{Scopes: scopes},
|
||||
@ -2223,6 +2248,17 @@ func (s *Session) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Varia
|
||||
name = fmt.Sprintf("(%s)", name)
|
||||
}
|
||||
|
||||
if v.isScope && v.Name == "Registers" {
|
||||
// Align all of the register names.
|
||||
name = fmt.Sprintf("%6s", strings.ToLower(c.Name))
|
||||
// Set the correct evaluate name for the register.
|
||||
cfqname = fmt.Sprintf("_%s", strings.ToUpper(c.Name))
|
||||
// Unquote the value
|
||||
if ucvalue, err := strconv.Unquote(cvalue); err == nil {
|
||||
cvalue = ucvalue
|
||||
}
|
||||
}
|
||||
|
||||
children[i] = dap.Variable{
|
||||
Name: name,
|
||||
EvaluateName: cfqname,
|
||||
|
||||
@ -33,6 +33,7 @@ import (
|
||||
const stopOnEntry bool = true
|
||||
const hasChildren bool = true
|
||||
const noChildren bool = false
|
||||
|
||||
const localsScope = 1000
|
||||
const globalsScope = 1001
|
||||
|
||||
@ -2074,7 +2075,7 @@ func TestGlobalScopeAndVariables(t *testing.T) {
|
||||
// Launch
|
||||
func() {
|
||||
client.LaunchRequestWithArgs(map[string]interface{}{
|
||||
"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
|
||||
"mode": "exec", "program": fixture.Path, "showGlobalVariables": true, "showRegisters": true,
|
||||
})
|
||||
},
|
||||
// Breakpoints are set within the program
|
||||
@ -2090,6 +2091,7 @@ func TestGlobalScopeAndVariables(t *testing.T) {
|
||||
scopes := client.ExpectScopesResponse(t)
|
||||
checkScope(t, scopes, 0, "Locals", localsScope)
|
||||
checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
|
||||
checkScope(t, scopes, 2, "Registers", globalsScope+1)
|
||||
|
||||
client.VariablesRequest(globalsScope)
|
||||
client.ExpectVariablesResponse(t)
|
||||
@ -2130,6 +2132,127 @@ func TestGlobalScopeAndVariables(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestRegisterScopeAndVariables launches the program with showRegisters
|
||||
// arg set, executes to a breakpoint in the main package and tests that the registers
|
||||
// got loaded. It then steps into a function in another package and tests that
|
||||
// the registers were updated by checking PC.
|
||||
func TestRegistersScopeAndVariables(t *testing.T) {
|
||||
runTest(t, "consts", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSessionWithBPs(t, client, "launch",
|
||||
// Launch
|
||||
func() {
|
||||
client.LaunchRequestWithArgs(map[string]interface{}{
|
||||
"mode": "exec", "program": fixture.Path, "showRegisters": true,
|
||||
})
|
||||
},
|
||||
// Breakpoints are set within the program
|
||||
fixture.Source, []int{},
|
||||
[]onBreakpoint{{
|
||||
// Stop at line 36
|
||||
execute: func() {
|
||||
client.StackTraceRequest(1, 0, 20)
|
||||
stack := client.ExpectStackTraceResponse(t)
|
||||
checkStackFramesExact(t, stack, "main.main", 36, 1000, 3, 3)
|
||||
|
||||
client.ScopesRequest(1000)
|
||||
scopes := client.ExpectScopesResponse(t)
|
||||
checkScope(t, scopes, 0, "Locals", localsScope)
|
||||
registersScope := localsScope + 1
|
||||
checkScope(t, scopes, 1, "Registers", registersScope)
|
||||
|
||||
// Check that instructionPointer points to the InstructionPointerReference.
|
||||
pc, err := getPC(t, client, 1)
|
||||
if err != nil {
|
||||
t.Error(pc)
|
||||
}
|
||||
|
||||
client.VariablesRequest(registersScope)
|
||||
vr := client.ExpectVariablesResponse(t)
|
||||
if len(vr.Body.Variables) == 0 {
|
||||
t.Fatal("no registers returned")
|
||||
}
|
||||
idx := findPcReg(vr.Body.Variables)
|
||||
if idx < 0 {
|
||||
t.Fatalf("got %#v, want a reg with instruction pointer", vr.Body.Variables)
|
||||
}
|
||||
pcReg := vr.Body.Variables[idx]
|
||||
gotPc, err := strconv.ParseUint(pcReg.Value, 0, 64)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
name := strings.TrimSpace(pcReg.Name)
|
||||
if gotPc != pc || pcReg.EvaluateName != fmt.Sprintf("_%s", strings.ToUpper(name)) {
|
||||
t.Errorf("got %#v,\nwant Name=%s Value=%#x EvaluateName=%q", pcReg, name, pc, fmt.Sprintf("_%s", strings.ToUpper(name)))
|
||||
}
|
||||
|
||||
// The program has no user-defined globals.
|
||||
// Depending on the Go version, there might
|
||||
// be some runtime globals (e.g. main..inittask)
|
||||
// so testing for the total number is too fragile.
|
||||
|
||||
// Step into pkg.AnotherMethod()
|
||||
client.StepInRequest(1)
|
||||
client.ExpectStepInResponse(t)
|
||||
client.ExpectStoppedEvent(t)
|
||||
|
||||
client.StackTraceRequest(1, 0, 20)
|
||||
stack = client.ExpectStackTraceResponse(t)
|
||||
checkStackFramesExact(t, stack, "", 13, 1000, 4, 4)
|
||||
|
||||
client.ScopesRequest(1000)
|
||||
scopes = client.ExpectScopesResponse(t)
|
||||
checkScope(t, scopes, 0, "Locals", localsScope)
|
||||
checkScope(t, scopes, 1, "Registers", registersScope)
|
||||
|
||||
// Check that rip points to the InstructionPointerReference.
|
||||
pc, err = getPC(t, client, 1)
|
||||
if err != nil {
|
||||
t.Error(pc)
|
||||
}
|
||||
client.VariablesRequest(registersScope)
|
||||
vr = client.ExpectVariablesResponse(t)
|
||||
if len(vr.Body.Variables) == 0 {
|
||||
t.Fatal("no registers returned")
|
||||
}
|
||||
|
||||
idx = findPcReg(vr.Body.Variables)
|
||||
if idx < 0 {
|
||||
t.Fatalf("got %#v, want a reg with instruction pointer", vr.Body.Variables)
|
||||
}
|
||||
pcReg = vr.Body.Variables[idx]
|
||||
gotPc, err = strconv.ParseUint(pcReg.Value, 0, 64)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if gotPc != pc || pcReg.EvaluateName != fmt.Sprintf("_%s", strings.ToUpper(name)) {
|
||||
t.Errorf("got %#v,\nwant Name=%s Value=%#x EvaluateName=%q", pcReg, name, pc, fmt.Sprintf("_%s", strings.ToUpper(name)))
|
||||
}
|
||||
},
|
||||
disconnect: false,
|
||||
}})
|
||||
})
|
||||
}
|
||||
|
||||
func findPcReg(regs []dap.Variable) int {
|
||||
for i, reg := range regs {
|
||||
if isPcReg(reg) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func isPcReg(reg dap.Variable) bool {
|
||||
pcRegNames := []string{"rip", "pc", "eip"}
|
||||
for _, name := range pcRegNames {
|
||||
if name == strings.TrimSpace(reg.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestShadowedVariables executes to a breakpoint and checks the shadowed
|
||||
// variable is named correctly.
|
||||
func TestShadowedVariables(t *testing.T) {
|
||||
@ -3938,13 +4061,13 @@ func TestStepInstruction(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func getPC(t *testing.T, client *daptest.Client, threadId int) (int64, error) {
|
||||
func getPC(t *testing.T, client *daptest.Client, threadId int) (uint64, error) {
|
||||
client.StackTraceRequest(threadId, 0, 1)
|
||||
st := client.ExpectStackTraceResponse(t)
|
||||
if len(st.Body.StackFrames) < 1 {
|
||||
t.Fatalf("\ngot %#v\nwant len(stackframes) => 1", st)
|
||||
}
|
||||
return strconv.ParseInt(st.Body.StackFrames[0].InstructionPointerReference, 0, 64)
|
||||
return strconv.ParseUint(st.Body.StackFrames[0].InstructionPointerReference, 0, 64)
|
||||
}
|
||||
|
||||
func TestNextParked(t *testing.T) {
|
||||
|
||||
@ -149,6 +149,10 @@ type LaunchAttachCommonConfig struct {
|
||||
// should be shown in the variables pane or not.
|
||||
ShowGlobalVariables bool `json:"showGlobalVariables,omitempty"`
|
||||
|
||||
// Boolean value to indicate whether registers should be shown
|
||||
// in the variables pane or not.
|
||||
ShowRegisters bool `json:"showRegisters,omitempty"`
|
||||
|
||||
// An array of mappings from a local path (client) to the remote path (debugger).
|
||||
// This setting is useful when working in a file system with symbolic links,
|
||||
// running remote debugging, or debugging an executable compiled externally.
|
||||
|
||||
Reference in New Issue
Block a user