debug 1.0.0.beta4 → 1.0.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/debug/client.rb CHANGED
@@ -1,5 +1,10 @@
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__
5
10
  class CommandLineOptionError < Exception; end
@@ -18,6 +23,8 @@ module DEBUGGER__
18
23
  end
19
24
 
20
25
  def initialize argv
26
+ return util(argv) if String === argv
27
+
21
28
  case argv.size
22
29
  when 0
23
30
  connect_unix
@@ -36,6 +43,23 @@ module DEBUGGER__
36
43
  else
37
44
  raise CommandLineOptionError
38
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}"
62
+ end
39
63
  end
40
64
 
41
65
  def cleanup_unix_domain_sockets
@@ -50,16 +74,20 @@ module DEBUGGER__
50
74
  end
51
75
  end
52
76
 
77
+ def list_connections
78
+ Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
79
+ end
80
+
53
81
  def connect_unix name = nil
54
82
  if name
55
83
  if File.exist? name
56
84
  @s = Socket.unix(name)
57
85
  else
58
- @s = Socket.unix(File.join(DEBUGGER__.unix_domain_socket_basedir, name))
86
+ @s = Socket.unix(File.join(DEBUGGER__.unix_domain_socket_dir, name))
59
87
  end
60
88
  else
61
89
  cleanup_unix_domain_sockets
62
- files = Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
90
+ files = list_connections
63
91
  case files.size
64
92
  when 0
65
93
  $stderr.puts "No debug session is available."
@@ -80,13 +108,22 @@ module DEBUGGER__
80
108
  @s = Socket.tcp(host, port)
81
109
  end
82
110
 
111
+ def send msg
112
+ p send: msg if $VERBOSE
113
+ @s.puts msg
114
+ end
115
+
83
116
  def connect
84
117
  trap(:SIGINT){
85
- @s.puts "pause"
118
+ send "pause"
119
+ }
120
+ trap(:SIGWINCH){
121
+ @width = IO.console_size[1]
122
+ @width_changed = true
86
123
  }
87
124
 
88
125
  while line = @s.gets
89
- # p line: line
126
+ p recv: line if $VERBOSE
90
127
  case line
91
128
  when /^out (.*)/
92
129
  puts "#{$1}"
@@ -102,10 +139,16 @@ module DEBUGGER__
102
139
  end
103
140
 
104
141
  line = (line || 'quit').strip
105
- @s.puts "command #{line}"
142
+
143
+ if @width_changed
144
+ @width_changed = false
145
+ send "width #{@width}"
146
+ end
147
+
148
+ send "command #{line}"
106
149
  when /^ask (.*)/
107
150
  print $1
108
- @s.puts "answer #{gets || ''}"
151
+ send "answer #{gets || ''}"
109
152
  when /^quit/
110
153
  raise 'quit'
111
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
@@ -47,6 +47,7 @@ module DEBUGGER__
47
47
  host: 'RUBY_DEBUG_HOST', # TCP/IP remote debugging: host (localhost if not given)
48
48
  sock_path: 'RUBY_DEBUG_SOCK_PATH', # UNIX Domain Socket remote debugging: socket path
49
49
  sock_dir: 'RUBY_DEBUG_SOCK_DIR', # UNIX Domain Socket remote debugging: socket directory
50
+ cookie: 'RUBY_DEBUG_COOKIE', # Cookie for negotiation
50
51
  }.freeze
51
52
 
52
53
  def self.config_to_env config
@@ -77,35 +78,46 @@ module DEBUGGER__
77
78
  return config if !argv || argv.empty?
78
79
 
79
80
  require 'optparse'
81
+ require_relative 'version'
80
82
 
81
83
  opt = OptionParser.new do |o|
82
84
  o.banner = "#{$0} [options] -- [debuggee options]"
83
85
  o.separator ''
86
+ o.version = ::DEBUGGER__::VERSION
84
87
 
85
88
  o.separator 'Debug console mode:'
86
89
  o.on('-n', '--nonstop', 'Do not stop at the beginning of the script.') do
87
90
  config[:nonstop] = '1'
88
91
  end
89
92
 
90
- o.on('-e [COMMAND]', 'execute debug command at the beginning of the script.') do |cmd|
93
+ o.on('-e COMMAND', 'execute debug command at the beginning of the script.') do |cmd|
91
94
  config[:commands] ||= ''
92
95
  config[:commands] << cmd + ';;'
93
96
  end
94
97
 
95
- o.on('-O', '--open', 'Start debuggee with opening the debugger port.',
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.',
96
105
  'If TCP/IP options are not given,',
97
106
  'a UNIX domain socket will be used.') do
98
107
  config[:remote] = true
99
108
  end
100
- o.on('--sock-path=[SOCK_PATH]', 'UNIX Doman socket path') do |path|
109
+ o.on('--sock-path=SOCK_PATH', 'UNIX Doman socket path') do |path|
101
110
  config[:sock_path] = path
102
111
  end
103
- o.on('--port=[PORT]', 'Listening TCP/IP port') do |port|
112
+ o.on('--port=PORT', 'Listening TCP/IP port') do |port|
104
113
  config[:port] = port
105
114
  end
106
- o.on('--host=[HOST]', 'Listening TCP/IP host') do |host|
115
+ o.on('--host=HOST', 'Listening TCP/IP host') do |host|
107
116
  config[:host] = host
108
117
  end
118
+ o.on('--cookie=COOKIE', 'Set a cookie for connection') do |c|
119
+ config[:cookie] = c
120
+ end
109
121
 
110
122
  rdbg = 'rdbg'
111
123
 
@@ -133,6 +145,28 @@ module DEBUGGER__
133
145
  o.separator " '#{rdbg} -A path' tries to connect via UNIX domain socket with given path name."
134
146
  o.separator " '#{rdbg} -A port' tries to connect to localhost:port via TCP/IP."
135
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.'
136
170
  end
137
171
 
138
172
  opt.parse!(argv)
data/lib/debug/console.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  require_relative 'session'
2
+ return unless defined?(DEBUGGER__)
3
+
4
+ require 'io/console/size'
2
5
 
3
6
  module DEBUGGER__
4
- class UI_Console
7
+ class UI_Console < UI_Base
5
8
  def initialize
6
9
  end
7
10
 
@@ -9,6 +12,10 @@ module DEBUGGER__
9
12
  false
10
13
  end
11
14
 
15
+ def width
16
+ IO.console_size[1]
17
+ end
18
+
12
19
  def quit n
13
20
  exit n
14
21
  end
@@ -1,8 +1,9 @@
1
1
 
2
2
  module DEBUGGER__
3
3
  FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
4
- :has_return_value, :return_value, :show_line)
5
-
4
+ :has_return_value, :return_value,
5
+ :has_raised_exception, :raised_exception,
6
+ :show_line)
6
7
 
7
8
  # extend FrameInfo with debug.so
8
9
  if File.exist? File.join(__dir__, 'debug.so')
@@ -39,33 +40,73 @@ module DEBUGGER__
39
40
  end
40
41
  end
41
42
 
43
+ def name
44
+ # p frame_type: frame_type, self: self
45
+ case frame_type
46
+ when :block
47
+ level, block_loc, _args = block_identifier
48
+ "block in #{block_loc}#{level}"
49
+ when :method
50
+ ci, _args = method_identifier
51
+ "#{ci}"
52
+ when :c
53
+ c_identifier
54
+ when :other
55
+ other_identifier
56
+ end
57
+ end
58
+
42
59
  def file_lines
43
- SESSION.source(realpath || path)
60
+ SESSION.source(self.iseq)
44
61
  end
45
62
 
46
- def call_identifier_str
63
+ def frame_type
47
64
  if binding && iseq
48
65
  if iseq.type == :block
49
- if (argc = iseq.argc) > 0
50
- args = parameters_info iseq.locals[0...argc]
51
- args_str = "{|#{args}|}"
52
- end
53
-
54
- location.label.sub('block'){ "block#{args_str}" }
55
- elsif (callee = binding.eval('__callee__', __FILE__, __LINE__)) && (argc = iseq.argc) > 0
56
- args = parameters_info iseq.locals[0...argc]
57
- "#{klass_sig}#{callee}(#{args})"
66
+ :block
67
+ elsif callee
68
+ :method
58
69
  else
59
- location.label
70
+ :other
60
71
  end
61
72
  else
62
- "[C] #{klass_sig}#{location.base_label}"
73
+ :c
63
74
  end
64
75
  end
65
76
 
77
+ BLOCK_LABL_REGEXP = /\Ablock( \(\d+ levels\))* in (.+)\z/
78
+
79
+ def block_identifier
80
+ return unless frame_type == :block
81
+ args = parameters_info(iseq.argc)
82
+ _, level, block_loc = location.label.match(BLOCK_LABL_REGEXP).to_a
83
+ [level || "", block_loc, args]
84
+ end
85
+
86
+ def method_identifier
87
+ return unless frame_type == :method
88
+ args = parameters_info(iseq.argc)
89
+ ci = "#{klass_sig}#{callee}"
90
+ [ci, args]
91
+ end
92
+
93
+ def c_identifier
94
+ return unless frame_type == :c
95
+ "[C] #{klass_sig}#{location.base_label}"
96
+ end
97
+
98
+ def other_identifier
99
+ return unless frame_type == :other
100
+ location.label
101
+ end
102
+
103
+ def callee
104
+ @callee ||= binding&.eval('__callee__', __FILE__, __LINE__)
105
+ end
106
+
66
107
  def return_str
67
108
  if binding && iseq && has_return_value
68
- short_inspect(return_value)
109
+ DEBUGGER__.short_inspect(return_value)
69
110
  end
70
111
  end
71
112
 
@@ -75,31 +116,21 @@ module DEBUGGER__
75
116
 
76
117
  private
77
118
 
78
- SHORT_INSPECT_LENGTH = 40
79
-
80
- def short_inspect obj
81
- str = obj.inspect
82
- if str.length > SHORT_INSPECT_LENGTH
83
- str[0...SHORT_INSPECT_LENGTH] + '...'
84
- else
85
- str
86
- end
87
- end
88
-
89
119
  def get_singleton_class obj
90
120
  obj.singleton_class # TODO: don't use it
91
121
  rescue TypeError
92
122
  nil
93
123
  end
94
124
 
95
- def parameters_info vars
125
+ def parameters_info(argc)
126
+ vars = iseq.locals[0...argc]
96
127
  vars.map{|var|
97
128
  begin
98
- "#{var}=#{short_inspect(binding.local_variable_get(var))}"
129
+ { name: var, value: DEBUGGER__.short_inspect(binding.local_variable_get(var)) }
99
130
  rescue NameError, TypeError
100
131
  nil
101
132
  end
102
- }.compact.join(', ')
133
+ }.compact
103
134
  end
104
135
 
105
136
  def klass_sig
data/lib/debug/open.rb CHANGED
@@ -7,4 +7,6 @@
7
7
  #
8
8
 
9
9
  require_relative 'server'
10
+ return unless defined?(DEBUGGER__)
11
+
10
12
  DEBUGGER__.open
data/lib/debug/run.rb CHANGED
@@ -1,2 +1,4 @@
1
1
  require_relative 'console'
2
+ return unless defined?(DEBUGGER__)
3
+
2
4
  DEBUGGER__.console
data/lib/debug/server.rb CHANGED
@@ -1,9 +1,13 @@
1
1
  require 'socket'
2
+
2
3
  require_relative 'session'
4
+ return unless defined?(DEBUGGER__)
5
+
3
6
  require_relative 'config'
7
+ require_relative 'version'
4
8
 
5
9
  module DEBUGGER__
6
- class UI_ServerBase
10
+ class UI_ServerBase < UI_Base
7
11
  def initialize
8
12
  @sock = nil
9
13
  @accept_m = Mutex.new
@@ -12,13 +16,19 @@ module DEBUGGER__
12
16
  @q_msg = Queue.new
13
17
  @q_ans = Queue.new
14
18
  @unsent_messages = []
19
+ @width = 80
15
20
 
16
21
  @reader_thread = Thread.new do
22
+ # An error on this thread should break the system.
23
+ Thread.current.abort_on_exception = true
24
+
17
25
  accept do |server|
18
26
  DEBUGGER__.message "Connected."
19
27
 
20
28
  @accept_m.synchronize{
21
29
  @sock = server
30
+ greeting
31
+
22
32
  @accept_cv.signal
23
33
 
24
34
  # flush unsent messages
@@ -27,26 +37,13 @@ module DEBUGGER__
27
37
  }
28
38
  @unsent_messages.clear
29
39
  }
30
- @q_msg = Queue.new
31
- @q_ans = Queue.new
32
40
 
33
41
  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
42
+ process
49
43
  end
44
+
45
+ rescue => e
46
+ DEBUGGER__.message "ReaderThreadError: #{e}"
50
47
  ensure
51
48
  DEBUGGER__.message "Disconnected."
52
49
  @sock = nil
@@ -56,10 +53,64 @@ module DEBUGGER__
56
53
  end
57
54
  end
58
55
 
56
+ def greeting
57
+ case g = @sock.gets
58
+ when /^version:\s+(.+)\s+width: (\d+) cookie:\s+(.*)$/
59
+ v, w, c = $1, $2, $3
60
+ # TODO: protocol version
61
+ if v != VERSION
62
+ raise "Incompatible version (#{VERSION} client:#{$1})"
63
+ end
64
+
65
+ cookie = CONFIG[:cookie]
66
+ if cookie && cookie != c
67
+ raise "Cookie mismatch (#{$2.inspect} was sent)"
68
+ end
69
+
70
+ @width = w.to_i
71
+
72
+ when /^Content-Length: (\d+)/
73
+ require_relative 'server_dap'
74
+
75
+ raise unless @sock.read(2) == "\r\n"
76
+ self.extend(UI_DAP)
77
+ dap_setup @sock.read($1.to_i)
78
+ else
79
+ raise "Greeting message error: #{g}"
80
+ end
81
+ end
82
+
83
+ def process
84
+ @q_msg = Queue.new
85
+ @q_ans = Queue.new
86
+
87
+ pause
88
+
89
+ while line = @sock.gets
90
+ case line
91
+ when /\Apause/
92
+ pause
93
+ when /\Acommand ?(.+)/
94
+ @q_msg << $1
95
+ when /\Aanswer (.*)/
96
+ @q_ans << $1
97
+ when /\Awidth (.+)/
98
+ @width = $1.to_i
99
+ else
100
+ STDERR.puts "unsupported: #{line}"
101
+ exit!
102
+ end
103
+ end
104
+ end
105
+
59
106
  def remote?
60
107
  true
61
108
  end
62
109
 
110
+ def width
111
+ @width
112
+ end
113
+
63
114
  def setup_interrupt
64
115
  prev_handler = trap(:SIGINT) do
65
116
  # $stderr.puts "trapped SIGINT"
@@ -158,7 +209,7 @@ module DEBUGGER__
158
209
 
159
210
  class UI_TcpServer < UI_ServerBase
160
211
  def initialize host: nil, port: nil
161
- @host = host || ::DEBUGGER__::CONFIG[:host] || 'localhost'
212
+ @host = host || ::DEBUGGER__::CONFIG[:host] || '127.0.0.1'
162
213
  @port = port || begin
163
214
  port_str = ::DEBUGGER__::CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
164
215
  if /\A\d+\z/ !~ port_str
@@ -207,6 +258,8 @@ module DEBUGGER__
207
258
  Socket.unix_server_loop @sock_path do |sock, client|
208
259
  @client_addr = client
209
260
  yield sock
261
+ ensure
262
+ sock.close
210
263
  end
211
264
  end
212
265
  end