Skip to content

Commit c38c836

Browse files
committed
DAP: introduce Rdbg Inspector
1 parent 0ec2caf commit c38c836

File tree

6 files changed

+622
-1
lines changed

6 files changed

+622
-1
lines changed
+314
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
module DEBUGGER__
2+
module DAP_TraceInspector
3+
class MultiTracer < Tracer
4+
def initialize ui, evts, trace_params, max_log_size: nil, **kw
5+
@evts = evts
6+
@log = []
7+
@trace_params = trace_params
8+
if max_log_size
9+
@max_log_size = max_log_size
10+
else
11+
@max_log_size = 50000
12+
end
13+
@dropped_trace_cnt = 0
14+
super(ui, **kw)
15+
@type = 'multi'
16+
@name = 'TraceInspector'
17+
end
18+
19+
attr_accessor :dropped_trace_cnt
20+
attr_reader :log
21+
22+
def setup
23+
@tracer = TracePoint.new(*@evts){|tp|
24+
next if skip?(tp)
25+
26+
case tp.event
27+
when :call, :c_call, :b_call
28+
if @trace_params
29+
params = parameters_info tp
30+
end
31+
append(call_trace_log(tp, params: params))
32+
when :return, :c_return, :b_return
33+
return_str = DEBUGGER__.safe_inspect(tp.return_value, short: true, max_length: 4096)
34+
append(call_trace_log(tp, return_str: return_str))
35+
when :line
36+
append(line_trace_log(tp))
37+
end
38+
}
39+
end
40+
41+
def parameters_info tp
42+
b = tp.binding
43+
tp.parameters.map{|_type, name|
44+
begin
45+
{ name: name, value: DEBUGGER__.safe_inspect(b.local_variable_get(name), short: true, max_length: 4096) }
46+
rescue NameError, TypeError
47+
nil
48+
end
49+
}.compact
50+
end
51+
52+
def call_identifier_str tp
53+
if tp.defined_class
54+
minfo(tp)
55+
else
56+
"block"
57+
end
58+
end
59+
60+
def append log
61+
if @log.size >= @max_log_size
62+
@dropped_trace_cnt += 1
63+
@log.shift
64+
end
65+
@log << log
66+
end
67+
68+
def call_trace_log tp, return_str: nil, params: nil
69+
log = {
70+
depth: DEBUGGER__.frame_depth,
71+
name: call_identifier_str(tp),
72+
threadId: Thread.current.instance_variable_get(:@__thread_client_id),
73+
location: {
74+
path: tp.path,
75+
line: tp.lineno
76+
}
77+
}
78+
log[:returnValue] = return_str if return_str
79+
log[:parameters] = params if params && params.size > 0
80+
log
81+
end
82+
83+
def line_trace_log tp
84+
{
85+
depth: DEBUGGER__.frame_depth,
86+
threadId: Thread.current.instance_variable_get(:@__thread_client_id),
87+
location: {
88+
path: tp.path,
89+
line: tp.lineno
90+
}
91+
}
92+
end
93+
94+
def skip? tp
95+
super || !@evts.include?(tp.event)
96+
end
97+
98+
def skip_with_pattern?(tp)
99+
super && !tp.method_id&.match?(@pattern)
100+
end
101+
end
102+
103+
class Custom_Recorder < ThreadClient::Recorder
104+
def initialize max_log_size: nil
105+
if max_log_size
106+
@max_log_size = max_log_size
107+
else
108+
@max_log_size = 50000
109+
end
110+
@dropped_trace_cnt = 0
111+
super()
112+
end
113+
114+
attr_accessor :dropped_trace_cnt
115+
116+
def append frames
117+
if @log.size >= @max_log_size
118+
@dropped_trace_cnt += 1
119+
@log.shift
120+
end
121+
@log << frames
122+
end
123+
end
124+
125+
module Custom_UI_DAP
126+
def custom_dap_request_rdbgTraceInspector(req)
127+
@q_msg << req
128+
end
129+
end
130+
131+
module Custom_Session
132+
def process_trace_cmd req
133+
cmd = req.dig('arguments', 'subCommand')
134+
case cmd
135+
when 'enable'
136+
events = req.dig('arguments', 'events')
137+
evts = []
138+
trace_params = false
139+
filter = req.dig('arguments', 'filterRegExp')
140+
max_log_size = req.dig('arguments', 'maxLogSize')
141+
events.each{|evt|
142+
case evt
143+
when 'traceLine'
144+
evts << :line
145+
when 'traceCall'
146+
evts << :call
147+
evts << :b_call
148+
when 'traceReturn'
149+
evts << :return
150+
evts << :b_return
151+
when 'traceParams'
152+
trace_params = true
153+
when 'traceClanguageCall'
154+
evts << :c_call
155+
when 'traceClanguageReturn'
156+
evts << :c_return
157+
else
158+
raise "unknown trace type #{evt}"
159+
end
160+
}
161+
add_tracer MultiTracer.new @ui, evts, trace_params, max_log_size: max_log_size, pattern: filter
162+
@ui.respond req, {}
163+
when 'disable'
164+
if t = find_multi_trace
165+
t.disable
166+
end
167+
@ui.respond req, {}
168+
when 'collect'
169+
logs = []
170+
if t = find_multi_trace
171+
logs = t.log
172+
if t.dropped_trace_cnt > 0
173+
@ui.puts "Return #{logs.size} traces and #{t.dropped_trace_cnt} traces are dropped"
174+
else
175+
@ui.puts "Return #{logs.size} traces"
176+
end
177+
t.dropped_trace_cnt = 0
178+
end
179+
@ui.respond req, logs: logs
180+
else
181+
raise "Unknown trace sub command #{cmd}"
182+
end
183+
return :retry
184+
end
185+
186+
def find_multi_trace
187+
@tracers.values.each{|t|
188+
if t.type == 'multi'
189+
return t
190+
end
191+
}
192+
return nil
193+
end
194+
195+
def process_record_cmd req
196+
cmd = req.dig('arguments', 'subCommand')
197+
case cmd
198+
when 'enable'
199+
@tc << [:dap, :rdbgTraceInspector, req]
200+
when 'disable'
201+
@tc << [:dap, :rdbgTraceInspector, req]
202+
when 'step'
203+
tid = req.dig('arguments', 'threadId')
204+
count = req.dig('arguments', 'count')
205+
if tc = find_waiting_tc(tid)
206+
@ui.respond req, {}
207+
tc << [:step, :in, count]
208+
else
209+
fail_response req
210+
end
211+
when 'stepBack'
212+
tid = req.dig('arguments', 'threadId')
213+
count = req.dig('arguments', 'count')
214+
if tc = find_waiting_tc(tid)
215+
@ui.respond req, {}
216+
tc << [:step, :back, count]
217+
else
218+
fail_response req
219+
end
220+
when 'collect'
221+
tid = req.dig('arguments', 'threadId')
222+
if tc = find_waiting_tc(tid)
223+
tc << [:dap, :rdbgTraceInspector, req]
224+
else
225+
fail_response req
226+
end
227+
else
228+
raise "Unknown record sub command #{cmd}"
229+
end
230+
end
231+
232+
def custom_dap_request_rdbgTraceInspector(req)
233+
cmd = req.dig('arguments', 'command')
234+
case cmd
235+
when 'trace'
236+
process_trace_cmd req
237+
when 'record'
238+
process_record_cmd req
239+
else
240+
raise "Unknown command #{cmd}"
241+
end
242+
end
243+
244+
def custom_dap_request_event_rdbgTraceInspector(req, result)
245+
cmd = req.dig('arguments', 'subCommand')
246+
case cmd
247+
when 'enable'
248+
@ui.respond req, {}
249+
when 'disable'
250+
@ui.respond req, {}
251+
when 'collect'
252+
cnt = result.delete :dropped_trace_cnt
253+
if cnt > 0
254+
@ui.puts "Return #{result[:logs].size} traces and #{cnt} traces are dropped"
255+
else
256+
@ui.puts "Return #{result[:logs].size} traces"
257+
end
258+
@ui.respond req, result
259+
else
260+
raise "Unknown command #{cmd}"
261+
end
262+
end
263+
end
264+
265+
module Custom_ThreadClient
266+
def custom_dap_request_rdbgTraceInspector(req)
267+
cmd = req.dig('arguments', 'subCommand')
268+
case cmd
269+
when 'enable'
270+
size = req.dig('arguments', 'maxLogSize')
271+
@recorder = Custom_Recorder.new max_log_size: size
272+
@recorder.enable
273+
event! :protocol_result, :rdbgTraceInspector, req
274+
when 'disable'
275+
if @recorder&.enabled?
276+
@recorder.disable
277+
end
278+
@recorder = nil
279+
event! :protocol_result, :rdbgTraceInspector, req
280+
when 'collect'
281+
logs = []
282+
log_index = nil
283+
unless @recorder.nil?
284+
log_index = @recorder.log_index
285+
@recorder.log.each{|frames|
286+
crt_frame = frames[0]
287+
log = {
288+
name: crt_frame.name,
289+
location: {
290+
path: crt_frame.location.path,
291+
line: crt_frame.location.lineno,
292+
},
293+
depth: crt_frame.frame_depth
294+
}
295+
if params = crt_frame.iseq_parameters_info
296+
log[:parameters] = params
297+
end
298+
if return_str = crt_frame.return_str
299+
log[:returnValue] = return_str
300+
end
301+
logs << log
302+
}
303+
end
304+
event! :protocol_result, :rdbgTraceInspector, req, logs: logs, stoppedIndex: log_index, dropped_trace_cnt: @recorder.dropped_trace_cnt
305+
@recorder.dropped_trace_cnt = 0
306+
else
307+
raise "Unknown command #{cmd}"
308+
end
309+
end
310+
end
311+
312+
::DEBUGGER__::SESSION.extend_feature session: Custom_Session, thread_client: Custom_ThreadClient, ui: Custom_UI_DAP
313+
end
314+
end

lib/debug/frame_info.rb

+9
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ def local_variables
147147
end
148148
end
149149

150+
def iseq_parameters_info
151+
case frame_type
152+
when :block, :method
153+
parameters_info
154+
else
155+
nil
156+
end
157+
end
158+
150159
def parameters_info
151160
vars = iseq.parameters_symbols
152161
vars.map{|var|

lib/debug/server_dap.rb

+12
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,14 @@ def recv_request
274274
retry
275275
end
276276

277+
def load_rdbgExtension req
278+
if exts = req.dig('arguments', 'rdbgExtension')
279+
exts.each{|ext|
280+
require_relative "dap_custom/#{File.basename(ext)}"
281+
}
282+
end
283+
end
284+
277285
def process
278286
while req = recv_request
279287
raise "not a request: #{req.inspect}" unless req['type'] == 'request'
@@ -288,6 +296,8 @@ def process
288296
UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap') || true
289297
@nonstop = true
290298

299+
load_rdbgExtension req
300+
291301
when 'attach'
292302
send_response req
293303
UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
@@ -298,6 +308,8 @@ def process
298308
@nonstop = false
299309
end
300310

311+
load_rdbgExtension req
312+
301313
when 'configurationDone'
302314
send_response req
303315

lib/debug/thread_client.rb

+5-1
Original file line numberDiff line numberDiff line change
@@ -1300,10 +1300,14 @@ def initialize
13001300
frame._callee = b.eval('__callee__')
13011301
end
13021302
}
1303-
@log << frames
1303+
append(frames)
13041304
}
13051305
end
13061306

1307+
def append frames
1308+
@log << frames
1309+
end
1310+
13071311
def enable
13081312
unless @tp_recorder.enabled?
13091313
@log.clear

0 commit comments

Comments
 (0)