ruby-debug19 0.11.5
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +9 -0
- data/LICENSE +23 -0
- data/bin/rdebug +415 -0
- data/cli/ruby-debug.rb +176 -0
- data/cli/ruby-debug/command.rb +228 -0
- data/cli/ruby-debug/commands/breakpoints.rb +153 -0
- data/cli/ruby-debug/commands/catchpoint.rb +55 -0
- data/cli/ruby-debug/commands/condition.rb +49 -0
- data/cli/ruby-debug/commands/continue.rb +38 -0
- data/cli/ruby-debug/commands/control.rb +107 -0
- data/cli/ruby-debug/commands/display.rb +120 -0
- data/cli/ruby-debug/commands/edit.rb +48 -0
- data/cli/ruby-debug/commands/enable.rb +202 -0
- data/cli/ruby-debug/commands/eval.rb +176 -0
- data/cli/ruby-debug/commands/finish.rb +42 -0
- data/cli/ruby-debug/commands/frame.rb +301 -0
- data/cli/ruby-debug/commands/help.rb +56 -0
- data/cli/ruby-debug/commands/info.rb +469 -0
- data/cli/ruby-debug/commands/irb.rb +123 -0
- data/cli/ruby-debug/commands/kill.rb +51 -0
- data/cli/ruby-debug/commands/list.rb +94 -0
- data/cli/ruby-debug/commands/method.rb +84 -0
- data/cli/ruby-debug/commands/quit.rb +39 -0
- data/cli/ruby-debug/commands/reload.rb +40 -0
- data/cli/ruby-debug/commands/save.rb +90 -0
- data/cli/ruby-debug/commands/set.rb +237 -0
- data/cli/ruby-debug/commands/show.rb +253 -0
- data/cli/ruby-debug/commands/source.rb +36 -0
- data/cli/ruby-debug/commands/stepping.rb +81 -0
- data/cli/ruby-debug/commands/threads.rb +189 -0
- data/cli/ruby-debug/commands/tmate.rb +36 -0
- data/cli/ruby-debug/commands/trace.rb +57 -0
- data/cli/ruby-debug/commands/variables.rb +199 -0
- data/cli/ruby-debug/debugger.rb +5 -0
- data/cli/ruby-debug/helper.rb +69 -0
- data/cli/ruby-debug/interface.rb +232 -0
- data/cli/ruby-debug/processor.rb +474 -0
- data/rdbg.rb +33 -0
- metadata +122 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
module Debugger
|
2
|
+
|
3
|
+
module ParseFunctions
|
4
|
+
Position_regexp = '(?:(\d+)|(.+?)[:.#]([^.:\s]+))'
|
5
|
+
|
6
|
+
# Parse 'str' of command 'cmd' as an integer between
|
7
|
+
# min and max. If either min or max is nil, that
|
8
|
+
# value has no bound.
|
9
|
+
def get_int(str, cmd, min=nil, max=nil, default=1)
|
10
|
+
return default unless str
|
11
|
+
begin
|
12
|
+
int = Integer(str)
|
13
|
+
if min and int < min
|
14
|
+
print "%s argument '%s' needs to at least %s.\n" % [cmd, str, min]
|
15
|
+
return nil
|
16
|
+
elsif max and int > max
|
17
|
+
print "%s argument '%s' needs to at most %s.\n" % [cmd, str, max]
|
18
|
+
return nil
|
19
|
+
end
|
20
|
+
return int
|
21
|
+
rescue
|
22
|
+
print "%s argument '%s' needs to be a number.\n" % [cmd, str]
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return true if arg is 'on' or 1 and false arg is 'off' or 0.
|
28
|
+
# Any other value raises RuntimeError.
|
29
|
+
def get_onoff(arg, default=nil, print_error=true)
|
30
|
+
if arg.nil? or arg == ''
|
31
|
+
if default.nil?
|
32
|
+
if print_error
|
33
|
+
print "Expecting 'on', 1, 'off', or 0. Got nothing.\n"
|
34
|
+
raise RuntimeError
|
35
|
+
end
|
36
|
+
return default
|
37
|
+
end
|
38
|
+
end
|
39
|
+
case arg.downcase
|
40
|
+
when '1', 'on'
|
41
|
+
return true
|
42
|
+
when '0', 'off'
|
43
|
+
return false
|
44
|
+
else
|
45
|
+
if print_error
|
46
|
+
print "Expecting 'on', 1, 'off', or 0. Got: %s.\n" % arg.to_s
|
47
|
+
raise RuntimeError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return 'on' or 'off' for supplied parameter. The parmeter should
|
53
|
+
# be true, false or nil.
|
54
|
+
def show_onoff(bool)
|
55
|
+
if not [TrueClass, FalseClass, NilClass].member?(bool.class)
|
56
|
+
return "??"
|
57
|
+
end
|
58
|
+
return bool ? 'on' : 'off'
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return true if code is syntactically correct for Ruby.
|
62
|
+
def syntax_valid?(code)
|
63
|
+
eval("BEGIN {return true}\n#{code}", nil, "", 0)
|
64
|
+
rescue Exception
|
65
|
+
false
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module Debugger
|
2
|
+
class Interface # :nodoc:
|
3
|
+
attr_writer :have_readline # true if Readline is available
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@have_readline = false
|
7
|
+
end
|
8
|
+
|
9
|
+
# Common routine for reporting debugger error messages.
|
10
|
+
# Derived classed may want to override this to capture output.
|
11
|
+
def errmsg(*args)
|
12
|
+
if Debugger.annotate.to_i > 2
|
13
|
+
aprint 'error-begin'
|
14
|
+
print(*args)
|
15
|
+
aprint ''
|
16
|
+
else
|
17
|
+
print '*** '
|
18
|
+
print(*args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Format msg with gdb-style annotation header
|
23
|
+
def afmt(msg, newline="\n")
|
24
|
+
"\032\032#{msg}#{newline}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def aprint(msg)
|
28
|
+
print afmt(msg)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class LocalInterface < Interface # :nodoc:
|
34
|
+
attr_accessor :command_queue
|
35
|
+
attr_accessor :histfile
|
36
|
+
attr_accessor :history_save
|
37
|
+
attr_accessor :history_length
|
38
|
+
attr_accessor :restart_file
|
39
|
+
|
40
|
+
unless defined?(FILE_HISTORY)
|
41
|
+
FILE_HISTORY = ".rdebug_hist"
|
42
|
+
end
|
43
|
+
def initialize()
|
44
|
+
super
|
45
|
+
@command_queue = []
|
46
|
+
@have_readline = false
|
47
|
+
@history_save = true
|
48
|
+
# take gdb's default
|
49
|
+
@history_length = ENV["HISTSIZE"] ? ENV["HISTSIZE"].to_i : 256
|
50
|
+
@histfile = File.join(ENV["HOME"]||ENV["HOMEPATH"]||".",
|
51
|
+
FILE_HISTORY)
|
52
|
+
open(@histfile, 'r') do |file|
|
53
|
+
file.each do |line|
|
54
|
+
line.chomp!
|
55
|
+
Readline::HISTORY << line
|
56
|
+
end
|
57
|
+
end if File.exist?(@histfile)
|
58
|
+
@restart_file = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def read_command(prompt)
|
62
|
+
readline(prompt, true)
|
63
|
+
end
|
64
|
+
|
65
|
+
def confirm(prompt)
|
66
|
+
readline(prompt, false)
|
67
|
+
end
|
68
|
+
|
69
|
+
def print(*args)
|
70
|
+
STDOUT.printf(*args)
|
71
|
+
end
|
72
|
+
|
73
|
+
def close
|
74
|
+
end
|
75
|
+
|
76
|
+
# Things to do before quitting
|
77
|
+
def finalize
|
78
|
+
if Debugger.method_defined?("annotate") and Debugger.annotate.to_i > 2
|
79
|
+
print "\032\032exited\n\n"
|
80
|
+
end
|
81
|
+
if Debugger.respond_to?(:save_history)
|
82
|
+
Debugger.save_history
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def readline_support?
|
87
|
+
@have_readline
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
begin
|
92
|
+
require 'readline'
|
93
|
+
class << Debugger
|
94
|
+
@have_readline = true
|
95
|
+
define_method(:save_history) do
|
96
|
+
iface = self.handler.interface
|
97
|
+
iface.histfile ||= File.join(ENV["HOME"]||ENV["HOMEPATH"]||".",
|
98
|
+
FILE_HISTORY)
|
99
|
+
open(iface.histfile, 'w') do |file|
|
100
|
+
Readline::HISTORY.to_a.last(iface.history_length).each do |line|
|
101
|
+
file.puts line unless line.strip.empty?
|
102
|
+
end if defined?(iface.history_save) and iface.history_save
|
103
|
+
end rescue nil
|
104
|
+
end
|
105
|
+
public :save_history
|
106
|
+
end
|
107
|
+
Debugger.debug_at_exit do
|
108
|
+
finalize if respond_to?(:finalize)
|
109
|
+
end
|
110
|
+
|
111
|
+
def readline(prompt, hist)
|
112
|
+
Readline::readline(prompt, hist)
|
113
|
+
end
|
114
|
+
rescue LoadError
|
115
|
+
def readline(prompt, hist)
|
116
|
+
@histfile = ''
|
117
|
+
@hist_save = false
|
118
|
+
STDOUT.print prompt
|
119
|
+
STDOUT.flush
|
120
|
+
line = STDIN.gets
|
121
|
+
exit unless line
|
122
|
+
line.chomp!
|
123
|
+
line
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class RemoteInterface < Interface # :nodoc:
|
129
|
+
attr_accessor :command_queue
|
130
|
+
attr_accessor :histfile
|
131
|
+
attr_accessor :history_save
|
132
|
+
attr_accessor :history_length
|
133
|
+
attr_accessor :restart_file
|
134
|
+
|
135
|
+
def initialize(socket)
|
136
|
+
@command_queue = []
|
137
|
+
@socket = socket
|
138
|
+
@history_save = false
|
139
|
+
@history_length = 256
|
140
|
+
@histfile = ''
|
141
|
+
# Do we read the histfile?
|
142
|
+
# open(@histfile, 'r') do |file|
|
143
|
+
# file.each do |line|
|
144
|
+
# line.chomp!
|
145
|
+
# Readline::HISTORY << line
|
146
|
+
# end
|
147
|
+
# end if File.exist?(@histfile)
|
148
|
+
@restart_file = nil
|
149
|
+
end
|
150
|
+
|
151
|
+
def close
|
152
|
+
@socket.close
|
153
|
+
rescue Exception
|
154
|
+
end
|
155
|
+
|
156
|
+
def confirm(prompt)
|
157
|
+
send_command "CONFIRM #{prompt}"
|
158
|
+
end
|
159
|
+
|
160
|
+
def finalize
|
161
|
+
end
|
162
|
+
|
163
|
+
def read_command(prompt)
|
164
|
+
send_command "PROMPT #{prompt}"
|
165
|
+
end
|
166
|
+
|
167
|
+
def readline_support?
|
168
|
+
false
|
169
|
+
end
|
170
|
+
|
171
|
+
def print(*args)
|
172
|
+
@socket.printf(*args)
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def send_command(msg)
|
178
|
+
@socket.puts msg
|
179
|
+
result = @socket.gets
|
180
|
+
raise IOError unless result
|
181
|
+
result.chomp
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class ScriptInterface < Interface # :nodoc:
|
186
|
+
attr_accessor :command_queue
|
187
|
+
attr_accessor :histfile
|
188
|
+
attr_accessor :history_save
|
189
|
+
attr_accessor :history_length
|
190
|
+
attr_accessor :restart_file
|
191
|
+
def initialize(file, out, verbose=false)
|
192
|
+
super()
|
193
|
+
@command_queue = []
|
194
|
+
@file = file.respond_to?(:gets) ? file : open(file)
|
195
|
+
@out = out
|
196
|
+
@verbose = verbose
|
197
|
+
@history_save = false
|
198
|
+
@history_length = 256 # take gdb default
|
199
|
+
@histfile = ''
|
200
|
+
end
|
201
|
+
|
202
|
+
def finalize
|
203
|
+
end
|
204
|
+
|
205
|
+
def read_command(prompt)
|
206
|
+
while result = @file.gets
|
207
|
+
puts "# #{result}" if @verbose
|
208
|
+
next if result =~ /^\s*#/
|
209
|
+
next if result.strip.empty?
|
210
|
+
break
|
211
|
+
end
|
212
|
+
raise IOError unless result
|
213
|
+
result.chomp!
|
214
|
+
end
|
215
|
+
|
216
|
+
def readline_support?
|
217
|
+
false
|
218
|
+
end
|
219
|
+
|
220
|
+
def confirm(prompt)
|
221
|
+
'y'
|
222
|
+
end
|
223
|
+
|
224
|
+
def print(*args)
|
225
|
+
@out.printf(*args)
|
226
|
+
end
|
227
|
+
|
228
|
+
def close
|
229
|
+
@file.close
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,474 @@
|
|
1
|
+
require_relative 'interface'
|
2
|
+
require_relative 'command'
|
3
|
+
|
4
|
+
module Debugger
|
5
|
+
|
6
|
+
# Should this be a mixin?
|
7
|
+
class Processor # :nodoc
|
8
|
+
attr_accessor :interface
|
9
|
+
|
10
|
+
# Format msg with gdb-style annotation header
|
11
|
+
def afmt(msg, newline="\n")
|
12
|
+
"\032\032#{msg}#{newline}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def aprint(msg)
|
16
|
+
print afmt(msg) if Debugger.annotate.to_i > 2
|
17
|
+
end
|
18
|
+
|
19
|
+
# FIXME: use delegate?
|
20
|
+
def errmsg(*args)
|
21
|
+
@interface.errmsg(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Callers of this routine should make sure to use comma to
|
25
|
+
# separate format argments rather than %. Otherwise it seems that
|
26
|
+
# if the string you want to print has format specifier, which
|
27
|
+
# could happen if you are trying to show say a source-code line
|
28
|
+
# with "puts" or "print" in it, this print routine will give an
|
29
|
+
# error saying it is looking for more arguments.
|
30
|
+
def print(*args)
|
31
|
+
@interface.print(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class CommandProcessor < Processor # :nodoc:
|
37
|
+
attr_reader :display
|
38
|
+
|
39
|
+
# FIXME: get from Command regexp method.
|
40
|
+
@@Show_breakpoints_postcmd = [
|
41
|
+
/^\s*b(?:reak)?/,
|
42
|
+
/^\s* cond(?:ition)? (?:\s+(\d+)\s*(.*))?$/ix,
|
43
|
+
/^\s*del(?:ete)?(?:\s+(.*))?$/ix,
|
44
|
+
/^\s* dis(?:able)? (?:\s+(.*))?$/ix,
|
45
|
+
/^\s* en(?:able)? (?:\s+(.*))?$/ix,
|
46
|
+
# "tbreak", "clear",
|
47
|
+
]
|
48
|
+
@@Show_annotations_run = [
|
49
|
+
/^\s*c(?:ont(?:inue)?)?(?:\s+(.*))?$/,
|
50
|
+
/^\s*fin(?:ish)?$/,
|
51
|
+
/^\s*n(?:ext)?([+-])?(?:\s+(.*))?$/,
|
52
|
+
/^\s*s(?:tep)?([+-])?(?:\s+(.*))?$/
|
53
|
+
]
|
54
|
+
|
55
|
+
@@Show_annotations_postcmd = [
|
56
|
+
/^\s* down (?:\s+(.*))? .*$/x,
|
57
|
+
/^\s* f(?:rame)? (?:\s+ (.*))? \s*$/x,
|
58
|
+
/^\s* u(?:p)? (?:\s+(.*))?$/x
|
59
|
+
]
|
60
|
+
|
61
|
+
def initialize(interface = LocalInterface.new)
|
62
|
+
@interface = interface
|
63
|
+
@display = []
|
64
|
+
|
65
|
+
@mutex = Mutex.new
|
66
|
+
@last_cmd = nil
|
67
|
+
@last_file = nil # Filename the last time we stopped
|
68
|
+
@last_line = nil # line number the last time we stopped
|
69
|
+
@debugger_breakpoints_were_empty = false # Show breakpoints 1st time
|
70
|
+
@debugger_displays_were_empty = true # No display 1st time
|
71
|
+
@debugger_context_was_dead = true # Assume we haven't started.
|
72
|
+
end
|
73
|
+
|
74
|
+
def interface=(interface)
|
75
|
+
@mutex.synchronize do
|
76
|
+
@interface.close if @interface
|
77
|
+
@interface = interface
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
require 'pathname' # For cleanpath
|
82
|
+
|
83
|
+
# Regularize file name.
|
84
|
+
# This is also used as a common funnel place if basename is
|
85
|
+
# desired or if we are working remotely and want to change the
|
86
|
+
# basename. Or we are eliding filenames.
|
87
|
+
def self.canonic_file(filename)
|
88
|
+
# For now we want resolved filenames
|
89
|
+
if Command.settings[:basename]
|
90
|
+
File.basename(filename)
|
91
|
+
else
|
92
|
+
# Cache this?
|
93
|
+
Pathname.new(filename).cleanpath.to_s
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.print_location_and_text(file, line)
|
98
|
+
file_line = "%s:%s\n%s" % [canonic_file(file), line,
|
99
|
+
Debugger.line_at(file, line)]
|
100
|
+
# FIXME: use annotations routines
|
101
|
+
if Debugger.annotate.to_i > 2
|
102
|
+
file_line = "\032\032source #{file_line}"
|
103
|
+
elsif ENV['EMACS']
|
104
|
+
file_line = "\032\032#{file_line}"
|
105
|
+
end
|
106
|
+
print file_line
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.protect(mname)
|
110
|
+
alias_method "__#{mname}", mname
|
111
|
+
module_eval %{
|
112
|
+
def #{mname}(*args)
|
113
|
+
@mutex.synchronize do
|
114
|
+
return unless @interface
|
115
|
+
__#{mname}(*args)
|
116
|
+
end
|
117
|
+
rescue IOError, Errno::EPIPE
|
118
|
+
self.interface = nil
|
119
|
+
rescue SignalException
|
120
|
+
raise
|
121
|
+
rescue Exception
|
122
|
+
print "INTERNAL ERROR!!! #\{$!\}\n" rescue nil
|
123
|
+
print $!.backtrace.map{|l| "\t#\{l\}"}.join("\n") rescue nil
|
124
|
+
end
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def at_breakpoint(context, breakpoint)
|
129
|
+
aprint 'stopped' if Debugger.annotate.to_i > 2
|
130
|
+
n = Debugger.breakpoints.index(breakpoint) + 1
|
131
|
+
file = CommandProcessor.canonic_file(breakpoint.source)
|
132
|
+
line = breakpoint.pos
|
133
|
+
if Debugger.annotate.to_i > 2
|
134
|
+
print afmt("source #{file}:#{line}")
|
135
|
+
end
|
136
|
+
print "Breakpoint %d at %s:%s\n", n, file, line
|
137
|
+
end
|
138
|
+
protect :at_breakpoint
|
139
|
+
|
140
|
+
def at_catchpoint(context, excpt)
|
141
|
+
aprint 'stopped' if Debugger.annotate.to_i > 2
|
142
|
+
file = CommandProcessor.canonic_file(context.frame_file(0))
|
143
|
+
line = context.frame_line(0)
|
144
|
+
print afmt("%s:%d" % [file, line]) if ENV['EMACS']
|
145
|
+
print "Catchpoint at %s:%d: `%s' (%s)\n", file, line, excpt, excpt.class
|
146
|
+
fs = context.stack_size
|
147
|
+
tb = caller(0)[-fs..-1]
|
148
|
+
if tb
|
149
|
+
for i in tb
|
150
|
+
print "\tfrom %s\n", i
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
protect :at_catchpoint
|
155
|
+
|
156
|
+
def at_tracing(context, file, line)
|
157
|
+
return if defined?(Debugger::RDEBUG_FILE) &&
|
158
|
+
Debugger::RDEBUG_FILE == file # Don't trace ourself
|
159
|
+
@last_file = CommandProcessor.canonic_file(file)
|
160
|
+
file = CommandProcessor.canonic_file(file)
|
161
|
+
unless file == @last_file and @last_line == line and
|
162
|
+
Command.settings[:tracing_plus]
|
163
|
+
print "Tracing(%d):%s:%s %s",
|
164
|
+
context.thnum, file, line, Debugger.line_at(file, line)
|
165
|
+
@last_file = file
|
166
|
+
@last_line = line
|
167
|
+
end
|
168
|
+
always_run(context, file, line, 2)
|
169
|
+
end
|
170
|
+
protect :at_tracing
|
171
|
+
|
172
|
+
def at_line(context, file, line)
|
173
|
+
process_commands(context, file, line)
|
174
|
+
end
|
175
|
+
protect :at_line
|
176
|
+
|
177
|
+
def at_return(context, file, line)
|
178
|
+
context.stop_frame = -1
|
179
|
+
process_commands(context, file, line)
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
# The prompt shown before reading a command.
|
185
|
+
def prompt(context)
|
186
|
+
p = '(rdb:%s) ' % (context.dead? ? 'post-mortem' : context.thnum)
|
187
|
+
p = afmt("pre-prompt")+p+"\n"+afmt("prompt") if
|
188
|
+
Debugger.annotate.to_i > 2
|
189
|
+
return p
|
190
|
+
end
|
191
|
+
|
192
|
+
# Run these commands, for example display commands or possibly
|
193
|
+
# the list or irb in an "autolist" or "autoirb".
|
194
|
+
# We return a list of commands that are acceptable to run bound
|
195
|
+
# to the current state.
|
196
|
+
def always_run(context, file, line, run_level)
|
197
|
+
event_cmds = Command.commands.select{|cmd| cmd.event }
|
198
|
+
|
199
|
+
# Remove some commands in post-mortem
|
200
|
+
event_cmds = event_cmds.find_all do |cmd|
|
201
|
+
cmd.allow_in_post_mortem
|
202
|
+
end if context.dead?
|
203
|
+
|
204
|
+
state = State.new do |s|
|
205
|
+
s.context = context
|
206
|
+
s.file = file
|
207
|
+
s.line = line
|
208
|
+
s.binding = context.frame_binding(0)
|
209
|
+
s.display = display
|
210
|
+
s.interface = interface
|
211
|
+
s.commands = event_cmds
|
212
|
+
end
|
213
|
+
@interface.state = state if @interface.respond_to?('state=')
|
214
|
+
|
215
|
+
# Bind commands to the current state.
|
216
|
+
commands = event_cmds.map{|cmd| cmd.new(state)}
|
217
|
+
|
218
|
+
commands.select do |cmd|
|
219
|
+
cmd.class.always_run >= run_level
|
220
|
+
end.each {|cmd| cmd.execute}
|
221
|
+
return state, commands
|
222
|
+
end
|
223
|
+
|
224
|
+
# Handle debugger commands
|
225
|
+
def process_commands(context, file, line)
|
226
|
+
state, commands = always_run(context, file, line, 1)
|
227
|
+
$rdebug_state = state if Command.settings[:debuggertesting]
|
228
|
+
splitter = lambda do |str|
|
229
|
+
str.split(/;/).inject([]) do |m, v|
|
230
|
+
if m.empty?
|
231
|
+
m << v
|
232
|
+
else
|
233
|
+
if m.last[-1] == ?\\
|
234
|
+
m.last[-1,1] = ''
|
235
|
+
m.last << ';' << v
|
236
|
+
else
|
237
|
+
m << v
|
238
|
+
end
|
239
|
+
end
|
240
|
+
m
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
preloop(commands, context)
|
245
|
+
CommandProcessor.print_location_and_text(file, line)
|
246
|
+
while !state.proceed?
|
247
|
+
input = if @interface.command_queue.empty?
|
248
|
+
@interface.read_command(prompt(context))
|
249
|
+
else
|
250
|
+
@interface.command_queue.shift
|
251
|
+
end
|
252
|
+
break unless input
|
253
|
+
catch(:debug_error) do
|
254
|
+
if input == ""
|
255
|
+
next unless @last_cmd
|
256
|
+
input = @last_cmd
|
257
|
+
else
|
258
|
+
@last_cmd = input
|
259
|
+
end
|
260
|
+
splitter[input].each do |cmd|
|
261
|
+
one_cmd(commands, context, cmd)
|
262
|
+
postcmd(commands, context, cmd)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
postloop(commands, context)
|
267
|
+
end # process_commands
|
268
|
+
|
269
|
+
def one_cmd(commands, context, input)
|
270
|
+
if cmd = commands.find{ |c| c.match(input) }
|
271
|
+
if context.dead? && cmd.class.need_context
|
272
|
+
p cmd
|
273
|
+
print "Command is unavailable\n"
|
274
|
+
else
|
275
|
+
cmd.execute
|
276
|
+
end
|
277
|
+
else
|
278
|
+
unknown_cmd = commands.find{|cmd| cmd.class.unknown }
|
279
|
+
if unknown_cmd
|
280
|
+
unknown_cmd.execute
|
281
|
+
else
|
282
|
+
errmsg "Unknown command: \"#{input}\". Try \"help\".\n"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def preloop(commands, context)
|
288
|
+
aprint('stopped') if Debugger.annotate.to_i > 2
|
289
|
+
if context.dead?
|
290
|
+
unless @debugger_context_was_dead
|
291
|
+
if Debugger.annotate.to_i > 2
|
292
|
+
aprint('exited')
|
293
|
+
print "The program finished.\n"
|
294
|
+
end
|
295
|
+
@debugger_context_was_dead = true
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
if Debugger.annotate.to_i > 2
|
300
|
+
# if we are here, the stack frames have changed outside the
|
301
|
+
# command loop (e.g. after a "continue" command), so we show
|
302
|
+
# the annotations again
|
303
|
+
breakpoint_annotations(commands, context)
|
304
|
+
display_annotations(commands, context)
|
305
|
+
annotation('stack', commands, context, "where")
|
306
|
+
annotation('variables', commands, context, "info variables") unless
|
307
|
+
context.dead?
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def postcmd(commands, context, cmd)
|
312
|
+
if Debugger.annotate.to_i > 0
|
313
|
+
cmd = @last_cmd unless cmd
|
314
|
+
breakpoint_annotations(commands, context) if
|
315
|
+
@@Show_breakpoints_postcmd.find{|pat| cmd =~ pat}
|
316
|
+
display_annotations(commands, context)
|
317
|
+
if @@Show_annotations_postcmd.find{|pat| cmd =~ pat}
|
318
|
+
annotation('stack', commands, context, "where") if
|
319
|
+
context.stack_size > 0
|
320
|
+
annotation('variables', commands, context, "info variables") unless
|
321
|
+
context.dead?
|
322
|
+
end
|
323
|
+
if not context.dead? and @@Show_annotations_run.find{|pat| cmd =~ pat}
|
324
|
+
aprint 'starting' if Debugger.annotate.to_i > 2
|
325
|
+
|
326
|
+
@debugger_context_was_dead = false
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def postloop(commands, context)
|
332
|
+
end
|
333
|
+
|
334
|
+
def annotation(label, commands, context, cmd)
|
335
|
+
print afmt(label)
|
336
|
+
one_cmd(commands, context, cmd)
|
337
|
+
### FIXME ANNOTATE: the following line should be deleted
|
338
|
+
print "\032\032\n"
|
339
|
+
end
|
340
|
+
|
341
|
+
def breakpoint_annotations(commands, context)
|
342
|
+
unless Debugger.breakpoints.empty? and @debugger_breakpoints_were_empty
|
343
|
+
annotation('breakpoints', commands, context, "info breakpoints")
|
344
|
+
@debugger_breakpoints_were_empty = Debugger.breakpoints.empty?
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def display_annotations(commands, context)
|
349
|
+
return if display.empty?
|
350
|
+
# have_display = display.find{|d| d[0]}
|
351
|
+
# return unless have_display and @debugger_displays_were_empty
|
352
|
+
# @debugger_displays_were_empty = have_display
|
353
|
+
annotation('display', commands, context, "display")
|
354
|
+
end
|
355
|
+
|
356
|
+
class State # :nodoc:
|
357
|
+
attr_accessor :context, :file, :line, :binding
|
358
|
+
attr_accessor :frame_pos, :previous_line, :display
|
359
|
+
attr_accessor :interface, :commands
|
360
|
+
|
361
|
+
def initialize
|
362
|
+
super()
|
363
|
+
@frame_pos = 0
|
364
|
+
@previous_line = nil
|
365
|
+
@proceed = false
|
366
|
+
yield self
|
367
|
+
end
|
368
|
+
|
369
|
+
# FIXME: use delegate?
|
370
|
+
def errmsg(*args)
|
371
|
+
@interface.errmsg(*args)
|
372
|
+
end
|
373
|
+
|
374
|
+
def print(*args)
|
375
|
+
@interface.print(*args)
|
376
|
+
end
|
377
|
+
|
378
|
+
def confirm(*args)
|
379
|
+
@interface.confirm(*args)
|
380
|
+
end
|
381
|
+
|
382
|
+
def proceed?
|
383
|
+
@proceed
|
384
|
+
end
|
385
|
+
|
386
|
+
def proceed
|
387
|
+
@proceed = true
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
class ControlCommandProcessor < Processor # :nodoc:
|
393
|
+
def initialize(interface)
|
394
|
+
super()
|
395
|
+
@interface = interface
|
396
|
+
@debugger_context_was_dead = true # Assume we haven't started.
|
397
|
+
end
|
398
|
+
|
399
|
+
def process_commands(verbose=false)
|
400
|
+
control_cmds = Command.commands.select do |cmd|
|
401
|
+
cmd.allow_in_control
|
402
|
+
end
|
403
|
+
state = State.new(@interface, control_cmds)
|
404
|
+
commands = control_cmds.map{|cmd| cmd.new(state) }
|
405
|
+
|
406
|
+
unless @debugger_context_was_dead
|
407
|
+
if Debugger.annotate.to_i > 2
|
408
|
+
aprint 'exited'
|
409
|
+
print "The program finished.\n"
|
410
|
+
end
|
411
|
+
@debugger_context_was_dead = true
|
412
|
+
end
|
413
|
+
|
414
|
+
while input = @interface.read_command(prompt(nil))
|
415
|
+
print "+#{input}" if verbose
|
416
|
+
catch(:debug_error) do
|
417
|
+
if cmd = commands.find{|c| c.match(input) }
|
418
|
+
cmd.execute
|
419
|
+
else
|
420
|
+
errmsg "Unknown command\n"
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
rescue IOError, Errno::EPIPE
|
425
|
+
rescue Exception
|
426
|
+
print "INTERNAL ERROR!!! #{$!}\n" rescue nil
|
427
|
+
print $!.backtrace.map{|l| "\t#{l}"}.join("\n") rescue nil
|
428
|
+
ensure
|
429
|
+
@interface.close
|
430
|
+
end
|
431
|
+
|
432
|
+
# The prompt shown before reading a command.
|
433
|
+
# Note: have an unused 'context' parameter to match the local interface.
|
434
|
+
def prompt(context)
|
435
|
+
p = '(rdb:ctrl) '
|
436
|
+
p = afmt("pre-prompt")+p+"\n"+afmt("prompt") if
|
437
|
+
Debugger.annotate.to_i > 2
|
438
|
+
return p
|
439
|
+
end
|
440
|
+
|
441
|
+
class State # :nodoc:
|
442
|
+
attr_reader :commands, :interface
|
443
|
+
|
444
|
+
def initialize(interface, commands)
|
445
|
+
@interface = interface
|
446
|
+
@commands = commands
|
447
|
+
end
|
448
|
+
|
449
|
+
def proceed
|
450
|
+
end
|
451
|
+
|
452
|
+
def errmsg(*args)
|
453
|
+
@interface.print(*args)
|
454
|
+
end
|
455
|
+
|
456
|
+
def print(*args)
|
457
|
+
@interface.print(*args)
|
458
|
+
end
|
459
|
+
|
460
|
+
def confirm(*args)
|
461
|
+
'y'
|
462
|
+
end
|
463
|
+
|
464
|
+
def context
|
465
|
+
nil
|
466
|
+
end
|
467
|
+
|
468
|
+
def file
|
469
|
+
errmsg "No filename given.\n"
|
470
|
+
throw :debug_error
|
471
|
+
end
|
472
|
+
end # State
|
473
|
+
end
|
474
|
+
end
|