debug 1.0.0.beta8 → 1.0.0.rc1
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 +4 -4
- data/CONTRIBUTING.md +108 -106
- data/README.md +90 -30
- data/debug.gemspec +1 -0
- data/exe/rdbg +3 -6
- data/ext/debug/debug.c +11 -1
- data/lib/debug/breakpoint.rb +55 -22
- data/lib/debug/client.rb +7 -12
- data/lib/debug/color.rb +19 -4
- data/lib/debug/config.rb +354 -177
- data/lib/debug/console.rb +76 -68
- data/lib/debug/frame_info.rb +40 -7
- data/lib/debug/local.rb +91 -0
- data/lib/debug/server.rb +74 -26
- data/lib/debug/server_dap.rb +32 -7
- data/lib/debug/session.rb +568 -274
- data/lib/debug/thread_client.rb +620 -162
- data/lib/debug/tracer.rb +242 -0
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +26 -25
- metadata +18 -2
data/debug.gemspec
CHANGED
data/exe/rdbg
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require_relative '../lib/debug/config'
|
4
|
-
config = DEBUGGER__
|
4
|
+
config = DEBUGGER__::Config::parse_argv(ARGV)
|
5
5
|
|
6
6
|
case config[:mode]
|
7
7
|
when :start
|
@@ -11,17 +11,14 @@ when :start
|
|
11
11
|
start_mode = config[:remote] ? "open" : 'start'
|
12
12
|
cmd = config[:command] ? ARGV.shift : RbConfig.ruby
|
13
13
|
|
14
|
-
env = ::DEBUGGER__.config_to_env_hash(config)
|
14
|
+
env = ::DEBUGGER__::Config.config_to_env_hash(config)
|
15
15
|
env['RUBYOPT'] = "-r #{libpath}/#{start_mode}"
|
16
16
|
|
17
17
|
exec(env, cmd, *ARGV)
|
18
18
|
|
19
19
|
when :attach
|
20
20
|
require_relative "../lib/debug/client"
|
21
|
-
|
22
|
-
config.each{|k, v|
|
23
|
-
DEBUGGER__::CONFIG[k] = v
|
24
|
-
}
|
21
|
+
::DEBUGGER__::CONFIG.update config
|
25
22
|
|
26
23
|
begin
|
27
24
|
if ARGV.empty? && config[:port]
|
data/ext/debug/debug.c
CHANGED
@@ -22,7 +22,17 @@ static VALUE rb_cFrameInfo;
|
|
22
22
|
static VALUE
|
23
23
|
di_entry(VALUE loc, VALUE self, VALUE binding, VALUE iseq, VALUE klass, VALUE depth)
|
24
24
|
{
|
25
|
-
return rb_struct_new(rb_cFrameInfo,
|
25
|
+
return rb_struct_new(rb_cFrameInfo,
|
26
|
+
// :location, :self, :binding, :iseq, :class, :frame_depth,
|
27
|
+
loc, self, binding, iseq, klass, depth,
|
28
|
+
// :has_return_value, :return_value,
|
29
|
+
Qnil, Qnil,
|
30
|
+
// :has_raised_exception, :raised_exception,
|
31
|
+
Qnil, Qnil,
|
32
|
+
// :show_line, :local_variables
|
33
|
+
Qnil,
|
34
|
+
// :_local_variables, :_callee # for recorder
|
35
|
+
Qnil, Qnil);
|
26
36
|
}
|
27
37
|
|
28
38
|
static int
|
data/lib/debug/breakpoint.rb
CHANGED
@@ -82,6 +82,11 @@ module DEBUGGER__
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
+
if RUBY_VERSION.to_f <= 2.7
|
86
|
+
# workaround for https://bugs.ruby-lang.org/issues/17302
|
87
|
+
TracePoint.new(:line){}.enable{}
|
88
|
+
end
|
89
|
+
|
85
90
|
class LineBreakpoint < Breakpoint
|
86
91
|
attr_reader :path, :line, :iseq
|
87
92
|
|
@@ -113,7 +118,6 @@ module DEBUGGER__
|
|
113
118
|
next unless safe_eval tp.binding, @cond
|
114
119
|
end
|
115
120
|
delete if @oneshot
|
116
|
-
|
117
121
|
suspend
|
118
122
|
end
|
119
123
|
end
|
@@ -231,19 +235,25 @@ module DEBUGGER__
|
|
231
235
|
|
232
236
|
class CatchBreakpoint < Breakpoint
|
233
237
|
attr_reader :last_exc
|
238
|
+
include SkipPathHelper
|
234
239
|
|
235
|
-
def initialize pat
|
240
|
+
def initialize pat, cond: nil, command: nil
|
236
241
|
@pat = pat.freeze
|
237
242
|
@key = [:catch, @pat].freeze
|
238
243
|
@last_exc = nil
|
239
244
|
|
245
|
+
@cond = cond
|
246
|
+
@command = command
|
247
|
+
|
240
248
|
super()
|
241
249
|
end
|
242
250
|
|
243
251
|
def setup
|
244
252
|
@tp = TracePoint.new(:raise){|tp|
|
253
|
+
next if skip_path?(tp.path)
|
245
254
|
exc = tp.raised_exception
|
246
255
|
next if SystemExit === exc
|
256
|
+
next if !safe_eval(tp.binding, @cond) if @cond
|
247
257
|
should_suspend = false
|
248
258
|
|
249
259
|
exc.class.ancestors.each{|cls|
|
@@ -278,8 +288,6 @@ module DEBUGGER__
|
|
278
288
|
@tp = TracePoint.new(:line){|tp|
|
279
289
|
next if tp.path.start_with? __dir__
|
280
290
|
next if tp.path.start_with? '<internal:'
|
281
|
-
# Skip when `JSON.generate` is called during tests
|
282
|
-
next if tp.defined_class.to_s == '#<Class:JSON>' and ENV['RUBY_DEBUG_TEST_MODE']
|
283
291
|
|
284
292
|
if safe_eval tp.binding, @expr
|
285
293
|
suspend
|
@@ -340,15 +348,17 @@ module DEBUGGER__
|
|
340
348
|
class MethodBreakpoint < Breakpoint
|
341
349
|
attr_reader :sig_method_name, :method
|
342
350
|
|
343
|
-
def initialize b, klass_name, op, method_name, cond, command: nil
|
351
|
+
def initialize b, klass_name, op, method_name, cond: nil, command: nil
|
344
352
|
@sig_klass_name = klass_name
|
345
353
|
@sig_op = op
|
346
354
|
@sig_method_name = method_name
|
347
355
|
@klass_eval_binding = b
|
356
|
+
@override_method = false
|
348
357
|
|
349
358
|
@klass = nil
|
350
359
|
@method = nil
|
351
360
|
@cond = cond
|
361
|
+
@cond_class = nil
|
352
362
|
@command = command
|
353
363
|
@key = "#{klass_name}#{op}#{method_name}".freeze
|
354
364
|
|
@@ -356,16 +366,13 @@ module DEBUGGER__
|
|
356
366
|
end
|
357
367
|
|
358
368
|
def setup
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
suspend
|
367
|
-
}
|
368
|
-
end
|
369
|
+
@tp = TracePoint.new(:call){|tp|
|
370
|
+
next if !safe_eval(tp.binding, @cond) if @cond
|
371
|
+
next if @cond_class && !tp.self.kind_of?(@cond_class)
|
372
|
+
next if @override_method ? (caller_locations(2, 1).first.to_s.start_with?(__dir__)) : tp.path.start_with?(__dir__)
|
373
|
+
|
374
|
+
suspend
|
375
|
+
}
|
369
376
|
end
|
370
377
|
|
371
378
|
def eval_class_name
|
@@ -390,31 +397,57 @@ module DEBUGGER__
|
|
390
397
|
try_enable
|
391
398
|
end
|
392
399
|
|
400
|
+
if RUBY_VERSION.to_f <= 2.6
|
401
|
+
def override klass
|
402
|
+
sig_method_name = @sig_method_name
|
403
|
+
klass.prepend Module.new{
|
404
|
+
define_method(sig_method_name) do |*args, &block|
|
405
|
+
super(*args, &block)
|
406
|
+
end
|
407
|
+
}
|
408
|
+
end
|
409
|
+
else
|
410
|
+
def override klass
|
411
|
+
sig_method_name = @sig_method_name
|
412
|
+
klass.prepend Module.new{
|
413
|
+
define_method(sig_method_name) do |*args, **kw, &block|
|
414
|
+
super(*args, **kw, &block)
|
415
|
+
end
|
416
|
+
}
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
393
420
|
def try_enable added: false
|
394
421
|
eval_class_name
|
395
422
|
search_method
|
396
423
|
|
397
424
|
begin
|
398
425
|
retried = false
|
426
|
+
|
399
427
|
@tp.enable(target: @method)
|
400
428
|
DEBUGGER__.warn "#{self} is activated." if added
|
401
429
|
|
430
|
+
if @sig_op == '#'
|
431
|
+
@cond_class = @klass if @method.owner != @klass
|
432
|
+
else # '.'
|
433
|
+
@cond_class = @klass.singleton_class if @method.owner != @klass.singleton_class
|
434
|
+
end
|
435
|
+
|
402
436
|
rescue ArgumentError
|
403
437
|
raise if retried
|
404
438
|
retried = true
|
405
|
-
sig_method_name = @sig_method_name
|
406
439
|
|
407
440
|
# maybe C method
|
408
|
-
@
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
end
|
441
|
+
case @sig_op
|
442
|
+
when '.'
|
443
|
+
override @klass.singleton_class
|
444
|
+
when '#'
|
445
|
+
override @klass
|
414
446
|
end
|
415
447
|
|
416
448
|
# re-collect the method object after the above patch
|
417
449
|
search_method
|
450
|
+
@override_method = true if @method
|
418
451
|
retry
|
419
452
|
end
|
420
453
|
rescue Exception
|
data/lib/debug/client.rb
CHANGED
@@ -5,6 +5,7 @@ require 'io/console/size'
|
|
5
5
|
|
6
6
|
require_relative 'config'
|
7
7
|
require_relative 'version'
|
8
|
+
require_relative 'console'
|
8
9
|
|
9
10
|
# $VERBOSE = true
|
10
11
|
|
@@ -12,21 +13,11 @@ module DEBUGGER__
|
|
12
13
|
class CommandLineOptionError < Exception; end
|
13
14
|
|
14
15
|
class Client
|
15
|
-
begin
|
16
|
-
require 'readline'
|
17
|
-
def readline
|
18
|
-
Readline.readline("\n(rdbg:remote) ", true)
|
19
|
-
end
|
20
|
-
rescue LoadError
|
21
|
-
def readline
|
22
|
-
print "\n(rdbg:remote) "
|
23
|
-
gets
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
16
|
def initialize argv
|
28
17
|
return util(argv) if String === argv
|
29
18
|
|
19
|
+
@console = Console.new
|
20
|
+
|
30
21
|
case argv.size
|
31
22
|
when 0
|
32
23
|
connect_unix
|
@@ -53,6 +44,10 @@ module DEBUGGER__
|
|
53
44
|
send "version: #{VERSION} width: #{@width} cookie: #{CONFIG[:cookie]}"
|
54
45
|
end
|
55
46
|
|
47
|
+
def readline
|
48
|
+
@console.readline "(rdbg:remote) "
|
49
|
+
end
|
50
|
+
|
56
51
|
def util name
|
57
52
|
case name
|
58
53
|
when 'gen-sockpath'
|
data/lib/debug/color.rb
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
begin
|
4
4
|
require 'irb/color'
|
5
|
+
|
6
|
+
module IRB
|
7
|
+
module Color
|
8
|
+
DIM = 2 unless defined? DIM
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
5
12
|
require "irb/color_printer"
|
6
13
|
rescue LoadError
|
7
14
|
warn "DEBUGGER: can not load newer irb for coloring. Write 'gem \"debug\" in your Gemfile."
|
@@ -24,7 +31,7 @@ module DEBUGGER__
|
|
24
31
|
end
|
25
32
|
|
26
33
|
if defined? IRB::ColorPrinter.pp
|
27
|
-
def color_pp obj, width
|
34
|
+
def color_pp obj, width
|
28
35
|
if !CONFIG[:no_color]
|
29
36
|
IRB::ColorPrinter.pp(obj, "".dup, width)
|
30
37
|
else
|
@@ -32,14 +39,14 @@ module DEBUGGER__
|
|
32
39
|
end
|
33
40
|
end
|
34
41
|
else
|
35
|
-
def color_pp obj
|
42
|
+
def color_pp obj, width
|
36
43
|
obj.pretty_inspect
|
37
44
|
end
|
38
45
|
end
|
39
46
|
|
40
|
-
def colored_inspect obj, no_color: false
|
47
|
+
def colored_inspect obj, width: SESSION.width, no_color: false
|
41
48
|
if !no_color
|
42
|
-
color_pp obj
|
49
|
+
color_pp obj, width
|
43
50
|
else
|
44
51
|
obj.pretty_inspect
|
45
52
|
end
|
@@ -72,5 +79,13 @@ module DEBUGGER__
|
|
72
79
|
def colorize_blue(str)
|
73
80
|
colorize(str, [:BLUE, :BOLD])
|
74
81
|
end
|
82
|
+
|
83
|
+
def colorize_magenta(str)
|
84
|
+
colorize(str, [:MAGENTA, :BOLD])
|
85
|
+
end
|
86
|
+
|
87
|
+
def colorize_dim(str)
|
88
|
+
colorize(str, [:DIM])
|
89
|
+
end
|
75
90
|
end
|
76
91
|
end
|
data/lib/debug/config.rb
CHANGED
@@ -1,46 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DEBUGGER__
|
4
|
-
def self.unix_domain_socket_dir
|
5
|
-
case
|
6
|
-
when path = ::DEBUGGER__::CONFIG[:sock_dir]
|
7
|
-
when path = ENV['XDG_RUNTIME_DIR']
|
8
|
-
when home = ENV['HOME']
|
9
|
-
path = File.join(home, '.ruby-debug-sock')
|
10
|
-
|
11
|
-
case
|
12
|
-
when !File.exist?(path)
|
13
|
-
Dir.mkdir(path, 0700)
|
14
|
-
when !File.directory?(path)
|
15
|
-
raise "#{path} is not a directory."
|
16
|
-
end
|
17
|
-
else
|
18
|
-
raise 'specify RUBY_DEBUG_SOCK_DIR environment variable for UNIX domain socket directory.'
|
19
|
-
end
|
20
|
-
|
21
|
-
path
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.create_unix_domain_socket_name_prefix(base_dir = unix_domain_socket_dir)
|
25
|
-
user = ENV['USER'] || 'ruby-debug'
|
26
|
-
File.join(base_dir, "ruby-debug-#{user}")
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir)
|
30
|
-
create_unix_domain_socket_name_prefix(base_dir) + "-#{Process.pid}"
|
31
|
-
end
|
32
|
-
|
33
4
|
CONFIG_SET = {
|
34
5
|
# UI setting
|
35
6
|
log_level: ['RUBY_DEBUG_LOG_LEVEL', "UI: Log level same as Logger (default: WARN)", :loglevel],
|
36
7
|
show_src_lines: ['RUBY_DEBUG_SHOW_SRC_LINES', "UI: Show n lines source code on breakpoint (default: 10 lines)", :int],
|
37
8
|
show_frames: ['RUBY_DEBUG_SHOW_FRAMES', "UI: Show n frames on breakpoint (default: 2 frames)", :int],
|
38
|
-
show_info_lines:['RUBY_DEBUG_SHOW_INFO_LINES',"UI: Show n lines on info command (default: 10 lines, 0 for unlimited)", :int],
|
39
9
|
use_short_path: ['RUBY_DEBUG_USE_SHORT_PATH', "UI: Show shoten PATH (like $(Gem)/foo.rb)", :bool],
|
40
|
-
skip_nosrc: ['RUBY_DEBUG_SKIP_NOSRC', "UI: Skip on no source code lines (default: false)", :bool],
|
41
|
-
skip_path: ['RUBY_DEBUG_SKIP_PATH', "UI: Skip showing frames for given paths (default: [])", :path],
|
42
10
|
no_color: ['RUBY_DEBUG_NO_COLOR', "UI: Do not use colorize (default: false)", :bool],
|
43
11
|
no_sigint_hook: ['RUBY_DEBUG_NO_SIGINT_HOOK', "UI: Do not suspend on SIGINT (default: false)", :bool],
|
12
|
+
no_reline: ['RUBY_DEBUG_NO_RELINE', "UI: Do not use Reline library (default: false)", :bool],
|
13
|
+
|
14
|
+
# control setting
|
15
|
+
skip_path: ['RUBY_DEBUG_SKIP_PATH', "CONTROL: Skip showing/entering frames for given paths (default: [])", :path],
|
16
|
+
skip_nosrc: ['RUBY_DEBUG_SKIP_NOSRC', "CONTROL: Skip on no source code lines (default: false)", :bool],
|
17
|
+
keep_alloc_site:['RUBY_DEBUG_KEEP_ALLOC_SITE',"CONTROL: Keep allocation site and p, pp shows it (default: false)", :bool],
|
18
|
+
postmortem: ['RUBY_DEBUG_POSTMORTEM', "CONTROL: Enable postmortem debug (default: false)", :bool],
|
19
|
+
parent_on_fork: ['RUBY_DEBUG_PARENT_ON_FORK', "CONTROL: Keep debugging parent process on fork (default: false)", :bool],
|
20
|
+
sigdump_sig: ['RUBY_DEBUG_SIGDUMP_SIG', "CONTROL: Sigdump signal (default: disabled)"],
|
44
21
|
|
45
22
|
# boot setting
|
46
23
|
nonstop: ['RUBY_DEBUG_NONSTOP', "BOOT: Nonstop mode", :bool],
|
@@ -58,188 +35,388 @@ module DEBUGGER__
|
|
58
35
|
|
59
36
|
CONFIG_MAP = CONFIG_SET.map{|k, (ev, desc)| [k, ev]}.to_h.freeze
|
60
37
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.parse_config_value name, valstr
|
68
|
-
return valstr unless valstr.kind_of? String
|
38
|
+
class Config
|
39
|
+
def self.config
|
40
|
+
@config
|
41
|
+
end
|
69
42
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
when '1', 'true', 'TRUE', 'T'
|
74
|
-
true
|
75
|
-
else
|
76
|
-
false
|
43
|
+
def initialize argv
|
44
|
+
if self.class.instance_variable_defined? :@config
|
45
|
+
raise 'Can not make multiple configurations in one process'
|
77
46
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
47
|
+
|
48
|
+
update self.class.parse_argv(argv)
|
49
|
+
end
|
50
|
+
|
51
|
+
def [](key)
|
52
|
+
config[key]
|
53
|
+
end
|
54
|
+
|
55
|
+
def []=(key, val)
|
56
|
+
set_config(key => val)
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_config(**kw)
|
60
|
+
conf = config.dup
|
61
|
+
kw.each{|k, v|
|
62
|
+
if CONFIG_MAP[k]
|
63
|
+
conf[k] = parse_config_value(k, v) # TODO: ractor support
|
90
64
|
else
|
91
|
-
|
65
|
+
raise "Unknown configuration: #{k}"
|
92
66
|
end
|
93
67
|
}
|
94
|
-
|
95
|
-
|
68
|
+
|
69
|
+
update conf
|
96
70
|
end
|
97
|
-
end
|
98
71
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
72
|
+
def append_config key, val
|
73
|
+
conf = self.config.dup
|
74
|
+
|
75
|
+
if CONFIG_SET[key]
|
76
|
+
if CONFIG_SET[key][2] == :path
|
77
|
+
conf[key] = [*conf[key], *parse_config_value(key, val)];
|
78
|
+
else
|
79
|
+
raise "not an Array type: #{key}"
|
80
|
+
end
|
81
|
+
else
|
82
|
+
raise "Unknown configuration: #{key}"
|
106
83
|
end
|
107
|
-
}
|
108
|
-
return config if !argv || argv.empty?
|
109
84
|
|
110
|
-
|
111
|
-
|
85
|
+
update conf
|
86
|
+
end
|
112
87
|
|
113
|
-
|
114
|
-
|
115
|
-
o.separator ''
|
116
|
-
o.version = ::DEBUGGER__::VERSION
|
88
|
+
def update conf
|
89
|
+
old_conf = self.class.instance_variable_get(:@config) || {}
|
117
90
|
|
118
|
-
|
119
|
-
|
120
|
-
config[:nonstop] = '1'
|
121
|
-
end
|
91
|
+
# TODO: Use Ractor.make_shareable(conf)
|
92
|
+
self.class.instance_variable_set(:@config, conf.freeze)
|
122
93
|
|
123
|
-
|
124
|
-
|
125
|
-
|
94
|
+
# Post process
|
95
|
+
if_updated old_conf, conf, :keep_alloc_site do |_, new|
|
96
|
+
if new
|
97
|
+
require 'objspace'
|
98
|
+
ObjectSpace.trace_object_allocations_start
|
99
|
+
else
|
100
|
+
ObjectSpace.trace_object_allocations_stop
|
101
|
+
end
|
126
102
|
end
|
127
103
|
|
128
|
-
|
129
|
-
|
130
|
-
end
|
131
|
-
o.on('--no-rc', 'Ignore ~/.rdbgrc') do
|
132
|
-
config[:no_rc] = true
|
133
|
-
end
|
134
|
-
o.on('--no-color', 'Disable colorize') do
|
135
|
-
config[:no_color] = true
|
136
|
-
end
|
137
|
-
o.on('--no-sigint-hook', 'Disable to trap SIGINT') do
|
138
|
-
config[:no_sigint_hook] = true
|
104
|
+
if_updated old_conf, conf, :postmortem do |_, new_p|
|
105
|
+
SESSION.postmortem = new_p
|
139
106
|
end
|
140
107
|
|
141
|
-
|
142
|
-
|
143
|
-
'Example: \'rdbg -c bundle exec rake test\'') do
|
144
|
-
config[:command] = true
|
108
|
+
if_updated old_conf, conf, :sigdump_sig do |old_sig, new_sig|
|
109
|
+
setup_sigdump old_sig, new_sig
|
145
110
|
end
|
111
|
+
end
|
146
112
|
|
147
|
-
|
113
|
+
private def if_updated old_conf, new_conf, key
|
114
|
+
old, new = old_conf[key], new_conf[key]
|
115
|
+
yield old, new if old != new
|
116
|
+
end
|
148
117
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
118
|
+
private def enable_sigdump sig
|
119
|
+
@sigdump_sig_prev = trap(sig) do
|
120
|
+
str = []
|
121
|
+
str << "Simple sigdump on #{Process.pid}"
|
122
|
+
Thread.list.each{|th|
|
123
|
+
str << "Thread: #{th}"
|
124
|
+
th.backtrace.each{|loc|
|
125
|
+
str << " #{loc}"
|
126
|
+
}
|
127
|
+
str << ''
|
128
|
+
}
|
129
|
+
|
130
|
+
STDERR.puts str
|
162
131
|
end
|
163
|
-
|
164
|
-
|
132
|
+
end
|
133
|
+
|
134
|
+
private def disable_sigdump old_sig
|
135
|
+
trap(old_sig, @sigdump_sig_prev)
|
136
|
+
@sigdump_sig_prev = nil
|
137
|
+
end
|
138
|
+
|
139
|
+
# emergency simple sigdump.
|
140
|
+
# Use `sigdump` gem for more rich features.
|
141
|
+
private def setup_sigdump old_sig = nil, sig = CONFIG[:sigdump_sig]
|
142
|
+
if !old_sig && sig
|
143
|
+
enable_sigdump sig
|
144
|
+
elsif old_sig && !sig
|
145
|
+
disable_sigdump old_sig
|
146
|
+
elsif old_sig && sig
|
147
|
+
disable_sigdump old_sig
|
148
|
+
enable_sigdump sig
|
165
149
|
end
|
150
|
+
end
|
166
151
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
152
|
+
private def config
|
153
|
+
self.class.config
|
154
|
+
end
|
155
|
+
|
156
|
+
private def parse_config_value name, valstr
|
157
|
+
self.class.parse_config_value name, valstr
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.parse_config_value name, valstr
|
161
|
+
return valstr unless valstr.kind_of? String
|
162
|
+
|
163
|
+
case CONFIG_SET[name][2]
|
164
|
+
when :bool
|
165
|
+
case valstr
|
166
|
+
when '1', 'true', 'TRUE', 'T'
|
167
|
+
true
|
168
|
+
else
|
169
|
+
false
|
170
|
+
end
|
171
|
+
when :int
|
172
|
+
valstr.to_i
|
173
|
+
when :loglevel
|
174
|
+
if DEBUGGER__::LOG_LEVELS[s = valstr.to_sym]
|
175
|
+
s
|
176
|
+
else
|
177
|
+
raise "Unknown loglevel: #{valstr}"
|
178
|
+
end
|
179
|
+
when :path # array of String
|
180
|
+
valstr.split(/:/).map{|e|
|
181
|
+
if /\A\/(.+)\/\z/ =~ e
|
182
|
+
Regexp.compile $1
|
183
|
+
else
|
184
|
+
e
|
185
|
+
end
|
186
|
+
}
|
187
|
+
else
|
188
|
+
valstr
|
185
189
|
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.parse_argv argv
|
193
|
+
config = {
|
194
|
+
mode: :start,
|
195
|
+
}
|
196
|
+
CONFIG_MAP.each{|key, evname|
|
197
|
+
if val = ENV[evname]
|
198
|
+
config[key] = parse_config_value(key, val)
|
199
|
+
end
|
200
|
+
}
|
201
|
+
return config if !argv || argv.empty?
|
186
202
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
o.separator " '#{rdbg} -A' tries to connect via UNIX domain socket."
|
191
|
-
o.separator " #{' ' * rdbg.size} If there are multiple processes are waiting for the"
|
192
|
-
o.separator " #{' ' * rdbg.size} debugger connection, list possible debuggee names."
|
193
|
-
o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
|
194
|
-
o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
|
195
|
-
o.separator " '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."
|
196
|
-
|
197
|
-
o.separator ''
|
198
|
-
o.separator 'Other options:'
|
199
|
-
|
200
|
-
o.on("-h", "--help", "Print help") do
|
201
|
-
puts o
|
202
|
-
exit
|
203
|
+
if argv.kind_of? String
|
204
|
+
require 'shellwords'
|
205
|
+
argv = Shellwords.split(argv)
|
203
206
|
end
|
204
207
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
208
|
+
require 'optparse'
|
209
|
+
require_relative 'version'
|
210
|
+
|
211
|
+
opt = OptionParser.new do |o|
|
212
|
+
o.banner = "#{$0} [options] -- [debuggee options]"
|
213
|
+
o.separator ''
|
214
|
+
o.version = ::DEBUGGER__::VERSION
|
215
|
+
|
216
|
+
o.separator 'Debug console mode:'
|
217
|
+
o.on('-n', '--nonstop', 'Do not stop at the beginning of the script.') do
|
218
|
+
config[:nonstop] = '1'
|
219
|
+
end
|
220
|
+
|
221
|
+
o.on('-e DEBUG_COMMAND', 'Execute debug command at the beginning of the script.') do |cmd|
|
222
|
+
config[:commands] ||= ''
|
223
|
+
config[:commands] += cmd + ';;'
|
224
|
+
end
|
225
|
+
|
226
|
+
o.on('-x FILE', '--init-script=FILE', 'Execute debug command in the FILE.') do |file|
|
227
|
+
config[:init_script] = file
|
228
|
+
end
|
229
|
+
o.on('--no-rc', 'Ignore ~/.rdbgrc') do
|
230
|
+
config[:no_rc] = true
|
231
|
+
end
|
232
|
+
o.on('--no-color', 'Disable colorize') do
|
233
|
+
config[:no_color] = true
|
234
|
+
end
|
235
|
+
o.on('--no-sigint-hook', 'Disable to trap SIGINT') do
|
236
|
+
config[:no_sigint_hook] = true
|
237
|
+
end
|
238
|
+
|
239
|
+
o.on('-c', '--command', 'Enable command mode.',
|
240
|
+
'The first argument should be a command name in $PATH.',
|
241
|
+
'Example: \'rdbg -c bundle exec rake test\'') do
|
242
|
+
config[:command] = true
|
243
|
+
end
|
244
|
+
|
245
|
+
o.separator ''
|
246
|
+
|
247
|
+
o.on('-O', '--open', 'Start remote debugging with opening the network port.',
|
248
|
+
'If TCP/IP options are not given,',
|
249
|
+
'a UNIX domain socket will be used.') do
|
250
|
+
config[:remote] = true
|
251
|
+
end
|
252
|
+
o.on('--sock-path=SOCK_PATH', 'UNIX Domain socket path') do |path|
|
253
|
+
config[:sock_path] = path
|
254
|
+
end
|
255
|
+
o.on('--port=PORT', 'Listening TCP/IP port') do |port|
|
256
|
+
config[:port] = port
|
257
|
+
end
|
258
|
+
o.on('--host=HOST', 'Listening TCP/IP host') do |host|
|
259
|
+
config[:host] = host
|
260
|
+
end
|
261
|
+
o.on('--cookie=COOKIE', 'Set a cookie for connection') do |c|
|
262
|
+
config[:cookie] = c
|
263
|
+
end
|
264
|
+
|
265
|
+
rdbg = 'rdbg'
|
266
|
+
|
267
|
+
o.separator ''
|
268
|
+
o.separator ' Debug console mode runs Ruby program with the debug console.'
|
269
|
+
o.separator ''
|
270
|
+
o.separator " '#{rdbg} target.rb foo bar' starts like 'ruby target.rb foo bar'."
|
271
|
+
o.separator " '#{rdbg} -- -r foo -e bar' starts like 'ruby -r foo -e bar'."
|
272
|
+
o.separator " '#{rdbg} -c rake test' starts like 'rake test'."
|
273
|
+
o.separator " '#{rdbg} -c -- rake test -t' starts like 'rake test -t'."
|
274
|
+
o.separator " '#{rdbg} -c bundle exec rake test' starts like 'bundle exec rake test'."
|
275
|
+
o.separator " '#{rdbg} -O target.rb foo bar' starts and accepts attaching with UNIX domain socket."
|
276
|
+
o.separator " '#{rdbg} -O --port 1234 target.rb foo bar' starts accepts attaching with TCP/IP localhost:1234."
|
277
|
+
o.separator " '#{rdbg} -O --port 1234 -- -r foo -e bar' starts accepts attaching with TCP/IP localhost:1234."
|
278
|
+
|
279
|
+
o.separator ''
|
280
|
+
o.separator 'Attach mode:'
|
281
|
+
o.on('-A', '--attach', 'Attach to debuggee process.') do
|
282
|
+
config[:mode] = :attach
|
283
|
+
end
|
284
|
+
|
285
|
+
o.separator ''
|
286
|
+
o.separator ' Attach mode attaches the remote debug console to the debuggee process.'
|
287
|
+
o.separator ''
|
288
|
+
o.separator " '#{rdbg} -A' tries to connect via UNIX domain socket."
|
289
|
+
o.separator " #{' ' * rdbg.size} If there are multiple processes are waiting for the"
|
290
|
+
o.separator " #{' ' * rdbg.size} debugger connection, list possible debuggee names."
|
291
|
+
o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
|
292
|
+
o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
|
293
|
+
o.separator " '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."
|
294
|
+
|
295
|
+
o.separator ''
|
296
|
+
o.separator 'Other options:'
|
297
|
+
|
298
|
+
o.on("-h", "--help", "Print help") do
|
299
|
+
puts o
|
300
|
+
exit
|
301
|
+
end
|
302
|
+
|
303
|
+
o.on('--util=NAME', 'Utility mode (used by tools)') do |name|
|
304
|
+
require_relative 'client'
|
305
|
+
Client.new(name)
|
306
|
+
exit
|
307
|
+
end
|
308
|
+
|
309
|
+
o.separator ''
|
310
|
+
o.separator 'NOTE'
|
311
|
+
o.separator ' All messages communicated between a debugger and a debuggee are *NOT* encrypted.'
|
312
|
+
o.separator ' Please use the remote debugging feature carefully.'
|
209
313
|
end
|
210
314
|
|
211
|
-
|
212
|
-
o.separator 'NOTE'
|
213
|
-
o.separator ' All messages communicated between a debugger and a debuggee are *NOT* encrypted.'
|
214
|
-
o.separator ' Please use the remote debugging feature carefully.'
|
215
|
-
end
|
315
|
+
opt.parse!(argv)
|
216
316
|
|
217
|
-
|
317
|
+
config
|
318
|
+
end
|
218
319
|
|
219
|
-
config
|
320
|
+
def self.config_to_env_hash config
|
321
|
+
CONFIG_MAP.each_with_object({}){|(key, evname), env|
|
322
|
+
unless config[key].nil?
|
323
|
+
case CONFIG_SET[key][2]
|
324
|
+
when :path
|
325
|
+
valstr = config[key].map{|e| e.kind_of?(Regexp) ? e.inspect : e}.join(':')
|
326
|
+
else
|
327
|
+
valstr = config[key].to_s
|
328
|
+
end
|
329
|
+
env[evname] = valstr
|
330
|
+
end
|
331
|
+
}
|
332
|
+
end
|
220
333
|
end
|
221
334
|
|
222
|
-
CONFIG =
|
335
|
+
CONFIG = Config.new ENV['RUBY_DEBUG_OPT']
|
223
336
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
337
|
+
## Unix domain socket configuration
|
338
|
+
|
339
|
+
def self.unix_domain_socket_dir
|
340
|
+
case
|
341
|
+
when path = CONFIG[:sock_dir]
|
342
|
+
when path = ENV['XDG_RUNTIME_DIR']
|
343
|
+
when home = ENV['HOME']
|
344
|
+
path = File.join(home, '.ruby-debug-sock')
|
345
|
+
|
346
|
+
case
|
347
|
+
when !File.exist?(path)
|
348
|
+
Dir.mkdir(path, 0700)
|
349
|
+
when !File.directory?(path)
|
350
|
+
raise "#{path} is not a directory."
|
230
351
|
end
|
231
|
-
|
352
|
+
else
|
353
|
+
raise 'specify RUBY_DEBUG_SOCK_DIR environment variable for UNIX domain socket directory.'
|
354
|
+
end
|
355
|
+
|
356
|
+
path
|
232
357
|
end
|
233
358
|
|
234
|
-
def self.
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
359
|
+
def self.create_unix_domain_socket_name_prefix(base_dir = unix_domain_socket_dir)
|
360
|
+
user = ENV['USER'] || 'ruby-debug'
|
361
|
+
File.join(base_dir, "ruby-debug-#{user}")
|
362
|
+
end
|
363
|
+
|
364
|
+
def self.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir)
|
365
|
+
create_unix_domain_socket_name_prefix(base_dir) + "-#{Process.pid}"
|
366
|
+
end
|
367
|
+
|
368
|
+
## Help
|
369
|
+
|
370
|
+
def self.parse_help
|
371
|
+
helps = Hash.new{|h, k| h[k] = []}
|
372
|
+
desc = cat = nil
|
373
|
+
cmds = Hash.new
|
374
|
+
|
375
|
+
File.read(File.join(__dir__, 'session.rb')).each_line do |line|
|
376
|
+
case line
|
377
|
+
when /\A\s*### (.+)/
|
378
|
+
cat = $1
|
379
|
+
break if $1 == 'END'
|
380
|
+
when /\A when (.+)/
|
381
|
+
next unless cat
|
382
|
+
next unless desc
|
383
|
+
ws = $1.split(/,\s*/).map{|e| e.gsub('\'', '')}
|
384
|
+
helps[cat] << [ws, desc]
|
385
|
+
desc = nil
|
386
|
+
max_w = ws.max_by{|w| w.length}
|
387
|
+
ws.each{|w|
|
388
|
+
cmds[w] = max_w
|
389
|
+
}
|
390
|
+
when /\A\s+# (\s*\*.+)/
|
391
|
+
if desc
|
392
|
+
desc << "\n" + $1
|
393
|
+
else
|
394
|
+
desc = $1
|
395
|
+
end
|
240
396
|
end
|
241
|
-
else
|
242
|
-
raise "Unknown configuration: #{key}"
|
243
397
|
end
|
398
|
+
@commands = cmds
|
399
|
+
@helps = helps
|
400
|
+
end
|
401
|
+
|
402
|
+
def self.helps
|
403
|
+
(defined?(@helps) && @helps) || parse_help
|
404
|
+
end
|
405
|
+
|
406
|
+
def self.commands
|
407
|
+
(defined?(@commands) && @commands) || (parse_help; @commands)
|
408
|
+
end
|
409
|
+
|
410
|
+
def self.help
|
411
|
+
r = []
|
412
|
+
self.helps.each{|cat, cmds|
|
413
|
+
r << "### #{cat}"
|
414
|
+
r << ''
|
415
|
+
cmds.each{|ws, desc|
|
416
|
+
r << desc
|
417
|
+
}
|
418
|
+
r << ''
|
419
|
+
}
|
420
|
+
r.join("\n")
|
244
421
|
end
|
245
422
|
end
|