debug 1.0.0.beta2 → 1.0.0.beta7
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/.github/workflows/ruby.yml +34 -0
- data/.gitignore +3 -0
- data/CONTRIBUTING.md +336 -0
- data/Gemfile +2 -2
- data/README.md +190 -73
- data/TODO.md +27 -0
- data/bin/gentest +22 -0
- data/debug.gemspec +2 -0
- data/exe/rdbg +14 -11
- data/ext/debug/debug.c +10 -9
- data/lib/debug.rb +4 -1
- data/lib/debug/breakpoint.rb +110 -45
- data/lib/debug/client.rb +55 -13
- data/lib/debug/color.rb +76 -0
- data/lib/debug/config.rb +157 -33
- data/lib/debug/console.rb +26 -2
- data/lib/debug/frame_info.rb +145 -0
- data/lib/debug/open.rb +3 -0
- data/lib/debug/run.rb +4 -1
- data/lib/debug/server.rb +103 -50
- data/lib/debug/server_dap.rb +607 -0
- data/lib/debug/session.rb +534 -169
- data/lib/debug/source_repository.rb +64 -12
- data/lib/debug/thread_client.rb +211 -126
- data/lib/debug/version.rb +3 -1
- data/misc/README.md.erb +95 -47
- metadata +24 -3
data/lib/debug/client.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'socket'
|
4
|
+
require 'io/console/size'
|
5
|
+
|
2
6
|
require_relative 'config'
|
7
|
+
require_relative 'version'
|
8
|
+
|
9
|
+
# $VERBOSE = true
|
3
10
|
|
4
11
|
module DEBUGGER__
|
5
12
|
class CommandLineOptionError < Exception; end
|
@@ -8,16 +15,18 @@ module DEBUGGER__
|
|
8
15
|
begin
|
9
16
|
require 'readline'
|
10
17
|
def readline
|
11
|
-
Readline.readline("\n(
|
18
|
+
Readline.readline("\n(rdbg:remote) ", true)
|
12
19
|
end
|
13
20
|
rescue LoadError
|
14
21
|
def readline
|
15
|
-
print "\n(
|
22
|
+
print "\n(rdbg:remote) "
|
16
23
|
gets
|
17
24
|
end
|
18
25
|
end
|
19
26
|
|
20
27
|
def initialize argv
|
28
|
+
return util(argv) if String === argv
|
29
|
+
|
21
30
|
case argv.size
|
22
31
|
when 0
|
23
32
|
connect_unix
|
@@ -36,6 +45,24 @@ module DEBUGGER__
|
|
36
45
|
else
|
37
46
|
raise CommandLineOptionError
|
38
47
|
end
|
48
|
+
|
49
|
+
@width = IO.console_size[1]
|
50
|
+
@width = 80 if @width == 0
|
51
|
+
@width_changed = false
|
52
|
+
|
53
|
+
send "version: #{VERSION} width: #{@width} cookie: #{CONFIG[:cookie]}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def util name
|
57
|
+
case name
|
58
|
+
when 'gen-sockpath'
|
59
|
+
puts DEBUGGER__.create_unix_domain_socket_name
|
60
|
+
when 'list-socks'
|
61
|
+
cleanup_unix_domain_sockets
|
62
|
+
puts list_connections
|
63
|
+
else
|
64
|
+
raise "Unknown utility: #{name}"
|
65
|
+
end
|
39
66
|
end
|
40
67
|
|
41
68
|
def cleanup_unix_domain_sockets
|
@@ -50,16 +77,20 @@ module DEBUGGER__
|
|
50
77
|
end
|
51
78
|
end
|
52
79
|
|
80
|
+
def list_connections
|
81
|
+
Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
|
82
|
+
end
|
83
|
+
|
53
84
|
def connect_unix name = nil
|
54
85
|
if name
|
55
86
|
if File.exist? name
|
56
87
|
@s = Socket.unix(name)
|
57
88
|
else
|
58
|
-
@s = Socket.unix(File.join(DEBUGGER__.
|
89
|
+
@s = Socket.unix(File.join(DEBUGGER__.unix_domain_socket_dir, name))
|
59
90
|
end
|
60
91
|
else
|
61
92
|
cleanup_unix_domain_sockets
|
62
|
-
files =
|
93
|
+
files = list_connections
|
63
94
|
case files.size
|
64
95
|
when 0
|
65
96
|
$stderr.puts "No debug session is available."
|
@@ -80,13 +111,22 @@ module DEBUGGER__
|
|
80
111
|
@s = Socket.tcp(host, port)
|
81
112
|
end
|
82
113
|
|
114
|
+
def send msg
|
115
|
+
p send: msg if $VERBOSE
|
116
|
+
@s.puts msg
|
117
|
+
end
|
118
|
+
|
83
119
|
def connect
|
84
120
|
trap(:SIGINT){
|
85
|
-
|
121
|
+
send "pause"
|
122
|
+
}
|
123
|
+
trap(:SIGWINCH){
|
124
|
+
@width = IO.console_size[1]
|
125
|
+
@width_changed = true
|
86
126
|
}
|
87
127
|
|
88
128
|
while line = @s.gets
|
89
|
-
|
129
|
+
p recv: line if $VERBOSE
|
90
130
|
case line
|
91
131
|
when /^out (.*)/
|
92
132
|
puts "#{$1}"
|
@@ -102,10 +142,16 @@ module DEBUGGER__
|
|
102
142
|
end
|
103
143
|
|
104
144
|
line = (line || 'quit').strip
|
105
|
-
|
145
|
+
|
146
|
+
if @width_changed
|
147
|
+
@width_changed = false
|
148
|
+
send "width #{@width}"
|
149
|
+
end
|
150
|
+
|
151
|
+
send "command #{line}"
|
106
152
|
when /^ask (.*)/
|
107
153
|
print $1
|
108
|
-
|
154
|
+
send "answer #{gets || ''}"
|
109
155
|
when /^quit/
|
110
156
|
raise 'quit'
|
111
157
|
else
|
@@ -119,10 +165,6 @@ module DEBUGGER__
|
|
119
165
|
end
|
120
166
|
end
|
121
167
|
|
122
|
-
def connect argv = ARGV
|
123
|
-
DEBUGGER__::Client.new(argv).connect
|
124
|
-
end
|
125
|
-
|
126
168
|
if __FILE__ == $0
|
127
|
-
connect
|
169
|
+
DEBUGGER__::Client.new(argv).connect
|
128
170
|
end
|
data/lib/debug/color.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'irb/color'
|
5
|
+
require "irb/color_printer"
|
6
|
+
rescue LoadError
|
7
|
+
warn "DEBUGGER: can not load newer irb for coloring. Write 'gem \"debug\" in your Gemfile."
|
8
|
+
end
|
9
|
+
|
10
|
+
module DEBUGGER__
|
11
|
+
module Color
|
12
|
+
if defined? IRB::Color.colorize
|
13
|
+
def colorize str, color
|
14
|
+
if !CONFIG[:no_color]
|
15
|
+
IRB::Color.colorize str, color
|
16
|
+
else
|
17
|
+
str
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
def colorize str, color
|
22
|
+
str
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if defined? IRB::ColorPrinter.pp
|
27
|
+
def color_pp obj, width = SESSION.width
|
28
|
+
if !CONFIG[:no_color]
|
29
|
+
IRB::ColorPrinter.pp(obj, "".dup, width)
|
30
|
+
else
|
31
|
+
obj.pretty_inspect
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
def color_pp obj
|
36
|
+
obj.pretty_inspect
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def colored_inspect obj, no_color: false
|
41
|
+
if !no_color
|
42
|
+
color_pp obj
|
43
|
+
else
|
44
|
+
obj.pretty_inspect
|
45
|
+
end
|
46
|
+
rescue => ex
|
47
|
+
err_msg = "#{ex.inspect} rescued during inspection"
|
48
|
+
string_result = obj.to_s rescue nil
|
49
|
+
|
50
|
+
# don't colorize the string here because it's not from user's application
|
51
|
+
if string_result
|
52
|
+
%Q{"#{string_result}" from #to_s because #{err_msg}}
|
53
|
+
else
|
54
|
+
err_msg
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if defined? IRB::Color.colorize_code
|
59
|
+
def colorize_code code
|
60
|
+
IRB::Color.colorize_code(code)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
def colorize_code code
|
64
|
+
code
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def colorize_cyan(str)
|
69
|
+
colorize(str, [:CYAN, :BOLD])
|
70
|
+
end
|
71
|
+
|
72
|
+
def colorize_blue(str)
|
73
|
+
colorize(str, [:BLUE, :BOLD])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/debug/config.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module DEBUGGER__
|
3
4
|
def self.unix_domain_socket_dir
|
4
5
|
case
|
5
|
-
when path = DEBUGGER__::CONFIG[:sock_dir]
|
6
|
+
when path = ::DEBUGGER__::CONFIG[:sock_dir]
|
6
7
|
when path = ENV['XDG_RUNTIME_DIR']
|
7
8
|
when home = ENV['HOME']
|
8
9
|
path = File.join(home, '.ruby-debug-sock')
|
@@ -29,71 +30,151 @@ module DEBUGGER__
|
|
29
30
|
create_unix_domain_socket_name_prefix(base_dir) + "-#{Process.pid}"
|
30
31
|
end
|
31
32
|
|
32
|
-
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
33
|
+
CONFIG_SET = {
|
34
|
+
# UI setting
|
35
|
+
log_level: ['RUBY_DEBUG_LOG_LEVEL', "UI: Log level same as Logger (default: WARN)", :loglevel],
|
36
|
+
show_src_lines: ['RUBY_DEBUG_SHOW_SRC_LINES', "UI: Show n lines source code on breakpoint (default: 10 lines)", :int],
|
37
|
+
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
|
+
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
|
+
no_color: ['RUBY_DEBUG_NO_COLOR', "UI: Do not use colorize (default: false)", :bool],
|
43
|
+
no_sigint_hook: ['RUBY_DEBUG_NO_SIGINT_HOOK', "UI: Do not suspend on SIGINT (default: false)", :bool],
|
44
|
+
|
45
|
+
# boot setting
|
46
|
+
nonstop: ['RUBY_DEBUG_NONSTOP', "BOOT: Nonstop mode", :bool],
|
47
|
+
init_script: ['RUBY_DEBUG_INIT_SCRIPT', "BOOT: debug command script path loaded at first stop"],
|
48
|
+
commands: ['RUBY_DEBUG_COMMANDS', "BOOT: debug commands invoked at first stop. commands should be separated by ';;'"],
|
49
|
+
no_rc: ['RUBY_DEBUG_NO_RC', "BOOT: ignore loading ~/.rdbgrc(.rb)", :bool],
|
50
|
+
history: ['RUBY_DEBUG_HISTORY', "BOOT: save and load history file (default: ~/.rdbg_history)"],
|
51
|
+
|
52
|
+
# remote setting
|
53
|
+
port: ['RUBY_DEBUG_PORT', "REMOTE: TCP/IP remote debugging: port"],
|
54
|
+
host: ['RUBY_DEBUG_HOST', "REMOTE: TCP/IP remote debugging: host (localhost if not given)"],
|
55
|
+
sock_path: ['RUBY_DEBUG_SOCK_PATH', "REMOTE: UNIX Domain Socket remote debugging: socket path"],
|
56
|
+
sock_dir: ['RUBY_DEBUG_SOCK_DIR', "REMOTE: UNIX Domain Socket remote debugging: socket directory"],
|
57
|
+
cookie: ['RUBY_DEBUG_COOKIE', "REMOTE: Cookie for negotiation"],
|
44
58
|
}.freeze
|
45
59
|
|
46
|
-
|
47
|
-
|
48
|
-
|
60
|
+
CONFIG_MAP = CONFIG_SET.map{|k, (ev, desc)| [k, ev]}.to_h.freeze
|
61
|
+
|
62
|
+
def self.config_to_env_hash config
|
63
|
+
CONFIG_MAP.each_with_object({}){|(key, evname), env|
|
64
|
+
env[evname] = config[key].to_s if config[key]
|
49
65
|
}
|
50
66
|
end
|
51
67
|
|
68
|
+
def self.parse_config_value name, valstr
|
69
|
+
return valstr unless valstr.kind_of? String
|
70
|
+
|
71
|
+
case CONFIG_SET[name][2]
|
72
|
+
when :bool
|
73
|
+
case valstr
|
74
|
+
when '1', 'true', 'TRUE', 'T'
|
75
|
+
true
|
76
|
+
else
|
77
|
+
false
|
78
|
+
end
|
79
|
+
when :int
|
80
|
+
valstr.to_i
|
81
|
+
when :loglevel
|
82
|
+
if DEBUGGER__::LOG_LEVELS[s = valstr.to_sym]
|
83
|
+
s
|
84
|
+
else
|
85
|
+
raise "Unknown loglevel: #{valstr}"
|
86
|
+
end
|
87
|
+
when :path # array of String
|
88
|
+
valstr.split(/:/).map{|e|
|
89
|
+
if /\A\/(.+)\/\z/ =~ e
|
90
|
+
Regexp.compile $1
|
91
|
+
else
|
92
|
+
e
|
93
|
+
end
|
94
|
+
}
|
95
|
+
else
|
96
|
+
valstr
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
52
100
|
def self.parse_argv argv
|
53
101
|
config = {
|
54
102
|
mode: :start,
|
55
103
|
}
|
56
104
|
CONFIG_MAP.each{|key, evname|
|
57
|
-
|
105
|
+
if val = ENV[evname]
|
106
|
+
config[key] = parse_config_value(key, val)
|
107
|
+
end
|
58
108
|
}
|
59
109
|
return config if !argv || argv.empty?
|
60
110
|
|
61
111
|
require 'optparse'
|
112
|
+
require_relative 'version'
|
62
113
|
|
63
114
|
opt = OptionParser.new do |o|
|
64
115
|
o.banner = "#{$0} [options] -- [debuggee options]"
|
65
116
|
o.separator ''
|
117
|
+
o.version = ::DEBUGGER__::VERSION
|
66
118
|
|
67
119
|
o.separator 'Debug console mode:'
|
68
120
|
o.on('-n', '--nonstop', 'Do not stop at the beginning of the script.') do
|
69
121
|
config[:nonstop] = '1'
|
70
122
|
end
|
71
123
|
|
72
|
-
o.on('-e
|
124
|
+
o.on('-e DEBUG_COMMAND', 'Execute debug command at the beginning of the script.') do |cmd|
|
73
125
|
config[:commands] ||= ''
|
74
|
-
config[:commands]
|
126
|
+
config[:commands] += cmd + ';;'
|
75
127
|
end
|
76
128
|
|
77
|
-
o.on('-
|
129
|
+
o.on('-x FILE', '--init-script=FILE', 'Execute debug command in the FILE.') do |file|
|
130
|
+
config[:init_script] = file
|
131
|
+
end
|
132
|
+
o.on('--no-rc', 'Ignore ~/.rdbgrc') do
|
133
|
+
config[:no_rc] = true
|
134
|
+
end
|
135
|
+
o.on('--no-color', 'Disable colorize') do
|
136
|
+
config[:no_color] = true
|
137
|
+
end
|
138
|
+
|
139
|
+
o.on('-c', '--command', 'Enable command mode.',
|
140
|
+
'The first argument should be a command name in $PATH.',
|
141
|
+
'Example: \'rdbg -c bundle exec rake test\'') do
|
142
|
+
config[:command] = true
|
143
|
+
end
|
144
|
+
|
145
|
+
o.separator ''
|
146
|
+
|
147
|
+
o.on('-O', '--open', 'Start remote debugging with opening the network port.',
|
78
148
|
'If TCP/IP options are not given,',
|
79
149
|
'a UNIX domain socket will be used.') do
|
80
150
|
config[:remote] = true
|
81
151
|
end
|
82
|
-
o.on('--
|
152
|
+
o.on('--sock-path=SOCK_PATH', 'UNIX Doman socket path') do |path|
|
153
|
+
config[:sock_path] = path
|
154
|
+
end
|
155
|
+
o.on('--port=PORT', 'Listening TCP/IP port') do |port|
|
83
156
|
config[:port] = port
|
84
157
|
end
|
85
|
-
o.on('--host=
|
158
|
+
o.on('--host=HOST', 'Listening TCP/IP host') do |host|
|
86
159
|
config[:host] = host
|
87
160
|
end
|
161
|
+
o.on('--cookie=COOKIE', 'Set a cookie for connection') do |c|
|
162
|
+
config[:cookie] = c
|
163
|
+
end
|
164
|
+
|
165
|
+
rdbg = 'rdbg'
|
88
166
|
|
89
167
|
o.separator ''
|
90
168
|
o.separator ' Debug console mode runs Ruby program with the debug console.'
|
91
169
|
o.separator ''
|
92
|
-
o.separator " #{
|
93
|
-
o.separator " #{
|
94
|
-
o.separator " #{
|
95
|
-
o.separator " #{
|
96
|
-
o.separator " #{
|
170
|
+
o.separator " '#{rdbg} target.rb foo bar' starts like 'ruby target.rb foo bar'."
|
171
|
+
o.separator " '#{rdbg} -- -r foo -e bar' starts like 'ruby -r foo -e bar'."
|
172
|
+
o.separator " '#{rdbg} -c rake test' starts like 'rake test'."
|
173
|
+
o.separator " '#{rdbg} -c -- rake test -t' starts like 'rake test -t'."
|
174
|
+
o.separator " '#{rdbg} -c bundle exec rake test' starts like 'bundle exec rake test'."
|
175
|
+
o.separator " '#{rdbg} -O target.rb foo bar' starts and accepts attaching with UNIX domain socket."
|
176
|
+
o.separator " '#{rdbg} -O --port 1234 target.rb foo bar' starts accepts attaching with TCP/IP localhost:1234."
|
177
|
+
o.separator " '#{rdbg} -O --port 1234 -- -r foo -e bar' starts accepts attaching with TCP/IP localhost:1234."
|
97
178
|
|
98
179
|
o.separator ''
|
99
180
|
o.separator 'Attach mode:'
|
@@ -104,16 +185,59 @@ module DEBUGGER__
|
|
104
185
|
o.separator ''
|
105
186
|
o.separator ' Attach mode attaches the remote debug console to the debuggee process.'
|
106
187
|
o.separator ''
|
107
|
-
o.separator " '#{
|
108
|
-
o.separator " #{' ' *
|
109
|
-
o.separator " #{' ' *
|
110
|
-
o.separator " '#{
|
111
|
-
o.separator " '#{
|
112
|
-
o.separator " '#{
|
188
|
+
o.separator " '#{rdbg} -A' tries to connect via UNIX domain socket."
|
189
|
+
o.separator " #{' ' * rdbg.size} If there are multiple processes are waiting for the"
|
190
|
+
o.separator " #{' ' * rdbg.size} debugger connection, list possible debuggee names."
|
191
|
+
o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
|
192
|
+
o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
|
193
|
+
o.separator " '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."
|
194
|
+
|
195
|
+
o.separator ''
|
196
|
+
o.separator 'Other options:'
|
197
|
+
|
198
|
+
o.on("-h", "--help", "Print help") do
|
199
|
+
puts o
|
200
|
+
exit
|
201
|
+
end
|
202
|
+
|
203
|
+
o.on('--util=NAME', 'Utility mode (used by tools)') do |name|
|
204
|
+
require_relative 'client'
|
205
|
+
Client.new(name)
|
206
|
+
exit
|
207
|
+
end
|
208
|
+
|
209
|
+
o.separator ''
|
210
|
+
o.separator 'NOTE'
|
211
|
+
o.separator ' All messages communicated between a debugger and a debuggee are *NOT* encrypted.'
|
212
|
+
o.separator ' Please use the remote debugging feature carefully.'
|
113
213
|
end
|
114
214
|
|
115
215
|
opt.parse!(argv)
|
116
216
|
|
117
217
|
config
|
118
218
|
end
|
219
|
+
|
220
|
+
CONFIG = ::DEBUGGER__.parse_argv(ENV['RUBY_DEBUG_OPT'])
|
221
|
+
|
222
|
+
def self.set_config kw
|
223
|
+
kw.each{|k, v|
|
224
|
+
if CONFIG_MAP[k]
|
225
|
+
CONFIG[k] = parse_config_value(k, v) # TODO: ractor support
|
226
|
+
else
|
227
|
+
raise "Unknown configuration: #{k}"
|
228
|
+
end
|
229
|
+
}
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.append_config key, val
|
233
|
+
if CONFIG_SET[key]
|
234
|
+
if CONFIG_SET[key][2] == :path
|
235
|
+
CONFIG[key] = [*CONFIG[key], *parse_config_value(key, val)];
|
236
|
+
else
|
237
|
+
raise "not an Array type: #{key}"
|
238
|
+
end
|
239
|
+
else
|
240
|
+
raise "Unknown configuration: #{key}"
|
241
|
+
end
|
242
|
+
end
|
119
243
|
end
|