You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
7.7 KiB
240 lines
7.7 KiB
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()
|