debug 1.7.1 → 1.8.0

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.
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