debug 1.6.1 → 1.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +22 -10
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +84 -55
- data/Rakefile +8 -3
- data/TODO.md +8 -8
- data/debug.gemspec +3 -3
- data/exe/rdbg +19 -4
- data/ext/debug/debug.c +33 -5
- data/ext/debug/extconf.rb +1 -0
- data/ext/debug/iseq_collector.c +2 -0
- data/lib/debug/abbrev_command.rb +77 -0
- data/lib/debug/breakpoint.rb +15 -11
- data/lib/debug/client.rb +26 -8
- data/lib/debug/color.rb +0 -0
- data/lib/debug/config.rb +69 -23
- 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/local.rb +16 -10
- data/lib/debug/open.rb +0 -0
- data/lib/debug/open_nonstop.rb +0 -0
- data/lib/debug/prelude.rb +2 -1
- data/lib/debug/server.rb +32 -27
- data/lib/debug/server_cdp.rb +360 -155
- data/lib/debug/server_dap.rb +330 -197
- data/lib/debug/session.rb +494 -258
- data/lib/debug/source_repository.rb +41 -21
- data/lib/debug/start.rb +1 -1
- data/lib/debug/thread_client.rb +241 -82
- data/lib/debug/tracer.rb +4 -5
- data/lib/debug/version.rb +1 -1
- data/lib/debug.rb +7 -2
- data/misc/README.md.erb +50 -44
- metadata +13 -10
data/lib/debug/session.rb
CHANGED
@@ -22,8 +22,9 @@ end
|
|
22
22
|
# restore RUBYOPT
|
23
23
|
if (added_opt = ENV['RUBY_DEBUG_ADDED_RUBYOPT']) &&
|
24
24
|
(rubyopt = ENV['RUBYOPT']) &&
|
25
|
-
rubyopt.
|
26
|
-
|
25
|
+
rubyopt.end_with?(added_opt)
|
26
|
+
|
27
|
+
ENV['RUBYOPT'] = rubyopt.delete_suffix(added_opt)
|
27
28
|
ENV['RUBY_DEBUG_ADDED_RUBYOPT'] = nil
|
28
29
|
end
|
29
30
|
|
@@ -81,14 +82,16 @@ class RubyVM::InstructionSequence
|
|
81
82
|
def first_line
|
82
83
|
self.to_a[4][:code_location][0]
|
83
84
|
end unless method_defined?(:first_line)
|
84
|
-
end
|
85
|
+
end if defined?(RubyVM::InstructionSequence)
|
85
86
|
|
86
87
|
module DEBUGGER__
|
87
|
-
|
88
|
+
PresetCommands = Struct.new(:commands, :source, :auto_continue)
|
89
|
+
SessionCommand = Struct.new(:block, :repeat, :unsafe, :cancel_auto_continue, :postmortem)
|
90
|
+
|
88
91
|
class PostmortemError < RuntimeError; end
|
89
92
|
|
90
93
|
class Session
|
91
|
-
attr_reader :intercepted_sigint_cmd, :process_group
|
94
|
+
attr_reader :intercepted_sigint_cmd, :process_group, :subsession_id
|
92
95
|
|
93
96
|
include Color
|
94
97
|
|
@@ -115,6 +118,7 @@ module DEBUGGER__
|
|
115
118
|
@intercepted_sigint_cmd = 'DEFAULT'
|
116
119
|
@process_group = ProcessGroup.new
|
117
120
|
@subsession_stack = []
|
121
|
+
@subsession_id = 0
|
118
122
|
|
119
123
|
@frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
|
120
124
|
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
@@ -124,33 +128,58 @@ module DEBUGGER__
|
|
124
128
|
@obj_map = {} # { object_id => ... } for CDP
|
125
129
|
|
126
130
|
@tp_thread_begin = nil
|
131
|
+
@tp_thread_end = nil
|
132
|
+
|
133
|
+
@commands = {}
|
134
|
+
@unsafe_context = false
|
135
|
+
|
136
|
+
@has_keep_script_lines = defined?(RubyVM.keep_script_lines)
|
137
|
+
|
127
138
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
128
|
-
|
139
|
+
eval_script = tp.eval_script unless @has_keep_script_lines
|
140
|
+
ThreadClient.current.on_load tp.instruction_sequence, eval_script
|
129
141
|
}
|
130
142
|
@tp_load_script.enable
|
131
143
|
|
132
144
|
@thread_stopper = thread_stopper
|
133
145
|
self.postmortem = CONFIG[:postmortem]
|
146
|
+
|
147
|
+
register_default_command
|
134
148
|
end
|
135
149
|
|
136
150
|
def active?
|
137
151
|
!@q_evt.closed?
|
138
152
|
end
|
139
153
|
|
140
|
-
def
|
141
|
-
@
|
154
|
+
def remote?
|
155
|
+
@ui.remote?
|
156
|
+
end
|
157
|
+
|
158
|
+
def stop_stepping? file, line, subsession_id = nil
|
159
|
+
if @bps.has_key? [file, line]
|
160
|
+
true
|
161
|
+
elsif subsession_id && @subsession_id != subsession_id
|
162
|
+
true
|
163
|
+
else
|
164
|
+
false
|
165
|
+
end
|
142
166
|
end
|
143
167
|
|
144
168
|
def activate ui = nil, on_fork: false
|
145
169
|
@ui = ui if ui
|
146
170
|
|
147
171
|
@tp_thread_begin&.disable
|
172
|
+
@tp_thread_end&.disable
|
148
173
|
@tp_thread_begin = nil
|
149
|
-
|
174
|
+
@tp_thread_end = nil
|
150
175
|
@ui.activate self, on_fork: on_fork
|
151
176
|
|
152
177
|
q = Queue.new
|
178
|
+
first_q = Queue.new
|
153
179
|
@session_server = Thread.new do
|
180
|
+
# make sure `@session_server` is assigned
|
181
|
+
first_q.pop; first_q = nil
|
182
|
+
|
154
183
|
Thread.current.name = 'DEBUGGER__::SESSION@server'
|
155
184
|
Thread.current.abort_on_exception = true
|
156
185
|
|
@@ -168,10 +197,21 @@ module DEBUGGER__
|
|
168
197
|
end
|
169
198
|
@tp_thread_begin.enable
|
170
199
|
|
200
|
+
@tp_thread_end = TracePoint.new(:thread_end) do |tp|
|
201
|
+
@th_clients.delete(Thread.current)
|
202
|
+
end
|
203
|
+
@tp_thread_end.enable
|
204
|
+
|
205
|
+
if CONFIG[:irb_console] && !CONFIG[:open]
|
206
|
+
require_relative "irb_integration"
|
207
|
+
thc.activate_irb_integration
|
208
|
+
end
|
209
|
+
|
171
210
|
# session start
|
172
211
|
q << true
|
173
212
|
session_server_main
|
174
213
|
end
|
214
|
+
first_q << :ok
|
175
215
|
|
176
216
|
q.pop
|
177
217
|
end
|
@@ -181,6 +221,7 @@ module DEBUGGER__
|
|
181
221
|
@thread_stopper.disable
|
182
222
|
@tp_load_script.disable
|
183
223
|
@tp_thread_begin.disable
|
224
|
+
@tp_thread_end.disable
|
184
225
|
@bps.each_value{|bp| bp.disable}
|
185
226
|
@th_clients.each_value{|thc| thc.close}
|
186
227
|
@tracers.values.each{|t| t.disable}
|
@@ -192,6 +233,16 @@ module DEBUGGER__
|
|
192
233
|
def reset_ui ui
|
193
234
|
@ui.deactivate
|
194
235
|
@ui = ui
|
236
|
+
|
237
|
+
# activate new ui
|
238
|
+
@tp_thread_begin.disable
|
239
|
+
@tp_thread_end.disable
|
240
|
+
@ui.activate self
|
241
|
+
if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
|
242
|
+
thc.mark_as_management
|
243
|
+
end
|
244
|
+
@tp_thread_begin.enable
|
245
|
+
@tp_thread_end.enable
|
195
246
|
end
|
196
247
|
|
197
248
|
def pop_event
|
@@ -210,20 +261,35 @@ module DEBUGGER__
|
|
210
261
|
@tc << req
|
211
262
|
end
|
212
263
|
|
264
|
+
def request_tc_with_restarted_threads(req)
|
265
|
+
restart_all_threads
|
266
|
+
request_tc(req)
|
267
|
+
end
|
268
|
+
|
269
|
+
def request_eval type, src
|
270
|
+
request_tc_with_restarted_threads [:eval, type, src]
|
271
|
+
end
|
272
|
+
|
213
273
|
def process_event evt
|
214
274
|
# variable `@internal_info` is only used for test
|
215
|
-
|
275
|
+
tc, output, ev, @internal_info, *ev_args = evt
|
216
276
|
|
217
277
|
output.each{|str| @ui.puts str} if ev != :suspend
|
218
278
|
|
219
|
-
|
220
|
-
|
221
|
-
|
279
|
+
# special event, tc is nil
|
280
|
+
# and we don't want to set @tc to the newly created thread's ThreadClient
|
281
|
+
if ev == :thread_begin
|
222
282
|
th = ev_args.shift
|
223
283
|
q = ev_args.shift
|
224
284
|
on_thread_begin th
|
225
285
|
q << true
|
226
286
|
|
287
|
+
return
|
288
|
+
end
|
289
|
+
|
290
|
+
@tc = tc
|
291
|
+
|
292
|
+
case ev
|
227
293
|
when :init
|
228
294
|
enter_subsession
|
229
295
|
wait_command_loop
|
@@ -241,7 +307,7 @@ module DEBUGGER__
|
|
241
307
|
|
242
308
|
when :suspend
|
243
309
|
enter_subsession if ev_args.first != :replay
|
244
|
-
output.each{|str| @ui.puts str}
|
310
|
+
output.each{|str| @ui.puts str} unless @ui.ignore_output_on_suspend?
|
245
311
|
|
246
312
|
case ev_args.first
|
247
313
|
when :breakpoint
|
@@ -262,7 +328,7 @@ module DEBUGGER__
|
|
262
328
|
if @displays.empty?
|
263
329
|
wait_command_loop
|
264
330
|
else
|
265
|
-
|
331
|
+
request_eval :display, @displays
|
266
332
|
end
|
267
333
|
when :result
|
268
334
|
raise "[BUG] not in subsession" if @subsession_stack.empty?
|
@@ -277,6 +343,7 @@ module DEBUGGER__
|
|
277
343
|
end
|
278
344
|
end
|
279
345
|
|
346
|
+
stop_all_threads
|
280
347
|
when :method_breakpoint, :watch_breakpoint
|
281
348
|
bp = ev_args[1]
|
282
349
|
if bp
|
@@ -290,17 +357,15 @@ module DEBUGGER__
|
|
290
357
|
obj_inspect = ev_args[2]
|
291
358
|
opt = ev_args[3]
|
292
359
|
add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
|
360
|
+
stop_all_threads
|
293
361
|
else
|
294
|
-
|
362
|
+
stop_all_threads
|
295
363
|
end
|
296
364
|
|
297
365
|
wait_command_loop
|
298
366
|
|
299
|
-
when :
|
300
|
-
|
301
|
-
wait_command_loop
|
302
|
-
when :cdp_result
|
303
|
-
cdp_event ev_args
|
367
|
+
when :protocol_result
|
368
|
+
process_protocol_result ev_args
|
304
369
|
wait_command_loop
|
305
370
|
end
|
306
371
|
end
|
@@ -316,7 +381,7 @@ module DEBUGGER__
|
|
316
381
|
if @preset_command && !@preset_command.commands.empty?
|
317
382
|
@preset_command.commands += cs
|
318
383
|
else
|
319
|
-
@preset_command =
|
384
|
+
@preset_command = PresetCommands.new(cs, name, continue)
|
320
385
|
end
|
321
386
|
|
322
387
|
ThreadClient.current.on_init name if kick
|
@@ -389,97 +454,121 @@ module DEBUGGER__
|
|
389
454
|
end
|
390
455
|
end
|
391
456
|
|
392
|
-
def
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
else
|
397
|
-
return :retry
|
398
|
-
end
|
399
|
-
else
|
400
|
-
@repl_prev_line = line
|
401
|
-
end
|
457
|
+
private def register_command *names,
|
458
|
+
repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true,
|
459
|
+
&b
|
460
|
+
cmd = SessionCommand.new(b, repeat, unsafe, cancel_auto_continue, postmortem)
|
402
461
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
462
|
+
names.each{|name|
|
463
|
+
@commands[name] = cmd
|
464
|
+
}
|
465
|
+
end
|
407
466
|
|
408
|
-
|
467
|
+
def register_default_command
|
409
468
|
### Control flow
|
410
469
|
|
411
470
|
# * `s[tep]`
|
412
471
|
# * Step in. Resume the program until next breakable point.
|
413
472
|
# * `s[tep] <n>`
|
414
473
|
# * Step in, resume the program at `<n>`th breakable point.
|
415
|
-
|
416
|
-
|
417
|
-
|
474
|
+
register_command 's', 'step',
|
475
|
+
repeat: true,
|
476
|
+
cancel_auto_continue: true,
|
477
|
+
postmortem: false do |arg|
|
418
478
|
step_command :in, arg
|
479
|
+
end
|
419
480
|
|
420
481
|
# * `n[ext]`
|
421
482
|
# * Step over. Resume the program until next line.
|
422
483
|
# * `n[ext] <n>`
|
423
484
|
# * Step over, same as `step <n>`.
|
424
|
-
|
425
|
-
|
426
|
-
|
485
|
+
register_command 'n', 'next',
|
486
|
+
repeat: true,
|
487
|
+
cancel_auto_continue: true,
|
488
|
+
postmortem: false do |arg|
|
427
489
|
step_command :next, arg
|
490
|
+
end
|
428
491
|
|
429
492
|
# * `fin[ish]`
|
430
493
|
# * Finish this frame. Resume the program until the current frame is finished.
|
431
494
|
# * `fin[ish] <n>`
|
432
495
|
# * Finish `<n>`th frames.
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
496
|
+
register_command 'fin', 'finish',
|
497
|
+
repeat: true,
|
498
|
+
cancel_auto_continue: true,
|
499
|
+
postmortem: false do |arg|
|
437
500
|
if arg&.to_i == 0
|
438
501
|
raise 'finish command with 0 does not make sense.'
|
439
502
|
end
|
440
503
|
|
441
504
|
step_command :finish, arg
|
505
|
+
end
|
442
506
|
|
443
|
-
# * `
|
507
|
+
# * `u[ntil]`
|
508
|
+
# * Similar to `next` command, but only stop later lines or the end of the current frame.
|
509
|
+
# * Similar to gdb's `advance` command.
|
510
|
+
# * `u[ntil] <[file:]line>`
|
511
|
+
# * Run til the program reaches given location or the end of the current frame.
|
512
|
+
# * `u[ntil] <name>`
|
513
|
+
# * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
|
514
|
+
register_command 'u', 'until',
|
515
|
+
repeat: true,
|
516
|
+
cancel_auto_continue: true,
|
517
|
+
postmortem: false do |arg|
|
518
|
+
|
519
|
+
step_command :until, arg
|
520
|
+
end
|
521
|
+
|
522
|
+
# * `c` or `cont` or `continue`
|
444
523
|
# * Resume the program.
|
445
|
-
|
446
|
-
|
524
|
+
register_command 'c', 'cont', 'continue',
|
525
|
+
repeat: true,
|
526
|
+
cancel_auto_continue: true do |arg|
|
447
527
|
leave_subsession :continue
|
528
|
+
end
|
448
529
|
|
449
530
|
# * `q[uit]` or `Ctrl-D`
|
450
531
|
# * Finish debugger (with the debuggee process on non-remote debugging).
|
451
|
-
|
532
|
+
register_command 'q', 'quit' do |arg|
|
452
533
|
if ask 'Really quit?'
|
453
|
-
@ui.quit arg.to_i
|
534
|
+
@ui.quit arg.to_i do
|
535
|
+
request_tc :quit
|
536
|
+
end
|
454
537
|
leave_subsession :continue
|
455
538
|
else
|
456
|
-
|
539
|
+
next :retry
|
457
540
|
end
|
541
|
+
end
|
458
542
|
|
459
543
|
# * `q[uit]!`
|
460
544
|
# * Same as q[uit] but without the confirmation prompt.
|
461
|
-
|
462
|
-
@ui.quit arg.to_i
|
463
|
-
|
545
|
+
register_command 'q!', 'quit!', unsafe: false do |arg|
|
546
|
+
@ui.quit arg.to_i do
|
547
|
+
request_tc :quit
|
548
|
+
end
|
549
|
+
leave_subsession :continue
|
550
|
+
end
|
464
551
|
|
465
552
|
# * `kill`
|
466
553
|
# * Stop the debuggee process with `Kernel#exit!`.
|
467
|
-
|
554
|
+
register_command 'kill' do |arg|
|
468
555
|
if ask 'Really kill?'
|
469
556
|
exit! (arg || 1).to_i
|
470
557
|
else
|
471
|
-
|
558
|
+
next :retry
|
472
559
|
end
|
560
|
+
end
|
473
561
|
|
474
562
|
# * `kill!`
|
475
563
|
# * Same as kill but without the confirmation prompt.
|
476
|
-
|
564
|
+
register_command 'kill!', unsafe: false do |arg|
|
477
565
|
exit! (arg || 1).to_i
|
566
|
+
end
|
478
567
|
|
479
568
|
# * `sigint`
|
480
569
|
# * Execute SIGINT handler registered by the debuggee.
|
481
570
|
# * Note that this command should be used just after stop by `SIGINT`.
|
482
|
-
|
571
|
+
register_command 'sigint' do
|
483
572
|
begin
|
484
573
|
case cmd = @intercepted_sigint_cmd
|
485
574
|
when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
|
@@ -495,8 +584,9 @@ module DEBUGGER__
|
|
495
584
|
rescue Exception => e
|
496
585
|
@ui.puts "Exception: #{e}"
|
497
586
|
@ui.puts e.backtrace.map{|line| " #{e}"}
|
498
|
-
|
587
|
+
next :retry
|
499
588
|
end
|
589
|
+
end
|
500
590
|
|
501
591
|
### Breakpoint
|
502
592
|
|
@@ -521,22 +611,21 @@ module DEBUGGER__
|
|
521
611
|
# * `b[reak] if: <expr>`
|
522
612
|
# * break if: `<expr>` is true at any lines.
|
523
613
|
# * Note that this feature is super slow.
|
524
|
-
|
525
|
-
check_postmortem
|
526
|
-
|
614
|
+
register_command 'b', 'break', postmortem: false, unsafe: false do |arg|
|
527
615
|
if arg == nil
|
528
616
|
show_bps
|
529
|
-
|
617
|
+
next :retry
|
530
618
|
else
|
531
619
|
case bp = repl_add_breakpoint(arg)
|
532
620
|
when :noretry
|
533
621
|
when nil
|
534
|
-
|
622
|
+
next :retry
|
535
623
|
else
|
536
624
|
show_bps bp
|
537
|
-
|
625
|
+
next :retry
|
538
626
|
end
|
539
627
|
end
|
628
|
+
end
|
540
629
|
|
541
630
|
# * `catch <Error>`
|
542
631
|
# * Set breakpoint on raising `<Error>`.
|
@@ -548,16 +637,16 @@ module DEBUGGER__
|
|
548
637
|
# * stops and run `<command>`, and continue.
|
549
638
|
# * `catch ... path: <path>`
|
550
639
|
# * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
|
551
|
-
|
552
|
-
check_postmortem
|
553
|
-
|
640
|
+
register_command 'catch', postmortem: false, unsafe: false do |arg|
|
554
641
|
if arg
|
555
642
|
bp = repl_add_catch_breakpoint arg
|
556
643
|
show_bps bp if bp
|
557
644
|
else
|
558
645
|
show_bps
|
559
646
|
end
|
560
|
-
|
647
|
+
|
648
|
+
:retry
|
649
|
+
end
|
561
650
|
|
562
651
|
# * `watch @ivar`
|
563
652
|
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
@@ -570,24 +659,20 @@ module DEBUGGER__
|
|
570
659
|
# * stops and run `<command>`, and continue.
|
571
660
|
# * `watch ... path: <path>`
|
572
661
|
# * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
|
573
|
-
|
574
|
-
check_postmortem
|
575
|
-
|
662
|
+
register_command 'wat', 'watch', postmortem: false, unsafe: false do |arg|
|
576
663
|
if arg && arg.match?(/\A@\w+/)
|
577
664
|
repl_add_watch_breakpoint(arg)
|
578
665
|
else
|
579
666
|
show_bps
|
580
|
-
|
667
|
+
:retry
|
581
668
|
end
|
669
|
+
end
|
582
670
|
|
583
671
|
# * `del[ete]`
|
584
672
|
# * delete all breakpoints.
|
585
673
|
# * `del[ete] <bpnum>`
|
586
674
|
# * delete specified breakpoint.
|
587
|
-
|
588
|
-
check_postmortem
|
589
|
-
|
590
|
-
bp =
|
675
|
+
register_command 'del', 'delete', postmortem: false, unsafe: false do |arg|
|
591
676
|
case arg
|
592
677
|
when nil
|
593
678
|
show_bps
|
@@ -595,12 +680,13 @@ module DEBUGGER__
|
|
595
680
|
delete_bp
|
596
681
|
end
|
597
682
|
when /\d+/
|
598
|
-
delete_bp arg.to_i
|
683
|
+
bp = delete_bp arg.to_i
|
599
684
|
else
|
600
685
|
nil
|
601
686
|
end
|
602
687
|
@ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
|
603
|
-
|
688
|
+
:retry
|
689
|
+
end
|
604
690
|
|
605
691
|
### Information
|
606
692
|
|
@@ -612,19 +698,20 @@ module DEBUGGER__
|
|
612
698
|
# * Only shows frames with method name or location info that matches `/regexp/`.
|
613
699
|
# * `bt <num> /regexp/` or `backtrace <num> /regexp/`
|
614
700
|
# * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
|
615
|
-
|
701
|
+
register_command 'bt', 'backtrace', unsafe: false do |arg|
|
616
702
|
case arg
|
617
703
|
when /\A(\d+)\z/
|
618
|
-
|
704
|
+
request_tc_with_restarted_threads [:show, :backtrace, arg.to_i, nil]
|
619
705
|
when /\A\/(.*)\/\z/
|
620
706
|
pattern = $1
|
621
|
-
|
707
|
+
request_tc_with_restarted_threads [:show, :backtrace, nil, Regexp.compile(pattern)]
|
622
708
|
when /\A(\d+)\s+\/(.*)\/\z/
|
623
709
|
max, pattern = $1, $2
|
624
|
-
|
710
|
+
request_tc_with_restarted_threads [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
|
625
711
|
else
|
626
|
-
|
712
|
+
request_tc_with_restarted_threads [:show, :backtrace, nil, nil]
|
627
713
|
end
|
714
|
+
end
|
628
715
|
|
629
716
|
# * `l[ist]`
|
630
717
|
# * Show current frame's source code.
|
@@ -633,7 +720,7 @@ module DEBUGGER__
|
|
633
720
|
# * Show predecessor lines as opposed to the `list` command.
|
634
721
|
# * `l[ist] <start>` or `l[ist] <start>-<end>`
|
635
722
|
# * Show current frame's source code from the line <start> to <end> if given.
|
636
|
-
|
723
|
+
register_command 'l', 'list', repeat: true, unsafe: false do |arg|
|
637
724
|
case arg ? arg.strip : nil
|
638
725
|
when /\A(\d+)\z/
|
639
726
|
request_tc [:show, :list, {start_line: arg.to_i - 1}]
|
@@ -645,45 +732,63 @@ module DEBUGGER__
|
|
645
732
|
request_tc [:show, :list]
|
646
733
|
else
|
647
734
|
@ui.puts "Can not handle list argument: #{arg}"
|
648
|
-
|
735
|
+
:retry
|
649
736
|
end
|
737
|
+
end
|
738
|
+
|
739
|
+
# * `whereami`
|
740
|
+
# * Show the current frame with source code.
|
741
|
+
register_command 'whereami', unsafe: false do
|
742
|
+
request_tc [:show, :whereami]
|
743
|
+
end
|
650
744
|
|
651
745
|
# * `edit`
|
652
746
|
# * Open the current file on the editor (use `EDITOR` environment variable).
|
653
747
|
# * Note that edited file will not be reloaded.
|
654
748
|
# * `edit <file>`
|
655
749
|
# * Open <file> on the editor.
|
656
|
-
|
750
|
+
register_command 'edit' do |arg|
|
657
751
|
if @ui.remote?
|
658
752
|
@ui.puts "not supported on the remote console."
|
659
|
-
|
753
|
+
next :retry
|
660
754
|
end
|
661
755
|
|
662
756
|
begin
|
663
757
|
arg = resolve_path(arg) if arg
|
664
758
|
rescue Errno::ENOENT
|
665
759
|
@ui.puts "not found: #{arg}"
|
666
|
-
|
760
|
+
next :retry
|
667
761
|
end
|
668
762
|
|
669
763
|
request_tc [:show, :edit, arg]
|
764
|
+
end
|
765
|
+
|
766
|
+
info_subcommands = nil
|
767
|
+
info_subcommands_abbrev = nil
|
670
768
|
|
671
769
|
# * `i[nfo]`
|
672
|
-
#
|
673
|
-
# * `i[nfo]
|
770
|
+
# * Show information about current frame (local/instance variables and defined constants).
|
771
|
+
# * `i[nfo]` <subcommand>
|
772
|
+
# * `info` has the following sub-commands.
|
773
|
+
# * Sub-commands can be specified with few letters which is unambiguous, like `l` for 'locals'.
|
774
|
+
# * `i[nfo] l or locals or local_variables`
|
674
775
|
# * Show information about the current frame (local variables)
|
675
|
-
# * It includes `self` as `%self` and a return value as
|
676
|
-
# * `i[nfo] i
|
776
|
+
# * It includes `self` as `%self` and a return value as `_return`.
|
777
|
+
# * `i[nfo] i or ivars or instance_variables`
|
677
778
|
# * Show information about instance variables about `self`.
|
678
|
-
#
|
779
|
+
# * `info ivars <expr>` shows the instance variables of the result of `<expr>`.
|
780
|
+
# * `i[nfo] c or consts or constants`
|
679
781
|
# * Show information about accessible constants except toplevel constants.
|
680
|
-
#
|
782
|
+
# * `info consts <expr>` shows the constants of a class/module of the result of `<expr>`
|
783
|
+
# * `i[nfo] g or globals or global_variables`
|
681
784
|
# * Show information about global variables
|
785
|
+
# * `i[nfo] th or threads`
|
786
|
+
# * Show all threads (same as `th[read]`).
|
787
|
+
# * `i[nfo] b or breakpoints or w or watchpoints`
|
788
|
+
# * Show all breakpoints and watchpoints.
|
682
789
|
# * `i[nfo] ... /regexp/`
|
683
790
|
# * Filter the output with `/regexp/`.
|
684
|
-
|
685
|
-
# * Show all threads (same as `th[read]`).
|
686
|
-
when 'i', 'info'
|
791
|
+
register_command 'i', 'info', unsafe: false do |arg|
|
687
792
|
if /\/(.+)\/\z/ =~ arg
|
688
793
|
pat = Regexp.compile($1)
|
689
794
|
sub = $~.pre_match.strip
|
@@ -691,63 +796,98 @@ module DEBUGGER__
|
|
691
796
|
sub = arg
|
692
797
|
end
|
693
798
|
|
799
|
+
if /\A(.+?)\b(.+)/ =~ sub
|
800
|
+
sub = $1
|
801
|
+
opt = $2.strip
|
802
|
+
opt = nil if opt.empty?
|
803
|
+
end
|
804
|
+
|
805
|
+
if sub && !info_subcommands
|
806
|
+
info_subcommands = {
|
807
|
+
locals: %w[ locals local_variables ],
|
808
|
+
ivars: %w[ ivars instance_variables ],
|
809
|
+
consts: %w[ consts constants ],
|
810
|
+
globals:%w[ globals global_variables ],
|
811
|
+
threads:%w[ threads ],
|
812
|
+
breaks: %w[ breakpoints ],
|
813
|
+
watchs: %w[ watchpoints ],
|
814
|
+
}
|
815
|
+
|
816
|
+
require_relative 'abbrev_command'
|
817
|
+
info_subcommands_abbrev = AbbrevCommand.new(info_subcommands)
|
818
|
+
end
|
819
|
+
|
820
|
+
if sub
|
821
|
+
sub = info_subcommands_abbrev.search sub, :unknown do |candidates|
|
822
|
+
# note: unreached now
|
823
|
+
@ui.puts "Ambiguous command '#{sub}': #{candidates.join(' ')}"
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
694
827
|
case sub
|
695
828
|
when nil
|
696
|
-
|
697
|
-
when
|
698
|
-
|
699
|
-
when
|
700
|
-
|
701
|
-
when
|
702
|
-
|
703
|
-
when
|
704
|
-
|
705
|
-
when
|
829
|
+
request_tc_with_restarted_threads [:show, :default, pat] # something useful
|
830
|
+
when :locals
|
831
|
+
request_tc_with_restarted_threads [:show, :locals, pat]
|
832
|
+
when :ivars
|
833
|
+
request_tc_with_restarted_threads [:show, :ivars, pat, opt]
|
834
|
+
when :consts
|
835
|
+
request_tc_with_restarted_threads [:show, :consts, pat, opt]
|
836
|
+
when :globals
|
837
|
+
request_tc_with_restarted_threads [:show, :globals, pat]
|
838
|
+
when :threads
|
706
839
|
thread_list
|
707
|
-
|
840
|
+
:retry
|
841
|
+
when :breaks, :watchs
|
842
|
+
show_bps
|
843
|
+
:retry
|
708
844
|
else
|
709
845
|
@ui.puts "unrecognized argument for info command: #{arg}"
|
710
846
|
show_help 'info'
|
711
|
-
|
847
|
+
:retry
|
712
848
|
end
|
849
|
+
end
|
713
850
|
|
714
851
|
# * `o[utline]` or `ls`
|
715
852
|
# * Show you available methods, constants, local variables, and instance variables in the current scope.
|
716
853
|
# * `o[utline] <expr>` or `ls <expr>`
|
717
854
|
# * Show you available methods and instance variables of the given object.
|
718
855
|
# * If the object is a class/module, it also lists its constants.
|
719
|
-
|
720
|
-
|
856
|
+
register_command 'outline', 'o', 'ls', unsafe: false do |arg|
|
857
|
+
request_tc_with_restarted_threads [:show, :outline, arg]
|
858
|
+
end
|
721
859
|
|
722
860
|
# * `display`
|
723
861
|
# * Show display setting.
|
724
862
|
# * `display <expr>`
|
725
863
|
# * Show the result of `<expr>` at every suspended timing.
|
726
|
-
|
864
|
+
register_command 'display', postmortem: false do |arg|
|
727
865
|
if arg && !arg.empty?
|
728
866
|
@displays << arg
|
729
|
-
|
867
|
+
request_eval :try_display, @displays
|
730
868
|
else
|
731
|
-
|
869
|
+
request_eval :display, @displays
|
732
870
|
end
|
871
|
+
end
|
733
872
|
|
734
873
|
# * `undisplay`
|
735
874
|
# * Remove all display settings.
|
736
875
|
# * `undisplay <displaynum>`
|
737
876
|
# * Remove a specified display setting.
|
738
|
-
|
877
|
+
register_command 'undisplay', postmortem: false, unsafe: false do |arg|
|
739
878
|
case arg
|
740
879
|
when /(\d+)/
|
741
880
|
if @displays[n = $1.to_i]
|
742
881
|
@displays.delete_at n
|
743
882
|
end
|
744
|
-
|
883
|
+
request_eval :display, @displays
|
745
884
|
when nil
|
746
885
|
if ask "clear all?", 'N'
|
747
886
|
@displays.clear
|
748
887
|
end
|
749
|
-
|
888
|
+
:retry
|
750
889
|
end
|
890
|
+
end
|
751
891
|
|
752
892
|
### Frame control
|
753
893
|
|
@@ -755,53 +895,58 @@ module DEBUGGER__
|
|
755
895
|
# * Show the current frame.
|
756
896
|
# * `f[rame] <framenum>`
|
757
897
|
# * Specify a current frame. Evaluation are run on specified frame.
|
758
|
-
|
898
|
+
register_command 'frame', 'f', unsafe: false do |arg|
|
759
899
|
request_tc [:frame, :set, arg]
|
900
|
+
end
|
760
901
|
|
761
902
|
# * `up`
|
762
903
|
# * Specify the upper frame.
|
763
|
-
|
904
|
+
register_command 'up', repeat: true, unsafe: false do |arg|
|
764
905
|
request_tc [:frame, :up]
|
906
|
+
end
|
765
907
|
|
766
908
|
# * `down`
|
767
909
|
# * Specify the lower frame.
|
768
|
-
|
910
|
+
register_command 'down', repeat: true, unsafe: false do |arg|
|
769
911
|
request_tc [:frame, :down]
|
912
|
+
end
|
770
913
|
|
771
914
|
### Evaluate
|
772
915
|
|
773
916
|
# * `p <expr>`
|
774
917
|
# * Evaluate like `p <expr>` on the current frame.
|
775
|
-
|
776
|
-
|
918
|
+
register_command 'p' do |arg|
|
919
|
+
request_eval :p, arg.to_s
|
920
|
+
end
|
777
921
|
|
778
922
|
# * `pp <expr>`
|
779
923
|
# * Evaluate like `pp <expr>` on the current frame.
|
780
|
-
|
781
|
-
|
924
|
+
register_command 'pp' do |arg|
|
925
|
+
request_eval :pp, arg.to_s
|
926
|
+
end
|
782
927
|
|
783
928
|
# * `eval <expr>`
|
784
929
|
# * Evaluate `<expr>` on the current frame.
|
785
|
-
|
930
|
+
register_command 'eval', 'call' do |arg|
|
786
931
|
if arg == nil || arg.empty?
|
787
932
|
show_help 'eval'
|
788
933
|
@ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
|
789
|
-
|
934
|
+
:retry
|
790
935
|
else
|
791
|
-
|
936
|
+
request_eval :call, arg
|
792
937
|
end
|
938
|
+
end
|
793
939
|
|
794
940
|
# * `irb`
|
795
941
|
# * Invoke `irb` on the current frame.
|
796
|
-
|
942
|
+
register_command 'irb' do |arg|
|
797
943
|
if @ui.remote?
|
798
|
-
@ui.puts "not supported on the remote console."
|
799
|
-
|
944
|
+
@ui.puts "\nIRB is not supported on the remote console."
|
945
|
+
:retry
|
946
|
+
else
|
947
|
+
request_eval :irb, nil
|
800
948
|
end
|
801
|
-
|
802
|
-
|
803
|
-
# don't repeat irb command
|
804
|
-
@repl_prev_line = nil
|
949
|
+
end
|
805
950
|
|
806
951
|
### Trace
|
807
952
|
# * `trace`
|
@@ -822,7 +967,7 @@ module DEBUGGER__
|
|
822
967
|
# * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
|
823
968
|
# * `trace off [line|call|pass]`
|
824
969
|
# * Disable all tracers. If `<type>` is provided, disable specified type tracers.
|
825
|
-
|
970
|
+
register_command 'trace', postmortem: false, unsafe: false do |arg|
|
826
971
|
if (re = /\s+into:\s*(.+)/) =~ arg
|
827
972
|
into = $1
|
828
973
|
arg.sub!(re, '')
|
@@ -840,22 +985,22 @@ module DEBUGGER__
|
|
840
985
|
@ui.puts "* \##{i} #{t}"
|
841
986
|
}
|
842
987
|
@ui.puts
|
843
|
-
|
988
|
+
:retry
|
844
989
|
|
845
990
|
when /\Aline\z/
|
846
991
|
add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
|
847
|
-
|
992
|
+
:retry
|
848
993
|
|
849
994
|
when /\Acall\z/
|
850
995
|
add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
|
851
|
-
|
996
|
+
:retry
|
852
997
|
|
853
998
|
when /\Aexception\z/
|
854
999
|
add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
|
855
|
-
|
1000
|
+
:retry
|
856
1001
|
|
857
1002
|
when /\Aobject\s+(.+)/
|
858
|
-
|
1003
|
+
request_tc_with_restarted_threads [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
859
1004
|
|
860
1005
|
when /\Aoff\s+(\d+)\z/
|
861
1006
|
if t = @tracers.values[$1.to_i]
|
@@ -864,7 +1009,7 @@ module DEBUGGER__
|
|
864
1009
|
else
|
865
1010
|
@ui.puts "Unmatched: #{$1}"
|
866
1011
|
end
|
867
|
-
|
1012
|
+
:retry
|
868
1013
|
|
869
1014
|
when /\Aoff(\s+(line|call|exception|object))?\z/
|
870
1015
|
@tracers.values.each{|t|
|
@@ -873,12 +1018,13 @@ module DEBUGGER__
|
|
873
1018
|
@ui.puts "Disable #{t.to_s}"
|
874
1019
|
end
|
875
1020
|
}
|
876
|
-
|
1021
|
+
:retry
|
877
1022
|
|
878
1023
|
else
|
879
1024
|
@ui.puts "Unknown trace option: #{arg.inspect}"
|
880
|
-
|
1025
|
+
:retry
|
881
1026
|
end
|
1027
|
+
end
|
882
1028
|
|
883
1029
|
# Record
|
884
1030
|
# * `record`
|
@@ -890,14 +1036,15 @@ module DEBUGGER__
|
|
890
1036
|
# * `s[tep]` does stepping forward with the last log.
|
891
1037
|
# * `step reset`
|
892
1038
|
# * Stop replay .
|
893
|
-
|
1039
|
+
register_command 'record', postmortem: false, unsafe: false do |arg|
|
894
1040
|
case arg
|
895
1041
|
when nil, 'on', 'off'
|
896
1042
|
request_tc [:record, arg&.to_sym]
|
897
1043
|
else
|
898
1044
|
@ui.puts "unknown command: #{arg}"
|
899
|
-
|
1045
|
+
:retry
|
900
1046
|
end
|
1047
|
+
end
|
901
1048
|
|
902
1049
|
### Thread control
|
903
1050
|
|
@@ -905,7 +1052,7 @@ module DEBUGGER__
|
|
905
1052
|
# * Show all threads.
|
906
1053
|
# * `th[read] <thnum>`
|
907
1054
|
# * Switch thread specified by `<thnum>`.
|
908
|
-
|
1055
|
+
register_command 'th', 'thread', unsafe: false do |arg|
|
909
1056
|
case arg
|
910
1057
|
when nil, 'list', 'l'
|
911
1058
|
thread_list
|
@@ -914,7 +1061,8 @@ module DEBUGGER__
|
|
914
1061
|
else
|
915
1062
|
@ui.puts "unknown thread command: #{arg}"
|
916
1063
|
end
|
917
|
-
|
1064
|
+
:retry
|
1065
|
+
end
|
918
1066
|
|
919
1067
|
### Configuration
|
920
1068
|
# * `config`
|
@@ -927,13 +1075,14 @@ module DEBUGGER__
|
|
927
1075
|
# * Append `<val>` to `<name>` if it is an array.
|
928
1076
|
# * `config unset <name>`
|
929
1077
|
# * Set <name> to default.
|
930
|
-
|
1078
|
+
register_command 'config', unsafe: false do |arg|
|
931
1079
|
config_command arg
|
932
|
-
|
1080
|
+
:retry
|
1081
|
+
end
|
933
1082
|
|
934
1083
|
# * `source <file>`
|
935
1084
|
# * Evaluate lines in `<file>` as debug commands.
|
936
|
-
|
1085
|
+
register_command 'source' do |arg|
|
937
1086
|
if arg
|
938
1087
|
begin
|
939
1088
|
cmds = File.readlines(path = File.expand_path(arg))
|
@@ -944,7 +1093,8 @@ module DEBUGGER__
|
|
944
1093
|
else
|
945
1094
|
show_help 'source'
|
946
1095
|
end
|
947
|
-
|
1096
|
+
:retry
|
1097
|
+
end
|
948
1098
|
|
949
1099
|
# * `open`
|
950
1100
|
# * open debuggee port on UNIX domain socket and wait for attaching.
|
@@ -955,26 +1105,28 @@ module DEBUGGER__
|
|
955
1105
|
# * open debuggee port for VSCode and launch VSCode if available.
|
956
1106
|
# * `open chrome`
|
957
1107
|
# * open debuggee port for Chrome and wait for attaching.
|
958
|
-
|
1108
|
+
register_command 'open' do |arg|
|
959
1109
|
case arg&.downcase
|
960
1110
|
when '', nil
|
961
|
-
|
962
|
-
when 'vscode'
|
963
|
-
repl_open_vscode
|
964
|
-
when /\A(.+):(\d+)\z/
|
965
|
-
repl_open_tcp $1, $2.to_i
|
1111
|
+
::DEBUGGER__.open nonstop: true
|
966
1112
|
when /\A(\d+)z/
|
967
|
-
|
1113
|
+
::DEBUGGER__.open_tcp host: nil, port: $1.to_i, nonstop: true
|
1114
|
+
when /\A(.+):(\d+)\z/
|
1115
|
+
::DEBUGGER__.open_tcp host: $1, port: $2.to_i, nonstop: true
|
968
1116
|
when 'tcp'
|
969
|
-
|
1117
|
+
::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
|
1118
|
+
when 'vscode'
|
1119
|
+
CONFIG[:open] = 'vscode'
|
1120
|
+
::DEBUGGER__.open nonstop: true
|
970
1121
|
when 'chrome', 'cdp'
|
971
|
-
CONFIG[:
|
972
|
-
|
1122
|
+
CONFIG[:open] = 'chrome'
|
1123
|
+
::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
|
973
1124
|
else
|
974
1125
|
raise "Unknown arg: #{arg}"
|
975
1126
|
end
|
976
1127
|
|
977
|
-
|
1128
|
+
:retry
|
1129
|
+
end
|
978
1130
|
|
979
1131
|
### Help
|
980
1132
|
|
@@ -982,26 +1134,38 @@ module DEBUGGER__
|
|
982
1134
|
# * Show help for all commands.
|
983
1135
|
# * `h[elp] <command>`
|
984
1136
|
# * Show help for the given command.
|
985
|
-
|
1137
|
+
register_command 'h', 'help', '?', unsafe: false do |arg|
|
986
1138
|
show_help arg
|
987
|
-
|
1139
|
+
:retry
|
1140
|
+
end
|
1141
|
+
end
|
988
1142
|
|
989
|
-
|
1143
|
+
def process_command line
|
1144
|
+
if line.empty?
|
1145
|
+
if @repl_prev_line
|
1146
|
+
line = @repl_prev_line
|
1147
|
+
else
|
1148
|
+
return :retry
|
1149
|
+
end
|
1150
|
+
else
|
1151
|
+
@repl_prev_line = line
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
/([^\s]+)(?:\s+(.+))?/ =~ line
|
1155
|
+
cmd_name, cmd_arg = $1, $2
|
1156
|
+
|
1157
|
+
if cmd = @commands[cmd_name]
|
1158
|
+
check_postmortem if !cmd.postmortem
|
1159
|
+
check_unsafe if cmd.unsafe
|
1160
|
+
cancel_auto_continue if cmd.cancel_auto_continue
|
1161
|
+
@repl_prev_line = nil if !cmd.repeat
|
1162
|
+
|
1163
|
+
cmd.block.call(cmd_arg)
|
990
1164
|
else
|
991
|
-
request_tc [:eval, :pp, line]
|
992
|
-
=begin
|
993
1165
|
@repl_prev_line = nil
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
spell_checker = DidYouMean::SpellChecker.new(dictionary: DEBUGGER__.commands)
|
998
|
-
correction = spell_checker.correct(line.split(/\s/).first || '')
|
999
|
-
@ui.puts "Did you mean? #{correction.join(' or ')}" unless correction.empty?
|
1000
|
-
rescue LoadError
|
1001
|
-
# Don't use D
|
1002
|
-
end
|
1003
|
-
return :retry
|
1004
|
-
=end
|
1166
|
+
check_unsafe
|
1167
|
+
|
1168
|
+
request_eval :pp, line
|
1005
1169
|
end
|
1006
1170
|
|
1007
1171
|
rescue Interrupt
|
@@ -1017,31 +1181,12 @@ module DEBUGGER__
|
|
1017
1181
|
return :retry
|
1018
1182
|
end
|
1019
1183
|
|
1020
|
-
def
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
thc.mark_as_management
|
1184
|
+
def step_command type, arg
|
1185
|
+
if type == :until
|
1186
|
+
leave_subsession [:step, type, arg]
|
1187
|
+
return
|
1025
1188
|
end
|
1026
|
-
@tp_thread_begin.enable
|
1027
|
-
end
|
1028
|
-
|
1029
|
-
def repl_open_tcp host, port, **kw
|
1030
|
-
DEBUGGER__.open_tcp host: host, port: port, nonstop: true, **kw
|
1031
|
-
repl_open_setup
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
def repl_open
|
1035
|
-
DEBUGGER__.open nonstop: true
|
1036
|
-
repl_open_setup
|
1037
|
-
end
|
1038
|
-
|
1039
|
-
def repl_open_vscode
|
1040
|
-
CONFIG[:open_frontend] = 'vscode'
|
1041
|
-
repl_open
|
1042
|
-
end
|
1043
1189
|
|
1044
|
-
def step_command type, arg
|
1045
1190
|
case arg
|
1046
1191
|
when nil, /\A\d+\z/
|
1047
1192
|
if type == :in && @tc.recorder&.replaying?
|
@@ -1049,12 +1194,14 @@ module DEBUGGER__
|
|
1049
1194
|
else
|
1050
1195
|
leave_subsession [:step, type, arg&.to_i]
|
1051
1196
|
end
|
1052
|
-
when /\
|
1197
|
+
when /\A(back)\z/, /\A(back)\s+(\d+)\z/, /\A(reset)\z/
|
1053
1198
|
if type != :in
|
1054
1199
|
@ui.puts "only `step #{arg}` is supported."
|
1055
1200
|
:retry
|
1056
1201
|
else
|
1057
|
-
|
1202
|
+
type = $1.to_sym
|
1203
|
+
iter = $2&.to_i
|
1204
|
+
request_tc [:step, type, iter]
|
1058
1205
|
end
|
1059
1206
|
else
|
1060
1207
|
@ui.puts "Unknown option: #{arg}"
|
@@ -1248,8 +1395,6 @@ module DEBUGGER__
|
|
1248
1395
|
|
1249
1396
|
def add_bp bp
|
1250
1397
|
# don't repeat commands that add breakpoints
|
1251
|
-
@repl_prev_line = nil
|
1252
|
-
|
1253
1398
|
if @bps.has_key? bp.key
|
1254
1399
|
if bp.duplicable?
|
1255
1400
|
bp
|
@@ -1281,7 +1426,7 @@ module DEBUGGER__
|
|
1281
1426
|
|
1282
1427
|
BREAK_KEYWORDS = %w(if: do: pre: path:).freeze
|
1283
1428
|
|
1284
|
-
def parse_break arg
|
1429
|
+
private def parse_break type, arg
|
1285
1430
|
mode = :sig
|
1286
1431
|
expr = Hash.new{|h, k| h[k] = []}
|
1287
1432
|
arg.split(' ').each{|w|
|
@@ -1298,13 +1443,18 @@ module DEBUGGER__
|
|
1298
1443
|
expr[:path] = Regexp.compile($1)
|
1299
1444
|
end
|
1300
1445
|
|
1446
|
+
if expr[:do] || expr[:pre]
|
1447
|
+
check_unsafe
|
1448
|
+
expr[:cmd] = [type, expr[:pre], expr[:do]]
|
1449
|
+
end
|
1450
|
+
|
1301
1451
|
expr
|
1302
1452
|
end
|
1303
1453
|
|
1304
1454
|
def repl_add_breakpoint arg
|
1305
|
-
expr = parse_break arg.strip
|
1455
|
+
expr = parse_break 'break', arg.strip
|
1306
1456
|
cond = expr[:if]
|
1307
|
-
cmd
|
1457
|
+
cmd = expr[:cmd]
|
1308
1458
|
path = expr[:path]
|
1309
1459
|
|
1310
1460
|
case expr[:sig]
|
@@ -1325,9 +1475,9 @@ module DEBUGGER__
|
|
1325
1475
|
end
|
1326
1476
|
|
1327
1477
|
def repl_add_catch_breakpoint arg
|
1328
|
-
expr = parse_break arg.strip
|
1478
|
+
expr = parse_break 'catch', arg.strip
|
1329
1479
|
cond = expr[:if]
|
1330
|
-
cmd
|
1480
|
+
cmd = expr[:cmd]
|
1331
1481
|
path = expr[:path]
|
1332
1482
|
|
1333
1483
|
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
|
@@ -1335,9 +1485,9 @@ module DEBUGGER__
|
|
1335
1485
|
end
|
1336
1486
|
|
1337
1487
|
def repl_add_watch_breakpoint arg
|
1338
|
-
expr = parse_break arg.strip
|
1488
|
+
expr = parse_break 'watch', arg.strip
|
1339
1489
|
cond = expr[:if]
|
1340
|
-
cmd
|
1490
|
+
cmd = expr[:cmd]
|
1341
1491
|
path = Regexp.compile(expr[:path]) if expr[:path]
|
1342
1492
|
|
1343
1493
|
request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
|
@@ -1374,7 +1524,7 @@ module DEBUGGER__
|
|
1374
1524
|
def clear_line_breakpoints path
|
1375
1525
|
path = resolve_path(path)
|
1376
1526
|
clear_breakpoints do |k, bp|
|
1377
|
-
bp.is_a?(LineBreakpoint) &&
|
1527
|
+
bp.is_a?(LineBreakpoint) && bp.path_is?(path)
|
1378
1528
|
end
|
1379
1529
|
rescue Errno::ENOENT
|
1380
1530
|
# just ignore
|
@@ -1398,9 +1548,7 @@ module DEBUGGER__
|
|
1398
1548
|
# tracers
|
1399
1549
|
|
1400
1550
|
def add_tracer tracer
|
1401
|
-
|
1402
|
-
@repl_prev_line = nil
|
1403
|
-
if @tracers.has_key? tracer.key
|
1551
|
+
if @tracers[tracer.key]&.enabled?
|
1404
1552
|
tracer.disable
|
1405
1553
|
@ui.puts "Duplicated tracer: #{tracer}"
|
1406
1554
|
else
|
@@ -1557,10 +1705,11 @@ module DEBUGGER__
|
|
1557
1705
|
end
|
1558
1706
|
|
1559
1707
|
private def enter_subsession
|
1708
|
+
@subsession_id += 1
|
1560
1709
|
if !@subsession_stack.empty?
|
1561
|
-
DEBUGGER__.
|
1710
|
+
DEBUGGER__.debug{ "Enter subsession (nested #{@subsession_stack.size})" }
|
1562
1711
|
else
|
1563
|
-
DEBUGGER__.
|
1712
|
+
DEBUGGER__.debug{ "Enter subsession" }
|
1564
1713
|
stop_all_threads
|
1565
1714
|
@process_group.lock
|
1566
1715
|
end
|
@@ -1573,11 +1722,11 @@ module DEBUGGER__
|
|
1573
1722
|
@subsession_stack.pop
|
1574
1723
|
|
1575
1724
|
if @subsession_stack.empty?
|
1576
|
-
DEBUGGER__.
|
1725
|
+
DEBUGGER__.debug{ "Leave subsession" }
|
1577
1726
|
@process_group.unlock
|
1578
1727
|
restart_all_threads
|
1579
1728
|
else
|
1580
|
-
DEBUGGER__.
|
1729
|
+
DEBUGGER__.debug{ "Leave subsession (nested #{@subsession_stack.size})" }
|
1581
1730
|
end
|
1582
1731
|
|
1583
1732
|
request_tc type if type
|
@@ -1599,25 +1748,30 @@ module DEBUGGER__
|
|
1599
1748
|
file_path, reloaded = @sr.add(iseq, src)
|
1600
1749
|
@ui.event :load, file_path, reloaded
|
1601
1750
|
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1751
|
+
# check breakpoints
|
1752
|
+
if file_path
|
1753
|
+
@bps.find_all do |_key, bp|
|
1754
|
+
LineBreakpoint === bp && bp.path_is?(file_path) && (iseq.first_lineno..iseq.last_line).cover?(bp.line)
|
1755
|
+
end.each do |_key, bp|
|
1756
|
+
if !bp.iseq
|
1757
|
+
bp.try_activate iseq
|
1758
|
+
elsif reloaded
|
1759
|
+
@bps.delete bp.key # to allow duplicate
|
1605
1760
|
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
end
|
1610
|
-
end
|
1761
|
+
# When we delete a breakpoint from the @bps hash, we also need to deactivate it or else its tracepoint event
|
1762
|
+
# will continue to be enabled and we'll suspend on ghost breakpoints
|
1763
|
+
bp.delete
|
1611
1764
|
|
1612
|
-
|
1613
|
-
@bps.find_all do |key, bp|
|
1614
|
-
LineBreakpoint === bp && DEBUGGER__.compare_path(bp.path, file_path)
|
1615
|
-
end.each do |_key, bp|
|
1616
|
-
@bps.delete bp.key # to allow duplicate
|
1617
|
-
if nbp = LineBreakpoint.copy(bp, iseq)
|
1765
|
+
nbp = LineBreakpoint.copy(bp, iseq)
|
1618
1766
|
add_bp nbp
|
1619
1767
|
end
|
1620
1768
|
end
|
1769
|
+
else # !file_path => file_path is not existing
|
1770
|
+
@bps.find_all do |_key, bp|
|
1771
|
+
LineBreakpoint === bp && !bp.iseq && DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
|
1772
|
+
end.each do |_key, bp|
|
1773
|
+
bp.try_activate iseq
|
1774
|
+
end
|
1621
1775
|
end
|
1622
1776
|
end
|
1623
1777
|
|
@@ -1716,6 +1870,12 @@ module DEBUGGER__
|
|
1716
1870
|
end
|
1717
1871
|
end
|
1718
1872
|
|
1873
|
+
def check_unsafe
|
1874
|
+
if @unsafe_context
|
1875
|
+
raise RuntimeError, "#{@repl_prev_line.dump} is not allowed on unsafe context."
|
1876
|
+
end
|
1877
|
+
end
|
1878
|
+
|
1719
1879
|
def enter_postmortem_session exc
|
1720
1880
|
return unless exc.instance_variable_defined? :@__debugger_postmortem_frames
|
1721
1881
|
|
@@ -1795,6 +1955,17 @@ module DEBUGGER__
|
|
1795
1955
|
end
|
1796
1956
|
end
|
1797
1957
|
|
1958
|
+
def set_no_sigint_hook old, new
|
1959
|
+
return unless old != new
|
1960
|
+
return unless @ui.respond_to? :activate_sigint
|
1961
|
+
|
1962
|
+
if old # no -> yes
|
1963
|
+
@ui.activate_sigint
|
1964
|
+
else
|
1965
|
+
@ui.deactivate_sigint
|
1966
|
+
end
|
1967
|
+
end
|
1968
|
+
|
1798
1969
|
def save_int_trap cmd
|
1799
1970
|
prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, cmd
|
1800
1971
|
prev
|
@@ -1838,6 +2009,13 @@ module DEBUGGER__
|
|
1838
2009
|
def after_fork_parent
|
1839
2010
|
@ui.after_fork_parent
|
1840
2011
|
end
|
2012
|
+
|
2013
|
+
# experimental API
|
2014
|
+
def extend_feature session: nil, thread_client: nil, ui: nil
|
2015
|
+
Session.include session if session
|
2016
|
+
ThreadClient.include thread_client if thread_client
|
2017
|
+
@ui.extend ui if ui
|
2018
|
+
end
|
1841
2019
|
end
|
1842
2020
|
|
1843
2021
|
class ProcessGroup
|
@@ -1886,9 +2064,11 @@ module DEBUGGER__
|
|
1886
2064
|
|
1887
2065
|
def after_fork child: true
|
1888
2066
|
if child || !@lock_file
|
1889
|
-
@m = Mutex.new
|
1890
|
-
@
|
1891
|
-
|
2067
|
+
@m = Mutex.new unless @m
|
2068
|
+
@m.synchronize do
|
2069
|
+
@lock_level = 0
|
2070
|
+
@lock_file = open(@lock_tempfile.path, 'w')
|
2071
|
+
end
|
1892
2072
|
end
|
1893
2073
|
end
|
1894
2074
|
|
@@ -1897,7 +2077,7 @@ module DEBUGGER__
|
|
1897
2077
|
end
|
1898
2078
|
|
1899
2079
|
def locked?
|
1900
|
-
# DEBUGGER__.
|
2080
|
+
# DEBUGGER__.debug{ "locked? #{@lock_level}" }
|
1901
2081
|
@lock_level > 0
|
1902
2082
|
end
|
1903
2083
|
|
@@ -1982,6 +2162,10 @@ module DEBUGGER__
|
|
1982
2162
|
end
|
1983
2163
|
end
|
1984
2164
|
|
2165
|
+
def ignore_output_on_suspend?
|
2166
|
+
false
|
2167
|
+
end
|
2168
|
+
|
1985
2169
|
def flush
|
1986
2170
|
end
|
1987
2171
|
end
|
@@ -2006,6 +2190,7 @@ module DEBUGGER__
|
|
2006
2190
|
case loc.absolute_path
|
2007
2191
|
when dir_prefix
|
2008
2192
|
when %r{rubygems/core_ext/kernel_require\.rb}
|
2193
|
+
when %r{bundled_gems\.rb}
|
2009
2194
|
else
|
2010
2195
|
return loc if loc.absolute_path
|
2011
2196
|
end
|
@@ -2018,19 +2203,22 @@ module DEBUGGER__
|
|
2018
2203
|
def self.start nonstop: false, **kw
|
2019
2204
|
CONFIG.set_config(**kw)
|
2020
2205
|
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2206
|
+
if CONFIG[:open]
|
2207
|
+
open nonstop: nonstop, **kw
|
2208
|
+
else
|
2209
|
+
unless defined? SESSION
|
2210
|
+
require_relative 'local'
|
2211
|
+
initialize_session{ UI_LocalConsole.new }
|
2212
|
+
end
|
2213
|
+
setup_initial_suspend unless nonstop
|
2024
2214
|
end
|
2025
|
-
|
2026
|
-
setup_initial_suspend unless nonstop
|
2027
2215
|
end
|
2028
2216
|
|
2029
2217
|
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
2030
2218
|
CONFIG.set_config(**kw)
|
2031
2219
|
require_relative 'server'
|
2032
2220
|
|
2033
|
-
if port || CONFIG[:
|
2221
|
+
if port || CONFIG[:open] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
|
2034
2222
|
open_tcp host: host, port: (port || 0), nonstop: nonstop
|
2035
2223
|
else
|
2036
2224
|
open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
|
@@ -2092,6 +2280,18 @@ module DEBUGGER__
|
|
2092
2280
|
end
|
2093
2281
|
end
|
2094
2282
|
|
2283
|
+
# Exiting control
|
2284
|
+
|
2285
|
+
class << self
|
2286
|
+
def skip_all
|
2287
|
+
@skip_all = true
|
2288
|
+
end
|
2289
|
+
|
2290
|
+
def skip?
|
2291
|
+
@skip_all
|
2292
|
+
end
|
2293
|
+
end
|
2294
|
+
|
2095
2295
|
def self.load_rc
|
2096
2296
|
[[File.expand_path('~/.rdbgrc'), true],
|
2097
2297
|
[File.expand_path('~/.rdbgrc.rb'), true],
|
@@ -2120,7 +2320,7 @@ module DEBUGGER__
|
|
2120
2320
|
end
|
2121
2321
|
|
2122
2322
|
# Inspector
|
2123
|
-
|
2323
|
+
|
2124
2324
|
SHORT_INSPECT_LENGTH = 40
|
2125
2325
|
|
2126
2326
|
class LimitedPP
|
@@ -2191,6 +2391,7 @@ module DEBUGGER__
|
|
2191
2391
|
def self.log level, msg
|
2192
2392
|
if check_loglevel level
|
2193
2393
|
@logfile = STDERR unless defined? @logfile
|
2394
|
+
return if @logfile.closed?
|
2194
2395
|
|
2195
2396
|
if defined? SESSION
|
2196
2397
|
pi = SESSION.process_info
|
@@ -2222,7 +2423,7 @@ module DEBUGGER__
|
|
2222
2423
|
# depend on the file system. So this check is only roughly estimation.
|
2223
2424
|
|
2224
2425
|
def self.compare_path(a, b)
|
2225
|
-
a
|
2426
|
+
a&.downcase == b&.downcase
|
2226
2427
|
end
|
2227
2428
|
else
|
2228
2429
|
def self.compare_path(a, b)
|
@@ -2273,8 +2474,24 @@ module DEBUGGER__
|
|
2273
2474
|
end
|
2274
2475
|
end
|
2275
2476
|
|
2276
|
-
|
2277
|
-
|
2477
|
+
module DaemonInterceptor
|
2478
|
+
def daemon(*args)
|
2479
|
+
return super unless defined?(SESSION) && SESSION.active?
|
2480
|
+
|
2481
|
+
_, child_hook = __fork_setup_for_debugger(:child)
|
2482
|
+
|
2483
|
+
unless SESSION.remote?
|
2484
|
+
DEBUGGER__.warn "Can't debug the code after Process.daemon locally. Use the remote debugging feature."
|
2485
|
+
end
|
2486
|
+
|
2487
|
+
super.tap do
|
2488
|
+
child_hook.call
|
2489
|
+
end
|
2490
|
+
end
|
2491
|
+
end
|
2492
|
+
|
2493
|
+
private def __fork_setup_for_debugger fork_mode = nil
|
2494
|
+
fork_mode ||= CONFIG[:fork_mode]
|
2278
2495
|
|
2279
2496
|
if fork_mode == :both && CONFIG[:parent_on_fork]
|
2280
2497
|
fork_mode = :parent
|
@@ -2289,19 +2506,19 @@ module DEBUGGER__
|
|
2289
2506
|
# Do nothing
|
2290
2507
|
}
|
2291
2508
|
child_hook = -> {
|
2292
|
-
DEBUGGER__.
|
2509
|
+
DEBUGGER__.info "Detaching after fork from child process #{Process.pid}"
|
2293
2510
|
SESSION.deactivate
|
2294
2511
|
}
|
2295
2512
|
when :child
|
2296
2513
|
SESSION.before_fork false
|
2297
2514
|
|
2298
2515
|
parent_hook = -> child_pid {
|
2299
|
-
DEBUGGER__.
|
2516
|
+
DEBUGGER__.info "Detaching after fork from parent process #{Process.pid}"
|
2300
2517
|
SESSION.after_fork_parent
|
2301
2518
|
SESSION.deactivate
|
2302
2519
|
}
|
2303
2520
|
child_hook = -> {
|
2304
|
-
DEBUGGER__.
|
2521
|
+
DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
2305
2522
|
SESSION.activate on_fork: true
|
2306
2523
|
}
|
2307
2524
|
when :both
|
@@ -2312,7 +2529,7 @@ module DEBUGGER__
|
|
2312
2529
|
SESSION.after_fork_parent
|
2313
2530
|
}
|
2314
2531
|
child_hook = -> {
|
2315
|
-
DEBUGGER__.
|
2532
|
+
DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
|
2316
2533
|
SESSION.process_group.after_fork child: true
|
2317
2534
|
SESSION.activate on_fork: true
|
2318
2535
|
}
|
@@ -2324,7 +2541,17 @@ module DEBUGGER__
|
|
2324
2541
|
|
2325
2542
|
module TrapInterceptor
|
2326
2543
|
def trap sig, *command, &command_proc
|
2327
|
-
|
2544
|
+
sym =
|
2545
|
+
case sig
|
2546
|
+
when String
|
2547
|
+
sig.to_sym
|
2548
|
+
when Integer
|
2549
|
+
Signal.signame(sig)&.to_sym
|
2550
|
+
else
|
2551
|
+
sig
|
2552
|
+
end
|
2553
|
+
|
2554
|
+
case sym
|
2328
2555
|
when :INT, :SIGINT
|
2329
2556
|
if defined?(SESSION) && SESSION.active? && SESSION.intercept_trap_sigint?
|
2330
2557
|
return SESSION.save_int_trap(command.empty? ? command_proc : command.first)
|
@@ -2339,6 +2566,7 @@ module DEBUGGER__
|
|
2339
2566
|
module ::Process
|
2340
2567
|
class << self
|
2341
2568
|
prepend ForkInterceptor
|
2569
|
+
prepend DaemonInterceptor
|
2342
2570
|
end
|
2343
2571
|
end
|
2344
2572
|
|
@@ -2374,6 +2602,7 @@ module DEBUGGER__
|
|
2374
2602
|
module ::Process
|
2375
2603
|
class << self
|
2376
2604
|
prepend ForkInterceptor
|
2605
|
+
prepend DaemonInterceptor
|
2377
2606
|
end
|
2378
2607
|
end
|
2379
2608
|
end
|
@@ -2390,10 +2619,17 @@ module Kernel
|
|
2390
2619
|
return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
|
2391
2620
|
|
2392
2621
|
if pre || (do_expr = binding.local_variable_get(:do))
|
2393
|
-
cmds = ['
|
2622
|
+
cmds = ['#debugger', pre, do_expr]
|
2394
2623
|
end
|
2395
2624
|
|
2396
|
-
|
2625
|
+
if ::DEBUGGER__::SESSION.in_subsession?
|
2626
|
+
if cmds
|
2627
|
+
commands = [*cmds[1], *cmds[2]].map{|c| c.split(';;').join("\n")}
|
2628
|
+
::DEBUGGER__::SESSION.add_preset_commands cmds[0], commands, kick: false, continue: false
|
2629
|
+
end
|
2630
|
+
else
|
2631
|
+
loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
|
2632
|
+
end
|
2397
2633
|
self
|
2398
2634
|
end
|
2399
2635
|
|