service/dap: add registers configuration for variables response (#2742)

This commit is contained in:
Suzy Mueller
2021-10-15 07:57:50 -04:00
committed by GitHub
parent 9a5d5bc996
commit 99f03597c3
4 changed files with 167 additions and 3 deletions

View File

@ -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

View File

@ -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,

View File

@ -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) {

View File

@ -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.