debug 1.0.0.beta5 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +213 -20
- data/Gemfile +1 -0
- data/README.md +460 -226
- data/Rakefile +2 -1
- data/TODO.md +0 -6
- data/bin/gentest +22 -0
- data/debug.gemspec +1 -0
- data/exe/rdbg +11 -18
- data/ext/debug/debug.c +11 -1
- data/lib/debug/breakpoint.rb +106 -62
- data/lib/debug/client.rb +11 -17
- data/lib/debug/color.rb +28 -7
- data/lib/debug/config.rb +378 -144
- data/lib/debug/console.rb +79 -57
- data/lib/debug/frame_info.rb +42 -8
- data/lib/debug/local.rb +91 -0
- data/lib/debug/open.rb +2 -1
- data/lib/debug/open_nonstop.rb +15 -0
- data/lib/debug/server.rb +96 -43
- data/lib/debug/server_dap.rb +34 -7
- data/lib/debug/session.rb +827 -341
- data/lib/debug/source_repository.rb +2 -0
- data/lib/debug/start.rb +5 -0
- data/lib/debug/thread_client.rb +691 -184
- data/lib/debug/tracer.rb +242 -0
- data/lib/debug/version.rb +3 -1
- data/lib/debug.rb +3 -0
- data/misc/README.md.erb +341 -216
- metadata +21 -4
- data/lib/debug/run.rb +0 -4
- data/lib/debug/test_console.rb +0 -0
data/lib/debug/start.rb
ADDED
data/lib/debug/thread_client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'objspace'
|
2
4
|
require 'pp'
|
3
5
|
|
@@ -5,6 +7,17 @@ require_relative 'frame_info'
|
|
5
7
|
require_relative 'color'
|
6
8
|
|
7
9
|
module DEBUGGER__
|
10
|
+
module SkipPathHelper
|
11
|
+
def skip_path?(path)
|
12
|
+
(skip_paths = CONFIG[:skip_path]) && skip_paths.any?{|skip_path| path.match?(skip_path)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def skip_location?(loc)
|
16
|
+
loc_path = loc.absolute_path || "!eval:#{loc.path}"
|
17
|
+
skip_path?(loc_path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
8
21
|
class ThreadClient
|
9
22
|
def self.current
|
10
23
|
Thread.current[:DEBUGGER__ThreadClient] || begin
|
@@ -14,8 +27,9 @@ module DEBUGGER__
|
|
14
27
|
end
|
15
28
|
|
16
29
|
include Color
|
30
|
+
include SkipPathHelper
|
17
31
|
|
18
|
-
attr_reader :location, :thread, :
|
32
|
+
attr_reader :location, :thread, :id, :recorder
|
19
33
|
|
20
34
|
def assemble_arguments(args)
|
21
35
|
args.map do |arg|
|
@@ -52,14 +66,14 @@ module DEBUGGER__
|
|
52
66
|
result = "#{call_identifier_str} at #{location_str}"
|
53
67
|
|
54
68
|
if return_str = frame.return_str
|
55
|
-
|
56
|
-
result += " #=> #{return_str}"
|
69
|
+
result += " #=> #{colorize_magenta(frame.return_str)}"
|
57
70
|
end
|
58
71
|
|
59
72
|
result
|
60
73
|
end
|
61
74
|
|
62
75
|
def initialize id, q_evt, q_cmd, thr = Thread.current
|
76
|
+
@is_management = false
|
63
77
|
@id = id
|
64
78
|
@thread = thr
|
65
79
|
@target_frames = nil
|
@@ -67,11 +81,52 @@ module DEBUGGER__
|
|
67
81
|
@q_cmd = q_cmd
|
68
82
|
@step_tp = nil
|
69
83
|
@output = []
|
70
|
-
@src_lines_on_stop = (::DEBUGGER__::CONFIG[:show_src_lines] || 10).to_i
|
71
|
-
@show_frames_on_stop = (::DEBUGGER__::CONFIG[:show_frames] || 2).to_i
|
72
84
|
@frame_formatter = method(:default_frame_formatter)
|
73
85
|
@var_map = {} # { thread_local_var_id => obj } for DAP
|
74
|
-
|
86
|
+
@recorder = nil
|
87
|
+
@mode = :waiting
|
88
|
+
set_mode :running
|
89
|
+
thr.instance_variable_set(:@__thread_client_id, id)
|
90
|
+
|
91
|
+
::DEBUGGER__.info("Thread \##{@id} is created.")
|
92
|
+
end
|
93
|
+
|
94
|
+
def deactivate
|
95
|
+
@step_tp.disable if @step_tp
|
96
|
+
end
|
97
|
+
|
98
|
+
def management?
|
99
|
+
@is_management
|
100
|
+
end
|
101
|
+
|
102
|
+
def is_management
|
103
|
+
@is_management = true
|
104
|
+
end
|
105
|
+
|
106
|
+
def set_mode mode
|
107
|
+
# STDERR.puts "#{@mode} => #{mode} @ #{caller.inspect}"
|
108
|
+
#pp caller
|
109
|
+
|
110
|
+
# mode transition check
|
111
|
+
case mode
|
112
|
+
when :running
|
113
|
+
raise "#{mode} is given, but #{mode}" unless self.waiting?
|
114
|
+
when :waiting
|
115
|
+
# TODO: there is waiting -> waiting
|
116
|
+
# raise "#{mode} is given, but #{mode}" unless self.running?
|
117
|
+
else
|
118
|
+
raise
|
119
|
+
end
|
120
|
+
|
121
|
+
@mode = mode
|
122
|
+
end
|
123
|
+
|
124
|
+
def running?
|
125
|
+
@mode == :running
|
126
|
+
end
|
127
|
+
|
128
|
+
def waiting?
|
129
|
+
@mode == :waiting
|
75
130
|
end
|
76
131
|
|
77
132
|
def name
|
@@ -83,17 +138,33 @@ module DEBUGGER__
|
|
83
138
|
end
|
84
139
|
|
85
140
|
def inspect
|
86
|
-
"#<DBG:TC #{self.id}:#{
|
141
|
+
"#<DBG:TC #{self.id}:#{@mode}@#{@thread.backtrace[-1]}>"
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_s
|
145
|
+
loc = current_frame&.location
|
146
|
+
|
147
|
+
if loc
|
148
|
+
str = "(#{@thread.name || @thread.status})@#{loc}"
|
149
|
+
else
|
150
|
+
str = "(#{@thread.name || @thread.status})@#{@thread.to_s}"
|
151
|
+
end
|
152
|
+
|
153
|
+
str += " (not under control)" unless self.waiting?
|
154
|
+
str
|
87
155
|
end
|
88
156
|
|
89
157
|
def puts str = ''
|
158
|
+
if @recorder&.replaying?
|
159
|
+
prefix = colorize_dim("[replay] ")
|
160
|
+
end
|
90
161
|
case str
|
91
162
|
when nil
|
92
163
|
@output << "\n"
|
93
164
|
when Array
|
94
165
|
str.each{|s| puts s}
|
95
166
|
else
|
96
|
-
@output << str.chomp
|
167
|
+
@output << "#{prefix}#{str.chomp}\n"
|
97
168
|
end
|
98
169
|
end
|
99
170
|
|
@@ -114,35 +185,61 @@ module DEBUGGER__
|
|
114
185
|
|
115
186
|
## events
|
116
187
|
|
117
|
-
def
|
118
|
-
if
|
119
|
-
# raise Interrupt
|
120
|
-
else
|
121
|
-
on_suspend :trap, sig: sig
|
122
|
-
end
|
123
|
-
end
|
188
|
+
def wait_reply event_arg
|
189
|
+
return if management?
|
124
190
|
|
125
|
-
|
126
|
-
|
191
|
+
set_mode :waiting
|
192
|
+
|
193
|
+
event!(*event_arg)
|
194
|
+
wait_next_action
|
127
195
|
end
|
128
196
|
|
129
197
|
def on_thread_begin th
|
130
|
-
|
131
|
-
wait_next_action
|
198
|
+
wait_reply [:thread_begin, th]
|
132
199
|
end
|
133
200
|
|
134
201
|
def on_load iseq, eval_src
|
135
|
-
|
136
|
-
|
202
|
+
wait_reply [:load, iseq, eval_src]
|
203
|
+
end
|
204
|
+
|
205
|
+
def on_init name
|
206
|
+
wait_reply [:init, name]
|
207
|
+
end
|
208
|
+
|
209
|
+
def on_trace trace_id, msg
|
210
|
+
wait_reply [:trace, trace_id, msg]
|
137
211
|
end
|
138
212
|
|
139
213
|
def on_breakpoint tp, bp
|
140
|
-
|
214
|
+
suspend tp.event, tp, bp: bp
|
215
|
+
end
|
216
|
+
|
217
|
+
def on_trap sig
|
218
|
+
if waiting?
|
219
|
+
# raise Interrupt
|
220
|
+
else
|
221
|
+
suspend :trap, sig: sig
|
222
|
+
end
|
141
223
|
end
|
142
224
|
|
143
|
-
def
|
225
|
+
def on_pause
|
226
|
+
suspend :pause
|
227
|
+
end
|
228
|
+
|
229
|
+
def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil
|
230
|
+
return if management?
|
231
|
+
|
144
232
|
@current_frame_index = 0
|
145
|
-
|
233
|
+
|
234
|
+
case
|
235
|
+
when postmortem_frames
|
236
|
+
@target_frames = postmortem_frames
|
237
|
+
@postmortem = true
|
238
|
+
when replay_frames
|
239
|
+
@target_frames = replay_frames
|
240
|
+
else
|
241
|
+
@target_frames = DEBUGGER__.capture_frames(__dir__)
|
242
|
+
end
|
146
243
|
|
147
244
|
cf = @target_frames.first
|
148
245
|
if cf
|
@@ -160,8 +257,10 @@ module DEBUGGER__
|
|
160
257
|
end
|
161
258
|
|
162
259
|
if event != :pause
|
163
|
-
show_src max_lines:
|
164
|
-
show_frames
|
260
|
+
show_src max_lines: (CONFIG[:show_src_lines] || 10)
|
261
|
+
show_frames CONFIG[:show_frames] || 2
|
262
|
+
|
263
|
+
set_mode :waiting
|
165
264
|
|
166
265
|
if bp
|
167
266
|
event! :suspend, :breakpoint, bp.key
|
@@ -170,11 +269,18 @@ module DEBUGGER__
|
|
170
269
|
else
|
171
270
|
event! :suspend, event
|
172
271
|
end
|
272
|
+
else
|
273
|
+
set_mode :waiting
|
173
274
|
end
|
174
275
|
|
175
276
|
wait_next_action
|
176
277
|
end
|
177
278
|
|
279
|
+
def replay_suspend
|
280
|
+
# @recorder.current_position
|
281
|
+
suspend :replay, replay_frames: @recorder.current_frame
|
282
|
+
end
|
283
|
+
|
178
284
|
## control all
|
179
285
|
|
180
286
|
begin
|
@@ -184,41 +290,77 @@ module DEBUGGER__
|
|
184
290
|
SUPPORT_TARGET_THREAD = false
|
185
291
|
end
|
186
292
|
|
187
|
-
def step_tp
|
293
|
+
def step_tp iter
|
188
294
|
@step_tp.disable if @step_tp
|
189
295
|
|
190
296
|
thread = Thread.current
|
191
297
|
|
192
298
|
if SUPPORT_TARGET_THREAD
|
193
299
|
@step_tp = TracePoint.new(:line, :b_return, :return){|tp|
|
194
|
-
next if SESSION.
|
300
|
+
next if SESSION.break_at? tp.path, tp.lineno
|
195
301
|
next if !yield
|
196
302
|
next if tp.path.start_with?(__dir__)
|
197
|
-
next
|
303
|
+
next if tp.path.start_with?('<internal:trace_point>')
|
304
|
+
next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
|
305
|
+
loc = caller_locations(1, 1).first
|
306
|
+
next if skip_location?(loc)
|
307
|
+
next if iter && (iter -= 1) > 0
|
198
308
|
|
199
309
|
tp.disable
|
200
|
-
|
310
|
+
suspend tp.event, tp
|
201
311
|
}
|
202
312
|
@step_tp.enable(target_thread: thread)
|
203
313
|
else
|
204
314
|
@step_tp = TracePoint.new(:line, :b_return, :return){|tp|
|
205
315
|
next if thread != Thread.current
|
206
|
-
next if SESSION.
|
316
|
+
next if SESSION.break_at? tp.path, tp.lineno
|
207
317
|
next if !yield
|
208
|
-
next
|
318
|
+
next if tp.path.start_with?(__dir__)
|
319
|
+
next if tp.path.start_with?('<internal:trace_point>')
|
320
|
+
next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
|
321
|
+
loc = caller_locations(1, 1).first
|
322
|
+
next if skip_location?(loc)
|
323
|
+
next if iter && (iter -= 1) > 0
|
209
324
|
|
210
325
|
tp.disable
|
211
|
-
|
326
|
+
suspend tp.event, tp
|
212
327
|
}
|
213
328
|
@step_tp.enable
|
214
329
|
end
|
215
330
|
end
|
216
331
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
332
|
+
## cmd helpers
|
333
|
+
|
334
|
+
# this method is extracted to hide frame_eval's local variables from C method eval's binding
|
335
|
+
def instance_eval_for_cmethod frame_self, src
|
336
|
+
frame_self.instance_eval(src)
|
337
|
+
end
|
338
|
+
|
339
|
+
def frame_eval src, re_raise: false
|
340
|
+
begin
|
341
|
+
@success_last_eval = false
|
342
|
+
|
343
|
+
b = current_frame.eval_binding
|
344
|
+
result = if b
|
345
|
+
f, _l = b.source_location
|
346
|
+
b.eval(src, "(rdbg)/#{f}")
|
347
|
+
else
|
348
|
+
frame_self = current_frame.self
|
349
|
+
instance_eval_for_cmethod(frame_self, src)
|
350
|
+
end
|
351
|
+
@success_last_eval = true
|
352
|
+
result
|
353
|
+
|
354
|
+
rescue Exception => e
|
355
|
+
return yield(e) if block_given?
|
356
|
+
|
357
|
+
puts "eval error: #{e}"
|
358
|
+
|
359
|
+
e.backtrace_locations.each do |loc|
|
360
|
+
break if loc.path == __FILE__
|
361
|
+
puts " #{loc}"
|
362
|
+
end
|
363
|
+
raise if re_raise
|
222
364
|
end
|
223
365
|
end
|
224
366
|
|
@@ -233,11 +375,9 @@ module DEBUGGER__
|
|
233
375
|
frame_line = frame.location.lineno - 1
|
234
376
|
|
235
377
|
lines = file_lines.map.with_index do |e, i|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
" #{'%4d' % (i+1)}| #{e}"
|
240
|
-
end
|
378
|
+
cur = i == frame_line ? '=>' : ' '
|
379
|
+
line = colorize_dim('%4d|' % (i+1))
|
380
|
+
"#{cur}#{line} #{e}"
|
241
381
|
end
|
242
382
|
|
243
383
|
unless start_line
|
@@ -275,98 +415,160 @@ module DEBUGGER__
|
|
275
415
|
exit!
|
276
416
|
end
|
277
417
|
|
278
|
-
def
|
279
|
-
|
280
|
-
|
281
|
-
path = frame.path
|
282
|
-
else
|
283
|
-
return # can't get path
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
if File.exist?(path)
|
288
|
-
if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR'])
|
289
|
-
puts "command: #{editor}"
|
290
|
-
puts " path: #{path}"
|
291
|
-
system(editor, path)
|
292
|
-
else
|
293
|
-
puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']"
|
294
|
-
end
|
418
|
+
def current_frame
|
419
|
+
if @target_frames
|
420
|
+
@target_frames[@current_frame_index]
|
295
421
|
else
|
296
|
-
|
422
|
+
nil
|
297
423
|
end
|
298
424
|
end
|
299
425
|
|
300
|
-
|
426
|
+
## cmd: show
|
427
|
+
|
428
|
+
def show_locals pat
|
301
429
|
if s = current_frame&.self
|
302
|
-
|
430
|
+
puts_variable_info '%self', s, pat
|
303
431
|
end
|
304
432
|
if current_frame&.has_return_value
|
305
|
-
|
433
|
+
puts_variable_info '%return', current_frame.return_value, pat
|
306
434
|
end
|
307
435
|
if current_frame&.has_raised_exception
|
308
|
-
|
436
|
+
puts_variable_info "%raised", current_frame.raised_exception, pat
|
309
437
|
end
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
438
|
+
|
439
|
+
if vars = current_frame&.local_variables
|
440
|
+
vars.each{|var, val|
|
441
|
+
puts_variable_info var, val, pat
|
314
442
|
}
|
315
443
|
end
|
316
444
|
end
|
317
445
|
|
318
|
-
def show_ivars
|
446
|
+
def show_ivars pat
|
319
447
|
if s = current_frame&.self
|
320
|
-
s.instance_variables.each{|iv|
|
448
|
+
s.instance_variables.sort.each{|iv|
|
321
449
|
value = s.instance_variable_get(iv)
|
322
|
-
|
450
|
+
puts_variable_info iv, value, pat
|
323
451
|
}
|
324
452
|
end
|
325
453
|
end
|
326
454
|
|
327
|
-
def
|
328
|
-
|
329
|
-
|
455
|
+
def show_consts pat, only_self: false
|
456
|
+
if s = current_frame&.self
|
457
|
+
cs = {}
|
458
|
+
if s.kind_of? Module
|
459
|
+
cs[s] = :self
|
460
|
+
else
|
461
|
+
s = s.class
|
462
|
+
cs[s] = :self unless only_self
|
463
|
+
end
|
330
464
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
465
|
+
unless only_self
|
466
|
+
s.ancestors.each{|c| break if c == Object; cs[c] = :ancestors}
|
467
|
+
if b = current_frame&.binding
|
468
|
+
b.eval('Module.nesting').each{|c| cs[c] = :nesting unless cs.has_key? c}
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
names = {}
|
473
|
+
|
474
|
+
cs.each{|c, _|
|
475
|
+
c.constants(false).sort.each{|name|
|
476
|
+
next if names.has_key? name
|
477
|
+
names[name] = nil
|
478
|
+
value = c.const_get(name)
|
479
|
+
puts_variable_info name, value, pat
|
480
|
+
}
|
481
|
+
}
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
SKIP_GLOBAL_LIST = %i[$= $KCODE $-K $SAFE].freeze
|
486
|
+
def show_globals pat
|
487
|
+
global_variables.sort.each{|name|
|
488
|
+
next if SKIP_GLOBAL_LIST.include? name
|
489
|
+
|
490
|
+
value = eval(name.to_s)
|
491
|
+
puts_variable_info name, value, pat
|
492
|
+
}
|
493
|
+
end
|
341
494
|
|
495
|
+
def puts_variable_info label, obj, pat
|
496
|
+
return if pat && pat !~ label
|
497
|
+
|
498
|
+
begin
|
499
|
+
inspected = obj.inspect
|
342
500
|
rescue Exception => e
|
343
|
-
|
501
|
+
inspected = e.inspect
|
502
|
+
end
|
503
|
+
mono_info = "#{label} = #{inspected}"
|
344
504
|
|
345
|
-
|
505
|
+
w = SESSION::width
|
346
506
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
507
|
+
if mono_info.length >= w
|
508
|
+
info = truncate(mono_info, width: w)
|
509
|
+
else
|
510
|
+
valstr = colored_inspect(obj, width: 2 ** 30)
|
511
|
+
valstr = inspected if valstr.lines.size > 1
|
512
|
+
info = "#{colorize_cyan(label)} = #{valstr}"
|
352
513
|
end
|
514
|
+
|
515
|
+
puts info
|
353
516
|
end
|
354
517
|
|
355
|
-
def
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
518
|
+
def truncate(string, width:)
|
519
|
+
str = string[0 .. (width-4)] + '...'
|
520
|
+
str += ">" if str.start_with?("#<")
|
521
|
+
str
|
522
|
+
end
|
523
|
+
|
524
|
+
### cmd: show edit
|
525
|
+
|
526
|
+
def show_by_editor path = nil
|
527
|
+
unless path
|
528
|
+
if @target_frames && frame = @target_frames[@current_frame_index]
|
529
|
+
path = frame.path
|
530
|
+
else
|
531
|
+
return # can't get path
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
if File.exist?(path)
|
536
|
+
if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR'])
|
537
|
+
puts "command: #{editor}"
|
538
|
+
puts " path: #{path}"
|
539
|
+
system(editor, path)
|
540
|
+
else
|
541
|
+
puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']"
|
542
|
+
end
|
543
|
+
else
|
544
|
+
puts "Can not find file: #{path}"
|
545
|
+
end
|
361
546
|
end
|
362
547
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
548
|
+
### cmd: show frames
|
549
|
+
|
550
|
+
def show_frames max = nil, pattern = nil
|
551
|
+
if @target_frames && (max ||= @target_frames.size) > 0
|
552
|
+
frames = []
|
553
|
+
@target_frames.each_with_index{|f, i|
|
554
|
+
next if pattern && !(f.name.match?(pattern) || f.location_str.match?(pattern))
|
555
|
+
next if CONFIG[:skip_path] && CONFIG[:skip_path].any?{|pat|
|
556
|
+
case pat
|
557
|
+
when String
|
558
|
+
f.location_str.start_with?(pat)
|
559
|
+
when Regexp
|
560
|
+
f.location_str.match?(pat)
|
561
|
+
end
|
562
|
+
}
|
563
|
+
|
564
|
+
frames << [i, f]
|
565
|
+
}
|
566
|
+
|
567
|
+
size = frames.size
|
367
568
|
max.times{|i|
|
368
|
-
break
|
369
|
-
|
569
|
+
break unless frames[i]
|
570
|
+
index, frame = frames[i]
|
571
|
+
puts frame_str(index, frame: frame)
|
370
572
|
}
|
371
573
|
puts " # and #{size - max} frames (use `bt' command for all frames)" if max < size
|
372
574
|
end
|
@@ -376,75 +578,144 @@ module DEBUGGER__
|
|
376
578
|
puts frame_str(i)
|
377
579
|
end
|
378
580
|
|
379
|
-
def
|
581
|
+
def frame_str(i, frame: @target_frames[i])
|
582
|
+
cur_str = (@current_frame_index == i ? '=>' : ' ')
|
583
|
+
prefix = "#{cur_str}##{i}"
|
584
|
+
frame_string = @frame_formatter.call(frame)
|
585
|
+
"#{prefix}\t#{frame_string}"
|
586
|
+
end
|
587
|
+
|
588
|
+
### cmd: show outline
|
589
|
+
|
590
|
+
def show_outline expr
|
380
591
|
begin
|
381
|
-
|
592
|
+
obj = frame_eval(expr, re_raise: true)
|
382
593
|
rescue Exception
|
383
594
|
# ignore
|
384
595
|
else
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
596
|
+
o = Output.new(@output)
|
597
|
+
|
598
|
+
locals = current_frame&.local_variables
|
599
|
+
klass = (obj.class == Class || obj.class == Module ? obj : obj.class)
|
600
|
+
|
601
|
+
o.dump("constants", obj.constants) if obj.respond_to?(:constants)
|
602
|
+
outline_method(o, klass, obj)
|
603
|
+
o.dump("instance variables", obj.instance_variables)
|
604
|
+
o.dump("class variables", klass.class_variables)
|
605
|
+
o.dump("locals", locals.keys) if locals
|
394
606
|
end
|
395
607
|
end
|
396
608
|
|
397
|
-
def
|
609
|
+
def outline_method(o, klass, obj)
|
610
|
+
singleton_class = begin obj.singleton_class; rescue TypeError; nil end
|
611
|
+
maps = class_method_map((singleton_class || klass).ancestors)
|
612
|
+
maps.each do |mod, methods|
|
613
|
+
name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods"
|
614
|
+
o.dump(name, methods)
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
def class_method_map(classes)
|
619
|
+
dumped = Array.new
|
620
|
+
classes.reject { |mod| mod >= Object }.map do |mod|
|
621
|
+
methods = mod.public_instance_methods(false).select do |m|
|
622
|
+
dumped.push(m) unless dumped.include?(m)
|
623
|
+
end
|
624
|
+
[mod, methods]
|
625
|
+
end.reverse
|
626
|
+
end
|
627
|
+
|
628
|
+
## cmd: breakpoint
|
629
|
+
|
630
|
+
def make_breakpoint args
|
398
631
|
case args.first
|
399
632
|
when :method
|
400
|
-
klass_name, op, method_name, cond = args[1..]
|
401
|
-
bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond)
|
633
|
+
klass_name, op, method_name, cond, cmd = args[1..]
|
634
|
+
bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond: cond, command: cmd)
|
402
635
|
begin
|
403
636
|
bp.enable
|
404
637
|
rescue Exception => e
|
405
638
|
puts e.message
|
406
639
|
::DEBUGGER__::METHOD_ADDED_TRACKER.enable
|
407
640
|
end
|
408
|
-
|
641
|
+
|
642
|
+
bp
|
643
|
+
when :watch
|
644
|
+
ivar, object, result = args[1..]
|
645
|
+
WatchIVarBreakpoint.new(ivar, object, result)
|
409
646
|
else
|
410
647
|
raise "unknown breakpoint: #{args}"
|
411
648
|
end
|
412
649
|
end
|
413
650
|
|
414
|
-
|
415
|
-
@mode = mode
|
651
|
+
class SuspendReplay < Exception
|
416
652
|
end
|
417
653
|
|
418
654
|
def wait_next_action
|
419
|
-
|
655
|
+
wait_next_action_
|
656
|
+
rescue SuspendReplay
|
657
|
+
replay_suspend
|
658
|
+
end
|
659
|
+
|
660
|
+
def wait_next_action_
|
661
|
+
# assertions
|
662
|
+
raise "@mode is #{@mode}" unless @mode == :waiting
|
420
663
|
|
421
|
-
SESSION.
|
664
|
+
unless SESSION.active?
|
665
|
+
pp caller
|
666
|
+
set_mode :running
|
667
|
+
return
|
668
|
+
end
|
669
|
+
# SESSION.check_forked
|
422
670
|
|
423
|
-
while
|
424
|
-
|
671
|
+
while true
|
672
|
+
begin
|
673
|
+
set_mode :waiting if @mode != :waiting
|
674
|
+
cmds = @q_cmd.pop
|
675
|
+
# pp [self, cmds: cmds]
|
676
|
+
break unless cmds
|
677
|
+
ensure
|
678
|
+
set_mode :running
|
679
|
+
end
|
425
680
|
|
426
681
|
cmd, *args = *cmds
|
427
682
|
|
428
683
|
case cmd
|
429
684
|
when :continue
|
430
685
|
break
|
686
|
+
|
431
687
|
when :step
|
432
688
|
step_type = args[0]
|
689
|
+
iter = args[1]
|
690
|
+
|
433
691
|
case step_type
|
434
692
|
when :in
|
435
|
-
|
693
|
+
if @recorder&.replaying?
|
694
|
+
@recorder.step_forward
|
695
|
+
raise SuspendReplay
|
696
|
+
else
|
697
|
+
step_tp iter do
|
698
|
+
true
|
699
|
+
end
|
700
|
+
break
|
701
|
+
end
|
702
|
+
|
436
703
|
when :next
|
437
704
|
frame = @target_frames.first
|
438
705
|
path = frame.location.absolute_path || "!eval:#{frame.path}"
|
439
706
|
line = frame.location.lineno
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
next_line =
|
707
|
+
|
708
|
+
if frame.iseq
|
709
|
+
frame.iseq.traceable_lines_norec(lines = {})
|
710
|
+
next_line = lines.keys.bsearch{|e| e > line}
|
711
|
+
if !next_line && (last_line = frame.iseq.last_line) > line
|
712
|
+
next_line = last_line
|
713
|
+
end
|
444
714
|
end
|
715
|
+
|
445
716
|
depth = @target_frames.first.frame_depth
|
446
717
|
|
447
|
-
step_tp
|
718
|
+
step_tp iter do
|
448
719
|
loc = caller_locations(2, 1).first
|
449
720
|
loc_path = loc.absolute_path || "!eval:#{loc.path}"
|
450
721
|
|
@@ -455,34 +726,57 @@ module DEBUGGER__
|
|
455
726
|
(next_line && loc_path == path &&
|
456
727
|
(loc_lineno = loc.lineno) > line &&
|
457
728
|
loc_lineno <= next_line)
|
458
|
-
|
729
|
+
end
|
730
|
+
break
|
731
|
+
|
459
732
|
when :finish
|
460
733
|
depth = @target_frames.first.frame_depth
|
461
|
-
step_tp
|
734
|
+
step_tp iter do
|
462
735
|
# 3 is debugger's frame count
|
463
736
|
DEBUGGER__.frame_depth - 3 < depth
|
464
|
-
|
737
|
+
end
|
738
|
+
break
|
739
|
+
|
740
|
+
when :back
|
741
|
+
if @recorder&.can_step_back?
|
742
|
+
unless @recorder.backup_frames
|
743
|
+
@recorder.backup_frames = @target_frames
|
744
|
+
end
|
745
|
+
@recorder.step_back
|
746
|
+
raise SuspendReplay
|
747
|
+
else
|
748
|
+
puts "Can not step back more."
|
749
|
+
event! :result, nil
|
750
|
+
end
|
751
|
+
|
752
|
+
when :reset
|
753
|
+
if @recorder&.replaying?
|
754
|
+
@recorder.step_reset
|
755
|
+
raise SuspendReplay
|
756
|
+
end
|
757
|
+
|
465
758
|
else
|
466
|
-
raise
|
759
|
+
raise "unknown: #{type}"
|
467
760
|
end
|
468
|
-
|
761
|
+
|
469
762
|
when :eval
|
470
763
|
eval_type, eval_src = *args
|
471
764
|
|
472
|
-
case eval_type
|
473
|
-
when :display, :try_display
|
474
|
-
else
|
475
|
-
result = frame_eval(eval_src)
|
476
|
-
end
|
477
765
|
result_type = nil
|
478
766
|
|
479
767
|
case eval_type
|
480
768
|
when :p
|
481
|
-
|
769
|
+
result = frame_eval(eval_src)
|
770
|
+
puts "=> " + color_pp(result, 2 ** 30)
|
771
|
+
if alloc_path = ObjectSpace.allocation_sourcefile(result)
|
772
|
+
puts "allocated at #{alloc_path}:#{ObjectSpace.allocation_sourceline(result)}"
|
773
|
+
end
|
482
774
|
when :pp
|
483
|
-
|
484
|
-
|
485
|
-
|
775
|
+
result = frame_eval(eval_src)
|
776
|
+
puts color_pp(result, SESSION.width)
|
777
|
+
if alloc_path = ObjectSpace.allocation_sourcefile(result)
|
778
|
+
puts "allocated at #{alloc_path}:#{ObjectSpace.allocation_sourceline(result)}"
|
779
|
+
end
|
486
780
|
when :call
|
487
781
|
result = frame_eval(eval_src)
|
488
782
|
when :display, :try_display
|
@@ -497,23 +791,6 @@ module DEBUGGER__
|
|
497
791
|
|
498
792
|
result_type = eval_type
|
499
793
|
result = failed_results
|
500
|
-
when :watch
|
501
|
-
if @success_last_eval
|
502
|
-
if eval_src.match?(/@\w+/)
|
503
|
-
object =
|
504
|
-
if b = current_frame.binding
|
505
|
-
b.receiver
|
506
|
-
else
|
507
|
-
current_frame.self
|
508
|
-
end
|
509
|
-
puts "#{object} #{eval_src} = #{result}"
|
510
|
-
result = WatchIVarBreakpoint.new(eval_src, object, result)
|
511
|
-
end
|
512
|
-
|
513
|
-
result_type = :watch
|
514
|
-
else
|
515
|
-
result = nil
|
516
|
-
end
|
517
794
|
else
|
518
795
|
raise "unknown error option: #{args.inspect}"
|
519
796
|
end
|
@@ -549,12 +826,14 @@ module DEBUGGER__
|
|
549
826
|
raise "unsupported frame operation: #{arg.inspect}"
|
550
827
|
end
|
551
828
|
event! :result, nil
|
829
|
+
|
552
830
|
when :show
|
553
831
|
type = args.shift
|
554
832
|
|
555
833
|
case type
|
556
834
|
when :backtrace
|
557
|
-
|
835
|
+
max_lines, pattern = *args
|
836
|
+
show_frames max_lines, pattern
|
558
837
|
|
559
838
|
when :list
|
560
839
|
show_src(update_line: true, **(args.first || {}))
|
@@ -562,14 +841,30 @@ module DEBUGGER__
|
|
562
841
|
when :edit
|
563
842
|
show_by_editor(args.first)
|
564
843
|
|
565
|
-
when :
|
566
|
-
|
567
|
-
show_locals
|
568
|
-
show_ivars
|
844
|
+
when :default
|
845
|
+
pat = args.shift
|
846
|
+
show_locals pat
|
847
|
+
show_ivars pat
|
848
|
+
show_consts pat, only_self: true
|
849
|
+
|
850
|
+
when :locals
|
851
|
+
pat = args.shift
|
852
|
+
show_locals pat
|
853
|
+
|
854
|
+
when :ivars
|
855
|
+
pat = args.shift
|
856
|
+
show_ivars pat
|
857
|
+
|
858
|
+
when :consts
|
859
|
+
pat = args.shift
|
860
|
+
show_consts pat
|
861
|
+
|
862
|
+
when :globals
|
863
|
+
pat = args.shift
|
864
|
+
show_globals pat
|
569
865
|
|
570
|
-
when :
|
571
|
-
|
572
|
-
show_object_info expr
|
866
|
+
when :outline
|
867
|
+
show_outline args.first || 'self'
|
573
868
|
|
574
869
|
else
|
575
870
|
raise "unknown show param: " + [type, *args].inspect
|
@@ -578,36 +873,248 @@ module DEBUGGER__
|
|
578
873
|
event! :result, nil
|
579
874
|
|
580
875
|
when :breakpoint
|
581
|
-
|
876
|
+
case args[0]
|
877
|
+
when :method
|
878
|
+
bp = make_breakpoint args
|
879
|
+
event! :result, :method_breakpoint, bp
|
880
|
+
when :watch
|
881
|
+
ivar = args[1]
|
882
|
+
result = frame_eval(ivar)
|
883
|
+
|
884
|
+
if @success_last_eval
|
885
|
+
object =
|
886
|
+
if b = current_frame.binding
|
887
|
+
b.receiver
|
888
|
+
else
|
889
|
+
current_frame.self
|
890
|
+
end
|
891
|
+
bp = make_breakpoint [:watch, ivar, object, result]
|
892
|
+
event! :result, :watch_breakpoint, bp
|
893
|
+
else
|
894
|
+
event! :result, nil
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
898
|
+
when :trace
|
899
|
+
case args.shift
|
900
|
+
when :object
|
901
|
+
begin
|
902
|
+
obj = frame_eval args.shift, re_raise: true
|
903
|
+
opt = args.shift
|
904
|
+
obj_inspect = obj.inspect
|
905
|
+
|
906
|
+
width = 50
|
907
|
+
|
908
|
+
if obj_inspect.length >= width
|
909
|
+
obj_inspect = truncate(obj_inspect, width: width)
|
910
|
+
end
|
911
|
+
|
912
|
+
event! :result, :trace_pass, obj.object_id, obj_inspect, opt
|
913
|
+
rescue => e
|
914
|
+
puts e.message
|
915
|
+
event! :result, nil
|
916
|
+
end
|
917
|
+
else
|
918
|
+
raise "unreachable"
|
919
|
+
end
|
920
|
+
|
921
|
+
when :record
|
922
|
+
case args[0]
|
923
|
+
when nil
|
924
|
+
# ok
|
925
|
+
when :on
|
926
|
+
# enable recording
|
927
|
+
if !@recorder
|
928
|
+
@recorder = Recorder.new
|
929
|
+
@recorder.enable
|
930
|
+
end
|
931
|
+
when :off
|
932
|
+
if @recorder&.enabled?
|
933
|
+
@recorder.disable
|
934
|
+
end
|
935
|
+
else
|
936
|
+
raise "unknown: #{args.inspect}"
|
937
|
+
end
|
938
|
+
|
939
|
+
if @recorder&.enabled?
|
940
|
+
puts "Recorder for #{Thread.current}: on (#{@recorder.log.size} records)"
|
941
|
+
else
|
942
|
+
puts "Recorder for #{Thread.current}: off"
|
943
|
+
end
|
944
|
+
event! :result, nil
|
582
945
|
|
583
946
|
when :dap
|
584
947
|
process_dap args
|
585
|
-
|
586
948
|
else
|
587
949
|
raise [cmd, *args].inspect
|
588
950
|
end
|
589
951
|
end
|
590
952
|
|
591
|
-
rescue SystemExit
|
953
|
+
rescue SuspendReplay, SystemExit
|
592
954
|
raise
|
593
955
|
rescue Exception => e
|
594
|
-
pp [__FILE__
|
956
|
+
pp ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
|
595
957
|
raise
|
596
|
-
ensure
|
597
|
-
set_mode nil
|
598
958
|
end
|
599
959
|
|
600
|
-
|
601
|
-
|
960
|
+
class Recorder
|
961
|
+
attr_reader :log, :index
|
962
|
+
attr_accessor :backup_frames
|
963
|
+
|
964
|
+
include SkipPathHelper
|
965
|
+
|
966
|
+
def initialize
|
967
|
+
@log = []
|
968
|
+
@index = 0
|
969
|
+
@backup_frames = nil
|
970
|
+
thread = Thread.current
|
971
|
+
|
972
|
+
@tp_recorder ||= TracePoint.new(:line){|tp|
|
973
|
+
next unless Thread.current == thread
|
974
|
+
next if tp.path.start_with? __dir__
|
975
|
+
next if tp.path.start_with? '<internal:'
|
976
|
+
loc = caller_locations(1, 1).first
|
977
|
+
next if skip_location?(loc)
|
978
|
+
|
979
|
+
frames = DEBUGGER__.capture_frames(__dir__)
|
980
|
+
frames.each{|frame|
|
981
|
+
if b = frame.binding
|
982
|
+
frame.binding = nil
|
983
|
+
frame._local_variables = b.local_variables.map{|name|
|
984
|
+
[name, b.local_variable_get(name)]
|
985
|
+
}.to_h
|
986
|
+
frame._callee = b.eval('__callee__')
|
987
|
+
end
|
988
|
+
}
|
989
|
+
@log << frames
|
990
|
+
}
|
991
|
+
end
|
602
992
|
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
993
|
+
def enable
|
994
|
+
unless @tp_recorder.enabled?
|
995
|
+
@log.clear
|
996
|
+
@tp_recorder.enable
|
997
|
+
end
|
607
998
|
end
|
608
999
|
|
609
|
-
|
610
|
-
|
1000
|
+
def disable
|
1001
|
+
if @tp_recorder.enabled?
|
1002
|
+
@log.clear
|
1003
|
+
@tp_recorder.disable
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
def enabled?
|
1008
|
+
@tp_recorder.enabled?
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
def step_back
|
1012
|
+
@index += 1
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
def step_forward
|
1016
|
+
@index -= 1
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
def step_reset
|
1020
|
+
@index = 0
|
1021
|
+
@backup_frames = nil
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
def replaying?
|
1025
|
+
@index > 0
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
def can_step_back?
|
1029
|
+
log.size > @index
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
def log_index
|
1033
|
+
@log.size - @index
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
def current_frame
|
1037
|
+
if @index == 0
|
1038
|
+
f = @backup_frames
|
1039
|
+
@backup_frames = nil
|
1040
|
+
f
|
1041
|
+
else
|
1042
|
+
frames = @log[log_index]
|
1043
|
+
frames
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
# for debugging
|
1048
|
+
def current_position
|
1049
|
+
puts "INDEX: #{@index}"
|
1050
|
+
li = log_index
|
1051
|
+
@log.each_with_index{|frame, i|
|
1052
|
+
loc = frame.first&.location
|
1053
|
+
prefix = i == li ? "=> " : ' '
|
1054
|
+
puts "#{prefix} #{loc}"
|
1055
|
+
}
|
1056
|
+
end
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
# copyed from irb
|
1060
|
+
class Output
|
1061
|
+
include Color
|
1062
|
+
|
1063
|
+
MARGIN = " "
|
1064
|
+
|
1065
|
+
def initialize(output)
|
1066
|
+
@output = output
|
1067
|
+
@line_width = screen_width - MARGIN.length # right padding
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
def dump(name, strs)
|
1071
|
+
strs = strs.sort
|
1072
|
+
return if strs.empty?
|
1073
|
+
|
1074
|
+
line = "#{colorize_blue(name)}: "
|
1075
|
+
|
1076
|
+
# Attempt a single line
|
1077
|
+
if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length)
|
1078
|
+
line += strs.join(MARGIN)
|
1079
|
+
@output << line
|
1080
|
+
return
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
# Multi-line
|
1084
|
+
@output << line
|
1085
|
+
|
1086
|
+
# Dump with the largest # of columns that fits on a line
|
1087
|
+
cols = strs.size
|
1088
|
+
until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1
|
1089
|
+
cols -= 1
|
1090
|
+
end
|
1091
|
+
widths = col_widths(strs, cols: cols)
|
1092
|
+
strs.each_slice(cols) do |ss|
|
1093
|
+
@output << ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
private
|
1098
|
+
|
1099
|
+
def fits_on_line?(strs, cols:, offset: 0)
|
1100
|
+
width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1)
|
1101
|
+
width <= @line_width - offset
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
def col_widths(strs, cols:)
|
1105
|
+
cols.times.map do |col|
|
1106
|
+
(col...strs.size).step(cols).map do |i|
|
1107
|
+
strs[i].length
|
1108
|
+
end.max
|
1109
|
+
end
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
def screen_width
|
1113
|
+
SESSION.width
|
1114
|
+
rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN>
|
1115
|
+
80
|
1116
|
+
end
|
611
1117
|
end
|
1118
|
+
private_constant :Output
|
612
1119
|
end
|
613
1120
|
end
|