Class: DEBUGGER__::Session
Relationships & Source Files | |
Super Chains via Extension / Inclusion / Inheritance | |
Instance Chain:
self,
Color ,
GlobalVariablesHelper
|
|
Inherits: | Object |
Defined in: | lib/debug/server_cdp.rb, lib/debug/server_dap.rb, lib/debug/session.rb |
Constant Summary
-
BREAK_KEYWORDS =
# File 'lib/debug/session.rb', line 1403%w(if: do: pre: path:).freeze
-
INTERNAL_ERROR =
# File 'lib/debug/server_cdp.rb', line 732-32603
-
INVALID_PARAMS =
# File 'lib/debug/server_cdp.rb', line 731-32602
-
METHOD_ADDED_TRACKERS =
# File 'lib/debug/session.rb', line 1830Hash.new
GlobalVariablesHelper
- Included
Class Method Summary
Instance Attribute Summary
- #active? ⇒ Boolean readonly
-
#bps_pending_until_load? ⇒ Boolean
readonly
breakpoint management.
- #in_subsession? ⇒ Boolean readonly
- #intercept_trap_sigint? ⇒ Boolean readonly
- #intercepted_sigint_cmd readonly
- #postmortem=(is_enable) writeonly
- #process_group readonly
- #remote? ⇒ Boolean readonly
- #subsession_id readonly
Instance Method Summary
- #activate(ui = nil, on_fork: false)
- #add_bp(bp)
- #add_catch_breakpoint(pat, cond: nil)
- #add_check_breakpoint(cond, path, command)
- #add_iseq_breakpoint(iseq, **kw)
- #add_line_breakpoint(file, line, **kw)
- #add_preset_commands(name, cmds, kick: true, continue: true)
-
#add_tracer(tracer)
tracers.
- #after_fork_parent
- #ask(msg, default = 'Y')
- #before_fork(need_lock = true)
- #bp_index(specific_bp_key)
- #cancel_auto_continue
- #capture_exception_frames(*exclude_path)
- #cdp_event(args)
- #check_postmortem
- #check_unsafe
- #clean_bps
- #clear_all_breakpoints
- #clear_breakpoints(&condition)
- #clear_catch_breakpoints(*exception_names)
- #clear_line_breakpoints(path)
- #config_command(arg)
- #config_set(key, val, append: false)
- #config_show(key)
- #dap_event(args)
- #deactivate
- #delete_bp(arg = nil)
- #enter_postmortem_session(exc)
-
#fail_response(req, **kw)
See additional method definition at file lib/debug/server_cdp.rb line 726.
- #find_waiting_tc(id)
-
#get_thread_client(th = Thread.current)
can be called by other threads.
-
#get_type(obj)
FIXME: unify this method with ThreadClient#propertyDescriptor.
- #inspect
- #intercept_trap_sigint(flag, &b) readonly
- #intercept_trap_sigint_end
- #intercept_trap_sigint_start(prev)
- #iterate_bps
- #managed_thread_clients
- #method_added(tp)
-
#on_load(iseq, src)
event.
- #on_thread_begin(th)
- #pop_event
- #process_command(line)
- #process_event(evt)
- #process_info
-
#process_protocol_request(req)
See additional method definition at file lib/debug/server_cdp.rb line 734.
- #prompt
- #register_default_command
- #register_var(v, tid)
- #register_vars(vars, tid)
- #rehash_bps
- #repl_add_breakpoint(arg)
- #repl_add_catch_breakpoint(arg)
- #repl_add_watch_breakpoint(arg)
- #request_tc(req)
- #reset_ui(ui)
- #resolve_path(file)
- #save_int_trap(cmd)
- #session_server_main
- #set_no_sigint_hook(old, new)
- #setup_threads
- #show_bps(specific_bp = nil)
- #show_help(arg = nil)
- #source(iseq)
- #step_command(type, arg)
- #stop_stepping?(file, line, subsession_id = nil) ⇒ Boolean
- #switch_thread(n)
- #thread_list
-
#update_thread_list
threads.
- #wait_command
- #wait_command_loop
- #width
- #ask_thread_client(th) private
- #create_thread_client(th) private
- #enter_subsession private
- #leave_subsession(type) private
- #parse_break(type, arg) private
- #register_command(*names, repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true, &b) private
- #restart_all_threads private
- #running_thread_clients_count private
- #stop_all_threads private
- #thread_stopper private
- #waiting_thread_clients private
Color
- Included
#color_pp | See additional method definition at line 50. |
#colored_inspect, | |
#colorize | See additional method definition at line 36. |
#colorize_blue, | |
#colorize_code | See additional method definition at line 79. |
#colorize_cyan, #colorize_dim, #colorize_magenta, | |
#irb_colorize | See additional method definition at line 27. |
#with_inspection_error_guard |
GlobalVariablesHelper
- Included
Constructor Details
.new ⇒ Session
# File 'lib/debug/session.rb', line 98
def initialize @ui = nil @sr = SourceRepository.new @bps = {} # bp.key => bp # [file, line] => LineBreakpoint # "Error" => CatchBreakpoint # "Foo#bar" => MethodBreakpoint # [:watch, ivar] => WatchIVarBreakpoint # [:check, expr] => CheckBreakpoint # @tracers = {} @th_clients = {} # {Thread => ThreadClient} @q_evt = Queue.new @displays = [] @tc = nil @tc_id = 0 @preset_command = nil @postmortem_hook = nil @postmortem = false @intercept_trap_sigint = false @intercepted_sigint_cmd = 'DEFAULT' @process_group = ProcessGroup.new @subsession_stack = [] @subsession_id = 0 @frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth} @var_map = {1 => [:globals], } # {id => ...} for DAP @src_map = {} # {id => src} @scr_id_map = {} # for CDP @obj_map = {} # { object_id => ... } for CDP @tp_thread_begin = nil @commands = {} @unsafe_context = false has_keep_script_lines = RubyVM.respond_to? :keep_script_lines @tp_load_script = TracePoint.new(:script_compiled){|tp| if !has_keep_script_lines || bps_pending_until_load? eval_script = tp.eval_script unless has_keep_script_lines ThreadClient.current.on_load tp.instruction_sequence, eval_script end } @tp_load_script.enable @thread_stopper = thread_stopper self.postmortem = CONFIG[:postmortem] register_default_command end
Class Method Details
.activate_method_added_trackers
[ GitHub ]# File 'lib/debug/session.rb', line 1816
def self.activate_method_added_trackers METHOD_ADDED_TRACKERS.each do |m, tp| tp.enable(target: m) unless tp.enabled? rescue ArgumentError DEBUGGER__.warn "Methods defined under #{m.owner} can not track by the debugger." end end
.create_method_added_tracker(mod, method_added_id, method_accessor = :method)
[ GitHub ]# File 'lib/debug/session.rb', line 1809
def self.create_method_added_tracker mod, method_added_id, method_accessor = :method m = mod.__send__(method_accessor, method_added_id) METHOD_ADDED_TRACKERS[m] = TracePoint.new(:call) do |tp| SESSION.method_added tp end end
.deactivate_method_added_trackers
[ GitHub ]# File 'lib/debug/session.rb', line 1824
def self.deactivate_method_added_trackers METHOD_ADDED_TRACKERS.each do |m, tp| tp.disable if tp.enabled? end end
Instance Attribute Details
#active? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/debug/session.rb', line 150
def active? !@q_evt.closed? end
#bps_pending_until_load? ⇒ Boolean
(readonly)
breakpoint management
# File 'lib/debug/session.rb', line 1324
def bps_pending_until_load? @bps.any?{|key, bp| bp.pending_until_load?} end
#in_subsession? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/debug/session.rb', line 1715
def in_subsession? !@subsession_stack.empty? end
#intercept_trap_sigint? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/debug/session.rb', line 1945
def intercept_trap_sigint? @intercept_trap_sigint end
#intercepted_sigint_cmd (readonly)
[ GitHub ]# File 'lib/debug/session.rb', line 94
attr_reader :intercepted_sigint_cmd, :process_group, :subsession_id
#postmortem=(is_enable) (writeonly)
[ GitHub ]# File 'lib/debug/session.rb', line 1890
def postmortem=(is_enable) if is_enable unless @postmortem_hook @postmortem_hook = TracePoint.new(:raise){|tp| exc = tp.raised_exception frames = DEBUGGER__.capture_frames(__dir__) exc.instance_variable_set(:@__debugger_postmortem_frames, frames) } at_exit{ @postmortem_hook.disable if CONFIG[:postmortem] && (exc = $!) != nil exc = exc.cause while exc.cause begin @ui.puts "Enter postmortem mode with #{exc.inspect}" @ui.puts exc.backtrace.map{|e| ' ' + e} @ui.puts "\n" enter_postmortem_session exc rescue SystemExit exit! rescue Exception => e @ui = STDERR unless @ui @ui.puts "Error while postmortem console: #{e.inspect}" end end } end if !@postmortem_hook.enabled? @postmortem_hook.enable end else if @postmortem_hook && @postmortem_hook.enabled? @postmortem_hook.disable end end end
#process_group (readonly)
[ GitHub ]# File 'lib/debug/session.rb', line 94
attr_reader :intercepted_sigint_cmd, :process_group, :subsession_id
#remote? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/debug/session.rb', line 154
def remote? @ui.remote? end
#subsession_id (readonly)
[ GitHub ]# File 'lib/debug/session.rb', line 94
attr_reader :intercepted_sigint_cmd, :process_group, :subsession_id
Instance Method Details
#activate(ui = nil, on_fork: false)
[ GitHub ]# File 'lib/debug/session.rb', line 168
def activate ui = nil, on_fork: false @ui = ui if ui @tp_thread_begin&.disable @tp_thread_begin = nil @ui.activate self, on_fork: on_fork q = Queue.new @session_server = Thread.new do Thread.current.name = 'DEBUGGER__::SESSION@server' Thread.current.abort_on_exception = true # Thread management setup_threads thc = get_thread_client Thread.current thc.mark_as_management if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread) thc.mark_as_management end @tp_thread_begin = TracePoint.new(:thread_begin) do |tp| get_thread_client end @tp_thread_begin.enable # session start q << true session_server_main end q.pop end
#add_bp(bp)
[ GitHub ]# File 'lib/debug/session.rb', line 1372
def add_bp bp # don't repeat commands that add breakpoints if @bps.has_key? bp.key if bp.duplicable? bp else @ui.puts "duplicated breakpoint: #{bp}" bp.disable nil end else @bps[bp.key] = bp end end
#add_catch_breakpoint(pat, cond: nil)
[ GitHub ]# File 'lib/debug/session.rb', line 1472
def add_catch_breakpoint pat, cond: nil bp = CatchBreakpoint.new(pat, cond: cond) add_bp bp end
#add_check_breakpoint(cond, path, command)
[ GitHub ]# File 'lib/debug/session.rb', line 1477
def add_check_breakpoint cond, path, command bp = CheckBreakpoint.new(cond: cond, path: path, command: command) add_bp bp end
#add_iseq_breakpoint(iseq, **kw)
[ GitHub ]# File 'lib/debug/session.rb', line 1519
def add_iseq_breakpoint iseq, **kw bp = ISeqBreakpoint.new(iseq, [:line], **kw) add_bp bp end
#add_line_breakpoint(file, line, **kw)
[ GitHub ]# File 'lib/debug/session.rb', line 1482
def add_line_breakpoint file, line, **kw file = resolve_path(file) bp = LineBreakpoint.new(file, line, **kw) add_bp bp rescue Errno::ENOENT => e @ui.puts e. end
#add_preset_commands(name, cmds, kick: true, continue: true)
[ GitHub ]# File 'lib/debug/session.rb', line 346
def add_preset_commands name, cmds, kick: true, continue: true cs = cmds.map{|c| c.each_line.map{|line| line = line.strip.gsub(/\A\s*\#.*/, '').strip line unless line.empty? }.compact }.flatten.compact if @preset_command && !@preset_command.commands.empty? @preset_command.commands += cs else @preset_command = PresetCommands.new(cs, name, continue) end ThreadClient.current.on_init name if kick end
#add_tracer(tracer)
tracers
# File 'lib/debug/session.rb', line 1526
def add_tracer tracer if @tracers.has_key? tracer.key tracer.disable @ui.puts "Duplicated tracer: #{tracer}" else @tracers[tracer.key] = tracer @ui.puts "Enable #{tracer}" end end
#after_fork_parent
[ GitHub ]# File 'lib/debug/session.rb', line 1980
def after_fork_parent @ui.after_fork_parent end
#ask(msg, default = 'Y')
[ GitHub ]# File 'lib/debug/session.rb', line 1310
def ask msg, default = 'Y' opts = '[y/n]'.tr(default.downcase, default) input = @ui.ask("#{msg} #{opts} ") input = default if input.empty? case input when 'y', 'Y' true else false end end
#ask_thread_client(th) (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1615
private def ask_thread_client th # TODO: Ractor support q2 = Queue.new # tc, output, ev, @internal_info, *ev_args = evt @q_evt << [nil, [], :thread_begin, nil, th, q2] q2.pop @th_clients[th] or raise "unexpected error" end
#before_fork(need_lock = true)
[ GitHub ]# File 'lib/debug/session.rb', line 1974
def before_fork need_lock = true if need_lock @process_group.multi_process! end end
#bp_index(specific_bp_key)
[ GitHub ]# File 'lib/debug/session.rb', line 1349
def bp_index specific_bp_key iterate_bps do |key, bp, i| if key == specific_bp_key return [bp, i] end end nil end
#cancel_auto_continue
[ GitHub ]# File 'lib/debug/session.rb', line 1258
def cancel_auto_continue if @preset_command&.auto_continue @preset_command.auto_continue = false end end
#capture_exception_frames(*exclude_path)
[ GitHub ]# File 'lib/debug/session.rb', line 1860
def capture_exception_frames *exclude_path postmortem_hook = TracePoint.new(:raise){|tp| exc = tp.raised_exception frames = DEBUGGER__.capture_frames(__dir__) exclude_path.each{|ex| if Regexp === ex frames.delete_if{|e| ex =~ e.path} else frames.delete_if{|e| e.path.start_with? ex.to_s} end } exc.instance_variable_set(:@__debugger_postmortem_frames, frames) } postmortem_hook.enable begin yield nil rescue Exception => e if e.instance_variable_defined? :@__debugger_postmortem_frames e else raise end ensure postmortem_hook.disable end end
#cdp_event(args)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 839
def cdp_event args type, req, result = args case type when :backtrace result[:callFrames].each.with_index do |frame, i| frame_id = frame[:callFrameId] @frame_map[frame_id] = i path = frame[:url] unless s_id = @scr_id_map[path] s_id = (@scr_id_map.size + 1).to_s @scr_id_map[path] = s_id if path && File.exist?(path) src = File.read(path) end @src_map[s_id] = src end if src = @src_map[s_id] lineno = src.lines.count else lineno = 0 end frame[:location][:scriptId] = s_id frame[:functionLocation][:scriptId] = s_id @ui.fire_event 'Debugger.scriptParsed', scriptId: s_id, url: frame[:url], startLine: 0, startColumn: 0, endLine: lineno, endColumn: 0, executionContextId: 1, hash: src.hash.inspect frame[:scopeChain].each {|s| oid = s.dig(:object, :objectId) @obj_map[oid] = [s[:type], frame_id] } end if oid = result.dig(:data, :objectId) @obj_map[oid] = ['properties'] end @ui.fire_event 'Debugger.paused', **result when :evaluate = result.delete : if fail_response req, code: INVALID_PARAMS, message: else src = req.dig('params', 'expression') s_id = (@src_map.size + 1).to_s @src_map[s_id] = src lineno = src.lines.count @ui.fire_event 'Debugger.scriptParsed', scriptId: s_id, url: '', startLine: 0, startColumn: 0, endLine: lineno, endColumn: 0, executionContextId: 1, hash: src.hash.inspect if exc = result.dig(:response, :exceptionDetails) exc[:stackTrace][:callFrames].each{|frame| if frame[:url].empty? frame[:scriptId] = s_id else path = frame[:url] unless s_id = @scr_id_map[path] s_id = (@scr_id_map.size + 1).to_s @scr_id_map[path] = s_id end frame[:scriptId] = s_id end } if oid = exc[:exception][:objectId] @obj_map[oid] = ['exception'] end end rs = result.dig(:response, :result) [rs].each{|obj| if oid = obj[:objectId] @obj_map[oid] = ['properties'] end } @ui.respond req, **result[:response] out = result[:output] if out && !out.empty? @ui.fire_event 'Runtime.consoleAPICalled', type: 'log', args: [ type: out.class, value: out ], executionContextId: 1, # Change this number if something goes wrong. timestamp: Time.now.to_f end end when :scope result.each{|obj| if oid = obj.dig(:value, :objectId) @obj_map[oid] = ['properties'] end } @ui.respond req, result: result when :properties result.each_value{|v| v.each{|obj| if oid = obj.dig(:value, :objectId) @obj_map[oid] = ['properties'] end } } @ui.respond req, **result when :exception @ui.respond req, **result end end
#check_postmortem
[ GitHub ]# File 'lib/debug/session.rb', line 1838
def check_postmortem if @postmortem raise PostmortemError, "Can not use this command on postmortem mode." end end
#check_unsafe
[ GitHub ]# File 'lib/debug/session.rb', line 1844
def check_unsafe if @unsafe_context raise RuntimeError, "#{@repl_prev_line.dump} is not allowed on unsafe context." end end
#clean_bps
[ GitHub ]# File 'lib/debug/session.rb', line 1366
def clean_bps @bps.delete_if{|_k, bp| bp.deleted? } end
#clear_all_breakpoints
[ GitHub ]# File 'lib/debug/session.rb', line 1515
def clear_all_breakpoints clear_breakpoints{true} end
#clear_breakpoints(&condition)
[ GitHub ]# File 'lib/debug/session.rb', line 1491
def clear_breakpoints(&condition) @bps.delete_if do |k, bp| if condition.call(k, bp) bp.delete true end end end
#clear_catch_breakpoints(*exception_names)
[ GitHub ]# File 'lib/debug/session.rb', line 1509
def clear_catch_breakpoints *exception_names clear_breakpoints do |k, bp| bp.is_a?(CatchBreakpoint) && exception_names.include?(k[1]) end end
#clear_line_breakpoints(path)
[ GitHub ]# File 'lib/debug/session.rb', line 1500
def clear_line_breakpoints path path = resolve_path(path) clear_breakpoints do |k, bp| bp.is_a?(LineBreakpoint) && DEBUGGER__.compare_path(k.first, path) end rescue Errno::ENOENT # just ignore end
#config_command(arg)
[ GitHub ]# File 'lib/debug/session.rb', line 1224
def config_command arg case arg when nil CONFIG_SET.each do |k, _| config_show k end when /\Aunset\s(.)\z/ if CONFIG_SET[key = $1.to_sym] CONFIG[key] = nil end config_show key when /\A(\w)\s*=\s*(.)\z/ config_set $1, $2 when /\A\s*set\s(\w)\s(.)\z/ config_set $1, $2 when /\A(\w)\s*<<\s*(.)\z/ config_set $1, $2, append: true when /\A\s*append\s(\w)\s(.)\z/ config_set $1, $2, append: true when /\A(\w+)\z/ config_show $1 else @ui.puts "Can not parse parameters: #{arg}" end end
#config_set(key, val, append: false)
[ GitHub ]# File 'lib/debug/session.rb', line 1208
def config_set key, val, append: false if CONFIG_SET[key = key.to_sym] begin if append CONFIG.append_config(key, val) else CONFIG[key] = val end rescue => e @ui.puts e. end end config_show key end
#config_show(key)
[ GitHub ]# File 'lib/debug/session.rb', line 1184
def config_show key key = key.to_sym config_detail = CONFIG_SET[key] if config_detail v = CONFIG[key] kv = "#{key} = #{v.inspect}" desc = config_detail[1] if config_default = config_detail[3] desc += " (default: #{config_default})" end line = "%-34s \# %s" % [kv, desc] if line.size > SESSION.width @ui.puts "\# #{desc}\n#{kv}" else @ui.puts line end else @ui.puts "Unknown configuration: #{key}. 'config' shows all configurations." end end
#create_thread_client(th) (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1609
private def create_thread_client th # TODO: Ractor support raise "Only session_server can create thread_client" unless Thread.current == @session_server @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th) end
#dap_event(args)
[ GitHub ]# File 'lib/debug/server_dap.rb', line 658
def dap_event args # puts({dap_event: args}.inspect) type, req, result = args case type when :backtrace result[:stackFrames].each{|fi| frame_depth = fi[:id] fi[:id] = id = @frame_map.size + 1 @frame_map[id] = [req.dig('arguments', 'threadId'), frame_depth] if fi[:source] if src = fi[:source][:sourceReference] src_id = @src_map.size + 1 @src_map[src_id] = src fi[:source][:sourceReference] = src_id else fi[:source][:sourceReference] = 0 end end } @ui.respond req, result when :scopes frame_id = req.dig('arguments', 'frameId') local_scope = result[:scopes].first local_scope[:variablesReference] = id = @var_map.size + 1 @var_map[id] = [:scope, frame_id] @ui.respond req, result when :scope tid = result.delete :tid register_vars result[:variables], tid @ui.respond req, result when :variable tid = result.delete :tid register_vars result[:variables], tid @ui.respond req, result when :evaluate = result.delete : if @ui.respond req, success: false, message: else tid = result.delete :tid register_var result, tid @ui.respond req, result end when :completions @ui.respond req, result else raise "unsupported: #{args.inspect}" end end
#deactivate
[ GitHub ]# File 'lib/debug/session.rb', line 203
def deactivate get_thread_client.deactivate @thread_stopper.disable @tp_load_script.disable @tp_thread_begin.disable @bps.each_value{|bp| bp.disable} @th_clients.each_value{|thc| thc.close} @tracers.values.each{|t| t.disable} @q_evt.close @ui&.deactivate @ui = nil end
#delete_bp(arg = nil)
[ GitHub ]# File 'lib/debug/session.rb', line 1387
def delete_bp arg = nil case arg when nil @bps.each{|key, bp| bp.delete} @bps.clear else del_bp = nil iterate_bps{|key, bp, i| del_bp = bp if i == arg} if del_bp del_bp.delete @bps.delete del_bp.key return [arg, del_bp] end end end
#enter_postmortem_session(exc)
[ GitHub ]# File 'lib/debug/session.rb', line 1850
def enter_postmortem_session exc return unless exc.instance_variable_defined? :@__debugger_postmortem_frames frames = exc.instance_variable_get(:@__debugger_postmortem_frames) @postmortem = true ThreadClient.current.suspend :postmortem, postmortem_frames: frames, postmortem_exc: exc ensure @postmortem = false end
#enter_subsession (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1683
private def enter_subsession @subsession_id += 1 if !@subsession_stack.empty? DEBUGGER__.debug{ "Enter subsession (nested #{@subsession_stack.size})" } else DEBUGGER__.debug{ "Enter subsession" } stop_all_threads @process_group.lock end @subsession_stack << true end
#fail_response(req, **kw)
See additional method definition at file lib/debug/server_cdp.rb line 726.
# File 'lib/debug/server_dap.rb', line 532
def fail_response req, **result @ui.respond_fail req, **result return :retry end
#find_waiting_tc(id)
[ GitHub ]# File 'lib/debug/server_dap.rb', line 525
def find_waiting_tc id @th_clients.each{|th, tc| return tc if tc.id == id && tc.waiting? } return nil end
#get_thread_client(th = Thread.current)
can be called by other threads
# File 'lib/debug/session.rb', line 1626
def get_thread_client th = Thread.current if @th_clients.has_key? th @th_clients[th] else if Thread.current == @session_server create_thread_client th else ask_thread_client th end end end
#get_type(obj)
FIXME: unify this method with ThreadClient#propertyDescriptor.
# File 'lib/debug/server_cdp.rb', line 705
def get_type obj case obj when Array ['object', 'array'] when Hash ['object', 'map'] when String ['string'] when TrueClass, FalseClass ['boolean'] when Symbol ['symbol'] when Integer, Float ['number'] when Exception ['object', 'error'] else ['object'] end end
#inspect
[ GitHub ]# File 'lib/debug/session.rb', line 371
def inspect "DEBUGGER__::SESSION" end
#intercept_trap_sigint(flag, &b) (readonly)
[ GitHub ]# File 'lib/debug/session.rb', line 1949
def intercept_trap_sigint flag, &b prev = @intercept_trap_sigint @intercept_trap_sigint = flag yield ensure @intercept_trap_sigint = prev end
#intercept_trap_sigint_end
[ GitHub ]# File 'lib/debug/session.rb', line 1962
def intercept_trap_sigint_end @intercept_trap_sigint = false prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, nil prev end
#intercept_trap_sigint_start(prev)
[ GitHub ]# File 'lib/debug/session.rb', line 1957
def intercept_trap_sigint_start prev @intercept_trap_sigint = true @intercepted_sigint_cmd = prev end
#iterate_bps
[ GitHub ]# File 'lib/debug/session.rb', line 1328
def iterate_bps deleted_bps = [] i = 0 @bps.each{|key, bp| if !bp.deleted? yield key, bp, i i += 1 else deleted_bps << bp end } ensure deleted_bps.each{|bp| @bps.delete bp} end
#leave_subsession(type) (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1696
private def leave_subsession type raise '[BUG] leave_subsession: not entered' if @subsession_stack.empty? @subsession_stack.pop if @subsession_stack.empty? DEBUGGER__.debug{ "Leave subsession" } @process_group.unlock restart_all_threads else DEBUGGER__.debug{ "Leave subsession (nested #{@subsession_stack.size})" } end request_tc type if type @tc = nil rescue Exception => e STDERR.puts PP.pp([e, e.backtrace], ''.dup) raise end
#managed_thread_clients
[ GitHub ]# File 'lib/debug/session.rb', line 1570
def managed_thread_clients thcs, _unmanaged_ths = update_thread_list thcs end
#method_added(tp)
[ GitHub ]# File 'lib/debug/session.rb', line 1767
def method_added tp b = tp.binding if var_name = b.local_variables.first mid = b.local_variable_get(var_name) resolved = true @bps.each{|k, bp| case bp when MethodBreakpoint if bp.method.nil? if bp.sig_method_name == mid.to_s bp.try_enable(added: true) end end resolved = false if !bp.enabled? end } if resolved Session.deactivate_method_added_trackers end case mid when :method_added, :singleton_method_added Session.create_method_added_tracker(tp.self, mid) Session.activate_method_added_trackers unless resolved end end end
#on_load(iseq, src)
event
# File 'lib/debug/session.rb', line 1721
def on_load iseq, src DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}" file_path, reloaded = @sr.add(iseq, src) @ui.event :load, file_path, reloaded pending_line_breakpoints = @bps.find_all do |key, bp| LineBreakpoint === bp && !bp.iseq end pending_line_breakpoints.each do |_key, bp| if DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path)) bp.try_activate iseq end end if reloaded @bps.find_all do |key, bp| LineBreakpoint === bp && DEBUGGER__.compare_path(bp.path, file_path) end.each do |_key, bp| @bps.delete bp.key # to allow duplicate if nbp = LineBreakpoint.copy(bp, iseq) add_bp nbp end end end end
#on_thread_begin(th)
[ GitHub ]# File 'lib/debug/session.rb', line 1601
def on_thread_begin th if @th_clients.has_key? th # TODO: NG? else create_thread_client th end end
#parse_break(type, arg) (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1405
private def parse_break type, arg mode = :sig expr = Hash.new{|h, k| h[k] = []} arg.split(' ').each{|w| if BREAK_KEYWORDS.any?{|pat| w == pat} mode = w[0..-2].to_sym else expr[mode] << w end } expr.default_proc = nil expr = expr.transform_values{|v| v.join(' ')} if (path = expr[:path]) && path =~ /\A\/(.*)\/\z/ expr[:path] = Regexp.compile($1) end if expr[:do] || expr[:pre] check_unsafe expr[:cmd] = [type, expr[:pre], expr[:do]] end expr end
#pop_event
[ GitHub ]# File 'lib/debug/session.rb', line 229
def pop_event @q_evt.pop end
#process_command(line)
[ GitHub ]# File 'lib/debug/session.rb', line 1115
def process_command line if line.empty? if @repl_prev_line line = @repl_prev_line else return :retry end else @repl_prev_line = line end /([^\s])(?:\s(.+))?/ =~ line cmd_name, cmd_arg = $1, $2 if cmd = @commands[cmd_name] check_postmortem if !cmd.postmortem check_unsafe if cmd.unsafe cancel_auto_continue if cmd.cancel_auto_continue @repl_prev_line = nil if !cmd.repeat cmd.block.call(cmd_arg) else @repl_prev_line = nil check_unsafe request_tc [:eval, :pp, line] end rescue Interrupt return :retry rescue SystemExit raise rescue PostmortemError => e @ui.puts e. return :retry rescue Exception => e @ui.puts "[REPL ERROR] #{e.inspect}" @ui.puts e.backtrace.map{|e| ' ' + e} return :retry end
#process_event(evt)
[ GitHub ]# File 'lib/debug/session.rb', line 245
def process_event evt # variable `@internal_info` is only used for test tc, output, ev, @internal_info, *ev_args = evt output.each{|str| @ui.puts str} if ev != :suspend # special event, tc is nil # and we don't want to set @tc to the newly created thread's ThreadClient if ev == :thread_begin th = ev_args.shift q = ev_args.shift on_thread_begin th q << true return end @tc = tc case ev when :init enter_subsession wait_command_loop when :load iseq, src = ev_args on_load iseq, src request_tc :continue when :trace trace_id, msg = ev_args if t = @tracers.values.find{|t| t.object_id == trace_id} t.puts msg end request_tc :continue when :suspend enter_subsession if ev_args.first != :replay output.each{|str| @ui.puts str} unless @ui.ignore_output_on_suspend? case ev_args.first when :breakpoint bp, i = bp_index ev_args[1] clean_bps unless bp @ui.event :suspend_bp, i, bp, @tc.id when :trap @ui.event :suspend_trap, sig = ev_args[1], @tc.id if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String)) @ui.puts "#{@intercepted_sigint_cmd.inspect} is registered as SIGINT handler." @ui.puts "`sigint` command execute it." end else @ui.event :suspended, @tc.id end if @displays.empty? wait_command_loop else request_tc [:eval, :display, @displays] end when :result raise "[BUG] not in subsession" if @subsession_stack.empty? case ev_args.first 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, :watch_breakpoint bp = ev_args[1] if bp add_bp(bp) show_bps bp else # can't make a bp end when :trace_pass obj_id = ev_args[1] obj_inspect = ev_args[2] opt = ev_args[3] add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt) else # ignore end wait_command_loop when :dap_result dap_event ev_args # server.rb wait_command_loop when :cdp_result cdp_event ev_args wait_command_loop end end
#process_info
[ GitHub ]# File 'lib/debug/session.rb', line 1968
def process_info if @process_group.multi? "#{$0}\##{Process.pid}" end end
#process_protocol_request(req)
See additional method definition at file lib/debug/server_cdp.rb line 734.
# File 'lib/debug/server_dap.rb', line 537
def process_protocol_request req case req['method'] when 'Debugger.stepOver', 'Debugger.stepInto', 'Debugger.stepOut', 'Debugger.resume', 'Debugger.enable' request_tc [:cdp, :backtrace, req] when 'Debugger.evaluateOnCallFrame' frame_id = req.dig('params', 'callFrameId') group = req.dig('params', 'objectGroup') if fid = @frame_map[frame_id] expr = req.dig('params', 'expression') request_tc [:cdp, :evaluate, req, fid, expr, group] else fail_response req, code: INVALID_PARAMS, message: "'callFrameId' is an invalid" end when 'Runtime.getProperties', 'Runtime.getExceptionDetails' oid = req.dig('params', 'objectId') || req.dig('params', 'errorObjectId') if ref = @obj_map[oid] case ref[0] when 'local' frame_id = ref[1] fid = @frame_map[frame_id] request_tc [:cdp, :scope, req, fid] when 'global' vars = safe_global_variables.sort.map do |name| gv = eval(name.to_s) prop = { name: name, value: { description: gv.inspect }, configurable: true, enumerable: true } type, subtype = get_type(gv) prop[:value][:type] = type prop[:value][:subtype] = subtype if subtype prop end @ui.respond req, result: vars return :retry when 'properties' request_tc [:cdp, :properties, req, oid] when 'exception' request_tc [:cdp, :exception, req, oid] when 'script' # TODO: Support script and global types @ui.respond req, result: [] return :retry else raise "Unknown type: #{ref.inspect}" end else fail_response req, code: INVALID_PARAMS, message: "'objectId' is an invalid" end when 'Debugger.getScriptSource' s_id = req.dig('params', 'scriptId') if src = @src_map[s_id] @ui.respond req, scriptSource: src else fail_response req, code: INVALID_PARAMS, message: "'scriptId' is an invalid" end return :retry when 'Debugger.getPossibleBreakpoints' s_id = req.dig('params', 'start', 'scriptId') if src = @src_map[s_id] lineno = req.dig('params', 'start', 'lineNumber') end_line = src.lines.count lineno = end_line if lineno > end_line @ui.respond req, locations: [{ scriptId: s_id, lineNumber: lineno }] else fail_response req, code: INVALID_PARAMS, message: "'scriptId' is an invalid" end return :retry when 'Debugger.setBreakpointByUrl' path = req.dig('params', 'scriptId') if s_id = @scr_id_map[path] lineno = req.dig('params', 'lineNumber') b_id = req.dig('params', 'breakpointId') @ui.respond req, breakpointId: b_id, locations: [{ scriptId: s_id, lineNumber: lineno }] else fail_response req, code: INTERNAL_ERROR, message: 'The target script is not found...' end return :retry end end
#prompt
[ GitHub ]# File 'lib/debug/session.rb', line 389
def prompt if @postmortem '(rdbg:postmortem) ' elsif @process_group.multi? "(rdbg@#{process_info}) " else '(rdbg) ' end end
#register_command(*names, repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true, &b) (private)
[ GitHub ]# File 'lib/debug/session.rb', line 430
private def register_command *names, repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true, &b cmd = SessionCommand.new(b, repeat, unsafe, cancel_auto_continue, postmortem) names.each{|name| @commands[name] = cmd } end
#register_default_command
[ GitHub ]# File 'lib/debug/session.rb', line 440
def register_default_command ### Control flow # * `s[tep]` # * Step in. Resume the program until next breakable point. # * `s[tep] <n>` # * Step in, resume the program at `<n>`th breakable point. register_command 's', 'step', repeat: true, cancel_auto_continue: true, postmortem: false do |arg| step_command :in, arg end # * `n[ext]` # * Step over. Resume the program until next line. # * `n[ext] <n>` # * Step over, same as `step <n>`. register_command 'n', 'next', repeat: true, cancel_auto_continue: true, postmortem: false do |arg| step_command :next, arg end # * `fin[ish]` # * Finish this frame. Resume the program until the current frame is finished. # * `fin[ish] <n>` # * Finish `<n>`th frames. register_command 'fin', 'finish', repeat: true, cancel_auto_continue: true, postmortem: false do |arg| if arg&.to_i == 0 raise 'finish command with 0 does not make sense.' end step_command :finish, arg end # * `u[ntil]` # * Similar to `next` command, but only stop later lines or the end of the current frame. # * Similar to gdb's `advance` command. # * `u[ntil] <[file:]line> # * Run til the program reaches given location or the end of the current frame. # * `u[ntil] <name> # * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`. register_command 'u', 'until', repeat: true, cancel_auto_continue: true, postmortem: false do |arg| step_command :until, arg end # * `c` or `cont` or `continue` # * Resume the program. register_command 'c', 'cont', 'continue', repeat: true, cancel_auto_continue: true do |arg| leave_subsession :continue end # * `q[uit]` or `Ctrl-D` # * Finish debugger (with the debuggee process on non-remote debugging). register_command 'q', 'quit' do |arg| if ask 'Really quit?' @ui.quit arg.to_i do request_tc :quit end leave_subsession :continue else next :retry end end # * `q[uit]!` # * Same as q[uit] but without the confirmation prompt. register_command 'q!', 'quit!', unsafe: false do |arg| @ui.quit arg.to_i do request_tc :quit end leave_subsession :continue end # * `kill` # * Stop the debuggee process with `Kernel#exit!`. register_command 'kill' do |arg| if ask 'Really kill?' exit! (arg || 1).to_i else next :retry end end # * `kill!` # * Same as kill but without the confirmation prompt. register_command 'kill!', unsafe: false do |arg| exit! (arg || 1).to_i end # * `sigint` # * Execute SIGINT handler registered by the debuggee. # * Note that this command should be used just after stop by `SIGINT`. register_command 'sigint' do begin case cmd = @intercepted_sigint_cmd when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT # ignore when String eval(cmd) when Proc cmd.call end leave_subsession :continue rescue Exception => e @ui.puts "Exception: #{e}" @ui.puts e.backtrace.map{|line| " #{e}"} next :retry end end ### Breakpoint # * `b[reak]` # * Show all breakpoints. # * `b[reak] <line>` # * Set breakpoint on `<line>` at the current frame's file. # * `b[reak] <file>:<line>` or `<file> <line>` # * Set breakpoint on `<file>:<line>`. # * `b[reak] <class>#<name>` # * Set breakpoint on the method `<class>#<name>`. # * `b[reak] <expr>.<name>` # * Set breakpoint on the method `<expr>.<name>`. # * `b[reak] ... if: <expr>` # * break if `<expr>` is true at specified location. # * `b[reak] ... pre: <command>` # * break and run `<command>` before stopping. # * `b[reak] ... do: <command>` # * break and run `<command>`, and continue. # * `b[reak] ... path: <path>` # * break if the path matches to `<path>`. `<path>` can be a regexp with `/regexp/`. # * `b[reak] if: <expr>` # * break if: `<expr>` is true at any lines. # * Note that this feature is super slow. register_command 'b', 'break', postmortem: false, unsafe: false do |arg| if arg == nil show_bps next :retry else case bp = repl_add_breakpoint(arg) when :noretry when nil next :retry else show_bps bp next :retry end end end # * `catch <Error>` # * Set breakpoint on raising `<Error>`. # * `catch ... if: <expr>` # * stops only if `<expr>` is true as well. # * `catch ... pre: <command>` # * runs `<command>` before stopping. # * `catch ... do: <command>` # * stops and run `<command>`, and continue. # * `catch ... path: <path>` # * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`. register_command 'catch', postmortem: false, unsafe: false do |arg| if arg bp = repl_add_catch_breakpoint arg show_bps bp if bp else show_bps end :retry end # * `watch @ivar` # * Stop the execution when the result of current scope's `@ivar` is changed. # * Note that this feature is super slow. # * `watch ... if: <expr>` # * stops only if `<expr>` is true as well. # * `watch ... pre: <command>` # * runs `<command>` before stopping. # * `watch ... do: <command>` # * stops and run `<command>`, and continue. # * `watch ... path: <path>` # * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`. register_command 'wat', 'watch', postmortem: false, unsafe: false do |arg| if arg && arg.match?(/\A@\w+/) repl_add_watch_breakpoint(arg) else show_bps :retry end end # * `del[ete]` # * delete all breakpoints. # * `del[ete] <bpnum>` # * delete specified breakpoint. register_command 'del', 'delete', postmortem: false, unsafe: false do |arg| case arg when nil show_bps if ask "Remove all breakpoints?", 'N' delete_bp end when /\d+/ bp = delete_bp arg.to_i else nil end @ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp :retry end ### Information # * `bt` or `backtrace` # * Show backtrace (frame) information. # * `bt <num>` or `backtrace <num>` # * Only shows first `<num>` frames. # * `bt /regexp/` or `backtrace /regexp/` # * Only shows frames with method name or location info that matches `/regexp/`. # * `bt <num> /regexp/` or `backtrace <num> /regexp/` # * Only shows first `<num>` frames with method name or location info that matches `/regexp/`. register_command 'bt', 'backtrace', unsafe: false do |arg| case arg when /\A(\d+)\z/ request_tc [:show, :backtrace, arg.to_i, nil] when /\A\/(.*)\/\z/ pattern = $1 request_tc [:show, :backtrace, nil, Regexp.compile(pattern)] when /\A(\d)\s\/(.*)\/\z/ max, pattern = $1, $2 request_tc [:show, :backtrace, max.to_i, Regexp.compile(pattern)] else request_tc [:show, :backtrace, nil, nil] end end # * `l[ist]` # * Show current frame's source code. # * Next `list` command shows the successor lines. # * `l[ist] -` # * Show predecessor lines as opposed to the `list` command. # * `l[ist] <start>` or `l[ist] <start>-<end>` # * Show current frame's source code from the line <start> to <end> if given. register_command 'l', 'list', repeat: true, unsafe: false do |arg| case arg ? arg.strip : nil when /\A(\d+)\z/ request_tc [:show, :list, {start_line: arg.to_i - 1}] when /\A-\z/ request_tc [:show, :list, {dir: -1}] when /\A(\d)-(\d)\z/ request_tc [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}] when nil request_tc [:show, :list] else @ui.puts "Can not handle list argument: #{arg}" :retry end end # * `whereami` # * Show the current frame with source code. register_command 'whereami', unsafe: false do request_tc [:show, :whereami] end # * `edit` # * Open the current file on the editor (use `EDITOR` environment variable). # * Note that edited file will not be reloaded. # * `edit <file>` # * Open <file> on the editor. register_command 'edit' do |arg| if @ui.remote? @ui.puts "not supported on the remote console." next :retry end begin arg = resolve_path(arg) if arg rescue Errno::ENOENT @ui.puts "not found: #{arg}" next :retry end request_tc [:show, :edit, arg] end info_subcommands = nil info_subcommands_abbrev = nil # * `i[nfo]` # * Show information about current frame (local/instance variables and defined constants). # * `i[nfo]` <subcommand> # * `info` has the following sub-commands. # * Sub-commands can be specified with few letters which is unambiguous, like `l` for 'locals'. # * `i[nfo] l or locals or local_variables` # * Show information about the current frame (local variables) # * It includes `self` as `%self` and a return value as `_return`. # * `i[nfo] i or ivars or instance_variables` # * Show information about instance variables about `self`. # * `info ivars <expr>` shows the instance variables of the result of `<expr>`. # * `i[nfo] c or consts or constants` # * Show information about accessible constants except toplevel constants. # * `info consts <expr>` shows the constants of a class/module of the result of `<expr>` # * `i[nfo] g or globals or global_variables` # * Show information about global variables # * `i[nfo] th or threads` # * Show all threads (same as `th[read]`). # * `i[nfo] b or breakpoints or w or watchpoints` # * Show all breakpoints and watchpoints. # * `i[nfo] ... /regexp/` # * Filter the output with `/regexp/`. register_command 'i', 'info', unsafe: false do |arg| if /\/(.+)\/\z/ =~ arg pat = Regexp.compile($1) sub = $~.pre_match.strip else sub = arg end if /\A(.?)\b(.)/ =~ sub sub = $1 opt = $2.strip opt = nil if opt.empty? end if sub && !info_subcommands info_subcommands = { locals: %w[ locals local_variables ], ivars: %w[ ivars instance_variables ], consts: %w[ consts constants ], globals:%w[ globals global_variables ], threads:%w[ threads ], breaks: %w[ breakpoints ], watchs: %w[ watchpoints ], } require_relative 'abbrev_command' info_subcommands_abbrev = AbbrevCommand.new(info_subcommands) end if sub sub = info_subcommands_abbrev.search sub, :unknown do |candidates| # note: unreached now @ui.puts "Ambiguous command '#{sub}': #{candidates.join(' ')}" end end case sub when nil request_tc [:show, :default, pat] # something useful when :locals request_tc [:show, :locals, pat] when :ivars request_tc [:show, :ivars, pat, opt] when :consts request_tc [:show, :consts, pat, opt] when :globals request_tc [:show, :globals, pat] when :threads thread_list :retry when :breaks, :watchs show_bps :retry else @ui.puts "unrecognized argument for info command: #{arg}" show_help 'info' :retry end end # * `o[utline]` or `ls` # * Show you available methods, constants, local variables, and instance variables in the current scope. # * `o[utline] <expr>` or `ls <expr>` # * Show you available methods and instance variables of the given object. # * If the object is a class/module, it also lists its constants. register_command 'outline', 'o', 'ls', unsafe: false do |arg| request_tc [:show, :outline, arg] end # * `display` # * Show display setting. # * `display <expr>` # * Show the result of `<expr>` at every suspended timing. register_command 'display', postmortem: false do |arg| if arg && !arg.empty? @displays << arg request_tc [:eval, :try_display, @displays] else request_tc [:eval, :display, @displays] end end # * `undisplay` # * Remove all display settings. # * `undisplay <displaynum>` # * Remove a specified display setting. register_command 'undisplay', postmortem: false, unsafe: false do |arg| case arg when /(\d+)/ if @displays[n = $1.to_i] @displays.delete_at n end request_tc [:eval, :display, @displays] when nil if ask "clear all?", 'N' @displays.clear end :retry end end ### Frame control # * `f[rame]` # * Show the current frame. # * `f[rame] <framenum>` # * Specify a current frame. Evaluation are run on specified frame. register_command 'frame', 'f', unsafe: false do |arg| request_tc [:frame, :set, arg] end # * `up` # * Specify the upper frame. register_command 'up', repeat: true, unsafe: false do |arg| request_tc [:frame, :up] end # * `down` # * Specify the lower frame. register_command 'down', repeat: true, unsafe: false do |arg| request_tc [:frame, :down] end ### Evaluate # * `p <expr>` # * Evaluate like `p <expr>` on the current frame. register_command 'p' do |arg| request_tc [:eval, :p, arg.to_s] end # * `pp <expr>` # * Evaluate like `pp <expr>` on the current frame. register_command 'pp' do |arg| request_tc [:eval, :pp, arg.to_s] end # * `eval <expr>` # * Evaluate `<expr>` on the current frame. register_command 'eval', 'call' do |arg| if arg == nil || arg.empty? show_help 'eval' @ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead." :retry else request_tc [:eval, :call, arg] end end # * `irb` # * Invoke `irb` on the current frame. register_command 'irb' do |arg| if @ui.remote? @ui.puts "not supported on the remote console." :retry end request_tc [:eval, :irb] end ### Trace # * `trace` # * Show available tracers list. # * `trace line` # * Add a line tracer. It indicates line events. # * `trace call` # * Add a call tracer. It indicate call/return events. # * `trace exception` # * Add an exception tracer. It indicates raising exceptions. # * `trace object <expr>` # * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call. # * `trace ... /regexp/` # * Indicates only matched events to `/regexp/`. # * `trace ... into: <file>` # * Save trace information into: `<file>`. # * `trace off <num>` # * Disable tracer specified by `<num>` (use `trace` command to check the numbers). # * `trace off [line|call|pass]` # * Disable all tracers. If `<type>` is provided, disable specified type tracers. register_command 'trace', postmortem: false, unsafe: false do |arg| if (re = /\sinto:\s*(.)/) =~ arg into = $1 arg.sub!(re, '') end if (re = /\s\/(.+)\/\z/) =~ arg pattern = $1 arg.sub!(re, '') end case arg when nil @ui.puts 'Tracers:' @tracers.values.each_with_index{|t, i| @ui.puts "* \##{i} #{t}" } @ui.puts :retry when /\Aline\z/ add_tracer LineTracer.new(@ui, pattern: pattern, into: into) :retry when /\Acall\z/ add_tracer CallTracer.new(@ui, pattern: pattern, into: into) :retry when /\Aexception\z/ add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into) :retry when /\Aobject\s(.)/ request_tc [:trace, :object, $1.strip, {pattern: pattern, into: into}] when /\Aoff\s(\d)\z/ if t = @tracers.values[$1.to_i] t.disable @ui.puts "Disable #{t.to_s}" else @ui.puts "Unmatched: #{$1}" end :retry when /\Aoff(\s+(line|call|exception|object))?\z/ @tracers.values.each{|t| if $2.nil? || t.type == $2 t.disable @ui.puts "Disable #{t.to_s}" end } :retry else @ui.puts "Unknown trace option: #{arg.inspect}" :retry end end # Record # * `record` # * Show recording status. # * `record [on|off]` # * Start/Stop recording. # * `step back` # * Start replay. Step back with the last execution log. # * `s[tep]` does stepping forward with the last log. # * `step reset` # * Stop replay . register_command 'record', postmortem: false, unsafe: false do |arg| case arg when nil, 'on', 'off' request_tc [:record, arg&.to_sym] else @ui.puts "unknown command: #{arg}" :retry end end ### Thread control # * `th[read]` # * Show all threads. # * `th[read] <thnum>` # * Switch thread specified by `<thnum>`. register_command 'th', 'thread', unsafe: false do |arg| case arg when nil, 'list', 'l' thread_list when /(\d+)/ switch_thread $1.to_i else @ui.puts "unknown thread command: #{arg}" end :retry end ### Configuration # * `config` # * Show all configuration with description. # * `config <name>` # * Show current configuration of <name>. # * `config set <name> <val>` or `config <name> = <val>` # * Set <name> to <val>. # * `config append <name> <val>` or `config <name> << <val>` # * Append `<val>` to `<name>` if it is an array. # * `config unset <name>` # * Set <name> to default. register_command 'config', unsafe: false do |arg| config_command arg :retry end # * `source <file>` # * Evaluate lines in `<file>` as debug commands. register_command 'source' do |arg| if arg begin cmds = File.readlines(path = File. (arg)) add_preset_commands path, cmds, kick: true, continue: false rescue Errno::ENOENT @ui.puts "File not found: #{arg}" end else show_help 'source' end :retry end # * `open` # * open debuggee port on UNIX domain socket and wait for attaching. # * Note that `open` command is EXPERIMENTAL. # * `open [<host>:]<port>` # * open debuggee port on TCP/IP with given `[<host>:]<port>` and wait for attaching. # * `open vscode` # * open debuggee port for VSCode and launch VSCode if available. # * `open chrome` # * open debuggee port for Chrome and wait for attaching. register_command 'open' do |arg| case arg&.downcase when '', nil ::DEBUGGER__.open nonstop: true when /\A(\d+)z/ ::DEBUGGER__.open_tcp host: nil, port: $1.to_i, nonstop: true when /\A(.):(\d)\z/ ::DEBUGGER__.open_tcp host: $1, port: $2.to_i, nonstop: true when 'tcp' ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true when 'vscode' CONFIG[:open] = 'vscode' ::DEBUGGER__.open nonstop: true when 'chrome', 'cdp' CONFIG[:open] = 'chrome' ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true else raise "Unknown arg: #{arg}" end :retry end ### Help # * `h[elp]` # * Show help for all commands. # * `h[elp] <command>` # * Show help for the given command. register_command 'h', 'help', '?', unsafe: false do |arg| show_help arg :retry end end
#register_var(v, tid)
[ GitHub ]# File 'lib/debug/server_dap.rb', line 710
def register_var v, tid if (tl_vid = v[:variablesReference]) > 0 vid = @var_map.size + 1 @var_map[vid] = [:variable, tid, tl_vid] v[:variablesReference] = vid end end
#register_vars(vars, tid)
[ GitHub ]# File 'lib/debug/server_dap.rb', line 718
def register_vars vars, tid raise tid.inspect unless tid.kind_of?(Integer) vars.each{|v| register_var v, tid } end
#rehash_bps
[ GitHub ]# File 'lib/debug/session.rb', line 1358
def rehash_bps bps = @bps.values @bps.clear bps.each{|bp| add_bp bp } end
#repl_add_breakpoint(arg)
[ GitHub ]# File 'lib/debug/session.rb', line 1430
def repl_add_breakpoint arg expr = parse_break 'break', arg.strip cond = expr[:if] cmd = expr[:cmd] path = expr[:path] case expr[:sig] when /\A(\d+)\z/ add_line_breakpoint @tc.location.path, $1.to_i, cond: cond, command: cmd when /\A(.)[:\s](\d+)\z/ add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd when /\A(.)([\.\#])(.)\z/ request_tc [:breakpoint, :method, $1, $2, $3, cond, cmd, path] return :noretry when nil add_check_breakpoint cond, path, cmd else @ui.puts "Unknown breakpoint format: #{arg}" @ui.puts show_help 'b' end end
#repl_add_catch_breakpoint(arg)
[ GitHub ]# File 'lib/debug/session.rb', line 1453
def repl_add_catch_breakpoint arg expr = parse_break 'catch', arg.strip cond = expr[:if] cmd = expr[:cmd] path = expr[:path] bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path) add_bp bp end
#repl_add_watch_breakpoint(arg)
[ GitHub ]# File 'lib/debug/session.rb', line 1463
def repl_add_watch_breakpoint arg expr = parse_break 'watch', arg.strip cond = expr[:if] cmd = expr[:cmd] path = Regexp.compile(expr[:path]) if expr[:path] request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path] end
#request_tc(req)
[ GitHub ]# File 'lib/debug/session.rb', line 241
def request_tc(req) @tc << req end
#reset_ui(ui)
[ GitHub ]# File 'lib/debug/session.rb', line 216
def reset_ui ui @ui.deactivate @ui = ui # activate new ui @tp_thread_begin.disable @ui.activate self if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread) thc.mark_as_management end @tp_thread_begin.enable end
#resolve_path(file)
[ GitHub ]# File 'lib/debug/session.rb', line 1749
def resolve_path file File.realpath(File. (file)) rescue Errno::ENOENT case file when '-e', '-' return file else $LOAD_PATH.each do |lp| libpath = File.join(lp, file) return File.realpath(libpath) rescue Errno::ENOENT # next end end raise end
#restart_all_threads (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1673
private def restart_all_threads stopper = @thread_stopper stopper.disable if stopper.enabled? waiting_thread_clients.each{|tc| next if @tc == tc tc << :continue } end
#running_thread_clients_count (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1638
private def running_thread_clients_count @th_clients.count{|th, tc| next if tc.management? next unless tc.running? true } end
#save_int_trap(cmd)
[ GitHub ]# File 'lib/debug/session.rb', line 1940
def save_int_trap cmd prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, cmd prev end
#session_server_main
[ GitHub ]# File 'lib/debug/session.rb', line 233
def session_server_main while evt = pop_event process_event evt end ensure deactivate end
#set_no_sigint_hook(old, new)
[ GitHub ]#setup_threads
[ GitHub ]# File 'lib/debug/session.rb', line 1588
def setup_threads prev_clients = @th_clients @th_clients = {} Thread.list.each{|th| if tc = prev_clients[th] @th_clients[th] = tc else create_thread_client(th) end } end
#show_bps(specific_bp = nil)
[ GitHub ]# File 'lib/debug/session.rb', line 1343
def show_bps specific_bp = nil iterate_bps do |key, bp, i| @ui.puts "#%d %s" % [i, bp.to_s] if !specific_bp || bp == specific_bp end end
#show_help(arg = nil)
[ GitHub ]# File 'lib/debug/session.rb', line 1264
def show_help arg = nil instructions = (DEBUGGER__.commands.keys + DEBUGGER__.commands.values).uniq print_instructions = proc do |desc| desc.split("\n").each do |line| next if line.start_with?(" ") # workaround for step back formatted_line = line.gsub(/[\[\]\*]/, "").strip instructions.each do |inst| if formatted_line.start_with?("`#{inst}") desc.sub!(line, colorize(line, [:CYAN, :BOLD])) end end end @ui.puts desc end print_category = proc do |cat| @ui.puts "\n" @ui.puts colorize("### #{cat}", [:GREEN, :BOLD]) @ui.puts "\n" end DEBUGGER__.helps.each { |cat, cs| # categories if arg.nil? print_category.call(cat) else cs.each { |ws, _| if ws.include?(arg) print_category.call(cat) break end } end # instructions cs.each { |ws, desc| if arg.nil? || ws.include?(arg) print_instructions.call(desc.dup) return if arg end } } @ui.puts "not found: #{arg}" if arg end
#source(iseq)
[ GitHub ]# File 'lib/debug/session.rb', line 363
def source iseq if !CONFIG[:no_color] @sr.get_colored(iseq) else @sr.get(iseq) end end
#step_command(type, arg)
[ GitHub ]# File 'lib/debug/session.rb', line 1156
def step_command type, arg if type == :until leave_subsession [:step, type, arg] return end case arg when nil, /\A\d+\z/ if type == :in && @tc.recorder&. request_tc [:step, type, arg&.to_i] else leave_subsession [:step, type, arg&.to_i] end when /\A(back)\z/, /\A(back)\s(\d)\z/, /\A(reset)\z/ if type != :in @ui.puts "only `step #{arg}` is supported." :retry else type = $1.to_sym iter = $2&.to_i request_tc [:step, type, iter] end else @ui.puts "Unknown option: #{arg}" :retry end end
#stop_all_threads (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1666
private def stop_all_threads return if running_thread_clients_count == 0 stopper = @thread_stopper stopper.enable unless stopper.enabled? end
#stop_stepping?(file, line, subsession_id = nil) ⇒ Boolean
# File 'lib/debug/session.rb', line 158
def stop_stepping? file, line, subsession_id = nil if @bps.has_key? [file, line] true elsif subsession_id && @subsession_id != subsession_id true else false end end
#switch_thread(n)
[ GitHub ]# File 'lib/debug/session.rb', line 1575
def switch_thread n thcs, _unmanaged_ths = update_thread_list if tc = thcs[n] if tc.waiting? @tc = tc else @ui.puts "#{tc.thread} is not controllable yet." end end thread_list end
#thread_list
[ GitHub ]# File 'lib/debug/session.rb', line 1556
def thread_list thcs, unmanaged_ths = update_thread_list thcs.each_with_index{|thc, i| @ui.puts "#{@tc == thc ? "--> " : " "}\##{i} #{thc}" } if !unmanaged_ths.empty? @ui.puts "The following threads are not managed yet by the debugger:" unmanaged_ths.each{|th| @ui.puts " " + th.to_s } end end
#thread_stopper (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1654
private def thread_stopper TracePoint.new(:line) do # run on each thread tc = ThreadClient.current next if tc.management? next unless tc.running? next if tc == @tc tc.on_pause end end
#update_thread_list
threads
# File 'lib/debug/session.rb', line 1538
def update_thread_list list = Thread.list thcs = [] unmanaged = [] list.each{|th| if thc = @th_clients[th] if !thc.management? thcs << thc end else unmanaged << th end } return thcs.sort_by{|thc| thc.id}, unmanaged end
#wait_command
[ GitHub ]# File 'lib/debug/session.rb', line 399
def wait_command if @preset_command if @preset_command.commands.empty? if @preset_command.auto_continue @preset_command = nil leave_subsession :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_UI'] == 'terminal' line = @ui.readline prompt end case line when String process_command line when Hash process_protocol_request line # defined in server.rb else raise "unexpected input: #{line.inspect}" end end
#wait_command_loop
[ GitHub ]# File 'lib/debug/session.rb', line 375
def wait_command_loop loop do case wait_command when :retry # nothing else break end rescue Interrupt @ui.puts "\n^C" retry end end
#waiting_thread_clients (private)
[ GitHub ]# File 'lib/debug/session.rb', line 1646
private def waiting_thread_clients @th_clients.map{|th, tc| next if tc.management? next unless tc.waiting? tc }.compact end
#width
[ GitHub ]# File 'lib/debug/session.rb', line 1834
def width @ui.width end