ruby-debug-ide22 0.7.4
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 +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,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
|
@@ -0,0 +1,187 @@
|
|
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/thread_alias'
|
8
|
+
require 'ruby-debug-ide/helper'
|
9
|
+
require 'delegate'
|
10
|
+
|
11
|
+
module Debugger
|
12
|
+
|
13
|
+
class Command < SimpleDelegator # :nodoc:
|
14
|
+
SubcmdStruct=Struct.new(:name, :min, :short_help, :long_help) unless
|
15
|
+
defined?(SubcmdStruct)
|
16
|
+
|
17
|
+
# Find param in subcmds. param id downcased and can be abbreviated
|
18
|
+
# to the minimum length listed in the subcommands
|
19
|
+
def find(subcmds, param)
|
20
|
+
param.downcase!
|
21
|
+
for try_subcmd in subcmds do
|
22
|
+
if (param.size >= try_subcmd.min) and
|
23
|
+
(try_subcmd.name[0..param.size-1] == param)
|
24
|
+
return try_subcmd
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return nil
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def commands
|
32
|
+
@commands ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
DEF_OPTIONS = {
|
36
|
+
:event => true,
|
37
|
+
:control => false,
|
38
|
+
:unknown => false,
|
39
|
+
:need_context => false,
|
40
|
+
}
|
41
|
+
|
42
|
+
def inherited(klass)
|
43
|
+
DEF_OPTIONS.each do |o, v|
|
44
|
+
klass.options[o] = v if klass.options[o].nil?
|
45
|
+
end
|
46
|
+
commands << klass
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_commands
|
50
|
+
dir = File.dirname(__FILE__)
|
51
|
+
Dir[File.join(dir, 'commands', '*')].each do |file|
|
52
|
+
require file if file =~ /\.rb$/
|
53
|
+
end
|
54
|
+
Debugger.constants.grep(/Functions$/).map { |name| Debugger.const_get(name) }.each do |mod|
|
55
|
+
include mod
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(meth, *args, &block)
|
60
|
+
if meth.to_s =~ /^(.+?)=$/
|
61
|
+
@options[$1.intern] = args.first
|
62
|
+
else
|
63
|
+
if @options.has_key?(meth)
|
64
|
+
@options[meth]
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def options
|
72
|
+
@options ||= {}
|
73
|
+
end
|
74
|
+
|
75
|
+
def unescape_incoming(str)
|
76
|
+
str.gsub(/((?:^|[^\\])(?:\\\\)*)((?:\\n)+)/) do |_|
|
77
|
+
$1 + "\n" * ($2.size / 2)
|
78
|
+
end.gsub(/\\\\/, '\\')
|
79
|
+
end
|
80
|
+
|
81
|
+
def file_filter_supported?
|
82
|
+
defined?(Debugger.file_filter)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def initialize(state, printer)
|
87
|
+
@state, @printer = state, printer
|
88
|
+
super @printer
|
89
|
+
end
|
90
|
+
|
91
|
+
def match(input)
|
92
|
+
@match = regexp.match(input)
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
def errmsg(*args)
|
98
|
+
@printer.print_error(*args)
|
99
|
+
end
|
100
|
+
|
101
|
+
def print(*args)
|
102
|
+
@state.print(*args)
|
103
|
+
end
|
104
|
+
|
105
|
+
# see Timeout::timeout, the difference is that we must use a DebugThread
|
106
|
+
# because every other thread would be halted when the event hook is reached
|
107
|
+
# in ruby-debug.c
|
108
|
+
def timeout(sec)
|
109
|
+
return yield if sec == nil or sec.zero?
|
110
|
+
if Thread.respond_to?(:critical) and Thread.critical
|
111
|
+
raise ThreadError, "timeout within critical session"
|
112
|
+
end
|
113
|
+
begin
|
114
|
+
x = Thread.current
|
115
|
+
y = DebugThread.start {
|
116
|
+
sleep sec
|
117
|
+
x.raise StandardError, "Timeout: evaluation took longer than #{sec} seconds." if x.alive?
|
118
|
+
}
|
119
|
+
yield sec
|
120
|
+
ensure
|
121
|
+
y.kill if y and y.alive?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def debug_eval(str, b = get_binding)
|
126
|
+
begin
|
127
|
+
str = str.to_s
|
128
|
+
str.force_encoding('UTF-8') if(RUBY_VERSION >= '2.0')
|
129
|
+
to_inspect = Command.unescape_incoming(str)
|
130
|
+
max_time = Debugger.evaluation_timeout
|
131
|
+
@printer.print_debug("Evaluating %s with timeout after %i sec", str, max_time)
|
132
|
+
|
133
|
+
Debugger::TimeoutHandler.do_thread_alias
|
134
|
+
|
135
|
+
eval_result = nil
|
136
|
+
|
137
|
+
timeout(max_time) do
|
138
|
+
eval_result = eval(to_inspect, b)
|
139
|
+
end
|
140
|
+
|
141
|
+
Debugger::TimeoutHandler.undo_thread_alias
|
142
|
+
|
143
|
+
return eval_result
|
144
|
+
rescue StandardError, ScriptError => e
|
145
|
+
@printer.print_exception(e, @state.binding)
|
146
|
+
throw :debug_error
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def debug_silent_eval(str)
|
151
|
+
begin
|
152
|
+
str = str.to_s
|
153
|
+
eval(str, get_binding)
|
154
|
+
rescue StandardError, ScriptError
|
155
|
+
nil
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def get_binding
|
160
|
+
@state.context.frame_binding(@state.frame_pos)
|
161
|
+
end
|
162
|
+
|
163
|
+
def line_at(file, line)
|
164
|
+
Debugger.line_at(file, line)
|
165
|
+
end
|
166
|
+
|
167
|
+
def get_context(thnum)
|
168
|
+
Debugger.contexts.find{|c| c.thnum == thnum}
|
169
|
+
end
|
170
|
+
|
171
|
+
def realpath(filename)
|
172
|
+
is_dir = filename.end_with?(File::SEPARATOR)
|
173
|
+
if filename.index(File::SEPARATOR) || File::ALT_SEPARATOR && filename.index(File::ALT_SEPARATOR)
|
174
|
+
filename = File.expand_path(filename)
|
175
|
+
end
|
176
|
+
if (RUBY_VERSION < '1.9') || (RbConfig::CONFIG['host_os'] =~ /mswin/)
|
177
|
+
filename
|
178
|
+
else
|
179
|
+
filename = File.realpath(filename) rescue filename
|
180
|
+
filename = filename + File::SEPARATOR if is_dir && !filename.end_with?(File::SEPARATOR)
|
181
|
+
filename
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
Command.load_commands
|
187
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Debugger
|
2
|
+
class AddBreakpoint < Command # :nodoc:
|
3
|
+
self.control = true
|
4
|
+
|
5
|
+
def regexp
|
6
|
+
/ ^\s*
|
7
|
+
b(?:reak)?
|
8
|
+
(?: \s+
|
9
|
+
(?:
|
10
|
+
(\d+) |
|
11
|
+
(.+?)[:.#]([^.:\s]+)
|
12
|
+
))?
|
13
|
+
(?:\s+
|
14
|
+
if\s+(.+)
|
15
|
+
)?
|
16
|
+
$
|
17
|
+
/x
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute
|
21
|
+
if @match[1]
|
22
|
+
line, _, _, expr = @match.captures
|
23
|
+
else
|
24
|
+
_, file, line, expr = @match.captures
|
25
|
+
end
|
26
|
+
|
27
|
+
if file.nil?
|
28
|
+
file = File.basename(@state.file)
|
29
|
+
else
|
30
|
+
if line !~ /^\d+$/
|
31
|
+
klass = debug_silent_eval(file)
|
32
|
+
if klass && !klass.kind_of?(Module)
|
33
|
+
print_error "Unknown class #{file}"
|
34
|
+
throw :debug_error
|
35
|
+
end
|
36
|
+
file = klass.name if klass
|
37
|
+
else
|
38
|
+
file = realpath(file)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if line =~ /^\d+$/
|
43
|
+
line = line.to_i
|
44
|
+
else
|
45
|
+
line = line.intern.id2name
|
46
|
+
end
|
47
|
+
|
48
|
+
b = Debugger.add_breakpoint file, line, expr
|
49
|
+
print_breakpoint_added b
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
def help_command
|
54
|
+
'break'
|
55
|
+
end
|
56
|
+
|
57
|
+
def help(cmd)
|
58
|
+
%{
|
59
|
+
b[reak] file:line [if expr]
|
60
|
+
b[reak] [file|class(:|.|#)]<line|method> [if expr] -
|
61
|
+
\tset breakpoint to some position, (optionally) if expr == true
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class BreakpointsCommand < Command # :nodoc:
|
68
|
+
self.control = true
|
69
|
+
|
70
|
+
def regexp
|
71
|
+
/^\s*info\s*break$/
|
72
|
+
end
|
73
|
+
|
74
|
+
def execute
|
75
|
+
print_breakpoints Debugger.breakpoints
|
76
|
+
end
|
77
|
+
|
78
|
+
class << self
|
79
|
+
def help_command
|
80
|
+
'break'
|
81
|
+
end
|
82
|
+
|
83
|
+
def help(cmd)
|
84
|
+
%{
|
85
|
+
b[reak]\tlist breakpoints
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class DeleteBreakpointCommand < Command # :nodoc:
|
92
|
+
self.control = true
|
93
|
+
|
94
|
+
def regexp
|
95
|
+
/^\s*del(?:ete)?(?:\s+(.*))?$/
|
96
|
+
end
|
97
|
+
|
98
|
+
def execute
|
99
|
+
brkpts = @match[1]
|
100
|
+
unless brkpts
|
101
|
+
Debugger.breakpoints.clear
|
102
|
+
else
|
103
|
+
brkpts.split(/[ \t]+/).each do |pos|
|
104
|
+
pos = get_int(pos, "Delete", 1)
|
105
|
+
return unless pos
|
106
|
+
b = Debugger.remove_breakpoint(pos)
|
107
|
+
if b
|
108
|
+
print_breakpoint_deleted b
|
109
|
+
else
|
110
|
+
print_error "No breakpoint number %d\n", pos
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class << self
|
117
|
+
def help_command
|
118
|
+
'delete'
|
119
|
+
end
|
120
|
+
|
121
|
+
def help(cmd)
|
122
|
+
%{
|
123
|
+
del[ete][ nnn...]\tdelete some or all breakpoints
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Debugger
|
2
|
+
class CatchCommand < Command # :nodoc:
|
3
|
+
self.control = true
|
4
|
+
|
5
|
+
def regexp
|
6
|
+
/^\s* cat(?:ch)?
|
7
|
+
(?:\s+ (\S+))?
|
8
|
+
(?:\s+ (off))? \s* $/ix
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
excn = @match[1]
|
13
|
+
if not excn
|
14
|
+
# No args given.
|
15
|
+
errmsg "Exception class must be specified for 'catch' command"
|
16
|
+
elsif not @match[2]
|
17
|
+
# One arg given.
|
18
|
+
if 'off' == excn
|
19
|
+
clear_catchpoints
|
20
|
+
else
|
21
|
+
Debugger.add_catchpoint(excn)
|
22
|
+
print_catchpoint_set(excn)
|
23
|
+
end
|
24
|
+
elsif @match[2] != 'off'
|
25
|
+
errmsg "Off expected. Got %s\n", @match[2]
|
26
|
+
elsif remove_catchpoint(excn)
|
27
|
+
print_catchpoint_deleted(excn)
|
28
|
+
else
|
29
|
+
errmsg "Catch for exception %s not found.\n", excn
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def help_command
|
35
|
+
'catch'
|
36
|
+
end
|
37
|
+
|
38
|
+
def help(cmd)
|
39
|
+
%{
|
40
|
+
cat[ch]\t\t\tshow catchpoint
|
41
|
+
cat[ch] off \tremove all catch points
|
42
|
+
cat[ch] <an Exception>\tset catchpoint to an exception
|
43
|
+
cat[ch] <an Exception> off \tremove catchpoint for an exception
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def clear_catchpoints
|
51
|
+
if Debugger.respond_to?(:clear_catchpoints)
|
52
|
+
Debugger.clear_catchpoints
|
53
|
+
else
|
54
|
+
Debugger.catchpoints.clear
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def remove_catchpoint(excn)
|
59
|
+
return Debugger.remove_catchpoint(excn) if Debugger.respond_to?(:remove_catchpoint)
|
60
|
+
return Debugger.catchpoints.delete(excn) if Debugger.catchpoints.member?(excn)
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|