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.
@@ -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
@@ -0,0 +1,2 @@
1
+ require_relative 'console'
2
+ DEBUGGER__.console
@@ -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