rubinius-debugger 2.0.0
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/rubinius/debugger.rb +486 -0
- data/lib/rubinius/debugger/breakpoint.rb +147 -0
- data/lib/rubinius/debugger/commands.rb +745 -0
- data/lib/rubinius/debugger/display.rb +27 -0
- data/lib/rubinius/debugger/frame.rb +78 -0
- data/lib/rubinius/debugger/version.rb +5 -0
- data/rubinius-debugger.gemspec +23 -0
- metadata +85 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
class Rubinius::Debugger
|
2
|
+
class BreakPoint
|
3
|
+
|
4
|
+
def self.for_ip(exec, ip, name=:anon)
|
5
|
+
line = exec.line_from_ip(ip)
|
6
|
+
|
7
|
+
BreakPoint.new(name, exec, ip, line)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(descriptor, method, ip, line, condition=nil)
|
11
|
+
@descriptor = descriptor
|
12
|
+
@method = method
|
13
|
+
@ip = ip
|
14
|
+
@line = line
|
15
|
+
@for_step = false
|
16
|
+
@paired_bp = nil
|
17
|
+
@temp = false
|
18
|
+
@commands = nil
|
19
|
+
@condition = condition
|
20
|
+
|
21
|
+
@set = false
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :method, :ip, :line, :paired_bp, :descriptor, :commands, :condition
|
25
|
+
|
26
|
+
def location
|
27
|
+
"#{@method.active_path}:#{@line} (+#{ip})"
|
28
|
+
end
|
29
|
+
|
30
|
+
def describe
|
31
|
+
"#{descriptor} - #{location}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def for_step!(scope)
|
35
|
+
@temp = true
|
36
|
+
@for_step = scope
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_temp!
|
40
|
+
@temp = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def for_step?
|
44
|
+
@for_step
|
45
|
+
end
|
46
|
+
|
47
|
+
def paired_with(bp)
|
48
|
+
@paired_bp = bp
|
49
|
+
end
|
50
|
+
|
51
|
+
def activate
|
52
|
+
@set = true
|
53
|
+
@method.set_breakpoint @ip, self
|
54
|
+
end
|
55
|
+
|
56
|
+
def remove!
|
57
|
+
return unless @set
|
58
|
+
|
59
|
+
@set = false
|
60
|
+
@method.clear_breakpoint(@ip)
|
61
|
+
end
|
62
|
+
|
63
|
+
def hit!(loc)
|
64
|
+
return true unless @temp
|
65
|
+
|
66
|
+
if @for_step
|
67
|
+
return false unless loc.variables == @for_step
|
68
|
+
end
|
69
|
+
|
70
|
+
remove!
|
71
|
+
|
72
|
+
@paired_bp.remove! if @paired_bp
|
73
|
+
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete!
|
78
|
+
remove!
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_commands(commands)
|
82
|
+
@commands = commands
|
83
|
+
end
|
84
|
+
|
85
|
+
def has_commands?
|
86
|
+
!@commands.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_condition(condition)
|
90
|
+
@condition = condition
|
91
|
+
end
|
92
|
+
|
93
|
+
def has_condition?
|
94
|
+
!@condition.nil?
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class DeferredBreakPoint
|
99
|
+
def initialize(debugger, frame, klass, which, name, line=nil, list=nil)
|
100
|
+
@debugger = debugger
|
101
|
+
@frame = frame
|
102
|
+
@klass_name = klass
|
103
|
+
@which = which
|
104
|
+
@name = name
|
105
|
+
@line = line
|
106
|
+
@list = list
|
107
|
+
end
|
108
|
+
|
109
|
+
def descriptor
|
110
|
+
"#{@klass_name}#{@which}#{@name}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def resolve!
|
114
|
+
begin
|
115
|
+
klass = @frame.run(@klass_name)
|
116
|
+
rescue NameError
|
117
|
+
return false
|
118
|
+
end
|
119
|
+
|
120
|
+
begin
|
121
|
+
if @which == "#"
|
122
|
+
method = klass.instance_method(@name)
|
123
|
+
else
|
124
|
+
method = klass.method(@name)
|
125
|
+
end
|
126
|
+
rescue NameError
|
127
|
+
return false
|
128
|
+
end
|
129
|
+
|
130
|
+
@debugger.info "Resolved breakpoint for #{@klass_name}#{@which}#{@name}"
|
131
|
+
|
132
|
+
@debugger.set_breakpoint_method descriptor, method, @line
|
133
|
+
|
134
|
+
return true
|
135
|
+
end
|
136
|
+
|
137
|
+
def describe
|
138
|
+
"#{descriptor} - unknown location (deferred)"
|
139
|
+
end
|
140
|
+
|
141
|
+
def delete!
|
142
|
+
if @list
|
143
|
+
@list.delete self
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,745 @@
|
|
1
|
+
require 'rubinius/debugger/display'
|
2
|
+
|
3
|
+
class Rubinius::Debugger
|
4
|
+
class CommandDescription
|
5
|
+
attr_accessor :klass, :patterns, :help, :ext_help
|
6
|
+
|
7
|
+
def initialize(klass)
|
8
|
+
@klass = klass
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
@klass.name
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Command
|
17
|
+
include Rubinius::Debugger::Display
|
18
|
+
|
19
|
+
@commands = []
|
20
|
+
|
21
|
+
def self.commands
|
22
|
+
@commands
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.descriptor
|
26
|
+
@descriptor ||= CommandDescription.new(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.pattern(*strs)
|
30
|
+
Command.commands << self
|
31
|
+
descriptor.patterns = strs
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.help(str)
|
35
|
+
descriptor.help = str
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.ext_help(str)
|
39
|
+
descriptor.ext_help = str
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.match?(cmd)
|
43
|
+
descriptor.patterns.include?(cmd)
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(debugger)
|
47
|
+
@debugger = debugger
|
48
|
+
end
|
49
|
+
|
50
|
+
def run_code(str)
|
51
|
+
@debugger.current_frame.run(str)
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_method
|
55
|
+
@debugger.current_frame.method
|
56
|
+
end
|
57
|
+
|
58
|
+
def current_frame
|
59
|
+
@debugger.current_frame
|
60
|
+
end
|
61
|
+
|
62
|
+
def variables
|
63
|
+
@debugger.variables
|
64
|
+
end
|
65
|
+
|
66
|
+
def listen(step=false)
|
67
|
+
@debugger.listen(step)
|
68
|
+
end
|
69
|
+
|
70
|
+
# ===== Commands =====
|
71
|
+
#
|
72
|
+
# These classes are in the order they should appear in the help output.
|
73
|
+
# As such, they're grouped by similar action.
|
74
|
+
|
75
|
+
class Help < Command
|
76
|
+
pattern "help", "h"
|
77
|
+
help "Show information about debugger commands"
|
78
|
+
|
79
|
+
def run(args)
|
80
|
+
|
81
|
+
if args and !args.empty?
|
82
|
+
klass = Command.commands.find { |k| k.match?(args.strip) }
|
83
|
+
if klass
|
84
|
+
des = klass.descriptor
|
85
|
+
puts "Help for #{des.name}:"
|
86
|
+
puts " Accessed using: #{des.patterns.join(', ')}"
|
87
|
+
puts "\n#{des.help}."
|
88
|
+
puts "\n#{des.ext_help}" if des.ext_help
|
89
|
+
else
|
90
|
+
puts "Unknown command: #{args}"
|
91
|
+
end
|
92
|
+
else
|
93
|
+
Command.commands.each do |klass|
|
94
|
+
des = klass.descriptor
|
95
|
+
|
96
|
+
puts "%20s: #{des.help}" % des.patterns.join(', ')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
class SetBreakPoint < Command
|
104
|
+
pattern "b", "break", "brk"
|
105
|
+
help "Set a breakpoint at a point in a method"
|
106
|
+
ext_help <<-HELP
|
107
|
+
The breakpoint must be specified using the following notation:
|
108
|
+
Klass[.#]method:line
|
109
|
+
|
110
|
+
Thus, to set a breakpoint for the instance method pop in
|
111
|
+
Array on line 33, use:
|
112
|
+
Array#pop:33
|
113
|
+
|
114
|
+
To breakpoint on class method start of Debugger line 4, use:
|
115
|
+
Debugger.start:4
|
116
|
+
|
117
|
+
Conditional breakpoints can be created in this way:
|
118
|
+
<breakpoint declaration> if <condition>
|
119
|
+
The breakpoint will be triggered only when the evaluation of the specified condition returns true.
|
120
|
+
HELP
|
121
|
+
|
122
|
+
# provide this method so it can be overriden for other languages wanting to use this debugger
|
123
|
+
def match_method(method_identifier)
|
124
|
+
/([A-Z]\w*(?:::[A-Z]\w*)*)([.#]|::)([a-zA-Z0-9_\[\]]+[!?=]?)(?:[:](\d+))?(\s+if\s+.*)?/.match(method_identifier)
|
125
|
+
end
|
126
|
+
|
127
|
+
def run(args, temp=false)
|
128
|
+
m = match_method(args)
|
129
|
+
unless m
|
130
|
+
error "Unrecognized position: '#{args}'"
|
131
|
+
return
|
132
|
+
end
|
133
|
+
|
134
|
+
klass_name = m[1]
|
135
|
+
which = m[2]
|
136
|
+
name = m[3]
|
137
|
+
line = m[4] ? m[4].to_i : nil
|
138
|
+
condition = m[5] ? m[5].sub(/\A\s+if\s+/, '') : nil
|
139
|
+
|
140
|
+
begin
|
141
|
+
klass = run_code(klass_name)
|
142
|
+
rescue NameError
|
143
|
+
error "Unable to find class/module: #{m[1]}"
|
144
|
+
ask_deferred klass_name, which, name, line
|
145
|
+
return
|
146
|
+
end
|
147
|
+
|
148
|
+
begin
|
149
|
+
if which == "#"
|
150
|
+
method = klass.instance_method(name)
|
151
|
+
else
|
152
|
+
method = klass.method(name)
|
153
|
+
end
|
154
|
+
rescue NameError
|
155
|
+
error "Unable to find method '#{name}' in #{klass}"
|
156
|
+
ask_deferred klass_name, which, name, line
|
157
|
+
return
|
158
|
+
end
|
159
|
+
|
160
|
+
bp = @debugger.set_breakpoint_method args.strip, method, line, condition
|
161
|
+
|
162
|
+
bp.set_temp! if temp
|
163
|
+
|
164
|
+
return bp
|
165
|
+
end
|
166
|
+
|
167
|
+
def ask_deferred(klass_name, which, name, line)
|
168
|
+
answer = ask "Would you like to defer this breakpoint to later? [y/n] "
|
169
|
+
|
170
|
+
if answer.strip.downcase[0] == ?y
|
171
|
+
@debugger.add_deferred_breakpoint(klass_name, which, name, line)
|
172
|
+
info "Deferred breakpoint created."
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
class SetTempBreakPoint < SetBreakPoint
|
179
|
+
pattern "tb", "tbreak", "tbrk"
|
180
|
+
help "Set a temporary breakpoint"
|
181
|
+
ext_help "Same as break, but the breakpoint is deleted when it is hit"
|
182
|
+
|
183
|
+
def run(args)
|
184
|
+
super args, true
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class DeleteBreakpoint < Command
|
189
|
+
pattern "d", "del", "delete"
|
190
|
+
help "Delete a breakpoint"
|
191
|
+
ext_help "Specify the breakpoint by number, use 'info break' to see the numbers"
|
192
|
+
|
193
|
+
def run(args)
|
194
|
+
if !args or args.empty?
|
195
|
+
error "Please specify which breakpoint by number"
|
196
|
+
return
|
197
|
+
end
|
198
|
+
|
199
|
+
begin
|
200
|
+
i = Integer(args.strip)
|
201
|
+
rescue ArgumentError
|
202
|
+
error "'#{args}' is not a number"
|
203
|
+
return
|
204
|
+
end
|
205
|
+
|
206
|
+
@debugger.delete_breakpoint(i)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class Next < Command
|
211
|
+
pattern "n", "next"
|
212
|
+
help "Move to the next line or conditional branch"
|
213
|
+
ext_help <<-HELP
|
214
|
+
Attempt to continue execution and stop at the next line. If there is
|
215
|
+
a conditional branch between the current position and the next line,
|
216
|
+
execution is stopped within the conditional branch instead.
|
217
|
+
|
218
|
+
The optional argument is a number which specifies how many lines to
|
219
|
+
attempt to skip past before stopping execution.
|
220
|
+
|
221
|
+
If the current line is the last in a method, execution is stopped
|
222
|
+
at the current position of the caller.
|
223
|
+
HELP
|
224
|
+
|
225
|
+
def run(args)
|
226
|
+
if !args or args.empty?
|
227
|
+
step = 1
|
228
|
+
else
|
229
|
+
step = args.to_i
|
230
|
+
end
|
231
|
+
|
232
|
+
if step <= 0
|
233
|
+
error "Invalid step count - #{step}"
|
234
|
+
return
|
235
|
+
end
|
236
|
+
|
237
|
+
step_over_by(step)
|
238
|
+
@debugger.listen
|
239
|
+
end
|
240
|
+
|
241
|
+
def step_over_by(step)
|
242
|
+
f = current_frame
|
243
|
+
|
244
|
+
ip = -1
|
245
|
+
|
246
|
+
exec = f.method
|
247
|
+
possible_line = f.line + step
|
248
|
+
fin_ip = exec.first_ip_on_line possible_line, f.ip
|
249
|
+
|
250
|
+
if !fin_ip
|
251
|
+
return step_to_parent
|
252
|
+
end
|
253
|
+
|
254
|
+
set_breakpoints_between(exec, f.ip, fin_ip)
|
255
|
+
end
|
256
|
+
|
257
|
+
def step_to_parent
|
258
|
+
f = @debugger.frame(current_frame.number + 1)
|
259
|
+
unless f
|
260
|
+
info "Unable to find frame to step to next"
|
261
|
+
return
|
262
|
+
end
|
263
|
+
|
264
|
+
exec = f.method
|
265
|
+
ip = f.ip
|
266
|
+
|
267
|
+
bp = BreakPoint.for_ip(exec, ip)
|
268
|
+
bp.for_step!(f.variables)
|
269
|
+
bp.activate
|
270
|
+
|
271
|
+
return bp
|
272
|
+
end
|
273
|
+
|
274
|
+
def set_breakpoints_between(exec, start_ip, fin_ip)
|
275
|
+
ips = goto_between(exec, start_ip, fin_ip)
|
276
|
+
if ips.kind_of? Fixnum
|
277
|
+
ip = ips
|
278
|
+
else
|
279
|
+
one, two = ips
|
280
|
+
bp1 = BreakPoint.for_ip(exec, one)
|
281
|
+
bp2 = BreakPoint.for_ip(exec, two)
|
282
|
+
|
283
|
+
bp1.paired_with(bp2)
|
284
|
+
bp2.paired_with(bp1)
|
285
|
+
|
286
|
+
bp1.for_step!(current_frame.variables)
|
287
|
+
bp2.for_step!(current_frame.variables)
|
288
|
+
|
289
|
+
bp1.activate
|
290
|
+
bp2.activate
|
291
|
+
|
292
|
+
return bp1
|
293
|
+
end
|
294
|
+
|
295
|
+
if ip == -1
|
296
|
+
error "No place to step to"
|
297
|
+
return nil
|
298
|
+
end
|
299
|
+
|
300
|
+
bp = BreakPoint.for_ip(exec, ip)
|
301
|
+
bp.for_step!(current_frame.variables)
|
302
|
+
bp.activate
|
303
|
+
|
304
|
+
return bp
|
305
|
+
end
|
306
|
+
|
307
|
+
def next_interesting(exec, ip)
|
308
|
+
pop = Rubinius::InstructionSet.opcodes_map[:pop]
|
309
|
+
|
310
|
+
if exec.iseq[ip] == pop
|
311
|
+
return ip + 1
|
312
|
+
end
|
313
|
+
|
314
|
+
return ip
|
315
|
+
end
|
316
|
+
|
317
|
+
def goto_between(exec, start, fin)
|
318
|
+
goto = Rubinius::InstructionSet.opcodes_map[:goto]
|
319
|
+
git = Rubinius::InstructionSet.opcodes_map[:goto_if_true]
|
320
|
+
gif = Rubinius::InstructionSet.opcodes_map[:goto_if_false]
|
321
|
+
|
322
|
+
iseq = exec.iseq
|
323
|
+
|
324
|
+
i = start
|
325
|
+
while i < fin
|
326
|
+
op = iseq[i]
|
327
|
+
case op
|
328
|
+
when goto
|
329
|
+
return next_interesting(exec, iseq[i + 1]) # goto target
|
330
|
+
when git, gif
|
331
|
+
return [next_interesting(exec, iseq[i + 1]),
|
332
|
+
next_interesting(exec, i + 2)] # target and next ip
|
333
|
+
else
|
334
|
+
op = Rubinius::InstructionSet[op]
|
335
|
+
i += (op.arg_count + 1)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
return next_interesting(exec, fin)
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
|
344
|
+
class StepInto < Next
|
345
|
+
pattern "s", "step"
|
346
|
+
help "Step into next method call or to next line"
|
347
|
+
ext_help <<-HELP
|
348
|
+
Behaves like next, but if there is a method call on the current line,
|
349
|
+
execution is stopped in the called method.
|
350
|
+
HELP
|
351
|
+
|
352
|
+
def run(args)
|
353
|
+
max = step_over_by(1)
|
354
|
+
|
355
|
+
listen(true)
|
356
|
+
|
357
|
+
# We remove the max position breakpoint no matter what
|
358
|
+
max.remove! if max
|
359
|
+
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
class NextInstruction < Next
|
364
|
+
pattern "ni", "nexti"
|
365
|
+
help "Move to the next bytecode instruction"
|
366
|
+
ext_help <<-HELP
|
367
|
+
Continue but stop execution at the next bytecode instruction.
|
368
|
+
|
369
|
+
Does not step into send instructions.
|
370
|
+
HELP
|
371
|
+
|
372
|
+
def run(args)
|
373
|
+
if args and !args.empty?
|
374
|
+
step = args.to_i
|
375
|
+
else
|
376
|
+
step = 1
|
377
|
+
end
|
378
|
+
|
379
|
+
exec = current_method
|
380
|
+
insn = Rubinius::InstructionSet[exec.iseq[current_frame.ip]]
|
381
|
+
|
382
|
+
next_ip = current_frame.ip + insn.width
|
383
|
+
|
384
|
+
if next_ip >= exec.iseq.size
|
385
|
+
step_to_parent
|
386
|
+
elsif is_a_goto(exec, current_frame.ip)
|
387
|
+
set_breakpoints_between(exec, current_frame.ip, next_ip)
|
388
|
+
else
|
389
|
+
line = exec.line_from_ip(next_ip)
|
390
|
+
|
391
|
+
bp = BreakPoint.for_ip(exec, next_ip)
|
392
|
+
bp.for_step!(current_frame.variables)
|
393
|
+
bp.activate
|
394
|
+
end
|
395
|
+
|
396
|
+
listen
|
397
|
+
end
|
398
|
+
|
399
|
+
def is_a_goto(exec, ip)
|
400
|
+
goto = Rubinius::InstructionSet.opcodes_map[:goto]
|
401
|
+
git = Rubinius::InstructionSet.opcodes_map[:goto_if_true]
|
402
|
+
gif = Rubinius::InstructionSet.opcodes_map[:goto_if_false]
|
403
|
+
|
404
|
+
i = exec.iseq[ip]
|
405
|
+
|
406
|
+
case i
|
407
|
+
when goto, git, gif
|
408
|
+
return true
|
409
|
+
end
|
410
|
+
|
411
|
+
return false
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
class SetFrame < Command
|
416
|
+
pattern "f", "frame"
|
417
|
+
help "Make a specific frame in the call stack the current frame"
|
418
|
+
ext_help <<-HELP
|
419
|
+
The argument must be a number corrisponding to the frame numbers reported by
|
420
|
+
'bt'.
|
421
|
+
|
422
|
+
The frame specified is made the current frame.
|
423
|
+
HELP
|
424
|
+
|
425
|
+
def run(args)
|
426
|
+
unless m = /(\d+)/.match(args)
|
427
|
+
error "Invalid frame number: #{args}"
|
428
|
+
return
|
429
|
+
end
|
430
|
+
|
431
|
+
num = m[1].to_i
|
432
|
+
|
433
|
+
if num >= @debugger.locations.size
|
434
|
+
error "Frame #{num} too big"
|
435
|
+
return
|
436
|
+
end
|
437
|
+
|
438
|
+
@debugger.set_frame(num)
|
439
|
+
|
440
|
+
info current_frame.describe
|
441
|
+
@debugger.show_code
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
class Continue < Command
|
446
|
+
pattern "c", "cont", "continue"
|
447
|
+
help "Continue running the target thread"
|
448
|
+
ext_help <<-HELP
|
449
|
+
Continue execution until another breakpoint is hit.
|
450
|
+
HELP
|
451
|
+
|
452
|
+
def run(args)
|
453
|
+
listen
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
class Backtrace < Command
|
458
|
+
pattern "bt", "backtrace"
|
459
|
+
help "Show the current call stack"
|
460
|
+
ext_help <<-HELP
|
461
|
+
Show the call stack as a simple list.
|
462
|
+
|
463
|
+
Passing "-v" will also show the values of all locals variables
|
464
|
+
in each frame.
|
465
|
+
HELP
|
466
|
+
|
467
|
+
def run(args)
|
468
|
+
verbose = (args =~ /-v/)
|
469
|
+
|
470
|
+
if m = /(\d+)/.match(args)
|
471
|
+
count = m[1].to_i
|
472
|
+
else
|
473
|
+
count = nil
|
474
|
+
end
|
475
|
+
|
476
|
+
info "Backtrace:"
|
477
|
+
|
478
|
+
@debugger.each_frame(current_frame) do |frame|
|
479
|
+
return if count and frame.number >= count
|
480
|
+
|
481
|
+
info "%4d %s" % [frame.number, frame.describe]
|
482
|
+
|
483
|
+
if verbose
|
484
|
+
frame.local_variables.each do |local|
|
485
|
+
info " #{local} = #{frame.run(local.to_s).inspect}"
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
class EvalCode < Command
|
493
|
+
pattern "p", "eval"
|
494
|
+
help "Run code in the current context"
|
495
|
+
ext_help <<-HELP
|
496
|
+
Run code in the context of the current frame.
|
497
|
+
|
498
|
+
The value of the expression is stored into a global variable so it
|
499
|
+
may be used again easily. The name of the global variable is printed
|
500
|
+
next to the inspect output of the value.
|
501
|
+
HELP
|
502
|
+
|
503
|
+
def run(args)
|
504
|
+
@debugger.eval_code(args)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
class Disassemble < Command
|
509
|
+
pattern "dis", "disassemble"
|
510
|
+
help "Show the bytecode for the current line or method"
|
511
|
+
ext_help <<-HELP
|
512
|
+
Disassemble bytecode for the current method. By default, the bytecode
|
513
|
+
for the current line is disassembled only.
|
514
|
+
|
515
|
+
If the argument is 'all', the entire method is shown as bytecode.
|
516
|
+
HELP
|
517
|
+
|
518
|
+
def run(args)
|
519
|
+
if args and args.strip == "all"
|
520
|
+
section "Bytecode for #{current_frame.method.name}"
|
521
|
+
puts current_method.decode
|
522
|
+
else
|
523
|
+
@debugger.show_bytecode
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
class ShowInfo < Command
|
529
|
+
pattern "i", "info"
|
530
|
+
help "Show information about things"
|
531
|
+
ext_help <<-HELP
|
532
|
+
Subcommands are:
|
533
|
+
break, breakpoints, bp: List all breakpoints
|
534
|
+
HELP
|
535
|
+
|
536
|
+
def run(args)
|
537
|
+
if args
|
538
|
+
case args.strip
|
539
|
+
when "break", "breakpoints", "bp"
|
540
|
+
section "Breakpoints"
|
541
|
+
if @debugger.breakpoints.empty?
|
542
|
+
info "No breakpoints set"
|
543
|
+
end
|
544
|
+
|
545
|
+
@debugger.breakpoints.each_with_index do |bp, i|
|
546
|
+
if bp
|
547
|
+
info "%3d: %s" % [i+1, bp.describe]
|
548
|
+
if bp.has_commands?
|
549
|
+
info " #{bp.commands}"
|
550
|
+
end
|
551
|
+
if bp.has_condition?
|
552
|
+
info " stop only if #{bp.condition}"
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
else
|
557
|
+
error "Unknown info: '#{args}'"
|
558
|
+
end
|
559
|
+
else
|
560
|
+
error "No info subcommand"
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
class SetVariable < Command
|
566
|
+
pattern "set"
|
567
|
+
help "Set a debugger config variable"
|
568
|
+
ext_help <<-HELP
|
569
|
+
Set a debugger configuration variable. Use 'show' to see all variables.
|
570
|
+
HELP
|
571
|
+
|
572
|
+
def run(args)
|
573
|
+
var, val = args.split(/\s+/, 2)
|
574
|
+
|
575
|
+
if val
|
576
|
+
case val.strip
|
577
|
+
when "true", "on", "yes", ""
|
578
|
+
val = true
|
579
|
+
when "false", "off", "no"
|
580
|
+
val = false
|
581
|
+
when "nil"
|
582
|
+
val = nil
|
583
|
+
when /\d+/
|
584
|
+
val = val.to_i
|
585
|
+
end
|
586
|
+
else
|
587
|
+
val = true
|
588
|
+
end
|
589
|
+
|
590
|
+
info "Set '#{var}' = #{val.inspect}"
|
591
|
+
|
592
|
+
@debugger.variables[var.to_sym] = val
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
class ShowVariable < Command
|
597
|
+
pattern "show"
|
598
|
+
help "Display the value of a variable or variables"
|
599
|
+
ext_help <<-HELP
|
600
|
+
Show debugger variables and user created variables. By default,
|
601
|
+
shows all variables.
|
602
|
+
|
603
|
+
The optional argument is which variable specificly to show the value of.
|
604
|
+
HELP
|
605
|
+
|
606
|
+
def run(args)
|
607
|
+
if !args or args.strip.empty?
|
608
|
+
variables.each do |name, val|
|
609
|
+
info "var '#{name}' = #{val.inspect}"
|
610
|
+
end
|
611
|
+
|
612
|
+
if @debugger.user_variables > 0
|
613
|
+
section "User variables"
|
614
|
+
(0...@debugger.user_variables).each do |i|
|
615
|
+
str = "$d#{i}"
|
616
|
+
val = Rubinius::Globals[str.to_sym]
|
617
|
+
info "var #{str} = #{val.inspect}"
|
618
|
+
end
|
619
|
+
end
|
620
|
+
else
|
621
|
+
var = args.strip.to_sym
|
622
|
+
if variables.key?(var)
|
623
|
+
info "var '#{var}' = #{variables[var].inspect}"
|
624
|
+
else
|
625
|
+
error "No variable set named '#{var}'"
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
class Quit < Command
|
633
|
+
pattern "quit", "q", "exit", "ex"
|
634
|
+
help "Quit the debugger"
|
635
|
+
ext_help <<-HELP
|
636
|
+
Quits your current session and shuts down the complete process
|
637
|
+
HELP
|
638
|
+
|
639
|
+
def run(args)
|
640
|
+
Process.exit!(1)
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
class CommandsList < Command
|
645
|
+
pattern "commands", "command"
|
646
|
+
help "execute code every time breakpoint is reached"
|
647
|
+
ext_help <<-HELP
|
648
|
+
Set commands to be executed when a breakpoint is hit.
|
649
|
+
Give breakpoint number as argument after "commands".
|
650
|
+
With no argument, the targeted breakpoint is the last one set.
|
651
|
+
The commands themselves follow starting on the next line.
|
652
|
+
Type a line containing "end" to indicate the end of them.
|
653
|
+
Give "silent" as the first line to make the breakpoint silent;
|
654
|
+
then no output is printed when it is hit, except what the commands print.
|
655
|
+
HELP
|
656
|
+
|
657
|
+
def run(args)
|
658
|
+
bp = @debugger.breakpoints[args[:bp_id] - 1]
|
659
|
+
bp.set_commands(args[:code])
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
class Condition < Command
|
664
|
+
pattern "condition", "cond"
|
665
|
+
help "New condition expression on breakpoint N"
|
666
|
+
ext_help <<-HELP
|
667
|
+
Specify breakpoint number N to break only if COND is true.
|
668
|
+
Usage is `condition N COND', where N is an integer and COND is an
|
669
|
+
expression to be evaluated whenever breakpoint N is reached.
|
670
|
+
HELP
|
671
|
+
|
672
|
+
def run(args)
|
673
|
+
bp_id, condition = args.split(/\s+/, 2)
|
674
|
+
bp_id = bp_id.to_i
|
675
|
+
|
676
|
+
if @debugger.breakpoints.empty?
|
677
|
+
error "No breakpoint set"
|
678
|
+
return
|
679
|
+
elsif bp_id > @debugger.breakpoints.size || bp_id < 1
|
680
|
+
error "Invalid breakpoint number."
|
681
|
+
return
|
682
|
+
end
|
683
|
+
|
684
|
+
bp = @debugger.breakpoints[bp_id - 1]
|
685
|
+
bp.set_condition(condition)
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
class ListCode < Command
|
690
|
+
pattern "l", "list"
|
691
|
+
help "List code"
|
692
|
+
ext_help <<-HELP
|
693
|
+
List specified function or line.
|
694
|
+
With no argument, lists ten more lines after or around previous listing.
|
695
|
+
"list -" lists the ten lines before a previous ten-line listing.
|
696
|
+
One argument specifies a line, and ten lines are listed around that line.
|
697
|
+
Two arguments with comma between specify starting and ending lines to list.
|
698
|
+
Lines can be specified in these ways:
|
699
|
+
LINENUM, to list around that line in current file,
|
700
|
+
FILE:LINENUM, to list around that line in that file,
|
701
|
+
HELP
|
702
|
+
|
703
|
+
def run(args)
|
704
|
+
path = nil
|
705
|
+
line = nil
|
706
|
+
lines_around = 10
|
707
|
+
|
708
|
+
if args =~ /^[\w#{File::Separator}]+(\.rb)?:\d+$/
|
709
|
+
path, line = args.split(':')
|
710
|
+
line = line.to_i
|
711
|
+
elsif args.nil?
|
712
|
+
line = if @debugger.variables[:list_command_history][:center_line]
|
713
|
+
@debugger.variables[:list_command_history][:center_line] + 1 + lines_around
|
714
|
+
else
|
715
|
+
@debugger.current_frame.line.to_i
|
716
|
+
end
|
717
|
+
path = @debugger.variables[:list_command_history][:path] || @debugger.current_frame.method.active_path
|
718
|
+
elsif args == "-"
|
719
|
+
if @debugger.variables[:list_command_history][:center_line].nil? || @debugger.variables[:list_command_history][:path].nil?
|
720
|
+
return
|
721
|
+
else
|
722
|
+
line = @debugger.variables[:list_command_history][:center_line] - lines_around
|
723
|
+
path = @debugger.variables[:list_command_history][:path]
|
724
|
+
end
|
725
|
+
elsif args =~ /^\d+$/
|
726
|
+
line = args.to_i
|
727
|
+
path = @debugger.current_frame.method.active_path
|
728
|
+
elsif match = /^(\d+),(\d+)$/.match(args)
|
729
|
+
start_line = match[1].to_i
|
730
|
+
end_line = match[2].to_i
|
731
|
+
path = @debugger.current_frame.method.active_path
|
732
|
+
|
733
|
+
@debugger.list_code_range(path, start_line, end_line, end_line)
|
734
|
+
return
|
735
|
+
else
|
736
|
+
error 'Invalid args for list'
|
737
|
+
return
|
738
|
+
end
|
739
|
+
|
740
|
+
@debugger.list_code_around_line(path, line, lines_around)
|
741
|
+
end
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
end
|