debug 1.7.1 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a84e87e9582c98cf24ca8aa9f1f30032c10736bf4dbc1c44da016db564a7a574
4
- data.tar.gz: da093ef6ebfd8fc5e4a10fcc39c59826e45829ec09da0d9dc164b9cd71085286
3
+ metadata.gz: 39fceaae982c712433b6bf0bc855328c5d923524658a399926e7a032f5d7cdbf
4
+ data.tar.gz: 4f80f39348e0cd46882bd54e04398342c3d124133aa15edcc57a18f1a20fe449
5
5
  SHA512:
6
- metadata.gz: d663b6db0dba8921aaaa535f834e768367e07fef03613e6a277a27724fb75ecf830c7557465521998d42cbb41744e6335495958f626285468d6e38ae6ec52515
7
- data.tar.gz: cb24e62c5875c069812ff1b056dda3b912a20069d09123b72dbd0da69e61aed66968bc4dc5f80e9b599df743fb7755a2399f1461f344fc43ed9ea6bbf20f251c
6
+ metadata.gz: 67a369a45e2debb29dbb42fcbc32d36e1c40467249d657f45caa8a04a5fb3521a10c91860a3f3407caaa8f97cf81ad35f8a3e5ac83a124b99811d82363afe2cf
7
+ data.tar.gz: b29458d948196114b7cb5c7c182decca2685495b96707d904674714a2256a46bd0244efdde5d1ada8a460d881dff7ad4acafaa2c433c6e0eddbcf928425f6748
data/CONTRIBUTING.md CHANGED
@@ -149,10 +149,10 @@ If the file already exists, **only method** will be added to it.
149
149
  ```ruby
150
150
  # frozen_string_literal: true
151
151
 
152
- require_relative '../support/test_case'
152
+ require_relative '../support/console_test_case'
153
153
 
154
154
  module DEBUGGER__
155
- class FooTest < TestCase
155
+ class FooTest < ConsoleTestCase
156
156
  def program
157
157
  <<~RUBY
158
158
  1| module Foo
data/README.md CHANGED
@@ -26,7 +26,7 @@ New debug.rb has several advantages:
26
26
  * Support threads (almost done) and ractors (TODO).
27
27
  * Support suspending and entering to the console debugging with `Ctrl-C` at most of timing.
28
28
  * Show parameters on backtrace command.
29
- * Support recording & reply debugging.
29
+ * Support recording & replay debugging.
30
30
 
31
31
  # Installation
32
32
 
@@ -476,6 +476,7 @@ config set no_color true
476
476
  * `RUBY_DEBUG_NO_SIGINT_HOOK` (`no_sigint_hook`): Do not suspend on SIGINT (default: false)
477
477
  * `RUBY_DEBUG_NO_RELINE` (`no_reline`): Do not use Reline library (default: false)
478
478
  * `RUBY_DEBUG_NO_HINT` (`no_hint`): Do not show the hint on the REPL (default: false)
479
+ * `RUBY_DEBUG_NO_LINENO` (`no_lineno`): Do not show line numbers (default: false)
479
480
 
480
481
  * CONTROL
481
482
  * `RUBY_DEBUG_SKIP_PATH` (`skip_path`): Skip showing/entering frames for given paths
@@ -563,9 +564,9 @@ The `<...>` notation means the argument.
563
564
  * `u[ntil]`
564
565
  * Similar to `next` command, but only stop later lines or the end of the current frame.
565
566
  * Similar to gdb's `advance` command.
566
- * `u[ntil] <[file:]line>
567
+ * `u[ntil] <[file:]line>`
567
568
  * Run til the program reaches given location or the end of the current frame.
568
- * `u[ntil] <name>
569
+ * `u[ntil] <name>`
569
570
  * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
570
571
  * `c` or `cont` or `continue`
571
572
  * Resume the program.
@@ -908,6 +909,8 @@ Attach mode:
908
909
  'rdbg -A host port' tries to connect to host:port via TCP/IP.
909
910
 
910
911
  Other options:
912
+ -v Show version number
913
+ --version Show version number and exit
911
914
  -h, --help Print help
912
915
  --util=NAME Utility mode (used by tools)
913
916
  --stop-at-load Stop immediately when the debugging feature is loaded.
data/Rakefile CHANGED
@@ -35,9 +35,14 @@ task :check_readme do
35
35
  end
36
36
  end
37
37
 
38
+ desc "Run debug.gem test-framework tests"
39
+ Rake::TestTask.new(:test_test) do |t|
40
+ t.test_files = FileList["test/support/*_test.rb"]
41
+ end
42
+
38
43
  desc "Run all debugger console related tests"
39
44
  Rake::TestTask.new(:test_console) do |t|
40
- t.test_files = FileList["test/console/*_test.rb", "test/support/*_test.rb"]
45
+ t.test_files = FileList["test/console/*_test.rb"]
41
46
  end
42
47
 
43
48
  desc "Run all debugger protocols (CAP & DAP) related tests"
@@ -46,7 +51,7 @@ Rake::TestTask.new(:test_protocol) do |t|
46
51
  end
47
52
 
48
53
  task test: 'test_console' do
49
- warn '`rake test` doesn\'t run protocol tests. Use `rake test-all` to test all.'
54
+ warn '`rake test` doesn\'t run protocol tests. Use `rake test_all` to test all.'
50
55
  end
51
56
 
52
- task test_all: [:test_console, :test_protocol]
57
+ task test_all: [:test_test, :test_console, :test_protocol]
@@ -101,10 +101,6 @@ module DEBUGGER__
101
101
  def generate_label(name)
102
102
  colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
103
103
  end
104
-
105
- def pending_until_load?
106
- false
107
- end
108
104
  end
109
105
 
110
106
  if RUBY_VERSION.to_f <= 2.7
@@ -163,10 +159,6 @@ module DEBUGGER__
163
159
  @pending = !@iseq
164
160
  end
165
161
 
166
- def pending_until_load?
167
- @pending
168
- end
169
-
170
162
  def setup
171
163
  return unless @type
172
164
 
@@ -207,6 +199,8 @@ module DEBUGGER__
207
199
  if @pending && !@oneshot
208
200
  DEBUGGER__.info "#{self} is activated."
209
201
  end
202
+
203
+ @pending = false
210
204
  end
211
205
 
212
206
  def activate_exact iseq, events, line
@@ -299,6 +293,10 @@ module DEBUGGER__
299
293
  def inspect
300
294
  "<#{self.class.name} #{self.to_s}>"
301
295
  end
296
+
297
+ def path_is? path
298
+ DEBUGGER__.compare_path(@path, path)
299
+ end
302
300
  end
303
301
 
304
302
  class CatchBreakpoint < Breakpoint
data/lib/debug/config.rb CHANGED
@@ -21,6 +21,7 @@ module DEBUGGER__
21
21
  no_sigint_hook: ['RUBY_DEBUG_NO_SIGINT_HOOK', "UI: Do not suspend on SIGINT", :bool, "false"],
22
22
  no_reline: ['RUBY_DEBUG_NO_RELINE', "UI: Do not use Reline library", :bool, "false"],
23
23
  no_hint: ['RUBY_DEBUG_NO_HINT', "UI: Do not show the hint on the REPL", :bool, "false"],
24
+ no_lineno: ['RUBY_DEBUG_NO_LINENO', "UI: Do not show line numbers", :bool, "false"],
24
25
 
25
26
  # control setting
26
27
  skip_path: ['RUBY_DEBUG_SKIP_PATH', "CONTROL: Skip showing/entering frames for given paths", :path],
@@ -265,6 +266,8 @@ module DEBUGGER__
265
266
  require 'optparse'
266
267
  require_relative 'version'
267
268
 
269
+ have_shown_version = false
270
+
268
271
  opt = OptionParser.new do |o|
269
272
  o.banner = "#{$0} [options] -- [debuggee options]"
270
273
  o.separator ''
@@ -372,6 +375,16 @@ module DEBUGGER__
372
375
  o.separator ''
373
376
  o.separator 'Other options:'
374
377
 
378
+ o.on('-v', 'Show version number') do
379
+ puts o.ver
380
+ have_shown_version = true
381
+ end
382
+
383
+ o.on('--version', 'Show version number and exit') do
384
+ puts o.ver
385
+ exit
386
+ end
387
+
375
388
  o.on("-h", "--help", "Print help") do
376
389
  puts o
377
390
  exit
@@ -395,6 +408,14 @@ module DEBUGGER__
395
408
 
396
409
  opt.parse!(argv)
397
410
 
411
+ if argv.empty?
412
+ case
413
+ when have_shown_version && config[:mode] == :start
414
+ pp config
415
+ exit
416
+ end
417
+ end
418
+
398
419
  config
399
420
  end
400
421
 
@@ -425,8 +446,9 @@ module DEBUGGER__
425
446
  unless (dir_uid = fs.uid) == (uid = Process.uid)
426
447
  raise "#{path} uid is #{dir_uid}, but Process.uid is #{uid}"
427
448
  end
428
- unless (dir_mode = fs.mode) == 040700 # 4: dir, 7:rwx
429
- raise "#{path}'s mode is #{dir_mode.to_s(8)} (should be 040700)"
449
+
450
+ if fs.world_writable? && !fs.sticky?
451
+ raise "#{path} is world writable but not sticky"
430
452
  end
431
453
 
432
454
  path
@@ -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|
data/lib/debug/server.rb CHANGED
@@ -406,14 +406,13 @@ module DEBUGGER__
406
406
  require_relative 'server_cdp'
407
407
 
408
408
  @uuid = SecureRandom.uuid
409
- unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
410
- DEBUGGER__.warn <<~EOS
411
- With Chrome browser, type the following URL in the address-bar:
409
+ @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
410
+ DEBUGGER__.warn <<~EOS
411
+ With Chrome browser, type the following URL in the address-bar:
412
412
 
413
- devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
413
+ devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
414
414
 
415
- EOS
416
- end
415
+ EOS
417
416
  end
418
417
 
419
418
  def accept