debug 1.0.0.beta5 → 1.0.0.rc1
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 +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
|