debug 1.0.0.alpha1 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,20 +1,27 @@
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
1
+ module DEBUGGER__
2
+ class SourceRepository
3
+ def initialize
4
+ @files = {} # filename => [src, iseq]
5
+ end
6
+
7
+ def add iseq, src
8
+ path = iseq.absolute_path
9
+ path = '-e' if iseq.path == '-e'
10
+
11
+ case
12
+ when path = iseq.absolute_path
13
+ src = File.read(path)
14
+ when iseq.path == '-e'
15
+ path = '-e'
16
+ else
17
+ src = nil
18
+ end
19
+
20
+ @files[path] = src.lines if src
21
+ end
22
+
23
+ def get path
24
+ @files[path]
25
+ end
26
+ end
27
+ end
@@ -1,25 +1,31 @@
1
+ require 'objspace'
2
+ require 'pp'
3
+
1
4
  module DEBUGGER__
2
5
  class ThreadClient
3
6
  def self.current
4
7
  Thread.current[:DEBUGGER__ThreadClient] || begin
5
- tc = SESSION.thread_client
8
+ tc = ::DEBUGGER__::SESSION.thread_client
6
9
  Thread.current[:DEBUGGER__ThreadClient] = tc
7
10
  end
8
11
  end
9
12
 
10
- attr_reader :location, :thread
13
+ attr_reader :location, :thread, :mode, :id
11
14
 
12
- def initialize q_evt, q_cmd, thr = Thread.current
15
+ def initialize id, q_evt, q_cmd, thr = Thread.current
16
+ @id = id
13
17
  @thread = thr
14
18
  @q_evt = q_evt
15
19
  @q_cmd = q_cmd
16
20
  @step_tp = nil
17
21
  @output = []
18
- @mode = nil
22
+ @src_lines_on_stop = (DEBUGGER__::CONFIG[:show_src_lines] || 10).to_i
23
+ @show_frames_on_stop = (DEBUGGER__::CONFIG[:show_frames] || 2).to_i
24
+ set_mode nil
19
25
  end
20
26
 
21
27
  def inspect
22
- "#<DEBUGGER__ThreadClient #{@thread}>"
28
+ "#<DBG:TC #{self.id}:#{self.mode}@#{@thread.backtrace[-1]}>"
23
29
  end
24
30
 
25
31
  def puts str = ''
@@ -44,11 +50,11 @@ module DEBUGGER__
44
50
 
45
51
  ## events
46
52
 
47
- def on_trap
48
- if @mode == :wait_next_action
49
- raise Interrupt
53
+ def on_trap sig
54
+ if self.mode == :wait_next_action
55
+ # raise Interrupt
50
56
  else
51
- on_suspend :trap
57
+ on_suspend :trap, sig: sig
52
58
  end
53
59
  end
54
60
 
@@ -56,18 +62,24 @@ module DEBUGGER__
56
62
  on_suspend :pause
57
63
  end
58
64
 
65
+ def on_thread_begin th
66
+ event! :thread_begin, th
67
+ wait_next_action
68
+ end
69
+
59
70
  def on_load iseq, eval_src
60
71
  event! :load, iseq, eval_src
61
72
  wait_next_action
62
73
  end
63
74
 
64
- def on_breakpoint tp
65
- on_suspend tp.event, tp
75
+ def on_breakpoint tp, bp
76
+ on_suspend tp.event, tp, bp: bp
66
77
  end
67
78
 
68
- def on_suspend event, tp = nil
79
+ def on_suspend event, tp = nil, bp: nil, sig: nil
69
80
  @current_frame_index = 0
70
- @target_frames = target_frames
81
+ @target_frames = DEBUGGER__.capture_frames __dir__
82
+
71
83
  cf = @target_frames.first
72
84
  if cf
73
85
  @location = cf.location
@@ -79,85 +91,140 @@ module DEBUGGER__
79
91
  end
80
92
 
81
93
  if event != :pause
82
- show_src
83
- print_frames 3
84
- event! :suspend, :breakpoint
94
+ show_src max_lines: @src_lines_on_stop
95
+ show_frames @show_frames_on_stop
96
+
97
+ if bp
98
+ event! :suspend, :breakpoint, bp.key
99
+ elsif sig
100
+ event! :suspend, :trap, sig
101
+ else
102
+ event! :suspend, event
103
+ end
85
104
  end
86
105
 
87
106
  wait_next_action
88
107
  end
89
-
108
+
90
109
  ## control all
91
110
 
111
+ begin
112
+ TracePoint.new(:raise){}.enable(target_thread: Thread.current)
113
+ SUPPORT_TARGET_THREAD = true
114
+ rescue ArgumentError
115
+ SUPPORT_TARGET_THREAD = false
116
+ end
117
+
92
118
  def step_tp
93
119
  @step_tp.disable if @step_tp
94
- @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
95
- next if SESSION.break? tp.path, tp.lineno
96
- next if !yield
97
- tp.disable
98
- on_suspend tp.event, tp
99
- }
100
-
101
- @step_tp.enable(target_thread: Thread.current)
102
- end
103
-
104
- FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :has_return_value, :return_value)
105
-
106
- def target_frames
107
- RubyVM::DebugInspector.open{|dc|
108
- locs = dc.backtrace_locations
109
- locs.map.with_index{|e, i|
110
- unless File.dirname(e.path) == File.dirname(__FILE__)
111
- FrameInfo.new(
112
- e,
113
- dc.frame_self(i),
114
- dc.frame_binding(i),
115
- dc.frame_iseq(i),
116
- dc.frame_class(i))
117
- end
118
- }.compact
119
- }
120
- end
121
120
 
122
- def target_frames_count
123
- RubyVM::DebugInspector.open{|dc|
124
- locs = dc.backtrace_locations
125
- locs.count{|e|
126
- e.path != __FILE__
121
+ thread = Thread.current
122
+
123
+ if SUPPORT_TARGET_THREAD
124
+ @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
125
+ next if SESSION.break? tp.path, tp.lineno
126
+ next if !yield
127
+ next if tp.path.start_with?(__dir__)
128
+ tp.disable
129
+ on_suspend tp.event, tp
130
+ }
131
+ @step_tp.enable(target_thread: thread)
132
+ else
133
+ @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
134
+ next if thread != Thread.current
135
+ next if SESSION.break? tp.path, tp.lineno
136
+ next if !yield
137
+ tp.disable
138
+ on_suspend tp.event, tp
127
139
  }
128
- }
140
+ @step_tp.enable
141
+ end
129
142
  end
130
143
 
131
144
  def current_frame
132
- @target_frames[@current_frame_index]
145
+ if @target_frames
146
+ @target_frames[@current_frame_index]
147
+ else
148
+ nil
149
+ end
133
150
  end
134
151
 
135
152
  def file_lines path
136
- if (src = SESSION.source(path)) && src[0]
137
- src[0].lines
153
+ if (src_lines = SESSION.source(path))
154
+ src_lines
138
155
  elsif File.exist?(path)
139
156
  File.readlines(path)
140
157
  end
141
158
  end
142
159
 
143
- def show_src frame_index = @current_frame_index, max_lines: 10
144
- if current_line = @target_frames[frame_index]&.location
145
- puts
146
- path, line = current_line.path, current_line.lineno - 1
147
- if file_lines = file_lines(path)
148
- lines = file_lines.map.with_index{|e, i|
149
- if i == line
160
+ def show_src(frame_index: @current_frame_index,
161
+ update_line: false,
162
+ max_lines: 10,
163
+ start_line: nil,
164
+ end_line: nil,
165
+ dir: +1)
166
+ #
167
+ if @target_frames && frame = @target_frames[frame_index]
168
+ if file_lines = file_lines(path = frame.location.path)
169
+ frame_line = frame.location.lineno - 1
170
+
171
+ lines = file_lines.map.with_index do |e, i|
172
+ if i == frame_line
150
173
  "=> #{'%4d' % (i+1)}| #{e}"
151
174
  else
152
175
  " #{'%4d' % (i+1)}| #{e}"
153
176
  end
154
- }
155
- min = [0, line - max_lines/2].max
156
- max = [min+max_lines, lines.size].min
157
- puts "[#{min+1}, #{max}] in #{path}"
158
- puts lines[min ... max]
177
+ end
178
+
179
+ unless start_line
180
+ if frame.show_line
181
+ if dir > 0
182
+ start_line = frame.show_line
183
+ else
184
+ end_line = frame.show_line - max_lines
185
+ start_line = [end_line - max_lines, 0].max
186
+ end
187
+ else
188
+ start_line = [frame_line - max_lines/2, 0].max
189
+ end
190
+ end
191
+
192
+ unless end_line
193
+ end_line = [start_line + max_lines, lines.size].min
194
+ end
195
+
196
+ if update_line
197
+ frame.show_line = end_line
198
+ end
199
+
200
+ if start_line != end_line
201
+ puts "[#{start_line+1}, #{end_line}] in #{path}" unless update_line
202
+ puts lines[start_line ... end_line]
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ def show_by_editor path = nil
209
+ unless path
210
+ if @target_frames && frame = @target_frames[@current_frame_index]
211
+ path = frame.location.path
212
+ else
213
+ return # can't get path
159
214
  end
160
215
  end
216
+
217
+ if File.exist?(path)
218
+ if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR'])
219
+ puts "command: #{editor}"
220
+ puts " path: #{path}"
221
+ system(editor, path)
222
+ else
223
+ puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']"
224
+ end
225
+ else
226
+ puts "Can not find file: #{path}"
227
+ end
161
228
  end
162
229
 
163
230
  def show_locals
@@ -176,39 +243,48 @@ module DEBUGGER__
176
243
 
177
244
  def show_ivars
178
245
  if s = current_frame&.self
179
- puts " self => #{s}"
180
- s.instance_variables.eaach{|iv|
246
+ s.instance_variables.each{|iv|
181
247
  puts " #{iv} => #{s.instance_variable_get(iv)}"
182
248
  }
183
249
  end
184
250
  end
185
251
 
186
- def frame_eval src, failed_value: nil
252
+ def frame_eval src, re_raise: false
187
253
  begin
254
+ @success_last_eval = false
255
+
188
256
  b = current_frame.binding
189
- if b
190
- b.eval(src)
191
- else
192
- frame_self = current_frame.self
193
- frame_self.instance_eval(src)
194
- # puts "eval is not supported on this frame."
195
- end
257
+ result = if b
258
+ f, l = b.source_location
259
+ b.eval(src, "(rdbg)/#{f}")
260
+ else
261
+ frame_self = current_frame.self
262
+ frame_self.instance_eval(src)
263
+ end
264
+ @success_last_eval = true
265
+ result
266
+
196
267
  rescue Exception => e
197
- return failed_value if failed_value
268
+ return yield(e) if block_given?
269
+
270
+ puts "eval error: #{e}"
198
271
 
199
- puts "Error: #{e}"
200
272
  e.backtrace_locations.each do |loc|
201
273
  break if loc.path == __FILE__
202
274
  puts " #{loc}"
203
275
  end
204
- nil
276
+ raise if re_raise
205
277
  end
206
278
  end
207
279
 
208
280
  def parameters_info b, vars
209
281
  vars.map{|var|
210
- "#{var}=#{short_inspect(b.eval(var.to_s))}"
211
- }.join(', ')
282
+ begin
283
+ "#{var}=#{short_inspect(b.local_variable_get(var))}"
284
+ rescue NameError, TypeError
285
+ nil
286
+ end
287
+ }.compact.join(', ')
212
288
  end
213
289
 
214
290
  def klass_sig frame
@@ -246,13 +322,12 @@ module DEBUGGER__
246
322
  if b && (iseq = frame.iseq)
247
323
  if iseq.type == :block
248
324
  if (argc = iseq.argc) > 0
249
- args = parameters_info b, iseq.locals[0...iseq.argc]
325
+ args = parameters_info b, iseq.locals[0...argc]
250
326
  buff << " {|#{args}|}"
251
327
  end
252
328
  else
253
- callee = b.eval('__callee__')
254
- if callee && (m = frame.self.method(callee))
255
- args = parameters_info b, m.parameters.map{|type, v| v}
329
+ if (callee = b.eval('__callee__', __FILE__, __LINE__)) && (argc = iseq.argc) > 0
330
+ args = parameters_info b, iseq.locals[0...argc]
256
331
  ksig = klass_sig frame
257
332
  buff << " #{ksig}#{callee}(#{args})"
258
333
  end
@@ -268,30 +343,67 @@ module DEBUGGER__
268
343
  buff
269
344
  end
270
345
 
271
- def show_frame_all
272
- @target_frames.size.times{|i|
273
- puts frame_str(i)
274
- }
346
+ def show_frames max = (@target_frames || []).size
347
+ if max > 0 && frames = @target_frames
348
+ size = @target_frames.size
349
+ max += 1 if size == max + 1
350
+ max.times{|i|
351
+ break if i >= size
352
+ puts frame_str(i)
353
+ }
354
+ puts " # and #{size - max} frames (use `bt' command for all frames)" if max < size
355
+ end
275
356
  end
276
357
 
277
- def print_frame i
358
+ def show_frame i=0
278
359
  puts frame_str(i)
279
360
  end
280
361
 
281
- def print_frames n
282
- size = @target_frames.size
283
- ([size, n].min).times{|i|
284
- print_frame i
285
- }
286
- if n < size
287
- puts " # and #{size - n} frames (use `bt' command for all frames)"
362
+ def show_object_info expr
363
+ begin
364
+ result = frame_eval(expr, re_raise: true)
365
+ rescue Exception
366
+ # ignore
367
+ else
368
+ klass = ObjectSpace.internal_class_of(result)
369
+ exists = []
370
+ klass.ancestors.each{|k|
371
+ puts "= #{k}"
372
+ if (ms = (k.instance_methods(false) - exists)).size > 0
373
+ puts ms.sort.join("\t")
374
+ exists |= ms
375
+ end
376
+ }
288
377
  end
289
378
  end
290
379
 
380
+ def add_breakpoint args
381
+ case args.first
382
+ when :method
383
+ klass_name, op, method_name, cond = args[1..]
384
+ bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond)
385
+ begin
386
+ bp.enable
387
+ rescue Exception => e
388
+ puts e.message
389
+ ::DEBUGGER__::METHOD_ADDED_TRACKER.enable
390
+ end
391
+ event! :result, :method_breakpoint, bp
392
+ else
393
+ raise "unknown breakpoint: #{args}"
394
+ end
395
+ end
396
+
397
+ def set_mode mode
398
+ @mode = mode
399
+ end
400
+
291
401
  def wait_next_action
292
- @mode = :wait_next_action
402
+ set_mode :wait_next_action
293
403
 
294
404
  while cmds = @q_cmd.pop
405
+ # pp [self, cmds: cmds]
406
+
295
407
  cmd, *args = *cmds
296
408
 
297
409
  case cmd
@@ -303,20 +415,43 @@ module DEBUGGER__
303
415
  when :in
304
416
  step_tp{true}
305
417
  when :next
306
- size = @target_frames.size
418
+ frame = @target_frames.first
419
+ path = frame.location.absolute_path || "!eval:#{frame.location.path}"
420
+ line = frame.location.lineno
421
+ frame.iseq.traceable_lines_norec(lines = {})
422
+ next_line = lines.keys.bsearch{|e| e > line}
423
+ if !next_line && (last_line = frame.iseq.last_line) > line
424
+ next_line = last_line
425
+ end
426
+ depth = @target_frames.first.frame_depth
427
+
307
428
  step_tp{
308
- target_frames_count() <= size
429
+ loc = caller_locations(2, 1).first
430
+ loc_path = loc.absolute_path || "!eval:#{loc.path}"
431
+
432
+ (next_line && loc_path == path &&
433
+ (loc_lineno = loc.lineno) > line && loc_lineno <= next_line) ||
434
+ (DEBUGGER__.frame_depth - 3 < depth)
309
435
  }
310
436
  when :finish
311
- size = @target_frames.size
312
- step_tp{target_frames_count() < size}
437
+ depth = @target_frames.first.frame_depth
438
+ step_tp{
439
+ # 3 is debugger's frame count
440
+ DEBUGGER__.frame_depth - 3 < depth
441
+ }
313
442
  else
314
443
  raise
315
444
  end
316
445
  break
317
446
  when :eval
318
447
  eval_type, eval_src = *args
319
- result = frame_eval(eval_src)
448
+
449
+ case eval_type
450
+ when :display, :try_display
451
+ else
452
+ result = frame_eval(eval_src)
453
+ end
454
+ result_type = nil
320
455
 
321
456
  case eval_type
322
457
  when :p
@@ -327,15 +462,31 @@ module DEBUGGER__
327
462
  puts out
328
463
  when :call
329
464
  result = frame_eval(eval_src)
330
- when :display
465
+ when :display, :try_display
466
+ failed_results = []
331
467
  eval_src.each_with_index{|src, i|
332
- puts "#{i}: #{src} = #{frame_eval(src, failed_value: :error).inspect}"
468
+ result = frame_eval(src){|e|
469
+ failed_results << [i, e.message]
470
+ "<error: #{e.message}>"
471
+ }
472
+ puts "#{i}: #{src} = #{result}"
333
473
  }
334
- result = :ok
474
+
475
+ result_type = eval_type
476
+ result = failed_results
477
+ when :watch
478
+ if @success_last_eval
479
+ puts "#{eval_src} = #{result}"
480
+ result = WatchExprBreakpoint.new(eval_src, result)
481
+ result_type = :watch
482
+ else
483
+ result = nil
484
+ end
335
485
  else
336
- raise "unknown error option: #{args.inspec}"
486
+ raise "unknown error option: #{args.inspect}"
337
487
  end
338
- event! :result, result
488
+
489
+ event! :result, result_type, result
339
490
  when :frame
340
491
  type, arg = *args
341
492
  case type
@@ -343,13 +494,13 @@ module DEBUGGER__
343
494
  if @current_frame_index + 1 < @target_frames.size
344
495
  @current_frame_index += 1
345
496
  show_src max_lines: 1
346
- print_frame(@current_frame_index)
497
+ show_frame(@current_frame_index)
347
498
  end
348
499
  when :down
349
500
  if @current_frame_index > 0
350
501
  @current_frame_index -= 1
351
502
  show_src max_lines: 1
352
- print_frame(@current_frame_index)
503
+ show_frame(@current_frame_index)
353
504
  end
354
505
  when :set
355
506
  if arg
@@ -361,28 +512,43 @@ module DEBUGGER__
361
512
  end
362
513
  end
363
514
  show_src max_lines: 1
364
- print_frame(@current_frame_index)
515
+ show_frame(@current_frame_index)
365
516
  else
366
517
  raise "unsupported frame operation: #{arg.inspect}"
367
518
  end
368
519
  event! :result, nil
369
520
  when :show
370
- type, = *args
521
+ type = args.shift
522
+
371
523
  case type
372
524
  when :backtrace
373
- show_frame_all
525
+ show_frames
526
+
374
527
  when :list
375
- show_src
376
- when :locals
528
+ show_src(update_line: true, **(args.first || {}))
529
+
530
+ when :edit
531
+ show_by_editor(args.first)
532
+
533
+ when :local
534
+ show_frame
377
535
  show_locals
378
- when :ivars
379
536
  show_ivars
537
+
538
+ when :object_info
539
+ expr = args.shift
540
+ show_object_info expr
541
+
380
542
  else
381
- raise "unknown show param: " + args.inspect
543
+ raise "unknown show param: " + [type, *args].inspect
382
544
  end
545
+
383
546
  event! :result, nil
547
+
548
+ when :breakpoint
549
+ add_breakpoint args
384
550
  else
385
- raise [ev, *args].inspect
551
+ raise [cmd, *args].inspect
386
552
  end
387
553
  end
388
554
 
@@ -392,11 +558,20 @@ module DEBUGGER__
392
558
  pp [__FILE__, __LINE__, e, e.backtrace]
393
559
  raise
394
560
  ensure
395
- @mode = nil
561
+ set_mode nil
396
562
  end
397
563
 
398
564
  def to_s
399
- "(#{@thread.name || @thread.status})@#{current_frame&.location}"
565
+ loc = current_frame&.location
566
+
567
+ if loc
568
+ str = "(#{@thread.name || @thread.status})@#{loc}"
569
+ else
570
+ str = "(#{@thread.name || @thread.status})@#{@thread.to_s}"
571
+ end
572
+
573
+ str += " (not under control)" unless self.mode
574
+ str
400
575
  end
401
576
  end
402
577
  end