Skip to content

Commit 9103a45

Browse files
authored
service/dap: add dap read memory request handler (#4083)
- enable setting SupportsReadMemoryRequest in Initialize response - variable listings now include MemoryReference for strings and slices - implement Session.onReadMemoryRequest handling for readMemory requests
1 parent cd7257f commit 9103a45

File tree

5 files changed

+337
-10
lines changed

5 files changed

+337
-10
lines changed

_fixtures/readmem_json.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package main
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/hex"
6+
"encoding/json"
7+
"fmt"
8+
"runtime"
9+
"strings"
10+
"unsafe"
11+
)
12+
13+
// Long json for MemoryReference
14+
func main() {
15+
runtime.Breakpoint()
16+
17+
payload := strings.Repeat("AB", 2500)
18+
19+
b, _ := json.Marshal(struct {
20+
Data string `json:"data"`
21+
}{Data: payload})
22+
23+
bytesString := []byte("this\nis\nit")
24+
nonprint := []byte{242, 243, 244, 245}
25+
maps := map[string]string{"some": "non"}
26+
27+
jsonString := string(b)
28+
29+
hashed := sha256.Sum256(b)
30+
jsonHash := hex.EncodeToString(hashed[:]) // used to validate fullness of a string
31+
32+
ptr := unsafe.StringData(jsonString)
33+
jsonAddr := fmt.Sprintf("%p", ptr) // used to validate string address
34+
35+
runtime.Breakpoint()
36+
37+
fmt.Println(jsonString)
38+
fmt.Println(jsonHash)
39+
fmt.Println(jsonAddr)
40+
fmt.Println(bytesString)
41+
fmt.Println(nonprint)
42+
fmt.Println(maps)
43+
}

service/dap/daptest/client.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ func (c *Client) InitializeRequest() {
213213
SupportsVariableType: true,
214214
SupportsVariablePaging: true,
215215
SupportsRunInTerminalRequest: true,
216+
SupportsMemoryReferences: true,
216217
Locale: "en-us",
217218
}
218219
c.send(request)
@@ -550,8 +551,14 @@ func (c *Client) SetDataBreakpointsRequest() {
550551
}
551552

552553
// ReadMemoryRequest sends a 'readMemory' request.
553-
func (c *Client) ReadMemoryRequest() {
554-
c.send(&dap.ReadMemoryRequest{Request: *c.newRequest("readMemory")})
554+
func (c *Client) ReadMemoryRequest(ref string, offset, count int) {
555+
c.send(&dap.ReadMemoryRequest{
556+
Request: *c.newRequest("readMemory"),
557+
Arguments: dap.ReadMemoryArguments{
558+
MemoryReference: ref,
559+
Offset: offset,
560+
Count: count,
561+
}})
555562
}
556563

557564
// DisassembleRequest sends a 'disassemble' request.

service/dap/error_ids.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828
UnableToDisassemble = 2013
2929
UnableToListRegisters = 2014
3030
UnableToRunDlvCommand = 2015
31+
UnableToReadMemory = 2016
3132

3233
// Add more codes as we support more requests
3334

service/dap/server.go

Lines changed: 157 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package dap
1111
import (
1212
"bufio"
1313
"bytes"
14+
"encoding/base64"
1415
"encoding/json"
1516
"errors"
1617
"fmt"
@@ -104,6 +105,71 @@ type Server struct {
104105
sessionMu sync.Mutex
105106
}
106107

108+
// memRef describe address and its size to stream data from
109+
type memRef struct {
110+
addr uint64
111+
size int64
112+
}
113+
114+
type referencesCollection struct {
115+
mu sync.Mutex
116+
refs map[string]memRef
117+
}
118+
119+
func (r *referencesCollection) get(reference string) (memRef, bool) {
120+
r.mu.Lock()
121+
defer r.mu.Unlock()
122+
123+
ref, ok := r.refs[reference]
124+
125+
return ref, ok
126+
}
127+
128+
func isAddressable(v *proc.Variable) bool {
129+
if v == nil || v.Unreadable != nil {
130+
return false
131+
}
132+
133+
switch v.Kind {
134+
case reflect.Slice, reflect.String:
135+
return true
136+
}
137+
138+
return false
139+
}
140+
141+
func (r *referencesCollection) put(v *proc.Variable) string {
142+
if !isAddressable(v) {
143+
return ""
144+
}
145+
146+
r.mu.Lock()
147+
defer r.mu.Unlock()
148+
149+
if r.refs == nil {
150+
r.refs = make(map[string]memRef)
151+
}
152+
153+
addr := v.Addr
154+
if v.Base != 0 {
155+
addr = v.Base
156+
}
157+
158+
ref := fmt.Sprintf("0x%x", addr)
159+
r.refs[ref] = memRef{addr: addr, size: v.Len}
160+
161+
return ref
162+
}
163+
164+
func (r *referencesCollection) reset() {
165+
r.mu.Lock()
166+
defer r.mu.Unlock()
167+
168+
if len(r.refs) > 0 {
169+
r.refs = make(map[string]memRef)
170+
}
171+
}
172+
107173
// Session is an abstraction for serving and shutting down
108174
// a DAP debug session with a pre-connected client.
109175
// TODO(polina): move this to a different file/package
@@ -119,6 +185,8 @@ type Session struct {
119185
// Reset at every stop.
120186
// See also comment for convertVariable.
121187
variableHandles *handlesMap[*fullyQualifiedVariable]
188+
// referencesCollection track references map for DAP client
189+
referencesCollection referencesCollection
122190
// args tracks special settings for handling debug session requests.
123191
args launchAttachArgs
124192
// exceptionErr tracks the runtime error that last occurred.
@@ -774,7 +842,7 @@ func (s *Session) handleRequest(request dap.Message) {
774842
case *dap.LoadedSourcesRequest: // Optional (capability 'supportsLoadedSourcesRequest')
775843
/*TODO*/ s.onLoadedSourcesRequest(request) // Not yet implemented
776844
case *dap.ReadMemoryRequest: // Optional (capability 'supportsReadMemoryRequest')
777-
/*TODO*/ s.onReadMemoryRequest(request) // Not yet implemented
845+
s.onReadMemoryRequest(request)
778846
case *dap.CancelRequest: // Optional (capability 'supportsCancelRequest')
779847
/*TODO*/ s.onCancelRequest(request) // Not yet implemented (does this make sense?)
780848
case *dap.ModulesRequest: // Optional (capability 'supportsModulesRequest')
@@ -871,7 +939,7 @@ func (s *Session) onInitializeRequest(request *dap.InitializeRequest) {
871939
response.Body.SupportsRestartRequest = true
872940
response.Body.SupportsSetExpression = false
873941
response.Body.SupportsLoadedSourcesRequest = false
874-
response.Body.SupportsReadMemoryRequest = false
942+
response.Body.SupportsReadMemoryRequest = true
875943
response.Body.SupportsCancelRequest = false
876944
response.Body.ExceptionBreakpointFilters = []dap.ExceptionBreakpointsFilter{
877945
{Filter: proc.UnrecoveredPanic, Label: "Unrecovered Panics", Default: true},
@@ -2656,6 +2724,7 @@ func (s *Session) childrenToDAPVariables(v *fullyQualifiedVariable) []dap.Variab
26562724
VariablesReference: cvarref,
26572725
IndexedVariables: getIndexedVariableCount(c),
26582726
NamedVariables: getNamedVariableCount(c),
2727+
MemoryReference: s.referencesCollection.put(c),
26592728
}
26602729
}
26612730
}
@@ -3355,10 +3424,92 @@ func (s *Session) onLoadedSourcesRequest(request *dap.LoadedSourcesRequest) {
33553424
s.sendNotYetImplementedErrorResponse(request.Request)
33563425
}
33573426

3358-
// onReadMemoryRequest sends a not-yet-implemented error response.
3359-
// Capability 'supportsReadMemoryRequest' is not set 'initialize' response.
3427+
// onReadMemoryRequest handles DAP read memory requests
33603428
func (s *Session) onReadMemoryRequest(request *dap.ReadMemoryRequest) {
3361-
s.sendNotYetImplementedErrorResponse(request.Request)
3429+
args := request.Arguments
3430+
3431+
if args.Count < 0 {
3432+
s.sendErrorResponse(request.Request, UnableToReadMemory, "Unable to read memory", "negative count")
3433+
return
3434+
}
3435+
3436+
ref, ok := s.referencesCollection.get(args.MemoryReference)
3437+
if !ok {
3438+
s.sendErrorResponse(request.Request, UnableToReadMemory, "Unable to read memory", "unknown memoryReference")
3439+
return
3440+
}
3441+
3442+
if args.Count == 0 {
3443+
s.send(makeReadMemoryResponse(request.Request, ref.addr, nil, 0))
3444+
return
3445+
}
3446+
3447+
endReq := int64(args.Offset + args.Count)
3448+
3449+
startRead := min(max(int64(args.Offset), 0), ref.size)
3450+
endRead := min(max(endReq, 0), ref.size)
3451+
3452+
memAddr := ref.addr + uint64(startRead)
3453+
3454+
readCount := endRead - startRead
3455+
if readCount <= 0 {
3456+
unreadable := max(args.Count, 0)
3457+
3458+
s.send(makeReadMemoryResponse(request.Request, memAddr, nil, unreadable))
3459+
return
3460+
}
3461+
3462+
unreadable := max(int64(args.Count)-readCount, 0)
3463+
3464+
data, n, err := s.readTargetMemory(memAddr, readCount)
3465+
if err != nil {
3466+
s.sendErrorResponse(request.Request, UnableToReadMemory, "Unable to read memory", err.Error())
3467+
return
3468+
}
3469+
3470+
if int64(n) < readCount {
3471+
unreadable += readCount - int64(n)
3472+
}
3473+
3474+
s.send(makeReadMemoryResponse(request.Request, memAddr, data, int(unreadable)))
3475+
}
3476+
3477+
func (s *Session) readTargetMemory(addr uint64, count int64) (data []byte, n int, err error) {
3478+
if count <= 0 {
3479+
return nil, 0, nil
3480+
}
3481+
3482+
buf := make([]byte, count)
3483+
3484+
tgrp, unlock := s.debugger.LockTargetGroup()
3485+
defer unlock()
3486+
3487+
n, err = tgrp.Selected.Memory().ReadMemory(buf, addr)
3488+
if err != nil {
3489+
return nil, 0, err
3490+
}
3491+
3492+
if n > 0 {
3493+
data = buf[:n]
3494+
}
3495+
3496+
return data, n, nil
3497+
}
3498+
3499+
func makeReadMemoryResponse(req dap.Request, addr uint64, data []byte, unreadable int) *dap.ReadMemoryResponse {
3500+
var response string
3501+
if len(data) > 0 {
3502+
response = base64.StdEncoding.EncodeToString(data)
3503+
}
3504+
3505+
return &dap.ReadMemoryResponse{
3506+
Response: *newResponse(req),
3507+
Body: dap.ReadMemoryResponseBody{
3508+
Address: fmt.Sprintf("%#x", addr),
3509+
Data: response,
3510+
UnreadableBytes: unreadable,
3511+
},
3512+
}
33623513
}
33633514

33643515
var invalidInstruction = dap.DisassembledInstruction{
@@ -3814,6 +3965,7 @@ Use 'Continue' to resume the original step command.`
38143965
func (s *Session) resetHandlesForStoppedEvent() {
38153966
s.stackFrameHandles.reset()
38163967
s.variableHandles.reset()
3968+
s.referencesCollection.reset()
38173969
s.exceptionErr = nil
38183970
}
38193971

0 commit comments

Comments
 (0)