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/lib/debug/console.rb
CHANGED
@@ -1,63 +1,87 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
module DEBUGGER__
|
3
|
+
class Console
|
4
|
+
begin
|
5
|
+
raise LoadError if CONFIG[:no_reline]
|
6
|
+
require 'reline'
|
2
7
|
|
3
|
-
|
8
|
+
# reline 0.2.7 or later is required.
|
9
|
+
raise LoadError if Reline::VERSION < '0.2.6'
|
4
10
|
|
5
|
-
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
unless CONFIG[:no_sigint_hook]
|
9
|
-
@prev_handler = trap(:SIGINT){
|
10
|
-
ThreadClient.current.on_trap :SIGINT
|
11
|
-
}
|
12
|
-
end
|
13
|
-
end
|
11
|
+
require_relative 'color'
|
12
|
+
include Color
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
# 0.2.7 has SIGWINCH issue on non-main thread
|
15
|
+
class ::Reline::LineEditor
|
16
|
+
m = Module.new do
|
17
|
+
def reset(prompt = '', encoding:)
|
18
|
+
super
|
19
|
+
Signal.trap(:SIGWINCH, nil)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
prepend m
|
18
23
|
end
|
19
|
-
end
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
25
|
+
def readline_setup prompt
|
26
|
+
commands = DEBUGGER__.commands
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
else
|
29
|
-
w
|
30
|
-
end
|
31
|
-
end
|
28
|
+
Reline.completion_proc = -> given do
|
29
|
+
buff = Reline.line_buffer
|
30
|
+
Reline.completion_append_character= ' '
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
if /\s/ =~ buff # second parameters
|
33
|
+
given = File.expand_path(given + 'a').sub(/a\z/, '')
|
34
|
+
files = Dir.glob(given + '*')
|
35
|
+
if files.size == 1 && File.directory?(files.first)
|
36
|
+
Reline.completion_append_character= '/'
|
37
|
+
end
|
38
|
+
files
|
39
|
+
else
|
40
|
+
commands.keys.grep(/\A#{given}/)
|
41
|
+
end
|
42
|
+
end
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
Reline.output_modifier_proc = -> buff, **kw do
|
45
|
+
c, rest = get_command buff
|
46
|
+
|
47
|
+
case
|
48
|
+
when commands.keys.include?(c = c.strip)
|
49
|
+
# [:DIM, :CYAN, :BLUE, :CLEAR, :UNDERLINE, :REVERSE, :RED, :GREEN, :MAGENTA, :BOLD, :YELLOW]
|
50
|
+
cmd = colorize(c.strip, [:CYAN, :UNDERLINE])
|
51
|
+
|
52
|
+
if commands[c] == c
|
53
|
+
rprompt = colorize(" # command", [:DIM])
|
54
|
+
else
|
55
|
+
rprompt = colorize(" # #{commands[c]} command", [:DIM])
|
56
|
+
end
|
57
|
+
|
58
|
+
rest = (rest ? colorize_code(rest) : '') + rprompt
|
59
|
+
cmd + rest
|
60
|
+
when !rest && /\A\s*[a-z]*\z/ =~ c
|
61
|
+
buff
|
62
|
+
else
|
63
|
+
colorize_code(buff.chomp) + colorize(" # ruby", [:DIM])
|
64
|
+
end
|
65
|
+
end
|
41
66
|
end
|
42
|
-
end
|
43
67
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
str.each_line{|line|
|
52
|
-
$stdout.puts line.chomp
|
53
|
-
}
|
54
|
-
when nil
|
55
|
-
$stdout.puts
|
68
|
+
private def get_command line
|
69
|
+
case line.chomp
|
70
|
+
when /\A(\s*[a-z]+)(\s.*)?\z$/
|
71
|
+
return $1, $2
|
72
|
+
else
|
73
|
+
line.chomp
|
74
|
+
end
|
56
75
|
end
|
57
|
-
end
|
58
76
|
|
77
|
+
def readline prompt
|
78
|
+
readline_setup prompt
|
79
|
+
Reline.readmultiline(prompt, true){ true }
|
80
|
+
end
|
81
|
+
|
82
|
+
rescue LoadError
|
59
83
|
begin
|
60
|
-
require 'readline'
|
84
|
+
require 'readline.so'
|
61
85
|
|
62
86
|
def readline_setup
|
63
87
|
Readline.completion_proc = proc{|given|
|
@@ -72,38 +96,22 @@ module DEBUGGER__
|
|
72
96
|
end
|
73
97
|
files
|
74
98
|
else
|
75
|
-
DEBUGGER__.commands.grep(/\A#{given}/)
|
99
|
+
DEBUGGER__.commands.keys.grep(/\A#{given}/)
|
76
100
|
end
|
77
101
|
}
|
78
102
|
end
|
79
103
|
|
80
|
-
def
|
104
|
+
def readline prompt
|
81
105
|
readline_setup
|
82
|
-
Readline.readline(
|
106
|
+
Readline.readline(prompt, true)
|
83
107
|
end
|
108
|
+
|
84
109
|
rescue LoadError
|
85
|
-
def
|
86
|
-
print
|
110
|
+
def readline prompt
|
111
|
+
print prompt
|
87
112
|
gets
|
88
113
|
end
|
89
114
|
end
|
90
|
-
|
91
|
-
def readline
|
92
|
-
setup_interrupt do
|
93
|
-
(readline_body || 'quit').strip
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def setup_interrupt
|
98
|
-
current_thread = Thread.current # should be session_server thread
|
99
|
-
|
100
|
-
prev_handler = trap(:INT){
|
101
|
-
current_thread.raise Interrupt
|
102
|
-
}
|
103
|
-
|
104
|
-
yield
|
105
|
-
ensure
|
106
|
-
trap(:INT, prev_handler)
|
107
115
|
end
|
108
116
|
end
|
109
117
|
end
|
data/lib/debug/frame_info.rb
CHANGED
@@ -4,7 +4,9 @@ module DEBUGGER__
|
|
4
4
|
FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
|
5
5
|
:has_return_value, :return_value,
|
6
6
|
:has_raised_exception, :raised_exception,
|
7
|
-
:show_line
|
7
|
+
:show_line,
|
8
|
+
:_local_variables, :_callee # for recorder
|
9
|
+
)
|
8
10
|
|
9
11
|
# extend FrameInfo with debug.so
|
10
12
|
if File.exist? File.join(__dir__, 'debug.so')
|
@@ -25,10 +27,10 @@ module DEBUGGER__
|
|
25
27
|
end
|
26
28
|
|
27
29
|
def pretty_path
|
28
|
-
use_short_path =
|
30
|
+
use_short_path = CONFIG[:use_short_path]
|
29
31
|
|
30
32
|
case
|
31
|
-
when use_short_path && path.start_with?(dir =
|
33
|
+
when use_short_path && path.start_with?(dir = CONFIG["rubylibdir"] + '/')
|
32
34
|
path.sub(dir, '$(rubylibdir)/')
|
33
35
|
when use_short_path && Gem.path.any? do |gp|
|
34
36
|
path.start_with?(dir = gp + '/gems/')
|
@@ -62,7 +64,7 @@ module DEBUGGER__
|
|
62
64
|
end
|
63
65
|
|
64
66
|
def frame_type
|
65
|
-
if
|
67
|
+
if self.local_variables && iseq
|
66
68
|
if iseq.type == :block
|
67
69
|
:block
|
68
70
|
elsif callee
|
@@ -102,11 +104,11 @@ module DEBUGGER__
|
|
102
104
|
end
|
103
105
|
|
104
106
|
def callee
|
105
|
-
|
107
|
+
self._callee ||= self.binding&.eval('__callee__')
|
106
108
|
end
|
107
109
|
|
108
110
|
def return_str
|
109
|
-
if binding && iseq && has_return_value
|
111
|
+
if self.binding && iseq && has_return_value
|
110
112
|
DEBUGGER__.short_inspect(return_value)
|
111
113
|
end
|
112
114
|
end
|
@@ -115,6 +117,33 @@ module DEBUGGER__
|
|
115
117
|
"#{pretty_path}:#{location.lineno}"
|
116
118
|
end
|
117
119
|
|
120
|
+
private def make_binding
|
121
|
+
__newb__ = self.self.instance_eval('binding')
|
122
|
+
self.local_variables.each{|var, val|
|
123
|
+
__newb__.local_variable_set(var, val)
|
124
|
+
}
|
125
|
+
__newb__
|
126
|
+
end
|
127
|
+
|
128
|
+
def eval_binding
|
129
|
+
if b = self.binding
|
130
|
+
b
|
131
|
+
elsif self.local_variables
|
132
|
+
make_binding
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def local_variables
|
137
|
+
if lvars = self._local_variables
|
138
|
+
lvars
|
139
|
+
elsif b = self.binding
|
140
|
+
lvars = b.local_variables.map{|var|
|
141
|
+
[var, b.local_variable_get(var)]
|
142
|
+
}.to_h
|
143
|
+
self._local_variables = lvars
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
118
147
|
private
|
119
148
|
|
120
149
|
def get_singleton_class obj
|
@@ -123,11 +152,15 @@ module DEBUGGER__
|
|
123
152
|
nil
|
124
153
|
end
|
125
154
|
|
155
|
+
private def local_variable_get var
|
156
|
+
local_variables[var]
|
157
|
+
end
|
158
|
+
|
126
159
|
def parameters_info(argc)
|
127
160
|
vars = iseq.locals[0...argc]
|
128
161
|
vars.map{|var|
|
129
162
|
begin
|
130
|
-
{ name: var, value: DEBUGGER__.short_inspect(
|
163
|
+
{ name: var, value: DEBUGGER__.short_inspect(local_variable_get(var)) }
|
131
164
|
rescue NameError, TypeError
|
132
165
|
nil
|
133
166
|
end
|
data/lib/debug/local.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/console/size'
|
4
|
+
require_relative 'console'
|
5
|
+
|
6
|
+
module DEBUGGER__
|
7
|
+
class UI_LocalConsole < UI_Base
|
8
|
+
def initialize
|
9
|
+
@console = Console.new
|
10
|
+
|
11
|
+
unless CONFIG[:no_sigint_hook]
|
12
|
+
@prev_handler = trap(:SIGINT){
|
13
|
+
if SESSION.active?
|
14
|
+
ThreadClient.current.on_trap :SIGINT
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def close
|
21
|
+
if @prev_handler
|
22
|
+
trap(:SIGINT, @prev_handler)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def remote?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def activate on_fork: false
|
31
|
+
# Do nothing
|
32
|
+
end
|
33
|
+
|
34
|
+
def deactivate
|
35
|
+
# Do nothing
|
36
|
+
end
|
37
|
+
|
38
|
+
def width
|
39
|
+
if (w = IO.console_size[1]) == 0 # for tests PTY
|
40
|
+
80
|
41
|
+
else
|
42
|
+
w
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def quit n
|
47
|
+
exit n
|
48
|
+
end
|
49
|
+
|
50
|
+
def ask prompt
|
51
|
+
setup_interrupt do
|
52
|
+
print prompt
|
53
|
+
($stdin.gets || '').strip
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def puts str = nil
|
58
|
+
case str
|
59
|
+
when Array
|
60
|
+
str.each{|line|
|
61
|
+
$stdout.puts line.chomp
|
62
|
+
}
|
63
|
+
when String
|
64
|
+
str.each_line{|line|
|
65
|
+
$stdout.puts line.chomp
|
66
|
+
}
|
67
|
+
when nil
|
68
|
+
$stdout.puts
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def readline prompt = '(rdbg)'
|
73
|
+
setup_interrupt do
|
74
|
+
(@console.readline(prompt) || 'quit').strip
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def setup_interrupt
|
79
|
+
current_thread = Thread.current # should be session_server thread
|
80
|
+
|
81
|
+
prev_handler = trap(:INT){
|
82
|
+
current_thread.raise Interrupt
|
83
|
+
}
|
84
|
+
|
85
|
+
yield
|
86
|
+
ensure
|
87
|
+
trap(:INT, prev_handler)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
data/lib/debug/server.rb
CHANGED
@@ -7,7 +7,7 @@ require_relative 'version'
|
|
7
7
|
module DEBUGGER__
|
8
8
|
class UI_ServerBase < UI_Base
|
9
9
|
def initialize
|
10
|
-
@sock = nil
|
10
|
+
@sock = @sock_for_fork = nil
|
11
11
|
@accept_m = Mutex.new
|
12
12
|
@accept_cv = ConditionVariable.new
|
13
13
|
@client_addr = nil
|
@@ -16,11 +16,33 @@ module DEBUGGER__
|
|
16
16
|
@unsent_messages = []
|
17
17
|
@width = 80
|
18
18
|
|
19
|
+
activate
|
20
|
+
end
|
21
|
+
|
22
|
+
class Terminate < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
def deactivate
|
26
|
+
@reader_thread.raise Terminate
|
27
|
+
end
|
28
|
+
|
29
|
+
def accept
|
30
|
+
if @sock_for_fork
|
31
|
+
begin
|
32
|
+
yield @sock_for_fork, already_connected: true
|
33
|
+
ensure
|
34
|
+
@sock_for_fork.close
|
35
|
+
@sock_for_fork = nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def activate on_fork: false
|
19
41
|
@reader_thread = Thread.new do
|
20
42
|
# An error on this thread should break the system.
|
21
43
|
Thread.current.abort_on_exception = true
|
22
44
|
|
23
|
-
accept do |server|
|
45
|
+
accept do |server, already_connected: false|
|
24
46
|
DEBUGGER__.warn "Connected."
|
25
47
|
|
26
48
|
@accept_m.synchronize{
|
@@ -37,14 +59,17 @@ module DEBUGGER__
|
|
37
59
|
|
38
60
|
@q_msg = Queue.new
|
39
61
|
@q_ans = Queue.new
|
40
|
-
}
|
62
|
+
} unless already_connected
|
41
63
|
|
42
64
|
setup_interrupt do
|
65
|
+
pause unless already_connected
|
43
66
|
process
|
44
67
|
end
|
45
68
|
|
69
|
+
rescue Terminate
|
70
|
+
raise # should catch at outer scope
|
46
71
|
rescue => e
|
47
|
-
DEBUGGER__.warn "ReaderThreadError: #{e}"
|
72
|
+
DEBUGGER__.warn "ReaderThreadError: #{e}"
|
48
73
|
ensure
|
49
74
|
DEBUGGER__.warn "Disconnected."
|
50
75
|
@sock = nil
|
@@ -52,7 +77,10 @@ module DEBUGGER__
|
|
52
77
|
@q_msg = nil
|
53
78
|
@q_ans.close
|
54
79
|
@q_ans = nil
|
55
|
-
end
|
80
|
+
end # accept
|
81
|
+
|
82
|
+
rescue Terminate
|
83
|
+
# ignore
|
56
84
|
end
|
57
85
|
end
|
58
86
|
|
@@ -84,8 +112,6 @@ module DEBUGGER__
|
|
84
112
|
end
|
85
113
|
|
86
114
|
def process
|
87
|
-
pause
|
88
|
-
|
89
115
|
while line = @sock.gets
|
90
116
|
case line
|
91
117
|
when /\Apause/
|
@@ -129,10 +155,6 @@ module DEBUGGER__
|
|
129
155
|
trap(:SIGINT, prev_handler)
|
130
156
|
end
|
131
157
|
|
132
|
-
def accept
|
133
|
-
raise "NOT IMPLEMENTED ERROR"
|
134
|
-
end
|
135
|
-
|
136
158
|
attr_reader :reader_thread
|
137
159
|
|
138
160
|
class NoRemoteError < Exception; end
|
@@ -154,7 +176,7 @@ module DEBUGGER__
|
|
154
176
|
until s = @sock
|
155
177
|
@accept_m.synchronize{
|
156
178
|
unless @sock
|
157
|
-
DEBUGGER__.warn "wait for
|
179
|
+
DEBUGGER__.warn "wait for debugger connection..."
|
158
180
|
@accept_cv.wait(@accept_m)
|
159
181
|
end
|
160
182
|
}
|
@@ -195,12 +217,15 @@ module DEBUGGER__
|
|
195
217
|
end
|
196
218
|
end
|
197
219
|
|
198
|
-
def readline
|
199
|
-
(sock do |s|
|
220
|
+
def readline prompt
|
221
|
+
input = (sock do |s|
|
200
222
|
s.puts "input"
|
201
223
|
sleep 0.01 until @q_msg
|
224
|
+
|
202
225
|
@q_msg.pop
|
203
226
|
end || 'continue').strip
|
227
|
+
|
228
|
+
input
|
204
229
|
end
|
205
230
|
|
206
231
|
def pause
|
@@ -218,9 +243,9 @@ module DEBUGGER__
|
|
218
243
|
|
219
244
|
class UI_TcpServer < UI_ServerBase
|
220
245
|
def initialize host: nil, port: nil
|
221
|
-
@host = host ||
|
246
|
+
@host = host || CONFIG[:host] || '127.0.0.1'
|
222
247
|
@port = port || begin
|
223
|
-
port_str =
|
248
|
+
port_str = CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
|
224
249
|
if /\A\d+\z/ !~ port_str
|
225
250
|
raise "Specify digits for port number"
|
226
251
|
else
|
@@ -232,17 +257,34 @@ module DEBUGGER__
|
|
232
257
|
end
|
233
258
|
|
234
259
|
def accept
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
260
|
+
retry_cnt = 0
|
261
|
+
super # for fork
|
262
|
+
|
263
|
+
begin
|
264
|
+
Socket.tcp_server_sockets @host, @port do |socks|
|
265
|
+
::DEBUGGER__.warn "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
|
266
|
+
Socket.accept_loop(socks) do |sock, client|
|
267
|
+
@client_addr = client
|
268
|
+
yield @sock_for_fork = sock
|
269
|
+
end
|
270
|
+
end
|
271
|
+
rescue Errno::EADDRINUSE
|
272
|
+
if retry_cnt < 10
|
273
|
+
retry_cnt += 1
|
274
|
+
sleep 0.1
|
275
|
+
retry
|
276
|
+
else
|
277
|
+
raise
|
240
278
|
end
|
279
|
+
rescue Terminate
|
280
|
+
# OK
|
281
|
+
rescue => e
|
282
|
+
$stderr.puts e.inspect, e.message
|
283
|
+
pp e.backtrace
|
284
|
+
exit
|
241
285
|
end
|
242
|
-
|
243
|
-
|
244
|
-
pp e.backtrace
|
245
|
-
exit
|
286
|
+
ensure
|
287
|
+
@sock_for_fork = nil
|
246
288
|
end
|
247
289
|
end
|
248
290
|
|
@@ -250,14 +292,17 @@ module DEBUGGER__
|
|
250
292
|
def initialize sock_dir: nil, sock_path: nil
|
251
293
|
@sock_path = sock_path
|
252
294
|
@sock_dir = sock_dir || DEBUGGER__.unix_domain_socket_dir
|
295
|
+
@sock_for_fork = nil
|
253
296
|
|
254
297
|
super()
|
255
298
|
end
|
256
299
|
|
257
300
|
def accept
|
301
|
+
super # for fork
|
302
|
+
|
258
303
|
case
|
259
304
|
when @sock_path
|
260
|
-
when sp =
|
305
|
+
when sp = CONFIG[:sock_path]
|
261
306
|
@sock_path = sp
|
262
307
|
else
|
263
308
|
@sock_path = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
|
@@ -265,10 +310,13 @@ module DEBUGGER__
|
|
265
310
|
|
266
311
|
::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
|
267
312
|
Socket.unix_server_loop @sock_path do |sock, client|
|
313
|
+
@sock_for_fork = sock
|
268
314
|
@client_addr = client
|
315
|
+
|
269
316
|
yield sock
|
270
317
|
ensure
|
271
318
|
sock.close
|
319
|
+
@sock_for_fork = nil
|
272
320
|
end
|
273
321
|
end
|
274
322
|
end
|