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.
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