debug 1.6.1 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,336 @@
1
+ module DEBUGGER__
2
+ module DAP_TraceInspector
3
+ class MultiTracer < Tracer
4
+ def initialize ui, evts, trace_params, max_log_size: nil, **kw
5
+ @evts = evts
6
+ @log = []
7
+ @trace_params = trace_params
8
+ if max_log_size
9
+ @max_log_size = max_log_size
10
+ else
11
+ @max_log_size = 50000
12
+ end
13
+ @dropped_trace_cnt = 0
14
+ super(ui, **kw)
15
+ @type = 'multi'
16
+ @name = 'TraceInspector'
17
+ end
18
+
19
+ attr_accessor :dropped_trace_cnt
20
+ attr_reader :log
21
+
22
+ def setup
23
+ @tracer = TracePoint.new(*@evts){|tp|
24
+ next if skip?(tp)
25
+
26
+ case tp.event
27
+ when :call, :c_call, :b_call
28
+ if @trace_params
29
+ params = parameters_info tp
30
+ end
31
+ append(call_trace_log(tp, params: params))
32
+ when :return, :c_return, :b_return
33
+ return_str = DEBUGGER__.safe_inspect(tp.return_value, short: true, max_length: 4096)
34
+ append(call_trace_log(tp, return_str: return_str))
35
+ when :line
36
+ append(line_trace_log(tp))
37
+ end
38
+ }
39
+ end
40
+
41
+ def parameters_info tp
42
+ b = tp.binding
43
+ tp.parameters.map{|_type, name|
44
+ begin
45
+ { name: name, value: DEBUGGER__.safe_inspect(b.local_variable_get(name), short: true, max_length: 4096) }
46
+ rescue NameError, TypeError
47
+ nil
48
+ end
49
+ }.compact
50
+ end
51
+
52
+ def call_identifier_str tp
53
+ if tp.defined_class
54
+ minfo(tp)
55
+ else
56
+ "block"
57
+ end
58
+ end
59
+
60
+ def append log
61
+ if @log.size >= @max_log_size
62
+ @dropped_trace_cnt += 1
63
+ @log.shift
64
+ end
65
+ @log << log
66
+ end
67
+
68
+ def call_trace_log tp, return_str: nil, params: nil
69
+ log = {
70
+ depth: DEBUGGER__.frame_depth,
71
+ name: call_identifier_str(tp),
72
+ threadId: Thread.current.instance_variable_get(:@__thread_client_id),
73
+ location: {
74
+ path: tp.path,
75
+ line: tp.lineno
76
+ }
77
+ }
78
+ log[:returnValue] = return_str if return_str
79
+ log[:parameters] = params if params && params.size > 0
80
+ log
81
+ end
82
+
83
+ def line_trace_log tp
84
+ {
85
+ depth: DEBUGGER__.frame_depth,
86
+ threadId: Thread.current.instance_variable_get(:@__thread_client_id),
87
+ location: {
88
+ path: tp.path,
89
+ line: tp.lineno
90
+ }
91
+ }
92
+ end
93
+
94
+ def skip? tp
95
+ super || !@evts.include?(tp.event)
96
+ end
97
+
98
+ def skip_with_pattern?(tp)
99
+ super && !tp.method_id&.match?(@pattern)
100
+ end
101
+ end
102
+
103
+ class Custom_Recorder < ThreadClient::Recorder
104
+ def initialize max_log_size: nil
105
+ if max_log_size
106
+ @max_log_size = max_log_size
107
+ else
108
+ @max_log_size = 50000
109
+ end
110
+ @dropped_trace_cnt = 0
111
+ super()
112
+ end
113
+
114
+ attr_accessor :dropped_trace_cnt
115
+
116
+ def append frames
117
+ if @log.size >= @max_log_size
118
+ @dropped_trace_cnt += 1
119
+ @log.shift
120
+ end
121
+ @log << frames
122
+ end
123
+ end
124
+
125
+ module Custom_UI_DAP
126
+ def custom_dap_request_rdbgTraceInspector(req)
127
+ @q_msg << req
128
+ end
129
+ end
130
+
131
+ module Custom_Session
132
+ def process_trace_cmd req
133
+ cmd = req.dig('arguments', 'subCommand')
134
+ case cmd
135
+ when 'enable'
136
+ events = req.dig('arguments', 'events')
137
+ evts = []
138
+ trace_params = false
139
+ filter = req.dig('arguments', 'filterRegExp')
140
+ max_log_size = req.dig('arguments', 'maxLogSize')
141
+ events.each{|evt|
142
+ case evt
143
+ when 'traceLine'
144
+ evts << :line
145
+ when 'traceCall'
146
+ evts << :call
147
+ evts << :b_call
148
+ when 'traceReturn'
149
+ evts << :return
150
+ evts << :b_return
151
+ when 'traceParams'
152
+ trace_params = true
153
+ when 'traceClanguageCall'
154
+ evts << :c_call
155
+ when 'traceClanguageReturn'
156
+ evts << :c_return
157
+ else
158
+ raise "unknown trace type #{evt}"
159
+ end
160
+ }
161
+ add_tracer MultiTracer.new @ui, evts, trace_params, max_log_size: max_log_size, pattern: filter
162
+ @ui.respond req, {}
163
+ when 'disable'
164
+ if t = find_multi_trace
165
+ t.disable
166
+ end
167
+ @ui.respond req, {}
168
+ when 'collect'
169
+ logs = []
170
+ if t = find_multi_trace
171
+ logs = t.log
172
+ if t.dropped_trace_cnt > 0
173
+ @ui.puts "Return #{logs.size} traces and #{t.dropped_trace_cnt} traces are dropped"
174
+ else
175
+ @ui.puts "Return #{logs.size} traces"
176
+ end
177
+ t.dropped_trace_cnt = 0
178
+ end
179
+ @ui.respond req, logs: logs
180
+ else
181
+ raise "Unknown trace sub command #{cmd}"
182
+ end
183
+ return :retry
184
+ end
185
+
186
+ def find_multi_trace
187
+ @tracers.values.each{|t|
188
+ if t.type == 'multi'
189
+ return t
190
+ end
191
+ }
192
+ return nil
193
+ end
194
+
195
+ def process_record_cmd req
196
+ cmd = req.dig('arguments', 'subCommand')
197
+ case cmd
198
+ when 'enable'
199
+ @tc << [:dap, :rdbgTraceInspector, req]
200
+ when 'disable'
201
+ @tc << [:dap, :rdbgTraceInspector, req]
202
+ when 'step'
203
+ tid = req.dig('arguments', 'threadId')
204
+ count = req.dig('arguments', 'count')
205
+ if tc = find_waiting_tc(tid)
206
+ @ui.respond req, {}
207
+ tc << [:step, :in, count]
208
+ else
209
+ fail_response req
210
+ end
211
+ when 'stepBack'
212
+ tid = req.dig('arguments', 'threadId')
213
+ count = req.dig('arguments', 'count')
214
+ if tc = find_waiting_tc(tid)
215
+ @ui.respond req, {}
216
+ tc << [:step, :back, count]
217
+ else
218
+ fail_response req
219
+ end
220
+ when 'collect'
221
+ tid = req.dig('arguments', 'threadId')
222
+ if tc = find_waiting_tc(tid)
223
+ tc << [:dap, :rdbgTraceInspector, req]
224
+ else
225
+ fail_response req
226
+ end
227
+ else
228
+ raise "Unknown record sub command #{cmd}"
229
+ end
230
+ end
231
+
232
+ def custom_dap_request_rdbgTraceInspector(req)
233
+ cmd = req.dig('arguments', 'command')
234
+ case cmd
235
+ when 'trace'
236
+ process_trace_cmd req
237
+ when 'record'
238
+ process_record_cmd req
239
+ else
240
+ raise "Unknown command #{cmd}"
241
+ end
242
+ end
243
+
244
+ def custom_dap_request_event_rdbgTraceInspector(req, result)
245
+ cmd = req.dig('arguments', 'command')
246
+ case cmd
247
+ when 'record'
248
+ process_event_record_cmd(req, result)
249
+ else
250
+ raise "Unknown command #{cmd}"
251
+ end
252
+ end
253
+
254
+ def process_event_record_cmd(req, result)
255
+ cmd = req.dig('arguments', 'subCommand')
256
+ case cmd
257
+ when 'enable'
258
+ @ui.respond req, {}
259
+ when 'disable'
260
+ @ui.respond req, {}
261
+ when 'collect'
262
+ cnt = result.delete :dropped_trace_cnt
263
+ if cnt > 0
264
+ @ui.puts "Return #{result[:logs].size} traces and #{cnt} traces are dropped"
265
+ else
266
+ @ui.puts "Return #{result[:logs].size} traces"
267
+ end
268
+ @ui.respond req, result
269
+ else
270
+ raise "Unknown command #{cmd}"
271
+ end
272
+ end
273
+ end
274
+
275
+ module Custom_ThreadClient
276
+ def custom_dap_request_rdbgTraceInspector(req)
277
+ cmd = req.dig('arguments', 'command')
278
+ case cmd
279
+ when 'record'
280
+ process_record_cmd(req)
281
+ else
282
+ raise "Unknown command #{cmd}"
283
+ end
284
+ end
285
+
286
+ def process_record_cmd(req)
287
+ cmd = req.dig('arguments', 'subCommand')
288
+ case cmd
289
+ when 'enable'
290
+ size = req.dig('arguments', 'maxLogSize')
291
+ @recorder = Custom_Recorder.new max_log_size: size
292
+ @recorder.enable
293
+ event! :protocol_result, :rdbgTraceInspector, req
294
+ when 'disable'
295
+ if @recorder&.enabled?
296
+ @recorder.disable
297
+ end
298
+ @recorder = nil
299
+ event! :protocol_result, :rdbgTraceInspector, req
300
+ when 'collect'
301
+ logs = []
302
+ log_index = nil
303
+ trace_cnt = 0
304
+ unless @recorder.nil?
305
+ log_index = @recorder.log_index
306
+ @recorder.log.each{|frames|
307
+ crt_frame = frames[0]
308
+ log = {
309
+ name: crt_frame.name,
310
+ location: {
311
+ path: crt_frame.location.path,
312
+ line: crt_frame.location.lineno,
313
+ },
314
+ depth: crt_frame.frame_depth
315
+ }
316
+ if params = crt_frame.iseq_parameters_info
317
+ log[:parameters] = params
318
+ end
319
+ if return_str = crt_frame.return_str
320
+ log[:returnValue] = return_str
321
+ end
322
+ logs << log
323
+ }
324
+ trace_cnt = @recorder.dropped_trace_cnt
325
+ @recorder.dropped_trace_cnt = 0
326
+ end
327
+ event! :protocol_result, :rdbgTraceInspector, req, logs: logs, stoppedIndex: log_index, dropped_trace_cnt: trace_cnt
328
+ else
329
+ raise "Unknown command #{cmd}"
330
+ end
331
+ end
332
+ end
333
+
334
+ ::DEBUGGER__::SESSION.extend_feature session: Custom_Session, thread_client: Custom_ThreadClient, ui: Custom_UI_DAP
335
+ end
336
+ end
@@ -147,6 +147,15 @@ module DEBUGGER__
147
147
  end
148
148
  end
149
149
 
150
+ def iseq_parameters_info
151
+ case frame_type
152
+ when :block, :method
153
+ parameters_info
154
+ else
155
+ nil
156
+ end
157
+ end
158
+
150
159
  def parameters_info
151
160
  vars = iseq.parameters_symbols
152
161
  vars.map{|var|
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'irb'
4
+
5
+ module DEBUGGER__
6
+ module IrbPatch
7
+ def evaluate(line, line_no)
8
+ SESSION.send(:restart_all_threads)
9
+ super
10
+ # This is to communicate with the test framework so it can feed the next input
11
+ puts "INTERNAL_INFO: {}" if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
12
+ ensure
13
+ SESSION.send(:stop_all_threads)
14
+ end
15
+ end
16
+
17
+ class ThreadClient
18
+ def activate_irb_integration
19
+ IRB.setup(location, argv: [])
20
+ workspace = IRB::WorkSpace.new(current_frame&.binding || TOPLEVEL_BINDING)
21
+ irb = IRB::Irb.new(workspace)
22
+ IRB.conf[:MAIN_CONTEXT] = irb.context
23
+ IRB::Debug.setup(irb)
24
+ IRB::Context.prepend(IrbPatch)
25
+ end
26
+ end
27
+ end
data/lib/debug/local.rb CHANGED
@@ -13,23 +13,28 @@ module DEBUGGER__
13
13
  false
14
14
  end
15
15
 
16
- def activate session, on_fork: false
17
- unless CONFIG[:no_sigint_hook]
18
- prev_handler = trap(:SIGINT){
19
- if session.active?
20
- ThreadClient.current.on_trap :SIGINT
21
- end
22
- }
23
- session.intercept_trap_sigint_start prev_handler
24
- end
16
+ def activate_sigint
17
+ prev_handler = trap(:SIGINT){
18
+ if SESSION.active?
19
+ ThreadClient.current.on_trap :SIGINT
20
+ end
21
+ }
22
+ SESSION.intercept_trap_sigint_start prev_handler
25
23
  end
26
24
 
27
- def deactivate
25
+ def deactivate_sigint
28
26
  if SESSION.intercept_trap_sigint?
29
27
  prev = SESSION.intercept_trap_sigint_end
30
28
  trap(:SIGINT, prev)
31
29
  end
30
+ end
31
+
32
+ def activate session, on_fork: false
33
+ activate_sigint unless CONFIG[:no_sigint_hook]
34
+ end
32
35
 
36
+ def deactivate
37
+ deactivate_sigint
33
38
  @console.deactivate
34
39
  end
35
40
 
@@ -42,6 +47,7 @@ module DEBUGGER__
42
47
  end
43
48
 
44
49
  def quit n
50
+ yield
45
51
  exit n
46
52
  end
47
53
 
data/lib/debug/open.rb CHANGED
File without changes
File without changes
data/lib/debug/prelude.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- return if defined?(::DEBUGGER__)
3
+ return if ENV['RUBY_DEBUG_ENABLE'] == '0'
4
+ return if defined?(::DEBUGGER__::Session)
4
5
 
5
6
  # Put the following line in your login script (e.g. ~/.bash_profile) with modified path:
6
7
  #
data/lib/debug/server.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'socket'
4
- require 'etc'
5
4
  require_relative 'config'
6
5
  require_relative 'version'
7
6
 
@@ -22,6 +21,7 @@ module DEBUGGER__
22
21
 
23
22
  class Terminate < StandardError; end
24
23
  class GreetingError < StandardError; end
24
+ class RetryConnection < StandardError; end
25
25
 
26
26
  def deactivate
27
27
  @reader_thread.raise Terminate
@@ -78,6 +78,8 @@ module DEBUGGER__
78
78
  next
79
79
  rescue Terminate
80
80
  raise # should catch at outer scope
81
+ rescue RetryConnection
82
+ next
81
83
  rescue => e
82
84
  DEBUGGER__.warn "ReaderThreadError: #{e}"
83
85
  pp e.backtrace
@@ -128,8 +130,10 @@ module DEBUGGER__
128
130
  def greeting
129
131
  case g = @sock.gets
130
132
  when /^info cookie:\s+(.*)$/
133
+ require 'etc'
134
+
131
135
  check_cookie $1
132
- @sock.puts "PID: #{Process.pid}, $0: #{$0}"
136
+ @sock.puts "PID: #{Process.pid}, $0: #{$0}, session_name: #{CONFIG[:session_name]}"
133
137
  @sock.puts "debug #{VERSION} on #{RUBY_DESCRIPTION}"
134
138
  @sock.puts "uname: #{Etc.uname.inspect}"
135
139
  @sock.close
@@ -140,11 +144,14 @@ module DEBUGGER__
140
144
 
141
145
  # TODO: protocol version
142
146
  if v != VERSION
143
- raise GreetingError, "Incompatible version (server:#{VERSION} and client:#{$1})"
147
+ @sock.puts msg = "out DEBUGGER: Incompatible version (server:#{VERSION} and client:#{$1})"
148
+ raise GreetingError, msg
144
149
  end
145
150
  parse_option(params)
146
151
 
147
- puts "DEBUGGER (client): Connected. PID:#{Process.pid}, $0:#{$0}"
152
+ session_name = CONFIG[:session_name]
153
+ session_name_str = ", session_name:#{session_name}" if session_name
154
+ puts "DEBUGGER (client): Connected. PID:#{Process.pid}, $0:#{$0}#{session_name_str}"
148
155
  puts "DEBUGGER (client): Type `Ctrl-C` to enter the debug console." unless @need_pause_at_first
149
156
  puts
150
157
 
@@ -157,16 +164,13 @@ module DEBUGGER__
157
164
  @need_pause_at_first = false
158
165
  dap_setup @sock.read($1.to_i)
159
166
 
160
- when /^GET \/.* HTTP\/1.1/
167
+ when /^GET\s\/json\sHTTP\/1.1/, /^GET\s\/json\/version\sHTTP\/1.1/, /^GET\s\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\sHTTP\/1.1/
168
+ # The reason for not using @uuid here is @uuid is nil if users run debugger without `--open=chrome`.
169
+
161
170
  require_relative 'server_cdp'
162
171
 
163
172
  self.extend(UI_CDP)
164
- @repl = false
165
- @need_pause_at_first = false
166
- CONFIG.set_config no_color: true
167
-
168
- @ws_server = UI_CDP::WebSocketServer.new(@sock)
169
- @ws_server.handshake
173
+ send_chrome_response g
170
174
  else
171
175
  raise GreetingError, "Unknown greeting message: #{g}"
172
176
  end
@@ -174,17 +178,17 @@ module DEBUGGER__
174
178
 
175
179
  def process
176
180
  while true
177
- DEBUGGER__.info "sleep IO.select"
178
- r = IO.select([@sock])
179
- DEBUGGER__.info "wakeup IO.select"
181
+ DEBUGGER__.debug{ "sleep IO.select" }
182
+ _r = IO.select([@sock])
183
+ DEBUGGER__.debug{ "wakeup IO.select" }
180
184
 
181
185
  line = @session.process_group.sync do
182
186
  unless IO.select([@sock], nil, nil, 0)
183
- DEBUGGER__.info "UI_Server can not read"
187
+ DEBUGGER__.debug{ "UI_Server can not read" }
184
188
  break :can_not_read
185
189
  end
186
190
  @sock.gets&.chomp.tap{|line|
187
- DEBUGGER__.info "UI_Server received: #{line}"
191
+ DEBUGGER__.debug{ "UI_Server received: #{line}" }
188
192
  }
189
193
  end
190
194
 
@@ -340,12 +344,12 @@ module DEBUGGER__
340
344
  if @repl
341
345
  raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
342
346
  line = "input #{Process.pid}"
343
- DEBUGGER__.info "send: #{line}"
347
+ DEBUGGER__.debug{ "send: #{line}" }
344
348
  s.puts line
345
349
  end
346
350
  sleep 0.01 until @q_msg
347
351
  @q_msg.pop.tap{|msg|
348
- DEBUGGER__.info "readline: #{msg.inspect}"
352
+ DEBUGGER__.debug{ "readline: #{msg.inspect}" }
349
353
  }
350
354
  end || 'continue')
351
355
 
@@ -361,7 +365,7 @@ module DEBUGGER__
361
365
  Process.kill(TRAP_SIGNAL, Process.pid)
362
366
  end
363
367
 
364
- def quit n
368
+ def quit n, &_b
365
369
  # ignore n
366
370
  sock do |s|
367
371
  s.puts "quit"
@@ -395,6 +399,7 @@ module DEBUGGER__
395
399
  raise "Specify digits for port number"
396
400
  end
397
401
  end
402
+ @uuid = nil # for CDP
398
403
 
399
404
  super()
400
405
  end
@@ -402,14 +407,14 @@ module DEBUGGER__
402
407
  def chrome_setup
403
408
  require_relative 'server_cdp'
404
409
 
405
- unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr)
406
- DEBUGGER__.warn <<~EOS
407
- With Chrome browser, type the following URL in the address-bar:
410
+ @uuid = SecureRandom.uuid
411
+ @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
412
+ DEBUGGER__.warn <<~EOS
413
+ With Chrome browser, type the following URL in the address-bar:
408
414
 
409
- devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{SecureRandom.uuid}
415
+ devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&noJavaScriptCompletion=true&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
410
416
 
411
- EOS
412
- end
417
+ EOS
413
418
  end
414
419
 
415
420
  def accept
@@ -434,7 +439,7 @@ module DEBUGGER__
434
439
  #
435
440
  EOS
436
441
 
437
- case CONFIG[:open_frontend]
442
+ case CONFIG[:open]
438
443
  when 'chrome'
439
444
  chrome_setup
440
445
  when 'vscode'
@@ -491,7 +496,7 @@ module DEBUGGER__
491
496
  end
492
497
 
493
498
  ::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
494
- vscode_setup @sock_path if CONFIG[:open_frontend] == 'vscode'
499
+ vscode_setup @sock_path if CONFIG[:open] == 'vscode'
495
500
 
496
501
  begin
497
502
  Socket.unix_server_loop @sock_path do |sock, client|