debug 0.2.1 → 1.0.0.alpha0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|