debug 1.0.0.beta4 → 1.0.0.beta8
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 +1 -0
- data/CONTRIBUTING.md +239 -0
- data/Gemfile +2 -1
- data/README.md +424 -215
- data/Rakefile +2 -1
- data/TODO.md +0 -6
- data/bin/gentest +22 -0
- data/debug.gemspec +2 -0
- data/exe/rdbg +14 -11
- data/ext/debug/debug.c +9 -8
- data/lib/debug.rb +3 -0
- data/lib/debug/breakpoint.rb +101 -43
- data/lib/debug/client.rb +55 -13
- data/lib/debug/color.rb +76 -0
- data/lib/debug/config.rb +130 -39
- data/lib/debug/console.rb +24 -3
- data/lib/debug/frame_info.rb +63 -31
- data/lib/debug/open.rb +4 -1
- data/lib/debug/open_nonstop.rb +15 -0
- data/lib/debug/server.rb +90 -32
- data/lib/debug/server_dap.rb +607 -0
- data/lib/debug/session.rb +461 -174
- data/lib/debug/source_repository.rb +55 -33
- data/lib/debug/start.rb +5 -0
- data/lib/debug/thread_client.rb +176 -59
- data/lib/debug/version.rb +3 -1
- data/misc/README.md.erb +351 -204
- metadata +23 -4
- data/lib/debug/run.rb +0 -2
data/lib/debug/session.rb
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# skip to load debugger for bundle exec
|
|
4
|
+
return if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
|
5
|
+
|
|
6
|
+
require_relative 'config'
|
|
2
7
|
require_relative 'thread_client'
|
|
3
8
|
require_relative 'source_repository'
|
|
4
9
|
require_relative 'breakpoint'
|
|
5
|
-
|
|
10
|
+
|
|
11
|
+
require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
|
|
6
12
|
|
|
7
13
|
class RubyVM::InstructionSequence
|
|
8
14
|
def traceable_lines_norec lines
|
|
@@ -40,9 +46,15 @@ class RubyVM::InstructionSequence
|
|
|
40
46
|
def last_line
|
|
41
47
|
self.to_a[4][:code_location][2]
|
|
42
48
|
end
|
|
49
|
+
|
|
50
|
+
def first_line
|
|
51
|
+
self.to_a[4][:code_location][0]
|
|
52
|
+
end
|
|
43
53
|
end
|
|
44
54
|
|
|
45
55
|
module DEBUGGER__
|
|
56
|
+
PresetCommand = Struct.new(:commands, :source, :auto_continue)
|
|
57
|
+
|
|
46
58
|
class Session
|
|
47
59
|
def initialize ui
|
|
48
60
|
@ui = ui
|
|
@@ -51,85 +63,29 @@ module DEBUGGER__
|
|
|
51
63
|
# [file, line] => LineBreakpoint
|
|
52
64
|
# "Error" => CatchBreakpoint
|
|
53
65
|
# "Foo#bar" => MethodBreakpoint
|
|
54
|
-
# [:watch,
|
|
66
|
+
# [:watch, ivar] => WatchIVarBreakpoint
|
|
55
67
|
# [:check, expr] => CheckBreakpoint
|
|
56
68
|
@th_clients = {} # {Thread => ThreadClient}
|
|
57
69
|
@q_evt = Queue.new
|
|
58
70
|
@displays = []
|
|
59
71
|
@tc = nil
|
|
60
72
|
@tc_id = 0
|
|
61
|
-
@
|
|
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}
|
|
62
78
|
|
|
63
79
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
|
64
|
-
|
|
80
|
+
unless @management_threads.include? Thread.current
|
|
81
|
+
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
|
82
|
+
end
|
|
65
83
|
}
|
|
66
84
|
@tp_load_script.enable
|
|
67
85
|
|
|
68
86
|
@session_server = Thread.new do
|
|
69
87
|
Thread.current.abort_on_exception = true
|
|
70
|
-
|
|
71
|
-
while evt = @q_evt.pop
|
|
72
|
-
tc, output, ev, *ev_args = evt
|
|
73
|
-
output.each{|str| @ui.puts str}
|
|
74
|
-
|
|
75
|
-
case ev
|
|
76
|
-
when :load
|
|
77
|
-
iseq, src = ev_args
|
|
78
|
-
on_load iseq, src
|
|
79
|
-
tc << :continue
|
|
80
|
-
when :thread_begin
|
|
81
|
-
th = ev_args.shift
|
|
82
|
-
on_thread_begin th
|
|
83
|
-
tc << :continue
|
|
84
|
-
when :suspend
|
|
85
|
-
case ev_args.first
|
|
86
|
-
when :breakpoint
|
|
87
|
-
bp, i = bp_index ev_args[1]
|
|
88
|
-
if bp
|
|
89
|
-
@ui.puts "\nStop by \##{i} #{bp}"
|
|
90
|
-
end
|
|
91
|
-
when :trap
|
|
92
|
-
@ui.puts ''
|
|
93
|
-
@ui.puts "\nStop by #{ev_args[1]}"
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
if @displays.empty?
|
|
97
|
-
wait_command_loop tc
|
|
98
|
-
else
|
|
99
|
-
tc << [:eval, :display, @displays]
|
|
100
|
-
end
|
|
101
|
-
when :result
|
|
102
|
-
case ev_args.first
|
|
103
|
-
when :watch
|
|
104
|
-
bp = ev_args[1]
|
|
105
|
-
@bps[bp.key] = bp
|
|
106
|
-
show_bps bp
|
|
107
|
-
when :try_display
|
|
108
|
-
failed_results = ev_args[1]
|
|
109
|
-
if failed_results.size > 0
|
|
110
|
-
i, msg = failed_results.last
|
|
111
|
-
if i+1 == @displays.size
|
|
112
|
-
@ui.puts "canceled: #{@displays.pop}"
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
when :method_breakpoint
|
|
116
|
-
bp = ev_args[1]
|
|
117
|
-
if bp
|
|
118
|
-
@bps[bp.key] = bp
|
|
119
|
-
show_bps bp
|
|
120
|
-
else
|
|
121
|
-
# can't make a bp
|
|
122
|
-
end
|
|
123
|
-
else
|
|
124
|
-
# ignore
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
wait_command_loop tc
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
ensure
|
|
131
|
-
@bps.each{|k, bp| bp.disable}
|
|
132
|
-
@th_clients.each{|th, thc| thc.close}
|
|
88
|
+
session_server_main
|
|
133
89
|
end
|
|
134
90
|
|
|
135
91
|
@management_threads = [@session_server]
|
|
@@ -138,23 +94,116 @@ module DEBUGGER__
|
|
|
138
94
|
setup_threads
|
|
139
95
|
|
|
140
96
|
@tp_thread_begin = TracePoint.new(:thread_begin){|tp|
|
|
141
|
-
|
|
97
|
+
unless @management_threads.include?(th = Thread.current)
|
|
98
|
+
ThreadClient.current.on_thread_begin th
|
|
99
|
+
end
|
|
142
100
|
}
|
|
143
101
|
@tp_thread_begin.enable
|
|
144
102
|
end
|
|
145
103
|
|
|
146
|
-
def
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
151
112
|
end
|
|
152
113
|
|
|
153
|
-
def
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
158
|
+
end
|
|
159
|
+
when :method_breakpoint, :watch_breakpoint
|
|
160
|
+
bp = ev_args[1]
|
|
161
|
+
if bp
|
|
162
|
+
add_breakpoint(bp)
|
|
163
|
+
show_bps bp
|
|
164
|
+
else
|
|
165
|
+
# can't make a bp
|
|
166
|
+
end
|
|
167
|
+
else
|
|
168
|
+
# ignore
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
wait_command_loop tc
|
|
172
|
+
|
|
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
|
|
184
|
+
end
|
|
185
|
+
|
|
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
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def source iseq
|
|
203
|
+
if !CONFIG[:no_color]
|
|
204
|
+
@sr.get_colored(iseq)
|
|
156
205
|
else
|
|
157
|
-
@sr.get(
|
|
206
|
+
@sr.get(iseq)
|
|
158
207
|
end
|
|
159
208
|
end
|
|
160
209
|
|
|
@@ -181,13 +230,36 @@ module DEBUGGER__
|
|
|
181
230
|
end
|
|
182
231
|
|
|
183
232
|
def wait_command
|
|
184
|
-
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']
|
|
185
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
|
|
186
257
|
else
|
|
187
|
-
|
|
188
|
-
@ui.puts "(rdbg:init) #{line}"
|
|
258
|
+
raise "unexpected input: #{line.inspect}"
|
|
189
259
|
end
|
|
260
|
+
end
|
|
190
261
|
|
|
262
|
+
def process_command line
|
|
191
263
|
if line.empty?
|
|
192
264
|
if @repl_prev_line
|
|
193
265
|
line = @repl_prev_line
|
|
@@ -209,21 +281,25 @@ module DEBUGGER__
|
|
|
209
281
|
# * `s[tep]`
|
|
210
282
|
# * Step in. Resume the program until next breakable point.
|
|
211
283
|
when 's', 'step'
|
|
284
|
+
cancel_auto_continue
|
|
212
285
|
@tc << [:step, :in]
|
|
213
286
|
|
|
214
287
|
# * `n[ext]`
|
|
215
288
|
# * Step over. Resume the program until next line.
|
|
216
289
|
when 'n', 'next'
|
|
290
|
+
cancel_auto_continue
|
|
217
291
|
@tc << [:step, :next]
|
|
218
292
|
|
|
219
293
|
# * `fin[ish]`
|
|
220
294
|
# * Finish this frame. Resume the program until the current frame is finished.
|
|
221
295
|
when 'fin', 'finish'
|
|
296
|
+
cancel_auto_continue
|
|
222
297
|
@tc << [:step, :finish]
|
|
223
298
|
|
|
224
299
|
# * `c[ontinue]`
|
|
225
300
|
# * Resume the program.
|
|
226
301
|
when 'c', 'continue'
|
|
302
|
+
cancel_auto_continue
|
|
227
303
|
@tc << :continue
|
|
228
304
|
|
|
229
305
|
# * `q[uit]` or `Ctrl-D`
|
|
@@ -268,10 +344,14 @@ module DEBUGGER__
|
|
|
268
344
|
# * Set breakpoint on the method `<class>#<name>`.
|
|
269
345
|
# * `b[reak] <expr>.<name>`
|
|
270
346
|
# * Set breakpoint on the method `<expr>.<name>`.
|
|
271
|
-
# * `b[reak] ... if <expr>`
|
|
347
|
+
# * `b[reak] ... if: <expr>`
|
|
272
348
|
# * break if `<expr>` is true at specified location.
|
|
273
|
-
# * `b[reak]
|
|
274
|
-
# * 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.
|
|
275
355
|
# * Note that this feature is super slow.
|
|
276
356
|
when 'b', 'break'
|
|
277
357
|
if arg == nil
|
|
@@ -324,12 +404,12 @@ module DEBUGGER__
|
|
|
324
404
|
end
|
|
325
405
|
return :retry
|
|
326
406
|
|
|
327
|
-
# * `watch
|
|
328
|
-
# * Stop the execution when the result of
|
|
407
|
+
# * `watch @ivar`
|
|
408
|
+
# * Stop the execution when the result of current scope's `@ivar` is changed.
|
|
329
409
|
# * Note that this feature is super slow.
|
|
330
410
|
when 'wat', 'watch'
|
|
331
|
-
if arg
|
|
332
|
-
@tc << [:
|
|
411
|
+
if arg && arg.match?(/\A@\w+/)
|
|
412
|
+
@tc << [:breakpoint, :watch, arg]
|
|
333
413
|
else
|
|
334
414
|
show_bps
|
|
335
415
|
return :retry
|
|
@@ -359,8 +439,25 @@ module DEBUGGER__
|
|
|
359
439
|
|
|
360
440
|
# * `bt` or `backtrace`
|
|
361
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/`.
|
|
362
448
|
when 'bt', 'backtrace'
|
|
363
|
-
|
|
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
|
|
364
461
|
|
|
365
462
|
# * `l[ist]`
|
|
366
463
|
# * Show current frame's source code.
|
|
@@ -407,7 +504,7 @@ module DEBUGGER__
|
|
|
407
504
|
# * `i[nfo]`, `i[nfo] l[ocal[s]]`
|
|
408
505
|
# * Show information about the current frame (local variables)
|
|
409
506
|
# * It includes `self` as `%self` and a return value as `%return`.
|
|
410
|
-
# * `i[nfo] th[read[s]]
|
|
507
|
+
# * `i[nfo] th[read[s]]`
|
|
411
508
|
# * Show all threads (same as `th[read]`).
|
|
412
509
|
when 'i', 'info'
|
|
413
510
|
case arg
|
|
@@ -443,9 +540,7 @@ module DEBUGGER__
|
|
|
443
540
|
case arg
|
|
444
541
|
when /(\d+)/
|
|
445
542
|
if @displays[n = $1.to_i]
|
|
446
|
-
|
|
447
|
-
@displays.delete_at n
|
|
448
|
-
end
|
|
543
|
+
@displays.delete_at n
|
|
449
544
|
end
|
|
450
545
|
@tc << [:eval, :display, @displays]
|
|
451
546
|
when nil
|
|
@@ -455,26 +550,6 @@ module DEBUGGER__
|
|
|
455
550
|
end
|
|
456
551
|
return :retry
|
|
457
552
|
|
|
458
|
-
# * `trace [on|off]`
|
|
459
|
-
# * enable or disable line tracer.
|
|
460
|
-
when 'trace'
|
|
461
|
-
case arg
|
|
462
|
-
when 'on'
|
|
463
|
-
dir = __dir__
|
|
464
|
-
@tracer ||= TracePoint.new(){|tp|
|
|
465
|
-
next if File.dirname(tp.path) == dir
|
|
466
|
-
next if tp.path == '<internal:trace_point>'
|
|
467
|
-
# next if tp.event != :line
|
|
468
|
-
@ui.puts pretty_tp(tp)
|
|
469
|
-
}
|
|
470
|
-
@tracer.enable
|
|
471
|
-
when 'off'
|
|
472
|
-
@tracer && @tracer.disable
|
|
473
|
-
end
|
|
474
|
-
enabled = (@tracer && @tracer.enabled?) ? true : false
|
|
475
|
-
@ui.puts "Trace #{enabled ? 'on' : 'off'}"
|
|
476
|
-
return :retry
|
|
477
|
-
|
|
478
553
|
### Frame control
|
|
479
554
|
|
|
480
555
|
# * `f[rame]`
|
|
@@ -520,6 +595,9 @@ module DEBUGGER__
|
|
|
520
595
|
end
|
|
521
596
|
@tc << [:eval, :call, 'binding.irb']
|
|
522
597
|
|
|
598
|
+
# don't repeat irb command
|
|
599
|
+
@repl_prev_line = nil
|
|
600
|
+
|
|
523
601
|
### Thread control
|
|
524
602
|
|
|
525
603
|
# * `th[read]`
|
|
@@ -537,6 +615,21 @@ module DEBUGGER__
|
|
|
537
615
|
end
|
|
538
616
|
return :retry
|
|
539
617
|
|
|
618
|
+
### Configuration
|
|
619
|
+
# * `config`
|
|
620
|
+
# * Show all configuration with description.
|
|
621
|
+
# * `config <name>`
|
|
622
|
+
# * Show current configuration of <name>.
|
|
623
|
+
# * `config set <name> <val>` or `config <name> = <val>`
|
|
624
|
+
# * Set <name> to <val>.
|
|
625
|
+
# * `config append <name> <val>` or `config <name> << <val>`
|
|
626
|
+
# * Append `<val>` to `<name>` if it is an array.
|
|
627
|
+
# * `config unset <name>`
|
|
628
|
+
# * Set <name> to default.
|
|
629
|
+
when 'config'
|
|
630
|
+
config_command arg
|
|
631
|
+
return :retry
|
|
632
|
+
|
|
540
633
|
### Help
|
|
541
634
|
|
|
542
635
|
# * `h[elp]`
|
|
@@ -568,6 +661,79 @@ module DEBUGGER__
|
|
|
568
661
|
return :retry
|
|
569
662
|
end
|
|
570
663
|
|
|
664
|
+
def config_show key
|
|
665
|
+
key = key.to_sym
|
|
666
|
+
if CONFIG_SET[key]
|
|
667
|
+
v = CONFIG[key]
|
|
668
|
+
kv = "#{key} = #{v.nil? ? '(default)' : v.inspect}"
|
|
669
|
+
desc = CONFIG_SET[key][1]
|
|
670
|
+
line = "%-30s \# %s" % [kv, desc]
|
|
671
|
+
if line.size > SESSION.width
|
|
672
|
+
@ui.puts "\# #{desc}\n#{kv}"
|
|
673
|
+
else
|
|
674
|
+
@ui.puts line
|
|
675
|
+
end
|
|
676
|
+
else
|
|
677
|
+
@ui.puts "Unknown configuration: #{key}. 'config' shows all configurations."
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def config_set key, val, append: false
|
|
682
|
+
if CONFIG_SET[key = key.to_sym]
|
|
683
|
+
begin
|
|
684
|
+
if append
|
|
685
|
+
DEBUGGER__.append_config(key, val)
|
|
686
|
+
else
|
|
687
|
+
DEBUGGER__.set_config({key => val})
|
|
688
|
+
end
|
|
689
|
+
rescue => e
|
|
690
|
+
@ui.puts e.message
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
config_show key
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def config_command arg
|
|
698
|
+
case arg
|
|
699
|
+
when nil
|
|
700
|
+
CONFIG_SET.each do |k, _|
|
|
701
|
+
config_show k
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
when /\Aunset\s+(.+)\z/
|
|
705
|
+
if CONFIG_SET[key = $1.to_sym]
|
|
706
|
+
DEBUGGER__.set_config({key => nil})
|
|
707
|
+
end
|
|
708
|
+
config_show key
|
|
709
|
+
|
|
710
|
+
when /\A(\w+)\s*=\s*(.+)\z/
|
|
711
|
+
config_set $1, $2
|
|
712
|
+
|
|
713
|
+
when /\A\s*set\s+(\w+)\s+(.+)\z/
|
|
714
|
+
config_set $1, $2
|
|
715
|
+
|
|
716
|
+
when /\A(\w+)\s*<<\s*(.+)\z/
|
|
717
|
+
config_set $1, $2, append: true
|
|
718
|
+
|
|
719
|
+
when /\A\s*append\s+(\w+)\s+(.+)\z/
|
|
720
|
+
config_set $1, $2
|
|
721
|
+
|
|
722
|
+
when /\A(\w+)\z/
|
|
723
|
+
config_show $1
|
|
724
|
+
|
|
725
|
+
else
|
|
726
|
+
@ui.puts "Can not parse parameters: #{arg}"
|
|
727
|
+
end
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def cancel_auto_continue
|
|
732
|
+
if @preset_command&.auto_continue
|
|
733
|
+
@preset_command.auto_continue = false
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
|
|
571
737
|
def show_help arg
|
|
572
738
|
DEBUGGER__.helps.each{|cat, cs|
|
|
573
739
|
cs.each{|ws, desc|
|
|
@@ -684,29 +850,37 @@ module DEBUGGER__
|
|
|
684
850
|
end
|
|
685
851
|
end
|
|
686
852
|
|
|
687
|
-
|
|
688
|
-
arg.strip!
|
|
853
|
+
BREAK_KEYWORDS = %w(if: do: pre:).freeze
|
|
689
854
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
855
|
+
def parse_break arg
|
|
856
|
+
mode = :sig
|
|
857
|
+
expr = Hash.new{|h, k| h[k] = []}
|
|
858
|
+
arg.split(' ').each{|w|
|
|
859
|
+
if BREAK_KEYWORDS.any?{|pat| w == pat}
|
|
860
|
+
mode = w[0..-2].to_sym
|
|
861
|
+
else
|
|
862
|
+
expr[mode] << w
|
|
863
|
+
end
|
|
864
|
+
}
|
|
865
|
+
expr.default_proc = nil
|
|
866
|
+
expr.transform_values{|v| v.join(' ')}
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
def repl_add_breakpoint arg
|
|
870
|
+
expr = parse_break arg.strip
|
|
871
|
+
cond = expr[:if]
|
|
872
|
+
cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
|
699
873
|
|
|
700
|
-
case sig
|
|
874
|
+
case expr[:sig]
|
|
701
875
|
when /\A(\d+)\z/
|
|
702
|
-
add_line_breakpoint @tc.location.path, $1.to_i, cond:
|
|
876
|
+
add_line_breakpoint @tc.location.path, $1.to_i, cond: expr[:if], command: cmd
|
|
703
877
|
when /\A(.+)[:\s+](\d+)\z/
|
|
704
|
-
add_line_breakpoint $1, $2.to_i, cond:
|
|
878
|
+
add_line_breakpoint $1, $2.to_i, cond: expr[:if], command: cmd
|
|
705
879
|
when /\A(.+)([\.\#])(.+)\z/
|
|
706
|
-
@tc << [:breakpoint, :method, $1, $2, $3,
|
|
880
|
+
@tc << [:breakpoint, :method, $1, $2, $3, expr[:if], cmd]
|
|
707
881
|
return :noretry
|
|
708
882
|
when nil
|
|
709
|
-
add_check_breakpoint
|
|
883
|
+
add_check_breakpoint expr[:if]
|
|
710
884
|
else
|
|
711
885
|
@ui.puts "Unknown breakpoint format: #{arg}"
|
|
712
886
|
@ui.puts
|
|
@@ -725,6 +899,8 @@ module DEBUGGER__
|
|
|
725
899
|
case
|
|
726
900
|
when th == Thread.current
|
|
727
901
|
# ignore
|
|
902
|
+
when @management_threads.include?(th)
|
|
903
|
+
# ignore
|
|
728
904
|
when @th_clients.has_key?(th)
|
|
729
905
|
thcs << @th_clients[th]
|
|
730
906
|
else
|
|
@@ -748,8 +924,13 @@ module DEBUGGER__
|
|
|
748
924
|
end
|
|
749
925
|
end
|
|
750
926
|
|
|
927
|
+
def managed_thread_clients
|
|
928
|
+
thcs, _unmanaged_ths = update_thread_list
|
|
929
|
+
thcs
|
|
930
|
+
end
|
|
931
|
+
|
|
751
932
|
def thread_switch n
|
|
752
|
-
thcs,
|
|
933
|
+
thcs, _unmanaged_ths = update_thread_list
|
|
753
934
|
|
|
754
935
|
if tc = thcs[n]
|
|
755
936
|
if tc.mode
|
|
@@ -823,6 +1004,7 @@ module DEBUGGER__
|
|
|
823
1004
|
## event
|
|
824
1005
|
|
|
825
1006
|
def on_load iseq, src
|
|
1007
|
+
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
|
|
826
1008
|
@sr.add iseq, src
|
|
827
1009
|
|
|
828
1010
|
pending_line_breakpoints do |bp|
|
|
@@ -835,8 +1017,14 @@ module DEBUGGER__
|
|
|
835
1017
|
# breakpoint management
|
|
836
1018
|
|
|
837
1019
|
def add_breakpoint bp
|
|
1020
|
+
# don't repeat commands that add breakpoints
|
|
1021
|
+
@repl_prev_line = nil
|
|
1022
|
+
|
|
838
1023
|
if @bps.has_key? bp.key
|
|
839
|
-
|
|
1024
|
+
unless bp.duplicable?
|
|
1025
|
+
@ui.puts "duplicated breakpoint: #{bp}"
|
|
1026
|
+
bp.disable
|
|
1027
|
+
end
|
|
840
1028
|
else
|
|
841
1029
|
@bps[bp.key] = bp
|
|
842
1030
|
end
|
|
@@ -867,12 +1055,16 @@ module DEBUGGER__
|
|
|
867
1055
|
def resolve_path file
|
|
868
1056
|
File.realpath(File.expand_path(file))
|
|
869
1057
|
rescue Errno::ENOENT
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1058
|
+
case file
|
|
1059
|
+
when '-e', '-'
|
|
1060
|
+
return file
|
|
1061
|
+
else
|
|
1062
|
+
$LOAD_PATH.each do |lp|
|
|
1063
|
+
libpath = File.join(lp, file)
|
|
1064
|
+
return File.realpath(libpath)
|
|
1065
|
+
rescue Errno::ENOENT
|
|
1066
|
+
# next
|
|
1067
|
+
end
|
|
876
1068
|
end
|
|
877
1069
|
|
|
878
1070
|
raise
|
|
@@ -881,6 +1073,7 @@ module DEBUGGER__
|
|
|
881
1073
|
def add_line_breakpoint file, line, **kw
|
|
882
1074
|
file = resolve_path(file)
|
|
883
1075
|
bp = LineBreakpoint.new(file, line, **kw)
|
|
1076
|
+
|
|
884
1077
|
add_breakpoint bp
|
|
885
1078
|
rescue Errno::ENOENT => e
|
|
886
1079
|
@ui.puts e.message
|
|
@@ -905,7 +1098,7 @@ module DEBUGGER__
|
|
|
905
1098
|
when MethodBreakpoint
|
|
906
1099
|
if bp.method.nil?
|
|
907
1100
|
if bp.sig_method_name == mid.to_s
|
|
908
|
-
bp.try_enable(
|
|
1101
|
+
bp.try_enable(added: true)
|
|
909
1102
|
end
|
|
910
1103
|
end
|
|
911
1104
|
|
|
@@ -917,6 +1110,29 @@ module DEBUGGER__
|
|
|
917
1110
|
end
|
|
918
1111
|
end
|
|
919
1112
|
end
|
|
1113
|
+
|
|
1114
|
+
def width
|
|
1115
|
+
@ui.width
|
|
1116
|
+
end
|
|
1117
|
+
|
|
1118
|
+
def check_forked
|
|
1119
|
+
unless @session_server.status
|
|
1120
|
+
# TODO: Support it
|
|
1121
|
+
raise 'DEBUGGER: stop at forked process is not supported yet.'
|
|
1122
|
+
end
|
|
1123
|
+
end
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
class UI_Base
|
|
1127
|
+
def event type, *args
|
|
1128
|
+
case type
|
|
1129
|
+
when :suspend_bp
|
|
1130
|
+
i, bp = *args
|
|
1131
|
+
puts "\nStop by \##{i} #{bp}" if bp
|
|
1132
|
+
when :suspend_trap
|
|
1133
|
+
puts "\nStop by #{args.first}"
|
|
1134
|
+
end
|
|
1135
|
+
end
|
|
920
1136
|
end
|
|
921
1137
|
|
|
922
1138
|
# manual configuration methods
|
|
@@ -940,7 +1156,7 @@ module DEBUGGER__
|
|
|
940
1156
|
when dir_prefix
|
|
941
1157
|
when %r{rubygems/core_ext/kernel_require\.rb}
|
|
942
1158
|
else
|
|
943
|
-
return loc
|
|
1159
|
+
return loc if loc.absolute_path
|
|
944
1160
|
end
|
|
945
1161
|
end
|
|
946
1162
|
nil
|
|
@@ -948,43 +1164,71 @@ module DEBUGGER__
|
|
|
948
1164
|
|
|
949
1165
|
# start methods
|
|
950
1166
|
|
|
951
|
-
def self.
|
|
1167
|
+
def self.start nonstop: false, **kw
|
|
952
1168
|
set_config(kw)
|
|
953
1169
|
|
|
954
|
-
|
|
955
|
-
|
|
1170
|
+
unless defined? SESSION
|
|
1171
|
+
require_relative 'console'
|
|
1172
|
+
initialize_session UI_Console.new
|
|
1173
|
+
end
|
|
956
1174
|
|
|
957
|
-
|
|
958
|
-
ThreadClient.current.on_trap :SIGINT
|
|
959
|
-
}
|
|
1175
|
+
setup_initial_suspend unless nonstop
|
|
960
1176
|
end
|
|
961
1177
|
|
|
962
|
-
def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_path: nil, sock_dir: nil, **kw
|
|
1178
|
+
def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
|
963
1179
|
set_config(kw)
|
|
964
1180
|
|
|
965
1181
|
if port
|
|
966
|
-
open_tcp host: host, port: port
|
|
1182
|
+
open_tcp host: host, port: port, nonstop: nonstop
|
|
967
1183
|
else
|
|
968
|
-
open_unix sock_path: sock_path, sock_dir: sock_dir
|
|
1184
|
+
open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
|
|
969
1185
|
end
|
|
970
1186
|
end
|
|
971
1187
|
|
|
972
|
-
def self.open_tcp host: nil, port:, **kw
|
|
1188
|
+
def self.open_tcp host: nil, port:, nonstop: false, **kw
|
|
973
1189
|
set_config(kw)
|
|
974
1190
|
require_relative 'server'
|
|
975
|
-
|
|
1191
|
+
|
|
1192
|
+
if defined? SESSION
|
|
1193
|
+
SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
|
|
1194
|
+
else
|
|
1195
|
+
initialize_session UI_TcpServer.new(host: host, port: port)
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
setup_initial_suspend unless nonstop
|
|
976
1199
|
end
|
|
977
1200
|
|
|
978
|
-
def self.open_unix sock_path: nil, sock_dir: nil, **kw
|
|
1201
|
+
def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
|
979
1202
|
set_config(kw)
|
|
980
1203
|
require_relative 'server'
|
|
981
|
-
|
|
1204
|
+
|
|
1205
|
+
if defined? SESSION
|
|
1206
|
+
SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
|
1207
|
+
else
|
|
1208
|
+
initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
setup_initial_suspend unless nonstop
|
|
982
1212
|
end
|
|
983
1213
|
|
|
984
1214
|
# boot utilities
|
|
985
1215
|
|
|
1216
|
+
def self.setup_initial_suspend
|
|
1217
|
+
if !::DEBUGGER__::CONFIG[:nonstop]
|
|
1218
|
+
if loc = ::DEBUGGER__.require_location
|
|
1219
|
+
# require 'debug/console' or 'debug'
|
|
1220
|
+
add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
|
|
1221
|
+
else
|
|
1222
|
+
# -r
|
|
1223
|
+
add_line_breakpoint $0, 1, oneshot: true, hook_call: false
|
|
1224
|
+
end
|
|
1225
|
+
end
|
|
1226
|
+
end
|
|
1227
|
+
|
|
986
1228
|
class << self
|
|
987
1229
|
define_method :initialize_session do |ui|
|
|
1230
|
+
DEBUGGER__.warn "Session start (pid: #{Process.pid})"
|
|
1231
|
+
|
|
988
1232
|
::DEBUGGER__.const_set(:SESSION, Session.new(ui))
|
|
989
1233
|
|
|
990
1234
|
# default breakpoints
|
|
@@ -992,18 +1236,18 @@ module DEBUGGER__
|
|
|
992
1236
|
# ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
|
|
993
1237
|
|
|
994
1238
|
Binding.module_eval do
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
end
|
|
1239
|
+
def break pre: nil, do: nil
|
|
1240
|
+
return unless SESSION.active?
|
|
998
1241
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
add_line_breakpoint $0, 1, oneshot: true, hook_call: false
|
|
1242
|
+
if pre || (do_expr = binding.local_variable_get(:do))
|
|
1243
|
+
cmds = ['binding.break', pre, do_expr]
|
|
1244
|
+
end
|
|
1245
|
+
|
|
1246
|
+
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
|
|
1247
|
+
true
|
|
1006
1248
|
end
|
|
1249
|
+
alias b break
|
|
1250
|
+
# alias bp break
|
|
1007
1251
|
end
|
|
1008
1252
|
|
|
1009
1253
|
load_rc
|
|
@@ -1011,27 +1255,29 @@ module DEBUGGER__
|
|
|
1011
1255
|
end
|
|
1012
1256
|
|
|
1013
1257
|
def self.load_rc
|
|
1014
|
-
[
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
# debug commands file
|
|
1021
|
-
[::DEBUGGER__::CONFIG[:init_script],
|
|
1022
|
-
'./.rdbgrc',
|
|
1023
|
-
File.expand_path('~/.rdbgrc')].each{|path|
|
|
1258
|
+
[[File.expand_path('~/.rdbgrc'), true],
|
|
1259
|
+
[File.expand_path('~/.rdbgrc.rb'), true],
|
|
1260
|
+
# ['./.rdbgrc', true], # disable because of security concern
|
|
1261
|
+
[::DEBUGGER__::CONFIG[:init_script], false],
|
|
1262
|
+
].each{|(path, rc)|
|
|
1024
1263
|
next unless path
|
|
1264
|
+
next if rc && ::DEBUGGER__::CONFIG[:no_rc] # ignore rc
|
|
1025
1265
|
|
|
1026
1266
|
if File.file? path
|
|
1027
|
-
|
|
1267
|
+
if path.end_with?('.rb')
|
|
1268
|
+
load path
|
|
1269
|
+
else
|
|
1270
|
+
::DEBUGGER__::SESSION.add_preset_commands path, File.readlines(path)
|
|
1271
|
+
end
|
|
1272
|
+
elsif !rc
|
|
1273
|
+
warn "Not found: #{path}"
|
|
1028
1274
|
end
|
|
1029
1275
|
}
|
|
1030
1276
|
|
|
1031
1277
|
# given debug commands
|
|
1032
1278
|
if ::DEBUGGER__::CONFIG[:commands]
|
|
1033
1279
|
cmds = ::DEBUGGER__::CONFIG[:commands].split(';;')
|
|
1034
|
-
::DEBUGGER__::SESSION.
|
|
1280
|
+
::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
|
|
1035
1281
|
end
|
|
1036
1282
|
end
|
|
1037
1283
|
|
|
@@ -1086,6 +1332,7 @@ module DEBUGGER__
|
|
|
1086
1332
|
end
|
|
1087
1333
|
|
|
1088
1334
|
class ::Module
|
|
1335
|
+
undef method_added
|
|
1089
1336
|
def method_added mid; end
|
|
1090
1337
|
def singleton_method_added mid; end
|
|
1091
1338
|
end
|
|
@@ -1099,4 +1346,44 @@ module DEBUGGER__
|
|
|
1099
1346
|
end
|
|
1100
1347
|
|
|
1101
1348
|
METHOD_ADDED_TRACKER = self.create_method_added_tracker
|
|
1349
|
+
|
|
1350
|
+
SHORT_INSPECT_LENGTH = 40
|
|
1351
|
+
def self.short_inspect obj, use_short = true
|
|
1352
|
+
str = obj.inspect
|
|
1353
|
+
if use_short && str.length > SHORT_INSPECT_LENGTH
|
|
1354
|
+
str[0...SHORT_INSPECT_LENGTH] + '...'
|
|
1355
|
+
else
|
|
1356
|
+
str
|
|
1357
|
+
end
|
|
1358
|
+
end
|
|
1359
|
+
|
|
1360
|
+
LOG_LEVELS = {
|
|
1361
|
+
UNKNOWN: 0,
|
|
1362
|
+
FATAL: 1,
|
|
1363
|
+
ERROR: 2,
|
|
1364
|
+
WARN: 3,
|
|
1365
|
+
INFO: 4,
|
|
1366
|
+
}.freeze
|
|
1367
|
+
|
|
1368
|
+
def self.warn msg
|
|
1369
|
+
log :WARN, msg
|
|
1370
|
+
end
|
|
1371
|
+
|
|
1372
|
+
def self.info msg
|
|
1373
|
+
log :INFO, msg
|
|
1374
|
+
end
|
|
1375
|
+
|
|
1376
|
+
def self.log level, msg
|
|
1377
|
+
lv = LOG_LEVELS[level]
|
|
1378
|
+
config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
|
|
1379
|
+
|
|
1380
|
+
if lv <= config_lv
|
|
1381
|
+
if level == :WARN
|
|
1382
|
+
# :WARN on debugger is general information
|
|
1383
|
+
STDERR.puts "DEBUGGER: #{msg}"
|
|
1384
|
+
else
|
|
1385
|
+
STDERR.puts "DEBUGGER (#{level}): #{msg}"
|
|
1386
|
+
end
|
|
1387
|
+
end
|
|
1388
|
+
end
|
|
1102
1389
|
end
|