Skip to content

Commit 4333f10

Browse files
viuginick1valich
authored andcommitted
improve attach mode to affect child processes (#108)
This commit enables the attachment to the child processes of the original one. Useful for debugging application servers (see https://youtrack.jetbrains.com/issue/RUBY-19369)
1 parent 828f0d8 commit 4333f10

File tree

4 files changed

+88
-42
lines changed

4 files changed

+88
-42
lines changed

bin/gdb_wrapper

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env ruby
22

33
require 'optparse'
4+
require 'thread'
45
require 'ostruct'
56

67
$stdout.sync = true
@@ -65,6 +66,7 @@ unless options.ruby_path
6566
end
6667

6768
argv = '["' + ARGV * '", "' + '"]'
69+
child_argv = '["' + ARGV * '", "' + "', '--ignore-port" + '"]'
6870
debugger_loader_path = File.expand_path(File.dirname(__FILE__)) + '/../lib/ruby-debug-ide/attach/debugger_loader'
6971

7072
options.gems_to_include.each do |gem_path|
@@ -78,51 +80,17 @@ require 'ruby-debug-ide/attach/util'
7880
require 'ruby-debug-ide/attach/native_debugger'
7981
require 'ruby-debug-ide/attach/process_thread'
8082

81-
debugger = choose_debugger(options.ruby_path, options.pid, options.gems_to_include, debugger_loader_path, argv)
8283

83-
trap('INT') do
84-
unless debugger.exited?
85-
$stderr.puts "backtraces for threads:\n\n"
86-
process_threads = debugger.process_threads
87-
if process_threads
88-
process_threads.each do |thread|
89-
$stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n"
90-
end
91-
end
92-
debugger.exit
93-
end
94-
exit!
95-
end
84+
child_pids = get_child_pids(options.pid.to_s)
85+
attach_threads = Array.new
86+
attach_threads << attach_and_return_thread(options, options.pid, debugger_loader_path, argv)
9687

97-
debugger.attach_to_process
98-
debugger.set_flags
88+
attach_threads << child_pids.map {|pid| attach_and_return_thread(options, pid, debugger_loader_path, child_argv)}
9989

90+
91+
attach_threads.each {|thread| thread.join}
10092
if options.uid
10193
DebugPrinter.print_debug("changing current uid from #{Process.uid} to #{options.uid}")
10294
Process::Sys.setuid(options.uid.to_i)
10395
end
104-
105-
if debugger.check_already_under_debug
106-
$stderr.puts "Process #{debugger.pid} is already under debug"
107-
debugger.exit
108-
exit!
109-
end
110-
111-
should_check_threads_state = true
112-
113-
while should_check_threads_state
114-
should_check_threads_state = false
115-
debugger.update_threads.each do |thread|
116-
thread.switch
117-
while thread.need_finish_frame
118-
should_check_threads_state = true
119-
thread.finish
120-
end
121-
end
122-
end
123-
124-
debugger.wait_line_event
125-
debugger.load_debugger
126-
debugger.exit
127-
12896
sleep

bin/rdebug-ide

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ EOB
4848
end
4949

5050
opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host}
51-
opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port}
51+
opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port}
5252
opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp|
5353
options.dispatcher_port = dp
5454
end
@@ -71,6 +71,9 @@ EOB
7171
opts.on("--attach-mode", "Tells that rdebug-ide is working in attach mode") do
7272
options.attach_mode = true
7373
end
74+
opts.on("--ignore-port", "Generate another port") do
75+
options.ignore_port = true
76+
end
7477
opts.on("--keep-frame-binding", "Keep frame bindings") {options.frame_bind = true}
7578
opts.on("--disable-int-handler", "Disables interrupt signal handler") {options.int_handler = false}
7679
opts.on("--rubymine-protocol-extensions", "Enable all RubyMine-specific incompatible protocol extensions") do

lib/ruby-debug-ide/attach/util.rb

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,76 @@
11
require 'ruby-debug-ide/attach/lldb'
22
require 'ruby-debug-ide/attach/gdb'
3+
require 'socket'
4+
5+
def attach_and_return_thread(options, pid, debugger_loader_path, argv)
6+
Thread.new(argv) do |argv|
7+
8+
debugger = choose_debugger(options.ruby_path, pid, options.gems_to_include, debugger_loader_path, argv)
9+
10+
trap('INT') do
11+
unless debugger.exited?
12+
$stderr.puts "backtraces for threads:\n\n"
13+
process_threads = debugger.process_threads
14+
if process_threads
15+
process_threads.each do |thread|
16+
$stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n"
17+
end
18+
end
19+
debugger.exit
20+
end
21+
exit!
22+
end
23+
24+
debugger.attach_to_process
25+
debugger.set_flags
26+
27+
if debugger.check_already_under_debug
28+
$stderr.puts "Process #{debugger.pid} is already under debug"
29+
debugger.exit
30+
exit!
31+
end
32+
33+
should_check_threads_state = true
34+
35+
while should_check_threads_state
36+
should_check_threads_state = false
37+
debugger.update_threads.each do |thread|
38+
thread.switch
39+
while thread.need_finish_frame
40+
should_check_threads_state = true
41+
thread.finish
42+
end
43+
end
44+
end
45+
46+
debugger.wait_line_event
47+
debugger.load_debugger
48+
debugger.exit
49+
end
50+
end
51+
52+
def get_child_pids(pid)
53+
return [] unless command_exists 'pgrep'
54+
55+
pids = Array.new
56+
57+
q = Queue.new
58+
q.push(pid)
59+
60+
while (!q.empty?) do
61+
pid = q.pop
62+
63+
pipe = IO.popen("pgrep -P #{pid}")
64+
65+
pipe.readlines.each do |child|
66+
child_pid = child.strip.to_i
67+
q.push(child_pid)
68+
pids << child_pid
69+
end
70+
end
71+
72+
pids
73+
end
374

475
def command_exists(command)
576
checking_command = "checking command #{command} for existence\n"

lib/ruby-debug-ide/multiprocess/pre_child.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ def pre_child(options = nil)
1919
'notify_dispatcher' => true
2020
)
2121

22+
if(options.ignore_port)
23+
options.port = find_free_port(options.host)
24+
options.notify_dispatcher = true
25+
end
26+
2227
start_debugger(options)
2328
end
2429

@@ -39,7 +44,6 @@ def start_debugger(options)
3944
Debugger.keep_frame_binding = options.frame_bind
4045
Debugger.tracing = options.tracing
4146
Debugger.cli_debug = options.cli_debug
42-
4347
Debugger.prepare_debugger(options)
4448
end
4549

0 commit comments

Comments
 (0)