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