debug 1.0.0.alpha1 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|