lib/debug/session.rb in debug-1.0.0.beta5 vs lib/debug/session.rb in debug-1.0.0.beta6
- old
+ new
@@ -1,6 +1,7 @@
-
+# frozen_string_literal: true
+
# skip to load debugger for bundle exec
return if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
require_relative 'config'
require_relative 'thread_client'
@@ -50,10 +51,12 @@
self.to_a[4][:code_location][0]
end
end
module DEBUGGER__
+ PresetCommand = Struct.new(:commands, :source, :auto_continue)
+
class Session
def initialize ui
@ui = ui
@sr = SourceRepository.new
@bps = {} # bp.key => bp
@@ -65,11 +68,11 @@
@th_clients = {} # {Thread => ThreadClient}
@q_evt = Queue.new
@displays = []
@tc = nil
@tc_id = 0
- @initial_commands = []
+ @preset_command = nil
@frame_map = {} # {id => [threadId, frame_depth]} for DAP
@var_map = {1 => [:globals], } # {id => ...} for DAP
@src_map = {} # {id => src}
@@ -96,17 +99,25 @@
end
}
@tp_thread_begin.enable
end
+ def reset_ui ui
+ @ui.close
+ @ui = ui
+ @management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
+ end
+
def session_server_main
while evt = @q_evt.pop
# varible `@internal_info` is only used for test
tc, output, ev, @internal_info, *ev_args = evt
output.each{|str| @ui.puts str}
case ev
+ when :init
+ wait_command_loop tc
when :load
iseq, src = ev_args
on_load iseq, src
@ui.event :load
tc << :continue
@@ -131,26 +142,22 @@
else
tc << [:eval, :display, @displays]
end
when :result
case ev_args.first
- when :watch
- bp = ev_args[1]
- @bps[bp.key] = bp
- show_bps bp
when :try_display
failed_results = ev_args[1]
if failed_results.size > 0
i, _msg = failed_results.last
if i+1 == @displays.size
@ui.puts "canceled: #{@displays.pop}"
end
end
- when :method_breakpoint
+ when :method_breakpoint, :watch_breakpoint
bp = ev_args[1]
if bp
- @bps[bp.key] = bp
+ add_breakpoint(bp)
show_bps bp
else
# can't make a bp
end
else
@@ -167,19 +174,28 @@
ensure
@bps.each{|k, bp| bp.disable}
@th_clients.each{|th, thc| thc.close}
end
- def add_initial_commands cmds
- cmds.each{|c|
- c.gsub('#.*', '').strip!
- @initial_commands << c unless c.empty?
- }
+ def add_preset_commands name, cmds, kick: true, continue: true
+ cs = cmds.map{|c|
+ c = c.strip.gsub(/\A\s*\#.*/, '').strip
+ c unless c.empty?
+ }.compact
+
+ unless cs.empty?
+ if @preset_command
+ @preset_command.commands += cs
+ else
+ @preset_command = PresetCommand.new(cs, name, continue)
+ end
+ ThreadClient.current.on_init name if kick
+ end
end
def source iseq
- if CONFIG[:use_colorize]
+ if !CONFIG[:no_color]
@sr.get_colored(iseq)
else
@sr.get(iseq)
end
end
@@ -205,16 +221,27 @@
ensure
@tc = nil
end
def wait_command
- if @initial_commands.empty?
+ if @preset_command
+ if @preset_command.commands.empty?
+ if @preset_command.auto_continue
+ @preset_command = nil
+ @tc << :continue
+ return
+ else
+ @preset_command = nil
+ return :retry
+ end
+ else
+ line = @preset_command.commands.shift
+ @ui.puts "(rdbg:#{@preset_command.source}) #{line}"
+ end
+ else
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
line = @ui.readline
- else
- line = @initial_commands.shift.strip
- @ui.puts "(rdbg:init) #{line}"
end
case line
when String
process_command line
@@ -245,25 +272,29 @@
### Control flow
# * `s[tep]`
# * Step in. Resume the program until next breakable point.
when 's', 'step'
+ cancel_auto_continue
@tc << [:step, :in]
# * `n[ext]`
# * Step over. Resume the program until next line.
when 'n', 'next'
+ cancel_auto_continue
@tc << [:step, :next]
# * `fin[ish]`
# * Finish this frame. Resume the program until the current frame is finished.
when 'fin', 'finish'
+ cancel_auto_continue
@tc << [:step, :finish]
# * `c[ontinue]`
# * Resume the program.
when 'c', 'continue'
+ cancel_auto_continue
@tc << :continue
# * `q[uit]` or `Ctrl-D`
# * Finish debugger (with the debuggee process on non-remote debugging).
when 'q', 'quit'
@@ -364,12 +395,12 @@
# * `watch @ivar`
# * Stop the execution when the result of current scope's `@ivar` is changed.
# * Note that this feature is super slow.
when 'wat', 'watch'
- if arg
- @tc << [:eval, :watch, arg]
+ if arg && arg.match?(/\A@\w+/)
+ @tc << [:breakpoint, :watch, arg]
else
show_bps
return :retry
end
@@ -479,13 +510,11 @@
# * Remove a specified display setting.
when 'undisplay'
case arg
when /(\d+)/
if @displays[n = $1.to_i]
- if ask "clear \##{n} #{@displays[n]}?"
- @displays.delete_at n
- end
+ @displays.delete_at n
end
@tc << [:eval, :display, @displays]
when nil
if ask "clear all?", 'N'
@displays.clear
@@ -558,10 +587,13 @@
@ui.puts "not supported on the remote console."
return :retry
end
@tc << [:eval, :call, 'binding.irb']
+ # don't repeat irb command
+ @repl_prev_line = nil
+
### Thread control
# * `th[read]`
# * Show all threads.
# * `th[read] <thnum>`
@@ -606,10 +638,16 @@
@ui.puts "[REPL ERROR] #{e.inspect}"
@ui.puts e.backtrace.map{|e| ' ' + e}
return :retry
end
+ def cancel_auto_continue
+ if @preset_command&.auto_continue
+ @preset_command.auto_continue = false
+ end
+ end
+
def show_help arg
DEBUGGER__.helps.each{|cat, cs|
cs.each{|ws, desc|
if ws.include? arg
@ui.puts desc
@@ -881,14 +919,20 @@
# breakpoint management
def add_breakpoint bp
if @bps.has_key? bp.key
- @ui.puts "duplicated breakpoint: #{bp}"
+ unless bp.duplicable?
+ @ui.puts "duplicated breakpoint: #{bp}"
+ bp.disable
+ end
else
@bps[bp.key] = bp
end
+
+ # don't repeat commands that add breakpoints
+ @repl_prev_line = nil
end
def rehash_bps
bps = @bps.values
@bps.clear
@@ -930,10 +974,11 @@
end
def add_line_breakpoint file, line, **kw
file = resolve_path(file)
bp = LineBreakpoint.new(file, line, **kw)
+
add_breakpoint bp
rescue Errno::ENOENT => e
@ui.puts e.message
end
@@ -1020,104 +1065,126 @@
nil
end
# start methods
- def self.console **kw
+ def self.start nonstop: false, **kw
set_config(kw)
- require_relative 'console'
+ unless defined? SESSION
+ require_relative 'console'
+ initialize_session UI_Console.new
+ end
- initialize_session UI_Console.new
-
- @prev_handler = trap(:SIGINT){
- ThreadClient.current.on_trap :SIGINT
- }
+ setup_initial_suspend unless nonstop
end
- def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_path: nil, sock_dir: nil, **kw
+ def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
set_config(kw)
if port
- open_tcp host: host, port: port
+ open_tcp host: host, port: port, nonstop: nonstop
else
- open_unix sock_path: sock_path, sock_dir: sock_dir
+ open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
end
end
- def self.open_tcp host: nil, port:, **kw
+ def self.open_tcp host: nil, port:, nonstop: false, **kw
set_config(kw)
require_relative 'server'
- initialize_session UI_TcpServer.new(host: host, port: port)
+
+ if defined? SESSION
+ SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
+ else
+ initialize_session UI_TcpServer.new(host: host, port: port)
+ end
+
+ setup_initial_suspend unless nonstop
end
- def self.open_unix sock_path: nil, sock_dir: nil, **kw
+ def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
set_config(kw)
require_relative 'server'
- initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
+
+ if defined? SESSION
+ SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
+ else
+ initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
+ end
+
+ setup_initial_suspend unless nonstop
end
# boot utilities
+ def self.setup_initial_suspend
+ if !::DEBUGGER__::CONFIG[:nonstop]
+ if loc = ::DEBUGGER__.require_location
+ # require 'debug/console' or 'debug'
+ add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
+ else
+ # -r
+ add_line_breakpoint $0, 1, oneshot: true, hook_call: false
+ end
+ end
+ end
+
class << self
define_method :initialize_session do |ui|
+ DEBUGGER__.warn "Session start (pid: #{Process.pid})"
+
::DEBUGGER__.const_set(:SESSION, Session.new(ui))
# default breakpoints
# ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
Binding.module_eval do
- def bp command: nil
+ def bp command: nil, nonstop: nil
if command
cmds = command.split(";;")
- SESSION.add_initial_commands cmds
+ # nonstop
+ # nil, true -> auto_continue
+ # false -> stop
+ SESSION.add_preset_commands 'binding.bp(command:)', cmds,
+ kick: false,
+ continue: nonstop == false ? false : true
end
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true
true
end
end
- if !::DEBUGGER__::CONFIG[:nonstop]
- if loc = ::DEBUGGER__.require_location
- # require 'debug/console' or 'debug'
- add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
- else
- # -r
- add_line_breakpoint $0, 1, oneshot: true, hook_call: false
- end
- end
-
load_rc
end
end
def self.load_rc
- ['./rdbgrc.rb', File.expand_path('~/.rdbgrc.rb')].each{|path|
- if File.file? path
- load path
- end
- }
+ [[File.expand_path('~/.rdbgrc'), true],
+ [File.expand_path('~/.rdbgrc.rb'), true],
+ # ['./.rdbgrc', true], # disable because of security concern
+ [::DEBUGGER__::CONFIG[:init_script], false],
+ ].each{|(path, rc)|
+ next unless path
+ next if rc && ::DEBUGGER__::CONFIG[:no_rc] # ignore rc
- # debug commands file
- [init_script = ::DEBUGGER__::CONFIG[:init_script],
- './.rdbgrc',
- File.expand_path('~/.rdbgrc')].each{|path|
- next unless path
-
if File.file? path
- ::DEBUGGER__::SESSION.add_initial_commands File.readlines(path)
- elsif path == init_script
+ if path.end_with?('.rb')
+ load path
+ else
+ ::DEBUGGER__::SESSION.add_preset_commands path, File.readlines(path)
+ end
+ elsif !rc
warn "Not found: #{path}"
end
}
# given debug commands
if ::DEBUGGER__::CONFIG[:commands]
cmds = ::DEBUGGER__::CONFIG[:commands].split(';;')
- ::DEBUGGER__::SESSION.add_initial_commands cmds
+ ::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
end
end
def self.parse_help
helps = Hash.new{|h, k| h[k] = []}
@@ -1190,8 +1257,17 @@
str = obj.inspect
if use_short && str.length > SHORT_INSPECT_LENGTH
str[0...SHORT_INSPECT_LENGTH] + '...'
else
str
+ end
+ end
+
+ def self.warn msg, level = :warn
+ case level
+ when :warn
+ STDERR.puts "DEBUGGER: #{msg}" unless CONFIG[:quiet]
+ when :error
+ STDERR.puts "DEBUGGER: #{msg}"
end
end
end