debug 1.1.0 → 1.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e9851059a728e477461cc318c9b94932fa0aeb19215f936ab6c25cf62f67cd0
4
- data.tar.gz: b050eee3dbf0afecfda2c1221fe38ab678808405c10f89d5832a1950e2728aa8
3
+ metadata.gz: d6eba8a00eaa711138e43036c5adb97834f1a87e586f8a5a3f580875e4d78e5c
4
+ data.tar.gz: 26ed8ac77bb93ab2a9f67fac6c4ef4481fbcdd227f116aaff78e808a2343b13b
5
5
  SHA512:
6
- metadata.gz: a15549ee06b890a32d0960dd24a99c4c925d66b5f6512a7e5ff3052f10ca3a255038d44eaa2d10046cbe2b68bca121c813db3a8e5fea68def5dcd2e1797864d7
7
- data.tar.gz: '058b5ef8c6e40a8ccfa12b62b09c8808c1b524c398e37a25ffd790b8a8e0364026911478019d06100b7a6d8fde878dd7695f2f2630f3f35f37f12695b84b2ddf'
6
+ metadata.gz: b459267a95928388d1bf53bab8bf51fc8d5a6d0db0bc2fbaa55f740b1b446aad2294e45cc2b70471ccf8bb1daada458f239f08c7ceb7588774f08796a7838620
7
+ data.tar.gz: 86d06a4ec76943e26af39a26120c8a3a906d3fcecca195a662e63b9be2b7b9a1ba82bd2fba177205b436cde89be9214988e0f2cccc0f7fb2dcbf7226e92886e9
data/README.md CHANGED
@@ -372,6 +372,7 @@ config set no_color true
372
372
 
373
373
  * BOOT
374
374
  * `RUBY_DEBUG_NONSTOP` (`nonstop`): Nonstop mode
375
+ * `RUBY_DEBUG_STOP_AT_LOAD` (`stop_at_load`): Stop at just loading location
375
376
  * `RUBY_DEBUG_INIT_SCRIPT` (`init_script`): debug command script path loaded at first stop
376
377
  * `RUBY_DEBUG_COMMANDS` (`commands`): debug commands invoked at first stop. commands should be separated by ';;'
377
378
  * `RUBY_DEBUG_NO_RC` (`no_rc`): ignore loading ~/.rdbgrc(.rb)
@@ -436,6 +437,9 @@ The `<...>` notation means the argument.
436
437
  * Stop the debuggee process with `Kernal#exit!`.
437
438
  * `kill!`
438
439
  * Same as kill but without the confirmation prompt.
440
+ * `sigint`
441
+ * Execute SIGINT handler registerred by the debuggee.
442
+ * Note that this command should be used just after stop by `SIGINT`.
439
443
 
440
444
  ### Breakpoint
441
445
 
@@ -728,6 +732,7 @@ Attach mode:
728
732
  Other options:
729
733
  -h, --help Print help
730
734
  --util=NAME Utility mode (used by tools)
735
+ --stop-at-load Stop immediately when the debugging feature is loaded.
731
736
 
732
737
  NOTE
733
738
  All messages communicated between a debugger and a debuggee are *NOT* encrypted.
data/ext/debug/debug.c CHANGED
@@ -122,6 +122,11 @@ Init_debug(void)
122
122
  {
123
123
  rb_mDebugger = rb_const_get(rb_cObject, rb_intern("DEBUGGER__"));
124
124
  rb_cFrameInfo = rb_const_get(rb_mDebugger, rb_intern("FrameInfo"));
125
+
126
+ // Debugger and FrameInfo were defined in Ruby. We need to register them
127
+ // as mark objects so they are automatically pinned.
128
+ rb_gc_register_mark_object(rb_mDebugger);
129
+ rb_gc_register_mark_object(rb_cFrameInfo);
125
130
  rb_define_singleton_method(rb_mDebugger, "capture_frames", capture_frames, 1);
126
131
  rb_define_singleton_method(rb_mDebugger, "frame_depth", frame_depth, 0);
127
132
  rb_define_singleton_method(rb_mDebugger, "create_method_added_tracker", create_method_added_tracker, 0);
@@ -288,6 +288,7 @@ module DEBUGGER__
288
288
  @tp = TracePoint.new(:line){|tp|
289
289
  next if tp.path.start_with? __dir__
290
290
  next if tp.path.start_with? '<internal:'
291
+ next if ThreadClient.current.management?
291
292
 
292
293
  if safe_eval tp.binding, @expr
293
294
  suspend
@@ -430,17 +431,24 @@ module DEBUGGER__
430
431
  if @sig_op == '#'
431
432
  @cond_class = @klass if @method.owner != @klass
432
433
  else # '.'
433
- @cond_class = @klass.singleton_class if @method.owner != @klass.singleton_class
434
+ begin
435
+ @cond_class = @klass.singleton_class if @method.owner != @klass.singleton_class
436
+ rescue TypeError
437
+ end
434
438
  end
435
439
 
436
- rescue ArgumentError
440
+ rescue ArgumentError => e
437
441
  raise if retried
438
442
  retried = true
439
443
 
440
444
  # maybe C method
441
445
  case @sig_op
442
446
  when '.'
443
- override @klass.singleton_class
447
+ begin
448
+ override @klass.singleton_class
449
+ rescue TypeError
450
+ override @klass.class
451
+ end
444
452
  when '#'
445
453
  override @klass
446
454
  end
@@ -450,7 +458,7 @@ module DEBUGGER__
450
458
  @override_method = true if @method
451
459
  retry
452
460
  end
453
- rescue Exception
461
+ rescue Exception => e
454
462
  raise unless added
455
463
  end
456
464
 
@@ -460,7 +468,8 @@ module DEBUGGER__
460
468
 
461
469
  def to_s
462
470
  if @method
463
- "#{generate_label("Method")} #{sig} at #{@method.source_location.join(':')}"
471
+ loc = @method.source_location || []
472
+ "#{generate_label("Method")} #{sig} at #{loc.join(':')}"
464
473
  else
465
474
  "#{generate_label("Method (pending)")} #{sig}"
466
475
  end + super
data/lib/debug/color.rb CHANGED
@@ -19,7 +19,7 @@ module DEBUGGER__
19
19
  if defined? IRB::Color.colorize
20
20
  def colorize str, color
21
21
  if !CONFIG[:no_color]
22
- IRB::Color.colorize str, color
22
+ IRB::Color.colorize str, color, colorable: true
23
23
  else
24
24
  str
25
25
  end
@@ -64,7 +64,7 @@ module DEBUGGER__
64
64
 
65
65
  if defined? IRB::Color.colorize_code
66
66
  def colorize_code code
67
- IRB::Color.colorize_code(code)
67
+ IRB::Color.colorize_code(code, colorable: true)
68
68
  end
69
69
  else
70
70
  def colorize_code code
data/lib/debug/config.rb CHANGED
@@ -29,6 +29,7 @@ module DEBUGGER__
29
29
 
30
30
  # boot setting
31
31
  nonstop: ['RUBY_DEBUG_NONSTOP', "BOOT: Nonstop mode", :bool],
32
+ stop_at_load: ['RUBY_DEBUG_STOP_AT_LOAD',"BOOT: Stop at just loading location", :bool],
32
33
  init_script: ['RUBY_DEBUG_INIT_SCRIPT', "BOOT: debug command script path loaded at first stop"],
33
34
  commands: ['RUBY_DEBUG_COMMANDS', "BOOT: debug commands invoked at first stop. commands should be separated by ';;'"],
34
35
  no_rc: ['RUBY_DEBUG_NO_RC', "BOOT: ignore loading ~/.rdbgrc(.rb)", :bool],
@@ -110,7 +111,9 @@ module DEBUGGER__
110
111
  end
111
112
 
112
113
  if_updated old_conf, conf, :postmortem do |_, new_p|
113
- SESSION.postmortem = new_p
114
+ if defined?(SESSION)
115
+ SESSION.postmortem = new_p
116
+ end
114
117
  end
115
118
 
116
119
  if_updated old_conf, conf, :sigdump_sig do |old_sig, new_sig|
@@ -314,6 +317,10 @@ module DEBUGGER__
314
317
  exit
315
318
  end
316
319
 
320
+ o.on('--stop-at-load', 'Stop immediately when the debugging feature is loaded.') do
321
+ config[:stop_at_load] = true
322
+ end
323
+
317
324
  o.separator ''
318
325
  o.separator 'NOTE'
319
326
  o.separator ' All messages communicated between a debugger and a debuggee are *NOT* encrypted.'
data/lib/debug/console.rb CHANGED
@@ -6,11 +6,19 @@ module DEBUGGER__
6
6
  require 'reline'
7
7
 
8
8
  # reline 0.2.7 or later is required.
9
- raise LoadError if Reline::VERSION < '0.2.6'
9
+ raise LoadError if Reline::VERSION < '0.2.7'
10
10
 
11
11
  require_relative 'color'
12
12
  include Color
13
13
 
14
+ begin
15
+ prev = trap(:SIGWINCH, nil)
16
+ trap(:SIGWINCH, prev)
17
+ SIGWINCH_SUPPORTED = true
18
+ rescue ArgumentError
19
+ SIGWINCH_SUPPORTED = false
20
+ end
21
+
14
22
  # 0.2.7 has SIGWINCH issue on non-main thread
15
23
  class ::Reline::LineEditor
16
24
  m = Module.new do
@@ -20,7 +28,7 @@ module DEBUGGER__
20
28
  end
21
29
  end
22
30
  prepend m
23
- end
31
+ end if SIGWINCH_SUPPORTED
24
32
 
25
33
  def readline_setup prompt
26
34
  commands = DEBUGGER__.commands
@@ -27,6 +27,7 @@ module DEBUGGER__
27
27
  end
28
28
 
29
29
  def pretty_path
30
+ return '#<none>' unless path = self.path
30
31
  use_short_path = CONFIG[:use_short_path]
31
32
 
32
33
  case
@@ -137,10 +138,9 @@ module DEBUGGER__
137
138
  if lvars = self._local_variables
138
139
  lvars
139
140
  elsif b = self.binding
140
- lvars = b.local_variables.map{|var|
141
+ b.local_variables.map{|var|
141
142
  [var, b.local_variable_get(var)]
142
143
  }.to_h
143
- self._local_variables = lvars
144
144
  end
145
145
  end
146
146
 
data/lib/debug/local.rb CHANGED
@@ -7,32 +7,27 @@ module DEBUGGER__
7
7
  class UI_LocalConsole < UI_Base
8
8
  def initialize
9
9
  @console = Console.new
10
-
11
- unless CONFIG[:no_sigint_hook]
12
- @prev_handler = trap(:SIGINT){
13
- if SESSION.active?
14
- ThreadClient.current.on_trap :SIGINT
15
- end
16
- }
17
- end
18
- end
19
-
20
- def close
21
- if @prev_handler
22
- trap(:SIGINT, @prev_handler)
23
- end
24
10
  end
25
11
 
26
12
  def remote?
27
13
  false
28
14
  end
29
15
 
30
- def activate on_fork: false
31
- # Do nothing
16
+ def activate session, on_fork: false
17
+ unless CONFIG[:no_sigint_hook]
18
+ prev_handler = trap(:SIGINT){
19
+ if session.active?
20
+ ThreadClient.current.on_trap :SIGINT
21
+ end
22
+ }
23
+ session.intercept_trap_sigint_start prev_handler
24
+ end
32
25
  end
33
26
 
34
27
  def deactivate
35
- # Do nothing
28
+ if SESSION.intercept_trap_sigint?
29
+ trap(:SIGINT, SESSION.intercepted_sigint_cmd)
30
+ end
36
31
  end
37
32
 
38
33
  def width
@@ -76,15 +71,17 @@ module DEBUGGER__
76
71
  end
77
72
 
78
73
  def setup_interrupt
79
- current_thread = Thread.current # should be session_server thread
74
+ SESSION.intercept_trap_sigint false do
75
+ current_thread = Thread.current # should be session_server thread
80
76
 
81
- prev_handler = trap(:INT){
82
- current_thread.raise Interrupt
83
- }
77
+ prev_handler = trap(:INT){
78
+ current_thread.raise Interrupt
79
+ }
84
80
 
85
- yield
86
- ensure
87
- trap(:INT, prev_handler)
81
+ yield
82
+ ensure
83
+ trap(:INT, prev_handler)
84
+ end
88
85
  end
89
86
  end
90
87
  end
data/lib/debug/server.rb CHANGED
@@ -15,8 +15,6 @@ module DEBUGGER__
15
15
  @q_ans = nil
16
16
  @unsent_messages = []
17
17
  @width = 80
18
-
19
- activate
20
18
  end
21
19
 
22
20
  class Terminate < StandardError
@@ -37,7 +35,7 @@ module DEBUGGER__
37
35
  end
38
36
  end
39
37
 
40
- def activate on_fork: false
38
+ def activate session, on_fork: false
41
39
  @reader_thread = Thread.new do
42
40
  # An error on this thread should break the system.
43
41
  Thread.current.abort_on_exception = true
@@ -138,9 +136,9 @@ module DEBUGGER__
138
136
  end
139
137
 
140
138
  def setup_interrupt
141
- prev_handler = trap(:SIGINT) do
139
+ prev_handler = trap(:SIGURG) do
142
140
  # $stderr.puts "trapped SIGINT"
143
- ThreadClient.current.on_trap :SIGINT
141
+ ThreadClient.current.on_trap :SIGURG
144
142
 
145
143
  case prev_handler
146
144
  when Proc
@@ -150,9 +148,12 @@ module DEBUGGER__
150
148
  end
151
149
  end
152
150
 
151
+ if prev_handler != "SYSTEM_DEFAULT"
152
+ DEBUGGER__.warn "SIGURG handler is overriddend by the debugger."
153
+ end
153
154
  yield
154
155
  ensure
155
- trap(:SIGINT, prev_handler)
156
+ trap(:SIGURG, prev_handler)
156
157
  end
157
158
 
158
159
  attr_reader :reader_thread
@@ -230,7 +231,7 @@ module DEBUGGER__
230
231
 
231
232
  def pause
232
233
  # $stderr.puts "DEBUG: pause request"
233
- Process.kill(:SIGINT, Process.pid)
234
+ Process.kill(:SIGURG, Process.pid)
234
235
  end
235
236
 
236
237
  def quit n
data/lib/debug/session.rb CHANGED
@@ -86,7 +86,7 @@ module DEBUGGER__
86
86
  # [:watch, ivar] => WatchIVarBreakpoint
87
87
  # [:check, expr] => CheckBreakpoint
88
88
  #
89
- @tracers = []
89
+ @tracers = {}
90
90
  @th_clients = nil # {Thread => ThreadClient}
91
91
  @q_evt = Queue.new
92
92
  @displays = []
@@ -96,17 +96,22 @@ module DEBUGGER__
96
96
  @postmortem_hook = nil
97
97
  @postmortem = false
98
98
  @thread_stopper = nil
99
+ @intercept_trap_sigint = false
100
+ @intercepted_sigint_cmd = 'DEFAULT'
99
101
 
100
102
  @frame_map = {} # {id => [threadId, frame_depth]} for DAP
101
103
  @var_map = {1 => [:globals], } # {id => ...} for DAP
102
104
  @src_map = {} # {id => src}
103
105
 
106
+ @tp_thread_begin = nil
104
107
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
105
108
  ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
106
109
  }
107
110
  @tp_load_script.enable
108
111
 
109
112
  activate
113
+
114
+ self.postmortem = CONFIG[:postmortem]
110
115
  end
111
116
 
112
117
  def active?
@@ -117,40 +122,41 @@ module DEBUGGER__
117
122
  @bps.has_key? [file, line]
118
123
  end
119
124
 
120
- def check_forked
121
- unless active?
122
- # TODO: Support it
123
- raise 'DEBUGGER: stop at forked process is not supported yet.'
125
+ def activate on_fork: false
126
+ @tp_thread_begin&.disable
127
+ @tp_thread_begin = nil
128
+
129
+ if on_fork
130
+ @ui.activate self, on_fork: true
131
+ else
132
+ @ui.activate self, on_fork: false
124
133
  end
125
- end
126
134
 
127
- def activate on_fork: false
135
+ q = Queue.new
128
136
  @session_server = Thread.new do
129
137
  Thread.current.name = 'DEBUGGER__::SESSION@server'
130
138
  Thread.current.abort_on_exception = true
131
- session_server_main
132
- end
133
139
 
134
- setup_threads
140
+ # Thread management
141
+ setup_threads
142
+ thc = thread_client Thread.current
143
+ thc.is_management
135
144
 
136
- thc = thread_client @session_server
137
- thc.is_management
145
+ if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
146
+ thc.is_management
147
+ end
138
148
 
139
- if on_fork
140
- @tp_thread_begin.disable
141
- @tp_thread_begin = nil
142
- @ui.activate on_fork: true
143
- end
149
+ @tp_thread_begin = TracePoint.new(:thread_begin) do |tp|
150
+ thread_client
151
+ end
152
+ @tp_thread_begin.enable
144
153
 
145
- if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
146
- thc.is_management
154
+ # session start
155
+ q << true
156
+ session_server_main
147
157
  end
148
158
 
149
- @tp_thread_begin = TracePoint.new(:thread_begin){|tp|
150
- th = Thread.current
151
- ThreadClient.current.on_thread_begin th
152
- }
153
- @tp_thread_begin.enable
159
+ q.pop
154
160
  end
155
161
 
156
162
  def deactivate
@@ -160,14 +166,14 @@ module DEBUGGER__
160
166
  @tp_thread_begin.disable
161
167
  @bps.each{|k, bp| bp.disable}
162
168
  @th_clients.each{|th, thc| thc.close}
163
- @tracers.each{|t| t.disable}
169
+ @tracers.values.each{|t| t.disable}
164
170
  @q_evt.close
165
171
  @ui&.deactivate
166
172
  @ui = nil
167
173
  end
168
174
 
169
175
  def reset_ui ui
170
- @ui.close
176
+ @ui.deactivate
171
177
  @ui = ui
172
178
  end
173
179
 
@@ -182,6 +188,13 @@ module DEBUGGER__
182
188
  output.each{|str| @ui.puts str}
183
189
 
184
190
  case ev
191
+
192
+ when :thread_begin # special event, tc is nil
193
+ th = ev_args.shift
194
+ q = ev_args.shift
195
+ on_thread_begin th
196
+ q << true
197
+
185
198
  when :init
186
199
  wait_command_loop tc
187
200
 
@@ -193,24 +206,23 @@ module DEBUGGER__
193
206
 
194
207
  when :trace
195
208
  trace_id, msg = ev_args
196
- if t = @tracers.find{|t| t.object_id == trace_id}
209
+ if t = @tracers.values.find{|t| t.object_id == trace_id}
197
210
  t.puts msg
198
211
  end
199
212
  tc << :continue
200
213
 
201
- when :thread_begin
202
- th = ev_args.shift
203
- on_thread_begin th
204
- @ui.event :thread_begin, th
205
- tc << :continue
206
-
207
214
  when :suspend
208
215
  case ev_args.first
209
216
  when :breakpoint
210
217
  bp, i = bp_index ev_args[1]
211
218
  @ui.event :suspend_bp, i, bp
212
219
  when :trap
213
- @ui.event :suspend_trap, ev_args[1]
220
+ @ui.event :suspend_trap, sig = ev_args[1]
221
+
222
+ if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
223
+ @ui.puts "#{@intercepted_sigint_cmd.inspect} is registerred as SIGINT handler."
224
+ @ui.puts "`sigint` command execute it."
225
+ end
214
226
  else
215
227
  @ui.event :suspended
216
228
  end
@@ -246,8 +258,7 @@ module DEBUGGER__
246
258
  obj_id = ev_args[1]
247
259
  obj_inspect = ev_args[2]
248
260
  opt = ev_args[3]
249
- @tracers << t = ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
250
- @ui.puts "Enable #{t.to_s}"
261
+ add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
251
262
  else
252
263
  # ignore
253
264
  end
@@ -432,6 +443,29 @@ module DEBUGGER__
432
443
  when 'kill!'
433
444
  exit! (arg || 1).to_i
434
445
 
446
+ # * `sigint`
447
+ # * Execute SIGINT handler registerred by the debuggee.
448
+ # * Note that this command should be used just after stop by `SIGINT`.
449
+ when 'sigint'
450
+ begin
451
+ case cmd = @intercepted_sigint_cmd
452
+ when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
453
+ # ignore
454
+ when String
455
+ eval(cmd)
456
+ when Proc
457
+ cmd.call
458
+ end
459
+
460
+ @tc << :continue
461
+ restart_all_threads
462
+
463
+ rescue Exception => e
464
+ @ui.puts "Exception: #{e}"
465
+ @ui.puts e.backtrace.map{|line| " #{e}"}
466
+ return :retry
467
+ end
468
+
435
469
  ### Breakpoint
436
470
 
437
471
  # * `b[reak]`
@@ -740,7 +774,7 @@ module DEBUGGER__
740
774
  @ui.puts "not supported on the remote console."
741
775
  return :retry
742
776
  end
743
- @tc << [:eval, :call, 'binding.irb']
777
+ @tc << [:eval, :irb]
744
778
 
745
779
  # don't repeat irb command
746
780
  @repl_prev_line = nil
@@ -778,32 +812,29 @@ module DEBUGGER__
778
812
  case arg
779
813
  when nil
780
814
  @ui.puts 'Tracers:'
781
- @tracers.each_with_index{|t, i|
815
+ @tracers.values.each_with_index{|t, i|
782
816
  @ui.puts "* \##{i} #{t}"
783
817
  }
784
818
  @ui.puts
785
819
  return :retry
786
820
 
787
821
  when /\Aline\z/
788
- @tracers << t = LineTracer.new(@ui, pattern: pattern, into: into)
789
- @ui.puts "Enable #{t.to_s}"
822
+ add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
790
823
  return :retry
791
824
 
792
825
  when /\Acall\z/
793
- @tracers << t = CallTracer.new(@ui, pattern: pattern, into: into)
794
- @ui.puts "Enable #{t.to_s}"
826
+ add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
795
827
  return :retry
796
828
 
797
829
  when /\Aexception\z/
798
- @tracers << t = ExceptionTracer.new(@ui, pattern: pattern, into: into)
799
- @ui.puts "Enable #{t.to_s}"
830
+ add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
800
831
  return :retry
801
832
 
802
833
  when /\Aobject\s+(.+)/
803
834
  @tc << [:trace, :object, $1.strip, {pattern: pattern, into: into}]
804
835
 
805
836
  when /\Aoff\s+(\d+)\z/
806
- if t = @tracers[$1.to_i]
837
+ if t = @tracers.values[$1.to_i]
807
838
  t.disable
808
839
  @ui.puts "Disable #{t.to_s}"
809
840
  else
@@ -812,7 +843,7 @@ module DEBUGGER__
812
843
  return :retry
813
844
 
814
845
  when /\Aoff(\s+(line|call|exception|object))?\z/
815
- @tracers.each{|t|
846
+ @tracers.values.each{|t|
816
847
  if $2.nil? || t.type == $2
817
848
  t.disable
818
849
  @ui.puts "Disable #{t.to_s}"
@@ -1190,6 +1221,20 @@ module DEBUGGER__
1190
1221
  @ui.puts e.message
1191
1222
  end
1192
1223
 
1224
+ # tracers
1225
+
1226
+ def add_tracer tracer
1227
+ # don't repeat commands that add tracers
1228
+ @repl_prev_line = nil
1229
+ if @tracers.has_key? tracer.key
1230
+ tracer.disable
1231
+ @ui.puts "Duplicated tracer: #{tracer}"
1232
+ else
1233
+ @tracers[tracer.key] = tracer
1234
+ @ui.puts "Enable #{tracer}"
1235
+ end
1236
+ end
1237
+
1193
1238
  # threads
1194
1239
 
1195
1240
  def update_thread_list
@@ -1242,10 +1287,6 @@ module DEBUGGER__
1242
1287
  thread_list
1243
1288
  end
1244
1289
 
1245
- def thread_client_create th
1246
- @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
1247
- end
1248
-
1249
1290
  def setup_threads
1250
1291
  @th_clients = {}
1251
1292
 
@@ -1256,18 +1297,38 @@ module DEBUGGER__
1256
1297
 
1257
1298
  def on_thread_begin th
1258
1299
  if @th_clients.has_key? th
1259
- # OK
1260
- else
1261
1300
  # TODO: NG?
1301
+ else
1262
1302
  thread_client_create th
1263
1303
  end
1264
1304
  end
1265
1305
 
1266
- def thread_client thr = Thread.current
1267
- if @th_clients.has_key? thr
1268
- @th_clients[thr]
1306
+ private def thread_client_create th
1307
+ # TODO: Ractor support
1308
+ raise "Only session_server can create thread_client" unless Thread.current == @session_server
1309
+ @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
1310
+ end
1311
+
1312
+ private def ask_thread_client th = Thread.current
1313
+ # TODO: Ractor support
1314
+ q2 = Queue.new
1315
+ # tc, output, ev, @internal_info, *ev_args = evt
1316
+ @q_evt << [nil, [], :thread_begin, nil, th, q2]
1317
+ q2.pop
1318
+
1319
+ @th_clients[th] or raise "unexpected error"
1320
+ end
1321
+
1322
+ # can be called by other threads
1323
+ def thread_client th = Thread.current
1324
+ if @th_clients.has_key? th
1325
+ @th_clients[th]
1269
1326
  else
1270
- @th_clients[thr] = thread_client_create(thr)
1327
+ if Thread.current == @session_server
1328
+ thread_client_create th
1329
+ else
1330
+ ask_thread_client th
1331
+ end
1271
1332
  end
1272
1333
  end
1273
1334
 
@@ -1386,7 +1447,10 @@ module DEBUGGER__
1386
1447
  end
1387
1448
  end
1388
1449
 
1389
- def enter_postmortem_session frames
1450
+ def enter_postmortem_session exc
1451
+ return unless exc.instance_variable_defined? :@__debugger_postmortem_frames
1452
+
1453
+ frames = exc.instance_variable_get(:@__debugger_postmortem_frames)
1390
1454
  @postmortem = true
1391
1455
  ThreadClient.current.suspend :postmortem, postmortem_frames: frames
1392
1456
  ensure
@@ -1399,7 +1463,7 @@ module DEBUGGER__
1399
1463
  @postmortem_hook = TracePoint.new(:raise){|tp|
1400
1464
  exc = tp.raised_exception
1401
1465
  frames = DEBUGGER__.capture_frames(__dir__)
1402
- exc.instance_variable_set(:@postmortem_frames, frames)
1466
+ exc.instance_variable_set(:@__debugger_postmortem_frames, frames)
1403
1467
  }
1404
1468
  at_exit{
1405
1469
  @postmortem_hook.disable
@@ -1411,7 +1475,7 @@ module DEBUGGER__
1411
1475
  @ui.puts exc.backtrace.map{|e| ' ' + e}
1412
1476
  @ui.puts "\n"
1413
1477
 
1414
- enter_postmortem_session exc.instance_variable_get(:@postmortem_frames)
1478
+ enter_postmortem_session exc
1415
1479
  rescue SystemExit
1416
1480
  exit!
1417
1481
  rescue Exception => e
@@ -1431,6 +1495,30 @@ module DEBUGGER__
1431
1495
  end
1432
1496
  end
1433
1497
  end
1498
+
1499
+ def save_int_trap cmd
1500
+ prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, cmd
1501
+ prev
1502
+ end
1503
+
1504
+ attr_reader :intercepted_sigint_cmd
1505
+
1506
+ def intercept_trap_sigint?
1507
+ @intercept_trap_sigint
1508
+ end
1509
+
1510
+ def intercept_trap_sigint flag, &b
1511
+ prev = @intercept_trap_sigint
1512
+ @intercept_trap_sigint = flag
1513
+ yield
1514
+ ensure
1515
+ @intercept_trap_sigint = prev
1516
+ end
1517
+
1518
+ def intercept_trap_sigint_start prev
1519
+ @intercept_trap_sigint = true
1520
+ @intercepted_sigint_cmd = prev
1521
+ end
1434
1522
  end
1435
1523
 
1436
1524
  class UI_Base
@@ -1526,6 +1614,9 @@ module DEBUGGER__
1526
1614
  def self.setup_initial_suspend
1527
1615
  if !CONFIG[:nonstop]
1528
1616
  case
1617
+ when CONFIG[:stop_at_load]
1618
+ add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, hook_call: false
1619
+ nil # stop here
1529
1620
  when path = ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH']
1530
1621
  add_line_breakpoint path, 0, oneshot: true, hook_call: false
1531
1622
  when loc = ::DEBUGGER__.require_location
@@ -1643,7 +1734,18 @@ module DEBUGGER__
1643
1734
 
1644
1735
  at_exit{
1645
1736
  trap(:SIGINT, :IGNORE)
1646
- Process.waitpid(child_pid)
1737
+
1738
+ # only check child process from its parent
1739
+ if Process.pid == parent_pid
1740
+ begin
1741
+ # sending a null signal to see if the child is still alive
1742
+ Process.kill(0, child_pid)
1743
+ # if the child is still alive, wait for it
1744
+ Process.waitpid(child_pid)
1745
+ rescue Errno::ESRCH
1746
+ # if the child process has died, do nothing
1747
+ end
1748
+ end
1647
1749
  }
1648
1750
  }
1649
1751
  child_hook = -> {
@@ -1675,8 +1777,36 @@ module DEBUGGER__
1675
1777
  end
1676
1778
  end
1677
1779
 
1678
- class ::Object
1679
- include ForkInterceptor
1780
+ module TrapInterceptor
1781
+ def trap sig, *command, &command_proc
1782
+ case sig&.to_sym
1783
+ when :INT, :SIGINT
1784
+ if defined?(SESSION) && SESSION.active? && SESSION.intercept_trap_sigint?
1785
+ return SESSION.save_int_trap(command.empty? ? command_proc : command.first)
1786
+ end
1787
+ end
1788
+
1789
+ super
1790
+ end
1791
+ end
1792
+
1793
+ if RUBY_VERSION >= '3.0.0'
1794
+ module ::Kernel
1795
+ prepend ForkInterceptor
1796
+ prepend TrapInterceptor
1797
+ end
1798
+ else
1799
+ class ::Object
1800
+ include ForkInterceptor
1801
+ include TrapInterceptor
1802
+ end
1803
+ end
1804
+
1805
+ module ::Kernel
1806
+ class << self
1807
+ prepend ForkInterceptor
1808
+ prepend TrapInterceptor
1809
+ end
1680
1810
  end
1681
1811
 
1682
1812
  module ::Process
@@ -1684,10 +1814,16 @@ module DEBUGGER__
1684
1814
  prepend ForkInterceptor
1685
1815
  end
1686
1816
  end
1817
+
1818
+ module ::Signal
1819
+ class << self
1820
+ prepend TrapInterceptor
1821
+ end
1822
+ end
1687
1823
  end
1688
1824
 
1689
- class Binding
1690
- def break pre: nil, do: nil
1825
+ module Kernel
1826
+ def debugger pre: nil, do: nil
1691
1827
  return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
1692
1828
 
1693
1829
  if pre || (do_expr = binding.local_variable_get(:do))
@@ -1697,20 +1833,10 @@ class Binding
1697
1833
  ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
1698
1834
  self
1699
1835
  end
1700
- alias b break
1701
1836
  end
1702
1837
 
1703
- module Kernel
1704
- if RUBY_VERSION >= '2.7.0'
1705
- eval <<~RUBY, binding, __FILE__, __LINE__
1706
- def debugger(...)
1707
- binding.break(...)
1708
- end
1709
- RUBY
1710
- else
1711
- def debugger pre: nil, do: nil
1712
- b = binding
1713
- b.break pre: pre, do: b.local_variable_get(:do)
1714
- end
1715
- end
1838
+ class Binding
1839
+ alias break debugger
1840
+ alias b debugger
1716
1841
  end
1842
+
@@ -20,9 +20,11 @@ module DEBUGGER__
20
20
 
21
21
  class ThreadClient
22
22
  def self.current
23
- Thread.current[:DEBUGGER__ThreadClient] || begin
24
- tc = ::DEBUGGER__::SESSION.thread_client
25
- Thread.current[:DEBUGGER__ThreadClient] = tc
23
+ if thc = Thread.current[:DEBUGGER__ThreadClient]
24
+ thc
25
+ else
26
+ thc = SESSION.thread_client
27
+ Thread.current[:DEBUGGER__ThreadClient] = thc
26
28
  end
27
29
  end
28
30
 
@@ -115,7 +117,7 @@ module DEBUGGER__
115
117
  # TODO: there is waiting -> waiting
116
118
  # raise "#{mode} is given, but #{mode}" unless self.running?
117
119
  else
118
- raise
120
+ raise "unknown mode: #{mode}"
119
121
  end
120
122
 
121
123
  @mode = mode
@@ -138,7 +140,11 @@ module DEBUGGER__
138
140
  end
139
141
 
140
142
  def inspect
141
- "#<DBG:TC #{self.id}:#{@mode}@#{@thread.backtrace[-1]}>"
143
+ if bt = @thread.backtrace
144
+ "#<DBG:TC #{self.id}:#{@mode}@#{bt[-1]}>"
145
+ else # bt can be nil
146
+ "#<DBG:TC #{self.id}:#{@mode}>"
147
+ end
142
148
  end
143
149
 
144
150
  def to_s
@@ -194,10 +200,6 @@ module DEBUGGER__
194
200
  wait_next_action
195
201
  end
196
202
 
197
- def on_thread_begin th
198
- wait_reply [:thread_begin, th]
199
- end
200
-
201
203
  def on_load iseq, eval_src
202
204
  wait_reply [:load, iseq, eval_src]
203
205
  end
@@ -340,7 +342,8 @@ module DEBUGGER__
340
342
  begin
341
343
  @success_last_eval = false
342
344
 
343
- b = current_frame.eval_binding
345
+ b = current_frame&.eval_binding || TOPLEVEL_BINDING
346
+
344
347
  result = if b
345
348
  f, _l = b.source_location
346
349
  b.eval(src, "(rdbg)/#{f}")
@@ -659,18 +662,17 @@ module DEBUGGER__
659
662
 
660
663
  def wait_next_action_
661
664
  # assertions
662
- raise "@mode is #{@mode}" unless @mode == :waiting
665
+ raise "@mode is #{@mode}" if !waiting?
663
666
 
664
667
  unless SESSION.active?
665
668
  pp caller
666
669
  set_mode :running
667
670
  return
668
671
  end
669
- # SESSION.check_forked
670
672
 
671
673
  while true
672
674
  begin
673
- set_mode :waiting if @mode != :waiting
675
+ set_mode :waiting if !waiting?
674
676
  cmds = @q_cmd.pop
675
677
  # pp [self, cmds: cmds]
676
678
  break unless cmds
@@ -779,6 +781,13 @@ module DEBUGGER__
779
781
  end
780
782
  when :call
781
783
  result = frame_eval(eval_src)
784
+ when :irb
785
+ begin
786
+ result = frame_eval('binding.irb')
787
+ ensure
788
+ # workaround: https://github.com/ruby/debug/issues/308
789
+ Reline.prompt_proc = nil if defined? Reline
790
+ end
782
791
  when :display, :try_display
783
792
  failed_results = []
784
793
  eval_src.each_with_index{|src, i|
data/lib/debug/tracer.rb CHANGED
@@ -14,7 +14,7 @@ module DEBUGGER__
14
14
  end
15
15
  end
16
16
 
17
- attr_reader :type
17
+ attr_reader :type, :key
18
18
 
19
19
  def initialize ui, pattern: nil, into: nil
20
20
  if /\ADEBUGGER__::(([A-Z][a-z]+?)[A-Z][a-z]+)/ =~ self.class.name
@@ -37,6 +37,8 @@ module DEBUGGER__
37
37
  @output = ui
38
38
  end
39
39
 
40
+ @key = [@type, @pattern, @into].freeze
41
+
40
42
  enable
41
43
  end
42
44
 
@@ -95,6 +97,8 @@ module DEBUGGER__
95
97
  end
96
98
 
97
99
  def minfo tp
100
+ return "block{}" if tp.event == :b_call
101
+
98
102
  klass = tp.defined_class
99
103
 
100
104
  if klass.singleton_class?
@@ -172,6 +176,7 @@ module DEBUGGER__
172
176
  @obj_id = obj_id
173
177
  @obj_inspect = obj_inspect
174
178
  super(ui, **kw)
179
+ @key = [@type, @obj_id, @pattern, @into].freeze
175
180
  end
176
181
 
177
182
  def description
@@ -201,7 +206,7 @@ module DEBUGGER__
201
206
  end
202
207
 
203
208
  out tp, " #{colorized_obj_inspect} receives #{colorize_blue(method_info)}"
204
- else
209
+ elsif !tp.parameters.empty?
205
210
  b = tp.binding
206
211
  method_info = colorize_blue(minfo(tp))
207
212
 
data/lib/debug/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DEBUGGER__
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: debug
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Sasada
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-14 00:00:00.000000000 Z
11
+ date: 2021-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: irb