debug 1.0.0 → 1.2.2
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 +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.github/ISSUE_TEMPLATE/custom.md +10 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
- data/README.md +17 -10
- data/Rakefile +10 -7
- data/TODO.md +2 -1
- data/debug.gemspec +1 -1
- data/ext/debug/debug.c +5 -0
- data/lib/debug/breakpoint.rb +14 -5
- data/lib/debug/color.rb +25 -3
- data/lib/debug/config.rb +18 -3
- data/lib/debug/console.rb +10 -2
- data/lib/debug/frame_info.rb +5 -5
- data/lib/debug/local.rb +21 -24
- data/lib/debug/server.rb +8 -7
- data/lib/debug/server_dap.rb +30 -19
- data/lib/debug/session.rb +244 -98
- data/lib/debug/thread_client.rb +22 -13
- data/lib/debug/tracer.rb +8 -3
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +10 -8
- metadata +11 -8
data/lib/debug/session.rb
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# skip to load debugger for bundle exec
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
|
6
|
+
trace_var(:$0) do |file|
|
|
7
|
+
trace_var(:$0, nil)
|
|
8
|
+
if /-r (#{__dir__}\S+)/ =~ ENV['RUBYOPT']
|
|
9
|
+
lib = $1
|
|
10
|
+
$LOADED_FEATURES.delete_if{|path| path.start_with?(__dir__)}
|
|
11
|
+
ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = file
|
|
12
|
+
require lib
|
|
13
|
+
ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return
|
|
18
|
+
end
|
|
5
19
|
|
|
6
20
|
require_relative 'config'
|
|
7
21
|
require_relative 'thread_client'
|
|
@@ -9,6 +23,10 @@ require_relative 'source_repository'
|
|
|
9
23
|
require_relative 'breakpoint'
|
|
10
24
|
require_relative 'tracer'
|
|
11
25
|
|
|
26
|
+
# To prevent loading old lib/debug.rb in Ruby 2.6 to 3.0
|
|
27
|
+
$LOADED_FEATURES << 'debug.rb'
|
|
28
|
+
require 'debug' # invalidate the $LOADED_FEATURE cache
|
|
29
|
+
|
|
12
30
|
require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
|
|
13
31
|
|
|
14
32
|
class RubyVM::InstructionSequence
|
|
@@ -68,7 +86,7 @@ module DEBUGGER__
|
|
|
68
86
|
# [:watch, ivar] => WatchIVarBreakpoint
|
|
69
87
|
# [:check, expr] => CheckBreakpoint
|
|
70
88
|
#
|
|
71
|
-
@tracers =
|
|
89
|
+
@tracers = {}
|
|
72
90
|
@th_clients = nil # {Thread => ThreadClient}
|
|
73
91
|
@q_evt = Queue.new
|
|
74
92
|
@displays = []
|
|
@@ -78,17 +96,22 @@ module DEBUGGER__
|
|
|
78
96
|
@postmortem_hook = nil
|
|
79
97
|
@postmortem = false
|
|
80
98
|
@thread_stopper = nil
|
|
99
|
+
@intercept_trap_sigint = false
|
|
100
|
+
@intercepted_sigint_cmd = 'DEFAULT'
|
|
81
101
|
|
|
82
102
|
@frame_map = {} # {id => [threadId, frame_depth]} for DAP
|
|
83
103
|
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
|
84
104
|
@src_map = {} # {id => src}
|
|
85
105
|
|
|
106
|
+
@tp_thread_begin = nil
|
|
86
107
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
|
87
108
|
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
|
88
109
|
}
|
|
89
110
|
@tp_load_script.enable
|
|
90
111
|
|
|
91
112
|
activate
|
|
113
|
+
|
|
114
|
+
self.postmortem = CONFIG[:postmortem]
|
|
92
115
|
end
|
|
93
116
|
|
|
94
117
|
def active?
|
|
@@ -99,40 +122,41 @@ module DEBUGGER__
|
|
|
99
122
|
@bps.has_key? [file, line]
|
|
100
123
|
end
|
|
101
124
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
106
133
|
end
|
|
107
|
-
end
|
|
108
134
|
|
|
109
|
-
|
|
135
|
+
q = Queue.new
|
|
110
136
|
@session_server = Thread.new do
|
|
111
137
|
Thread.current.name = 'DEBUGGER__::SESSION@server'
|
|
112
138
|
Thread.current.abort_on_exception = true
|
|
113
|
-
session_server_main
|
|
114
|
-
end
|
|
115
139
|
|
|
116
|
-
|
|
140
|
+
# Thread management
|
|
141
|
+
setup_threads
|
|
142
|
+
thc = thread_client Thread.current
|
|
143
|
+
thc.is_management
|
|
117
144
|
|
|
118
|
-
|
|
119
|
-
|
|
145
|
+
if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
|
|
146
|
+
thc.is_management
|
|
147
|
+
end
|
|
120
148
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@
|
|
125
|
-
end
|
|
149
|
+
@tp_thread_begin = TracePoint.new(:thread_begin) do |tp|
|
|
150
|
+
thread_client
|
|
151
|
+
end
|
|
152
|
+
@tp_thread_begin.enable
|
|
126
153
|
|
|
127
|
-
|
|
128
|
-
|
|
154
|
+
# session start
|
|
155
|
+
q << true
|
|
156
|
+
session_server_main
|
|
129
157
|
end
|
|
130
158
|
|
|
131
|
-
|
|
132
|
-
th = Thread.current
|
|
133
|
-
ThreadClient.current.on_thread_begin th
|
|
134
|
-
}
|
|
135
|
-
@tp_thread_begin.enable
|
|
159
|
+
q.pop
|
|
136
160
|
end
|
|
137
161
|
|
|
138
162
|
def deactivate
|
|
@@ -142,14 +166,14 @@ module DEBUGGER__
|
|
|
142
166
|
@tp_thread_begin.disable
|
|
143
167
|
@bps.each{|k, bp| bp.disable}
|
|
144
168
|
@th_clients.each{|th, thc| thc.close}
|
|
145
|
-
@tracers.each{|t| t.disable}
|
|
169
|
+
@tracers.values.each{|t| t.disable}
|
|
146
170
|
@q_evt.close
|
|
147
171
|
@ui&.deactivate
|
|
148
172
|
@ui = nil
|
|
149
173
|
end
|
|
150
174
|
|
|
151
175
|
def reset_ui ui
|
|
152
|
-
@ui.
|
|
176
|
+
@ui.deactivate
|
|
153
177
|
@ui = ui
|
|
154
178
|
end
|
|
155
179
|
|
|
@@ -159,11 +183,18 @@ module DEBUGGER__
|
|
|
159
183
|
|
|
160
184
|
def session_server_main
|
|
161
185
|
while evt = pop_event
|
|
162
|
-
#
|
|
186
|
+
# variable `@internal_info` is only used for test
|
|
163
187
|
tc, output, ev, @internal_info, *ev_args = evt
|
|
164
188
|
output.each{|str| @ui.puts str}
|
|
165
189
|
|
|
166
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
|
+
|
|
167
198
|
when :init
|
|
168
199
|
wait_command_loop tc
|
|
169
200
|
|
|
@@ -175,26 +206,25 @@ module DEBUGGER__
|
|
|
175
206
|
|
|
176
207
|
when :trace
|
|
177
208
|
trace_id, msg = ev_args
|
|
178
|
-
if t = @tracers.find{|t| t.object_id == trace_id}
|
|
209
|
+
if t = @tracers.values.find{|t| t.object_id == trace_id}
|
|
179
210
|
t.puts msg
|
|
180
211
|
end
|
|
181
212
|
tc << :continue
|
|
182
213
|
|
|
183
|
-
when :thread_begin
|
|
184
|
-
th = ev_args.shift
|
|
185
|
-
on_thread_begin th
|
|
186
|
-
@ui.event :thread_begin, th
|
|
187
|
-
tc << :continue
|
|
188
|
-
|
|
189
214
|
when :suspend
|
|
190
215
|
case ev_args.first
|
|
191
216
|
when :breakpoint
|
|
192
217
|
bp, i = bp_index ev_args[1]
|
|
193
|
-
@ui.event :suspend_bp, i, bp
|
|
218
|
+
@ui.event :suspend_bp, i, bp, tc.id
|
|
194
219
|
when :trap
|
|
195
|
-
@ui.event :suspend_trap, ev_args[1]
|
|
220
|
+
@ui.event :suspend_trap, sig = ev_args[1], tc.id
|
|
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
|
|
196
226
|
else
|
|
197
|
-
@ui.event :suspended
|
|
227
|
+
@ui.event :suspended, tc.id
|
|
198
228
|
end
|
|
199
229
|
|
|
200
230
|
if @displays.empty?
|
|
@@ -228,8 +258,7 @@ module DEBUGGER__
|
|
|
228
258
|
obj_id = ev_args[1]
|
|
229
259
|
obj_inspect = ev_args[2]
|
|
230
260
|
opt = ev_args[3]
|
|
231
|
-
|
|
232
|
-
@ui.puts "Enable #{t.to_s}"
|
|
261
|
+
add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
|
|
233
262
|
else
|
|
234
263
|
# ignore
|
|
235
264
|
end
|
|
@@ -414,6 +443,29 @@ module DEBUGGER__
|
|
|
414
443
|
when 'kill!'
|
|
415
444
|
exit! (arg || 1).to_i
|
|
416
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
|
+
|
|
417
469
|
### Breakpoint
|
|
418
470
|
|
|
419
471
|
# * `b[reak]`
|
|
@@ -598,7 +650,7 @@ module DEBUGGER__
|
|
|
598
650
|
# * Show information about the current frame (local variables)
|
|
599
651
|
# * It includes `self` as `%self` and a return value as `%return`.
|
|
600
652
|
# * `i[nfo] i[var[s]]` or `i[nfo] instance`
|
|
601
|
-
# * Show information about
|
|
653
|
+
# * Show information about instance variables about `self`.
|
|
602
654
|
# * `i[nfo] c[onst[s]]` or `i[nfo] constant[s]`
|
|
603
655
|
# * Show information about accessible constants except toplevel constants.
|
|
604
656
|
# * `i[nfo] g[lobal[s]]`
|
|
@@ -722,7 +774,7 @@ module DEBUGGER__
|
|
|
722
774
|
@ui.puts "not supported on the remote console."
|
|
723
775
|
return :retry
|
|
724
776
|
end
|
|
725
|
-
@tc << [:eval, :
|
|
777
|
+
@tc << [:eval, :irb]
|
|
726
778
|
|
|
727
779
|
# don't repeat irb command
|
|
728
780
|
@repl_prev_line = nil
|
|
@@ -760,32 +812,29 @@ module DEBUGGER__
|
|
|
760
812
|
case arg
|
|
761
813
|
when nil
|
|
762
814
|
@ui.puts 'Tracers:'
|
|
763
|
-
@tracers.each_with_index{|t, i|
|
|
815
|
+
@tracers.values.each_with_index{|t, i|
|
|
764
816
|
@ui.puts "* \##{i} #{t}"
|
|
765
817
|
}
|
|
766
818
|
@ui.puts
|
|
767
819
|
return :retry
|
|
768
820
|
|
|
769
821
|
when /\Aline\z/
|
|
770
|
-
|
|
771
|
-
@ui.puts "Enable #{t.to_s}"
|
|
822
|
+
add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
|
|
772
823
|
return :retry
|
|
773
824
|
|
|
774
825
|
when /\Acall\z/
|
|
775
|
-
|
|
776
|
-
@ui.puts "Enable #{t.to_s}"
|
|
826
|
+
add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
|
|
777
827
|
return :retry
|
|
778
828
|
|
|
779
829
|
when /\Aexception\z/
|
|
780
|
-
|
|
781
|
-
@ui.puts "Enable #{t.to_s}"
|
|
830
|
+
add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
|
|
782
831
|
return :retry
|
|
783
832
|
|
|
784
833
|
when /\Aobject\s+(.+)/
|
|
785
834
|
@tc << [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
|
786
835
|
|
|
787
836
|
when /\Aoff\s+(\d+)\z/
|
|
788
|
-
if t = @tracers[$1.to_i]
|
|
837
|
+
if t = @tracers.values[$1.to_i]
|
|
789
838
|
t.disable
|
|
790
839
|
@ui.puts "Disable #{t.to_s}"
|
|
791
840
|
else
|
|
@@ -794,7 +843,7 @@ module DEBUGGER__
|
|
|
794
843
|
return :retry
|
|
795
844
|
|
|
796
845
|
when /\Aoff(\s+(line|call|exception|object))?\z/
|
|
797
|
-
@tracers.each{|t|
|
|
846
|
+
@tracers.values.each{|t|
|
|
798
847
|
if $2.nil? || t.type == $2
|
|
799
848
|
t.disable
|
|
800
849
|
@ui.puts "Disable #{t.to_s}"
|
|
@@ -1172,6 +1221,20 @@ module DEBUGGER__
|
|
|
1172
1221
|
@ui.puts e.message
|
|
1173
1222
|
end
|
|
1174
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
|
+
|
|
1175
1238
|
# threads
|
|
1176
1239
|
|
|
1177
1240
|
def update_thread_list
|
|
@@ -1224,10 +1287,6 @@ module DEBUGGER__
|
|
|
1224
1287
|
thread_list
|
|
1225
1288
|
end
|
|
1226
1289
|
|
|
1227
|
-
def thread_client_create th
|
|
1228
|
-
@th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
|
|
1229
|
-
end
|
|
1230
|
-
|
|
1231
1290
|
def setup_threads
|
|
1232
1291
|
@th_clients = {}
|
|
1233
1292
|
|
|
@@ -1238,18 +1297,38 @@ module DEBUGGER__
|
|
|
1238
1297
|
|
|
1239
1298
|
def on_thread_begin th
|
|
1240
1299
|
if @th_clients.has_key? th
|
|
1241
|
-
# OK
|
|
1242
|
-
else
|
|
1243
1300
|
# TODO: NG?
|
|
1301
|
+
else
|
|
1244
1302
|
thread_client_create th
|
|
1245
1303
|
end
|
|
1246
1304
|
end
|
|
1247
1305
|
|
|
1248
|
-
def
|
|
1249
|
-
|
|
1250
|
-
|
|
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]
|
|
1251
1326
|
else
|
|
1252
|
-
|
|
1327
|
+
if Thread.current == @session_server
|
|
1328
|
+
thread_client_create th
|
|
1329
|
+
else
|
|
1330
|
+
ask_thread_client th
|
|
1331
|
+
end
|
|
1253
1332
|
end
|
|
1254
1333
|
end
|
|
1255
1334
|
|
|
@@ -1368,7 +1447,10 @@ module DEBUGGER__
|
|
|
1368
1447
|
end
|
|
1369
1448
|
end
|
|
1370
1449
|
|
|
1371
|
-
def enter_postmortem_session
|
|
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)
|
|
1372
1454
|
@postmortem = true
|
|
1373
1455
|
ThreadClient.current.suspend :postmortem, postmortem_frames: frames
|
|
1374
1456
|
ensure
|
|
@@ -1381,7 +1463,7 @@ module DEBUGGER__
|
|
|
1381
1463
|
@postmortem_hook = TracePoint.new(:raise){|tp|
|
|
1382
1464
|
exc = tp.raised_exception
|
|
1383
1465
|
frames = DEBUGGER__.capture_frames(__dir__)
|
|
1384
|
-
exc.instance_variable_set(:@
|
|
1466
|
+
exc.instance_variable_set(:@__debugger_postmortem_frames, frames)
|
|
1385
1467
|
}
|
|
1386
1468
|
at_exit{
|
|
1387
1469
|
@postmortem_hook.disable
|
|
@@ -1393,7 +1475,7 @@ module DEBUGGER__
|
|
|
1393
1475
|
@ui.puts exc.backtrace.map{|e| ' ' + e}
|
|
1394
1476
|
@ui.puts "\n"
|
|
1395
1477
|
|
|
1396
|
-
enter_postmortem_session exc
|
|
1478
|
+
enter_postmortem_session exc
|
|
1397
1479
|
rescue SystemExit
|
|
1398
1480
|
exit!
|
|
1399
1481
|
rescue Exception => e
|
|
@@ -1413,6 +1495,30 @@ module DEBUGGER__
|
|
|
1413
1495
|
end
|
|
1414
1496
|
end
|
|
1415
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
|
|
1416
1522
|
end
|
|
1417
1523
|
|
|
1418
1524
|
class UI_Base
|
|
@@ -1507,7 +1613,13 @@ module DEBUGGER__
|
|
|
1507
1613
|
|
|
1508
1614
|
def self.setup_initial_suspend
|
|
1509
1615
|
if !CONFIG[:nonstop]
|
|
1510
|
-
|
|
1616
|
+
case
|
|
1617
|
+
when CONFIG[:stop_at_load]
|
|
1618
|
+
add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, hook_call: false
|
|
1619
|
+
nil # stop here
|
|
1620
|
+
when path = ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH']
|
|
1621
|
+
add_line_breakpoint path, 0, oneshot: true, hook_call: false
|
|
1622
|
+
when loc = ::DEBUGGER__.require_location
|
|
1511
1623
|
# require 'debug/start' or 'debug'
|
|
1512
1624
|
add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
|
|
1513
1625
|
else
|
|
@@ -1519,29 +1631,8 @@ module DEBUGGER__
|
|
|
1519
1631
|
|
|
1520
1632
|
class << self
|
|
1521
1633
|
define_method :initialize_session do |ui|
|
|
1522
|
-
DEBUGGER__.
|
|
1523
|
-
|
|
1634
|
+
DEBUGGER__.info "Session start (pid: #{Process.pid})"
|
|
1524
1635
|
::DEBUGGER__.const_set(:SESSION, Session.new(ui))
|
|
1525
|
-
|
|
1526
|
-
# default breakpoints
|
|
1527
|
-
|
|
1528
|
-
# ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
|
|
1529
|
-
|
|
1530
|
-
Binding.module_eval do
|
|
1531
|
-
def break pre: nil, do: nil
|
|
1532
|
-
return unless SESSION.active?
|
|
1533
|
-
|
|
1534
|
-
if pre || (do_expr = binding.local_variable_get(:do))
|
|
1535
|
-
cmds = ['binding.break', pre, do_expr]
|
|
1536
|
-
end
|
|
1537
|
-
|
|
1538
|
-
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
|
|
1539
|
-
true
|
|
1540
|
-
end
|
|
1541
|
-
alias b break
|
|
1542
|
-
# alias bp break
|
|
1543
|
-
end
|
|
1544
|
-
|
|
1545
1636
|
load_rc
|
|
1546
1637
|
end
|
|
1547
1638
|
end
|
|
@@ -1599,14 +1690,6 @@ module DEBUGGER__
|
|
|
1599
1690
|
end
|
|
1600
1691
|
end
|
|
1601
1692
|
|
|
1602
|
-
LOG_LEVELS = {
|
|
1603
|
-
UNKNOWN: 0,
|
|
1604
|
-
FATAL: 1,
|
|
1605
|
-
ERROR: 2,
|
|
1606
|
-
WARN: 3,
|
|
1607
|
-
INFO: 4,
|
|
1608
|
-
}.freeze
|
|
1609
|
-
|
|
1610
1693
|
def self.warn msg
|
|
1611
1694
|
log :WARN, msg
|
|
1612
1695
|
end
|
|
@@ -1651,7 +1734,18 @@ module DEBUGGER__
|
|
|
1651
1734
|
|
|
1652
1735
|
at_exit{
|
|
1653
1736
|
trap(:SIGINT, :IGNORE)
|
|
1654
|
-
|
|
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
|
|
1655
1749
|
}
|
|
1656
1750
|
}
|
|
1657
1751
|
child_hook = -> {
|
|
@@ -1683,8 +1777,36 @@ module DEBUGGER__
|
|
|
1683
1777
|
end
|
|
1684
1778
|
end
|
|
1685
1779
|
|
|
1686
|
-
|
|
1687
|
-
|
|
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
|
|
1688
1810
|
end
|
|
1689
1811
|
|
|
1690
1812
|
module ::Process
|
|
@@ -1692,5 +1814,29 @@ module DEBUGGER__
|
|
|
1692
1814
|
prepend ForkInterceptor
|
|
1693
1815
|
end
|
|
1694
1816
|
end
|
|
1817
|
+
|
|
1818
|
+
module ::Signal
|
|
1819
|
+
class << self
|
|
1820
|
+
prepend TrapInterceptor
|
|
1821
|
+
end
|
|
1822
|
+
end
|
|
1823
|
+
end
|
|
1824
|
+
|
|
1825
|
+
module Kernel
|
|
1826
|
+
def debugger pre: nil, do: nil
|
|
1827
|
+
return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
|
|
1828
|
+
|
|
1829
|
+
if pre || (do_expr = binding.local_variable_get(:do))
|
|
1830
|
+
cmds = ['binding.break', pre, do_expr]
|
|
1831
|
+
end
|
|
1832
|
+
|
|
1833
|
+
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, command: cmds
|
|
1834
|
+
self
|
|
1835
|
+
end
|
|
1836
|
+
end
|
|
1837
|
+
|
|
1838
|
+
class Binding
|
|
1839
|
+
alias break debugger
|
|
1840
|
+
alias b debugger
|
|
1695
1841
|
end
|
|
1696
1842
|
|
data/lib/debug/thread_client.rb
CHANGED
|
@@ -20,9 +20,11 @@ module DEBUGGER__
|
|
|
20
20
|
|
|
21
21
|
class ThreadClient
|
|
22
22
|
def self.current
|
|
23
|
-
Thread.current[:DEBUGGER__ThreadClient]
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
|
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}"
|
|
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
|
|
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|
|