debug 1.0.0.alpha1 → 1.0.0.beta1
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/.gitignore +1 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +20 -19
- data/README.md +353 -51
- data/Rakefile +21 -1
- data/debug.gemspec +6 -11
- data/exe/rdbg +32 -1
- data/ext/debug/debug.c +118 -0
- data/ext/debug/extconf.rb +2 -0
- data/ext/debug/iseq_collector.c +91 -0
- data/lib/debug.rb +1 -1
- data/lib/debug/breakpoint.rb +310 -36
- data/lib/debug/client.rb +4 -11
- data/lib/debug/config.rb +100 -7
- data/lib/debug/console.rb +89 -0
- data/lib/debug/open.rb +10 -0
- data/lib/debug/run.rb +2 -0
- data/lib/debug/server.rb +128 -21
- data/lib/debug/session.rb +605 -170
- data/lib/debug/source_repository.rb +27 -20
- data/lib/debug/thread_client.rb +294 -119
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +325 -0
- metadata +22 -42
- data/lib/debug/repl.rb +0 -69
- data/lib/debug/tcpserver.rb +0 -22
- data/lib/debug/unixserver.rb +0 -18
data/lib/debug/client.rb
CHANGED
@@ -2,6 +2,8 @@ require 'socket'
|
|
2
2
|
require_relative 'config'
|
3
3
|
|
4
4
|
module DEBUGGER__
|
5
|
+
class CommandLineOptionError < Exception; end
|
6
|
+
|
5
7
|
class Client
|
6
8
|
begin
|
7
9
|
require 'readline'
|
@@ -15,14 +17,6 @@ module DEBUGGER__
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
def help
|
19
|
-
puts "-run -e attach # connect via UNIX Domain socket"
|
20
|
-
puts "-run -e attach name # connect via UNIX Domain socket with specified name"
|
21
|
-
puts "-run -e attach port_num # connect via TCP/IP socket with specified port"
|
22
|
-
puts "-run -e attach host port_num"
|
23
|
-
puts " # connect via TCP/IP socket with specified host and port"
|
24
|
-
end
|
25
|
-
|
26
20
|
def initialize argv
|
27
21
|
case argv.size
|
28
22
|
when 0
|
@@ -40,8 +34,7 @@ module DEBUGGER__
|
|
40
34
|
when 2
|
41
35
|
connect_tcp argv[0], argv[1]
|
42
36
|
else
|
43
|
-
|
44
|
-
exit!
|
37
|
+
raise CommandLineOptionError
|
45
38
|
end
|
46
39
|
end
|
47
40
|
|
@@ -69,7 +62,7 @@ module DEBUGGER__
|
|
69
62
|
files = Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
|
70
63
|
case files.size
|
71
64
|
when 0
|
72
|
-
$stderr.puts "
|
65
|
+
$stderr.puts "No debug session is available."
|
73
66
|
exit
|
74
67
|
when 1
|
75
68
|
@s = Socket.unix(files.first)
|
data/lib/debug/config.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
+
|
1
2
|
module DEBUGGER__
|
2
|
-
def self.
|
3
|
+
def self.unix_domain_socket_dir
|
3
4
|
case
|
4
|
-
when path =
|
5
|
+
when path = DEBUGGER__::CONFIG[:sock_dir]
|
5
6
|
when path = ENV['XDG_RUNTIME_DIR']
|
6
7
|
when home = ENV['HOME']
|
7
8
|
path = File.join(home, '.ruby-debug-sock')
|
8
|
-
|
9
|
+
|
10
|
+
case
|
11
|
+
when !File.exist?(path)
|
9
12
|
Dir.mkdir(path, 0700)
|
13
|
+
when !File.directory?(path)
|
14
|
+
raise "#{path} is not a directory."
|
10
15
|
end
|
11
16
|
else
|
12
17
|
raise 'specify RUBY_DEBUG_SOCK_DIR environment variable for UNIX domain socket directory.'
|
@@ -15,12 +20,100 @@ module DEBUGGER__
|
|
15
20
|
path
|
16
21
|
end
|
17
22
|
|
18
|
-
def self.create_unix_domain_socket_name_prefix
|
23
|
+
def self.create_unix_domain_socket_name_prefix(base_dir = unix_domain_socket_dir)
|
19
24
|
user = ENV['USER'] || 'ruby-debug'
|
20
|
-
File.join(
|
25
|
+
File.join(base_dir, "ruby-debug-#{user}")
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir)
|
29
|
+
create_unix_domain_socket_name_prefix(base_dir) + "-#{Process.pid}"
|
21
30
|
end
|
22
31
|
|
23
|
-
|
24
|
-
|
32
|
+
CONFIG_MAP = {
|
33
|
+
# execution preferences
|
34
|
+
nonstop: 'RUBY_DEBUG_NONSTOP', # Nonstop mode ('1' is nonstop)
|
35
|
+
init_script: 'RUBY_DEBUG_INIT_SCRIPT', # debug command script path loaded at first stop
|
36
|
+
commands: 'RUBY_DEBUG_COMMANDS', # debug commands invoked at first stop. commands should be separated by ';;'
|
37
|
+
show_src_lines: 'RUBY_DEBUG_SHOW_SRC_LINES', # Show n lines source code on breakpoint (default: 10 lines).
|
38
|
+
show_frames: 'RUBY_DEBUG_SHOW_FRAMES', # Show n frames on breakpoint (default: 2 frames).
|
39
|
+
|
40
|
+
# remote
|
41
|
+
port: 'RUBY_DEBUG_PORT', # TCP/IP remote debugging: port
|
42
|
+
host: 'RUBY_DEBUG_HOST', # TCP/IP remote debugging: host (localhost if not given)
|
43
|
+
sock_dir: 'RUBY_DEBUG_SOCK_DIR', # UNIX Domain Socket remote debugging: socket directory
|
44
|
+
}.freeze
|
45
|
+
|
46
|
+
def self.config_to_env config
|
47
|
+
CONFIG_MAP.each{|key, evname|
|
48
|
+
ENV[evname] = config[key]
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.parse_argv argv
|
53
|
+
config = {
|
54
|
+
mode: :start,
|
55
|
+
}
|
56
|
+
CONFIG_MAP.each{|key, evname|
|
57
|
+
config[key] = ENV[evname] if ENV[evname]
|
58
|
+
}
|
59
|
+
return config if !argv || argv.empty?
|
60
|
+
|
61
|
+
require 'optparse'
|
62
|
+
|
63
|
+
opt = OptionParser.new do |o|
|
64
|
+
o.banner = "#{$0} [options] -- [debuggee options]"
|
65
|
+
o.separator ''
|
66
|
+
|
67
|
+
o.separator 'Debug console mode:'
|
68
|
+
o.on('-n', '--nonstop', 'Do not stop at the beggining of the script.') do
|
69
|
+
config[:nonstop] = '1'
|
70
|
+
end
|
71
|
+
|
72
|
+
o.on('-e [COMMAND]', 'execute debug command at the beggining of the script.') do |cmd|
|
73
|
+
config[:commands] ||= ''
|
74
|
+
config[:commands] << cmd + ';;'
|
75
|
+
end
|
76
|
+
|
77
|
+
o.on('-O', '--open', 'Start debuggee with opning the debagger port.',
|
78
|
+
'If TCP/IP options are not given,',
|
79
|
+
'a UNIX domain socket will be used.') do
|
80
|
+
config[:remote] = true
|
81
|
+
end
|
82
|
+
o.on('--port=[PORT]', 'Listening TCP/IP port') do |port|
|
83
|
+
config[:port] = port
|
84
|
+
end
|
85
|
+
o.on('--host=[HOST]', 'Listening TCP/IP host') do |host|
|
86
|
+
config[:host] = host
|
87
|
+
end
|
88
|
+
|
89
|
+
o.separator ''
|
90
|
+
o.separator ' Debug console mode runs Ruby program with the debug console.'
|
91
|
+
o.separator ''
|
92
|
+
o.separator " #{$0} target.rb foo bar starts like 'ruby target.rb foo bar'."
|
93
|
+
o.separator " #{$0} -- -r foo -e bar starts like 'ruby -r foo -e bar'."
|
94
|
+
o.separator " #{$0} -O target.rb foo bar starts and accepts attaching with UNIX domain socket."
|
95
|
+
o.separator " #{$0} -O --port 1234 target.rb foo bar starts accepts attaching with TCP/IP localhost:1234."
|
96
|
+
o.separator " #{$0} -O --port 1234 -- -r foo -e bar starts accepts attaching with TCP/IP localhost:1234."
|
97
|
+
|
98
|
+
o.separator ''
|
99
|
+
o.separator 'Attach mode:'
|
100
|
+
o.on('-A', '--attach', 'Attach to debuggee process.') do
|
101
|
+
config[:mode] = :attach
|
102
|
+
end
|
103
|
+
|
104
|
+
o.separator ''
|
105
|
+
o.separator ' Attach mode attaches the remote debug console to the debuggee process.'
|
106
|
+
o.separator ''
|
107
|
+
o.separator " '#{$0} -A' tries to connect via UNIX domain socket."
|
108
|
+
o.separator " #{' ' * $0.size} If there are multiple processes are waiting for the"
|
109
|
+
o.separator " #{' ' * $0.size} debugger connection, list possible debuggee names."
|
110
|
+
o.separator " '#{$0} -A path' tries to connect via UNIX domain socket with given path name."
|
111
|
+
o.separator " '#{$0} -A port' tries to connect localhost:port via TCP/IP."
|
112
|
+
o.separator " '#{$0} -A host port' tris to connect host:port via TCP/IP."
|
113
|
+
end
|
114
|
+
|
115
|
+
opt.parse!(argv)
|
116
|
+
|
117
|
+
config
|
25
118
|
end
|
26
119
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require_relative 'session'
|
2
|
+
|
3
|
+
module DEBUGGER__
|
4
|
+
class UI_Console
|
5
|
+
def initialize
|
6
|
+
end
|
7
|
+
|
8
|
+
def remote?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def quit n
|
13
|
+
exit n
|
14
|
+
end
|
15
|
+
|
16
|
+
def ask prompt
|
17
|
+
setup_interrupt do
|
18
|
+
print prompt
|
19
|
+
(gets || '').strip
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def puts str = nil
|
24
|
+
case str
|
25
|
+
when Array
|
26
|
+
str.each{|line|
|
27
|
+
$stdout.puts line.chomp
|
28
|
+
}
|
29
|
+
when String
|
30
|
+
str.each_line{|line|
|
31
|
+
$stdout.puts line.chomp
|
32
|
+
}
|
33
|
+
when nil
|
34
|
+
$stdout.puts
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'readline'
|
40
|
+
|
41
|
+
def readline_setup
|
42
|
+
Readline.completion_proc = proc{|given|
|
43
|
+
buff = Readline.line_buffer
|
44
|
+
Readline.completion_append_character= ' '
|
45
|
+
|
46
|
+
if /\s/ =~ buff # second parameters
|
47
|
+
given = File.expand_path(given + 'a').sub(/a\z/, '')
|
48
|
+
files = Dir.glob(given + '*')
|
49
|
+
if files.size == 1 && File.directory?(files.first)
|
50
|
+
Readline.completion_append_character= '/'
|
51
|
+
end
|
52
|
+
files
|
53
|
+
else
|
54
|
+
DEBUGGER__.commands.grep(/\A#{given}/)
|
55
|
+
end
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def readline_body
|
60
|
+
readline_setup
|
61
|
+
Readline.readline("\n(rdbg) ", true)
|
62
|
+
end
|
63
|
+
rescue LoadError
|
64
|
+
def readline_body
|
65
|
+
print "\n(rdbg) "
|
66
|
+
gets
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def readline
|
71
|
+
setup_interrupt do
|
72
|
+
(readline_body || 'quit').strip
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def setup_interrupt
|
77
|
+
current_thread = Thread.current # should be session_server thread
|
78
|
+
|
79
|
+
prev_handler = trap(:INT){
|
80
|
+
current_thread.raise Interrupt
|
81
|
+
}
|
82
|
+
|
83
|
+
yield
|
84
|
+
ensure
|
85
|
+
trap(:INT, prev_handler)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
data/lib/debug/open.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#
|
2
|
+
# Open the door for the debugger to connect.
|
3
|
+
# Users can connect to debuggee program with "rdbg --attach" option.
|
4
|
+
#
|
5
|
+
# If RUBY_DEBUG_PORT envval is provided (digits), open TCP/IP port.
|
6
|
+
# Otherwise, UNIX domain socket is used.
|
7
|
+
#
|
8
|
+
|
9
|
+
require_relative 'server'
|
10
|
+
DEBUGGER__.open
|
data/lib/debug/run.rb
ADDED
data/lib/debug/server.rb
CHANGED
@@ -1,40 +1,54 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require_relative 'session'
|
3
|
+
require_relative 'config'
|
3
4
|
|
4
5
|
module DEBUGGER__
|
5
|
-
class
|
6
|
+
class UI_ServerBase
|
6
7
|
def initialize
|
7
8
|
@sock = nil
|
9
|
+
@accept_m = Mutex.new
|
10
|
+
@accept_cv = ConditionVariable.new
|
8
11
|
@client_addr = nil
|
9
12
|
@q_msg = Queue.new
|
10
13
|
@q_ans = Queue.new
|
14
|
+
@unsent_messages = []
|
11
15
|
|
12
16
|
@reader_thread = Thread.new do
|
13
17
|
accept do |server|
|
14
|
-
|
18
|
+
DEBUGGER__.message "Connected."
|
19
|
+
|
20
|
+
@accept_m.synchronize{
|
21
|
+
@sock = server
|
22
|
+
@accept_cv.signal
|
23
|
+
|
24
|
+
# flush unsent messages
|
25
|
+
@unsent_messages.each{|m|
|
26
|
+
@sock.puts m
|
27
|
+
}
|
28
|
+
@unsent_messages.clear
|
29
|
+
}
|
15
30
|
@q_msg = Queue.new
|
16
31
|
@q_ans = Queue.new
|
17
32
|
|
18
33
|
setup_interrupt do
|
19
34
|
pause
|
20
35
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
exit!
|
33
|
-
end
|
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!
|
34
47
|
end
|
35
48
|
end
|
36
49
|
end
|
37
50
|
ensure
|
51
|
+
DEBUGGER__.message "Disconnected."
|
38
52
|
@sock = nil
|
39
53
|
@q_msg.close
|
40
54
|
@q_ans.close
|
@@ -42,11 +56,14 @@ module DEBUGGER__
|
|
42
56
|
end
|
43
57
|
end
|
44
58
|
|
59
|
+
def remote?
|
60
|
+
true
|
61
|
+
end
|
45
62
|
|
46
63
|
def setup_interrupt
|
47
64
|
prev_handler = trap(:SIGINT) do
|
48
65
|
# $stderr.puts "trapped SIGINT"
|
49
|
-
ThreadClient.current.on_trap
|
66
|
+
ThreadClient.current.on_trap :SIGINT
|
50
67
|
|
51
68
|
case prev_handler
|
52
69
|
when Proc
|
@@ -69,8 +86,23 @@ module DEBUGGER__
|
|
69
86
|
|
70
87
|
class NoRemoteError < Exception; end
|
71
88
|
|
72
|
-
def sock
|
73
|
-
|
89
|
+
def sock skip: false
|
90
|
+
if s = @sock # already connection
|
91
|
+
# ok
|
92
|
+
elsif skip == true # skip process
|
93
|
+
return yield nil
|
94
|
+
else # wait for connection
|
95
|
+
until s = @sock
|
96
|
+
@accept_m.synchronize{
|
97
|
+
unless @sock
|
98
|
+
DEBUGGER__.message "wait for debuger connection..."
|
99
|
+
@accept_cv.wait(@accept_m)
|
100
|
+
end
|
101
|
+
}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
yield s
|
74
106
|
rescue Errno::EPIPE
|
75
107
|
# ignore
|
76
108
|
end
|
@@ -82,17 +114,24 @@ module DEBUGGER__
|
|
82
114
|
end
|
83
115
|
end
|
84
116
|
|
85
|
-
def puts str
|
117
|
+
def puts str = nil
|
86
118
|
case str
|
87
119
|
when Array
|
88
120
|
enum = str.each
|
89
121
|
when String
|
90
122
|
enum = str.each_line
|
123
|
+
when nil
|
124
|
+
enum = [''].each
|
91
125
|
end
|
92
126
|
|
93
|
-
sock do |s|
|
127
|
+
sock skip: true do |s|
|
94
128
|
enum.each do |line|
|
95
|
-
|
129
|
+
msg = "out #{line.chomp}"
|
130
|
+
if s
|
131
|
+
s.puts msg
|
132
|
+
else
|
133
|
+
@unsent_messages << msg
|
134
|
+
end
|
96
135
|
end
|
97
136
|
end
|
98
137
|
end
|
@@ -116,4 +155,72 @@ module DEBUGGER__
|
|
116
155
|
end
|
117
156
|
end
|
118
157
|
end
|
158
|
+
|
159
|
+
class UI_TcpServer < UI_ServerBase
|
160
|
+
def initialize host: nil, port: nil
|
161
|
+
@host = host || ::DEBUGGER__::CONFIG[:host] || 'localhost'
|
162
|
+
@port = port || begin
|
163
|
+
port_str = ::DEBUGGER__::CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
|
164
|
+
if /\A\d+\z/ !~ port_str
|
165
|
+
raise "Specify digits for port number"
|
166
|
+
else
|
167
|
+
port_str.to_i
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
super()
|
172
|
+
end
|
173
|
+
|
174
|
+
def accept
|
175
|
+
Socket.tcp_server_sockets @host, @port do |socks|
|
176
|
+
::DEBUGGER__.message "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
|
177
|
+
Socket.accept_loop(socks) do |sock, client|
|
178
|
+
@client_addr = client
|
179
|
+
yield sock
|
180
|
+
end
|
181
|
+
end
|
182
|
+
rescue => e
|
183
|
+
$stderr.puts e.message
|
184
|
+
pp e.backtrace
|
185
|
+
exit
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class UI_UnixDomainServer < UI_ServerBase
|
190
|
+
def initialize sock_dir: nil
|
191
|
+
@sock_dir = sock_dir || DEBUGGER__.unix_domain_socket_dir
|
192
|
+
|
193
|
+
super()
|
194
|
+
end
|
195
|
+
|
196
|
+
def accept
|
197
|
+
@file = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
|
198
|
+
|
199
|
+
::DEBUGGER__.message "Debugger can attach via UNIX domain socket (#{@file})"
|
200
|
+
Socket.unix_server_loop @file do |sock, client|
|
201
|
+
@client_addr = client
|
202
|
+
yield sock
|
203
|
+
end
|
204
|
+
end
|
205
|
+
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
|
119
226
|
end
|