debug 1.0.0.beta2 → 1.0.0.beta7
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/.github/workflows/ruby.yml +34 -0
- data/.gitignore +3 -0
- data/CONTRIBUTING.md +336 -0
- data/Gemfile +2 -2
- data/README.md +190 -73
- data/TODO.md +27 -0
- data/bin/gentest +22 -0
- data/debug.gemspec +2 -0
- data/exe/rdbg +14 -11
- data/ext/debug/debug.c +10 -9
- data/lib/debug.rb +4 -1
- data/lib/debug/breakpoint.rb +110 -45
- data/lib/debug/client.rb +55 -13
- data/lib/debug/color.rb +76 -0
- data/lib/debug/config.rb +157 -33
- data/lib/debug/console.rb +26 -2
- data/lib/debug/frame_info.rb +145 -0
- data/lib/debug/open.rb +3 -0
- data/lib/debug/run.rb +4 -1
- data/lib/debug/server.rb +103 -50
- data/lib/debug/server_dap.rb +607 -0
- data/lib/debug/session.rb +534 -169
- data/lib/debug/source_repository.rb +64 -12
- data/lib/debug/thread_client.rb +211 -126
- data/lib/debug/version.rb +3 -1
- data/misc/README.md.erb +95 -47
- metadata +24 -3
data/lib/debug/session.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
|
-
|
2
|
-
# used in thread_client.c
|
3
|
-
FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
|
4
|
-
:has_return_value, :return_value, :show_line)
|
5
|
-
end
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
3
|
+
# skip to load debugger for bundle exec
|
4
|
+
return if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
8
5
|
|
6
|
+
require_relative 'config'
|
7
|
+
require_relative 'thread_client'
|
9
8
|
require_relative 'source_repository'
|
10
9
|
require_relative 'breakpoint'
|
11
|
-
|
12
|
-
|
10
|
+
|
11
|
+
require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
|
13
12
|
|
14
13
|
class RubyVM::InstructionSequence
|
15
14
|
def traceable_lines_norec lines
|
@@ -47,9 +46,15 @@ class RubyVM::InstructionSequence
|
|
47
46
|
def last_line
|
48
47
|
self.to_a[4][:code_location][2]
|
49
48
|
end
|
49
|
+
|
50
|
+
def first_line
|
51
|
+
self.to_a[4][:code_location][0]
|
52
|
+
end
|
50
53
|
end
|
51
54
|
|
52
55
|
module DEBUGGER__
|
56
|
+
PresetCommand = Struct.new(:commands, :source, :auto_continue)
|
57
|
+
|
53
58
|
class Session
|
54
59
|
def initialize ui
|
55
60
|
@ui = ui
|
@@ -57,103 +62,149 @@ module DEBUGGER__
|
|
57
62
|
@bps = {} # bp.key => bp
|
58
63
|
# [file, line] => LineBreakpoint
|
59
64
|
# "Error" => CatchBreakpoint
|
60
|
-
#
|
61
|
-
# [:watch,
|
65
|
+
# "Foo#bar" => MethodBreakpoint
|
66
|
+
# [:watch, ivar] => WatchIVarBreakpoint
|
62
67
|
# [:check, expr] => CheckBreakpoint
|
63
68
|
@th_clients = {} # {Thread => ThreadClient}
|
64
69
|
@q_evt = Queue.new
|
65
70
|
@displays = []
|
66
71
|
@tc = nil
|
67
72
|
@tc_id = 0
|
68
|
-
@
|
73
|
+
@preset_command = nil
|
74
|
+
|
75
|
+
@frame_map = {} # {id => [threadId, frame_depth]} for DAP
|
76
|
+
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
77
|
+
@src_map = {} # {id => src}
|
69
78
|
|
70
79
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
71
|
-
|
72
|
-
|
80
|
+
unless @management_threads.include? Thread.current
|
81
|
+
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
82
|
+
end
|
83
|
+
}
|
84
|
+
@tp_load_script.enable
|
73
85
|
|
74
86
|
@session_server = Thread.new do
|
75
87
|
Thread.current.abort_on_exception = true
|
88
|
+
session_server_main
|
89
|
+
end
|
76
90
|
|
77
|
-
|
78
|
-
|
79
|
-
output.each{|str| @ui.puts str}
|
80
|
-
|
81
|
-
case ev
|
82
|
-
when :load
|
83
|
-
iseq, src = ev_args
|
84
|
-
on_load iseq, src
|
85
|
-
tc << :continue
|
86
|
-
when :thread_begin
|
87
|
-
th = ev_args.shift
|
88
|
-
on_thread_begin th
|
89
|
-
tc << :continue
|
90
|
-
when :suspend
|
91
|
-
case ev_args.first
|
92
|
-
when :breakpoint
|
93
|
-
bp, i = bp_index ev_args[1]
|
94
|
-
if bp
|
95
|
-
@ui.puts "\nStop by \##{i} #{bp}"
|
96
|
-
end
|
97
|
-
when :trap
|
98
|
-
@ui.puts ''
|
99
|
-
@ui.puts "\nStop by #{ev_args[1]}"
|
100
|
-
end
|
91
|
+
@management_threads = [@session_server]
|
92
|
+
@management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
|
101
93
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
94
|
+
setup_threads
|
95
|
+
|
96
|
+
@tp_thread_begin = TracePoint.new(:thread_begin){|tp|
|
97
|
+
unless @management_threads.include?(th = Thread.current)
|
98
|
+
ThreadClient.current.on_thread_begin th
|
99
|
+
end
|
100
|
+
}
|
101
|
+
@tp_thread_begin.enable
|
102
|
+
end
|
103
|
+
|
104
|
+
def active?
|
105
|
+
@ui ? true : false
|
106
|
+
end
|
107
|
+
|
108
|
+
def reset_ui ui
|
109
|
+
@ui.close
|
110
|
+
@ui = ui
|
111
|
+
@management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
|
112
|
+
end
|
113
|
+
|
114
|
+
def session_server_main
|
115
|
+
while evt = @q_evt.pop
|
116
|
+
# varible `@internal_info` is only used for test
|
117
|
+
tc, output, ev, @internal_info, *ev_args = evt
|
118
|
+
output.each{|str| @ui.puts str}
|
119
|
+
|
120
|
+
case ev
|
121
|
+
when :init
|
122
|
+
wait_command_loop tc
|
123
|
+
when :load
|
124
|
+
iseq, src = ev_args
|
125
|
+
on_load iseq, src
|
126
|
+
@ui.event :load
|
127
|
+
tc << :continue
|
128
|
+
when :thread_begin
|
129
|
+
th = ev_args.shift
|
130
|
+
on_thread_begin th
|
131
|
+
@ui.event :thread_begin, th
|
132
|
+
tc << :continue
|
133
|
+
when :suspend
|
134
|
+
case ev_args.first
|
135
|
+
when :breakpoint
|
136
|
+
bp, i = bp_index ev_args[1]
|
137
|
+
@ui.event :suspend_bp, i, bp
|
138
|
+
when :trap
|
139
|
+
@ui.event :suspend_trap, ev_args[1]
|
140
|
+
else
|
141
|
+
@ui.event :suspended
|
142
|
+
end
|
143
|
+
|
144
|
+
if @displays.empty?
|
145
|
+
wait_command_loop tc
|
146
|
+
else
|
147
|
+
tc << [:eval, :display, @displays]
|
148
|
+
end
|
149
|
+
when :result
|
150
|
+
case ev_args.first
|
151
|
+
when :try_display
|
152
|
+
failed_results = ev_args[1]
|
153
|
+
if failed_results.size > 0
|
154
|
+
i, _msg = failed_results.last
|
155
|
+
if i+1 == @displays.size
|
156
|
+
@ui.puts "canceled: #{@displays.pop}"
|
157
|
+
end
|
106
158
|
end
|
107
|
-
when :
|
108
|
-
|
109
|
-
|
110
|
-
bp
|
111
|
-
@bps[bp.key] = bp
|
159
|
+
when :method_breakpoint, :watch_breakpoint
|
160
|
+
bp = ev_args[1]
|
161
|
+
if bp
|
162
|
+
add_breakpoint(bp)
|
112
163
|
show_bps bp
|
113
|
-
when :try_display
|
114
|
-
failed_results = ev_args[1]
|
115
|
-
if failed_results.size > 0
|
116
|
-
i, msg = failed_results.last
|
117
|
-
if i+1 == @displays.size
|
118
|
-
@ui.puts "canceled: #{@displays.pop}"
|
119
|
-
end
|
120
|
-
end
|
121
|
-
when :method_breakpoint
|
122
|
-
bp = ev_args[1]
|
123
|
-
if bp
|
124
|
-
@bps[bp.key] = bp
|
125
|
-
show_bps bp
|
126
|
-
else
|
127
|
-
# can't make a bp
|
128
|
-
end
|
129
164
|
else
|
130
|
-
#
|
165
|
+
# can't make a bp
|
131
166
|
end
|
132
|
-
|
133
|
-
|
167
|
+
else
|
168
|
+
# ignore
|
134
169
|
end
|
135
|
-
end
|
136
|
-
end
|
137
170
|
|
138
|
-
|
139
|
-
@management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
|
171
|
+
wait_command_loop tc
|
140
172
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
173
|
+
when :dap_result
|
174
|
+
dap_event ev_args # server.rb
|
175
|
+
wait_command_loop tc
|
176
|
+
end
|
177
|
+
end
|
178
|
+
ensure
|
179
|
+
@tp_load_script.disable
|
180
|
+
@tp_thread_begin.disable
|
181
|
+
@bps.each{|k, bp| bp.disable}
|
182
|
+
@th_clients.each{|th, thc| thc.close}
|
183
|
+
@ui = nil
|
146
184
|
end
|
147
185
|
|
148
|
-
def
|
149
|
-
cmds.
|
150
|
-
c.gsub(
|
151
|
-
|
152
|
-
}
|
186
|
+
def add_preset_commands name, cmds, kick: true, continue: true
|
187
|
+
cs = cmds.map{|c|
|
188
|
+
c = c.strip.gsub(/\A\s*\#.*/, '').strip
|
189
|
+
c unless c.empty?
|
190
|
+
}.compact
|
191
|
+
|
192
|
+
unless cs.empty?
|
193
|
+
if @preset_command
|
194
|
+
@preset_command.commands += cs
|
195
|
+
else
|
196
|
+
@preset_command = PresetCommand.new(cs, name, continue)
|
197
|
+
end
|
198
|
+
ThreadClient.current.on_init name if kick
|
199
|
+
end
|
153
200
|
end
|
154
201
|
|
155
|
-
def source
|
156
|
-
|
202
|
+
def source iseq
|
203
|
+
if !CONFIG[:no_color]
|
204
|
+
@sr.get_colored(iseq)
|
205
|
+
else
|
206
|
+
@sr.get(iseq)
|
207
|
+
end
|
157
208
|
end
|
158
209
|
|
159
210
|
def inspect
|
@@ -179,13 +230,36 @@ module DEBUGGER__
|
|
179
230
|
end
|
180
231
|
|
181
232
|
def wait_command
|
182
|
-
if @
|
233
|
+
if @preset_command
|
234
|
+
if @preset_command.commands.empty?
|
235
|
+
if @preset_command.auto_continue
|
236
|
+
@preset_command = nil
|
237
|
+
@tc << :continue
|
238
|
+
return
|
239
|
+
else
|
240
|
+
@preset_command = nil
|
241
|
+
return :retry
|
242
|
+
end
|
243
|
+
else
|
244
|
+
line = @preset_command.commands.shift
|
245
|
+
@ui.puts "(rdbg:#{@preset_command.source}) #{line}"
|
246
|
+
end
|
247
|
+
else
|
248
|
+
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
|
183
249
|
line = @ui.readline
|
250
|
+
end
|
251
|
+
|
252
|
+
case line
|
253
|
+
when String
|
254
|
+
process_command line
|
255
|
+
when Hash
|
256
|
+
process_dap_request line # defined in server.rb
|
184
257
|
else
|
185
|
-
|
186
|
-
@ui.puts "(rdbg:init) #{line}"
|
258
|
+
raise "unexpected input: #{line.inspect}"
|
187
259
|
end
|
260
|
+
end
|
188
261
|
|
262
|
+
def process_command line
|
189
263
|
if line.empty?
|
190
264
|
if @repl_prev_line
|
191
265
|
line = @repl_prev_line
|
@@ -207,26 +281,30 @@ module DEBUGGER__
|
|
207
281
|
# * `s[tep]`
|
208
282
|
# * Step in. Resume the program until next breakable point.
|
209
283
|
when 's', 'step'
|
284
|
+
cancel_auto_continue
|
210
285
|
@tc << [:step, :in]
|
211
286
|
|
212
287
|
# * `n[ext]`
|
213
288
|
# * Step over. Resume the program until next line.
|
214
289
|
when 'n', 'next'
|
290
|
+
cancel_auto_continue
|
215
291
|
@tc << [:step, :next]
|
216
292
|
|
217
293
|
# * `fin[ish]`
|
218
294
|
# * Finish this frame. Resume the program until the current frame is finished.
|
219
295
|
when 'fin', 'finish'
|
296
|
+
cancel_auto_continue
|
220
297
|
@tc << [:step, :finish]
|
221
298
|
|
222
299
|
# * `c[ontinue]`
|
223
300
|
# * Resume the program.
|
224
301
|
when 'c', 'continue'
|
302
|
+
cancel_auto_continue
|
225
303
|
@tc << :continue
|
226
304
|
|
227
|
-
# * `q[uit]` or
|
305
|
+
# * `q[uit]` or `Ctrl-D`
|
228
306
|
# * Finish debugger (with the debuggee process on non-remote debugging).
|
229
|
-
when 'q', 'quit'
|
307
|
+
when 'q', 'quit'
|
230
308
|
if ask 'Really quit?'
|
231
309
|
@ui.quit arg.to_i
|
232
310
|
@tc << :continue
|
@@ -234,15 +312,26 @@ module DEBUGGER__
|
|
234
312
|
return :retry
|
235
313
|
end
|
236
314
|
|
237
|
-
# * `
|
238
|
-
# *
|
239
|
-
when '
|
315
|
+
# * `q[uit]!`
|
316
|
+
# * Same as q[uit] but without the confirmation prompt.
|
317
|
+
when 'q!', 'quit!'
|
318
|
+
@ui.quit arg.to_i
|
319
|
+
@tc << :continue
|
320
|
+
|
321
|
+
# * `kill`
|
322
|
+
# * Stop the debuggee process with `Kernal#exit!`.
|
323
|
+
when 'kill'
|
240
324
|
if ask 'Really kill?'
|
241
325
|
exit! (arg || 1).to_i
|
242
326
|
else
|
243
327
|
return :retry
|
244
328
|
end
|
245
329
|
|
330
|
+
# * `kill!`
|
331
|
+
# * Same as kill but without the confirmation prompt.
|
332
|
+
when 'kill!'
|
333
|
+
exit! (arg || 1).to_i
|
334
|
+
|
246
335
|
### Breakpoint
|
247
336
|
|
248
337
|
# * `b[reak]`
|
@@ -255,10 +344,14 @@ module DEBUGGER__
|
|
255
344
|
# * Set breakpoint on the method `<class>#<name>`.
|
256
345
|
# * `b[reak] <expr>.<name>`
|
257
346
|
# * Set breakpoint on the method `<expr>.<name>`.
|
258
|
-
# * `b[reak] ... if <expr>`
|
347
|
+
# * `b[reak] ... if: <expr>`
|
259
348
|
# * break if `<expr>` is true at specified location.
|
260
|
-
# * `b[reak]
|
261
|
-
# * break
|
349
|
+
# * `b[reak] ... pre: <command>`
|
350
|
+
# * break and run `<command>` before stopping.
|
351
|
+
# * `b[reak] ... do: <command>`
|
352
|
+
# * break and run `<command>`, and continue.
|
353
|
+
# * `b[reak] if: <expr>`
|
354
|
+
# * break if: `<expr>` is true at any lines.
|
262
355
|
# * Note that this feature is super slow.
|
263
356
|
when 'b', 'break'
|
264
357
|
if arg == nil
|
@@ -311,12 +404,12 @@ module DEBUGGER__
|
|
311
404
|
end
|
312
405
|
return :retry
|
313
406
|
|
314
|
-
# * `watch
|
315
|
-
# * Stop the execution when the result of
|
407
|
+
# * `watch @ivar`
|
408
|
+
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
316
409
|
# * Note that this feature is super slow.
|
317
410
|
when 'wat', 'watch'
|
318
|
-
if arg
|
319
|
-
@tc << [:
|
411
|
+
if arg && arg.match?(/\A@\w+/)
|
412
|
+
@tc << [:breakpoint, :watch, arg]
|
320
413
|
else
|
321
414
|
show_bps
|
322
415
|
return :retry
|
@@ -346,8 +439,25 @@ module DEBUGGER__
|
|
346
439
|
|
347
440
|
# * `bt` or `backtrace`
|
348
441
|
# * Show backtrace (frame) information.
|
442
|
+
# * `bt <num>` or `backtrace <num>`
|
443
|
+
# * Only shows first `<num>` frames.
|
444
|
+
# * `bt /regexp/` or `backtrace /regexp/`
|
445
|
+
# * Only shows frames with method name or location info that matches `/regexp/`.
|
446
|
+
# * `bt <num> /regexp/` or `backtrace <num> /regexp/`
|
447
|
+
# * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
|
349
448
|
when 'bt', 'backtrace'
|
350
|
-
|
449
|
+
case arg
|
450
|
+
when /\A(\d+)\z/
|
451
|
+
@tc << [:show, :backtrace, arg.to_i, nil]
|
452
|
+
when /\A\/(.*)\/\z/
|
453
|
+
pattern = $1
|
454
|
+
@tc << [:show, :backtrace, nil, Regexp.compile(pattern)]
|
455
|
+
when /\A(\d+)\s+\/(.*)\/\z/
|
456
|
+
max, pattern = $1, $2
|
457
|
+
@tc << [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
|
458
|
+
else
|
459
|
+
@tc << [:show, :backtrace, nil, nil]
|
460
|
+
end
|
351
461
|
|
352
462
|
# * `l[ist]`
|
353
463
|
# * Show current frame's source code.
|
@@ -391,17 +501,23 @@ module DEBUGGER__
|
|
391
501
|
|
392
502
|
@tc << [:show, :edit, arg]
|
393
503
|
|
394
|
-
# * `i[nfo]`
|
504
|
+
# * `i[nfo]`, `i[nfo] l[ocal[s]]`
|
395
505
|
# * Show information about the current frame (local variables)
|
396
506
|
# * It includes `self` as `%self` and a return value as `%return`.
|
397
|
-
# * `i[nfo]
|
398
|
-
# * Show
|
507
|
+
# * `i[nfo] th[read[s]]`
|
508
|
+
# * Show all threads (same as `th[read]`).
|
399
509
|
when 'i', 'info'
|
400
510
|
case arg
|
401
511
|
when nil
|
402
512
|
@tc << [:show, :local]
|
513
|
+
when 'l', /locals?/
|
514
|
+
@tc << [:show, :local]
|
515
|
+
when 'th', /threads?/
|
516
|
+
thread_list
|
517
|
+
return :retry
|
403
518
|
else
|
404
|
-
|
519
|
+
show_help 'info'
|
520
|
+
return :retry
|
405
521
|
end
|
406
522
|
|
407
523
|
# * `display`
|
@@ -410,7 +526,7 @@ module DEBUGGER__
|
|
410
526
|
# * Show the result of `<expr>` at every suspended timing.
|
411
527
|
when 'display'
|
412
528
|
if arg && !arg.empty?
|
413
|
-
@displays << arg
|
529
|
+
@displays << arg
|
414
530
|
@tc << [:eval, :try_display, @displays]
|
415
531
|
else
|
416
532
|
@tc << [:eval, :display, @displays]
|
@@ -424,9 +540,7 @@ module DEBUGGER__
|
|
424
540
|
case arg
|
425
541
|
when /(\d+)/
|
426
542
|
if @displays[n = $1.to_i]
|
427
|
-
|
428
|
-
@displays.delete_at n
|
429
|
-
end
|
543
|
+
@displays.delete_at n
|
430
544
|
end
|
431
545
|
@tc << [:eval, :display, @displays]
|
432
546
|
when nil
|
@@ -442,9 +556,11 @@ module DEBUGGER__
|
|
442
556
|
case arg
|
443
557
|
when 'on'
|
444
558
|
dir = __dir__
|
445
|
-
@tracer ||= TracePoint.new(){|tp|
|
559
|
+
@tracer ||= TracePoint.new(:call, :return, :b_call, :b_return, :line, :class, :end){|tp|
|
446
560
|
next if File.dirname(tp.path) == dir
|
447
561
|
next if tp.path == '<internal:trace_point>'
|
562
|
+
# Skip when `JSON.generate` is called during tests
|
563
|
+
next if tp.binding.eval('self').to_s == 'JSON' and ENV['RUBY_DEBUG_TEST_MODE']
|
448
564
|
# next if tp.event != :line
|
449
565
|
@ui.puts pretty_tp(tp)
|
450
566
|
}
|
@@ -459,19 +575,19 @@ module DEBUGGER__
|
|
459
575
|
### Frame control
|
460
576
|
|
461
577
|
# * `f[rame]`
|
462
|
-
# * Show current frame.
|
578
|
+
# * Show the current frame.
|
463
579
|
# * `f[rame] <framenum>`
|
464
|
-
# * Specify frame. Evaluation are run on
|
580
|
+
# * Specify a current frame. Evaluation are run on specified frame.
|
465
581
|
when 'frame', 'f'
|
466
582
|
@tc << [:frame, :set, arg]
|
467
583
|
|
468
584
|
# * `up`
|
469
|
-
# * Specify upper frame.
|
585
|
+
# * Specify the upper frame.
|
470
586
|
when 'up'
|
471
587
|
@tc << [:frame, :up]
|
472
588
|
|
473
589
|
# * `down`
|
474
|
-
# * Specify
|
590
|
+
# * Specify the lower frame.
|
475
591
|
when 'down'
|
476
592
|
@tc << [:frame, :down]
|
477
593
|
|
@@ -501,6 +617,9 @@ module DEBUGGER__
|
|
501
617
|
end
|
502
618
|
@tc << [:eval, :call, 'binding.irb']
|
503
619
|
|
620
|
+
# don't repeat irb command
|
621
|
+
@repl_prev_line = nil
|
622
|
+
|
504
623
|
### Thread control
|
505
624
|
|
506
625
|
# * `th[read]`
|
@@ -518,6 +637,21 @@ module DEBUGGER__
|
|
518
637
|
end
|
519
638
|
return :retry
|
520
639
|
|
640
|
+
### Configuration
|
641
|
+
# * `config`
|
642
|
+
# * Show all configuration with description.
|
643
|
+
# * `config <name>`
|
644
|
+
# * Show current configuration of <name>.
|
645
|
+
# * `config set <name> <val>` or `config <name> = <val>`
|
646
|
+
# * Set <name> to <val>.
|
647
|
+
# * `config append <name> <val>` or `config <name> << <val>`
|
648
|
+
# * Append `<val>` to `<name>` if it is an array.
|
649
|
+
# * `config unset <name>`
|
650
|
+
# * Set <name> to default.
|
651
|
+
when 'config'
|
652
|
+
config_command arg
|
653
|
+
return :retry
|
654
|
+
|
521
655
|
### Help
|
522
656
|
|
523
657
|
# * `h[elp]`
|
@@ -549,6 +683,79 @@ module DEBUGGER__
|
|
549
683
|
return :retry
|
550
684
|
end
|
551
685
|
|
686
|
+
def config_show key
|
687
|
+
key = key.to_sym
|
688
|
+
if CONFIG_SET[key]
|
689
|
+
v = CONFIG[key]
|
690
|
+
kv = "#{key} = #{v.nil? ? '(default)' : v.inspect}"
|
691
|
+
desc = CONFIG_SET[key][1]
|
692
|
+
line = "%-30s \# %s" % [kv, desc]
|
693
|
+
if line.size > SESSION.width
|
694
|
+
@ui.puts "\# #{desc}\n#{kv}"
|
695
|
+
else
|
696
|
+
@ui.puts line
|
697
|
+
end
|
698
|
+
else
|
699
|
+
@ui.puts "Unknown configuration: #{key}. 'config' shows all configurations."
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
def config_set key, val, append: false
|
704
|
+
if CONFIG_SET[key = key.to_sym]
|
705
|
+
begin
|
706
|
+
if append
|
707
|
+
DEBUGGER__.append_config(key, val)
|
708
|
+
else
|
709
|
+
DEBUGGER__.set_config({key => val})
|
710
|
+
end
|
711
|
+
rescue => e
|
712
|
+
@ui.puts e.message
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
716
|
+
config_show key
|
717
|
+
end
|
718
|
+
|
719
|
+
def config_command arg
|
720
|
+
case arg
|
721
|
+
when nil
|
722
|
+
CONFIG_SET.each do |k, _|
|
723
|
+
config_show k
|
724
|
+
end
|
725
|
+
|
726
|
+
when /\Aunset\s+(.+)\z/
|
727
|
+
if CONFIG_SET[key = $1.to_sym]
|
728
|
+
DEBUGGER__.set_config({key => nil})
|
729
|
+
end
|
730
|
+
config_show key
|
731
|
+
|
732
|
+
when /\A(\w+)\s*=\s*(.+)\z/
|
733
|
+
config_set $1, $2
|
734
|
+
|
735
|
+
when /\A\s*set\s+(\w+)\s+(.+)\z/
|
736
|
+
config_set $1, $2
|
737
|
+
|
738
|
+
when /\A(\w+)\s*<<\s*(.+)\z/
|
739
|
+
config_set $1, $2, append: true
|
740
|
+
|
741
|
+
when /\A\s*append\s+(\w+)\s+(.+)\z/
|
742
|
+
config_set $1, $2
|
743
|
+
|
744
|
+
when /\A(\w+)\z/
|
745
|
+
config_show $1
|
746
|
+
|
747
|
+
else
|
748
|
+
@ui.puts "Can not parse parameters: #{arg}"
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
|
753
|
+
def cancel_auto_continue
|
754
|
+
if @preset_command&.auto_continue
|
755
|
+
@preset_command.auto_continue = false
|
756
|
+
end
|
757
|
+
end
|
758
|
+
|
552
759
|
def show_help arg
|
553
760
|
DEBUGGER__.helps.each{|cat, cs|
|
554
761
|
cs.each{|ws, desc|
|
@@ -665,29 +872,37 @@ module DEBUGGER__
|
|
665
872
|
end
|
666
873
|
end
|
667
874
|
|
668
|
-
|
669
|
-
arg.strip!
|
875
|
+
BREAK_KEYWORDS = %w(if: do: pre:).freeze
|
670
876
|
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
877
|
+
def parse_break arg
|
878
|
+
mode = :sig
|
879
|
+
expr = Hash.new{|h, k| h[k] = []}
|
880
|
+
arg.split(' ').each{|w|
|
881
|
+
if BREAK_KEYWORDS.any?{|pat| w == pat}
|
882
|
+
mode = w[0..-2].to_sym
|
883
|
+
else
|
884
|
+
expr[mode] << w
|
885
|
+
end
|
886
|
+
}
|
887
|
+
expr.default_proc = nil
|
888
|
+
expr.transform_values{|v| v.join(' ')}
|
889
|
+
end
|
890
|
+
|
891
|
+
def repl_add_breakpoint arg
|
892
|
+
expr = parse_break arg.strip
|
893
|
+
cond = expr[:if]
|
894
|
+
cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
680
895
|
|
681
|
-
case sig
|
896
|
+
case expr[:sig]
|
682
897
|
when /\A(\d+)\z/
|
683
|
-
add_line_breakpoint @tc.location.path, $1.to_i, cond:
|
898
|
+
add_line_breakpoint @tc.location.path, $1.to_i, cond: expr[:if], command: cmd
|
684
899
|
when /\A(.+)[:\s+](\d+)\z/
|
685
|
-
add_line_breakpoint $1, $2.to_i, cond:
|
900
|
+
add_line_breakpoint $1, $2.to_i, cond: expr[:if], command: cmd
|
686
901
|
when /\A(.+)([\.\#])(.+)\z/
|
687
|
-
@tc << [:breakpoint, :method, $1, $2, $3,
|
902
|
+
@tc << [:breakpoint, :method, $1, $2, $3, expr[:if], cmd]
|
688
903
|
return :noretry
|
689
904
|
when nil
|
690
|
-
add_check_breakpoint
|
905
|
+
add_check_breakpoint expr[:if]
|
691
906
|
else
|
692
907
|
@ui.puts "Unknown breakpoint format: #{arg}"
|
693
908
|
@ui.puts
|
@@ -706,6 +921,8 @@ module DEBUGGER__
|
|
706
921
|
case
|
707
922
|
when th == Thread.current
|
708
923
|
# ignore
|
924
|
+
when @management_threads.include?(th)
|
925
|
+
# ignore
|
709
926
|
when @th_clients.has_key?(th)
|
710
927
|
thcs << @th_clients[th]
|
711
928
|
else
|
@@ -729,8 +946,13 @@ module DEBUGGER__
|
|
729
946
|
end
|
730
947
|
end
|
731
948
|
|
949
|
+
def managed_thread_clients
|
950
|
+
thcs, _unmanaged_ths = update_thread_list
|
951
|
+
thcs
|
952
|
+
end
|
953
|
+
|
732
954
|
def thread_switch n
|
733
|
-
thcs,
|
955
|
+
thcs, _unmanaged_ths = update_thread_list
|
734
956
|
|
735
957
|
if tc = thcs[n]
|
736
958
|
if tc.mode
|
@@ -801,9 +1023,10 @@ module DEBUGGER__
|
|
801
1023
|
end
|
802
1024
|
end
|
803
1025
|
|
804
|
-
## event
|
1026
|
+
## event
|
805
1027
|
|
806
1028
|
def on_load iseq, src
|
1029
|
+
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
|
807
1030
|
@sr.add iseq, src
|
808
1031
|
|
809
1032
|
pending_line_breakpoints do |bp|
|
@@ -816,8 +1039,14 @@ module DEBUGGER__
|
|
816
1039
|
# breakpoint management
|
817
1040
|
|
818
1041
|
def add_breakpoint bp
|
1042
|
+
# don't repeat commands that add breakpoints
|
1043
|
+
@repl_prev_line = nil
|
1044
|
+
|
819
1045
|
if @bps.has_key? bp.key
|
820
|
-
|
1046
|
+
unless bp.duplicable?
|
1047
|
+
@ui.puts "duplicated breakpoint: #{bp}"
|
1048
|
+
bp.disable
|
1049
|
+
end
|
821
1050
|
else
|
822
1051
|
@bps[bp.key] = bp
|
823
1052
|
end
|
@@ -837,7 +1066,7 @@ module DEBUGGER__
|
|
837
1066
|
|
838
1067
|
def add_catch_breakpoint arg
|
839
1068
|
bp = CatchBreakpoint.new(arg)
|
840
|
-
|
1069
|
+
add_breakpoint bp
|
841
1070
|
end
|
842
1071
|
|
843
1072
|
def add_check_breakpoint expr
|
@@ -848,13 +1077,25 @@ module DEBUGGER__
|
|
848
1077
|
def resolve_path file
|
849
1078
|
File.realpath(File.expand_path(file))
|
850
1079
|
rescue Errno::ENOENT
|
851
|
-
|
1080
|
+
case file
|
1081
|
+
when '-e', '-'
|
1082
|
+
return file
|
1083
|
+
else
|
1084
|
+
$LOAD_PATH.each do |lp|
|
1085
|
+
libpath = File.join(lp, file)
|
1086
|
+
return File.realpath(libpath)
|
1087
|
+
rescue Errno::ENOENT
|
1088
|
+
# next
|
1089
|
+
end
|
1090
|
+
end
|
1091
|
+
|
852
1092
|
raise
|
853
1093
|
end
|
854
1094
|
|
855
1095
|
def add_line_breakpoint file, line, **kw
|
856
1096
|
file = resolve_path(file)
|
857
1097
|
bp = LineBreakpoint.new(file, line, **kw)
|
1098
|
+
|
858
1099
|
add_breakpoint bp
|
859
1100
|
rescue Errno::ENOENT => e
|
860
1101
|
@ui.puts e.message
|
@@ -872,24 +1113,58 @@ module DEBUGGER__
|
|
872
1113
|
b = tp.binding
|
873
1114
|
if var_name = b.local_variables.first
|
874
1115
|
mid = b.local_variable_get(var_name)
|
875
|
-
|
1116
|
+
unresolved = false
|
876
1117
|
|
877
1118
|
@bps.each{|k, bp|
|
878
1119
|
case bp
|
879
1120
|
when MethodBreakpoint
|
880
1121
|
if bp.method.nil?
|
881
|
-
found = true
|
882
1122
|
if bp.sig_method_name == mid.to_s
|
883
|
-
bp.
|
1123
|
+
bp.try_enable(added: true)
|
884
1124
|
end
|
885
1125
|
end
|
1126
|
+
|
1127
|
+
unresolved = true unless bp.enabled?
|
886
1128
|
end
|
887
1129
|
}
|
888
|
-
unless
|
1130
|
+
unless unresolved
|
889
1131
|
METHOD_ADDED_TRACKER.disable
|
890
1132
|
end
|
891
1133
|
end
|
892
1134
|
end
|
1135
|
+
|
1136
|
+
def width
|
1137
|
+
@ui.width
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
def check_forked
|
1141
|
+
unless @session_server.status
|
1142
|
+
# TODO: Support it
|
1143
|
+
raise 'DEBUGGER: stop at forked process is not supported yet.'
|
1144
|
+
end
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
class UI_Base
|
1149
|
+
def event type, *args
|
1150
|
+
case type
|
1151
|
+
when :suspend_bp
|
1152
|
+
i, bp = *args
|
1153
|
+
puts "\nStop by \##{i} #{bp}" if bp
|
1154
|
+
when :suspend_trap
|
1155
|
+
puts "\nStop by #{args.first}"
|
1156
|
+
end
|
1157
|
+
end
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
# manual configuration methods
|
1161
|
+
|
1162
|
+
def self.add_line_breakpoint file, line, **kw
|
1163
|
+
::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
def self.add_catch_breakpoint pat
|
1167
|
+
::DEBUGGER__::SESSION.add_catch_breakpoint pat
|
893
1168
|
end
|
894
1169
|
|
895
1170
|
# String for requring location
|
@@ -903,30 +1178,79 @@ module DEBUGGER__
|
|
903
1178
|
when dir_prefix
|
904
1179
|
when %r{rubygems/core_ext/kernel_require\.rb}
|
905
1180
|
else
|
906
|
-
return loc
|
1181
|
+
return loc if loc.absolute_path
|
907
1182
|
end
|
908
1183
|
end
|
909
1184
|
nil
|
910
1185
|
end
|
911
1186
|
|
912
|
-
|
913
|
-
initialize_session UI_Console.new
|
1187
|
+
# start methods
|
914
1188
|
|
915
|
-
|
916
|
-
|
917
|
-
|
1189
|
+
def self.start nonstop: false, **kw
|
1190
|
+
set_config(kw)
|
1191
|
+
|
1192
|
+
unless defined? SESSION
|
1193
|
+
require_relative 'console'
|
1194
|
+
initialize_session UI_Console.new
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
setup_initial_suspend unless nonstop
|
918
1198
|
end
|
919
1199
|
|
920
|
-
def self.
|
921
|
-
|
1200
|
+
def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
1201
|
+
set_config(kw)
|
1202
|
+
|
1203
|
+
if port
|
1204
|
+
open_tcp host: host, port: port, nonstop: nonstop
|
1205
|
+
else
|
1206
|
+
open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
|
1207
|
+
end
|
922
1208
|
end
|
923
1209
|
|
924
|
-
def self.
|
925
|
-
|
1210
|
+
def self.open_tcp host: nil, port:, nonstop: false, **kw
|
1211
|
+
set_config(kw)
|
1212
|
+
require_relative 'server'
|
1213
|
+
|
1214
|
+
if defined? SESSION
|
1215
|
+
SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
|
1216
|
+
else
|
1217
|
+
initialize_session UI_TcpServer.new(host: host, port: port)
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
setup_initial_suspend unless nonstop
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
1224
|
+
set_config(kw)
|
1225
|
+
require_relative 'server'
|
1226
|
+
|
1227
|
+
if defined? SESSION
|
1228
|
+
SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
1229
|
+
else
|
1230
|
+
initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
setup_initial_suspend unless nonstop
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
# boot utilities
|
1237
|
+
|
1238
|
+
def self.setup_initial_suspend
|
1239
|
+
if !::DEBUGGER__::CONFIG[:nonstop]
|
1240
|
+
if loc = ::DEBUGGER__.require_location
|
1241
|
+
# require 'debug/console' or 'debug'
|
1242
|
+
add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
|
1243
|
+
else
|
1244
|
+
# -r
|
1245
|
+
add_line_breakpoint $0, 1, oneshot: true, hook_call: false
|
1246
|
+
end
|
1247
|
+
end
|
926
1248
|
end
|
927
1249
|
|
928
1250
|
class << self
|
929
1251
|
define_method :initialize_session do |ui|
|
1252
|
+
DEBUGGER__.warn "Session start (pid: #{Process.pid})"
|
1253
|
+
|
930
1254
|
::DEBUGGER__.const_set(:SESSION, Session.new(ui))
|
931
1255
|
|
932
1256
|
# default breakpoints
|
@@ -934,18 +1258,18 @@ module DEBUGGER__
|
|
934
1258
|
# ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
|
935
1259
|
|
936
1260
|
Binding.module_eval do
|
937
|
-
|
938
|
-
|
939
|
-
end
|
1261
|
+
def break pre: nil, do: nil
|
1262
|
+
return unless SESSION.active?
|
940
1263
|
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
add_line_breakpoint $0, 1, oneshot: true, hook_call: false
|
1264
|
+
if pre || (do_expr = binding.local_variable_get(:do))
|
1265
|
+
cmds = ['binding.break', pre, do_expr]
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
|
1269
|
+
true
|
948
1270
|
end
|
1271
|
+
alias b break
|
1272
|
+
# alias bp break
|
949
1273
|
end
|
950
1274
|
|
951
1275
|
load_rc
|
@@ -953,27 +1277,29 @@ module DEBUGGER__
|
|
953
1277
|
end
|
954
1278
|
|
955
1279
|
def self.load_rc
|
956
|
-
[
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
# debug commands file
|
963
|
-
[::DEBUGGER__::CONFIG[:init_script],
|
964
|
-
'./.rdbgrc',
|
965
|
-
File.expand_path('~/.rdbgrc')].each{|path|
|
1280
|
+
[[File.expand_path('~/.rdbgrc'), true],
|
1281
|
+
[File.expand_path('~/.rdbgrc.rb'), true],
|
1282
|
+
# ['./.rdbgrc', true], # disable because of security concern
|
1283
|
+
[::DEBUGGER__::CONFIG[:init_script], false],
|
1284
|
+
].each{|(path, rc)|
|
966
1285
|
next unless path
|
1286
|
+
next if rc && ::DEBUGGER__::CONFIG[:no_rc] # ignore rc
|
967
1287
|
|
968
1288
|
if File.file? path
|
969
|
-
|
1289
|
+
if path.end_with?('.rb')
|
1290
|
+
load path
|
1291
|
+
else
|
1292
|
+
::DEBUGGER__::SESSION.add_preset_commands path, File.readlines(path)
|
1293
|
+
end
|
1294
|
+
elsif !rc
|
1295
|
+
warn "Not found: #{path}"
|
970
1296
|
end
|
971
1297
|
}
|
972
1298
|
|
973
1299
|
# given debug commands
|
974
1300
|
if ::DEBUGGER__::CONFIG[:commands]
|
975
1301
|
cmds = ::DEBUGGER__::CONFIG[:commands].split(';;')
|
976
|
-
::DEBUGGER__::SESSION.
|
1302
|
+
::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
|
977
1303
|
end
|
978
1304
|
end
|
979
1305
|
|
@@ -1027,9 +1353,8 @@ module DEBUGGER__
|
|
1027
1353
|
r.join("\n")
|
1028
1354
|
end
|
1029
1355
|
|
1030
|
-
CONFIG = ::DEBUGGER__.parse_argv(ENV['RUBY_DEBUG_OPT'])
|
1031
|
-
|
1032
1356
|
class ::Module
|
1357
|
+
undef method_added
|
1033
1358
|
def method_added mid; end
|
1034
1359
|
def singleton_method_added mid; end
|
1035
1360
|
end
|
@@ -1043,4 +1368,44 @@ module DEBUGGER__
|
|
1043
1368
|
end
|
1044
1369
|
|
1045
1370
|
METHOD_ADDED_TRACKER = self.create_method_added_tracker
|
1371
|
+
|
1372
|
+
SHORT_INSPECT_LENGTH = 40
|
1373
|
+
def self.short_inspect obj, use_short = true
|
1374
|
+
str = obj.inspect
|
1375
|
+
if use_short && str.length > SHORT_INSPECT_LENGTH
|
1376
|
+
str[0...SHORT_INSPECT_LENGTH] + '...'
|
1377
|
+
else
|
1378
|
+
str
|
1379
|
+
end
|
1380
|
+
end
|
1381
|
+
|
1382
|
+
LOG_LEVELS = {
|
1383
|
+
UNKNOWN: 0,
|
1384
|
+
FATAL: 1,
|
1385
|
+
ERROR: 2,
|
1386
|
+
WARN: 3,
|
1387
|
+
INFO: 4,
|
1388
|
+
}.freeze
|
1389
|
+
|
1390
|
+
def self.warn msg
|
1391
|
+
log :WARN, msg
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
def self.info msg
|
1395
|
+
log :INFO, msg
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
def self.log level, msg
|
1399
|
+
lv = LOG_LEVELS[level]
|
1400
|
+
config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
|
1401
|
+
|
1402
|
+
if lv <= config_lv
|
1403
|
+
if level == :WARN
|
1404
|
+
# :WARN on debugger is general information
|
1405
|
+
STDERR.puts "DEBUGGER: #{msg}"
|
1406
|
+
else
|
1407
|
+
STDERR.puts "DEBUGGER (#{level}): #{msg}"
|
1408
|
+
end
|
1409
|
+
end
|
1410
|
+
end
|
1046
1411
|
end
|