debug 1.6.1 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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|