debug 1.2.2 → 1.3.1
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.
- checksums.yaml +4 -4
- data/README.md +116 -5
- data/ext/debug/debug.c +2 -1
- data/ext/debug/extconf.rb +2 -0
- data/lib/debug/breakpoint.rb +2 -2
- data/lib/debug/client.rb +69 -46
- data/lib/debug/config.rb +74 -21
- data/lib/debug/console.rb +92 -25
- data/lib/debug/local.rb +22 -1
- data/lib/debug/prelude.rb +49 -0
- data/lib/debug/server.rb +186 -25
- data/lib/debug/server_cdp.rb +412 -0
- data/lib/debug/server_dap.rb +53 -23
- data/lib/debug/session.rb +387 -120
- data/lib/debug/thread_client.rb +4 -2
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +95 -1
- metadata +8 -6
data/lib/debug/console.rb
CHANGED
@@ -31,6 +31,7 @@ module DEBUGGER__
|
|
31
31
|
end if SIGWINCH_SUPPORTED
|
32
32
|
|
33
33
|
def readline_setup prompt
|
34
|
+
load_history_if_not_loaded
|
34
35
|
commands = DEBUGGER__.commands
|
35
36
|
|
36
37
|
Reline.completion_proc = -> given do
|
@@ -87,40 +88,106 @@ module DEBUGGER__
|
|
87
88
|
Reline.readmultiline(prompt, true){ true }
|
88
89
|
end
|
89
90
|
|
91
|
+
def history
|
92
|
+
Reline::HISTORY
|
93
|
+
end
|
94
|
+
|
90
95
|
rescue LoadError
|
91
|
-
|
92
|
-
|
96
|
+
begin
|
97
|
+
require 'readline.so'
|
98
|
+
|
99
|
+
def readline_setup
|
100
|
+
load_history_if_not_loaded
|
101
|
+
commands = DEBUGGER__.commands
|
102
|
+
|
103
|
+
Readline.completion_proc = proc{|given|
|
104
|
+
buff = Readline.line_buffer
|
105
|
+
Readline.completion_append_character= ' '
|
106
|
+
|
107
|
+
if /\s/ =~ buff # second parameters
|
108
|
+
given = File.expand_path(given + 'a').sub(/a\z/, '')
|
109
|
+
files = Dir.glob(given + '*')
|
110
|
+
if files.size == 1 && File.directory?(files.first)
|
111
|
+
Readline.completion_append_character= '/'
|
112
|
+
end
|
113
|
+
files
|
114
|
+
else
|
115
|
+
commands.keys.grep(/\A#{given}/)
|
116
|
+
end
|
117
|
+
}
|
118
|
+
end
|
93
119
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
120
|
+
def readline prompt
|
121
|
+
readline_setup
|
122
|
+
Readline.readline(prompt, true)
|
123
|
+
end
|
98
124
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
125
|
+
def history
|
126
|
+
Readline::HISTORY
|
127
|
+
end
|
128
|
+
|
129
|
+
rescue LoadError
|
130
|
+
def readline prompt
|
131
|
+
print prompt
|
132
|
+
gets
|
133
|
+
end
|
134
|
+
|
135
|
+
def history
|
136
|
+
nil
|
137
|
+
end
|
110
138
|
end
|
139
|
+
end
|
111
140
|
|
112
|
-
|
113
|
-
|
114
|
-
|
141
|
+
def history_file
|
142
|
+
CONFIG[:history_file] || File.expand_path("~/.rdbg_history")
|
143
|
+
end
|
144
|
+
|
145
|
+
FH = "# Today's OMIKUJI: "
|
146
|
+
|
147
|
+
def read_history_file
|
148
|
+
if history && File.exists?(path = history_file)
|
149
|
+
f = (['', 'DAI-', 'CHU-', 'SHO-'].map{|e| e+'KICHI'}+['KYO']).sample
|
150
|
+
["#{FH}#{f}".dup] + File.readlines(path)
|
151
|
+
else
|
152
|
+
[]
|
115
153
|
end
|
154
|
+
end
|
116
155
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
156
|
+
def initialize
|
157
|
+
@init_history_lines = nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def load_history_if_not_loaded
|
161
|
+
return if @init_history_lines
|
162
|
+
|
163
|
+
@init_history_lines = load_history
|
164
|
+
end
|
165
|
+
|
166
|
+
def deactivate
|
167
|
+
if history && @init_history_lines
|
168
|
+
added_records = history.to_a[@init_history_lines .. -1]
|
169
|
+
path = history_file
|
170
|
+
max = CONFIG[:save_history] || 10_000
|
171
|
+
|
172
|
+
if !added_records.empty? && !path.empty?
|
173
|
+
orig_records = read_history_file
|
174
|
+
open(history_file, 'w'){|f|
|
175
|
+
(orig_records + added_records).last(max).each{|line|
|
176
|
+
if !line.start_with?(FH) && !line.strip.empty?
|
177
|
+
f.puts line.strip
|
178
|
+
end
|
179
|
+
}
|
180
|
+
}
|
181
|
+
end
|
121
182
|
end
|
122
183
|
end
|
184
|
+
|
185
|
+
def load_history
|
186
|
+
read_history_file.count{|line|
|
187
|
+
line.strip!
|
188
|
+
history << line unless line.empty?
|
189
|
+
}
|
123
190
|
end
|
124
|
-
end
|
191
|
+
end # class Console
|
125
192
|
end
|
126
193
|
|
data/lib/debug/local.rb
CHANGED
@@ -26,8 +26,11 @@ module DEBUGGER__
|
|
26
26
|
|
27
27
|
def deactivate
|
28
28
|
if SESSION.intercept_trap_sigint?
|
29
|
-
|
29
|
+
prev = SESSION.intercept_trap_sigint_end
|
30
|
+
trap(:SIGINT, prev)
|
30
31
|
end
|
32
|
+
|
33
|
+
@console.deactivate
|
31
34
|
end
|
32
35
|
|
33
36
|
def width
|
@@ -83,6 +86,24 @@ module DEBUGGER__
|
|
83
86
|
trap(:INT, prev_handler)
|
84
87
|
end
|
85
88
|
end
|
89
|
+
|
90
|
+
def after_fork_parent
|
91
|
+
parent_pid = Process.pid
|
92
|
+
|
93
|
+
at_exit{
|
94
|
+
SESSION.intercept_trap_sigint_end
|
95
|
+
trap(:SIGINT, :IGNORE)
|
96
|
+
|
97
|
+
if Process.pid == parent_pid
|
98
|
+
# only check child process from its parent
|
99
|
+
begin
|
100
|
+
# wait for all child processes to keep terminal
|
101
|
+
loop{ Process.waitpid }
|
102
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
103
|
+
end
|
104
|
+
end
|
105
|
+
}
|
106
|
+
end
|
86
107
|
end
|
87
108
|
end
|
88
109
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
return if defined?(::DEBUGGER__)
|
4
|
+
|
5
|
+
#
|
6
|
+
# put the following line in .bash_profile
|
7
|
+
# export RUBYOPT="-r .../debug/prelude $(RUBYOPT)"
|
8
|
+
#
|
9
|
+
module Kernel
|
10
|
+
def debugger(*a, up_level: 0, **kw)
|
11
|
+
begin
|
12
|
+
require_relative 'version'
|
13
|
+
cur_version = ::DEBUGGER__::VERSION
|
14
|
+
require_relative 'frame_info'
|
15
|
+
|
16
|
+
if !defined?(::DEBUGGER__::SO_VERSION) || ::DEBUGGER__::VERSION != ::DEBUGGER__::SO_VERSION
|
17
|
+
::Object.send(:remove_const, :DEBUGGER__)
|
18
|
+
raise LoadError
|
19
|
+
end
|
20
|
+
require_relative 'session'
|
21
|
+
up_level += 1
|
22
|
+
rescue LoadError
|
23
|
+
$LOADED_FEATURES.delete_if{|e|
|
24
|
+
e.start_with?(__dir__) || e.end_with?('debug/debug.so')
|
25
|
+
}
|
26
|
+
require 'debug/session'
|
27
|
+
require 'debug/version'
|
28
|
+
::DEBUGGER__.info "Can not activate debug #{cur_version} specified by debug/prelude.rb. Activate debug #{DEBUGGER__::VERSION} instead."
|
29
|
+
up_level += 1
|
30
|
+
end
|
31
|
+
|
32
|
+
::DEBUGGER__::start no_sigint_hook: true, nonstop: true
|
33
|
+
|
34
|
+
begin
|
35
|
+
debugger(*a, up_level: up_level, **kw)
|
36
|
+
self
|
37
|
+
rescue ArgumentError # for 1.2.4 and earlier
|
38
|
+
debugger(*a, **kw)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
alias bb debugger if ENV['RUBY_DEBUG_BB']
|
44
|
+
end
|
45
|
+
|
46
|
+
class Binding
|
47
|
+
alias break debugger
|
48
|
+
alias b debugger
|
49
|
+
end
|
data/lib/debug/server.rb
CHANGED
@@ -15,6 +15,8 @@ module DEBUGGER__
|
|
15
15
|
@q_ans = nil
|
16
16
|
@unsent_messages = []
|
17
17
|
@width = 80
|
18
|
+
@repl = true
|
19
|
+
@session = nil
|
18
20
|
end
|
19
21
|
|
20
22
|
class Terminate < StandardError
|
@@ -22,6 +24,7 @@ module DEBUGGER__
|
|
22
24
|
|
23
25
|
def deactivate
|
24
26
|
@reader_thread.raise Terminate
|
27
|
+
@reader_thread.join
|
25
28
|
end
|
26
29
|
|
27
30
|
def accept
|
@@ -36,9 +39,11 @@ module DEBUGGER__
|
|
36
39
|
end
|
37
40
|
|
38
41
|
def activate session, on_fork: false
|
42
|
+
@session = session
|
39
43
|
@reader_thread = Thread.new do
|
40
44
|
# An error on this thread should break the system.
|
41
45
|
Thread.current.abort_on_exception = true
|
46
|
+
Thread.current.name = 'DEBUGGER__::Server::reader'
|
42
47
|
|
43
48
|
accept do |server, already_connected: false|
|
44
49
|
DEBUGGER__.warn "Connected."
|
@@ -52,7 +57,7 @@ module DEBUGGER__
|
|
52
57
|
# flush unsent messages
|
53
58
|
@unsent_messages.each{|m|
|
54
59
|
@sock.puts m
|
55
|
-
}
|
60
|
+
} if @repl
|
56
61
|
@unsent_messages.clear
|
57
62
|
|
58
63
|
@q_msg = Queue.new
|
@@ -68,6 +73,7 @@ module DEBUGGER__
|
|
68
73
|
raise # should catch at outer scope
|
69
74
|
rescue => e
|
70
75
|
DEBUGGER__.warn "ReaderThreadError: #{e}"
|
76
|
+
pp e.backtrace
|
71
77
|
ensure
|
72
78
|
DEBUGGER__.warn "Disconnected."
|
73
79
|
@sock = nil
|
@@ -103,23 +109,58 @@ module DEBUGGER__
|
|
103
109
|
|
104
110
|
raise unless @sock.read(2) == "\r\n"
|
105
111
|
self.extend(UI_DAP)
|
112
|
+
@repl = false
|
106
113
|
dap_setup @sock.read($1.to_i)
|
114
|
+
when /^GET \/ HTTP\/1.1/
|
115
|
+
require_relative 'server_cdp'
|
116
|
+
|
117
|
+
self.extend(UI_CDP)
|
118
|
+
@repl = false
|
119
|
+
@web_sock = UI_CDP::WebSocket.new(@sock)
|
120
|
+
@web_sock.handshake
|
107
121
|
else
|
108
122
|
raise "Greeting message error: #{g}"
|
109
123
|
end
|
110
124
|
end
|
111
125
|
|
112
126
|
def process
|
113
|
-
while
|
127
|
+
while true
|
128
|
+
DEBUGGER__.info "sleep IO.select"
|
129
|
+
r = IO.select([@sock])
|
130
|
+
DEBUGGER__.info "wakeup IO.select"
|
131
|
+
|
132
|
+
line = @session.process_group.sync do
|
133
|
+
unless IO.select([@sock], nil, nil, 0)
|
134
|
+
DEBUGGER__.info "UI_Server can not read"
|
135
|
+
break :can_not_read
|
136
|
+
end
|
137
|
+
@sock.gets&.chomp.tap{|line|
|
138
|
+
DEBUGGER__.info "UI_Server received: #{line}"
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
next if line == :can_not_read
|
143
|
+
|
114
144
|
case line
|
115
145
|
when /\Apause/
|
116
146
|
pause
|
117
|
-
when /\Acommand ?(.+)/
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
147
|
+
when /\Acommand (\d+) (\d+) ?(.+)/
|
148
|
+
raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
|
149
|
+
|
150
|
+
if $1.to_i == Process.pid
|
151
|
+
@width = $2.to_i
|
152
|
+
@q_msg << $3
|
153
|
+
else
|
154
|
+
raise "pid:#{Process.pid} but get #{line}"
|
155
|
+
end
|
156
|
+
when /\Aanswer (\d+) (.*)/
|
157
|
+
raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
|
158
|
+
|
159
|
+
if $1.to_i == Process.pid
|
160
|
+
@q_ans << $2
|
161
|
+
else
|
162
|
+
raise "pid:#{Process.pid} but get #{line}"
|
163
|
+
end
|
123
164
|
else
|
124
165
|
STDERR.puts "unsupported: #{line}"
|
125
166
|
exit!
|
@@ -135,6 +176,21 @@ module DEBUGGER__
|
|
135
176
|
@width
|
136
177
|
end
|
137
178
|
|
179
|
+
def sigurg_overridden? prev_handler
|
180
|
+
case prev_handler
|
181
|
+
when "SYSTEM_DEFAULT"
|
182
|
+
false
|
183
|
+
when Proc
|
184
|
+
if prev_handler.source_location[0] == __FILE__
|
185
|
+
false
|
186
|
+
else
|
187
|
+
true
|
188
|
+
end
|
189
|
+
else
|
190
|
+
true
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
138
194
|
def setup_interrupt
|
139
195
|
prev_handler = trap(:SIGURG) do
|
140
196
|
# $stderr.puts "trapped SIGINT"
|
@@ -148,8 +204,8 @@ module DEBUGGER__
|
|
148
204
|
end
|
149
205
|
end
|
150
206
|
|
151
|
-
if prev_handler
|
152
|
-
DEBUGGER__.warn "SIGURG handler is
|
207
|
+
if sigurg_overridden?(prev_handler)
|
208
|
+
DEBUGGER__.warn "SIGURG handler is overridden by the debugger."
|
153
209
|
end
|
154
210
|
yield
|
155
211
|
ensure
|
@@ -191,7 +247,7 @@ module DEBUGGER__
|
|
191
247
|
|
192
248
|
def ask prompt
|
193
249
|
sock do |s|
|
194
|
-
s.puts "ask #{prompt}"
|
250
|
+
s.puts "ask #{Process.pid} #{prompt}"
|
195
251
|
@q_ans.pop
|
196
252
|
end
|
197
253
|
end
|
@@ -220,13 +276,23 @@ module DEBUGGER__
|
|
220
276
|
|
221
277
|
def readline prompt
|
222
278
|
input = (sock do |s|
|
223
|
-
|
279
|
+
if @repl
|
280
|
+
raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
|
281
|
+
line = "input #{Process.pid}"
|
282
|
+
DEBUGGER__.info "send: #{line}"
|
283
|
+
s.puts line
|
284
|
+
end
|
224
285
|
sleep 0.01 until @q_msg
|
286
|
+
@q_msg.pop.tap{|msg|
|
287
|
+
DEBUGGER__.info "readline: #{msg.inspect}"
|
288
|
+
}
|
289
|
+
end || 'continue')
|
225
290
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
291
|
+
if input.is_a?(String)
|
292
|
+
input.strip
|
293
|
+
else
|
294
|
+
input
|
295
|
+
end
|
230
296
|
end
|
231
297
|
|
232
298
|
def pause
|
@@ -240,10 +306,15 @@ module DEBUGGER__
|
|
240
306
|
s.puts "quit"
|
241
307
|
end
|
242
308
|
end
|
309
|
+
|
310
|
+
def after_fork_parent
|
311
|
+
# do nothing
|
312
|
+
end
|
243
313
|
end
|
244
314
|
|
245
315
|
class UI_TcpServer < UI_ServerBase
|
246
316
|
def initialize host: nil, port: nil
|
317
|
+
@addr = nil
|
247
318
|
@host = host || CONFIG[:host] || '127.0.0.1'
|
248
319
|
@port = port || begin
|
249
320
|
port_str = CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
|
@@ -263,7 +334,24 @@ module DEBUGGER__
|
|
263
334
|
|
264
335
|
begin
|
265
336
|
Socket.tcp_server_sockets @host, @port do |socks|
|
266
|
-
|
337
|
+
addr = socks[0].local_address.inspect_sockaddr # Change this part if `socks` are multiple.
|
338
|
+
rdbg = File.expand_path('../../exe/rdbg', __dir__)
|
339
|
+
|
340
|
+
DEBUGGER__.warn "Debugger can attach via TCP/IP (#{addr})"
|
341
|
+
DEBUGGER__.info <<~EOS
|
342
|
+
With rdbg, use the following command line:
|
343
|
+
#
|
344
|
+
# #{rdbg} --attach #{addr.split(':').join(' ')}
|
345
|
+
#
|
346
|
+
EOS
|
347
|
+
|
348
|
+
DEBUGGER__.warn <<~EOS if CONFIG[:open_frontend] == 'chrome'
|
349
|
+
With Chrome browser, type the following URL in the address-bar:
|
350
|
+
|
351
|
+
devtools://devtools/bundled/inspector.html?ws=#{addr}
|
352
|
+
|
353
|
+
EOS
|
354
|
+
|
267
355
|
Socket.accept_loop(socks) do |sock, client|
|
268
356
|
@client_addr = client
|
269
357
|
yield @sock_for_fork = sock
|
@@ -298,6 +386,64 @@ module DEBUGGER__
|
|
298
386
|
super()
|
299
387
|
end
|
300
388
|
|
389
|
+
def vscode_setup
|
390
|
+
require 'tmpdir'
|
391
|
+
require 'json'
|
392
|
+
require 'fileutils'
|
393
|
+
|
394
|
+
dir = Dir.mktmpdir("ruby-debug-vscode-")
|
395
|
+
at_exit{
|
396
|
+
FileUtils.rm_rf dir
|
397
|
+
}
|
398
|
+
Dir.chdir(dir) do
|
399
|
+
Dir.mkdir('.vscode')
|
400
|
+
open('README.rb', 'w'){|f|
|
401
|
+
f.puts <<~MSG
|
402
|
+
# Wait for starting the attaching to the Ruby process
|
403
|
+
# This file will be removed at the end of the debuggee process.
|
404
|
+
#
|
405
|
+
# Note that vscode-rdbg extension is needed. Please install if you don't have.
|
406
|
+
MSG
|
407
|
+
}
|
408
|
+
open('.vscode/launch.json', 'w'){|f|
|
409
|
+
f.puts JSON.pretty_generate({
|
410
|
+
version: '0.2.0',
|
411
|
+
configurations: [
|
412
|
+
{
|
413
|
+
type: "rdbg",
|
414
|
+
name: "Attach with rdbg",
|
415
|
+
request: "attach",
|
416
|
+
rdbgPath: File.expand_path('../../exe/rdbg', __dir__),
|
417
|
+
debugPort: @sock_path,
|
418
|
+
autoAttach: true,
|
419
|
+
}
|
420
|
+
]
|
421
|
+
})
|
422
|
+
}
|
423
|
+
end
|
424
|
+
|
425
|
+
cmds = ['code', "#{dir}/", "#{dir}/README.rb"]
|
426
|
+
cmdline = cmds.join(' ')
|
427
|
+
ssh_cmdline = "code --remote ssh-remote+[SSH hostname] #{dir}/ #{dir}/README.rb"
|
428
|
+
|
429
|
+
STDERR.puts "Launching: #{cmdline}"
|
430
|
+
env = ENV.delete_if{|k, h| /RUBY/ =~ k}.to_h
|
431
|
+
|
432
|
+
unless system(env, *cmds)
|
433
|
+
DEBUGGER__.warn <<~MESSAGE
|
434
|
+
Can not invoke the command.
|
435
|
+
Use the command-line on your terminal (with modification if you need).
|
436
|
+
|
437
|
+
#{cmdline}
|
438
|
+
|
439
|
+
If your application is running on a SSH remote host, please try:
|
440
|
+
|
441
|
+
#{ssh_cmdline}
|
442
|
+
|
443
|
+
MESSAGE
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
301
447
|
def accept
|
302
448
|
super # for fork
|
303
449
|
|
@@ -310,14 +456,29 @@ module DEBUGGER__
|
|
310
456
|
end
|
311
457
|
|
312
458
|
::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
459
|
+
vscode_setup if CONFIG[:open_frontend] == 'vscode'
|
460
|
+
|
461
|
+
begin
|
462
|
+
Socket.unix_server_loop @sock_path do |sock, client|
|
463
|
+
@sock_for_fork = sock
|
464
|
+
@client_addr = client
|
465
|
+
|
466
|
+
yield sock
|
467
|
+
ensure
|
468
|
+
sock.close
|
469
|
+
@sock_for_fork = nil
|
470
|
+
end
|
471
|
+
rescue Errno::ECONNREFUSED => _e
|
472
|
+
::DEBUGGER__.warn "#{_e.message} (socket path: #{@sock_path})"
|
473
|
+
|
474
|
+
if @sock_path.start_with? Config.unix_domain_socket_tmpdir
|
475
|
+
# try on homedir
|
476
|
+
@sock_path = Config.create_unix_domain_socket_name(unix_domain_socket_homedir)
|
477
|
+
::DEBUGGER__.warn "retry with #{@sock_path}"
|
478
|
+
retry
|
479
|
+
else
|
480
|
+
raise
|
481
|
+
end
|
321
482
|
end
|
322
483
|
end
|
323
484
|
end
|