class DEBUGGER__::ThreadClient
Constants
- SKIP_GLOBAL_LIST
- SPECIAL_LOCAL_VARS
- SUPPORT_TARGET_THREAD
Attributes
id[R]
location[R]
recorder[R]
thread[R]
Public Class Methods
current()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 21 def self.current if thc = Thread.current[:DEBUGGER__ThreadClient] thc else thc = SESSION.get_thread_client Thread.current[:DEBUGGER__ThreadClient] = thc end end
new(id, q_evt, q_cmd, thr = Thread.current)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 76 def initialize id, q_evt, q_cmd, thr = Thread.current @is_management = false @id = id @thread = thr @target_frames = nil @q_evt = q_evt @q_cmd = q_cmd @step_tp = nil @output = [] @frame_formatter = method(:default_frame_formatter) @var_map = {} # { thread_local_var_id => obj } for DAP @obj_map = {} # { object_id => obj } for CDP @recorder = nil @mode = :waiting set_mode :running thr.instance_variable_set(:@__thread_client_id, id) ::DEBUGGER__.info("Thread \##{@id} is created.") end
Public Instance Methods
<<(req)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 178 def << req @q_cmd << req end
assemble_arguments(args)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 35 def assemble_arguments(args) args.map do |arg| "#{colorize_cyan(arg[:name])}=#{arg[:value]}" end.join(", ") end
class_method_map(classes)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 639 def class_method_map(classes) dumped = Array.new classes.reject { |mod| mod >= Object }.map do |mod| methods = mod.public_instance_methods(false).select do |m| dumped.push(m) unless dumped.include?(m) end [mod, methods] end.reverse end
close()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 139 def close @q_cmd.close end
current_frame()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 434 def current_frame if @target_frames @target_frames[@current_frame_index] else nil end end
deactivate()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 96 def deactivate @step_tp.disable if @step_tp end
default_frame_formatter(frame)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 41 def default_frame_formatter frame call_identifier_str = case frame.frame_type when :block level, block_loc, args = frame.block_identifier if !args.empty? args_str = " {|#{assemble_arguments(args)}|}" end "#{colorize_blue("block")}#{args_str} in #{colorize_blue(block_loc + level)}" when :method ci, args = frame.method_identifier if !args.empty? args_str = "(#{assemble_arguments(args)})" end "#{colorize_blue(ci)}#{args_str}" when :c colorize_blue(frame.c_identifier) when :other colorize_blue(frame.other_identifier) end location_str = colorize(frame.location_str, [:GREEN]) result = "#{call_identifier_str} at #{location_str}" if return_str = frame.return_str result += " #=> #{colorize_magenta(return_str)}" end result end
evaluate_result(r)
click to toggle source
# File debug-1.4.0/lib/debug/server_cdp.rb, line 860 def evaluate_result r v = variable nil, r v[:value] end
event!(ev, *args)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 188 def event! ev, *args @q_evt << [self, @output, ev, generate_info, *args] @output = [] end
frame_eval(src, re_raise: false) { |e| ... }
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 352 def frame_eval src, re_raise: false @success_last_eval = false b = current_frame.eval_binding special_local_variables current_frame do |name, var| b.local_variable_set(name, var) if /\%/ !~ name end result = if b f, _l = b.source_location b.eval(src, "(rdbg)/#{f}") else frame_self = current_frame.self instance_eval_for_cmethod(frame_self, src) end @success_last_eval = true result rescue Exception => e return yield(e) if block_given? puts "eval error: #{e}" e.backtrace_locations&.each do |loc| break if loc.path == __FILE__ puts " #{loc}" end raise if re_raise end
frame_str(i, frame: @target_frames[i])
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 602 def frame_str(i, frame: @target_frames[i]) cur_str = (@current_frame_index == i ? '=>' : ' ') prefix = "#{cur_str}##{i}" frame_string = @frame_formatter.call(frame) "#{prefix}\t#{frame_string}" end
generate_info()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 182 def generate_info return unless current_frame { location: current_frame.location_str, line: current_frame.location.lineno } end
inspect()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 143 def inspect if bt = @thread.backtrace "#<DBG:TC #{self.id}:#{@mode}@#{bt[-1]}>" else # bt can be nil "#<DBG:TC #{self.id}:#{@mode}>" end end
instance_eval_for_cmethod(frame_self, src)
click to toggle source
this method is extracted to hide frame_eval
‘s local variables from C method eval’s binding
# File debug-1.4.0/lib/debug/thread_client.rb, line 343 def instance_eval_for_cmethod frame_self, src frame_self.instance_eval(src) end
make_breakpoint(args)
click to toggle source
cmd: breakpoint
# File debug-1.4.0/lib/debug/thread_client.rb, line 651 def make_breakpoint args case args.first when :method klass_name, op, method_name, cond, cmd, path = args[1..] bp = MethodBreakpoint.new(current_frame.eval_binding, klass_name, op, method_name, cond: cond, command: cmd, path: path) begin bp.enable rescue Exception => e puts e.message ::DEBUGGER__::METHOD_ADDED_TRACKER.enable end bp when :watch ivar, object, result, cond, command, path = args[1..] WatchIVarBreakpoint.new(ivar, object, result, cond: cond, command: command, path: path) else raise "unknown breakpoint: #{args}" end end
management?()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 100 def management? @is_management end
mark_as_management()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 104 def mark_as_management @is_management = true end
name()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 135 def name "##{@id} #{@thread.name || @thread.backtrace.last}" end
on_breakpoint(tp, bp)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 216 def on_breakpoint tp, bp suspend tp.event, tp, bp: bp end
on_init(name)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 208 def on_init name wait_reply [:init, name] end
on_load(iseq, eval_src)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 204 def on_load iseq, eval_src wait_reply [:load, iseq, eval_src] end
on_pause()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 228 def on_pause suspend :pause end
on_trace(trace_id, msg)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 212 def on_trace trace_id, msg wait_reply [:trace, trace_id, msg] end
on_trap(sig)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 220 def on_trap sig if waiting? # raise Interrupt else suspend :trap, sig: sig end end
outline_method(o, klass, obj)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 630 def outline_method(o, klass, obj) singleton_class = begin obj.singleton_class; rescue TypeError; nil end maps = class_method_map((singleton_class || klass).ancestors) maps.each do |mod, methods| name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods" o.dump(name, methods) end end
process_cdp(args)
click to toggle source
# File debug-1.4.0/lib/debug/server_cdp.rb, line 641 def process_cdp args type = args.shift req = args.shift case type when :backtrace exception = nil result = { reason: 'other', callFrames: @target_frames.map.with_index{|frame, i| exception = frame.raised_exception if frame == current_frame && frame.has_raised_exception path = frame.realpath || frame.path if path.match /<internal:(.*)>/ path = $1 end { callFrameId: SecureRandom.hex(16), functionName: frame.name, functionLocation: { scriptId: path, lineNumber: 0 }, location: { scriptId: path, lineNumber: frame.location.lineno - 1 # The line number is 0-based. }, url: "http://debuggee#{path}", scopeChain: [ { type: 'local', object: { type: 'object', objectId: rand.to_s } }, { type: 'script', object: { type: 'object', objectId: rand.to_s } }, { type: 'global', object: { type: 'object', objectId: rand.to_s } } ], this: { type: 'object' } } } } if exception result[:data] = evaluate_result exception result[:reason] = 'exception' end event! :cdp_result, :backtrace, req, result when :evaluate res = {} fid, expr = args frame = @target_frames[fid] message = nil if frame && (b = frame.binding) b = b.dup special_local_variables current_frame do |name, var| b.local_variable_set(name, var) if /\%/ !~name end result = nil case req.dig('params', 'objectGroup') when 'popover' case expr # Chrome doesn't read instance variables when /\A\$\S/ global_variables.each{|gvar| if gvar.to_s == expr result = eval(gvar.to_s) break false end } and (message = "Error: Not defined global variable: #{expr.inspect}") when /(\A[A-Z][a-zA-Z]*)/ unless result = search_const(b, $1) message = "Error: Not defined constant: #{expr.inspect}" end else begin # try to check local variables b.local_variable_defined?(expr) or raise NameError result = b.local_variable_get(expr) rescue NameError # try to check method if b.receiver.respond_to? expr, include_all: true result = b.receiver.method(expr) else message = "Error: Can not evaluate: #{expr.inspect}" end end end else begin orig_stdout = $stdout $stdout = StringIO.new result = current_frame.binding.eval(expr.to_s, '(DEBUG CONSOLE)') rescue Exception => e result = e b = result.backtrace.map{|e| " #{e}\n"} line = b.first.match('.*:(\d+):in .*')[1].to_i res[:exceptionDetails] = { exceptionId: 1, text: 'Uncaught', lineNumber: line - 1, columnNumber: 0, exception: evaluate_result(result), } ensure output = $stdout.string $stdout = orig_stdout end end else result = Exception.new("Error: Can not evaluate on this frame") end res[:result] = evaluate_result(result) event! :cdp_result, :evaluate, req, message: message, response: res, output: output when :scope fid = args.shift frame = @target_frames[fid] if b = frame.binding vars = b.local_variables.map{|name| v = b.local_variable_get(name) variable(name, v) } special_local_variables frame do |name, val| vars.unshift variable(name, val) end vars.unshift variable('%self', b.receiver) elsif lvars = frame.local_variables vars = lvars.map{|var, val| variable(var, val) } else vars = [variable('%self', frame.self)] special_local_variables frame do |name, val| vars.unshift variable(name, val) end end event! :cdp_result, :scope, req, vars when :properties oid = args.shift result = [] prop = [] if obj = @obj_map[oid] case obj when Array result = obj.map.with_index{|o, i| variable i.to_s, o } when Hash result = obj.map{|k, v| variable(k, v) } when Struct result = obj.members.map{|m| variable(m, obj[m]) } when String prop = [ property('#length', obj.length), property('#encoding', obj.encoding) ] when Class, Module result = obj.instance_variables.map{|iv| variable(iv, obj.instance_variable_get(iv)) } prop = [property('%ancestors', obj.ancestors[1..])] when Range prop = [ property('#begin', obj.begin), property('#end', obj.end), ] end result += obj.instance_variables.map{|iv| variable(iv, obj.instance_variable_get(iv)) } prop += [property('#class', obj.class)] end event! :cdp_result, :properties, req, result: result, internalProperties: prop end end
process_dap(args)
click to toggle source
# File debug-1.4.0/lib/debug/server_dap.rb, line 588 def process_dap args # pp tc: self, args: args type = args.shift req = args.shift case type when :backtrace event! :dap_result, :backtrace, req, { stackFrames: @target_frames.map{|frame| path = frame.realpath || frame.path ref = frame.file_lines unless path && File.exist?(path) { # id: ??? # filled by SESSION name: frame.name, line: frame.location.lineno, column: 1, source: { name: File.basename(frame.path), path: path, sourceReference: ref, }, } } } when :scopes fid = args.shift frame = @target_frames[fid] lnum = if frame.binding frame.binding.local_variables.size elsif vars = frame.local_variables vars.size else 0 end event! :dap_result, :scopes, req, scopes: [{ name: 'Local variables', presentationHint: 'locals', # variablesReference: N, # filled by SESSION namedVariables: lnum, indexedVariables: 0, expensive: false, }, { name: 'Global variables', presentationHint: 'globals', variablesReference: 1, # GLOBAL namedVariables: global_variables.size, indexedVariables: 0, expensive: false, }] when :scope fid = args.shift frame = @target_frames[fid] if b = frame.binding vars = b.local_variables.map{|name| v = b.local_variable_get(name) variable(name, v) } special_local_variables frame do |name, val| vars.unshift variable(name, val) end vars.unshift variable('%self', b.receiver) elsif lvars = frame.local_variables vars = lvars.map{|var, val| variable(var, val) } else vars = [variable('%self', frame.self)] special_local_variables frame do |name, val| vars.push variable(name, val) end end event! :dap_result, :scope, req, variables: vars, tid: self.id when :variable vid = args.shift obj = @var_map[vid] if obj case req.dig('arguments', 'filter') when 'indexed' start = req.dig('arguments', 'start') || 0 count = req.dig('arguments', 'count') || obj.size vars = (start ... (start + count)).map{|i| variable(i.to_s, obj[i]) } else vars = [] case obj when Hash vars = obj.map{|k, v| variable(DEBUGGER__.safe_inspect(k), v,) } when Struct vars = obj.members.map{|m| variable(m, obj[m]) } when String vars = [ variable('#length', obj.length), variable('#encoding', obj.encoding) ] when Class, Module vars = obj.instance_variables.map{|iv| variable(iv, obj.instance_variable_get(iv)) } vars.unshift variable('%ancestors', obj.ancestors[1..]) when Range vars = [ variable('#begin', obj.begin), variable('#end', obj.end), ] end vars += obj.instance_variables.map{|iv| variable(iv, obj.instance_variable_get(iv)) } vars.unshift variable('#class', obj.class) end end event! :dap_result, :variable, req, variables: (vars || []), tid: self.id when :evaluate fid, expr, context = args frame = @target_frames[fid] message = nil if frame && (b = frame.binding) b = b.dup special_local_variables current_frame do |name, var| b.local_variable_set(name, var) if /\%/ !~ name end case context when 'repl', 'watch' begin result = b.eval(expr.to_s, '(DEBUG CONSOLE)') rescue Exception => e result = e end when 'hover' case expr when /\A\@\S/ begin (r = b.receiver).instance_variable_defined?(expr) or raise(NameError) result = r.instance_variable_get(expr) rescue NameError message = "Error: Not defined instance variable: #{expr.inspect}" end when /\A\$\S/ global_variables.each{|gvar| if gvar.to_s == expr result = eval(gvar.to_s) break false end } and (message = "Error: Not defined global variable: #{expr.inspect}") when /\A[A-Z]/ unless result = search_const(b, expr) message = "Error: Not defined constants: #{expr.inspect}" end else begin # try to check local variables b.local_variable_defined?(expr) or raise NameError result = b.local_variable_get(expr) rescue NameError # try to check method if b.receiver.respond_to? expr, include_all: true result = b.receiver.method(expr) else message = "Error: Can not evaluate: #{expr.inspect}" end end end else message = "Error: unknown context: #{context}" end else result = 'Error: Can not evaluate on this frame' end event! :dap_result, :evaluate, req, message: message, tid: self.id, **evaluate_result(result) when :completions fid, text = args frame = @target_frames[fid] if (b = frame&.binding) && word = text&.split(/[\s\{]/)&.last words = IRB::InputCompletor::retrieve_completion_data(word, bind: b).compact end event! :dap_result, :completions, req, targets: (words || []).map{|phrase| if /\b([_a-zA-Z]\w*[!\?]?)\z/ =~ phrase w = $1 else w = phrase end begin if b&.local_variable_defined?(w) v = b.local_variable_get(w) phrase += " (variable:#{DEBUGGER__.safe_inspect(v)})" end rescue NameError end { label: phrase, text: w, } } else raise "Unknown req: #{args.inspect}" end end
property(name, obj)
click to toggle source
# File debug-1.4.0/lib/debug/server_cdp.rb, line 865 def property name, obj v = variable name, obj v.delete :configurable v.delete :enumerable v end
puts(str = '')
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 164 def puts str = '' if @recorder&.replaying? prefix = colorize_dim("[replay] ") end case str when nil @output << "\n" when Array str.each{|s| puts s} else @output << "#{prefix}#{str.chomp}\n" end end
puts_variable_info(label, obj, pat)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 516 def puts_variable_info label, obj, pat return if pat && pat !~ label begin inspected = obj.inspect rescue Exception => e inspected = e.inspect end mono_info = "#{label} = #{inspected}" w = SESSION::width if mono_info.length >= w info = truncate(mono_info, width: w) else valstr = colored_inspect(obj, width: 2 ** 30) valstr = inspected if valstr.lines.size > 1 info = "#{colorize_cyan(label)} = #{valstr}" end puts info end
replay_suspend()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 287 def replay_suspend # @recorder.current_position suspend :replay, replay_frames: @recorder.current_frame end
running?()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 127 def running? @mode == :running end
search_const(b, expr)
click to toggle source
# File debug-1.4.0/lib/debug/server_cdp.rb, line 843 def search_const b, expr cs = expr.split('::') [Object, *b.eval('Module.nesting')].reverse_each{|mod| if cs.all?{|c| if mod.const_defined?(c) mod = mod.const_get(c) else false end } # if-body return mod end } false end
set_mode(mode)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 108 def set_mode mode # STDERR.puts "#{@mode} => #{mode} @ #{caller.inspect}" # pp caller # mode transition check case mode when :running raise "#{mode} is given, but #{mode}" unless self.waiting? when :waiting # TODO: there is waiting -> waiting # raise "#{mode} is given, but #{mode}" unless self.running? else raise "unknown mode: #{mode}" end # DEBUGGER__.warn "#{@mode} => #{mode} @ #{self.inspect}" @mode = mode end
show_by_editor(path = nil)
click to toggle source
cmd: show edit
# File debug-1.4.0/lib/debug/thread_client.rb, line 547 def show_by_editor path = nil unless path if @target_frames && frame = @target_frames[@current_frame_index] path = frame.path else return # can't get path end end if File.exist?(path) if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR']) puts "command: #{editor}" puts " path: #{path}" system(editor, path) else puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']" end else puts "Can not find file: #{path}" end end
show_consts(pat, only_self: false)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 476 def show_consts pat, only_self: false if s = current_frame&.self cs = {} if s.kind_of? Module cs[s] = :self else s = s.class cs[s] = :self unless only_self end unless only_self s.ancestors.each{|c| break if c == Object; cs[c] = :ancestors} if b = current_frame&.binding b.eval('Module.nesting').each{|c| cs[c] = :nesting unless cs.has_key? c} end end names = {} cs.each{|c, _| c.constants(false).sort.each{|name| next if names.has_key? name names[name] = nil value = c.const_get(name) puts_variable_info name, value, pat } } end end
show_frame(i=0)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 598 def show_frame i=0 puts frame_str(i) end
show_frames(max = nil, pattern = nil)
click to toggle source
cmd: show frames
# File debug-1.4.0/lib/debug/thread_client.rb, line 571 def show_frames max = nil, pattern = nil if @target_frames && (max ||= @target_frames.size) > 0 frames = [] @target_frames.each_with_index{|f, i| next if pattern && !(f.name.match?(pattern) || f.location_str.match?(pattern)) next if CONFIG[:skip_path] && CONFIG[:skip_path].any?{|pat| case pat when String f.location_str.start_with?(pat) when Regexp f.location_str.match?(pat) end } frames << [i, f] } size = frames.size max.times{|i| break unless frames[i] index, frame = frames[i] puts frame_str(index, frame: frame) } puts " # and #{size - max} frames (use `bt' command for all frames)" if max < size end end
show_globals(pat)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 507 def show_globals pat global_variables.sort.each{|name| next if SKIP_GLOBAL_LIST.include? name value = eval(name.to_s) puts_variable_info name, value, pat } end
show_ivars(pat)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 467 def show_ivars pat if s = current_frame&.self s.instance_variables.sort.each{|iv| value = s.instance_variable_get(iv) puts_variable_info iv, value, pat } end end
show_locals(pat)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 452 def show_locals pat if s = current_frame&.self puts_variable_info '%self', s, pat end special_local_variables current_frame do |name, val| puts_variable_info name, val, pat end if vars = current_frame&.local_variables vars.each{|var, val| puts_variable_info var, val, pat } end end
show_outline(expr)
click to toggle source
cmd: show outline
# File debug-1.4.0/lib/debug/thread_client.rb, line 611 def show_outline expr begin obj = frame_eval(expr, re_raise: true) rescue Exception # ignore else o = Output.new(@output) locals = current_frame&.local_variables klass = (obj.class == Class || obj.class == Module ? obj : obj.class) o.dump("constants", obj.constants) if obj.respond_to?(:constants) outline_method(o, klass, obj) o.dump("instance variables", obj.instance_variables) o.dump("class variables", klass.class_variables) o.dump("locals", locals.keys) if locals end end
show_src(frame_index: @current_frame_index, update_line: false, max_lines: CONFIG[:show_src_lines] || 10, start_line: nil, end_line: nil, dir: +1)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 383 def show_src(frame_index: @current_frame_index, update_line: false, max_lines: CONFIG[:show_src_lines] || 10, start_line: nil, end_line: nil, dir: +1) if @target_frames && frame = @target_frames[frame_index] if file_lines = frame.file_lines frame_line = frame.location.lineno - 1 lines = file_lines.map.with_index do |e, i| cur = i == frame_line ? '=>' : ' ' line = colorize_dim('%4d|' % (i+1)) "#{cur}#{line} #{e}" end unless start_line if frame.show_line if dir > 0 start_line = frame.show_line else end_line = frame.show_line - max_lines start_line = [end_line - max_lines, 0].max end else start_line = [frame_line - max_lines/2, 0].max end end unless end_line end_line = [start_line + max_lines, lines.size].min end if update_line frame.show_line = end_line end if start_line != end_line && max_lines puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1 puts lines[start_line ... end_line] end else # no file lines puts "# No sourcefile available for #{frame.path}" end end rescue Exception => e p e pp e.backtrace exit! end
special_local_variables(frame) { |name, send| ... }
click to toggle source
cmd: show
# File debug-1.4.0/lib/debug/thread_client.rb, line 444 def special_local_variables frame SPECIAL_LOCAL_VARS.each do |mid, name| next unless frame&.send("has_#{mid}") name = name.sub('_', '%') if frame.eval_binding.local_variable_defined?(name) yield name, frame.send(mid) end end
step_tp(iter, events = [:line, :b_return, :return]) { |event| ... }
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 301 def step_tp iter, events = [:line, :b_return, :return] @step_tp.disable if @step_tp thread = Thread.current if SUPPORT_TARGET_THREAD @step_tp = TracePoint.new(*events){|tp| next if SESSION.break_at? tp.path, tp.lineno next if !yield(tp.event) next if tp.path.start_with?(__dir__) next if tp.path.start_with?('<internal:trace_point>') next unless File.exist?(tp.path) if CONFIG[:skip_nosrc] loc = caller_locations(1, 1).first next if skip_location?(loc) next if iter && (iter -= 1) > 0 tp.disable suspend tp.event, tp } @step_tp.enable(target_thread: thread) else @step_tp = TracePoint.new(*events){|tp| next if thread != Thread.current next if SESSION.break_at? tp.path, tp.lineno next if !yield(tp.event) next if tp.path.start_with?(__dir__) next if tp.path.start_with?('<internal:trace_point>') next unless File.exist?(tp.path) if CONFIG[:skip_nosrc] loc = caller_locations(1, 1).first next if skip_location?(loc) next if iter && (iter -= 1) > 0 tp.disable suspend tp.event, tp } @step_tp.enable end end
suspend(event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 232 def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil return if management? @current_frame_index = 0 case when postmortem_frames @target_frames = postmortem_frames @postmortem = true when replay_frames @target_frames = replay_frames else @target_frames = DEBUGGER__.capture_frames(__dir__) end cf = @target_frames.first if cf @location = cf.location case event when :return, :b_return, :c_return cf.has_return_value = true cf.return_value = tp.return_value end if CatchBreakpoint === bp cf.has_raised_exception = true cf.raised_exception = bp.last_exc end if postmortem_exc cf.has_raised_exception = true cf.raised_exception = postmortem_exc end end if event != :pause show_src show_frames CONFIG[:show_frames] || 2 set_mode :waiting if bp event! :suspend, :breakpoint, bp.key elsif sig event! :suspend, :trap, sig else event! :suspend, event end else set_mode :waiting end wait_next_action end
to_s()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 151 def to_s loc = current_frame&.location if loc str = "(#{@thread.name || @thread.status})@#{loc}" else str = "(#{@thread.name || @thread.status})@#{@thread.to_s}" end str += " (not under control)" unless self.waiting? str end
truncate(string, width:)
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 539 def truncate(string, width:) str = string[0 .. (width-4)] + '...' str += ">" if str.start_with?("#<") str end
variable(name, obj)
click to toggle source
# File debug-1.4.0/lib/debug/server_cdp.rb, line 896 def variable name, obj case obj when Array variable_ name, obj, 'object', description: "Array(#{obj.size})", subtype: 'array' when Hash variable_ name, obj, 'object', description: "Hash(#{obj.size})", subtype: 'map' when String variable_ name, obj, 'string', description: obj when Class, Module, Struct, Range, Time, Method variable_ name, obj, 'object' when TrueClass, FalseClass variable_ name, obj, 'boolean' when Symbol variable_ name, obj, 'symbol' when Integer, Float variable_ name, obj, 'number' when Exception bt = nil if log = obj.backtrace bt = log.map{|e| " #{e}\n"}.join end variable_ name, obj, 'object', description: "#{obj.inspect}\n#{bt}", subtype: 'error' else variable_ name, obj, 'undefined' end end
variable_(name, obj, type, description: obj.inspect, subtype: nil)
click to toggle source
# File debug-1.4.0/lib/debug/server_cdp.rb, line 872 def variable_ name, obj, type, description: obj.inspect, subtype: nil oid = rand.to_s @obj_map[oid] = obj prop = { name: name, value: { type: type, description: description, value: obj, objectId: oid }, configurable: true, # TODO: Change these parts because enumerable: true # they are not necessarily `true`. } if type == 'object' v = prop[:value] v.delete :value v[:subtype] = subtype if subtype v[:className] = obj.class end prop end
wait_next_action()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 675 def wait_next_action wait_next_action_ rescue SuspendReplay replay_suspend end
wait_next_action_()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 681 def wait_next_action_ # assertions raise "@mode is #{@mode}" if !waiting? unless SESSION.active? pp caller set_mode :running return end while true begin set_mode :waiting if !waiting? cmds = @q_cmd.pop # pp [self, cmds: cmds] break unless cmds ensure set_mode :running end cmd, *args = *cmds case cmd when :continue break when :step step_type = args[0] iter = args[1] case step_type when :in if @recorder&.replaying? @recorder.step_forward raise SuspendReplay else step_tp iter do true end break end when :next frame = @target_frames.first path = frame.location.absolute_path || "!eval:#{frame.path}" line = frame.location.lineno if frame.iseq frame.iseq.traceable_lines_norec(lines = {}) next_line = lines.keys.bsearch{|e| e > line} if !next_line && (last_line = frame.iseq.last_line) > line next_line = last_line end end depth = @target_frames.first.frame_depth step_tp iter do loc = caller_locations(2, 1).first loc_path = loc.absolute_path || "!eval:#{loc.path}" # same stack depth (DEBUGGER__.frame_depth - 3 <= depth) || # different frame (next_line && loc_path == path && (loc_lineno = loc.lineno) > line && loc_lineno <= next_line) end break when :finish finish_frames = (iter || 1) - 1 goal_depth = @target_frames.first.frame_depth - finish_frames step_tp nil, [:return, :b_return] do DEBUGGER__.frame_depth - 3 <= goal_depth ? true : false end break when :back if @recorder&.can_step_back? unless @recorder.backup_frames @recorder.backup_frames = @target_frames end @recorder.step_back raise SuspendReplay else puts "Can not step back more." event! :result, nil end when :reset if @recorder&.replaying? @recorder.step_reset raise SuspendReplay end else raise "unknown: #{type}" end when :eval eval_type, eval_src = *args result_type = nil case eval_type when :p result = frame_eval(eval_src) puts "=> " + color_pp(result, 2 ** 30) if alloc_path = ObjectSpace.allocation_sourcefile(result) puts "allocated at #{alloc_path}:#{ObjectSpace.allocation_sourceline(result)}" end when :pp result = frame_eval(eval_src) puts color_pp(result, SESSION.width) if alloc_path = ObjectSpace.allocation_sourcefile(result) puts "allocated at #{alloc_path}:#{ObjectSpace.allocation_sourceline(result)}" end when :call result = frame_eval(eval_src) when :irb begin result = frame_eval('binding.irb') ensure # workaround: https://github.com/ruby/debug/issues/308 Reline.prompt_proc = nil if defined? Reline end when :display, :try_display failed_results = [] eval_src.each_with_index{|src, i| result = frame_eval(src){|e| failed_results << [i, e.message] "<error: #{e.message}>" } puts "#{i}: #{src} = #{result}" } result_type = eval_type result = failed_results else raise "unknown error option: #{args.inspect}" end event! :result, result_type, result when :frame type, arg = *args case type when :up if @current_frame_index + 1 < @target_frames.size @current_frame_index += 1 show_src max_lines: 1 show_frame(@current_frame_index) end when :down if @current_frame_index > 0 @current_frame_index -= 1 show_src max_lines: 1 show_frame(@current_frame_index) end when :set if arg index = arg.to_i if index >= 0 && index < @target_frames.size @current_frame_index = index else puts "out of frame index: #{index}" end end show_src max_lines: 1 show_frame(@current_frame_index) else raise "unsupported frame operation: #{arg.inspect}" end event! :result, nil when :show type = args.shift case type when :backtrace max_lines, pattern = *args show_frames max_lines, pattern when :list show_src(update_line: true, **(args.first || {})) when :edit show_by_editor(args.first) when :default pat = args.shift show_locals pat show_ivars pat show_consts pat, only_self: true when :locals pat = args.shift show_locals pat when :ivars pat = args.shift show_ivars pat when :consts pat = args.shift show_consts pat when :globals pat = args.shift show_globals pat when :outline show_outline args.first || 'self' else raise "unknown show param: " + [type, *args].inspect end event! :result, nil when :breakpoint case args[0] when :method bp = make_breakpoint args event! :result, :method_breakpoint, bp when :watch ivar, cond, command, path = args[1..] result = frame_eval(ivar) if @success_last_eval object = if b = current_frame.binding b.receiver else current_frame.self end bp = make_breakpoint [:watch, ivar, object, result, cond, command, path] event! :result, :watch_breakpoint, bp else event! :result, nil end end when :trace case args.shift when :object begin obj = frame_eval args.shift, re_raise: true opt = args.shift obj_inspect = obj.inspect width = 50 if obj_inspect.length >= width obj_inspect = truncate(obj_inspect, width: width) end event! :result, :trace_pass, obj.object_id, obj_inspect, opt rescue => e puts e.message event! :result, nil end else raise "unreachable" end when :record case args[0] when nil # ok when :on # enable recording if !@recorder @recorder = Recorder.new @recorder.enable end when :off if @recorder&.enabled? @recorder.disable end else raise "unknown: #{args.inspect}" end if @recorder&.enabled? puts "Recorder for #{Thread.current}: on (#{@recorder.log.size} records)" else puts "Recorder for #{Thread.current}: off" end event! :result, nil when :dap process_dap args when :cdp process_cdp args else raise [cmd, *args].inspect end end rescue SuspendReplay, SystemExit, Interrupt raise rescue Exception => e pp ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace] raise end
wait_reply(event_arg)
click to toggle source
events
# File debug-1.4.0/lib/debug/thread_client.rb, line 195 def wait_reply event_arg return if management? set_mode :waiting event!(*event_arg) wait_next_action end
waiting?()
click to toggle source
# File debug-1.4.0/lib/debug/thread_client.rb, line 131 def waiting? @mode == :waiting end