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/console.rb
CHANGED
@@ -1,14 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'session'
|
4
|
+
return unless defined?(DEBUGGER__)
|
5
|
+
|
6
|
+
require 'io/console/size'
|
2
7
|
|
3
8
|
module DEBUGGER__
|
4
|
-
class UI_Console
|
9
|
+
class UI_Console < UI_Base
|
5
10
|
def initialize
|
11
|
+
unless CONFIG[:no_sigint_hook]
|
12
|
+
@prev_handler = trap(:SIGINT){
|
13
|
+
ThreadClient.current.on_trap :SIGINT
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def close
|
19
|
+
if @prev_handler
|
20
|
+
trap(:SIGINT, @prev_handler)
|
21
|
+
end
|
6
22
|
end
|
7
23
|
|
8
24
|
def remote?
|
9
25
|
false
|
10
26
|
end
|
11
27
|
|
28
|
+
def width
|
29
|
+
if (w = IO.console_size[1]) == 0 # for tests PTY
|
30
|
+
80
|
31
|
+
else
|
32
|
+
w
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
12
36
|
def quit n
|
13
37
|
exit n
|
14
38
|
end
|
@@ -16,7 +40,7 @@ module DEBUGGER__
|
|
16
40
|
def ask prompt
|
17
41
|
setup_interrupt do
|
18
42
|
print prompt
|
19
|
-
(gets || '').strip
|
43
|
+
($stdin.gets || '').strip
|
20
44
|
end
|
21
45
|
end
|
22
46
|
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DEBUGGER__
|
4
|
+
FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
|
5
|
+
:has_return_value, :return_value,
|
6
|
+
:has_raised_exception, :raised_exception,
|
7
|
+
:show_line)
|
8
|
+
|
9
|
+
# extend FrameInfo with debug.so
|
10
|
+
if File.exist? File.join(__dir__, 'debug.so')
|
11
|
+
require_relative 'debug.so'
|
12
|
+
else
|
13
|
+
require_relative 'debug'
|
14
|
+
end
|
15
|
+
|
16
|
+
class FrameInfo
|
17
|
+
HOME = ENV['HOME'] ? (ENV['HOME'] + '/') : nil
|
18
|
+
|
19
|
+
def path
|
20
|
+
location.path
|
21
|
+
end
|
22
|
+
|
23
|
+
def realpath
|
24
|
+
location.absolute_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def pretty_path
|
28
|
+
use_short_path = ::DEBUGGER__::CONFIG[:use_short_path]
|
29
|
+
|
30
|
+
case
|
31
|
+
when use_short_path && path.start_with?(dir = ::DEBUGGER__::CONFIG["rubylibdir"] + '/')
|
32
|
+
path.sub(dir, '$(rubylibdir)/')
|
33
|
+
when use_short_path && Gem.path.any? do |gp|
|
34
|
+
path.start_with?(dir = gp + '/gems/')
|
35
|
+
end
|
36
|
+
path.sub(dir, '$(Gem)/')
|
37
|
+
when HOME && path.start_with?(HOME)
|
38
|
+
path.sub(HOME, '~/')
|
39
|
+
else
|
40
|
+
path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def name
|
45
|
+
# p frame_type: frame_type, self: self
|
46
|
+
case frame_type
|
47
|
+
when :block
|
48
|
+
level, block_loc, _args = block_identifier
|
49
|
+
"block in #{block_loc}#{level}"
|
50
|
+
when :method
|
51
|
+
ci, _args = method_identifier
|
52
|
+
"#{ci}"
|
53
|
+
when :c
|
54
|
+
c_identifier
|
55
|
+
when :other
|
56
|
+
other_identifier
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def file_lines
|
61
|
+
SESSION.source(self.iseq)
|
62
|
+
end
|
63
|
+
|
64
|
+
def frame_type
|
65
|
+
if binding && iseq
|
66
|
+
if iseq.type == :block
|
67
|
+
:block
|
68
|
+
elsif callee
|
69
|
+
:method
|
70
|
+
else
|
71
|
+
:other
|
72
|
+
end
|
73
|
+
else
|
74
|
+
:c
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
BLOCK_LABL_REGEXP = /\Ablock( \(\d+ levels\))* in (.+)\z/
|
79
|
+
|
80
|
+
def block_identifier
|
81
|
+
return unless frame_type == :block
|
82
|
+
args = parameters_info(iseq.argc)
|
83
|
+
_, level, block_loc = location.label.match(BLOCK_LABL_REGEXP).to_a
|
84
|
+
[level || "", block_loc, args]
|
85
|
+
end
|
86
|
+
|
87
|
+
def method_identifier
|
88
|
+
return unless frame_type == :method
|
89
|
+
args = parameters_info(iseq.argc)
|
90
|
+
ci = "#{klass_sig}#{callee}"
|
91
|
+
[ci, args]
|
92
|
+
end
|
93
|
+
|
94
|
+
def c_identifier
|
95
|
+
return unless frame_type == :c
|
96
|
+
"[C] #{klass_sig}#{location.base_label}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def other_identifier
|
100
|
+
return unless frame_type == :other
|
101
|
+
location.label
|
102
|
+
end
|
103
|
+
|
104
|
+
def callee
|
105
|
+
@callee ||= binding&.eval('__callee__', __FILE__, __LINE__)
|
106
|
+
end
|
107
|
+
|
108
|
+
def return_str
|
109
|
+
if binding && iseq && has_return_value
|
110
|
+
DEBUGGER__.short_inspect(return_value)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def location_str
|
115
|
+
"#{pretty_path}:#{location.lineno}"
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def get_singleton_class obj
|
121
|
+
obj.singleton_class # TODO: don't use it
|
122
|
+
rescue TypeError
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
def parameters_info(argc)
|
127
|
+
vars = iseq.locals[0...argc]
|
128
|
+
vars.map{|var|
|
129
|
+
begin
|
130
|
+
{ name: var, value: DEBUGGER__.short_inspect(binding.local_variable_get(var)) }
|
131
|
+
rescue NameError, TypeError
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
}.compact
|
135
|
+
end
|
136
|
+
|
137
|
+
def klass_sig
|
138
|
+
if self.class == get_singleton_class(self.self)
|
139
|
+
"#{self.self}."
|
140
|
+
else
|
141
|
+
"#{self.class}#"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/lib/debug/open.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
#
|
2
3
|
# Open the door for the debugger to connect.
|
3
4
|
# Users can connect to debuggee program with "rdbg --attach" option.
|
@@ -7,4 +8,6 @@
|
|
7
8
|
#
|
8
9
|
|
9
10
|
require_relative 'server'
|
11
|
+
return unless defined?(DEBUGGER__)
|
12
|
+
|
10
13
|
DEBUGGER__.open
|
data/lib/debug/run.rb
CHANGED
data/lib/debug/server.rb
CHANGED
@@ -1,24 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'socket'
|
4
|
+
|
2
5
|
require_relative 'session'
|
6
|
+
return unless defined?(DEBUGGER__)
|
7
|
+
|
3
8
|
require_relative 'config'
|
9
|
+
require_relative 'version'
|
4
10
|
|
5
11
|
module DEBUGGER__
|
6
|
-
class UI_ServerBase
|
12
|
+
class UI_ServerBase < UI_Base
|
7
13
|
def initialize
|
8
14
|
@sock = nil
|
9
15
|
@accept_m = Mutex.new
|
10
16
|
@accept_cv = ConditionVariable.new
|
11
17
|
@client_addr = nil
|
12
|
-
@q_msg =
|
13
|
-
@q_ans =
|
18
|
+
@q_msg = nil
|
19
|
+
@q_ans = nil
|
14
20
|
@unsent_messages = []
|
21
|
+
@width = 80
|
15
22
|
|
16
23
|
@reader_thread = Thread.new do
|
24
|
+
# An error on this thread should break the system.
|
25
|
+
Thread.current.abort_on_exception = true
|
26
|
+
|
17
27
|
accept do |server|
|
18
|
-
DEBUGGER__.
|
28
|
+
DEBUGGER__.warn "Connected."
|
19
29
|
|
20
30
|
@accept_m.synchronize{
|
21
31
|
@sock = server
|
32
|
+
greeting
|
33
|
+
|
22
34
|
@accept_cv.signal
|
23
35
|
|
24
36
|
# flush unsent messages
|
@@ -26,32 +38,71 @@ module DEBUGGER__
|
|
26
38
|
@sock.puts m
|
27
39
|
}
|
28
40
|
@unsent_messages.clear
|
41
|
+
|
42
|
+
@q_msg = Queue.new
|
43
|
+
@q_ans = Queue.new
|
29
44
|
}
|
30
|
-
@q_msg = Queue.new
|
31
|
-
@q_ans = Queue.new
|
32
45
|
|
33
46
|
setup_interrupt do
|
34
|
-
|
35
|
-
|
36
|
-
while line = @sock.gets
|
37
|
-
case line
|
38
|
-
when /\Apause/
|
39
|
-
pause
|
40
|
-
when /\Acommand ?(.+)/
|
41
|
-
@q_msg << $1
|
42
|
-
when /\Aanswer (.*)/
|
43
|
-
@q_ans << $1
|
44
|
-
else
|
45
|
-
STDERR.puts "unsupported: #{line}"
|
46
|
-
exit!
|
47
|
-
end
|
48
|
-
end
|
47
|
+
process
|
49
48
|
end
|
49
|
+
|
50
|
+
rescue => e
|
51
|
+
DEBUGGER__.warn "ReaderThreadError: #{e}", :error
|
50
52
|
ensure
|
51
|
-
DEBUGGER__.
|
53
|
+
DEBUGGER__.warn "Disconnected."
|
52
54
|
@sock = nil
|
53
55
|
@q_msg.close
|
56
|
+
@q_msg = nil
|
54
57
|
@q_ans.close
|
58
|
+
@q_ans = nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def greeting
|
64
|
+
case g = @sock.gets
|
65
|
+
when /^version:\s+(.+)\s+width: (\d+) cookie:\s+(.*)$/
|
66
|
+
v, w, c = $1, $2, $3
|
67
|
+
# TODO: protocol version
|
68
|
+
if v != VERSION
|
69
|
+
raise "Incompatible version (#{VERSION} client:#{$1})"
|
70
|
+
end
|
71
|
+
|
72
|
+
cookie = CONFIG[:cookie]
|
73
|
+
if cookie && cookie != c
|
74
|
+
raise "Cookie mismatch (#{$2.inspect} was sent)"
|
75
|
+
end
|
76
|
+
|
77
|
+
@width = w.to_i
|
78
|
+
|
79
|
+
when /^Content-Length: (\d+)/
|
80
|
+
require_relative 'server_dap'
|
81
|
+
|
82
|
+
raise unless @sock.read(2) == "\r\n"
|
83
|
+
self.extend(UI_DAP)
|
84
|
+
dap_setup @sock.read($1.to_i)
|
85
|
+
else
|
86
|
+
raise "Greeting message error: #{g}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def process
|
91
|
+
pause
|
92
|
+
|
93
|
+
while line = @sock.gets
|
94
|
+
case line
|
95
|
+
when /\Apause/
|
96
|
+
pause
|
97
|
+
when /\Acommand ?(.+)/
|
98
|
+
@q_msg << $1
|
99
|
+
when /\Aanswer (.*)/
|
100
|
+
@q_ans << $1
|
101
|
+
when /\Awidth (.+)/
|
102
|
+
@width = $1.to_i
|
103
|
+
else
|
104
|
+
STDERR.puts "unsupported: #{line}"
|
105
|
+
exit!
|
55
106
|
end
|
56
107
|
end
|
57
108
|
end
|
@@ -60,6 +111,10 @@ module DEBUGGER__
|
|
60
111
|
true
|
61
112
|
end
|
62
113
|
|
114
|
+
def width
|
115
|
+
@width
|
116
|
+
end
|
117
|
+
|
63
118
|
def setup_interrupt
|
64
119
|
prev_handler = trap(:SIGINT) do
|
65
120
|
# $stderr.puts "trapped SIGINT"
|
@@ -90,12 +145,20 @@ module DEBUGGER__
|
|
90
145
|
if s = @sock # already connection
|
91
146
|
# ok
|
92
147
|
elsif skip == true # skip process
|
93
|
-
|
148
|
+
no_sock = true
|
149
|
+
r = @accept_m.synchronize do
|
150
|
+
if @sock
|
151
|
+
no_sock = false
|
152
|
+
else
|
153
|
+
yield nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
return r if no_sock
|
94
157
|
else # wait for connection
|
95
158
|
until s = @sock
|
96
159
|
@accept_m.synchronize{
|
97
160
|
unless @sock
|
98
|
-
DEBUGGER__.
|
161
|
+
DEBUGGER__.warn "wait for debuger connection..."
|
99
162
|
@accept_cv.wait(@accept_m)
|
100
163
|
end
|
101
164
|
}
|
@@ -139,6 +202,7 @@ module DEBUGGER__
|
|
139
202
|
def readline
|
140
203
|
(sock do |s|
|
141
204
|
s.puts "input"
|
205
|
+
sleep 0.01 until @q_msg
|
142
206
|
@q_msg.pop
|
143
207
|
end || 'continue').strip
|
144
208
|
end
|
@@ -158,7 +222,7 @@ module DEBUGGER__
|
|
158
222
|
|
159
223
|
class UI_TcpServer < UI_ServerBase
|
160
224
|
def initialize host: nil, port: nil
|
161
|
-
@host = host || ::DEBUGGER__::CONFIG[:host] || '
|
225
|
+
@host = host || ::DEBUGGER__::CONFIG[:host] || '127.0.0.1'
|
162
226
|
@port = port || begin
|
163
227
|
port_str = ::DEBUGGER__::CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
|
164
228
|
if /\A\d+\z/ !~ port_str
|
@@ -173,7 +237,7 @@ module DEBUGGER__
|
|
173
237
|
|
174
238
|
def accept
|
175
239
|
Socket.tcp_server_sockets @host, @port do |socks|
|
176
|
-
::DEBUGGER__.
|
240
|
+
::DEBUGGER__.warn "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
|
177
241
|
Socket.accept_loop(socks) do |sock, client|
|
178
242
|
@client_addr = client
|
179
243
|
yield sock
|
@@ -187,40 +251,29 @@ module DEBUGGER__
|
|
187
251
|
end
|
188
252
|
|
189
253
|
class UI_UnixDomainServer < UI_ServerBase
|
190
|
-
def initialize sock_dir: nil
|
254
|
+
def initialize sock_dir: nil, sock_path: nil
|
255
|
+
@sock_path = sock_path
|
191
256
|
@sock_dir = sock_dir || DEBUGGER__.unix_domain_socket_dir
|
192
257
|
|
193
258
|
super()
|
194
259
|
end
|
195
260
|
|
196
261
|
def accept
|
197
|
-
|
262
|
+
case
|
263
|
+
when @sock_path
|
264
|
+
when sp = ::DEBUGGER__::CONFIG[:sock_path]
|
265
|
+
@sock_path = sp
|
266
|
+
else
|
267
|
+
@sock_path = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
|
268
|
+
end
|
198
269
|
|
199
|
-
::DEBUGGER__.
|
200
|
-
Socket.unix_server_loop @
|
270
|
+
::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
|
271
|
+
Socket.unix_server_loop @sock_path do |sock, client|
|
201
272
|
@client_addr = client
|
202
273
|
yield sock
|
274
|
+
ensure
|
275
|
+
sock.close
|
203
276
|
end
|
204
277
|
end
|
205
278
|
end
|
206
|
-
|
207
|
-
def self.open host: nil, port: ::DEBUGGER__::CONFIG[:port], sock_dir: nil
|
208
|
-
if port
|
209
|
-
open_tcp host: host, port: port
|
210
|
-
else
|
211
|
-
open_unix sock_dir: sock_dir
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def self.open_tcp(host: nil, port:)
|
216
|
-
initialize_session UI_TcpServer.new(host: host, port: port)
|
217
|
-
end
|
218
|
-
|
219
|
-
def self.open_unix sock_dir: nil
|
220
|
-
initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir)
|
221
|
-
end
|
222
|
-
|
223
|
-
def self.message msg
|
224
|
-
$stderr.puts "DEBUGGER: #{msg}"
|
225
|
-
end
|
226
279
|
end
|