Skip to content

Commit 4198723

Browse files
committed
support postmortem mode (config)
postmortem mode enables to see an environment (backtrace, local variables and so on) when it exit by some exception. ``` DEBUGGER: Session start (pid: 29868) [1, 8] in target.rb 1| => 2| def foo a 3| raise 'foo' 4| end 5| 6| foo(10) 7| 8| __END__ =>#0 <main> at target.rb:2 (rdbg:commands) config postmortem=true postmortem = true # CONTROL: Enable postmortem debug (default: false) (rdbg:commands) c Enter postmortem mode with #<RuntimeError: foo> target.rb:3:in `foo' target.rb:6:in `<main>' [1, 8] in target.rb 1| 2| def foo a => 3| raise 'foo' 4| end 5| 6| foo(10) 7| 8| __END__ =>#0 Object#foo(a=10) at target.rb:3 #1 <main> at target.rb:6 (rdbg:postmortem) i %self = main a = 10 ```
1 parent 5e7cba4 commit 4198723

7 files changed

Lines changed: 99 additions & 11 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ config set no_color true
363363
* `RUBY_DEBUG_SKIP_PATH` (`skip_path`): Skip showing/entering frames for given paths (default: [])
364364
* `RUBY_DEBUG_SKIP_NOSRC` (`skip_nosrc`): Skip on no source code lines (default: false)
365365
* `RUBY_DEBUG_KEEP_ALLOC_SITE` (`keep_alloc_site`): Keep allocation site and p, pp shows it (default: false)
366+
* `RUBY_DEBUG_POSTMORTEM` (`postmortem`): Enable postmortem debug (default: false)
366367

367368
* BOOT
368369
* `RUBY_DEBUG_NONSTOP` (`nonstop`): Nonstop mode

lib/debug/config.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module DEBUGGER__
1515
skip_path: ['RUBY_DEBUG_SKIP_PATH', "CONTROL: Skip showing/entering frames for given paths (default: [])", :path],
1616
skip_nosrc: ['RUBY_DEBUG_SKIP_NOSRC', "CONTROL: Skip on no source code lines (default: false)", :bool],
1717
keep_alloc_site:['RUBY_DEBUG_KEEP_ALLOC_SITE',"CONTROL: Keep allocation site and p, pp shows it (default: false)", :bool],
18+
postmortem: ['RUBY_DEBUG_POSTMORTEM', "CONTROL: Enable postmortem debug (default: false)", :bool],
1819

1920
# boot setting
2021
nonstop: ['RUBY_DEBUG_NONSTOP', "BOOT: Nonstop mode", :bool],
@@ -99,6 +100,10 @@ def update conf
99100
else
100101
# ignore
101102
end
103+
104+
if !old_conf[:postmortem] != !conf[:postmortem]
105+
SESSION.postmortem = conf[:postmortem]
106+
end
102107
end
103108

104109
private def config

lib/debug/console.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def get_command line
4444
end
4545
end
4646

47-
def readline prompt = "(rdbg) "
47+
def readline prompt
4848
readline_setup prompt
4949
Reline.readmultiline(prompt, true){ true }
5050
end
@@ -71,14 +71,14 @@ def readline_setup
7171
}
7272
end
7373

74-
def readline
74+
def readline prompt
7575
readline_setup
76-
Readline.readline("(rdbg) ", true)
76+
Readline.readline(prompt, true)
7777
end
7878

7979
rescue LoadError
80-
def readline
81-
print "(rdbg) "
80+
def readline prompt
81+
print prompt
8282
gets
8383
end
8484
end

lib/debug/local.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ def puts str = nil
5959
end
6060
end
6161

62-
def readline
62+
def readline prompt = '(rdbg)'
6363
setup_interrupt do
64-
(@console.readline || 'quit').strip
64+
(@console.readline(prompt) || 'quit').strip
6565
end
6666
end
6767

lib/debug/server.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def puts str = nil
195195
end
196196
end
197197

198-
def readline
198+
def readline prompt
199199
(sock do |s|
200200
s.puts "input"
201201
sleep 0.01 until @q_msg

lib/debug/session.rb

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def first_line
5555

5656
module DEBUGGER__
5757
PresetCommand = Struct.new(:commands, :source, :auto_continue)
58+
class PostmortemError < RuntimeError; end
5859

5960
class Session
6061
def initialize ui
@@ -74,6 +75,8 @@ def initialize ui
7475
@tc = nil
7576
@tc_id = 0
7677
@preset_command = nil
78+
@postmortem_hook = nil
79+
@postmortem = false
7780

7881
@frame_map = {} # {id => [threadId, frame_depth]} for DAP
7982
@var_map = {1 => [:globals], } # {id => ...} for DAP
@@ -245,6 +248,14 @@ def wait_command_loop tc
245248
@tc = nil
246249
end
247250

251+
def prompt
252+
if @postmortem
253+
'(rdbg:postmortem) '
254+
else
255+
'(rdbg) '
256+
end
257+
end
258+
248259
def wait_command
249260
if @preset_command
250261
if @preset_command.commands.empty?
@@ -262,7 +273,7 @@ def wait_command
262273
end
263274
else
264275
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
265-
line = @ui.readline
276+
line = @ui.readline prompt
266277
end
267278

268279
case line
@@ -298,18 +309,21 @@ def process_command line
298309
# * Step in. Resume the program until next breakable point.
299310
when 's', 'step'
300311
cancel_auto_continue
312+
check_postmortem
301313
@tc << [:step, :in]
302314

303315
# * `n[ext]`
304316
# * Step over. Resume the program until next line.
305317
when 'n', 'next'
306318
cancel_auto_continue
319+
check_postmortem
307320
@tc << [:step, :next]
308321

309322
# * `fin[ish]`
310323
# * Finish this frame. Resume the program until the current frame is finished.
311324
when 'fin', 'finish'
312325
cancel_auto_continue
326+
check_postmortem
313327
@tc << [:step, :finish]
314328

315329
# * `c[ontinue]`
@@ -370,6 +384,8 @@ def process_command line
370384
# * break if: `<expr>` is true at any lines.
371385
# * Note that this feature is super slow.
372386
when 'b', 'break'
387+
check_postmortem
388+
373389
if arg == nil
374390
show_bps
375391
return :retry
@@ -386,6 +402,7 @@ def process_command line
386402

387403
# skip
388404
when 'bv'
405+
check_postmortem
389406
require 'json'
390407

391408
h = Hash.new{|h, k| h[k] = []}
@@ -412,6 +429,8 @@ def process_command line
412429
# * `catch <Error>`
413430
# * Set breakpoint on raising `<Error>`.
414431
when 'catch'
432+
check_postmortem
433+
415434
if arg
416435
bp = add_catch_breakpoint arg
417436
show_bps bp if bp
@@ -424,6 +443,8 @@ def process_command line
424443
# * Stop the execution when the result of current scope's `@ivar` is changed.
425444
# * Note that this feature is super slow.
426445
when 'wat', 'watch'
446+
check_postmortem
447+
427448
if arg && arg.match?(/\A@\w+/)
428449
@tc << [:breakpoint, :watch, arg]
429450
else
@@ -436,6 +457,8 @@ def process_command line
436457
# * `del[ete] <bpnum>`
437458
# * delete specified breakpoint.
438459
when 'del', 'delete'
460+
check_postmortem
461+
439462
bp =
440463
case arg
441464
when nil
@@ -784,6 +807,9 @@ def process_command line
784807
return :retry
785808
rescue SystemExit
786809
raise
810+
rescue PostmortemError => e
811+
@ui.puts e.message
812+
return :retry
787813
rescue Exception => e
788814
@ui.puts "[REPL ERROR] #{e.inspect}"
789815
@ui.puts e.backtrace.map{|e| ' ' + e}
@@ -1250,6 +1276,56 @@ def check_forked
12501276
raise 'DEBUGGER: stop at forked process is not supported yet.'
12511277
end
12521278
end
1279+
1280+
def check_postmortem
1281+
if @postmortem
1282+
raise PostmortemError, "Can not use this command on postmortem mode."
1283+
end
1284+
end
1285+
1286+
def enter_postmortem_session frames
1287+
@postmortem = true
1288+
ThreadClient.current.on_suspend :postmortem, postmortem_frames: frames
1289+
ensure
1290+
@postmortem = false
1291+
end
1292+
1293+
def postmortem=(is_enable)
1294+
if is_enable
1295+
unless @postmortem_hook
1296+
@postmortem_hook = TracePoint.new(:raise){|tp|
1297+
exc = tp.raised_exception
1298+
frames = DEBUGGER__.capture_frames(__dir__)
1299+
exc.instance_variable_set(:@postmortem_frames, frames)
1300+
}
1301+
at_exit{
1302+
@postmortem_hook.disable
1303+
if CONFIG[:postmortem] && (exc = $!) != nil
1304+
begin
1305+
@ui.puts "Enter postmortem mode with #{exc.inspect}"
1306+
@ui.puts exc.backtrace.map{|e| ' ' + e}
1307+
@ui.puts "\n"
1308+
1309+
enter_postmortem_session exc.instance_variable_get(:@postmortem_frames)
1310+
rescue SystemExit
1311+
exit!
1312+
rescue Exception => e
1313+
@ui = STDERR unless @ui
1314+
@ui.puts "Error while postmortem console: #{e.inspect}"
1315+
end
1316+
end
1317+
}
1318+
end
1319+
1320+
if !@postmortem_hook.enabled?
1321+
@postmortem_hook.enable
1322+
end
1323+
else
1324+
if @postmortem_hook && @postmortem_hook.enabled?
1325+
@postmortem_hook.disable
1326+
end
1327+
end
1328+
end
12531329
end
12541330

12551331
class UI_Base

lib/debug/thread_client.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,15 @@ def on_trace trace_id, msg
170170
wait_next_action
171171
end
172172

173-
def on_suspend event, tp = nil, bp: nil, sig: nil
173+
def on_suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil
174174
@current_frame_index = 0
175-
@target_frames = DEBUGGER__.capture_frames __dir__
175+
176+
if postmortem_frames
177+
@target_frames = postmortem_frames
178+
@postmortem = true
179+
else
180+
@target_frames = DEBUGGER__.capture_frames(__dir__)
181+
end
176182

177183
cf = @target_frames.first
178184
if cf

0 commit comments

Comments
 (0)