debug 1.0.0.alpha1 → 1.0.0.beta1

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.
@@ -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