debug 1.2.3 → 1.3.2
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/CONTRIBUTING.md +9 -11
- data/README.md +116 -5
- data/ext/debug/debug.c +3 -2
- data/ext/debug/extconf.rb +2 -0
- data/lib/debug/client.rb +39 -17
- data/lib/debug/config.rb +66 -30
- 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 +188 -25
- data/lib/debug/server_cdp.rb +410 -0
- data/lib/debug/server_dap.rb +53 -23
- data/lib/debug/session.rb +388 -121
- 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,60 @@ 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
|
+
CONFIG.set_config no_color: true
|
120
|
+
|
121
|
+
@web_sock = UI_CDP::WebSocket.new(@sock)
|
122
|
+
@web_sock.handshake
|
107
123
|
else
|
108
124
|
raise "Greeting message error: #{g}"
|
109
125
|
end
|
110
126
|
end
|
111
127
|
|
112
128
|
def process
|
113
|
-
while
|
129
|
+
while true
|
130
|
+
DEBUGGER__.info "sleep IO.select"
|
131
|
+
r = IO.select([@sock])
|
132
|
+
DEBUGGER__.info "wakeup IO.select"
|
133
|
+
|
134
|
+
line = @session.process_group.sync do
|
135
|
+
unless IO.select([@sock], nil, nil, 0)
|
136
|
+
DEBUGGER__.info "UI_Server can not read"
|
137
|
+
break :can_not_read
|
138
|
+
end
|
139
|
+
@sock.gets&.chomp.tap{|line|
|
140
|
+
DEBUGGER__.info "UI_Server received: #{line}"
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
next if line == :can_not_read
|
145
|
+
|
114
146
|
case line
|
115
147
|
when /\Apause/
|
116
148
|
pause
|
117
|
-
when /\Acommand ?(.+)/
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
149
|
+
when /\Acommand (\d+) (\d+) ?(.+)/
|
150
|
+
raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
|
151
|
+
|
152
|
+
if $1.to_i == Process.pid
|
153
|
+
@width = $2.to_i
|
154
|
+
@q_msg << $3
|
155
|
+
else
|
156
|
+
raise "pid:#{Process.pid} but get #{line}"
|
157
|
+
end
|
158
|
+
when /\Aanswer (\d+) (.*)/
|
159
|
+
raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
|
160
|
+
|
161
|
+
if $1.to_i == Process.pid
|
162
|
+
@q_ans << $2
|
163
|
+
else
|
164
|
+
raise "pid:#{Process.pid} but get #{line}"
|
165
|
+
end
|
123
166
|
else
|
124
167
|
STDERR.puts "unsupported: #{line}"
|
125
168
|
exit!
|
@@ -135,6 +178,21 @@ module DEBUGGER__
|
|
135
178
|
@width
|
136
179
|
end
|
137
180
|
|
181
|
+
def sigurg_overridden? prev_handler
|
182
|
+
case prev_handler
|
183
|
+
when "SYSTEM_DEFAULT"
|
184
|
+
false
|
185
|
+
when Proc
|
186
|
+
if prev_handler.source_location[0] == __FILE__
|
187
|
+
false
|
188
|
+
else
|
189
|
+
true
|
190
|
+
end
|
191
|
+
else
|
192
|
+
true
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
138
196
|
def setup_interrupt
|
139
197
|
prev_handler = trap(:SIGURG) do
|
140
198
|
# $stderr.puts "trapped SIGINT"
|
@@ -148,8 +206,8 @@ module DEBUGGER__
|
|
148
206
|
end
|
149
207
|
end
|
150
208
|
|
151
|
-
if prev_handler
|
152
|
-
DEBUGGER__.warn "SIGURG handler is
|
209
|
+
if sigurg_overridden?(prev_handler)
|
210
|
+
DEBUGGER__.warn "SIGURG handler is overridden by the debugger."
|
153
211
|
end
|
154
212
|
yield
|
155
213
|
ensure
|
@@ -191,7 +249,7 @@ module DEBUGGER__
|
|
191
249
|
|
192
250
|
def ask prompt
|
193
251
|
sock do |s|
|
194
|
-
s.puts "ask #{prompt}"
|
252
|
+
s.puts "ask #{Process.pid} #{prompt}"
|
195
253
|
@q_ans.pop
|
196
254
|
end
|
197
255
|
end
|
@@ -220,13 +278,23 @@ module DEBUGGER__
|
|
220
278
|
|
221
279
|
def readline prompt
|
222
280
|
input = (sock do |s|
|
223
|
-
|
281
|
+
if @repl
|
282
|
+
raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
|
283
|
+
line = "input #{Process.pid}"
|
284
|
+
DEBUGGER__.info "send: #{line}"
|
285
|
+
s.puts line
|
286
|
+
end
|
224
287
|
sleep 0.01 until @q_msg
|
288
|
+
@q_msg.pop.tap{|msg|
|
289
|
+
DEBUGGER__.info "readline: #{msg.inspect}"
|
290
|
+
}
|
291
|
+
end || 'continue')
|
225
292
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
293
|
+
if input.is_a?(String)
|
294
|
+
input.strip
|
295
|
+
else
|
296
|
+
input
|
297
|
+
end
|
230
298
|
end
|
231
299
|
|
232
300
|
def pause
|
@@ -240,10 +308,15 @@ module DEBUGGER__
|
|
240
308
|
s.puts "quit"
|
241
309
|
end
|
242
310
|
end
|
311
|
+
|
312
|
+
def after_fork_parent
|
313
|
+
# do nothing
|
314
|
+
end
|
243
315
|
end
|
244
316
|
|
245
317
|
class UI_TcpServer < UI_ServerBase
|
246
318
|
def initialize host: nil, port: nil
|
319
|
+
@addr = nil
|
247
320
|
@host = host || CONFIG[:host] || '127.0.0.1'
|
248
321
|
@port = port || begin
|
249
322
|
port_str = CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
|
@@ -263,7 +336,24 @@ module DEBUGGER__
|
|
263
336
|
|
264
337
|
begin
|
265
338
|
Socket.tcp_server_sockets @host, @port do |socks|
|
266
|
-
|
339
|
+
addr = socks[0].local_address.inspect_sockaddr # Change this part if `socks` are multiple.
|
340
|
+
rdbg = File.expand_path('../../exe/rdbg', __dir__)
|
341
|
+
|
342
|
+
DEBUGGER__.warn "Debugger can attach via TCP/IP (#{addr})"
|
343
|
+
DEBUGGER__.info <<~EOS
|
344
|
+
With rdbg, use the following command line:
|
345
|
+
#
|
346
|
+
# #{rdbg} --attach #{addr.split(':').join(' ')}
|
347
|
+
#
|
348
|
+
EOS
|
349
|
+
|
350
|
+
DEBUGGER__.warn <<~EOS if CONFIG[:open_frontend] == 'chrome'
|
351
|
+
With Chrome browser, type the following URL in the address-bar:
|
352
|
+
|
353
|
+
devtools://devtools/bundled/inspector.html?ws=#{addr}
|
354
|
+
|
355
|
+
EOS
|
356
|
+
|
267
357
|
Socket.accept_loop(socks) do |sock, client|
|
268
358
|
@client_addr = client
|
269
359
|
yield @sock_for_fork = sock
|
@@ -298,6 +388,64 @@ module DEBUGGER__
|
|
298
388
|
super()
|
299
389
|
end
|
300
390
|
|
391
|
+
def vscode_setup
|
392
|
+
require 'tmpdir'
|
393
|
+
require 'json'
|
394
|
+
require 'fileutils'
|
395
|
+
|
396
|
+
dir = Dir.mktmpdir("ruby-debug-vscode-")
|
397
|
+
at_exit{
|
398
|
+
FileUtils.rm_rf dir
|
399
|
+
}
|
400
|
+
Dir.chdir(dir) do
|
401
|
+
Dir.mkdir('.vscode')
|
402
|
+
open('README.rb', 'w'){|f|
|
403
|
+
f.puts <<~MSG
|
404
|
+
# Wait for starting the attaching to the Ruby process
|
405
|
+
# This file will be removed at the end of the debuggee process.
|
406
|
+
#
|
407
|
+
# Note that vscode-rdbg extension is needed. Please install if you don't have.
|
408
|
+
MSG
|
409
|
+
}
|
410
|
+
open('.vscode/launch.json', 'w'){|f|
|
411
|
+
f.puts JSON.pretty_generate({
|
412
|
+
version: '0.2.0',
|
413
|
+
configurations: [
|
414
|
+
{
|
415
|
+
type: "rdbg",
|
416
|
+
name: "Attach with rdbg",
|
417
|
+
request: "attach",
|
418
|
+
rdbgPath: File.expand_path('../../exe/rdbg', __dir__),
|
419
|
+
debugPort: @sock_path,
|
420
|
+
autoAttach: true,
|
421
|
+
}
|
422
|
+
]
|
423
|
+
})
|
424
|
+
}
|
425
|
+
end
|
426
|
+
|
427
|
+
cmds = ['code', "#{dir}/", "#{dir}/README.rb"]
|
428
|
+
cmdline = cmds.join(' ')
|
429
|
+
ssh_cmdline = "code --remote ssh-remote+[SSH hostname] #{dir}/ #{dir}/README.rb"
|
430
|
+
|
431
|
+
STDERR.puts "Launching: #{cmdline}"
|
432
|
+
env = ENV.delete_if{|k, h| /RUBY/ =~ k}.to_h
|
433
|
+
|
434
|
+
unless system(env, *cmds)
|
435
|
+
DEBUGGER__.warn <<~MESSAGE
|
436
|
+
Can not invoke the command.
|
437
|
+
Use the command-line on your terminal (with modification if you need).
|
438
|
+
|
439
|
+
#{cmdline}
|
440
|
+
|
441
|
+
If your application is running on a SSH remote host, please try:
|
442
|
+
|
443
|
+
#{ssh_cmdline}
|
444
|
+
|
445
|
+
MESSAGE
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
301
449
|
def accept
|
302
450
|
super # for fork
|
303
451
|
|
@@ -310,14 +458,29 @@ module DEBUGGER__
|
|
310
458
|
end
|
311
459
|
|
312
460
|
::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
461
|
+
vscode_setup if CONFIG[:open_frontend] == 'vscode'
|
462
|
+
|
463
|
+
begin
|
464
|
+
Socket.unix_server_loop @sock_path do |sock, client|
|
465
|
+
@sock_for_fork = sock
|
466
|
+
@client_addr = client
|
467
|
+
|
468
|
+
yield sock
|
469
|
+
ensure
|
470
|
+
sock.close
|
471
|
+
@sock_for_fork = nil
|
472
|
+
end
|
473
|
+
rescue Errno::ECONNREFUSED => _e
|
474
|
+
::DEBUGGER__.warn "#{_e.message} (socket path: #{@sock_path})"
|
475
|
+
|
476
|
+
if @sock_path.start_with? Config.unix_domain_socket_tmpdir
|
477
|
+
# try on homedir
|
478
|
+
@sock_path = Config.create_unix_domain_socket_name(unix_domain_socket_homedir)
|
479
|
+
::DEBUGGER__.warn "retry with #{@sock_path}"
|
480
|
+
retry
|
481
|
+
else
|
482
|
+
raise
|
483
|
+
end
|
321
484
|
end
|
322
485
|
end
|
323
486
|
end
|