ruby-debug-ide22 0.7.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES +75 -0
- data/ChangeLog.archive +1073 -0
- data/ChangeLog.md +594 -0
- data/Gemfile +38 -0
- data/MIT-LICENSE +24 -0
- data/Rakefile +93 -0
- data/bin/gdb_wrapper +96 -0
- data/bin/rdebug-ide +200 -0
- data/ext/mkrf_conf.rb +44 -0
- data/lib/ruby-debug-ide/attach/debugger_loader.rb +20 -0
- data/lib/ruby-debug-ide/attach/gdb.rb +73 -0
- data/lib/ruby-debug-ide/attach/lldb.rb +71 -0
- data/lib/ruby-debug-ide/attach/native_debugger.rb +133 -0
- data/lib/ruby-debug-ide/attach/process_thread.rb +54 -0
- data/lib/ruby-debug-ide/attach/util.rb +115 -0
- data/lib/ruby-debug-ide/command.rb +187 -0
- data/lib/ruby-debug-ide/commands/breakpoints.rb +128 -0
- data/lib/ruby-debug-ide/commands/catchpoint.rb +64 -0
- data/lib/ruby-debug-ide/commands/condition.rb +51 -0
- data/lib/ruby-debug-ide/commands/control.rb +158 -0
- data/lib/ruby-debug-ide/commands/enable.rb +203 -0
- data/lib/ruby-debug-ide/commands/eval.rb +64 -0
- data/lib/ruby-debug-ide/commands/expression_info.rb +71 -0
- data/lib/ruby-debug-ide/commands/file_filtering.rb +107 -0
- data/lib/ruby-debug-ide/commands/frame.rb +155 -0
- data/lib/ruby-debug-ide/commands/inspect.rb +25 -0
- data/lib/ruby-debug-ide/commands/jump.rb +73 -0
- data/lib/ruby-debug-ide/commands/load.rb +18 -0
- data/lib/ruby-debug-ide/commands/pause.rb +33 -0
- data/lib/ruby-debug-ide/commands/set_type.rb +47 -0
- data/lib/ruby-debug-ide/commands/stepping.rb +108 -0
- data/lib/ruby-debug-ide/commands/threads.rb +178 -0
- data/lib/ruby-debug-ide/commands/variables.rb +154 -0
- data/lib/ruby-debug-ide/event_processor.rb +71 -0
- data/lib/ruby-debug-ide/greeter.rb +42 -0
- data/lib/ruby-debug-ide/helper.rb +33 -0
- data/lib/ruby-debug-ide/ide_processor.rb +155 -0
- data/lib/ruby-debug-ide/interface.rb +45 -0
- data/lib/ruby-debug-ide/multiprocess/monkey.rb +47 -0
- data/lib/ruby-debug-ide/multiprocess/pre_child.rb +59 -0
- data/lib/ruby-debug-ide/multiprocess/starter.rb +11 -0
- data/lib/ruby-debug-ide/multiprocess/unmonkey.rb +31 -0
- data/lib/ruby-debug-ide/multiprocess.rb +23 -0
- data/lib/ruby-debug-ide/thread_alias.rb +27 -0
- data/lib/ruby-debug-ide/version.rb +3 -0
- data/lib/ruby-debug-ide/xml_printer.rb +571 -0
- data/lib/ruby-debug-ide.rb +228 -0
- data/ruby-debug-ide.gemspec +47 -0
- metadata +110 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
module Debugger
|
2
|
+
class VarConstantCommand < Command # :nodoc:
|
3
|
+
def regexp
|
4
|
+
/^\s*v(?:ar)?\s+c(?:onst(?:ant)?)?\s+/
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute
|
8
|
+
obj = debug_eval(@match.post_match)
|
9
|
+
unless obj.kind_of? Module
|
10
|
+
print_msg "Should be Class/Module: %s", @match.post_match
|
11
|
+
else
|
12
|
+
print_variables(obj.constants, "constant") do |var|
|
13
|
+
obj.const_get(var)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def help_command
|
20
|
+
'var'
|
21
|
+
end
|
22
|
+
|
23
|
+
def help(cmd)
|
24
|
+
%{
|
25
|
+
v[ar] c[onst] <object>\t\tshow constants of object
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class VarGlobalCommand < Command # :nodoc:
|
32
|
+
def regexp
|
33
|
+
/^\s*v(?:ar)?\s+g(?:lobal)?\s*$/
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute
|
37
|
+
globals = []
|
38
|
+
if RUBY_VERSION < "1.9"
|
39
|
+
globals = global_variables - ['$=', '$IGNORECASE']
|
40
|
+
else
|
41
|
+
globals = global_variables - [:$KCODE, :$-K, :$=, :$IGNORECASE, :$FILENAME]
|
42
|
+
end
|
43
|
+
print_variables(globals, 'global') do |var|
|
44
|
+
debug_eval(var)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
def help_command
|
50
|
+
'var'
|
51
|
+
end
|
52
|
+
|
53
|
+
def help(cmd)
|
54
|
+
%{
|
55
|
+
v[ar] g[lobal]\t\t\tshow global variables
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class VarInstanceCommand < Command # :nodoc:
|
62
|
+
# TODO: try to find out a way to use Kernel.binding for Rubinius
|
63
|
+
# ::Kernel.binding doesn't for for ruby 1.8 (see RUBY-14679)
|
64
|
+
BINDING_COMMAND = (defined?(Rubinius) || RUBY_VERSION < '1.9') ? 'binding' : '::Kernel.binding'
|
65
|
+
|
66
|
+
def regexp
|
67
|
+
# id will be read as first match, name as post match
|
68
|
+
/^\s*v(?:ar)?\s+i(?:nstance)?\s+((?:[\\+-]0x)[\dabcdef]+)?/
|
69
|
+
end
|
70
|
+
|
71
|
+
def execute
|
72
|
+
if (@match[1])
|
73
|
+
obj = ObjectSpace._id2ref(@match[1].hex) rescue nil
|
74
|
+
|
75
|
+
unless obj
|
76
|
+
print_element("variables")
|
77
|
+
@printer.print_msg("Unknown object id : %s", @match[1])
|
78
|
+
end
|
79
|
+
else
|
80
|
+
obj = debug_eval(@match.post_match)
|
81
|
+
end
|
82
|
+
return unless obj
|
83
|
+
if obj.is_a?(Array)
|
84
|
+
print_array(obj)
|
85
|
+
elsif obj.is_a?(Hash)
|
86
|
+
print_hash(obj)
|
87
|
+
elsif obj.is_a?(String)
|
88
|
+
print_string(obj)
|
89
|
+
else
|
90
|
+
print_element("variables") do
|
91
|
+
# instance variables
|
92
|
+
kind = 'instance'
|
93
|
+
inst_vars = obj.instance_variables
|
94
|
+
instance_binding = obj.instance_eval(BINDING_COMMAND)
|
95
|
+
# print self at top position
|
96
|
+
print_variable('self', debug_eval('self', instance_binding), kind) if inst_vars.include?('self')
|
97
|
+
inst_vars.sort.each do |var|
|
98
|
+
print_variable(var, debug_eval(var, instance_binding), kind) unless var == 'self'
|
99
|
+
end
|
100
|
+
|
101
|
+
# class variables
|
102
|
+
class_binding = obj.class.class_eval(BINDING_COMMAND)
|
103
|
+
obj.class.class_variables.sort.each do |var|
|
104
|
+
print_variable(var, debug_eval(var, class_binding), 'class')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class << self
|
111
|
+
def help_command
|
112
|
+
'var'
|
113
|
+
end
|
114
|
+
|
115
|
+
def help(cmd)
|
116
|
+
%{
|
117
|
+
v[ar] i[nstance] <object>\tshow instance variables of object, object can be given by its id or an expression
|
118
|
+
}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class VarLocalCommand < Command # :nodoc:
|
124
|
+
def regexp
|
125
|
+
/^\s*v(?:ar)?\s+l(?:ocal)?\s*$/
|
126
|
+
end
|
127
|
+
|
128
|
+
def execute
|
129
|
+
locals = @state.context.frame_locals(@state.frame_pos)
|
130
|
+
_self = @state.context.frame_self(@state.frame_pos)
|
131
|
+
begin
|
132
|
+
locals['self'] = _self unless TOPLEVEL_BINDING.eval('self') == _self
|
133
|
+
rescue => ex
|
134
|
+
locals['self'] = "<Cannot evaluate self>"
|
135
|
+
$stderr << "Cannot evaluate self\n#{ex.class.name}: #{ex.message}\n #{ex.backtrace.join("\n ")}"
|
136
|
+
end
|
137
|
+
print_variables(locals.keys, 'local') do |var|
|
138
|
+
locals[var]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class << self
|
143
|
+
def help_command
|
144
|
+
'var'
|
145
|
+
end
|
146
|
+
|
147
|
+
def help(cmd)
|
148
|
+
%{
|
149
|
+
v[ar] l[ocal]\t\t\tshow local variables
|
150
|
+
}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'ruby-debug-ide/xml_printer'
|
2
|
+
|
3
|
+
module Debugger
|
4
|
+
|
5
|
+
class EventProcessor
|
6
|
+
|
7
|
+
attr_accessor :line, :file, :context
|
8
|
+
|
9
|
+
def initialize(interface)
|
10
|
+
@printer = XmlPrinter.new(interface)
|
11
|
+
@interface = interface
|
12
|
+
@line = nil
|
13
|
+
@file = nil
|
14
|
+
@last_breakpoint = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def at_breakpoint(context, breakpoint)
|
18
|
+
raise "@last_breakpoint supposed to be nil. is #{@last_breakpoint}" if @last_breakpoint
|
19
|
+
# at_breakpoint is immediately followed by #at_line event in
|
20
|
+
# ruby-debug-base. So postpone breakpoint printing until #at_line.
|
21
|
+
@last_breakpoint = breakpoint
|
22
|
+
end
|
23
|
+
|
24
|
+
def at_catchpoint(context, excpt)
|
25
|
+
@printer.print_catchpoint(excpt)
|
26
|
+
end
|
27
|
+
|
28
|
+
def at_tracing(context, file, line)
|
29
|
+
@printer.print_trace(context, file, line)
|
30
|
+
end
|
31
|
+
|
32
|
+
def at_line(context, file, line)
|
33
|
+
@printer.print_at_line(context, file, line) if context.nil? || context.stop_reason == :step
|
34
|
+
line_event(context, file, line)
|
35
|
+
end
|
36
|
+
|
37
|
+
def at_return(context, file, line)
|
38
|
+
@printer.print_at_line(context, file, line)
|
39
|
+
context.stop_frame = -1
|
40
|
+
line_event(context, file, line)
|
41
|
+
end
|
42
|
+
|
43
|
+
def line_event(context, file, line)
|
44
|
+
@line = line
|
45
|
+
@file = file
|
46
|
+
@context = context
|
47
|
+
if @last_breakpoint
|
48
|
+
# followed after #at_breakpoint in the same thread. Print breakpoint
|
49
|
+
# now when @line, @file and @context are correctly set to prevent race
|
50
|
+
# condition with `control thread'.
|
51
|
+
n = Debugger.breakpoints.index(@last_breakpoint) + 1
|
52
|
+
@printer.print_breakpoint n, @last_breakpoint
|
53
|
+
@last_breakpoint = nil
|
54
|
+
end
|
55
|
+
raise "DebuggerThread are not supposed to be traced (#{context.thread})" if context.thread.is_a?(Debugger::DebugThread)
|
56
|
+
@printer.print_debug("Stopping Thread %s (%s)", context.thread.to_s, Process.pid.to_s)
|
57
|
+
@printer.print_debug("Threads equal: %s", Thread.current == context.thread)
|
58
|
+
IdeCommandProcessor.new(@interface).process_commands
|
59
|
+
InspectCommand.clear_references
|
60
|
+
@printer.print_debug("Resumed Thread %s", context.thread.to_s)
|
61
|
+
@line = nil
|
62
|
+
@file = nil
|
63
|
+
@context = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def at_line?
|
67
|
+
@line
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
if RUBY_VERSION < '2.0' || defined?(JRUBY_VERSION)
|
2
|
+
require 'ruby-debug-base'
|
3
|
+
else
|
4
|
+
require 'debase'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'ruby-debug-ide/version'
|
8
|
+
require 'ruby-debug-ide/ide_processor'
|
9
|
+
|
10
|
+
module Debugger
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def print_greeting_msg(stream, host, port, debugger_name = "Fast", socket_path = nil)
|
14
|
+
base_gem_name = if defined?(JRUBY_VERSION) || RUBY_VERSION < '1.9.0'
|
15
|
+
'ruby-debug-base'
|
16
|
+
elsif RUBY_VERSION < '2.0.0'
|
17
|
+
'ruby-debug-base19x'
|
18
|
+
else
|
19
|
+
'debase'
|
20
|
+
end
|
21
|
+
|
22
|
+
file_filtering_support = if Command.file_filter_supported?
|
23
|
+
'supported'
|
24
|
+
else
|
25
|
+
'not supported'
|
26
|
+
end
|
27
|
+
|
28
|
+
if host && port
|
29
|
+
listens_on = " listens on #{host}:#{port}\n"
|
30
|
+
elsif socket_path
|
31
|
+
listens_on = " listens on #{socket_path}\n"
|
32
|
+
else
|
33
|
+
listens_on = "\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
msg = "#{debugger_name} Debugger (ruby-debug-ide #{IDE_VERSION}, #{base_gem_name} #{VERSION}, file filtering is #{file_filtering_support})" + listens_on
|
37
|
+
|
38
|
+
stream.printf msg
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Debugger
|
2
|
+
|
3
|
+
module ParseFunctions
|
4
|
+
# Parse 'str' of command 'cmd' as an integer between
|
5
|
+
# min and max. If either min or max is nil, that
|
6
|
+
# value has no bound.
|
7
|
+
def get_int(str, cmd, min=nil, max=nil, default=1)
|
8
|
+
return default unless str
|
9
|
+
begin
|
10
|
+
int = Integer(str)
|
11
|
+
if min and int < min
|
12
|
+
print_error "%s argument '%s' needs to at least %s.\n" % [cmd, str, min]
|
13
|
+
return nil
|
14
|
+
elsif max and int > max
|
15
|
+
print_error "%s argument '%s' needs to at most %s.\n" % [cmd, str, max]
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
return int
|
19
|
+
rescue
|
20
|
+
print_error "%s argument '%s' needs to be a number.\n" % [cmd, str]
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return true if code is syntactically correct for Ruby.
|
26
|
+
def syntax_valid?(code)
|
27
|
+
eval("BEGIN {return true}\n#{code}", nil, "", 0)
|
28
|
+
rescue Exception
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'ruby-debug-ide/interface'
|
2
|
+
require 'ruby-debug-ide/command'
|
3
|
+
|
4
|
+
module Debugger
|
5
|
+
class IdeCommandProcessor
|
6
|
+
def initialize(interface = nil)
|
7
|
+
@interface = interface
|
8
|
+
@printer = XmlPrinter.new(@interface)
|
9
|
+
end
|
10
|
+
|
11
|
+
def print(*args)
|
12
|
+
@interface.print(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def process_commands
|
16
|
+
unless Debugger.handler.at_line?
|
17
|
+
@printer.print_error "There is no thread suspended at the time and therefore no context to execute '#{input.gsub('%', '%%')}'"
|
18
|
+
return
|
19
|
+
end
|
20
|
+
context = Debugger.handler.context
|
21
|
+
file = Debugger.handler.file
|
22
|
+
line = Debugger.handler.line
|
23
|
+
state = State.new do |s|
|
24
|
+
s.context = context
|
25
|
+
s.file = file
|
26
|
+
s.line = line
|
27
|
+
s.binding = context.frame_binding(0)
|
28
|
+
s.interface = @interface
|
29
|
+
end
|
30
|
+
event_cmds = Command.commands.map{|cmd| cmd.new(state, @printer) }
|
31
|
+
until state.proceed? do
|
32
|
+
input = @interface.command_queue.pop
|
33
|
+
catch(:debug_error) do
|
34
|
+
splitter[input].each do |input|
|
35
|
+
# escape % since print_debug might use printf
|
36
|
+
@printer.print_debug "Processing in context: #{input.gsub('%', '%%')}"
|
37
|
+
if (cmd = event_cmds.find { |c| c.match(input) })
|
38
|
+
if context.dead? && cmd.class.need_context
|
39
|
+
@printer.print_msg "Command is unavailable\n"
|
40
|
+
else
|
41
|
+
cmd.execute
|
42
|
+
end
|
43
|
+
else
|
44
|
+
@printer.print_msg "Unknown command: #{input}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
state.restore_context
|
49
|
+
end
|
50
|
+
rescue ::Exception
|
51
|
+
@printer.print_error "INTERNAL ERROR!!! #{$!}\n" rescue nil
|
52
|
+
@printer.print_error $!.backtrace.map{|l| "\t#{l}"}.join("\n") rescue nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def splitter
|
56
|
+
lambda do |str|
|
57
|
+
str.split(/;/).inject([]) do |m, v|
|
58
|
+
if m.empty?
|
59
|
+
m << v
|
60
|
+
else
|
61
|
+
if m.last[-1] == ?\\
|
62
|
+
m.last[-1,1] = ''
|
63
|
+
m.last << ';' << v
|
64
|
+
else
|
65
|
+
m << v
|
66
|
+
end
|
67
|
+
end
|
68
|
+
m
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class IdeControlCommandProcessor < IdeCommandProcessor# :nodoc:
|
75
|
+
def process_commands
|
76
|
+
@printer.print_debug("Starting control thread")
|
77
|
+
ctrl_cmd_classes = Command.commands.select{|cmd| cmd.control}
|
78
|
+
state = ControlState.new(@interface)
|
79
|
+
ctrl_cmds = ctrl_cmd_classes.map{|cmd| cmd.new(state, @printer)}
|
80
|
+
while input = @interface.read_command
|
81
|
+
# escape % since print_debug might use printf
|
82
|
+
# sleep 0.3
|
83
|
+
catch(:debug_error) do
|
84
|
+
if cmd = ctrl_cmds.find{|c| c.match(input) }
|
85
|
+
@printer.print_debug "Processing in control: #{input.gsub('%', '%%')}"
|
86
|
+
cmd.execute
|
87
|
+
else
|
88
|
+
@interface.command_queue << input
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
rescue ::Exception
|
93
|
+
@printer.print_debug "INTERNAL ERROR!!! #{$!}\n" rescue nil
|
94
|
+
@printer.print_error "INTERNAL ERROR!!! #{$!}\n" rescue nil
|
95
|
+
@printer.print_error $!.backtrace.map{|l| "\t#{l}"}.join("\n") rescue nil
|
96
|
+
ensure
|
97
|
+
@interface.close
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class State # :nodoc:
|
102
|
+
|
103
|
+
attr_accessor :context, :original_context
|
104
|
+
attr_accessor :file, :line, :binding
|
105
|
+
attr_accessor :frame_pos, :previous_line
|
106
|
+
attr_accessor :interface
|
107
|
+
|
108
|
+
def initialize
|
109
|
+
@frame_pos = 0
|
110
|
+
@previous_line = nil
|
111
|
+
@proceed = false
|
112
|
+
yield self
|
113
|
+
@original_context = context
|
114
|
+
end
|
115
|
+
|
116
|
+
def print(*args)
|
117
|
+
@interface.print(*args)
|
118
|
+
end
|
119
|
+
|
120
|
+
def proceed?
|
121
|
+
@proceed
|
122
|
+
end
|
123
|
+
|
124
|
+
def proceed
|
125
|
+
@proceed = true
|
126
|
+
end
|
127
|
+
|
128
|
+
def restore_context
|
129
|
+
@context = @original_context
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class ControlState # :nodoc:
|
134
|
+
|
135
|
+
def initialize(interface)
|
136
|
+
@interface = interface
|
137
|
+
end
|
138
|
+
|
139
|
+
def proceed
|
140
|
+
end
|
141
|
+
|
142
|
+
def print(*args)
|
143
|
+
@interface.print(*args)
|
144
|
+
end
|
145
|
+
|
146
|
+
def context
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
|
150
|
+
def file
|
151
|
+
print "ERROR: No filename given.\n"
|
152
|
+
throw :debug_error
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Debugger
|
4
|
+
class Interface
|
5
|
+
end
|
6
|
+
|
7
|
+
class LocalInterface < Interface
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
class RemoteInterface < Interface # :nodoc:
|
12
|
+
attr_accessor :command_queue
|
13
|
+
|
14
|
+
def initialize(socket)
|
15
|
+
@socket = socket
|
16
|
+
@command_queue = Queue.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def read_command
|
20
|
+
result = non_blocking_gets
|
21
|
+
raise IOError unless result
|
22
|
+
result.chomp
|
23
|
+
end
|
24
|
+
|
25
|
+
def print(*args)
|
26
|
+
@socket.printf(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def close
|
30
|
+
@socket.close
|
31
|
+
rescue IOError, SystemCallError
|
32
|
+
end
|
33
|
+
|
34
|
+
# Workaround for JRuby issue http://jira.codehaus.org/browse/JRUBY-2063
|
35
|
+
def non_blocking_gets
|
36
|
+
loop do
|
37
|
+
result, _, _ = IO.select( [@socket], nil, nil, 0.2 )
|
38
|
+
next unless result
|
39
|
+
return result[0].gets
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Debugger
|
2
|
+
module MultiProcess
|
3
|
+
def self.create_mp_fork(private=false)
|
4
|
+
%Q{
|
5
|
+
alias pre_debugger_fork fork
|
6
|
+
|
7
|
+
#{private ? "private" : ""}
|
8
|
+
def fork(*args)
|
9
|
+
if block_given?
|
10
|
+
return pre_debugger_fork{Debugger::MultiProcess::pre_child; yield}
|
11
|
+
end
|
12
|
+
result = pre_debugger_fork
|
13
|
+
Debugger::MultiProcess::pre_child unless result
|
14
|
+
result
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create_mp_exec(private=false)
|
20
|
+
%Q{
|
21
|
+
alias pre_debugger_exec exec
|
22
|
+
|
23
|
+
#{private ? "private" : ""}
|
24
|
+
def exec(*args)
|
25
|
+
Debugger.interface.close
|
26
|
+
pre_debugger_exec(*args)
|
27
|
+
end
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Kernel
|
34
|
+
class << self
|
35
|
+
module_eval Debugger::MultiProcess.create_mp_fork
|
36
|
+
module_eval Debugger::MultiProcess.create_mp_exec
|
37
|
+
end
|
38
|
+
module_eval Debugger::MultiProcess.create_mp_fork(true)
|
39
|
+
module_eval Debugger::MultiProcess.create_mp_exec(true)
|
40
|
+
end
|
41
|
+
|
42
|
+
module Process
|
43
|
+
class << self
|
44
|
+
module_eval Debugger::MultiProcess.create_mp_fork
|
45
|
+
module_eval Debugger::MultiProcess.create_mp_exec
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Debugger
|
2
|
+
module MultiProcess
|
3
|
+
class << self
|
4
|
+
def pre_child(options = nil)
|
5
|
+
require 'socket'
|
6
|
+
require 'ostruct'
|
7
|
+
|
8
|
+
host = ENV['DEBUGGER_HOST']
|
9
|
+
|
10
|
+
options ||= OpenStruct.new(
|
11
|
+
'frame_bind' => false,
|
12
|
+
'host' => host,
|
13
|
+
'load_mode' => false,
|
14
|
+
'port' => Debugger.find_free_port(host),
|
15
|
+
'stop' => false,
|
16
|
+
'tracing' => false,
|
17
|
+
'int_handler' => true,
|
18
|
+
'cli_debug' => (ENV['DEBUGGER_CLI_DEBUG'] == 'true'),
|
19
|
+
'notify_dispatcher' => true,
|
20
|
+
'evaluation_timeout' => 10,
|
21
|
+
'trace_to_s' => false,
|
22
|
+
'debugger_memory_limit' => 10,
|
23
|
+
'inspect_time_limit' => 100
|
24
|
+
)
|
25
|
+
|
26
|
+
if(options.ignore_port)
|
27
|
+
options.port = Debugger.find_free_port(options.host)
|
28
|
+
options.notify_dispatcher = true
|
29
|
+
end
|
30
|
+
|
31
|
+
start_debugger(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def start_debugger(options)
|
35
|
+
if Debugger.started?
|
36
|
+
# we're in forked child, only need to restart control thread
|
37
|
+
Debugger.breakpoints.clear
|
38
|
+
Debugger.control_thread = nil
|
39
|
+
Debugger.start_control(options.host, options.port, options.notify_dispatcher)
|
40
|
+
end
|
41
|
+
|
42
|
+
if options.int_handler
|
43
|
+
# install interruption handler
|
44
|
+
trap('INT') { Debugger.interrupt_last }
|
45
|
+
end
|
46
|
+
|
47
|
+
# set options
|
48
|
+
Debugger.keep_frame_binding = options.frame_bind
|
49
|
+
Debugger.tracing = options.tracing
|
50
|
+
Debugger.evaluation_timeout = options.evaluation_timeout
|
51
|
+
Debugger.trace_to_s = options.trace_to_s
|
52
|
+
Debugger.debugger_memory_limit = options.debugger_memory_limit
|
53
|
+
Debugger.inspect_time_limit = options.inspect_time_limit
|
54
|
+
Debugger.cli_debug = options.cli_debug
|
55
|
+
Debugger.prepare_debugger(options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
if ENV['IDE_PROCESS_DISPATCHER']
|
2
|
+
require 'rubygems'
|
3
|
+
ENV['DEBUGGER_STORED_RUBYLIB'].split(File::PATH_SEPARATOR).each do |path|
|
4
|
+
next unless path =~ /ruby-debug-ide|ruby-debug-base|linecache|debase/
|
5
|
+
$LOAD_PATH << path
|
6
|
+
end
|
7
|
+
require 'ruby-debug-ide'
|
8
|
+
require 'ruby-debug-ide/multiprocess'
|
9
|
+
Debugger::MultiProcess::do_monkey
|
10
|
+
Debugger::MultiProcess::pre_child
|
11
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Debugger
|
2
|
+
module MultiProcess
|
3
|
+
def self.restore_fork
|
4
|
+
%Q{
|
5
|
+
alias fork pre_debugger_fork
|
6
|
+
}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.restore_exec
|
10
|
+
%Q{
|
11
|
+
alias exec pre_debugger_exec
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Kernel
|
18
|
+
class << self
|
19
|
+
module_eval Debugger::MultiProcess.restore_fork
|
20
|
+
module_eval Debugger::MultiProcess.restore_exec
|
21
|
+
end
|
22
|
+
module_eval Debugger::MultiProcess.restore_fork
|
23
|
+
module_eval Debugger::MultiProcess.restore_exec
|
24
|
+
end
|
25
|
+
|
26
|
+
module Process
|
27
|
+
class << self
|
28
|
+
module_eval Debugger::MultiProcess.restore_fork
|
29
|
+
module_eval Debugger::MultiProcess.restore_exec
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
if RUBY_VERSION < '1.9'
|
2
|
+
require 'ruby-debug-ide/multiprocess/pre_child'
|
3
|
+
else
|
4
|
+
require_relative 'multiprocess/pre_child'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Debugger
|
8
|
+
module MultiProcess
|
9
|
+
class << self
|
10
|
+
def do_monkey
|
11
|
+
load File.expand_path(File.dirname(__FILE__) + '/multiprocess/monkey.rb')
|
12
|
+
end
|
13
|
+
|
14
|
+
def undo_monkey
|
15
|
+
if ENV['IDE_PROCESS_DISPATCHER']
|
16
|
+
load File.expand_path(File.dirname(__FILE__) + '/multiprocess/unmonkey.rb')
|
17
|
+
ruby_opts = ENV['RUBYOPT'].split(' ')
|
18
|
+
ENV['RUBYOPT'] = ruby_opts.keep_if {|opt| !opt.end_with?('ruby-debug-ide/multiprocess/starter')}.join(' ')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Debugger
|
2
|
+
module TimeoutHandler
|
3
|
+
class << self
|
4
|
+
def do_thread_alias
|
5
|
+
if defined? ::OldThread
|
6
|
+
Debugger.print_debug 'Tried to re-alias thread for eval'
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
Object.const_set :OldThread, ::Thread
|
11
|
+
Object.__send__ :remove_const, :Thread
|
12
|
+
Object.const_set :Thread, ::Debugger::DebugThread
|
13
|
+
end
|
14
|
+
|
15
|
+
def undo_thread_alias
|
16
|
+
unless defined? ::OldThread
|
17
|
+
Debugger.print_debug 'Tried to de-alias thread twice'
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
Object.__send__ :remove_const, :Thread
|
22
|
+
Object.const_set :Thread, ::OldThread
|
23
|
+
Object.__send__ :remove_const, :OldThread
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|