ruby-debug-ide-docker 0.6.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +75 -0
  3. data/ChangeLog.archive +1073 -0
  4. data/ChangeLog.md +594 -0
  5. data/Gemfile +28 -0
  6. data/MIT-LICENSE +24 -0
  7. data/Rakefile +42 -0
  8. data/bin/gdb_wrapper +96 -0
  9. data/bin/rdebug-ide +183 -0
  10. data/ext/mkrf_conf.rb +48 -0
  11. data/lib/ruby-debug-ide.rb +173 -0
  12. data/lib/ruby-debug-ide/attach/debugger_loader.rb +20 -0
  13. data/lib/ruby-debug-ide/attach/gdb.rb +73 -0
  14. data/lib/ruby-debug-ide/attach/lldb.rb +71 -0
  15. data/lib/ruby-debug-ide/attach/native_debugger.rb +133 -0
  16. data/lib/ruby-debug-ide/attach/process_thread.rb +54 -0
  17. data/lib/ruby-debug-ide/attach/util.rb +115 -0
  18. data/lib/ruby-debug-ide/command.rb +177 -0
  19. data/lib/ruby-debug-ide/commands/breakpoints.rb +128 -0
  20. data/lib/ruby-debug-ide/commands/catchpoint.rb +64 -0
  21. data/lib/ruby-debug-ide/commands/condition.rb +51 -0
  22. data/lib/ruby-debug-ide/commands/control.rb +158 -0
  23. data/lib/ruby-debug-ide/commands/enable.rb +203 -0
  24. data/lib/ruby-debug-ide/commands/eval.rb +64 -0
  25. data/lib/ruby-debug-ide/commands/expression_info.rb +71 -0
  26. data/lib/ruby-debug-ide/commands/file_filtering.rb +107 -0
  27. data/lib/ruby-debug-ide/commands/frame.rb +155 -0
  28. data/lib/ruby-debug-ide/commands/inspect.rb +25 -0
  29. data/lib/ruby-debug-ide/commands/jump.rb +73 -0
  30. data/lib/ruby-debug-ide/commands/load.rb +18 -0
  31. data/lib/ruby-debug-ide/commands/pause.rb +33 -0
  32. data/lib/ruby-debug-ide/commands/set_type.rb +47 -0
  33. data/lib/ruby-debug-ide/commands/stepping.rb +108 -0
  34. data/lib/ruby-debug-ide/commands/threads.rb +178 -0
  35. data/lib/ruby-debug-ide/commands/variables.rb +154 -0
  36. data/lib/ruby-debug-ide/event_processor.rb +71 -0
  37. data/lib/ruby-debug-ide/greeter.rb +40 -0
  38. data/lib/ruby-debug-ide/helper.rb +33 -0
  39. data/lib/ruby-debug-ide/ide_processor.rb +155 -0
  40. data/lib/ruby-debug-ide/interface.rb +45 -0
  41. data/lib/ruby-debug-ide/multiprocess.rb +23 -0
  42. data/lib/ruby-debug-ide/multiprocess/monkey.rb +47 -0
  43. data/lib/ruby-debug-ide/multiprocess/pre_child.rb +67 -0
  44. data/lib/ruby-debug-ide/multiprocess/starter.rb +11 -0
  45. data/lib/ruby-debug-ide/multiprocess/unmonkey.rb +31 -0
  46. data/lib/ruby-debug-ide/version.rb +3 -0
  47. data/lib/ruby-debug-ide/xml_printer.rb +545 -0
  48. data/ruby-debug-ide-docker.gemspec +51 -0
  49. metadata +110 -0
@@ -0,0 +1,20 @@
1
+ def load_debugger(gems_to_include, new_argv)
2
+ path_to_rdebug = File.expand_path(File.dirname(__FILE__)) + '/../../../bin/rdebug-ide'
3
+
4
+ old_argv = ARGV.clone
5
+ ARGV.clear
6
+ new_argv.each do |x|
7
+ ARGV << x
8
+ end
9
+
10
+ gems_to_include.each do |gem_path|
11
+ $LOAD_PATH.unshift(gem_path) unless $LOAD_PATH.include?(gem_path)
12
+ end
13
+
14
+ load path_to_rdebug
15
+
16
+ ARGV.clear
17
+ old_argv.each do |x|
18
+ ARGV << x
19
+ end
20
+ end
@@ -0,0 +1,73 @@
1
+ require 'ruby-debug-ide/attach/native_debugger'
2
+
3
+ class GDB < NativeDebugger
4
+
5
+ def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv)
6
+ super(executable, pid, flags, gems_to_include, debugger_loader_path, argv)
7
+ end
8
+
9
+ def set_flags
10
+ execute 'set scheduler-locking off' # we will deadlock with it
11
+ execute 'set unwindonsignal on' # in case of some signal we will exit gdb
12
+ end
13
+
14
+ def update_threads
15
+ @process_threads = []
16
+ info_threads = (execute 'info threads').split("\n")
17
+ info_threads.each do |thread_info|
18
+ next unless thread_info =~ /[\s*]*\d+\s+Thread.*/
19
+ $stdout.puts "thread_info: #{thread_info}"
20
+ is_main = thread_info[0] == '*'
21
+ thread_num = thread_info.sub(/[\s*]*/, '').sub(/\s.*$/, '').to_i
22
+ thread = ProcessThread.new(thread_num, is_main, thread_info, self)
23
+ if thread.is_main
24
+ @main_thread = thread
25
+ end
26
+ @process_threads << thread
27
+ end
28
+ @process_threads
29
+ end
30
+
31
+ def check_already_under_debug
32
+ threads = execute 'info threads'
33
+ threads =~ /ruby-debug-ide/
34
+ end
35
+
36
+ def switch_to_thread(thread_num)
37
+ execute "thread #{thread_num}"
38
+ end
39
+
40
+ def set_break(str)
41
+ execute "tbreak #{str}"
42
+ end
43
+
44
+ def call_start_attach
45
+ super()
46
+ execute "call dlopen(\"#{@path_to_attach}\", 2)"
47
+ execute 'call debase_start_attach()'
48
+ set_break(@tbreak)
49
+ end
50
+
51
+ def print_delimiter
52
+ @pipe.puts "print \"#{@delimiter}\""
53
+ end
54
+
55
+ def check_delimiter(line)
56
+ line =~ /\$\d+\s=\s"#{@delimiter}"/
57
+ end
58
+
59
+ def load_debugger
60
+ execute "call #{@eval_string}"
61
+ end
62
+
63
+ def to_s
64
+ GDB.to_s
65
+ end
66
+
67
+ class << self
68
+ def to_s
69
+ 'gdb'
70
+ end
71
+ end
72
+
73
+ end
@@ -0,0 +1,71 @@
1
+ require 'ruby-debug-ide/attach/native_debugger'
2
+
3
+ class LLDB < NativeDebugger
4
+
5
+ def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv)
6
+ super(executable, pid, flags, gems_to_include, debugger_loader_path, argv)
7
+ end
8
+
9
+ def set_flags
10
+
11
+ end
12
+
13
+ def update_threads
14
+ @process_threads = []
15
+ info_threads = (execute 'thread list').split("\n")
16
+ info_threads.each do |thread_info|
17
+ next unless thread_info =~ /[\s*]*thread\s#\d+.*/
18
+ is_main = thread_info[0] == '*'
19
+ thread_num = thread_info.sub(/[\s*]*thread\s#/, '').sub(/:\s.*$/, '').to_i
20
+ thread = ProcessThread.new(thread_num, is_main, thread_info, self)
21
+ if thread.is_main
22
+ @main_thread = thread
23
+ end
24
+ @process_threads << thread
25
+ end
26
+ @process_threads
27
+ end
28
+
29
+ def check_already_under_debug
30
+ threads = execute 'thread list'
31
+ threads =~ /ruby-debug-ide/
32
+ end
33
+
34
+ def switch_to_thread(thread_num)
35
+ execute "thread select #{thread_num}"
36
+ end
37
+
38
+ def set_break(str)
39
+ execute "breakpoint set --shlib #{@path_to_attach} --name #{str}"
40
+ end
41
+
42
+ def call_start_attach
43
+ super()
44
+ execute "expr (void *) dlopen(\"#{@path_to_attach}\", 2)"
45
+ execute 'expr (int) debase_start_attach()'
46
+ set_break(@tbreak)
47
+ end
48
+
49
+ def print_delimiter
50
+ @pipe.puts "script print \"#{@delimiter}\""
51
+ end
52
+
53
+ def check_delimiter(line)
54
+ line =~ /#{@delimiter}$/
55
+ end
56
+
57
+ def load_debugger
58
+ execute "expr (void) #{@eval_string}"
59
+ end
60
+
61
+ def to_s
62
+ LLDB.to_s
63
+ end
64
+
65
+ class << self
66
+ def to_s
67
+ 'lldb'
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,133 @@
1
+ class NativeDebugger
2
+
3
+ attr_reader :pid, :main_thread, :process_threads, :pipe
4
+
5
+ # @param executable -- path to ruby interpreter
6
+ # @param pid -- pid of process you want to debug
7
+ # @param flags -- flags you want to specify to your debugger as a string (e.g. "-nx -nh" for gdb to disable .gdbinit)
8
+ def initialize(executable, pid, flags, gems_to_include, debugger_loader_path, argv)
9
+ @pid = pid
10
+ @delimiter = '__OUTPUT_FINISHED__' # for getting response
11
+ @tbreak = '__func_to_set_breakpoint_at'
12
+ @main_thread = nil
13
+ @process_threads = nil
14
+ debase_path = gems_to_include.select {|gem_path| gem_path =~ /debase/}
15
+ if debase_path.size == 0
16
+ raise 'No debase gem found.'
17
+ end
18
+ @path_to_attach = find_attach_lib(debase_path[0])
19
+
20
+ @gems_to_include = '["' + gems_to_include * '", "' + '"]'
21
+ @debugger_loader_path = debugger_loader_path
22
+ @argv = argv
23
+
24
+ @eval_string = "debase_rb_eval(\"require '#{@debugger_loader_path}'; load_debugger(#{@gems_to_include.gsub("\"", "'")}, #{@argv.gsub("\"", "'")})\")"
25
+
26
+ launch_string = "#{self} #{executable} #{flags}"
27
+ @pipe = IO.popen(launch_string, 'r+')
28
+ $stdout.puts "executed '#{launch_string}'"
29
+ end
30
+
31
+ def find_attach_lib(debase_path)
32
+ attach_lib = debase_path + '/attach'
33
+ known_extensions = %w(.so .bundle .dll .dylib)
34
+ known_extensions.each do |ext|
35
+ if File.file?(attach_lib + ext)
36
+ return attach_lib + ext
37
+ end
38
+ end
39
+
40
+ raise 'Could not find attach library'
41
+ end
42
+
43
+ def attach_to_process
44
+ execute "attach #{@pid}"
45
+ end
46
+
47
+ def execute(command)
48
+ @pipe.puts command
49
+ $stdout.puts "executed `#{command}` command inside #{self}."
50
+ if command == 'q'
51
+ return ''
52
+ end
53
+ get_response
54
+ end
55
+
56
+ def get_response
57
+ # we need this hack to understand that debugger gave us all output from last executed command
58
+ print_delimiter
59
+
60
+ content = ''
61
+ loop do
62
+ line = @pipe.readline
63
+ DebugPrinter.print_debug('respond line: ' + line)
64
+ break if check_delimiter(line)
65
+ next if line =~ /\(lldb\)/ # lldb repeats your input to its output
66
+ content += line
67
+ end
68
+
69
+ content
70
+ end
71
+
72
+ def update_threads
73
+
74
+ end
75
+
76
+ def check_already_under_debug
77
+
78
+ end
79
+
80
+ def print_delimiter
81
+
82
+ end
83
+
84
+ def check_delimiter(line)
85
+
86
+ end
87
+
88
+ def switch_to_thread
89
+
90
+ end
91
+
92
+ def set_break(str)
93
+
94
+ end
95
+
96
+ def continue
97
+ $stdout.puts 'continuing'
98
+ @pipe.puts 'c'
99
+ loop do
100
+ line = @pipe.readline
101
+ DebugPrinter.print_debug('respond line: ' + line)
102
+ break if line =~ /#{Regexp.escape(@tbreak)}/
103
+ end
104
+ get_response
105
+ end
106
+
107
+ def call_start_attach
108
+ raise 'No main thread found. Did you forget to call `update_threads`?' if @main_thread == nil
109
+ @main_thread.switch
110
+ end
111
+
112
+ def wait_line_event
113
+ call_start_attach
114
+ continue
115
+ end
116
+
117
+ def load_debugger
118
+
119
+ end
120
+
121
+ def exited?
122
+ @pipe.closed?
123
+ end
124
+
125
+ def exit
126
+ @pipe.close
127
+ end
128
+
129
+ def to_s
130
+ 'native_debugger'
131
+ end
132
+
133
+ end
@@ -0,0 +1,54 @@
1
+ require 'ruby-debug-ide/attach/native_debugger'
2
+
3
+ class ProcessThread
4
+
5
+ attr_reader :thread_num, :is_main, :thread_info, :last_bt
6
+
7
+ def initialize(thread_num, is_main, thread_info, native_debugger)
8
+ @thread_num = thread_num
9
+ @is_main = is_main
10
+ @native_debugger = native_debugger
11
+ @thread_info = thread_info
12
+ @last_bt = nil
13
+ end
14
+
15
+ def switch
16
+ @native_debugger.switch_to_thread(thread_num)
17
+ end
18
+
19
+ def finish
20
+ @native_debugger.execute 'finish'
21
+ end
22
+
23
+ def get_bt
24
+ @last_bt = @native_debugger.execute 'bt'
25
+ end
26
+
27
+ def any_caller_match(bt, pattern)
28
+ bt =~ /#{pattern}/
29
+ end
30
+
31
+ def is_inside_malloc(bt = get_bt)
32
+ if any_caller_match(bt, '(malloc)')
33
+ $stderr.puts "process #{@native_debugger.pid} is currently inside malloc."
34
+ true
35
+ else
36
+ false
37
+ end
38
+ end
39
+
40
+ def is_inside_gc(bt = get_bt)
41
+ if any_caller_match(bt, '(gc\.c)')
42
+ $stderr.puts "process #{@native_debugger.pid} is currently in garbage collection phase."
43
+ true
44
+ else
45
+ false
46
+ end
47
+ end
48
+
49
+ def need_finish_frame
50
+ bt = get_bt
51
+ is_inside_malloc(bt) || is_inside_gc(bt)
52
+ end
53
+
54
+ end
@@ -0,0 +1,115 @@
1
+ require 'ruby-debug-ide/attach/lldb'
2
+ require 'ruby-debug-ide/attach/gdb'
3
+ require 'socket'
4
+ require 'set'
5
+
6
+ def attach_and_return_thread(options, pid, debugger_loader_path, argv)
7
+ Thread.new(argv) do |argv|
8
+
9
+ debugger = choose_debugger(options.ruby_path, pid, options.gems_to_include, debugger_loader_path, argv)
10
+
11
+ trap('INT') do
12
+ unless debugger.exited?
13
+ $stderr.puts "backtraces for threads:\n\n"
14
+ process_threads = debugger.process_threads
15
+ if process_threads
16
+ process_threads.each do |thread|
17
+ $stderr.puts "#{thread.thread_info}\n#{thread.last_bt}\n\n"
18
+ end
19
+ end
20
+ debugger.exit
21
+ end
22
+ exit!
23
+ end
24
+
25
+ debugger.attach_to_process
26
+ debugger.set_flags
27
+
28
+ if debugger.check_already_under_debug
29
+ $stderr.puts "Process #{debugger.pid} is already under debug"
30
+ debugger.exit
31
+ exit!
32
+ end
33
+
34
+ should_check_threads_state = true
35
+
36
+ while should_check_threads_state
37
+ should_check_threads_state = false
38
+ debugger.update_threads.each do |thread|
39
+ thread.switch
40
+ while thread.need_finish_frame
41
+ should_check_threads_state = true
42
+ thread.finish
43
+ end
44
+ end
45
+ end
46
+
47
+ debugger.wait_line_event
48
+ debugger.load_debugger
49
+ debugger.exit
50
+ end
51
+ end
52
+
53
+ def get_child_pids(pid)
54
+ return [] unless command_exists 'pgrep'
55
+
56
+ pids = Array.new
57
+
58
+ q = Queue.new
59
+ q.push(pid)
60
+
61
+ until q.empty? do
62
+ pid = q.pop
63
+
64
+ pipe = IO.popen("pgrep -P #{pid}")
65
+
66
+ pipe.readlines.each do |child|
67
+ child_pid = child.strip.to_i
68
+ q.push(child_pid)
69
+ pids << child_pid
70
+ end
71
+ end
72
+
73
+ filter_ruby_processes(pids)
74
+ end
75
+
76
+ def filter_ruby_processes(pids)
77
+ pipe = IO.popen(%Q(lsof -c ruby | awk '{print $2 ":" $9}' | grep -E 'bin/ruby([[:digit:]]+\.?)*$'))
78
+
79
+ ruby_processes = Set.new
80
+
81
+ pipe.readlines.each do |process|
82
+ pid = process.split(/:/).first
83
+ ruby_processes.add(pid.to_i)
84
+ end
85
+
86
+ ruby_processes_pids, non_ruby_processes_pids = pids.partition {|pid| ruby_processes.include? pid}
87
+
88
+ DebugPrinter.print_debug("The following child processes was added to attach: #{ruby_processes_pids.join(', ')}") unless ruby_processes_pids.empty?
89
+ DebugPrinter.print_debug("The following child are not ruby processes: #{non_ruby_processes_pids.join(', ')}") unless non_ruby_processes_pids.empty?
90
+
91
+ ruby_processes_pids
92
+ end
93
+
94
+ def command_exists(command)
95
+ checking_command = "checking command #{command} for existence\n"
96
+ `command -v #{command} >/dev/null 2>&1 || { exit 1; }`
97
+ if $?.exitstatus != 0
98
+ DebugPrinter.print_debug("#{checking_command}command does not exist.")
99
+ else
100
+ DebugPrinter.print_debug("#{checking_command}command does exist.")
101
+ end
102
+ $?.exitstatus == 0
103
+ end
104
+
105
+ def choose_debugger(ruby_path, pid, gems_to_include, debugger_loader_path, argv)
106
+ if command_exists(LLDB.to_s)
107
+ debugger = LLDB.new(ruby_path, pid, '--no-lldbinit', gems_to_include, debugger_loader_path, argv)
108
+ elsif command_exists(GDB.to_s)
109
+ debugger = GDB.new(ruby_path, pid, '-nh -nx', gems_to_include, debugger_loader_path, argv)
110
+ else
111
+ raise 'Neither gdb nor lldb was found. Aborting.'
112
+ end
113
+
114
+ debugger
115
+ end