diff --git a/README.md b/README.md index f628b33..4b96d78 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,9 @@ heap select - query used heap chunks hexdump [-c] - print a hexdump, stating at the specific region of memory (expose hex characters with -c option) heap arenas - print glibs arenas heap arena - select glibc arena number +heap find -- Find stuff anywhere; supplies ranges to the find command +heap range -- Print all non-empty merged memory ranges sorted by start address +heap range run -- Run a command for each range from `heap range` ``` Useful resources diff --git a/heap/__init__.py b/heap/__init__.py index f4f87a3..b428120 100644 --- a/heap/__init__.py +++ b/heap/__init__.py @@ -1,4 +1,6 @@ +# -*- coding: utf-8 -*- # Copyright (C) 2010 David Hugh Malcolm +# Copyright (C) 2015 Stefan Bühler # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public diff --git a/heap/commands.py b/heap/commands.py index d6244c0..10bf5cf 100644 --- a/heap/commands.py +++ b/heap/commands.py @@ -282,6 +282,77 @@ def invoke(self, args, from_tty): except ParserError as e: print(e) +class HeapRange(gdb.Command): + '''Print all non-empty merged memory ranges sorted by start address + + Each line contains a start and end address; the end address is the + first not valid address. + ''' + def __init__(self): + gdb.Command.__init__ (self, + "heap range", + gdb.COMMAND_DATA, + prefix = True) + + def invoke(self, args, from_tty): + import ranges + for r in ranges.merged_ranges(): + print('\t%s' % r) + +class HeapRangeRun(gdb.Command): + '''Run a command for each range from `heap range` + + Use $range_start, $range_last (end - 1) and $range_end variables in the + command. + Example: heap range run find /g $range_start, $range_last, some_value + ''' + def __init__(self): + gdb.Command.__init__ (self, + "heap range run", + gdb.COMMAND_DATA, + gdb.COMPLETE_COMMAND) + + def invoke(self, args, from_tty): + import ranges + for r in ranges.merged_ranges(): + gdb.execute("set $range_start=%s" % fmt_addr(r.start)) + gdb.execute("set $range_last=%s" % fmt_addr(r.last)) + gdb.execute("set $range_end=%s" % fmt_addr(r.end)) + gdb.execute(args, from_tty = True) + +class HeapFind(gdb.Command): + '''Find stuff anywhere; supplies ranges to the find command + + Example: heap find /g some_callback_function + ''' + def __init__(self): + gdb.Command.__init__ (self, + "heap find", + gdb.COMMAND_DATA) + + def invoke(self, args, from_tty): + import ranges + import re + from heap.compat import execute + FIND_SWITCHES = re.compile('^((?:\s*/\w+)*)(.*)$') + (switches, args) = FIND_SWITCHES.match(args).groups() + while len(args) > 0 and args[0][0] == '/': + switches.append(args.pop(0)) + sumfound = 0 + for r in ranges.merged_ranges(): + result = execute('find%s %s, %s, %s' % (switches, fmt_addr(r.start), fmt_addr(r.last), args)) + numfound = int(gdb.parse_and_eval('$numfound')) + sumfound += numfound + if numfound > 0: + result = result.splitlines() + result.pop() # no summary + for l in result: + print(l) + if sumfound > 0: + print ('%s patterns found' % sumfound) + else: + print ('Pattern not found') + class Hexdump(gdb.Command): 'Print a hexdump, starting at the specific region of memory' def __init__(self): @@ -354,6 +425,10 @@ def register_commands(): HeapSelect() HeapArenas() HeapArenaSelect() + HeapRange() + HeapRangeRun() + HeapFind() + Hexdump() from heap.cpython import register_commands as register_cpython_commands diff --git a/heap/ranges.py b/heap/ranges.py new file mode 100644 index 0000000..bee13a4 --- /dev/null +++ b/heap/ranges.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2015 Stefan Bühler +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from heap import caching_lookup_type, fmt_addr + +class Range(object): + '''Memory range''' + + def __init__(self, start, end): + if start > end: + raise ValueError("Invalid range") + self.start = start + self.end = end + + def __str__(self): + return ('%s - %s') % (fmt_addr(self.start), fmt_addr(self.end)) + + @property + def last(self): + return self.end - 1 + + @property + def empty(self): + return self.start == self.end + + def __cmp__(self, other): + return cmp((self.start, self.end), (other.start, other.end)) + +def ranges(): + import re + PARSE_RANGE_LINE = re.compile('^\t(0x[0-9a-fA-F]+) - (0x[0-9a-fA-F]+)') + + from heap.compat import execute + + result = [] + for line in execute('info file').splitlines(): + match = PARSE_RANGE_LINE.match(line) + if not match: continue + (start, end) = match.groups() + result.append(Range(int(start, 16), int(end, 16))) + return result + +def merged_ranges(): + '''merge neighbours, drop empty ranges''' + cur_range = None + result = [] + for r in sorted(ranges()): + if r.empty: continue + if cur_range: + if cur_range.end >= r.start: + # merge range + cur_range = Range(cur_range.start, max(cur_range.end, r.end)) + else: + # no overlap, move forward + result.append(cur_range) + cur_range = r + else: + cur_range = r + if cur_range: + result.append(cur_range) + + return result