debug 1.0.0.alpha1 → 1.0.0.beta5

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
@@ -1,7 +1,14 @@
1
1
  require 'socket'
2
+ require 'io/console/size'
3
+
2
4
  require_relative 'config'
5
+ require_relative 'version'
6
+
7
+ # $VERBOSE = true
3
8
 
4
9
  module DEBUGGER__
10
+ class CommandLineOptionError < Exception; end
11
+
5
12
  class Client
6
13
  begin
7
14
  require 'readline'
@@ -15,15 +22,9 @@ module DEBUGGER__
15
22
  end
16
23
  end
17
24
 
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
25
  def initialize argv
26
+ return util(argv) if String === argv
27
+
27
28
  case argv.size
28
29
  when 0
29
30
  connect_unix
@@ -40,8 +41,24 @@ module DEBUGGER__
40
41
  when 2
41
42
  connect_tcp argv[0], argv[1]
42
43
  else
43
- help
44
- exit!
44
+ raise CommandLineOptionError
45
+ end
46
+
47
+ @width = IO.console_size[1]
48
+ @width_changed = false
49
+
50
+ send "version: #{VERSION} width: #{@width} cookie: #{CONFIG[:cookie]}"
51
+ end
52
+
53
+ def util name
54
+ case name
55
+ when 'gen-sockpath'
56
+ puts DEBUGGER__.create_unix_domain_socket_name
57
+ when 'list-socks'
58
+ cleanup_unix_domain_sockets
59
+ puts list_connections
60
+ else
61
+ raise "Unknown utility: #{name}"
45
62
  end
46
63
  end
47
64
 
@@ -57,19 +74,23 @@ module DEBUGGER__
57
74
  end
58
75
  end
59
76
 
77
+ def list_connections
78
+ Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
79
+ end
80
+
60
81
  def connect_unix name = nil
61
82
  if name
62
83
  if File.exist? name
63
84
  @s = Socket.unix(name)
64
85
  else
65
- @s = Socket.unix(File.join(DEBUGGER__.unix_domain_socket_basedir, name))
86
+ @s = Socket.unix(File.join(DEBUGGER__.unix_domain_socket_dir, name))
66
87
  end
67
88
  else
68
89
  cleanup_unix_domain_sockets
69
- files = Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
90
+ files = list_connections
70
91
  case files.size
71
92
  when 0
72
- $stderr.puts "There is no debug sessions."
93
+ $stderr.puts "No debug session is available."
73
94
  exit
74
95
  when 1
75
96
  @s = Socket.unix(files.first)
@@ -87,13 +108,22 @@ module DEBUGGER__
87
108
  @s = Socket.tcp(host, port)
88
109
  end
89
110
 
111
+ def send msg
112
+ p send: msg if $VERBOSE
113
+ @s.puts msg
114
+ end
115
+
90
116
  def connect
91
117
  trap(:SIGINT){
92
- @s.puts "pause"
118
+ send "pause"
119
+ }
120
+ trap(:SIGWINCH){
121
+ @width = IO.console_size[1]
122
+ @width_changed = true
93
123
  }
94
124
 
95
125
  while line = @s.gets
96
- # p line: line
126
+ p recv: line if $VERBOSE
97
127
  case line
98
128
  when /^out (.*)/
99
129
  puts "#{$1}"
@@ -109,10 +139,16 @@ module DEBUGGER__
109
139
  end
110
140
 
111
141
  line = (line || 'quit').strip
112
- @s.puts "command #{line}"
142
+
143
+ if @width_changed
144
+ @width_changed = false
145
+ send "width #{@width}"
146
+ end
147
+
148
+ send "command #{line}"
113
149
  when /^ask (.*)/
114
150
  print $1
115
- @s.puts "answer #{gets || ''}"
151
+ send "answer #{gets || ''}"
116
152
  when /^quit/
117
153
  raise 'quit'
118
154
  else
@@ -0,0 +1,70 @@
1
+ begin
2
+ require 'irb/color'
3
+ require "irb/color_printer"
4
+ rescue LoadError
5
+ warn "DEBUGGER: can not load newer irb for coloring. Write 'gem \"debug\" in your Gemfile."
6
+ end
7
+
8
+ module DEBUGGER__
9
+ module Color
10
+ if defined? IRB::Color.colorize
11
+ def colorize str, color
12
+ if CONFIG[:use_colorize]
13
+ IRB::Color.colorize str, color
14
+ else
15
+ str
16
+ end
17
+ end
18
+ else
19
+ def colorize str, color
20
+ str
21
+ end
22
+ end
23
+
24
+ if defined? IRB::ColorPrinter.pp
25
+ def color_pp obj
26
+ IRB::ColorPrinter.pp(obj, "")
27
+ end
28
+ else
29
+ def color_pp obj
30
+ obj.pretty_inspect
31
+ end
32
+ end
33
+
34
+ def colored_inspect obj
35
+ if CONFIG[:use_colorize]
36
+ color_pp obj
37
+ else
38
+ obj.pretty_inspect
39
+ end
40
+ rescue => ex
41
+ err_msg = "#{ex.inspect} rescued during inspection"
42
+ string_result = obj.to_s rescue nil
43
+
44
+ # don't colorize the string here because it's not from user's application
45
+ if string_result
46
+ %Q{"#{string_result}" from #to_s because #{err_msg}}
47
+ else
48
+ err_msg
49
+ end
50
+ end
51
+
52
+ if defined? IRB::Color.colorize_code
53
+ def colorize_code code
54
+ IRB::Color.colorize_code(code)
55
+ end
56
+ else
57
+ def colorize_code code
58
+ code
59
+ end
60
+ end
61
+
62
+ def colorize_cyan(str)
63
+ colorize(str, [:CYAN, :BOLD])
64
+ end
65
+
66
+ def colorize_blue(str)
67
+ colorize(str, [:BLUE, :BOLD])
68
+ end
69
+ end
70
+ end
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,169 @@ 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}"
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
+ use_colorize: 'RUBY_DEBUG_USE_COLORIZE', # Show coloring
44
+
45
+ # remote
46
+ port: 'RUBY_DEBUG_PORT', # TCP/IP remote debugging: port
47
+ host: 'RUBY_DEBUG_HOST', # TCP/IP remote debugging: host (localhost if not given)
48
+ sock_path: 'RUBY_DEBUG_SOCK_PATH', # UNIX Domain Socket remote debugging: socket path
49
+ sock_dir: 'RUBY_DEBUG_SOCK_DIR', # UNIX Domain Socket remote debugging: socket directory
50
+ cookie: 'RUBY_DEBUG_COOKIE', # Cookie for negotiation
51
+ }.freeze
52
+
53
+ def self.config_to_env config
54
+ CONFIG_MAP.each{|key, evname|
55
+ ENV[evname] = config[key].to_s if config[key]
56
+ }
21
57
  end
22
58
 
23
- def self.create_unix_domain_socket_name
24
- create_unix_domain_socket_name_prefix + "-#{Process.pid}"
59
+ def self.parse_argv argv
60
+ config = {
61
+ mode: :start,
62
+ use_colorize: true,
63
+ }
64
+ CONFIG_MAP.each{|key, evname|
65
+ if val = ENV[evname]
66
+ if /_USE_/ =~ evname || /NONSTOP/ =~ evname
67
+ case val
68
+ when '1', 'true', 'TRUE', 'T'
69
+ config[key] = true
70
+ else
71
+ config[key] = false
72
+ end
73
+ else
74
+ config[key] = val
75
+ end
76
+ end
77
+ }
78
+ return config if !argv || argv.empty?
79
+
80
+ require 'optparse'
81
+ require_relative 'version'
82
+
83
+ opt = OptionParser.new do |o|
84
+ o.banner = "#{$0} [options] -- [debuggee options]"
85
+ o.separator ''
86
+ o.version = ::DEBUGGER__::VERSION
87
+
88
+ o.separator 'Debug console mode:'
89
+ o.on('-n', '--nonstop', 'Do not stop at the beginning of the script.') do
90
+ config[:nonstop] = '1'
91
+ end
92
+
93
+ o.on('-e COMMAND', 'execute debug command at the beginning of the script.') do |cmd|
94
+ config[:commands] ||= ''
95
+ config[:commands] << cmd + ';;'
96
+ end
97
+
98
+ o.on('-x FILE', '--init-script=FILE', 'execute debug command in the FILE.') do |file|
99
+ config[:init_script] = file
100
+ end
101
+
102
+ o.separator ''
103
+
104
+ o.on('-O', '--open', 'Start remote debugging with opening the network port.',
105
+ 'If TCP/IP options are not given,',
106
+ 'a UNIX domain socket will be used.') do
107
+ config[:remote] = true
108
+ end
109
+ o.on('--sock-path=SOCK_PATH', 'UNIX Doman socket path') do |path|
110
+ config[:sock_path] = path
111
+ end
112
+ o.on('--port=PORT', 'Listening TCP/IP port') do |port|
113
+ config[:port] = port
114
+ end
115
+ o.on('--host=HOST', 'Listening TCP/IP host') do |host|
116
+ config[:host] = host
117
+ end
118
+ o.on('--cookie=COOKIE', 'Set a cookie for connection') do |c|
119
+ config[:cookie] = c
120
+ end
121
+
122
+ rdbg = 'rdbg'
123
+
124
+ o.separator ''
125
+ o.separator ' Debug console mode runs Ruby program with the debug console.'
126
+ o.separator ''
127
+ o.separator " '#{rdbg} target.rb foo bar' starts like 'ruby target.rb foo bar'."
128
+ o.separator " '#{rdbg} -- -r foo -e bar' starts like 'ruby -r foo -e bar'."
129
+ o.separator " '#{rdbg} -O target.rb foo bar' starts and accepts attaching with UNIX domain socket."
130
+ o.separator " '#{rdbg} -O --port 1234 target.rb foo bar' starts accepts attaching with TCP/IP localhost:1234."
131
+ o.separator " '#{rdbg} -O --port 1234 -- -r foo -e bar' starts accepts attaching with TCP/IP localhost:1234."
132
+
133
+ o.separator ''
134
+ o.separator 'Attach mode:'
135
+ o.on('-A', '--attach', 'Attach to debuggee process.') do
136
+ config[:mode] = :attach
137
+ end
138
+
139
+ o.separator ''
140
+ o.separator ' Attach mode attaches the remote debug console to the debuggee process.'
141
+ o.separator ''
142
+ o.separator " '#{rdbg} -A' tries to connect via UNIX domain socket."
143
+ o.separator " #{' ' * rdbg.size} If there are multiple processes are waiting for the"
144
+ o.separator " #{' ' * rdbg.size} debugger connection, list possible debuggee names."
145
+ o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
146
+ o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
147
+ o.separator " '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."
148
+
149
+ o.separator ''
150
+ o.separator 'Other options:'
151
+
152
+ o.on("-h", "--help", "Print help") do
153
+ puts o
154
+ exit
155
+ end
156
+
157
+ o.on('-c', '--command', 'Command mode (first argument is command name)') do
158
+ config[:command] = true
159
+ end
160
+
161
+ o.on('--util=NAME', 'Utility mode (used by tools)') do |name|
162
+ Client.new(name)
163
+ exit
164
+ end
165
+
166
+ o.separator ''
167
+ o.separator 'NOTE'
168
+ o.separator ' All messages communicated between a debugger and a debuggee are *NOT* encrypted.'
169
+ o.separator ' Please use the remote debugging feature carefully.'
170
+ end
171
+
172
+ opt.parse!(argv)
173
+
174
+ config
175
+ end
176
+
177
+ CONFIG = ::DEBUGGER__.parse_argv(ENV['RUBY_DEBUG_OPT'])
178
+
179
+ def self.set_config kw
180
+ kw.each{|k, v|
181
+ if CONFIG_MAP[k]
182
+ CONFIG[k] = v # TODO: ractor support
183
+ else
184
+ raise "unknown option: #{k}"
185
+ end
186
+ }
25
187
  end
26
188
  end
@@ -0,0 +1,96 @@
1
+ require_relative 'session'
2
+ return unless defined?(DEBUGGER__)
3
+
4
+ require 'io/console/size'
5
+
6
+ module DEBUGGER__
7
+ class UI_Console < UI_Base
8
+ def initialize
9
+ end
10
+
11
+ def remote?
12
+ false
13
+ end
14
+
15
+ def width
16
+ IO.console_size[1]
17
+ end
18
+
19
+ def quit n
20
+ exit n
21
+ end
22
+
23
+ def ask prompt
24
+ setup_interrupt do
25
+ print prompt
26
+ (gets || '').strip
27
+ end
28
+ end
29
+
30
+ def puts str = nil
31
+ case str
32
+ when Array
33
+ str.each{|line|
34
+ $stdout.puts line.chomp
35
+ }
36
+ when String
37
+ str.each_line{|line|
38
+ $stdout.puts line.chomp
39
+ }
40
+ when nil
41
+ $stdout.puts
42
+ end
43
+ end
44
+
45
+ begin
46
+ require 'readline'
47
+
48
+ def readline_setup
49
+ Readline.completion_proc = proc{|given|
50
+ buff = Readline.line_buffer
51
+ Readline.completion_append_character= ' '
52
+
53
+ if /\s/ =~ buff # second parameters
54
+ given = File.expand_path(given + 'a').sub(/a\z/, '')
55
+ files = Dir.glob(given + '*')
56
+ if files.size == 1 && File.directory?(files.first)
57
+ Readline.completion_append_character= '/'
58
+ end
59
+ files
60
+ else
61
+ DEBUGGER__.commands.grep(/\A#{given}/)
62
+ end
63
+ }
64
+ end
65
+
66
+ def readline_body
67
+ readline_setup
68
+ Readline.readline("\n(rdbg) ", true)
69
+ end
70
+ rescue LoadError
71
+ def readline_body
72
+ print "\n(rdbg) "
73
+ gets
74
+ end
75
+ end
76
+
77
+ def readline
78
+ setup_interrupt do
79
+ (readline_body || 'quit').strip
80
+ end
81
+ end
82
+
83
+ def setup_interrupt
84
+ current_thread = Thread.current # should be session_server thread
85
+
86
+ prev_handler = trap(:INT){
87
+ current_thread.raise Interrupt
88
+ }
89
+
90
+ yield
91
+ ensure
92
+ trap(:INT, prev_handler)
93
+ end
94
+ end
95
+ end
96
+