forked from hardenedapple/gdb-walkers
/
commands.py
873 lines (699 loc) · 30.2 KB
/
commands.py
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
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
'''
Miscellaneous gdb helper commands.
The commands defined here are:
`shellpipe`
`attach-matching`
`whereami`
'''
import subprocess as sp
from collections import namedtuple
import re
import gdb
import helpers
from helpers import eval_int, function_disassembly, func_and_offset
class ShellPipe(gdb.Command):
'''Pass the output of a gdb command into a shell command.
This command splits the gdb command line at an unquoted exclamation mark
`!`, into a gdb command and a shell command.
It then runs the gdb command, and pipes the input into the shell command.
If the shell command prints to stdout or stderr that is printed to the
screen.
It hence allows commands along the lines of
(gdb) shellpipe disassemble main ! grep mov
'''
def __init__(self):
super(ShellPipe, self).__init__('shellpipe', gdb.COMMAND_USER)
def invoke(self, arg, _):
# XXX allow escaped `!` chars to be used in the gdb command.
gdb_command, shell_command = arg.split('!', 1)
gdb_command = gdb_command.strip()
shell_command = shell_command.strip()
shell_input = gdb.execute(gdb_command, False, True)
completed_handle = sp.run(shell_command,
input=shell_input,
stdout=sp.PIPE, stderr=sp.PIPE,
shell=True,
universal_newlines=True)
print(completed_handle.stdout, end='')
if completed_handle.stderr:
print('\nSTDERR\n{}'.format(completed_handle.stderr), end='')
if completed_handle.returncode != 0:
print('\nShell process returned with exit code: '
'{}'.format(completed_handle.returncode))
class AttachMatching(gdb.Command):
'''Attach to a process whose arguments match a regular expression.
`attach-matching` searches for processes whose command line matches an
argument. If there is only one matching process, attach to it, otherwise
print out the process ID's and command lines of each process.
Usage:
attach-matching vim test_file.txt
attach-matching xterm .+sh
'''
def __init__(self):
super(AttachMatching, self).__init__('attach-matching',
gdb.COMMAND_RUNNING)
def invoke(self, arg, _):
self.dont_repeat()
argv = gdb.string_to_argv(arg)
process = argv[0]
search_pattern = ' '.join(argv[1:])
matching_processes = []
# TODO pgrep output doesn't contain information about whether spaces
# are there because an argument has spaces in it or there to make the
# output look pretty.
# I could do extra parsing by checking /proc/<pid>/cmdline directly and
# removing the process name from what I'm comparing to a regular
# expression, but I don't think that's too useful.
processes = sp.check_output(['pgrep', '-a', process]).splitlines()
for pid, cmdline in [val.decode('utf-8').split(None, maxsplit=1)
for val in processes]:
if re.search(search_pattern, cmdline):
matching_processes.append((pid, cmdline))
if len(matching_processes) == 1:
gdb.execute('attach {}'.format(matching_processes[0][0]))
else:
print(len(matching_processes), ' matching processes')
for pid, cmdline in matching_processes:
print(pid, cmdline)
class GlobalUsed(gdb.Command):
'''Is a given global used in a function
Takes either the memory address of a function, or the symbol referring to a
function and prints out all uses of a global symbol in that function.
NOTE:
Far from perfect.
Can't see indirect addressing, (i.e.
myvar = &global_var; call_func(myvar); ) nor can it see direct
assembly addressing (i.e. "movabs" ).
It's really quite useful though -- which is why this flawed
implementation is kept around.
Usage:
global-used [function-name | function-address] [global name]
'''
def __init__(self):
super(GlobalUsed, self).__init__(
'global-used', gdb.COMMAND_FILES, gdb.COMPLETE_SYMBOL)
# TODO
# gdb misses the direct encoding of a global variable.
# I would like to change that.
'''
Questions:
Does gdb miss direct encoding of a global variable?
Answer: Yes
Proof:
Everything is compiled with gcc 6.3.1 gcc -Wall -W -g ~/test.c -o ~/test
With the test file (n.b may act strangely -- I haven't made any
real attempt to ensure it makes sense, just to proove the point).
#include <stdio.h>
#include <stdlib.h>
static int glob_var;
void asm_set_val()
{
// movabs should load from the given instruction into rax (see below)
// http://stackoverflow.com/questions/19415184/load-from-a-64-bit-address-into-other-register-than-rax
asm ("movabs 0x601034, %%rax\n\t"
: :);
}
int main()
{
asm_set_val();
printf("The global variable is: %d\n", glob_var);
return 0;
}
Do the following:
vshcmd: > # Testing whether gdb notices direct addressing.
vshcmd: > gdb ~/test
Reading symbols from /home/matthew/test...done.
(gdb)
vshcmd: > start
Temporary breakpoint 1 at 0x40050b: file /home/matthew/test.c, line 15.
Starting program: /home/matthew/test
Temporary breakpoint 1, main () at /home/matthew/test.c:15
warning: Source file is more recent than executable.
15 int main()
(gdb)
vshcmd: > print &glob_var
$1 = (int *) 0x601034 <glob_var>
(gdb)
vshcmd: > disassemble asm_set_val
Dump of assembler code for function asm_set_val:
0x00000000004004f6 <+0>: push %rbp
0x00000000004004f7 <+1>: mov %rsp,%rbp
0x00000000004004fa <+4>: movabs 0x601034,%rax
0x0000000000400504 <+14>: nop
0x0000000000400505 <+15>: pop %rbp
0x0000000000400506 <+16>: retq
End of assembler dump.
(gdb)
vshcmd: > detach
Detaching from program: /home/matthew/test, process 31978
The global variable is: 0
(gdb)
While with the following test file
#include <stdio.h>
#include <stdlib.h>
static int glob_var;
void asm_set_val(int src)
{
glob_var = src;
}
int main()
{
asm_set_val(10);
printf("The global variable is: %d\n", glob_var);
return 0;
}
Do the same to show it can pick things up.
vshcmd: > # Testing whether gdb notices direct addressing.
vshcmd: > gdb ~/test
Reading symbols from /home/matthew/test...done.
(gdb)
vshcmd: > start
Temporary breakpoint 1 at 0x40050d: file /home/matthew/test.c, line 14.
Starting program: /home/matthew/test
Temporary breakpoint 1, main () at /home/matthew/test.c:14
14 asm_set_val(10);
(gdb)
vshcmd: > print &glob_var
$1 = (int *) 0x601034 <glob_var>
(gdb)
vshcmd: > disassemble asm_set_val
Dump of assembler code for function asm_set_val:
0x00000000004004f6 <+0>: push %rbp
0x00000000004004f7 <+1>: mov %rsp,%rbp
0x00000000004004fa <+4>: mov %edi,-0x4(%rbp)
0x00000000004004fd <+7>: mov -0x4(%rbp),%eax
0x0000000000400500 <+10>: mov %eax,0x200b2e(%rip) # 0x601034 <glob_var>
0x0000000000400506 <+16>: nop
0x0000000000400507 <+17>: pop %rbp
0x0000000000400508 <+18>: retq
End of assembler dump.
(gdb)
vshcmd: > detach
Detaching from program: /home/matthew/test, process 32009
(gdb) The global variable is: 10
'''
@staticmethod
def make_info(instruction):
address = gdb.execute('output/a {}'.format(instruction['addr']),
False, True)
return address + '\t' + instruction['asm']
def invoke(self, arg, _):
args = gdb.string_to_argv(arg)
func_addr = eval_int(''.join(args[:-1]))
# Let possible error raise -- user needs to know something went wrong.
func_dis, func_name, func_file = function_disassembly(func_addr)
glob_name = args[-1]
glob_uses = [
self.make_info(val) for val in func_dis
if val['asm'].split()[-1] == '<{}>'.format(glob_name)
]
if glob_uses:
print('"{}" uses "{}" in the following places'.format(
func_name, glob_name))
print('\n'.join(glob_uses))
class PrintHypotheticalStack(gdb.Command):
'''Print each function name and file position in hypothetical call stack.
NOTE:
This command doesn't print the addresses where things were called, but
the address of each function that was called. This makes things much
simpler in the implementation of the `called-functions` walker.
'''
def __init__(self):
super(PrintHypotheticalStack, self).__init__('hypothetical-stack',
gdb.COMMAND_DATA)
def invoke(self, arg, _):
if arg and arg.split() != []:
raise ValueError('hypothetical-stack takes no arguments')
gdb.execute('pipe hypothetical-call-stack | show whereis {} | devnull')
print()
class PrintString(gdb.Command):
'''Print a string without quotation marks and without allocating memory.
The gdb command `printf "%s\\n", $_as_string(<something>)` allocates memory
in the inferior, and you have no reference with which to free it.
In order to avoid this you can do things like
set variable $tmpvar = (char *)$_as_string(<something>)
printf "%s\\n", $tmpvar
call free($tmpvar)
but this means you have $tmpvar in the gdb environment.
Commands such as `output` and `print` print the text with surrounding
quotes to show this is a string.
This `print-string` command simply prints a gdb internal string without
those enclosing quotes.
i.e. it is the `output` command limited to strings but without the
enclosing "".
'''
def __init__(self):
super(PrintString, self).__init__('print-string', gdb.COMMAND_DATA)
def invoke(self, arg, _):
args = gdb.string_to_argv(arg)
for argument in args:
try:
tmp = gdb.parse_and_eval(argument)
except gdb.error as e:
# Assume whatever error in parsing happens is because we're
# given a literal string.
raise ValueError(
'Failed to parse `{}` as a gdb value'.format(argument))
value = str(tmp)[1:-1]
print(value, end='')
# Alternate thoughts about FuncGraph
#
# Questions:
# How are the gdb `finish` statements implemented?
# What about the dtrace return probes?
# NOTE these include the position where the return instruction came?
# If I read the manual correctly, this is just the position of the
# ret instruction, not the position in the source code of the
# `return` statement.
# http://docs.oracle.com/cd/E19253-01/817-6223/chp-fbt/index.html
# stap return probes?
# watching the frame pointer $rbp?
# looking at the return address stored in the stack, and setting a
# breakpoint there? (would this break if calling that function
# recursively?)
# Finding all `ret` instructions of the function and placing
# breakpoints there?
#
#
# Given a regexp, iterate through all functions in the symtab and on each
# function that matches put a break point.
#
# Returning breakpoints are the problem.
# I am thinking of three different options
# 1) Don't do anything about them.
# On each breakpoint, search the current stack, if that function isn't
# at that position in the stack (known by looking at the stack
# pointer), then it must have returned in the intervening time.
#
# Problems:
# The last set of returns won't be printed
# This could be overcome by putting a listener on gdb.events.stop
#
# 2) disassemble the function and put a breakpoint on the return
# instruction.
# When the return instruction is hit, print a "leaving" thing.
#
# Problems:
# At the moment (because there isn't any way to access the
# minimal_symbol structures inside gdb) we need debugging
# information.
#
# 3) Make a "finish" breakpoint for the current frame.
# This would print the "leaving" thing.
#
# Problems:
# Same as above -- how to define the command.
# Would have to make a breakpoint each time a given function is
# called (as opposed to previous ones that don't).
#
# My best answer is option (2) -- it is the fastest by far (though it has a
# higher startup cost).
# I've overcome the problem with defining what should happen on the command
# by defining the `stop()` function to print what I want, and always return
# False. This means I never actually stop, which makes it even faster!!
class FuncGraph1(gdb.Command):
'''Continues the program, printing out the function call graph.
Usage:
func-graph
Example;
vshcmd: > gdb demos/tree
vshcmd: > start 10
vshcmd: > func-graph
vshcmd: > watch $rbp
vshcmd: > command 2
vshcmd: > silent
vshcmd: > func-graph
vshcmd: > cont
vshcmd: > end
vshcmd: > cont
'''
# NOTE Don't use this
# It's really slow.
# It has a terrible user interface
# It probably has a bunch of missed edge cases.
# I only wrote it to see if it would work.
# There's a much better option below (call-graph and its subcommands).
def __init__(self):
super(FuncGraph1, self).__init__('func-graph', gdb.COMMAND_USER)
self.indent = 0
self.fp_stack = []
def update_fp_stack(self):
'''Update fp stack according to if $fp was seen before.
Return False if $fp was seen before (and hence if we think we are
leaving a function), True otherwise.
'''
curfp = eval_int('$rbp')
if not self.fp_stack or curfp < self.fp_stack[-1]:
self.fp_stack.append(curfp)
return True
# The frame pointer decreases upon entering a new function, and
# increases otherwise.
# Because strange things can happen with the indirected @plt functions,
# we take all frame pointers that are below the current one off our
# stack.
self.fp_stack.pop()
return False
def invoke(self, *_):
pos = eval_int('$pc')
line = gdb.find_pc_line(pos)
if not line.symtab:
return
try:
block = gdb.block_for_pc(pos)
except RuntimeError as e:
if e.args == ('Cannot locate object file for block.',):
return
raise
if block.function is None:
print('First found block no function', pos)
return
while block.function.name is None:
if block.superblock:
block = block.superblock
else:
print('No superblock at', pos)
return
if block.function is None:
print('Function iterated to None in', pos)
return
offset = pos - block.start
offset_str = '+{}'.format(offset) if offset else ''
entering = self.update_fp_stack()
if entering:
curindent = self.indent
self.indent += 4
direction_string = '-->'
else:
self.indent -= 4
curindent = self.indent
direction_string = '<--'
print_str = '{}{}{}{} {}:{}'.format(' '*curindent, direction_string,
block.function.name, offset_str,
line.symtab.filename, line.line)
print(print_str)
class CallGraphDynlibs(gdb.Parameter):
'''Should `call-graph` trace symbols in dynamic libraries.
Boolean - true => trace dynamic library functions.
false => do not trace dynamic library functions.
'''
def __init__(self):
super(CallGraphDynlibs, self).__init__('call-graph-dynlibs',
gdb.COMMAND_NONE,
gdb.PARAM_BOOLEAN)
def get_set_string(self):
return 'call-graph will {}include dynamic libraries'.format(
'' if self.value else 'not ')
def get_show_string(self, curval):
return curval + ': ' + self.get_set_string()
class CallGraphNonDebug(gdb.Parameter):
'''Should `call-graph` include non-debug symbols.
Boolean - true => use non-debug symbols.
false => ignore non-debug symbols.
'''
def __init__(self):
super(CallGraphNonDebug, self).__init__('call-graph-nondebug',
gdb.COMMAND_NONE,
gdb.PARAM_BOOLEAN)
def get_set_string(self):
return 'call-graph will {} non-debug symbols'.format(
'use' if self.value else 'ignore')
def get_show_string(self, curval):
return curval + ': ' + self.get_set_string()
class CallGraph(gdb.Command):
'''Prefix command for call graph tracing commands.
These tracing commands put an internal breakpoint on the entry point and
exit point of each function specified on the command line.
The breakpoints print an indentation representing the current function
nesting level, an arrow indicating whether this is an entry or exit point,
and the function name that is currently being entered/exited.
The format of regular expressions for this command is
file_regex:func_regex
If `file_regex` matches the empty string '', then non-debugging symbols are
searched too.
If there is no colon in the pattern, then all files are used, and whether
non-debugging symbols are used or not depends on the value of
`call-graph-nondebug`.
This parameter may be set with `set call-graph-nondebug`.
By default, call-graph ignores functions in the dynamic libraries (i.e.
libc etc). This can be configured using `set call-graph-dynlib`.
'''
entry_breaks = {}
ret_breaks = {}
indent_level = 0
@classmethod
def clear_previous_breakpoints(cls):
all_addresses = list(cls.entry_breaks.keys())
for addr in all_addresses:
remove_addr_trace(addr)
cls.indent_level = 0
def __init__(self):
super(CallGraph, self).__init__('call-graph', gdb.COMMAND_USER,
gdb.COMPLETE_COMMAND, True)
def retpoints(addr, arch):
'''Return a list of addresses for all ret instructions in function `symbol`
Disassembles the function that `symbol` refers to, and returns a list of
the position of those return instructions in a format ready for creating a
breakpoint with.
The specific format returned is `*<symbol-name>+<offset>`.
'''
func_dis, func_name, _ = function_disassembly(addr, arch)
start = func_dis[0]['addr']
description = lambda val: func_name + '+{}'.format(val['addr'] - start)
location = lambda val: '*{}'.format(val['addr'])
return [(location(val), description(val))
for val in func_dis if val['asm'].startswith('ret') ]
def file_func_split(regexp):
'''Split file_regexp:func_regexp into its component parts.
If there is no colon, then func_regexp is `regexp` and file_regexp is None
'''
file_func = regexp.split(':', maxsplit=1)
if len(file_func) == 1:
file_regex = None
func_regex = file_func[0]
else:
file_regex, func_regex = file_func
return file_regex, func_regex
def add_tracer(symbol, arch):
'''Trace the function defined by symbol.
`symbol` should have a `value()` method, a `symtab.filename` member (i.e.
a symtab member that itself has a filename member), and a `name` member.
'''
addr = int(symbol.value().cast(helpers.uintptr_t))
# Use hex just because it's pretty for `info call-graph exact`.
entry_loc, *names = ('*{}'.format(hex(addr)),
symbol.name, symbol.symtab.filename)
# Avoid duplicate symbols by checking if the address already has a
# breakpoint -- very often happens in non-debug symbols where the same
# function has many names (e.g. __GI___libc_longjmp, __libc_longjmp,
# __libc_siglongjmp, _longjmp, longjmp, siglongjmp) all have the same
# address.
# This also means that if the given regexp matches functions already
# traced, we don't end up with duplicate tracers.
if addr not in CallGraph.entry_breaks:
CallGraph.entry_breaks[addr] = EntryBreak(entry_loc, *names)
if addr not in CallGraph.ret_breaks:
CallGraph.ret_breaks[addr] = [
ReturnBreak(retloc, retdesc, *names)
for retloc, retdesc in retpoints(addr, arch)]
def trace_regexp(regexp):
'Trace all functions matching `regexp` in the current symbol table.'
file_regex, func_regex = file_func_split(regexp)
if file_regex is None:
file_regex = '.*' if gdb.parameter('call-graph-nondebug') else '.+'
arch = gdb.current_arch()
# We have to break on address to distinguish symbols with the same name in
# different files.
#
# In order to have nice output, we create a string that describes the
# function for a human -- though symbols with the same name will have the
# same output for entry tracepoints.
for symbol in gdb.search_symbols(func_regex, file_regex,
gdb.parameter('call-graph-dynlibs')):
add_tracer(symbol, arch)
def remove_addr_trace(del_addr):
'Remove tracers on the function at `addr`.'
# The following code would be more elegant, and should work in any
# situation the data structures are allowed to get in.
#
# try:
# CallGraph.entry_breaks[del_addr].delete()
# del CallGraph.entry_breaks[del_addr]
# except KeyError:
# return
# for bp in CallGraph.ret_breaks[del_addr]:
# bp.delete()
# del CallGraph.ret_breaks[del_addr]
#
# But I use the below to give warning if anything strange is happening.
missing_key = False
try:
CallGraph.entry_breaks[del_addr].delete()
del CallGraph.entry_breaks[del_addr]
except KeyError:
missing_key = True
try:
for bp in CallGraph.ret_breaks[del_addr]:
bp.delete()
del CallGraph.ret_breaks[del_addr]
except KeyError:
if not missing_key:
raise RuntimeError('Tracer in entry dict not in return dict')
return
if missing_key:
raise RuntimeError('Tracer in return dict, not in entry dict')
def remove_tracers(regexp):
'Remove all trace points matching `regexp` in the CallGraph class lists.'
file_regex, func_regex = file_func_split(regexp)
# If no file_regex given, remove *all* matching functions.
# We don't predicate this on `call-graph-nondebug` because it's confusing
# if non-debug functions aren't removed after changing the value.
file_regex = '.*' if file_regex is None else file_regex
entry_dels = [(addr, bp) for addr, bp in CallGraph.entry_breaks.items()
if re.match(func_regex, bp.func_name)
and re.search(file_regex, bp.filename)]
for addr, _ in entry_dels:
remove_addr_trace(addr)
class EntryBreak(gdb.Breakpoint):
'''Subclass of gdb.Breakpoint that never stops execution and prints the
current function indentation.
This class prints a string representing _entering_ a function and adds an
indentation level from the CallGraph data.
'''
def __init__(self, loc, desc, filename):
super(EntryBreak, self).__init__(loc, gdb.BP_BREAKPOINT,
-1, True, False)
self.desc = desc
self.func_name = desc
self.filename = filename
def stop(self):
CallGraph.indent_level += 4
print('{} --> {}'.format(' '*CallGraph.indent_level, self.desc))
return False
class ReturnBreak(gdb.Breakpoint):
'''Subclass of gdb.Breakpoint that never stops execution and prints the
current function indentation.
This class prints a string representing _exiting_ a function and removes an
indentation level from the CallGraph data.
'''
def __init__(self, loc, desc, func_name, filename):
super(ReturnBreak, self).__init__(loc, gdb.BP_BREAKPOINT,
-1, True, False)
self.desc = desc
self.func_name = func_name
self.filename = filename
def stop(self):
print('{} <-- {}'.format(' '*CallGraph.indent_level, self.desc))
CallGraph.indent_level -= 4
return False
class CallGraphClear(gdb.Command):
'''Remove the current call-flow tracing breakpoints.
This removes the current tracing implemented by `call-graph trace REGEXP`.
After this has been called, no tracing will be implemented.
Usage:
call-graph clear
'''
def __init__(self):
super(CallGraphClear, self).__init__('call-graph clear',
gdb.COMMAND_USER)
def invoke(self, *_):
self.dont_repeat()
CallGraph.clear_previous_breakpoints()
class CallGraphInit(gdb.Command):
'''Trace execution flow through functions matching REGEXP.
This command is useful to view the flow of code as it is executed.
Only one set of tracers may be active at one time.
Usage:
call-graph init REGEXP
call-graph init REGEXP
'''
def __init__(self):
super(CallGraphInit, self).__init__('call-graph init',
gdb.COMMAND_USER)
def invoke(self, arg, _):
self.dont_repeat()
CallGraph.clear_previous_breakpoints()
trace_regexp(arg)
class CallGraphUpdate(gdb.Command):
'''Add or remove tracers matching REGEXP.
This command is useful to incrementally update the functions currently
traced. For example you might want to trace create_tree and free_tree but
not create_random_tree, you can run the following commands:
call-graph clear
call-graph update + create_tree
call-graph update + free_tree
Or you can run:
call-graph clear
call-graph init .*_tree
call-graph update - create_random_tree
For when there are more than one function with the same name, you can pass
the argument "exact" and a memory address instead of a regular expression.
Memory addresses of currently traced functions can be seen with
info call-graph exact
Usage:
call-graph update [+ | -] <REGEXP>
call-graph update [+ | -] exact <address>
'''
usage_string = 'Usage: call-graph [+ | -] [<REGEXP> | exact <address>]'
def __init__(self):
super(CallGraphUpdate, self).__init__('call-graph update',
gdb.COMMAND_USER)
def update_exact(self, direction, addr):
'''Add or remove a function from tracing that starts at a given memory
address instead of whose name matches a regular expression.'''
func_name, offset = func_and_offset(int(addr, base=0))
# Checks we don't give an invalid address to remove_addr_trace(), or
# add_tracer().
if func_name is None or offset != 0:
raise ValueError('{} does not start a function'.format(addr))
if direction == '+':
arch = gdb.current_arch()
return add_tracer(helpers.FakeSymbol(func_name, addr), arch)
remove_addr_trace(int(addr, 0))
def invoke(self, arg, _):
args = gdb.string_to_argv(arg)
if len(args) != 2 and len(args) != 3:
raise ValueError(self.usage_string)
if args[0] not in ['+', '-']:
raise ValueError(self.usage_string)
if len(args) == 3:
assert args[1] == 'exact', self.usage_string
self.update_exact(args[0], args[2])
return
if args[0] == '+':
trace_regexp(args[1])
return
remove_tracers(args[1])
class CallGraphInfo(gdb.Command):
'''Print all functions currently traced with call-graph
If given the `exact` argument, print the addresses of each entry
breakpoint.
This may be needed if there are multiple functions with the same name in
your program (to avoid.
'''
def __init__(self):
super(CallGraphInfo, self).__init__('info call-graph',
gdb.COMMAND_STATUS)
def invoke(self, arg, _):
args = gdb.string_to_argv(arg)
if len(args) > 1 or (len(args) == 1 and args[0] != 'exact'):
raise ValueError('Usage: info call-graph [exact]')
print('Functions currently traced by call-graph:')
# If ask for exact location, print the address the breakpoint is at.
if args:
for bp in CallGraph.entry_breaks.values():
print('\t', bp.location.strip('*'), '\t', bp.desc)
return
for bp in CallGraph.entry_breaks.values():
print('\t', bp.desc)
AttachMatching()
ShellPipe()
GlobalUsed()
PrintHypotheticalStack()
PrintString()
CallGraph()
CallGraphClear()
CallGraphInit()
CallGraphUpdate()
CallGraphInfo()
CallGraphNonDebug()
CallGraphDynlibs()