mirror of
https://github.com/go-delve/delve.git
synced 2025-10-28 20:53:42 +08:00
service/dap: add registers configuration for variables response (#2742)
This commit is contained in:
@ -25,6 +25,7 @@ const (
|
|||||||
UnableToGetExceptionInfo = 2011
|
UnableToGetExceptionInfo = 2011
|
||||||
UnableToSetVariable = 2012
|
UnableToSetVariable = 2012
|
||||||
UnableToDisassemble = 2013
|
UnableToDisassemble = 2013
|
||||||
|
UnableToListRegisters = 2014
|
||||||
// Add more codes as we support more requests
|
// Add more codes as we support more requests
|
||||||
NoDebugIsRunning = 3000
|
NoDebugIsRunning = 3000
|
||||||
DebuggeeIsRunning = 4000
|
DebuggeeIsRunning = 4000
|
||||||
|
|||||||
@ -185,6 +185,8 @@ type launchAttachArgs struct {
|
|||||||
stackTraceDepth int
|
stackTraceDepth int
|
||||||
// showGlobalVariables indicates if global package variables should be loaded.
|
// showGlobalVariables indicates if global package variables should be loaded.
|
||||||
showGlobalVariables bool
|
showGlobalVariables bool
|
||||||
|
// showRegisters indicates if register values should be loaded.
|
||||||
|
showRegisters bool
|
||||||
// substitutePathClientToServer indicates rules for converting file paths between client and debugger.
|
// substitutePathClientToServer indicates rules for converting file paths between client and debugger.
|
||||||
// These must be directory paths.
|
// These must be directory paths.
|
||||||
substitutePathClientToServer [][2]string
|
substitutePathClientToServer [][2]string
|
||||||
@ -200,6 +202,7 @@ var defaultArgs = launchAttachArgs{
|
|||||||
stopOnEntry: false,
|
stopOnEntry: false,
|
||||||
stackTraceDepth: 50,
|
stackTraceDepth: 50,
|
||||||
showGlobalVariables: false,
|
showGlobalVariables: false,
|
||||||
|
showRegisters: false,
|
||||||
substitutePathClientToServer: [][2]string{},
|
substitutePathClientToServer: [][2]string{},
|
||||||
substitutePathServerToClient: [][2]string{},
|
substitutePathServerToClient: [][2]string{},
|
||||||
}
|
}
|
||||||
@ -302,6 +305,7 @@ func (s *Session) setLaunchAttachArgs(args LaunchAttachCommonConfig) error {
|
|||||||
s.args.stackTraceDepth = depth
|
s.args.stackTraceDepth = depth
|
||||||
}
|
}
|
||||||
s.args.showGlobalVariables = args.ShowGlobalVariables
|
s.args.showGlobalVariables = args.ShowGlobalVariables
|
||||||
|
s.args.showRegisters = args.ShowRegisters
|
||||||
if paths := args.SubstitutePath; len(paths) > 0 {
|
if paths := args.SubstitutePath; len(paths) > 0 {
|
||||||
clientToServer := make([][2]string, 0, len(paths))
|
clientToServer := make([][2]string, 0, len(paths))
|
||||||
serverToClient := 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)}
|
scopeGlobals := dap.Scope{Name: globScope.Name, VariablesReference: s.variableHandles.create(globScope)}
|
||||||
scopes = append(scopes, scopeGlobals)
|
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 := &dap.ScopesResponse{
|
||||||
Response: *newResponse(request.Request),
|
Response: *newResponse(request.Request),
|
||||||
Body: dap.ScopesResponseBody{Scopes: scopes},
|
Body: dap.ScopesResponseBody{Scopes: scopes},
|
||||||
@ -2223,6 +2248,17 @@ func (s *Session) childrenToDAPVariables(v *fullyQualifiedVariable) ([]dap.Varia
|
|||||||
name = fmt.Sprintf("(%s)", name)
|
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{
|
children[i] = dap.Variable{
|
||||||
Name: name,
|
Name: name,
|
||||||
EvaluateName: cfqname,
|
EvaluateName: cfqname,
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import (
|
|||||||
const stopOnEntry bool = true
|
const stopOnEntry bool = true
|
||||||
const hasChildren bool = true
|
const hasChildren bool = true
|
||||||
const noChildren bool = false
|
const noChildren bool = false
|
||||||
|
|
||||||
const localsScope = 1000
|
const localsScope = 1000
|
||||||
const globalsScope = 1001
|
const globalsScope = 1001
|
||||||
|
|
||||||
@ -2074,7 +2075,7 @@ func TestGlobalScopeAndVariables(t *testing.T) {
|
|||||||
// Launch
|
// Launch
|
||||||
func() {
|
func() {
|
||||||
client.LaunchRequestWithArgs(map[string]interface{}{
|
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
|
// Breakpoints are set within the program
|
||||||
@ -2090,6 +2091,7 @@ func TestGlobalScopeAndVariables(t *testing.T) {
|
|||||||
scopes := client.ExpectScopesResponse(t)
|
scopes := client.ExpectScopesResponse(t)
|
||||||
checkScope(t, scopes, 0, "Locals", localsScope)
|
checkScope(t, scopes, 0, "Locals", localsScope)
|
||||||
checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
|
checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
|
||||||
|
checkScope(t, scopes, 2, "Registers", globalsScope+1)
|
||||||
|
|
||||||
client.VariablesRequest(globalsScope)
|
client.VariablesRequest(globalsScope)
|
||||||
client.ExpectVariablesResponse(t)
|
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
|
// TestShadowedVariables executes to a breakpoint and checks the shadowed
|
||||||
// variable is named correctly.
|
// variable is named correctly.
|
||||||
func TestShadowedVariables(t *testing.T) {
|
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)
|
client.StackTraceRequest(threadId, 0, 1)
|
||||||
st := client.ExpectStackTraceResponse(t)
|
st := client.ExpectStackTraceResponse(t)
|
||||||
if len(st.Body.StackFrames) < 1 {
|
if len(st.Body.StackFrames) < 1 {
|
||||||
t.Fatalf("\ngot %#v\nwant len(stackframes) => 1", st)
|
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) {
|
func TestNextParked(t *testing.T) {
|
||||||
|
|||||||
@ -149,6 +149,10 @@ type LaunchAttachCommonConfig struct {
|
|||||||
// should be shown in the variables pane or not.
|
// should be shown in the variables pane or not.
|
||||||
ShowGlobalVariables bool `json:"showGlobalVariables,omitempty"`
|
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).
|
// 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,
|
// This setting is useful when working in a file system with symbolic links,
|
||||||
// running remote debugging, or debugging an executable compiled externally.
|
// running remote debugging, or debugging an executable compiled externally.
|
||||||
|
|||||||
Reference in New Issue
Block a user