debug 1.0.0.beta4 → 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,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