debug 1.7.1 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +2 -2
- data/README.md +50 -44
- data/Rakefile +8 -3
- data/debug.gemspec +3 -3
- data/ext/debug/debug.c +18 -4
- data/ext/debug/extconf.rb +1 -0
- data/ext/debug/iseq_collector.c +2 -0
- data/lib/debug/breakpoint.rb +6 -8
- data/lib/debug/client.rb +3 -2
- data/lib/debug/config.rb +35 -7
- data/lib/debug/console.rb +8 -29
- data/lib/debug/dap_custom/traceInspector.rb +336 -0
- data/lib/debug/frame_info.rb +9 -0
- data/lib/debug/irb_integration.rb +27 -0
- data/lib/debug/server.rb +9 -8
- data/lib/debug/server_cdp.rb +71 -72
- data/lib/debug/server_dap.rb +227 -179
- data/lib/debug/session.rb +105 -58
- data/lib/debug/source_repository.rb +3 -3
- data/lib/debug/thread_client.rb +51 -25
- data/lib/debug/tracer.rb +4 -5
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +42 -42
- metadata +12 -10
data/lib/debug/console.rb
CHANGED
@@ -5,30 +5,9 @@ module DEBUGGER__
|
|
5
5
|
raise LoadError if CONFIG[:no_reline]
|
6
6
|
require 'reline'
|
7
7
|
|
8
|
-
# reline 0.2.7 or later is required.
|
9
|
-
raise LoadError if Reline::VERSION < '0.2.7'
|
10
|
-
|
11
8
|
require_relative 'color'
|
12
|
-
include Color
|
13
|
-
|
14
|
-
begin
|
15
|
-
prev = trap(:SIGWINCH, nil)
|
16
|
-
trap(:SIGWINCH, prev)
|
17
|
-
SIGWINCH_SUPPORTED = true
|
18
|
-
rescue ArgumentError
|
19
|
-
SIGWINCH_SUPPORTED = false
|
20
|
-
end
|
21
9
|
|
22
|
-
|
23
|
-
class ::Reline::LineEditor
|
24
|
-
m = Module.new do
|
25
|
-
def reset(prompt = '', encoding:)
|
26
|
-
super
|
27
|
-
Signal.trap(:SIGWINCH, nil)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
prepend m
|
31
|
-
end if SIGWINCH_SUPPORTED
|
10
|
+
include Color
|
32
11
|
|
33
12
|
def parse_input buff, commands
|
34
13
|
c, rest = get_command buff
|
@@ -56,10 +35,10 @@ module DEBUGGER__
|
|
56
35
|
Reline.prompt_proc = -> args, *kw do
|
57
36
|
case state = parse_input(args.first, commands)
|
58
37
|
when nil, :command
|
59
|
-
[prompt
|
38
|
+
[prompt]
|
60
39
|
when :ruby
|
61
|
-
[prompt.sub('rdbg'){colorize('ruby', [:RED])}]
|
62
|
-
end
|
40
|
+
[prompt.sub('rdbg'){colorize('ruby', [:RED])}]
|
41
|
+
end * args.size
|
63
42
|
end
|
64
43
|
|
65
44
|
Reline.completion_proc = -> given do
|
@@ -96,7 +75,7 @@ module DEBUGGER__
|
|
96
75
|
when nil
|
97
76
|
buff
|
98
77
|
when :ruby
|
99
|
-
colorize_code(buff
|
78
|
+
colorize_code(buff)
|
100
79
|
end
|
101
80
|
end unless CONFIG[:no_hint]
|
102
81
|
|
@@ -224,11 +203,11 @@ module DEBUGGER__
|
|
224
203
|
end
|
225
204
|
|
226
205
|
def load_history
|
227
|
-
read_history_file.
|
206
|
+
read_history_file.each{|line|
|
228
207
|
line.strip!
|
229
208
|
history << line unless line.empty?
|
230
|
-
}
|
209
|
+
} if history.empty?
|
210
|
+
history.count
|
231
211
|
end
|
232
212
|
end # class Console
|
233
213
|
end
|
234
|
-
|
@@ -0,0 +1,336 @@
|
|
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', 'command')
|
246
|
+
case cmd
|
247
|
+
when 'record'
|
248
|
+
process_event_record_cmd(req, result)
|
249
|
+
else
|
250
|
+
raise "Unknown command #{cmd}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def process_event_record_cmd(req, result)
|
255
|
+
cmd = req.dig('arguments', 'subCommand')
|
256
|
+
case cmd
|
257
|
+
when 'enable'
|
258
|
+
@ui.respond req, {}
|
259
|
+
when 'disable'
|
260
|
+
@ui.respond req, {}
|
261
|
+
when 'collect'
|
262
|
+
cnt = result.delete :dropped_trace_cnt
|
263
|
+
if cnt > 0
|
264
|
+
@ui.puts "Return #{result[:logs].size} traces and #{cnt} traces are dropped"
|
265
|
+
else
|
266
|
+
@ui.puts "Return #{result[:logs].size} traces"
|
267
|
+
end
|
268
|
+
@ui.respond req, result
|
269
|
+
else
|
270
|
+
raise "Unknown command #{cmd}"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
module Custom_ThreadClient
|
276
|
+
def custom_dap_request_rdbgTraceInspector(req)
|
277
|
+
cmd = req.dig('arguments', 'command')
|
278
|
+
case cmd
|
279
|
+
when 'record'
|
280
|
+
process_record_cmd(req)
|
281
|
+
else
|
282
|
+
raise "Unknown command #{cmd}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def process_record_cmd(req)
|
287
|
+
cmd = req.dig('arguments', 'subCommand')
|
288
|
+
case cmd
|
289
|
+
when 'enable'
|
290
|
+
size = req.dig('arguments', 'maxLogSize')
|
291
|
+
@recorder = Custom_Recorder.new max_log_size: size
|
292
|
+
@recorder.enable
|
293
|
+
event! :protocol_result, :rdbgTraceInspector, req
|
294
|
+
when 'disable'
|
295
|
+
if @recorder&.enabled?
|
296
|
+
@recorder.disable
|
297
|
+
end
|
298
|
+
@recorder = nil
|
299
|
+
event! :protocol_result, :rdbgTraceInspector, req
|
300
|
+
when 'collect'
|
301
|
+
logs = []
|
302
|
+
log_index = nil
|
303
|
+
trace_cnt = 0
|
304
|
+
unless @recorder.nil?
|
305
|
+
log_index = @recorder.log_index
|
306
|
+
@recorder.log.each{|frames|
|
307
|
+
crt_frame = frames[0]
|
308
|
+
log = {
|
309
|
+
name: crt_frame.name,
|
310
|
+
location: {
|
311
|
+
path: crt_frame.location.path,
|
312
|
+
line: crt_frame.location.lineno,
|
313
|
+
},
|
314
|
+
depth: crt_frame.frame_depth
|
315
|
+
}
|
316
|
+
if params = crt_frame.iseq_parameters_info
|
317
|
+
log[:parameters] = params
|
318
|
+
end
|
319
|
+
if return_str = crt_frame.return_str
|
320
|
+
log[:returnValue] = return_str
|
321
|
+
end
|
322
|
+
logs << log
|
323
|
+
}
|
324
|
+
trace_cnt = @recorder.dropped_trace_cnt
|
325
|
+
@recorder.dropped_trace_cnt = 0
|
326
|
+
end
|
327
|
+
event! :protocol_result, :rdbgTraceInspector, req, logs: logs, stoppedIndex: log_index, dropped_trace_cnt: trace_cnt
|
328
|
+
else
|
329
|
+
raise "Unknown command #{cmd}"
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
::DEBUGGER__::SESSION.extend_feature session: Custom_Session, thread_client: Custom_ThreadClient, ui: Custom_UI_DAP
|
335
|
+
end
|
336
|
+
end
|
data/lib/debug/frame_info.rb
CHANGED
@@ -147,6 +147,15 @@ module DEBUGGER__
|
|
147
147
|
end
|
148
148
|
end
|
149
149
|
|
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
|
+
|
150
159
|
def parameters_info
|
151
160
|
vars = iseq.parameters_symbols
|
152
161
|
vars.map{|var|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'irb'
|
4
|
+
|
5
|
+
module DEBUGGER__
|
6
|
+
module IrbPatch
|
7
|
+
def evaluate(line, line_no)
|
8
|
+
SESSION.send(:restart_all_threads)
|
9
|
+
super
|
10
|
+
# This is to communicate with the test framework so it can feed the next input
|
11
|
+
puts "INTERNAL_INFO: {}" if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
|
12
|
+
ensure
|
13
|
+
SESSION.send(:stop_all_threads)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ThreadClient
|
18
|
+
def activate_irb_integration
|
19
|
+
IRB.setup(location, argv: [])
|
20
|
+
workspace = IRB::WorkSpace.new(current_frame&.binding || TOPLEVEL_BINDING)
|
21
|
+
irb = IRB::Irb.new(workspace)
|
22
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
23
|
+
IRB::Debug.setup(irb)
|
24
|
+
IRB::Context.prepend(IrbPatch)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/debug/server.rb
CHANGED
@@ -133,7 +133,7 @@ module DEBUGGER__
|
|
133
133
|
require 'etc'
|
134
134
|
|
135
135
|
check_cookie $1
|
136
|
-
@sock.puts "PID: #{Process.pid}, $0: #{$0}"
|
136
|
+
@sock.puts "PID: #{Process.pid}, $0: #{$0}, session_name: #{CONFIG[:session_name]}"
|
137
137
|
@sock.puts "debug #{VERSION} on #{RUBY_DESCRIPTION}"
|
138
138
|
@sock.puts "uname: #{Etc.uname.inspect}"
|
139
139
|
@sock.close
|
@@ -149,7 +149,9 @@ module DEBUGGER__
|
|
149
149
|
end
|
150
150
|
parse_option(params)
|
151
151
|
|
152
|
-
|
152
|
+
session_name = CONFIG[:session_name]
|
153
|
+
session_name_str = ", session_name:#{session_name}" if session_name
|
154
|
+
puts "DEBUGGER (client): Connected. PID:#{Process.pid}, $0:#{$0}#{session_name_str}"
|
153
155
|
puts "DEBUGGER (client): Type `Ctrl-C` to enter the debug console." unless @need_pause_at_first
|
154
156
|
puts
|
155
157
|
|
@@ -406,14 +408,13 @@ module DEBUGGER__
|
|
406
408
|
require_relative 'server_cdp'
|
407
409
|
|
408
410
|
@uuid = SecureRandom.uuid
|
409
|
-
|
410
|
-
|
411
|
-
|
411
|
+
@chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
|
412
|
+
DEBUGGER__.warn <<~EOS
|
413
|
+
With Chrome browser, type the following URL in the address-bar:
|
412
414
|
|
413
|
-
|
415
|
+
devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&noJavaScriptCompletion=true&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
|
414
416
|
|
415
|
-
|
416
|
-
end
|
417
|
+
EOS
|
417
418
|
end
|
418
419
|
|
419
420
|
def accept
|