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.
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
- help
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 "There is no debug sessions."
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.unix_domain_socket_basedir
3
+ def self.unix_domain_socket_dir
3
4
  case
4
- when path = ENV['RUBY_DEBUG_SOCK_DIR']
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
- unless File.exist?(path)
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(unix_domain_socket_basedir, "ruby-debug-#{user}")
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
- def self.create_unix_domain_socket_name
24
- create_unix_domain_socket_name_prefix + "-#{Process.pid}"
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
@@ -0,0 +1,2 @@
1
+ require_relative 'console'
2
+ DEBUGGER__.console
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 UI_Server
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
- @sock = 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
+ }
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
- begin
22
- while line = @sock.gets
23
- case line
24
- when /\Apause/
25
- pause
26
- when /\Acommand ?(.+)/
27
- @q_msg << $1
28
- when /\Aanswer (.*)/
29
- @q_ans << $1
30
- else
31
- STDERR.puts "unsupported: #{line}"
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
- yield @sock if @sock
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
- s.puts "out #{line.chomp}"
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