debug 0.2.0 → 1.0.0.beta3
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 +4 -5
- data/LICENSE.txt +0 -0
- data/README.md +442 -27
- data/Rakefile +29 -0
- data/debug.gemspec +10 -5
- data/exe/rdbg +34 -0
- 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 -1111
- data/lib/debug/bp.vim +68 -0
- data/lib/debug/breakpoint.rb +374 -0
- data/lib/debug/client.rb +128 -0
- data/lib/debug/config.rb +125 -0
- 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 +226 -0
- data/lib/debug/session.rb +1071 -0
- data/lib/debug/source_repository.rb +27 -0
- data/lib/debug/thread_client.rb +618 -0
- data/lib/debug/version.rb +3 -0
- data/misc/README.md.erb +324 -0
- metadata +34 -14
data/lib/debug/config.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
|
2
|
+
module DEBUGGER__
|
3
|
+
def self.unix_domain_socket_dir
|
4
|
+
case
|
5
|
+
when path = DEBUGGER__::CONFIG[:sock_dir]
|
6
|
+
when path = ENV['XDG_RUNTIME_DIR']
|
7
|
+
when home = ENV['HOME']
|
8
|
+
path = File.join(home, '.ruby-debug-sock')
|
9
|
+
|
10
|
+
case
|
11
|
+
when !File.exist?(path)
|
12
|
+
Dir.mkdir(path, 0700)
|
13
|
+
when !File.directory?(path)
|
14
|
+
raise "#{path} is not a directory."
|
15
|
+
end
|
16
|
+
else
|
17
|
+
raise 'specify RUBY_DEBUG_SOCK_DIR environment variable for UNIX domain socket directory.'
|
18
|
+
end
|
19
|
+
|
20
|
+
path
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.create_unix_domain_socket_name_prefix(base_dir = unix_domain_socket_dir)
|
24
|
+
user = ENV['USER'] || 'ruby-debug'
|
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}"
|
30
|
+
end
|
31
|
+
|
32
|
+
CONFIG_MAP = {
|
33
|
+
# boot setting
|
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
|
+
|
38
|
+
# UI setting
|
39
|
+
show_src_lines: 'RUBY_DEBUG_SHOW_SRC_LINES', # Show n lines source code on breakpoint (default: 10 lines).
|
40
|
+
show_frames: 'RUBY_DEBUG_SHOW_FRAMES', # Show n frames on breakpoint (default: 2 frames).
|
41
|
+
use_short_path: 'RUBY_DEBUG_USE_SHORT_PATH', # Show shoten PATH (like $(Gem)/foo.rb).
|
42
|
+
skip_nosrc: 'RUBY_DEBUG_SKIP_NOSRC', # Skip on no source code lines (default: false).
|
43
|
+
|
44
|
+
# remote
|
45
|
+
port: 'RUBY_DEBUG_PORT', # TCP/IP remote debugging: port
|
46
|
+
host: 'RUBY_DEBUG_HOST', # TCP/IP remote debugging: host (localhost if not given)
|
47
|
+
sock_dir: 'RUBY_DEBUG_SOCK_DIR', # UNIX Domain Socket remote debugging: socket directory
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
def self.config_to_env config
|
51
|
+
CONFIG_MAP.each{|key, evname|
|
52
|
+
ENV[evname] = config[key]
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.parse_argv argv
|
57
|
+
config = {
|
58
|
+
mode: :start,
|
59
|
+
}
|
60
|
+
CONFIG_MAP.each{|key, evname|
|
61
|
+
config[key] = ENV[evname] if ENV[evname]
|
62
|
+
}
|
63
|
+
return config if !argv || argv.empty?
|
64
|
+
|
65
|
+
require 'optparse'
|
66
|
+
|
67
|
+
opt = OptionParser.new do |o|
|
68
|
+
o.banner = "#{$0} [options] -- [debuggee options]"
|
69
|
+
o.separator ''
|
70
|
+
|
71
|
+
o.separator 'Debug console mode:'
|
72
|
+
o.on('-n', '--nonstop', 'Do not stop at the beginning of the script.') do
|
73
|
+
config[:nonstop] = '1'
|
74
|
+
end
|
75
|
+
|
76
|
+
o.on('-e [COMMAND]', 'execute debug command at the beginning of the script.') do |cmd|
|
77
|
+
config[:commands] ||= ''
|
78
|
+
config[:commands] << cmd + ';;'
|
79
|
+
end
|
80
|
+
|
81
|
+
o.on('-O', '--open', 'Start debuggee with opening the debugger port.',
|
82
|
+
'If TCP/IP options are not given,',
|
83
|
+
'a UNIX domain socket will be used.') do
|
84
|
+
config[:remote] = true
|
85
|
+
end
|
86
|
+
o.on('--port=[PORT]', 'Listening TCP/IP port') do |port|
|
87
|
+
config[:port] = port
|
88
|
+
end
|
89
|
+
o.on('--host=[HOST]', 'Listening TCP/IP host') do |host|
|
90
|
+
config[:host] = host
|
91
|
+
end
|
92
|
+
|
93
|
+
rdbg = 'rdbg'
|
94
|
+
|
95
|
+
o.separator ''
|
96
|
+
o.separator ' Debug console mode runs Ruby program with the debug console.'
|
97
|
+
o.separator ''
|
98
|
+
o.separator " #{rdbg} target.rb foo bar starts like 'ruby target.rb foo bar'."
|
99
|
+
o.separator " #{rdbg} -- -r foo -e bar starts like 'ruby -r foo -e bar'."
|
100
|
+
o.separator " #{rdbg} -O target.rb foo bar starts and accepts attaching with UNIX domain socket."
|
101
|
+
o.separator " #{rdbg} -O --port 1234 target.rb foo bar starts accepts attaching with TCP/IP localhost:1234."
|
102
|
+
o.separator " #{rdbg} -O --port 1234 -- -r foo -e bar starts accepts attaching with TCP/IP localhost:1234."
|
103
|
+
|
104
|
+
o.separator ''
|
105
|
+
o.separator 'Attach mode:'
|
106
|
+
o.on('-A', '--attach', 'Attach to debuggee process.') do
|
107
|
+
config[:mode] = :attach
|
108
|
+
end
|
109
|
+
|
110
|
+
o.separator ''
|
111
|
+
o.separator ' Attach mode attaches the remote debug console to the debuggee process.'
|
112
|
+
o.separator ''
|
113
|
+
o.separator " '#{rdbg} -A' tries to connect via UNIX domain socket."
|
114
|
+
o.separator " #{' ' * rdbg.size} If there are multiple processes are waiting for the"
|
115
|
+
o.separator " #{' ' * rdbg.size} debugger connection, list possible debuggee names."
|
116
|
+
o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
|
117
|
+
o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
|
118
|
+
o.separator " '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."
|
119
|
+
end
|
120
|
+
|
121
|
+
opt.parse!(argv)
|
122
|
+
|
123
|
+
config
|
124
|
+
end
|
125
|
+
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
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require_relative 'session'
|
3
|
+
require_relative 'config'
|
4
|
+
|
5
|
+
module DEBUGGER__
|
6
|
+
class UI_ServerBase
|
7
|
+
def initialize
|
8
|
+
@sock = nil
|
9
|
+
@accept_m = Mutex.new
|
10
|
+
@accept_cv = ConditionVariable.new
|
11
|
+
@client_addr = nil
|
12
|
+
@q_msg = Queue.new
|
13
|
+
@q_ans = Queue.new
|
14
|
+
@unsent_messages = []
|
15
|
+
|
16
|
+
@reader_thread = Thread.new do
|
17
|
+
accept do |server|
|
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
|
+
}
|
30
|
+
@q_msg = Queue.new
|
31
|
+
@q_ans = Queue.new
|
32
|
+
|
33
|
+
setup_interrupt do
|
34
|
+
pause
|
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
|
49
|
+
end
|
50
|
+
ensure
|
51
|
+
DEBUGGER__.message "Disconnected."
|
52
|
+
@sock = nil
|
53
|
+
@q_msg.close
|
54
|
+
@q_ans.close
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def remote?
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_interrupt
|
64
|
+
prev_handler = trap(:SIGINT) do
|
65
|
+
# $stderr.puts "trapped SIGINT"
|
66
|
+
ThreadClient.current.on_trap :SIGINT
|
67
|
+
|
68
|
+
case prev_handler
|
69
|
+
when Proc
|
70
|
+
prev_handler.call
|
71
|
+
else
|
72
|
+
# ignore
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
yield
|
77
|
+
ensure
|
78
|
+
trap(:SIGINT, prev_handler)
|
79
|
+
end
|
80
|
+
|
81
|
+
def accept
|
82
|
+
raise "NOT IMPLEMENTED ERROR"
|
83
|
+
end
|
84
|
+
|
85
|
+
attr_reader :reader_thread
|
86
|
+
|
87
|
+
class NoRemoteError < Exception; end
|
88
|
+
|
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
|
106
|
+
rescue Errno::EPIPE
|
107
|
+
# ignore
|
108
|
+
end
|
109
|
+
|
110
|
+
def ask prompt
|
111
|
+
sock do |s|
|
112
|
+
s.puts "ask #{prompt}"
|
113
|
+
@q_ans.pop
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def puts str = nil
|
118
|
+
case str
|
119
|
+
when Array
|
120
|
+
enum = str.each
|
121
|
+
when String
|
122
|
+
enum = str.each_line
|
123
|
+
when nil
|
124
|
+
enum = [''].each
|
125
|
+
end
|
126
|
+
|
127
|
+
sock skip: true do |s|
|
128
|
+
enum.each do |line|
|
129
|
+
msg = "out #{line.chomp}"
|
130
|
+
if s
|
131
|
+
s.puts msg
|
132
|
+
else
|
133
|
+
@unsent_messages << msg
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def readline
|
140
|
+
(sock do |s|
|
141
|
+
s.puts "input"
|
142
|
+
@q_msg.pop
|
143
|
+
end || 'continue').strip
|
144
|
+
end
|
145
|
+
|
146
|
+
def pause
|
147
|
+
# $stderr.puts "DEBUG: pause request"
|
148
|
+
Process.kill(:SIGINT, Process.pid)
|
149
|
+
end
|
150
|
+
|
151
|
+
def quit n
|
152
|
+
# ignore n
|
153
|
+
sock do |s|
|
154
|
+
s.puts "quit"
|
155
|
+
end
|
156
|
+
end
|
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
|
226
|
+
end
|