ed-precompiled_debug 1.11.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.
@@ -0,0 +1,592 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DEBUGGER__
4
+ LOG_LEVELS = {
5
+ UNKNOWN: 0,
6
+ FATAL: 1,
7
+ ERROR: 2,
8
+ WARN: 3,
9
+ INFO: 4,
10
+ DEBUG: 5
11
+ }.freeze
12
+
13
+ CONFIG_SET = {
14
+ # UI setting
15
+ log_level: ['RUBY_DEBUG_LOG_LEVEL', "UI: Log level same as Logger", :loglevel, "WARN"],
16
+ show_src_lines: ['RUBY_DEBUG_SHOW_SRC_LINES', "UI: Show n lines source code on breakpoint", :int, "10"],
17
+ show_src_lines_frame:['RUBY_DEBUG_SHOW_SRC_LINES_FRAME', "UI: Show n lines source code on frame operations", :int, "1"],
18
+ show_evaledsrc: ['RUBY_DEBUG_SHOW_EVALEDSRC', "UI: Show actually evaluated source", :bool, "false"],
19
+ show_frames: ['RUBY_DEBUG_SHOW_FRAMES', "UI: Show n frames on breakpoint", :int, "2"],
20
+ use_short_path: ['RUBY_DEBUG_USE_SHORT_PATH', "UI: Show shorten PATH (like $(Gem)/foo.rb)", :bool, "false"],
21
+ no_color: ['RUBY_DEBUG_NO_COLOR', "UI: Do not use colorize", :bool, "false"],
22
+ no_sigint_hook: ['RUBY_DEBUG_NO_SIGINT_HOOK', "UI: Do not suspend on SIGINT", :bool, "false"],
23
+ no_reline: ['RUBY_DEBUG_NO_RELINE', "UI: Do not use Reline library", :bool, "false"],
24
+ no_hint: ['RUBY_DEBUG_NO_HINT', "UI: Do not show the hint on the REPL", :bool, "false"],
25
+ no_lineno: ['RUBY_DEBUG_NO_LINENO', "UI: Do not show line numbers", :bool, "false"],
26
+ no_repeat: ['RUBY_DEBUG_NO_REPEAT', "UI: Do not repeat last line when empty line",:bool, "false"],
27
+ irb_console: ["RUBY_DEBUG_IRB_CONSOLE", "UI: Use IRB as the console", :bool, "false"],
28
+
29
+ # control setting
30
+ skip_path: ['RUBY_DEBUG_SKIP_PATH', "CONTROL: Skip showing/entering frames for given paths", :path],
31
+ skip_nosrc: ['RUBY_DEBUG_SKIP_NOSRC', "CONTROL: Skip on no source code lines", :bool, "false"],
32
+ keep_alloc_site: ['RUBY_DEBUG_KEEP_ALLOC_SITE',"CONTROL: Keep allocation site and p, pp shows it", :bool, "false"],
33
+ postmortem: ['RUBY_DEBUG_POSTMORTEM', "CONTROL: Enable postmortem debug", :bool, "false"],
34
+ fork_mode: ['RUBY_DEBUG_FORK_MODE', "CONTROL: Control which process activates a debugger after fork (both/parent/child)", :forkmode, "both"],
35
+ sigdump_sig: ['RUBY_DEBUG_SIGDUMP_SIG', "CONTROL: Sigdump signal", :bool, "false"],
36
+
37
+ # boot setting
38
+ nonstop: ['RUBY_DEBUG_NONSTOP', "BOOT: Nonstop mode", :bool, "false"],
39
+ stop_at_load: ['RUBY_DEBUG_STOP_AT_LOAD',"BOOT: Stop at just loading location", :bool, "false"],
40
+ init_script: ['RUBY_DEBUG_INIT_SCRIPT', "BOOT: debug command script path loaded at first stop"],
41
+ commands: ['RUBY_DEBUG_COMMANDS', "BOOT: debug commands invoked at first stop. Commands should be separated by `;;`"],
42
+ no_rc: ['RUBY_DEBUG_NO_RC', "BOOT: ignore loading ~/.rdbgrc(.rb)", :bool, "false"],
43
+ history_file: ['RUBY_DEBUG_HISTORY_FILE',"BOOT: history file (default: ${XDG_STATE_HOME-~/.local/state}/rdbg/history)", :string, nil],
44
+ save_history: ['RUBY_DEBUG_SAVE_HISTORY',"BOOT: maximum save history lines", :int, "10000"],
45
+
46
+ # remote setting
47
+ open: ['RUBY_DEBUG_OPEN', "REMOTE: Open remote port (same as `rdbg --open` option)"],
48
+ port: ['RUBY_DEBUG_PORT', "REMOTE: TCP/IP remote debugging: port"],
49
+ port_range: ['RUBY_DEBUG_PORT_RANGE', "REMOTE: TCP/IP remote debugging: length of port range"],
50
+ host: ['RUBY_DEBUG_HOST', "REMOTE: TCP/IP remote debugging: host", :string, "127.0.0.1"],
51
+ sock_path: ['RUBY_DEBUG_SOCK_PATH', "REMOTE: UNIX Domain Socket remote debugging: socket path"],
52
+ sock_dir: ['RUBY_DEBUG_SOCK_DIR', "REMOTE: UNIX Domain Socket remote debugging: socket directory"],
53
+ local_fs_map: ['RUBY_DEBUG_LOCAL_FS_MAP', "REMOTE: Specify local fs map", :path_map],
54
+ skip_bp: ['RUBY_DEBUG_SKIP_BP', "REMOTE: Skip breakpoints if no clients are attached", :bool, 'false'],
55
+ cookie: ['RUBY_DEBUG_COOKIE', "REMOTE: Cookie for negotiation"],
56
+ session_name: ['RUBY_DEBUG_SESSION_NAME', "REMOTE: Session name for differentiating multiple sessions"],
57
+ chrome_path: ['RUBY_DEBUG_CHROME_PATH', "REMOTE: Platform dependent path of Chrome (For more information, See [here](https://github.com/ruby/debug/pull/334/files#diff-5fc3d0a901379a95bc111b86cf0090b03f857edfd0b99a0c1537e26735698453R55-R64))"],
58
+
59
+ # obsolete
60
+ parent_on_fork: ['RUBY_DEBUG_PARENT_ON_FORK', "OBSOLETE: Keep debugging parent process on fork", :bool, "false"],
61
+ }.freeze
62
+
63
+ CONFIG_MAP = CONFIG_SET.map{|k, (ev, _)| [k, ev]}.to_h.freeze
64
+
65
+ class Config
66
+ @config = nil
67
+
68
+ def self.config
69
+ @config
70
+ end
71
+
72
+ def initialize argv
73
+ if self.class.config
74
+ raise 'Can not make multiple configurations in one process'
75
+ end
76
+
77
+ config = self.class.parse_argv(argv)
78
+
79
+ # apply defaults
80
+ CONFIG_SET.each do |k, config_detail|
81
+ unless config.key?(k)
82
+ default_value = config_detail[3]
83
+ config[k] = parse_config_value(k, default_value)
84
+ end
85
+ end
86
+
87
+ update config
88
+ end
89
+
90
+ def inspect
91
+ config.inspect
92
+ end
93
+
94
+ def [](key)
95
+ config[key]
96
+ end
97
+
98
+ def []=(key, val)
99
+ set_config(key => val)
100
+ end
101
+
102
+ def set_config(**kw)
103
+ conf = config.dup
104
+ kw.each{|k, v|
105
+ if CONFIG_MAP[k]
106
+ conf[k] = parse_config_value(k, v) # TODO: ractor support
107
+ else
108
+ raise "Unknown configuration: #{k}"
109
+ end
110
+ }
111
+
112
+ update conf
113
+ end
114
+
115
+ def append_config key, val
116
+ conf = config.dup
117
+
118
+ if CONFIG_SET[key]
119
+ if CONFIG_SET[key][2] == :path
120
+ conf[key] = [*conf[key], *parse_config_value(key, val)];
121
+ else
122
+ raise "not an Array type: #{key}"
123
+ end
124
+ else
125
+ raise "Unknown configuration: #{key}"
126
+ end
127
+
128
+ update conf
129
+ end
130
+
131
+ def update conf
132
+ old_conf = self.class.instance_variable_get(:@config) || {}
133
+
134
+ # TODO: Use Ractor.make_shareable(conf)
135
+ self.class.instance_variable_set(:@config, conf.freeze)
136
+
137
+ # Post process
138
+ if_updated old_conf, conf, :keep_alloc_site do |old, new|
139
+ if new
140
+ require 'objspace'
141
+ ObjectSpace.trace_object_allocations_start
142
+ end
143
+
144
+ if old && !new
145
+ ObjectSpace.trace_object_allocations_stop
146
+ end
147
+ end
148
+
149
+ if_updated old_conf, conf, :postmortem do |_, new_p|
150
+ if defined?(SESSION)
151
+ SESSION.postmortem = new_p
152
+ end
153
+ end
154
+
155
+ if_updated old_conf, conf, :sigdump_sig do |old_sig, new_sig|
156
+ setup_sigdump old_sig, new_sig
157
+ end
158
+
159
+ if_updated old_conf, conf, :no_sigint_hook do |old, new|
160
+ if defined?(SESSION)
161
+ SESSION.set_no_sigint_hook old, new
162
+ end
163
+ end
164
+
165
+ if_updated old_conf, conf, :irb_console do |old, new|
166
+ if defined?(SESSION) && SESSION.active?
167
+ # irb_console is switched from true to false
168
+ if old
169
+ SESSION.deactivate_irb_integration
170
+ # irb_console is switched from false to true
171
+ else
172
+ if CONFIG[:open]
173
+ SESSION.instance_variable_get(:@ui).puts "\nIRB is not supported on the remote console."
174
+ else
175
+ SESSION.activate_irb_integration
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+ private def if_updated old_conf, new_conf, key
183
+ old, new = old_conf[key], new_conf[key]
184
+ yield old, new if old != new
185
+ end
186
+
187
+ private def enable_sigdump sig
188
+ @sigdump_sig_prev = trap(sig) do
189
+ str = []
190
+ str << "Simple sigdump on #{Process.pid}"
191
+ Thread.list.each{|th|
192
+ str << "Thread: #{th}"
193
+ th.backtrace.each{|loc|
194
+ str << " #{loc}"
195
+ }
196
+ str << ''
197
+ }
198
+
199
+ STDERR.puts str
200
+ end
201
+ end
202
+
203
+ private def disable_sigdump old_sig
204
+ trap(old_sig, @sigdump_sig_prev)
205
+ @sigdump_sig_prev = nil
206
+ end
207
+
208
+ # emergency simple sigdump.
209
+ # Use `sigdump` gem for more rich features.
210
+ private def setup_sigdump old_sig = nil, sig = CONFIG[:sigdump_sig]
211
+ if !old_sig && sig
212
+ enable_sigdump sig
213
+ elsif old_sig && !sig
214
+ disable_sigdump old_sig
215
+ elsif old_sig && sig
216
+ disable_sigdump old_sig
217
+ enable_sigdump sig
218
+ end
219
+ end
220
+
221
+ private def config
222
+ self.class.config
223
+ end
224
+
225
+ private def parse_config_value name, valstr
226
+ self.class.parse_config_value name, valstr
227
+ end
228
+
229
+ def self.parse_config_value name, valstr
230
+ return valstr unless valstr.kind_of? String
231
+
232
+ case CONFIG_SET[name][2]
233
+ when :bool
234
+ case valstr
235
+ when '1', 'true', 'TRUE', 'T'
236
+ true
237
+ else
238
+ false
239
+ end
240
+ when :int
241
+ valstr.to_i
242
+ when :loglevel
243
+ if DEBUGGER__::LOG_LEVELS[s = valstr.to_sym]
244
+ s
245
+ else
246
+ raise "Unknown loglevel: #{valstr}"
247
+ end
248
+ when :forkmode
249
+ case sym = valstr.to_sym
250
+ when :parent, :child, :both, nil
251
+ sym
252
+ else
253
+ raise "unknown fork mode: #{sym}"
254
+ end
255
+ when :path # array of String
256
+ valstr.split(/:/).map{|e|
257
+ if /\A\/(.+)\/\z/ =~ e
258
+ Regexp.compile $1
259
+ else
260
+ e
261
+ end
262
+ }
263
+ when :path_map
264
+ valstr.split(',').map{|e| e.split(':')}
265
+ else
266
+ valstr
267
+ end
268
+ end
269
+
270
+ def self.parse_argv argv
271
+ config = {
272
+ mode: :start,
273
+ no_color: (nc = ENV['NO_COLOR']) && !nc.empty?,
274
+ }
275
+ CONFIG_MAP.each{|key, evname|
276
+ if val = ENV[evname]
277
+ config[key] = parse_config_value(key, val)
278
+ end
279
+ }
280
+ return config if !argv || argv.empty?
281
+
282
+ if argv.kind_of? String
283
+ require 'shellwords'
284
+ argv = Shellwords.split(argv)
285
+ end
286
+
287
+ require 'optparse'
288
+ require_relative 'version'
289
+
290
+ have_shown_version = false
291
+
292
+ opt = OptionParser.new do |o|
293
+ o.banner = "#{$0} [options] -- [debuggee options]"
294
+ o.separator ''
295
+ o.version = ::DEBUGGER__::VERSION
296
+
297
+ o.separator 'Debug console mode:'
298
+ o.on('-n', '--nonstop', 'Do not stop at the beginning of the script.') do
299
+ config[:nonstop] = '1'
300
+ end
301
+
302
+ o.on('-e DEBUG_COMMAND', 'Execute debug command at the beginning of the script.') do |cmd|
303
+ config[:commands] ||= ''
304
+ config[:commands] += cmd + ';;'
305
+ end
306
+
307
+ o.on('-x FILE', '--init-script=FILE', 'Execute debug command in the FILE.') do |file|
308
+ config[:init_script] = file
309
+ end
310
+ o.on('--no-rc', 'Ignore ~/.rdbgrc') do
311
+ config[:no_rc] = true
312
+ end
313
+ o.on('--no-color', 'Disable colorize') do
314
+ config[:no_color] = true
315
+ end
316
+ o.on('--no-sigint-hook', 'Disable to trap SIGINT') do
317
+ config[:no_sigint_hook] = true
318
+ end
319
+
320
+ o.on('-c', '--command', 'Enable command mode.',
321
+ 'The first argument should be a command name in $PATH.',
322
+ 'Example: \'rdbg -c bundle exec rake test\'') do
323
+ config[:command] = true
324
+ end
325
+
326
+ o.separator ''
327
+
328
+ o.on('-O', '--open=[FRONTEND]', 'Start remote debugging with opening the network port.',
329
+ 'If TCP/IP options are not given, a UNIX domain socket will be used.',
330
+ 'If FRONTEND is given, prepare for the FRONTEND.',
331
+ 'Now rdbg, vscode and chrome is supported.') do |f|
332
+
333
+ case f # some format patterns are not documented yet
334
+ when nil
335
+ config[:open] = true
336
+ when /\A\d\z/
337
+ config[:open] = true
338
+ config[:port] = f.to_i
339
+ when /\A(\S+):(\d+)\z/
340
+ config[:open] = true
341
+ config[:host] = $1
342
+ config[:port] = $2.to_i
343
+ when 'tcp'
344
+ config[:open] = true
345
+ config[:port] ||= 0
346
+ when 'vscode', 'chrome', 'cdp'
347
+ config[:open] = f&.downcase
348
+ else
349
+ raise "Unknown option for --open: #{f}"
350
+ end
351
+ end
352
+ o.on('--sock-path=SOCK_PATH', 'UNIX Domain socket path') do |path|
353
+ config[:sock_path] = path
354
+ end
355
+ o.on('--port=PORT', 'Listening TCP/IP port') do |port|
356
+ config[:port] = port
357
+ end
358
+ o.on('--port-range=PORT_RANGE', 'Number of ports to try to connect to') do |port_range|
359
+ config[:port_range] = port_range
360
+ end
361
+ o.on('--host=HOST', 'Listening TCP/IP host') do |host|
362
+ config[:host] = host
363
+ end
364
+ o.on('--cookie=COOKIE', 'Set a cookie for connection') do |c|
365
+ config[:cookie] = c
366
+ end
367
+ o.on('--session-name=NAME', 'Session name') do |name|
368
+ config[:session_name] = name
369
+ end
370
+
371
+ rdbg = 'rdbg'
372
+
373
+ o.separator ''
374
+ o.separator ' Debug console mode runs Ruby program with the debug console.'
375
+ o.separator ''
376
+ o.separator " '#{rdbg} target.rb foo bar' starts like 'ruby target.rb foo bar'."
377
+ o.separator " '#{rdbg} -- -r foo -e bar' starts like 'ruby -r foo -e bar'."
378
+ o.separator " '#{rdbg} -c rake test' starts like 'rake test'."
379
+ o.separator " '#{rdbg} -c -- rake test -t' starts like 'rake test -t'."
380
+ o.separator " '#{rdbg} -c bundle exec rake test' starts like 'bundle exec rake test'."
381
+ o.separator " '#{rdbg} -O target.rb foo bar' starts and accepts attaching with UNIX domain socket."
382
+ o.separator " '#{rdbg} -O --port 1234 target.rb foo bar' starts accepts attaching with TCP/IP localhost:1234."
383
+ o.separator " '#{rdbg} -O --port 1234 -- -r foo -e bar' starts accepts attaching with TCP/IP localhost:1234."
384
+ o.separator " '#{rdbg} target.rb -O chrome --port 1234' starts and accepts connecting from Chrome Devtools with localhost:1234."
385
+
386
+ o.separator ''
387
+ o.separator 'Attach mode:'
388
+ o.on('-A', '--attach', 'Attach to debuggee process.') do
389
+ config[:mode] = :attach
390
+ end
391
+
392
+ o.separator ''
393
+ o.separator ' Attach mode attaches the remote debug console to the debuggee process.'
394
+ o.separator ''
395
+ o.separator " '#{rdbg} -A' tries to connect via UNIX domain socket."
396
+ o.separator " #{' ' * rdbg.size} If there are multiple processes are waiting for the"
397
+ o.separator " #{' ' * rdbg.size} debugger connection, list possible debuggee names."
398
+ o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
399
+ o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
400
+ o.separator " '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."
401
+
402
+ o.separator ''
403
+ o.separator 'Other options:'
404
+
405
+ o.on('-v', 'Show version number') do
406
+ puts o.ver
407
+ have_shown_version = true
408
+ end
409
+
410
+ o.on('--version', 'Show version number and exit') do
411
+ puts o.ver
412
+ exit
413
+ end
414
+
415
+ o.on("-h", "--help", "Print help") do
416
+ puts o
417
+ exit
418
+ end
419
+
420
+ o.on('--util=NAME', 'Utility mode (used by tools)') do |name|
421
+ require_relative 'client'
422
+ Client.util(name)
423
+ exit
424
+ end
425
+
426
+ o.on('--stop-at-load', 'Stop immediately when the debugging feature is loaded.') do
427
+ config[:stop_at_load] = true
428
+ end
429
+
430
+ o.separator ''
431
+ o.separator 'NOTE'
432
+ o.separator ' All messages communicated between a debugger and a debuggee are *NOT* encrypted.'
433
+ o.separator ' Please use the remote debugging feature carefully.'
434
+ end
435
+
436
+ opt.parse!(argv)
437
+
438
+ if argv.empty?
439
+ case
440
+ when have_shown_version && config[:mode] == :start
441
+ exit
442
+ end
443
+ end
444
+
445
+ config
446
+ end
447
+
448
+ def self.config_to_env_hash config
449
+ CONFIG_MAP.each_with_object({}){|(key, evname), env|
450
+ unless config[key].nil?
451
+ case CONFIG_SET[key][2]
452
+ when :path
453
+ valstr = config[key].map{|e| e.kind_of?(Regexp) ? e.inspect : e}.join(':')
454
+ when :path_map
455
+ valstr = config[key].map{|e| e.join(':')}.join(',')
456
+ else
457
+ valstr = config[key].to_s
458
+ end
459
+ env[evname] = valstr
460
+ end
461
+ }
462
+ end
463
+ end
464
+
465
+ CONFIG = Config.new ENV['RUBY_DEBUG_OPT']
466
+
467
+ ## Unix domain socket configuration
468
+
469
+ def self.check_dir_authority path
470
+ fs = File.stat(path)
471
+
472
+ unless (dir_uid = fs.uid) == (uid = Process.uid)
473
+ raise "#{path} uid is #{dir_uid}, but Process.uid is #{uid}"
474
+ end
475
+
476
+ if fs.world_writable? && !fs.sticky?
477
+ raise "#{path} is world writable but not sticky"
478
+ end
479
+
480
+ path
481
+ end
482
+
483
+ def self.unix_domain_socket_tmpdir
484
+ require 'tmpdir'
485
+
486
+ if tmpdir = Dir.tmpdir
487
+ path = File.join(tmpdir, "rdbg-#{Process.uid}")
488
+
489
+ unless File.exist?(path)
490
+ d = Dir.mktmpdir
491
+ File.rename(d, path)
492
+ end
493
+
494
+ check_dir_authority(path)
495
+ end
496
+ end
497
+
498
+ def self.unix_domain_socket_homedir
499
+ if home = ENV['HOME']
500
+ path = File.join(home, '.rdbg-sock')
501
+
502
+ unless File.exist?(path)
503
+ Dir.mkdir(path, 0700)
504
+ end
505
+
506
+ check_dir_authority(path)
507
+ end
508
+ end
509
+
510
+ def self.unix_domain_socket_dir
511
+ case
512
+ when path = CONFIG[:sock_dir]
513
+ when path = ENV['XDG_RUNTIME_DIR']
514
+ when path = unix_domain_socket_tmpdir
515
+ when path = unix_domain_socket_homedir
516
+ else
517
+ raise 'specify RUBY_DEBUG_SOCK_DIR environment variable.'
518
+ end
519
+
520
+ path
521
+ end
522
+
523
+ def self.create_unix_domain_socket_name_prefix(base_dir = unix_domain_socket_dir)
524
+ File.join(base_dir, "rdbg")
525
+ end
526
+
527
+ def self.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir)
528
+ suffix = "-#{Process.pid}"
529
+ name = CONFIG[:session_name]
530
+ suffix << "-#{name}" if name
531
+ create_unix_domain_socket_name_prefix(base_dir) + suffix
532
+ end
533
+
534
+ ## Help
535
+
536
+ def self.parse_help
537
+ helps = Hash.new{|h, k| h[k] = []}
538
+ desc = cat = nil
539
+ cmds = Hash.new
540
+
541
+ File.read(File.join(__dir__, 'session.rb'), encoding: Encoding::UTF_8).each_line do |line|
542
+ case line
543
+ when /\A\s*### (.+)/
544
+ cat = $1
545
+ break if $1 == 'END'
546
+ when /\A register_command (.+)/
547
+ next unless cat
548
+ next unless desc
549
+
550
+ ws = []
551
+ $1.gsub(/'([a-z]+)'/){|w|
552
+ ws << $1
553
+ }
554
+ helps[cat] << [ws, desc]
555
+ desc = nil
556
+ max_w = ws.max_by{|w| w.length}
557
+ ws.each{|w|
558
+ cmds[w] = max_w
559
+ }
560
+ when /\A\s+# (\s*\*.+)/
561
+ if desc
562
+ desc << "\n" + $1
563
+ else
564
+ desc = $1
565
+ end
566
+ end
567
+ end
568
+ @commands = cmds
569
+ @helps = helps
570
+ end
571
+
572
+ def self.helps
573
+ (defined?(@helps) && @helps) || parse_help
574
+ end
575
+
576
+ def self.commands
577
+ (defined?(@commands) && @commands) || (parse_help; @commands)
578
+ end
579
+
580
+ def self.help
581
+ r = []
582
+ self.helps.each{|cat, cmds|
583
+ r << "### #{cat}"
584
+ r << ''
585
+ cmds.each{|_, desc|
586
+ r << desc
587
+ }
588
+ r << ''
589
+ }
590
+ r.join("\n")
591
+ end
592
+ end