module DEBUGGER__ module DAP_TraceInspector class MultiTracer < Tracer def initialize ui, evts, trace_params, max_log_size: nil, **kw @evts = evts @log = [] @trace_params = trace_params if max_log_size @max_log_size = max_log_size else @max_log_size = 50000 end @dropped_trace_cnt = 0 super(ui, **kw) @type = 'multi' @name = 'TraceInspector' end attr_accessor :dropped_trace_cnt attr_reader :log def setup @tracer = TracePoint.new(*@evts){|tp| next if skip?(tp) case tp.event when :call, :c_call, :b_call if @trace_params params = parameters_info tp end append(call_trace_log(tp, params: params)) when :return, :c_return, :b_return return_str = DEBUGGER__.safe_inspect(tp.return_value, short: true, max_length: 4096) append(call_trace_log(tp, return_str: return_str)) when :line append(line_trace_log(tp)) end } end def parameters_info tp b = tp.binding tp.parameters.map{|_type, name| begin { name: name, value: DEBUGGER__.safe_inspect(b.local_variable_get(name), short: true, max_length: 4096) } rescue NameError, TypeError nil end }.compact end def call_identifier_str tp if tp.defined_class minfo(tp) else "block" end end def append log if @log.size >= @max_log_size @dropped_trace_cnt += 1 @log.shift end @log << log end def call_trace_log tp, return_str: nil, params: nil log = { depth: DEBUGGER__.frame_depth, name: call_identifier_str(tp), threadId: Thread.current.instance_variable_get(:@__thread_client_id), location: { path: tp.path, line: tp.lineno } } log[:returnValue] = return_str if return_str log[:parameters] = params if params && params.size > 0 log end def line_trace_log tp { depth: DEBUGGER__.frame_depth, threadId: Thread.current.instance_variable_get(:@__thread_client_id), location: { path: tp.path, line: tp.lineno } } end def skip? tp super || !@evts.include?(tp.event) end def skip_with_pattern?(tp) super && !tp.method_id&.match?(@pattern) end end class Custom_Recorder < ThreadClient::Recorder def initialize max_log_size: nil if max_log_size @max_log_size = max_log_size else @max_log_size = 50000 end @dropped_trace_cnt = 0 super() end attr_accessor :dropped_trace_cnt def append frames if @log.size >= @max_log_size @dropped_trace_cnt += 1 @log.shift end @log << frames end end module Custom_UI_DAP def custom_dap_request_rdbgTraceInspector(req) @q_msg << req end end module Custom_Session def process_trace_cmd req cmd = req.dig('arguments', 'subCommand') case cmd when 'enable' events = req.dig('arguments', 'events') evts = [] trace_params = false filter = req.dig('arguments', 'filterRegExp') max_log_size = req.dig('arguments', 'maxLogSize') events.each{|evt| case evt when 'traceLine' evts << :line when 'traceCall' evts << :call evts << :b_call when 'traceReturn' evts << :return evts << :b_return when 'traceParams' trace_params = true when 'traceClanguageCall' evts << :c_call when 'traceClanguageReturn' evts << :c_return else raise "unknown trace type #{evt}" end } add_tracer MultiTracer.new @ui, evts, trace_params, max_log_size: max_log_size, pattern: filter @ui.respond req, {} when 'disable' if t = find_multi_trace t.disable end @ui.respond req, {} when 'collect' logs = [] if t = find_multi_trace logs = t.log if t.dropped_trace_cnt > 0 @ui.puts "Return #{logs.size} traces and #{t.dropped_trace_cnt} traces are dropped" else @ui.puts "Return #{logs.size} traces" end t.dropped_trace_cnt = 0 end @ui.respond req, logs: logs else raise "Unknown trace sub command #{cmd}" end return :retry end def find_multi_trace @tracers.values.each{|t| if t.type == 'multi' return t end } return nil end def process_record_cmd req cmd = req.dig('arguments', 'subCommand') case cmd when 'enable' @tc << [:dap, :rdbgTraceInspector, req] when 'disable' @tc << [:dap, :rdbgTraceInspector, req] when 'step' tid = req.dig('arguments', 'threadId') count = req.dig('arguments', 'count') if tc = find_waiting_tc(tid) @ui.respond req, {} tc << [:step, :in, count] else fail_response req end when 'stepBack' tid = req.dig('arguments', 'threadId') count = req.dig('arguments', 'count') if tc = find_waiting_tc(tid) @ui.respond req, {} tc << [:step, :back, count] else fail_response req end when 'collect' tid = req.dig('arguments', 'threadId') if tc = find_waiting_tc(tid) tc << [:dap, :rdbgTraceInspector, req] else fail_response req end else raise "Unknown record sub command #{cmd}" end end def custom_dap_request_rdbgTraceInspector(req) cmd = req.dig('arguments', 'command') case cmd when 'trace' process_trace_cmd req when 'record' process_record_cmd req else raise "Unknown command #{cmd}" end end def custom_dap_request_event_rdbgTraceInspector(req, result) cmd = req.dig('arguments', 'command') case cmd when 'record' process_event_record_cmd(req, result) else raise "Unknown command #{cmd}" end end def process_event_record_cmd(req, result) cmd = req.dig('arguments', 'subCommand') case cmd when 'enable' @ui.respond req, {} when 'disable' @ui.respond req, {} when 'collect' cnt = result.delete :dropped_trace_cnt if cnt > 0 @ui.puts "Return #{result[:logs].size} traces and #{cnt} traces are dropped" else @ui.puts "Return #{result[:logs].size} traces" end @ui.respond req, result else raise "Unknown command #{cmd}" end end end module Custom_ThreadClient def custom_dap_request_rdbgTraceInspector(req) cmd = req.dig('arguments', 'command') case cmd when 'record' process_record_cmd(req) else raise "Unknown command #{cmd}" end end def process_record_cmd(req) cmd = req.dig('arguments', 'subCommand') case cmd when 'enable' size = req.dig('arguments', 'maxLogSize') @recorder = Custom_Recorder.new max_log_size: size @recorder.enable event! :protocol_result, :rdbgTraceInspector, req when 'disable' if @recorder&.enabled? @recorder.disable end @recorder = nil event! :protocol_result, :rdbgTraceInspector, req when 'collect' logs = [] log_index = nil trace_cnt = 0 unless @recorder.nil? log_index = @recorder.log_index @recorder.log.each{|frames| crt_frame = frames[0] log = { name: crt_frame.name, location: { path: crt_frame.location.path, line: crt_frame.location.lineno, }, depth: crt_frame.frame_depth } if params = crt_frame.iseq_parameters_info log[:parameters] = params end if return_str = crt_frame.return_str log[:returnValue] = return_str end logs << log } trace_cnt = @recorder.dropped_trace_cnt @recorder.dropped_trace_cnt = 0 end event! :protocol_result, :rdbgTraceInspector, req, logs: logs, stoppedIndex: log_index, dropped_trace_cnt: trace_cnt else raise "Unknown command #{cmd}" end end end ::DEBUGGER__::SESSION.extend_feature session: Custom_Session, thread_client: Custom_ThreadClient, ui: Custom_UI_DAP end end