debug 1.0.0.beta2 → 1.0.0.beta7

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/console.rb CHANGED
@@ -1,14 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'session'
4
+ return unless defined?(DEBUGGER__)
5
+
6
+ require 'io/console/size'
2
7
 
3
8
  module DEBUGGER__
4
- class UI_Console
9
+ class UI_Console < UI_Base
5
10
  def initialize
11
+ unless CONFIG[:no_sigint_hook]
12
+ @prev_handler = trap(:SIGINT){
13
+ ThreadClient.current.on_trap :SIGINT
14
+ }
15
+ end
16
+ end
17
+
18
+ def close
19
+ if @prev_handler
20
+ trap(:SIGINT, @prev_handler)
21
+ end
6
22
  end
7
23
 
8
24
  def remote?
9
25
  false
10
26
  end
11
27
 
28
+ def width
29
+ if (w = IO.console_size[1]) == 0 # for tests PTY
30
+ 80
31
+ else
32
+ w
33
+ end
34
+ end
35
+
12
36
  def quit n
13
37
  exit n
14
38
  end
@@ -16,7 +40,7 @@ module DEBUGGER__
16
40
  def ask prompt
17
41
  setup_interrupt do
18
42
  print prompt
19
- (gets || '').strip
43
+ ($stdin.gets || '').strip
20
44
  end
21
45
  end
22
46
 
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DEBUGGER__
4
+ FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
5
+ :has_return_value, :return_value,
6
+ :has_raised_exception, :raised_exception,
7
+ :show_line)
8
+
9
+ # extend FrameInfo with debug.so
10
+ if File.exist? File.join(__dir__, 'debug.so')
11
+ require_relative 'debug.so'
12
+ else
13
+ require_relative 'debug'
14
+ end
15
+
16
+ class FrameInfo
17
+ HOME = ENV['HOME'] ? (ENV['HOME'] + '/') : nil
18
+
19
+ def path
20
+ location.path
21
+ end
22
+
23
+ def realpath
24
+ location.absolute_path
25
+ end
26
+
27
+ def pretty_path
28
+ use_short_path = ::DEBUGGER__::CONFIG[:use_short_path]
29
+
30
+ case
31
+ when use_short_path && path.start_with?(dir = ::DEBUGGER__::CONFIG["rubylibdir"] + '/')
32
+ path.sub(dir, '$(rubylibdir)/')
33
+ when use_short_path && Gem.path.any? do |gp|
34
+ path.start_with?(dir = gp + '/gems/')
35
+ end
36
+ path.sub(dir, '$(Gem)/')
37
+ when HOME && path.start_with?(HOME)
38
+ path.sub(HOME, '~/')
39
+ else
40
+ path
41
+ end
42
+ end
43
+
44
+ def name
45
+ # p frame_type: frame_type, self: self
46
+ case frame_type
47
+ when :block
48
+ level, block_loc, _args = block_identifier
49
+ "block in #{block_loc}#{level}"
50
+ when :method
51
+ ci, _args = method_identifier
52
+ "#{ci}"
53
+ when :c
54
+ c_identifier
55
+ when :other
56
+ other_identifier
57
+ end
58
+ end
59
+
60
+ def file_lines
61
+ SESSION.source(self.iseq)
62
+ end
63
+
64
+ def frame_type
65
+ if binding && iseq
66
+ if iseq.type == :block
67
+ :block
68
+ elsif callee
69
+ :method
70
+ else
71
+ :other
72
+ end
73
+ else
74
+ :c
75
+ end
76
+ end
77
+
78
+ BLOCK_LABL_REGEXP = /\Ablock( \(\d+ levels\))* in (.+)\z/
79
+
80
+ def block_identifier
81
+ return unless frame_type == :block
82
+ args = parameters_info(iseq.argc)
83
+ _, level, block_loc = location.label.match(BLOCK_LABL_REGEXP).to_a
84
+ [level || "", block_loc, args]
85
+ end
86
+
87
+ def method_identifier
88
+ return unless frame_type == :method
89
+ args = parameters_info(iseq.argc)
90
+ ci = "#{klass_sig}#{callee}"
91
+ [ci, args]
92
+ end
93
+
94
+ def c_identifier
95
+ return unless frame_type == :c
96
+ "[C] #{klass_sig}#{location.base_label}"
97
+ end
98
+
99
+ def other_identifier
100
+ return unless frame_type == :other
101
+ location.label
102
+ end
103
+
104
+ def callee
105
+ @callee ||= binding&.eval('__callee__', __FILE__, __LINE__)
106
+ end
107
+
108
+ def return_str
109
+ if binding && iseq && has_return_value
110
+ DEBUGGER__.short_inspect(return_value)
111
+ end
112
+ end
113
+
114
+ def location_str
115
+ "#{pretty_path}:#{location.lineno}"
116
+ end
117
+
118
+ private
119
+
120
+ def get_singleton_class obj
121
+ obj.singleton_class # TODO: don't use it
122
+ rescue TypeError
123
+ nil
124
+ end
125
+
126
+ def parameters_info(argc)
127
+ vars = iseq.locals[0...argc]
128
+ vars.map{|var|
129
+ begin
130
+ { name: var, value: DEBUGGER__.short_inspect(binding.local_variable_get(var)) }
131
+ rescue NameError, TypeError
132
+ nil
133
+ end
134
+ }.compact
135
+ end
136
+
137
+ def klass_sig
138
+ if self.class == get_singleton_class(self.self)
139
+ "#{self.self}."
140
+ else
141
+ "#{self.class}#"
142
+ end
143
+ end
144
+ end
145
+ end
data/lib/debug/open.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Open the door for the debugger to connect.
3
4
  # Users can connect to debuggee program with "rdbg --attach" option.
@@ -7,4 +8,6 @@
7
8
  #
8
9
 
9
10
  require_relative 'server'
11
+ return unless defined?(DEBUGGER__)
12
+
10
13
  DEBUGGER__.open
data/lib/debug/run.rb CHANGED
@@ -1,2 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'console'
2
- DEBUGGER__.console
4
+ return unless defined?(DEBUGGER__)
5
+ DEBUGGER__.start
data/lib/debug/server.rb CHANGED
@@ -1,24 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'socket'
4
+
2
5
  require_relative 'session'
6
+ return unless defined?(DEBUGGER__)
7
+
3
8
  require_relative 'config'
9
+ require_relative 'version'
4
10
 
5
11
  module DEBUGGER__
6
- class UI_ServerBase
12
+ class UI_ServerBase < UI_Base
7
13
  def initialize
8
14
  @sock = nil
9
15
  @accept_m = Mutex.new
10
16
  @accept_cv = ConditionVariable.new
11
17
  @client_addr = nil
12
- @q_msg = Queue.new
13
- @q_ans = Queue.new
18
+ @q_msg = nil
19
+ @q_ans = nil
14
20
  @unsent_messages = []
21
+ @width = 80
15
22
 
16
23
  @reader_thread = Thread.new do
24
+ # An error on this thread should break the system.
25
+ Thread.current.abort_on_exception = true
26
+
17
27
  accept do |server|
18
- DEBUGGER__.message "Connected."
28
+ DEBUGGER__.warn "Connected."
19
29
 
20
30
  @accept_m.synchronize{
21
31
  @sock = server
32
+ greeting
33
+
22
34
  @accept_cv.signal
23
35
 
24
36
  # flush unsent messages
@@ -26,32 +38,71 @@ module DEBUGGER__
26
38
  @sock.puts m
27
39
  }
28
40
  @unsent_messages.clear
41
+
42
+ @q_msg = Queue.new
43
+ @q_ans = Queue.new
29
44
  }
30
- @q_msg = Queue.new
31
- @q_ans = Queue.new
32
45
 
33
46
  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
47
+ process
49
48
  end
49
+
50
+ rescue => e
51
+ DEBUGGER__.warn "ReaderThreadError: #{e}", :error
50
52
  ensure
51
- DEBUGGER__.message "Disconnected."
53
+ DEBUGGER__.warn "Disconnected."
52
54
  @sock = nil
53
55
  @q_msg.close
56
+ @q_msg = nil
54
57
  @q_ans.close
58
+ @q_ans = nil
59
+ end
60
+ end
61
+ end
62
+
63
+ def greeting
64
+ case g = @sock.gets
65
+ when /^version:\s+(.+)\s+width: (\d+) cookie:\s+(.*)$/
66
+ v, w, c = $1, $2, $3
67
+ # TODO: protocol version
68
+ if v != VERSION
69
+ raise "Incompatible version (#{VERSION} client:#{$1})"
70
+ end
71
+
72
+ cookie = CONFIG[:cookie]
73
+ if cookie && cookie != c
74
+ raise "Cookie mismatch (#{$2.inspect} was sent)"
75
+ end
76
+
77
+ @width = w.to_i
78
+
79
+ when /^Content-Length: (\d+)/
80
+ require_relative 'server_dap'
81
+
82
+ raise unless @sock.read(2) == "\r\n"
83
+ self.extend(UI_DAP)
84
+ dap_setup @sock.read($1.to_i)
85
+ else
86
+ raise "Greeting message error: #{g}"
87
+ end
88
+ end
89
+
90
+ def process
91
+ pause
92
+
93
+ while line = @sock.gets
94
+ case line
95
+ when /\Apause/
96
+ pause
97
+ when /\Acommand ?(.+)/
98
+ @q_msg << $1
99
+ when /\Aanswer (.*)/
100
+ @q_ans << $1
101
+ when /\Awidth (.+)/
102
+ @width = $1.to_i
103
+ else
104
+ STDERR.puts "unsupported: #{line}"
105
+ exit!
55
106
  end
56
107
  end
57
108
  end
@@ -60,6 +111,10 @@ module DEBUGGER__
60
111
  true
61
112
  end
62
113
 
114
+ def width
115
+ @width
116
+ end
117
+
63
118
  def setup_interrupt
64
119
  prev_handler = trap(:SIGINT) do
65
120
  # $stderr.puts "trapped SIGINT"
@@ -90,12 +145,20 @@ module DEBUGGER__
90
145
  if s = @sock # already connection
91
146
  # ok
92
147
  elsif skip == true # skip process
93
- return yield nil
148
+ no_sock = true
149
+ r = @accept_m.synchronize do
150
+ if @sock
151
+ no_sock = false
152
+ else
153
+ yield nil
154
+ end
155
+ end
156
+ return r if no_sock
94
157
  else # wait for connection
95
158
  until s = @sock
96
159
  @accept_m.synchronize{
97
160
  unless @sock
98
- DEBUGGER__.message "wait for debuger connection..."
161
+ DEBUGGER__.warn "wait for debuger connection..."
99
162
  @accept_cv.wait(@accept_m)
100
163
  end
101
164
  }
@@ -139,6 +202,7 @@ module DEBUGGER__
139
202
  def readline
140
203
  (sock do |s|
141
204
  s.puts "input"
205
+ sleep 0.01 until @q_msg
142
206
  @q_msg.pop
143
207
  end || 'continue').strip
144
208
  end
@@ -158,7 +222,7 @@ module DEBUGGER__
158
222
 
159
223
  class UI_TcpServer < UI_ServerBase
160
224
  def initialize host: nil, port: nil
161
- @host = host || ::DEBUGGER__::CONFIG[:host] || 'localhost'
225
+ @host = host || ::DEBUGGER__::CONFIG[:host] || '127.0.0.1'
162
226
  @port = port || begin
163
227
  port_str = ::DEBUGGER__::CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
164
228
  if /\A\d+\z/ !~ port_str
@@ -173,7 +237,7 @@ module DEBUGGER__
173
237
 
174
238
  def accept
175
239
  Socket.tcp_server_sockets @host, @port do |socks|
176
- ::DEBUGGER__.message "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
240
+ ::DEBUGGER__.warn "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
177
241
  Socket.accept_loop(socks) do |sock, client|
178
242
  @client_addr = client
179
243
  yield sock
@@ -187,40 +251,29 @@ module DEBUGGER__
187
251
  end
188
252
 
189
253
  class UI_UnixDomainServer < UI_ServerBase
190
- def initialize sock_dir: nil
254
+ def initialize sock_dir: nil, sock_path: nil
255
+ @sock_path = sock_path
191
256
  @sock_dir = sock_dir || DEBUGGER__.unix_domain_socket_dir
192
257
 
193
258
  super()
194
259
  end
195
260
 
196
261
  def accept
197
- @file = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
262
+ case
263
+ when @sock_path
264
+ when sp = ::DEBUGGER__::CONFIG[:sock_path]
265
+ @sock_path = sp
266
+ else
267
+ @sock_path = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
268
+ end
198
269
 
199
- ::DEBUGGER__.message "Debugger can attach via UNIX domain socket (#{@file})"
200
- Socket.unix_server_loop @file do |sock, client|
270
+ ::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
271
+ Socket.unix_server_loop @sock_path do |sock, client|
201
272
  @client_addr = client
202
273
  yield sock
274
+ ensure
275
+ sock.close
203
276
  end
204
277
  end
205
278
  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
226
279
  end