import gdb def parse_address_to_int(address): int_address_string = gdb.execute( 'p/d {}'.format(address), to_string=True) int_address = int(int_address_string.split('=')[1].strip()) return int_address def parse_gdb_equals(str): """ str is $1 = value. so it returns value """ return str.split("=")[1].strip() class HeapMapping: """ Wrapper class for dictionary to have customization for the dictionary and one entry point """ address_length_mapping = {} address_set = set() @staticmethod def put(address, length): HeapMapping.address_length_mapping[address] = length HeapMapping.address_set.add(address) @staticmethod def get(address): """ Gets the length of the dynamic array corresponding to address. Suppose dynamic array is {1,2,3,4,5} and starting address is 400 which is passed as address to this method, then method would return 20(i.e. 5 * sizeof(int)). When this address is offsetted for eg 408 is passed to this method, then it will return remainder number of bytes allocated, here it would be 12 (i.e. 420 - 408) Algorithm tries to find address in address_length_apping, if it doesn't find it then it tries to find the range that can fit the address. if it fails to find such mapping then it would return None. """ length_found = HeapMapping.address_length_mapping.get(address) if length_found: return length_found else: address_list = list(HeapMapping.address_set) address_list.sort() left = 0 right = len(address_list) - 1 while left <= right: mid = int((left + right) / 2) if address > address_list[mid]: left = mid + 1 # only < case would be accounted in else. # As == would be handled in the if-check above (outside while) else: right = mid - 1 index = left - 1 if index == -1: return None base_address = address_list[index] base_len = HeapMapping.address_length_mapping.get(base_address) if base_address + base_len > address: return base_address + base_len - address else: return None @staticmethod def remove(address): HeapMapping.address_length_mapping.pop(address, None) HeapMapping.address_set.discard(address) class AllocationFinishedBreakpoint(gdb.FinishBreakpoint): """ Sets temporary breakpoints on returns (specifically returns of memory allocations) to record address allocated. It get instantiated from AllocationBreakpoint and ReallocationBreakpoint. When it is instantiated from ReallocationBreakPoint, it carries prev_address. """ def __init__(self, length, prev_address=None): super().__init__(internal=True) self.length = length self.prev_address = prev_address def stop(self): """ Called when the return address in the current frame is hit. It parses hex address into int address. If return address is not null then it stores address and length into the address_length_mapping dictionary. """ return_address = self.return_value if return_address is not None or return_address == 0x0: if self.prev_address != None: HeapMapping.remove(self.prev_address) # Converting hex address to int address int_address = parse_address_to_int(return_address) HeapMapping.put(int_address, self.length) return False class AllocationBreakpoint(gdb.Breakpoint): """ Handler class when malloc and operator new[] gets hit """ def __init__(self, spec): super().__init__(spec, internal=True) def stop(self): # handle malloc and new func_args_string = gdb.execute('info args', to_string=True) if func_args_string.find("=") != -1: # There will be just 1 argument to malloc. So no need to handle multiline length = int(parse_gdb_equals(func_args_string)) AllocationFinishedBreakpoint(length) return False class ReallocationBreakpoint(gdb.Breakpoint): """ Handler class when realloc gets hit """ def __init__(self, spec): super().__init__(spec, internal=True) def stop(self): # handle realloc func_args_string = gdb.execute('info args', to_string=True) if func_args_string.find("=") != -1: args = func_args_string.split("\n") address = parse_gdb_equals(args[0]) int_address = parse_address_to_int(address) length = int(parse_gdb_equals(args[1])) AllocationFinishedBreakpoint(length, int_address) return False class DeallocationBreakpoint(gdb.Breakpoint): """ Handler class when free and operator delete[] gets hit """ def __init__(self, spec): super().__init__(spec, internal=True) def stop(self): func_args_string = gdb.execute('info args', to_string=True) if func_args_string.find("=") != -1: address = parse_gdb_equals(func_args_string) int_address = parse_address_to_int(address) HeapMapping.remove(int_address) return False class WatchHeap(gdb.Command): """ Custom Command to keep track of Heap Memory Allocation. Currently keeps tracks of memory allocated/deallocated using malloc, realloc, free, operator new[] and operator delete[] """ def __init__(self): super(WatchHeap, self).__init__("watch_heap", gdb.COMMAND_USER) def complete(self, text, word): return gdb.COMPLETE_COMMAND def invoke(self, args, from_tty): # TODO : Check whether break location methods are defined AllocationBreakpoint("malloc") AllocationBreakpoint("operator new[]") ReallocationBreakpoint("realloc") DeallocationBreakpoint("free") DeallocationBreakpoint("operator delete[]") class PrintHeapPointer(gdb.Command): """ Custom command to print memory allocated at dynamic time """ def __init__(self): super(PrintHeapPointer, self).__init__("print_ptr", gdb.COMMAND_USER) def complete(self, text, word): return gdb.COMPLETE_COMMAND def invoke(self, args, from_tty=True): try: value = gdb.parse_and_eval(args) if value.type.code == gdb.TYPE_CODE_PTR: print("Type : ", value.type) starting_address_string = gdb.execute( 'p/x {}'.format(value), to_string=True) print("Address: ", parse_gdb_equals(starting_address_string)) int_address = parse_address_to_int(value) # print memory self.print_heap(int_address) except Exception: print('No symbol found!') def print_heap(self, address): """ Prints the memory that is being pointed by address in hex format Parameters --------- address : raw pointer """ memory_size = HeapMapping.get(address) if memory_size: print('Length :', memory_size) result = '' i = 0 while i < memory_size: byte_string = gdb.execute( 'x/1bx {}'.format(address), to_string=True) result += byte_string.split(':')[1].strip() + " " address += 1 i += 1 print(result) else: print("No address mapping found!") if __name__ == '__main__': WatchHeap() PrintHeapPointer()