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.
Files changed (50) 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 +38 -0
  6. data/MIT-LICENSE +24 -0
  7. data/Rakefile +93 -0
  8. data/bin/gdb_wrapper +96 -0
  9. data/bin/rdebug-ide +200 -0
  10. data/ext/mkrf_conf.rb +44 -0
  11. data/lib/ruby-debug-ide/attach/debugger_loader.rb +20 -0
  12. data/lib/ruby-debug-ide/attach/gdb.rb +73 -0
  13. data/lib/ruby-debug-ide/attach/lldb.rb +71 -0
  14. data/lib/ruby-debug-ide/attach/native_debugger.rb +133 -0
  15. data/lib/ruby-debug-ide/attach/process_thread.rb +54 -0
  16. data/lib/ruby-debug-ide/attach/util.rb +115 -0
  17. data/lib/ruby-debug-ide/command.rb +187 -0
  18. data/lib/ruby-debug-ide/commands/breakpoints.rb +128 -0
  19. data/lib/ruby-debug-ide/commands/catchpoint.rb +64 -0
  20. data/lib/ruby-debug-ide/commands/condition.rb +51 -0
  21. data/lib/ruby-debug-ide/commands/control.rb +158 -0
  22. data/lib/ruby-debug-ide/commands/enable.rb +203 -0
  23. data/lib/ruby-debug-ide/commands/eval.rb +64 -0
  24. data/lib/ruby-debug-ide/commands/expression_info.rb +71 -0
  25. data/lib/ruby-debug-ide/commands/file_filtering.rb +107 -0
  26. data/lib/ruby-debug-ide/commands/frame.rb +155 -0
  27. data/lib/ruby-debug-ide/commands/inspect.rb +25 -0
  28. data/lib/ruby-debug-ide/commands/jump.rb +73 -0
  29. data/lib/ruby-debug-ide/commands/load.rb +18 -0
  30. data/lib/ruby-debug-ide/commands/pause.rb +33 -0
  31. data/lib/ruby-debug-ide/commands/set_type.rb +47 -0
  32. data/lib/ruby-debug-ide/commands/stepping.rb +108 -0
  33. data/lib/ruby-debug-ide/commands/threads.rb +178 -0
  34. data/lib/ruby-debug-ide/commands/variables.rb +154 -0
  35. data/lib/ruby-debug-ide/event_processor.rb +71 -0
  36. data/lib/ruby-debug-ide/greeter.rb +42 -0
  37. data/lib/ruby-debug-ide/helper.rb +33 -0
  38. data/lib/ruby-debug-ide/ide_processor.rb +155 -0
  39. data/lib/ruby-debug-ide/interface.rb +45 -0
  40. data/lib/ruby-debug-ide/multiprocess/monkey.rb +47 -0
  41. data/lib/ruby-debug-ide/multiprocess/pre_child.rb +59 -0
  42. data/lib/ruby-debug-ide/multiprocess/starter.rb +11 -0
  43. data/lib/ruby-debug-ide/multiprocess/unmonkey.rb +31 -0
  44. data/lib/ruby-debug-ide/multiprocess.rb +23 -0
  45. data/lib/ruby-debug-ide/thread_alias.rb +27 -0
  46. data/lib/ruby-debug-ide/version.rb +3 -0
  47. data/lib/ruby-debug-ide/xml_printer.rb +571 -0
  48. data/lib/ruby-debug-ide.rb +228 -0
  49. data/ruby-debug-ide.gemspec +47 -0
  50. 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