debug 1.1.0 → 1.2.0

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