debug 0.2.1 → 1.0.0.alpha0
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/.gitignore +0 -1
- data/LICENSE.txt +19 -20
- data/README.md +142 -28
- data/Rakefile +9 -0
- data/debug.gemspec +20 -10
- data/exe/rdbg +3 -0
- data/lib/debug/bp.vim +68 -0
- data/lib/debug/breakpoint.rb +97 -0
- data/lib/debug/client.rb +135 -0
- data/lib/debug/config.rb +26 -0
- data/lib/debug/repl.rb +67 -0
- data/lib/debug/server.rb +119 -0
- data/lib/debug/session.rb +598 -0
- data/lib/debug/source_repository.rb +20 -0
- data/lib/debug/tcpserver.rb +23 -0
- data/lib/debug/thread_client.rb +402 -0
- data/lib/debug/unixserver.rb +19 -0
- data/lib/debug/version.rb +3 -0
- data/lib/debug.rb +11 -1116
- metadata +61 -21
- data/Gemfile +0 -8
@@ -0,0 +1,598 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'pp'
|
3
|
+
require 'debug_inspector'
|
4
|
+
require 'iseq_collector'
|
5
|
+
|
6
|
+
require_relative 'source_repository'
|
7
|
+
require_relative 'breakpoint'
|
8
|
+
require_relative 'thread_client'
|
9
|
+
|
10
|
+
class RubyVM::InstructionSequence
|
11
|
+
def traceable_lines_norec lines
|
12
|
+
code = self.to_a[13]
|
13
|
+
line = 0
|
14
|
+
code.each{|e|
|
15
|
+
case e
|
16
|
+
when Integer
|
17
|
+
line = e
|
18
|
+
when Symbol
|
19
|
+
if /\ARUBY_EVENT_/ =~ e.to_s
|
20
|
+
lines[line] = [e, *lines[line]]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def traceable_lines_rec lines
|
27
|
+
self.each_child{|ci| ci.traceable_lines_rec(lines)}
|
28
|
+
traceable_lines_norec lines
|
29
|
+
end
|
30
|
+
|
31
|
+
def type
|
32
|
+
self.to_a[9]
|
33
|
+
end
|
34
|
+
|
35
|
+
def argc
|
36
|
+
self.to_a[4][:arg_size]
|
37
|
+
end
|
38
|
+
|
39
|
+
def locals
|
40
|
+
self.to_a[10]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module DEBUGGER__
|
45
|
+
class Session
|
46
|
+
def initialize ui
|
47
|
+
@ui = ui
|
48
|
+
@sr = SourceRepository.new
|
49
|
+
@reserved_bps = []
|
50
|
+
@bps = {} # [file, line] => LineBreakpoint || "Error" => CatchBreakpoint
|
51
|
+
@th_clients = {} # {Thread => ThreadClient}
|
52
|
+
@q_evt = Queue.new
|
53
|
+
@displays = []
|
54
|
+
@tc = nil
|
55
|
+
|
56
|
+
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
57
|
+
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
58
|
+
}.enable
|
59
|
+
|
60
|
+
@session_server = Thread.new do
|
61
|
+
Thread.current.abort_on_exception = true
|
62
|
+
|
63
|
+
while evt = @q_evt.pop
|
64
|
+
tc, output, ev, *ev_args = evt
|
65
|
+
output.each{|str| @ui.puts str}
|
66
|
+
|
67
|
+
case ev
|
68
|
+
when :load
|
69
|
+
iseq, src = ev_args
|
70
|
+
on_load iseq, src
|
71
|
+
tc << :continue
|
72
|
+
when :suspend
|
73
|
+
if @displays.empty?
|
74
|
+
wait_command_loop tc
|
75
|
+
else
|
76
|
+
tc << [:eval, :display, @displays]
|
77
|
+
end
|
78
|
+
when :result
|
79
|
+
wait_command_loop tc
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@management_threads = [@session_server]
|
85
|
+
|
86
|
+
setup_threads
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_reader :management_threads
|
90
|
+
|
91
|
+
def source path
|
92
|
+
@sr.get(path)
|
93
|
+
end
|
94
|
+
|
95
|
+
def inspect
|
96
|
+
"DEBUGGER__::SESSION"
|
97
|
+
end
|
98
|
+
|
99
|
+
def wait_command_loop tc
|
100
|
+
@tc = tc
|
101
|
+
stop_all_threads do
|
102
|
+
loop do
|
103
|
+
case wait_command
|
104
|
+
when :retry
|
105
|
+
# nothing
|
106
|
+
else
|
107
|
+
break
|
108
|
+
end
|
109
|
+
rescue Interrupt
|
110
|
+
retry
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def wait_command
|
116
|
+
line = @ui.readline
|
117
|
+
|
118
|
+
if line.empty?
|
119
|
+
if @repl_prev_line
|
120
|
+
line = @repl_prev_line
|
121
|
+
else
|
122
|
+
return :retry
|
123
|
+
end
|
124
|
+
else
|
125
|
+
@repl_prev_line = line
|
126
|
+
end
|
127
|
+
|
128
|
+
/([^\s]+)(?:\s+(.+))?/ =~ line
|
129
|
+
cmd, arg = $1, $2
|
130
|
+
|
131
|
+
# p cmd: [cmd, *arg]
|
132
|
+
|
133
|
+
case cmd
|
134
|
+
|
135
|
+
# control
|
136
|
+
when 's', 'step'
|
137
|
+
@tc << [:step, :in]
|
138
|
+
when 'n', 'next'
|
139
|
+
@tc << [:step, :next]
|
140
|
+
when 'fin', 'finish'
|
141
|
+
@tc << [:step, :finish]
|
142
|
+
when 'c', 'continue'
|
143
|
+
@tc << :continue
|
144
|
+
when 'q', 'quit'
|
145
|
+
if ask 'Really quit?'
|
146
|
+
@ui.quit arg.to_i
|
147
|
+
@tc << :continue
|
148
|
+
else
|
149
|
+
return :retry
|
150
|
+
end
|
151
|
+
when 'kill'
|
152
|
+
if ask 'Really quit?'
|
153
|
+
exit! (arg || 1).to_i
|
154
|
+
else
|
155
|
+
return :retry
|
156
|
+
end
|
157
|
+
|
158
|
+
# breakpoints
|
159
|
+
when 'b', 'break'
|
160
|
+
if arg == nil
|
161
|
+
show_bps
|
162
|
+
else
|
163
|
+
bp = repl_add_breakpoint arg
|
164
|
+
show_bps bp if bp
|
165
|
+
end
|
166
|
+
return :retry
|
167
|
+
when 'bv'
|
168
|
+
h = Hash.new{|h, k| h[k] = []}
|
169
|
+
@bps.each{|key, bp|
|
170
|
+
if LineBreakpoint === bp
|
171
|
+
h[bp.path] << {lnum: bp.line}
|
172
|
+
end
|
173
|
+
}
|
174
|
+
if h.empty?
|
175
|
+
# TODO: clean?
|
176
|
+
else
|
177
|
+
open(".rdb_breakpoints.json", 'w'){|f| JSON.dump(h, f)}
|
178
|
+
end
|
179
|
+
|
180
|
+
vimsrc = File.join(__dir__, 'bp.vim')
|
181
|
+
system("vim -R -S #{vimsrc} #{@tc.location.path}")
|
182
|
+
|
183
|
+
if File.exist?(".rdb_breakpoints.json")
|
184
|
+
pp JSON.load(File.read(".rdb_breakpoints.json"))
|
185
|
+
end
|
186
|
+
|
187
|
+
return :retry
|
188
|
+
when 'catch'
|
189
|
+
if arg
|
190
|
+
bp = add_catch_breakpoint arg
|
191
|
+
show_bps bp if bp
|
192
|
+
end
|
193
|
+
return :retry
|
194
|
+
when 'del', 'delete'
|
195
|
+
bp =
|
196
|
+
case arg
|
197
|
+
when nil
|
198
|
+
show_bps
|
199
|
+
if ask "Remove all breakpoints?", 'N'
|
200
|
+
delete_breakpoint
|
201
|
+
end
|
202
|
+
when /\d+/
|
203
|
+
delete_breakpoint arg.to_i
|
204
|
+
else
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
@ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
|
208
|
+
return :retry
|
209
|
+
|
210
|
+
# evaluate
|
211
|
+
when 'p'
|
212
|
+
@tc << [:eval, :p, arg.to_s]
|
213
|
+
when 'pp'
|
214
|
+
@tc << [:eval, :pp, arg.to_s]
|
215
|
+
when 'e', 'eval', 'call'
|
216
|
+
@tc << [:eval, :call, arg]
|
217
|
+
when 'irb'
|
218
|
+
@tc << [:eval, :call, 'binding.irb']
|
219
|
+
|
220
|
+
# evaluate/frame selector
|
221
|
+
when 'up'
|
222
|
+
@tc << [:frame, :up]
|
223
|
+
when 'down'
|
224
|
+
@tc << [:frame, :down]
|
225
|
+
when 'frame', 'f'
|
226
|
+
@tc << [:frame, :set, arg]
|
227
|
+
|
228
|
+
# information
|
229
|
+
when 'bt', 'backtrace'
|
230
|
+
@tc << [:show, :backtrace]
|
231
|
+
when 'list'
|
232
|
+
@tc << [:show, :list]
|
233
|
+
when 'info'
|
234
|
+
case arg
|
235
|
+
when 'l', 'local', 'locals'
|
236
|
+
@tc << [:show, :locals]
|
237
|
+
when 'i', 'instance', 'ivars'
|
238
|
+
@tc << [:show, :ivars]
|
239
|
+
else
|
240
|
+
@ui.puts "unknown info argument: #{arg}"
|
241
|
+
return :retry
|
242
|
+
end
|
243
|
+
when 'display'
|
244
|
+
@displays << arg if arg && !arg.empty?
|
245
|
+
@tc << [:eval, :display, @displays]
|
246
|
+
when 'undisplay'
|
247
|
+
case arg
|
248
|
+
when /(\d+)/
|
249
|
+
if @displays[n = $1.to_i]
|
250
|
+
if ask "clear \##{n} #{@displays[n]}?"
|
251
|
+
@displays.delete_at n
|
252
|
+
end
|
253
|
+
end
|
254
|
+
@tc << [:eval, :display, @displays]
|
255
|
+
when nil
|
256
|
+
if ask "clear all?", 'N'
|
257
|
+
@displays.clear
|
258
|
+
end
|
259
|
+
end
|
260
|
+
return :retry
|
261
|
+
|
262
|
+
# trace
|
263
|
+
when 'trace'
|
264
|
+
case arg
|
265
|
+
when 'on'
|
266
|
+
@tracer ||= TracePoint.new(){|tp|
|
267
|
+
next if tp.path == __FILE__
|
268
|
+
next if tp.path == '<internal:trace_point>'
|
269
|
+
# next if tp.event != :line
|
270
|
+
@ui.puts pretty_tp(tp)
|
271
|
+
}
|
272
|
+
@tracer.enable
|
273
|
+
when 'off'
|
274
|
+
@tracer && @tracer.disable
|
275
|
+
else
|
276
|
+
enabled = (@tracer && @tracer.enabled?) ? true : false
|
277
|
+
@ui.puts "Trace #{enabled ? 'on' : 'off'}"
|
278
|
+
end
|
279
|
+
return :retry
|
280
|
+
|
281
|
+
# threads
|
282
|
+
when 'th', 'thread'
|
283
|
+
case arg
|
284
|
+
when nil, 'list', 'l'
|
285
|
+
thread_list
|
286
|
+
when /(\d+)/
|
287
|
+
thread_switch $1.to_i
|
288
|
+
else
|
289
|
+
@ui.puts "unknown thread command: #{arg}"
|
290
|
+
end
|
291
|
+
return :retry
|
292
|
+
|
293
|
+
else
|
294
|
+
@ui.puts "unknown command: #{line}"
|
295
|
+
@repl_prev_line = nil
|
296
|
+
return :retry
|
297
|
+
end
|
298
|
+
|
299
|
+
rescue Interrupt
|
300
|
+
return :retry
|
301
|
+
rescue SystemExit
|
302
|
+
raise
|
303
|
+
rescue Exception => e
|
304
|
+
@ui.puts "[REPL ERROR] #{e.inspect}"
|
305
|
+
@ui.puts e.backtrace.map{|e| ' ' + e}
|
306
|
+
return :retry
|
307
|
+
end
|
308
|
+
|
309
|
+
def ask msg, default = 'Y'
|
310
|
+
opts = '[y/n]'.tr(default.downcase, default)
|
311
|
+
input = @ui.ask("#{msg} #{opts} ")
|
312
|
+
input = default if input.empty?
|
313
|
+
case input
|
314
|
+
when 'y', 'Y'
|
315
|
+
true
|
316
|
+
else
|
317
|
+
false
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def msig klass, receiver
|
322
|
+
if klass.singleton_class?
|
323
|
+
"#{receiver}."
|
324
|
+
else
|
325
|
+
"#{klass}#"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def pretty_tp tp
|
330
|
+
loc = "#{tp.path}:#{tp.lineno}"
|
331
|
+
level = caller.size
|
332
|
+
|
333
|
+
info =
|
334
|
+
case tp.event
|
335
|
+
when :line
|
336
|
+
"line at #{loc}"
|
337
|
+
when :call, :c_call
|
338
|
+
klass = tp.defined_class
|
339
|
+
"#{tp.event} #{msig(klass, tp.self)}#{tp.method_id} at #{loc}"
|
340
|
+
when :return, :c_return
|
341
|
+
klass = tp.defined_class
|
342
|
+
"#{tp.event} #{msig(klass, tp.self)}#{tp.method_id} => #{tp.return_value.inspect} at #{loc}"
|
343
|
+
when :b_call
|
344
|
+
"b_call at #{loc}"
|
345
|
+
when :b_return
|
346
|
+
"b_return => #{tp.return_value} at #{loc}"
|
347
|
+
when :class
|
348
|
+
"class #{tp.self} at #{loc}"
|
349
|
+
when :end
|
350
|
+
"class #{tp.self} end at #{loc}"
|
351
|
+
else
|
352
|
+
"#{tp.event} at #{loc}"
|
353
|
+
end
|
354
|
+
|
355
|
+
case tp.event
|
356
|
+
when :call, :b_call, :return, :b_return, :class, :end
|
357
|
+
level -= 1
|
358
|
+
end
|
359
|
+
|
360
|
+
"Tracing:#{' ' * level} #{info}"
|
361
|
+
rescue => e
|
362
|
+
p e
|
363
|
+
pp e.backtrace
|
364
|
+
exit!
|
365
|
+
end
|
366
|
+
|
367
|
+
def show_bps specified_bp = nil
|
368
|
+
@bps.each_with_index{|(key, bp), i|
|
369
|
+
if !specified_bp || bp == specified_bp
|
370
|
+
@ui.puts "#%d %s" % [i, bp.to_s]
|
371
|
+
end
|
372
|
+
}
|
373
|
+
end
|
374
|
+
|
375
|
+
def thread_list
|
376
|
+
thcs, unmanaged_ths = update_thread_list
|
377
|
+
thcs.each_with_index{|thc, i|
|
378
|
+
@ui.puts "#{@tc == thc ? "--> " : " "}\##{i} #{thc}"
|
379
|
+
}
|
380
|
+
|
381
|
+
if !unmanaged_ths.empty?
|
382
|
+
@ui.puts "The following threads are not managed yet by the debugger:"
|
383
|
+
unmanaged_ths.each{|th|
|
384
|
+
@ui.puts " " + th.to_s
|
385
|
+
}
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
def thread_switch n
|
390
|
+
if th = @th_clients.keys[n]
|
391
|
+
@tc = @th_clients[th]
|
392
|
+
end
|
393
|
+
thread_list
|
394
|
+
end
|
395
|
+
|
396
|
+
def update_thread_list
|
397
|
+
list = Thread.list
|
398
|
+
thcs = []
|
399
|
+
unmanaged = []
|
400
|
+
|
401
|
+
list.each{|th|
|
402
|
+
case
|
403
|
+
when th == Thread.current
|
404
|
+
# ignore
|
405
|
+
when @th_clients.has_key?(th)
|
406
|
+
thcs << @th_clients[th]
|
407
|
+
else
|
408
|
+
unmanaged << th
|
409
|
+
end
|
410
|
+
}
|
411
|
+
return thcs, unmanaged
|
412
|
+
end
|
413
|
+
|
414
|
+
def delete_breakpoint arg = nil
|
415
|
+
case arg
|
416
|
+
when nil
|
417
|
+
@bps.each{|key, bp| bp.disable}
|
418
|
+
@bps.clear
|
419
|
+
else
|
420
|
+
if bp = @bps[key = @bps.keys[arg]]
|
421
|
+
bp.disable
|
422
|
+
@bps.delete key
|
423
|
+
return [arg, bp]
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
def repl_add_breakpoint arg
|
429
|
+
arg.strip!
|
430
|
+
|
431
|
+
if /(.+?)\s+if\s+(.+)\z/ =~ arg
|
432
|
+
sig = $1
|
433
|
+
cond = $2
|
434
|
+
else
|
435
|
+
sig = arg
|
436
|
+
end
|
437
|
+
|
438
|
+
case sig
|
439
|
+
when /\A(\d+)\z/
|
440
|
+
add_line_breakpoint @tc.location.path, $1.to_i, cond
|
441
|
+
when /\A(.+):(\d+)\z/
|
442
|
+
add_line_breakpoint $1, $2.to_i, cond
|
443
|
+
when /\A(.+)[\.\#](.+)\z/
|
444
|
+
add_method_breakpoint arg, cond
|
445
|
+
else
|
446
|
+
raise "unknown breakpoint format: #{arg}"
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def break? file, line
|
451
|
+
@bps.has_key? [file, line]
|
452
|
+
end
|
453
|
+
|
454
|
+
def setup_threads
|
455
|
+
stop_all_threads do
|
456
|
+
Thread.list.each{|th|
|
457
|
+
@th_clients[th] = ThreadClient.new(@q_evt, Queue.new, th)
|
458
|
+
}
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
def thread_client
|
463
|
+
thr = Thread.current
|
464
|
+
@th_clients[thr] ||= ThreadClient.new(@q_evt, Queue.new)
|
465
|
+
end
|
466
|
+
|
467
|
+
def stop_all_threads
|
468
|
+
current = Thread.current
|
469
|
+
|
470
|
+
if Thread.list.size > 1
|
471
|
+
TracePoint.new(:line) do
|
472
|
+
th = Thread.current
|
473
|
+
if current == th || @management_threads.include?(th)
|
474
|
+
next
|
475
|
+
else
|
476
|
+
tc = ThreadClient.current
|
477
|
+
tc.on_pause
|
478
|
+
end
|
479
|
+
end.enable do
|
480
|
+
yield
|
481
|
+
ensure
|
482
|
+
@th_clients.each{|thr, tc|
|
483
|
+
case thr
|
484
|
+
when current, (@tc && @tc.thread)
|
485
|
+
next
|
486
|
+
else
|
487
|
+
tc << :continue if thr != Thread.current
|
488
|
+
end
|
489
|
+
}
|
490
|
+
end
|
491
|
+
else
|
492
|
+
yield
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
## event
|
497
|
+
|
498
|
+
def on_load iseq, src
|
499
|
+
@sr.add iseq, src
|
500
|
+
@reserved_bps.each{|(path, line, cond)|
|
501
|
+
if path == iseq.absolute_path
|
502
|
+
add_line_breakpoint(path, line, cond)
|
503
|
+
end
|
504
|
+
}
|
505
|
+
end
|
506
|
+
|
507
|
+
# configuration
|
508
|
+
|
509
|
+
def add_catch_breakpoint arg
|
510
|
+
bp = CatchBreakpoint.new(arg)
|
511
|
+
@bps[bp.key] = bp
|
512
|
+
bp
|
513
|
+
end
|
514
|
+
|
515
|
+
def add_line_breakpoint_exact iseq, events, file, line, cond
|
516
|
+
if @bps[[file, line]]
|
517
|
+
return nil # duplicated
|
518
|
+
end
|
519
|
+
|
520
|
+
bp = case
|
521
|
+
when events.include?(:RUBY_EVENT_CALL)
|
522
|
+
# "def foo" line set bp on the beggining of method foo
|
523
|
+
LineBreakpoint.new(:call, iseq, line, cond)
|
524
|
+
when events.include?(:RUBY_EVENT_LINE)
|
525
|
+
LineBreakpoint.new(:line, iseq, line, cond)
|
526
|
+
when events.include?(:RUBY_EVENT_RETURN)
|
527
|
+
LineBreakpoint.new(:return, iseq, line, cond)
|
528
|
+
when events.include?(:RUBY_EVENT_B_RETURN)
|
529
|
+
LineBreakpoint.new(:b_return, iseq, line, cond)
|
530
|
+
when events.include?(:RUBY_EVENT_END)
|
531
|
+
LineBreakpoint.new(:end, iseq, line, cond)
|
532
|
+
else
|
533
|
+
nil
|
534
|
+
end
|
535
|
+
@bps[bp.key] = bp if bp
|
536
|
+
end
|
537
|
+
|
538
|
+
NearestISeq = Struct.new(:iseq, :line, :events)
|
539
|
+
|
540
|
+
def add_line_breakpoint_nearest file, line, cond
|
541
|
+
nearest = nil # NearestISeq
|
542
|
+
|
543
|
+
ObjectSpace.each_iseq{|iseq|
|
544
|
+
if iseq.absolute_path == file && iseq.first_lineno <= line
|
545
|
+
iseq.traceable_lines_norec(line_events = {})
|
546
|
+
lines = line_events.keys.sort
|
547
|
+
|
548
|
+
if !lines.empty? && lines.last >= line
|
549
|
+
nline = lines.bsearch{|l| line <= l}
|
550
|
+
events = line_events[nline]
|
551
|
+
|
552
|
+
if !nearest
|
553
|
+
nearest = NearestISeq.new(iseq, nline, events)
|
554
|
+
else
|
555
|
+
if nearest.iseq.first_lineno <= iseq.first_lineno
|
556
|
+
if (nearest.line > line && !nearest.events.include?(:RUBY_EVENT_CALL)) ||
|
557
|
+
events.include?(:RUBY_EVENT_CALL)
|
558
|
+
nearest = NearestISeq.new(iseq, nline, events)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
}
|
565
|
+
|
566
|
+
if nearest
|
567
|
+
add_line_breakpoint_exact nearest.iseq, nearest.events, file, nearest.line, cond
|
568
|
+
else
|
569
|
+
return nil
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
def resolve_path file
|
574
|
+
File.realpath(File.expand_path(file))
|
575
|
+
rescue Errno::ENOENT
|
576
|
+
file
|
577
|
+
end
|
578
|
+
|
579
|
+
def add_line_breakpoint file, line, cond = nil
|
580
|
+
file = resolve_path(file)
|
581
|
+
bp = add_line_breakpoint_nearest file, line, cond
|
582
|
+
@reserved_bps << [file, line, cond] unless bp
|
583
|
+
bp
|
584
|
+
end
|
585
|
+
|
586
|
+
def add_method_breakpoint signature
|
587
|
+
raise
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
def self.add_line_breakpoint file, line, if: if_not_given = true
|
592
|
+
::DEBUGGER__::SESSION.add_line_breakpoint file, line, if_not_given ? nil : binding.local_variable_get(:if)
|
593
|
+
end
|
594
|
+
|
595
|
+
def self.add_catch_breakpoint pat
|
596
|
+
::DEBUGGER__::SESSION.add_catch_breakpoint pat
|
597
|
+
end
|
598
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DEBUGGER__
|
2
|
+
class SourceRepository
|
3
|
+
def initialize
|
4
|
+
@files = {} # filename => [src, iseq]
|
5
|
+
end
|
6
|
+
|
7
|
+
def add iseq, src
|
8
|
+
begin
|
9
|
+
src = File.read(iseq.path)
|
10
|
+
rescue
|
11
|
+
src = nil
|
12
|
+
end unless src
|
13
|
+
@files[iseq.path] = [src, iseq]
|
14
|
+
end
|
15
|
+
|
16
|
+
def get path
|
17
|
+
@files[path]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require_relative 'server'
|
3
|
+
|
4
|
+
module DEBUGGER__
|
5
|
+
class UI_TcpServer < UI_Server
|
6
|
+
def accept
|
7
|
+
host = ENV[''] || 'localhost'
|
8
|
+
port = ENV['RUBY_DEBUG_PORT'] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
|
9
|
+
port = port.to_i.tap{|i| i != 0 || raise("Specify valid port number (#{port} is specified)")}
|
10
|
+
|
11
|
+
$stderr.puts "Debugger can attach via TCP/IP (#{host}:#{port})"
|
12
|
+
Socket.tcp_server_loop(host, port) do |sock, client|
|
13
|
+
yield sock
|
14
|
+
end
|
15
|
+
rescue => e
|
16
|
+
$stderr.puts e.message
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
SESSION = Session.new(s = UI_TcpServer.new)
|
22
|
+
SESSION.management_threads << s.reader_thread
|
23
|
+
end
|