import argparse
import binascii
import os
import re
from collections import defaultdict
from typing import Dict, Generator, List, Protocol, Tuple
import gdb
from . import autocompeletion, backtrace, mm, utils
class MMNodeDump(Protocol):
"""Node information protocol for dump"""
address: int
nodesize: int
seqno: int
pid: int
backtrace: Tuple[int]
is_free: bool
from_pool: bool
overhead: int
useraddress: int
usersize: int = 0
def contains(self, addr: int) -> bool: ...
def read_memory(self) -> memoryview: ...
def filter_node(
pid=None,
nodesize=None,
used=None,
free=None,
seqmin=None,
seqmax=None,
orphan=None,
no_pid=None,
no_heap=None,
no_pool=None,
) -> bool:
return lambda node: (
(pid is None or node.pid == pid)
and (no_pid is None or node.pid != no_pid)
and (nodesize is None or node.nodesize == nodesize)
and (not used or not node.is_free)
and (not free or node.is_free)
and (seqmin is None or node.seqno >= seqmin)
and (seqmax is None or node.seqno <= seqmax)
and (not orphan or node.is_orphan)
)
def dump_nodes(
heaps: List[mm.MMHeap],
filters=None,
) -> Generator[MMNodeDump, None, None]:
no_heap = filters and filters.get("no_heap")
no_pool = filters and filters.get("no_pool")
if not no_heap:
yield from (
node
for heap in heaps
for node in filter(filter_node(**filters), heap.nodes)
)
if not no_pool:
yield from (
blk
for pool in mm.get_pools(heaps)
for blk in filter(filter_node(**filters), pool.blks)
)
def group_nodes(
nodes: List[MMNodeDump], grouped: Dict[MMNodeDump, List[MMNodeDump]] = None
) -> Dict[MMNodeDump, List[MMNodeDump]]:
grouped = grouped or defaultdict(list)
for node in nodes:
grouped[node].append(node)
return grouped
def print_node(node: MMNodeDump, alive, count=1, formatter=None, no_backtrace=False):
formatter = (
formatter or "{:>1} {:>4} {:>12} {:>12} {:>12} {:>9} {:>14} {:>18} {:}\n"
)
gdb.write(
formatter.format(
"\x1b[33;1m*\x1b[m" if not alive else "",
"P" if node.from_pool else "H",
count,
node.pid,
node.nodesize,
node.overhead,
node.seqno,
hex(node.address),
"",
)
)
if mm.MM_RECORD_STACK_DEPTH > 0 and not no_backtrace:
leading = formatter.format("", "", "", "", "", "", "", "", "")[:-1]
btformat = leading + "{1:<48}{2}\n"
if node.backtrace and node.backtrace[0]:
gdb.write(f"{backtrace.Backtrace(node.backtrace, formatter=btformat)}\n")
def print_header(formatter=None):
formatter = (
formatter or "{:>1} {:>4} {:>12} {:>12} {:>12} {:>9} {:>14} {:>18} {:}\n"
)
head = (
"",
"Pool",
"CNT",
"PID",
"NodeSize",
"Overhead",
"Seqno",
"Address",
"Backtrace",
)
gdb.write(formatter.format(*head))
def get_heaps(args_heap: str = None) -> List[mm.MMHeap]:
"""Get the list of heaps, or a specific heap if args_heap is provided."""
if args_heap is not None:
try:
heaps = mm.get_heaps()
heap = next((heap for heap in heaps if heap.name == args_heap), None)
if heap is not None:
return [heap]
except gdb.MemoryError:
pass
finally:
return [mm.MMHeap(utils.parse_arg(args_heap))]
else:
return mm.get_heaps()
def parse_memdump_log(logfile, filters=None) -> Generator[MMNodeDump, None, None]:
nodes = []
class DumpNode(MMNodeDump):
def __init__(self, address, nodesize, seqno, pid, backtrace, is_free, overhead):
self.address = address - overhead
self.nodesize = nodesize
self.seqno = seqno
self.pid = pid
self.backtrace = backtrace
self.overhead = overhead
self.is_free = False
self.from_pool = False
def __repr__(self) -> str:
return f"node@{self.address:#x}: size:{self.nodesize} seq:{self.seqno} pid:{self.pid} "
def contains(self, addr: int) -> bool:
return self.address <= addr < self.address + self.nodesize
@property
def prevnode(self):
return next(
(node for node in nodes if node.contains(self.address - 1)), None
)
@property
def nextnode(self):
address = self.address + self.nodesize
return next(
(node for node in nodes if node.address == address),
None,
)
with open(logfile, "r", errors="ignore") as f:
for line in f:
match = re.search(
r"(\d+)\s+(\d+)\s+(\d+)\s+(\d+)((?:\s+0x[0-9a-fA-F]+)+)", line
)
if not match:
continue
try:
pid = int(match.group(1))
size = int(match.group(2))
overhead = int(match.group(3))
seq = int(match.group(4))
addresses = match.group(5).split()
addr = int(addresses[0], base=16)
mem = tuple(int(addr, base=16) for addr in addresses[1:])
nodes.append(DumpNode(addr, size, seq, pid, mem, False, overhead))
except Exception as e:
print(f"Error parsing line: {line}, {e}")
return filter(filter_node(**filters), nodes) if filters else nodes
@autocompeletion.complete
class MMDump(gdb.Command):
"""Dump memory manager heap"""
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument(
"-a",
"--address",
type=str,
metavar="symbol",
default=None,
help="Find the node that contains the address and exit",
)
parser.add_argument(
"-l",
"--log",
type=str,
metavar="file",
default=None,
help="Use the memdump log file generated by memdump command on device instead of live dump",
)
parser.add_argument(
"--heap",
type=str,
metavar="symbol",
default=None,
help="Which heap to inspect",
)
parser.add_argument(
"-p", "--pid", type=int, default=None, help="Thread PID, -1 for mempool"
)
parser.add_argument(
"-i", "--min", type=int, default=None, help="Minimum sequence number"
)
parser.add_argument(
"-x", "--max", type=int, default=None, help="Maximum sequence number"
)
parser.add_argument("--free", action="store_true", help="Free flag")
parser.add_argument("--biggest", action="store_true", help="biggest allocated")
parser.add_argument(
"--orphan", action="store_true", help="Filter nodes that are orphan"
)
parser.add_argument(
"--top", type=int, default=None, help="biggest top n, default to all"
)
parser.add_argument(
"--size", type=int, default=None, help="Node block size filter."
)
parser.add_argument(
"--no-pool",
"--nop",
action="store_true",
help="Exclude dump from memory pool",
)
parser.add_argument(
"--no-heap", "--noh", action="store_true", help="Exclude heap dump"
)
parser.add_argument(
"--no-group", "--nog", action="store_true", help="Do not group the nodes"
)
parser.add_argument(
"--no-backtrace",
"--nob",
action="store_true",
help="Do not print backtrace",
)
parser.add_argument(
"--no-reverse",
"--nor",
action="store_true",
help="Do not reverse the sort result",
)
parser.add_argument(
"--no-pid", type=int, default=None, help="Exclude nodes from this PID"
)
parser.add_argument(
"--sort",
type=str,
choices=["size", "nodesize", "count", "seq", "address"],
default="count",
help="sort the node by size(nodesize * count), nodesize, count or sequence number",
)
return parser
def __init__(self):
super().__init__("mm dump", gdb.COMMAND_USER)
utils.alias("memdump", "mm dump")
self.parser = self.get_argparser()
def parse_args(self, arg):
try:
return self.parser.parse_args(gdb.string_to_argv(arg))
except SystemExit:
return
def find_address(self, addr, heaps: List[mm.MMHeap] = None, log=None):
"""Find the node that contains the address from memdump log or live dump."""
addr = int(utils.parse_and_eval(addr))
if log:
nodes = parse_memdump_log(log)
node = next((node for node in nodes if node.contains(addr)), None)
else:
node = next(
(blk for pool in mm.get_pools(heaps) if (blk := pool.find(addr))), None
)
node = node or next(
(node for heap in heaps if (node := heap.find(addr))), None
)
return addr, node
def collect_nodes(self, heaps: List[mm.MMHeap], log=None, filters=None):
if log:
nodes = parse_memdump_log(log, filters=filters)
else:
nodes = dump_nodes(heaps, filters)
return nodes
@utils.dont_repeat_decorator
def invoke(self, arg: str, from_tty: bool) -> None:
if not (args := self.parse_args(arg)):
return
print_header()
pids = [int(tcb["pid"]) for tcb in utils.get_tcbs()]
def printnode(node, count):
print_node(node, node.pid in pids, count, no_backtrace=args.no_backtrace)
heaps = get_heaps(args.heap)
if args.address:
addr, node = self.find_address(args.address, heaps, args.log)
if not node:
print(f"Address {addr:#x} not found in any heap")
else:
source = "Pool" if node.from_pool else "Heap"
printnode(node, 1)
state = (
"unknown"
if node.is_free is None
else "free" if node.is_free else "used"
)
print(f"{addr: #x} is {state}, found belongs to {source} - {node}")
if node.prevnode:
print(f"prevnode: {node.prevnode}")
if node.nextnode:
print(f"nextnode: {node.nextnode}")
return
filters = {
"pid": args.pid,
"nodesize": args.size,
"used": not args.free,
"free": args.free,
"seqmin": args.min,
"seqmax": args.max,
"orphan": args.orphan,
"no_heap": args.no_heap,
"no_pool": args.no_pool,
"no_pid": args.no_pid,
}
nodes = self.collect_nodes(heaps, log=args.log, filters=filters)
sort_method = {
"count": lambda node: 1,
"size": lambda node: node.nodesize,
"nodesize": lambda node: node.nodesize,
"seq": lambda node: node.seqno,
"address": lambda node: node.address,
}
def sort_nodes(nodes, sort=None):
sort = sort or args.sort
nodes = sorted(nodes, key=sort_method[sort], reverse=not args.no_reverse)
if args.top is not None:
nodes = nodes[: args.top] if args.top > 0 else nodes[args.top :]
return nodes
if args.biggest:
args.sort = "nodesize"
args.no_group = True
if args.no_group:
nodes = list(nodes)
for node in sort_nodes(nodes):
printnode(node, 1)
gdb.write(f"Total blks: {len(nodes)}\n")
else:
grouped: Dict[MMNodeDump, MMNodeDump] = defaultdict(list)
grouped = group_nodes(nodes)
sort_method["count"] = lambda node: len(grouped[node])
sort_method["size"] = lambda node: node.nodesize * len(grouped[node])
total_blk = total_size = 0
for node in sort_nodes(grouped.keys()):
count = len(grouped[node])
total_blk += count
if node.pid != mm.PID_MM_MEMPOOL:
total_size += count * node.nodesize
printnode(node, count)
print(f"Total {total_blk} blks, {total_size} bytes")
@autocompeletion.complete
class MMfrag(gdb.Command):
"""Show memory fragmentation rate and analyze fragmentation causes"""
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument(
"--heap",
type=str,
default=None,
help="Which heap to inspect",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Show detailed fragmentation causes (orphan nodes between free nodes)",
)
parser.add_argument(
"--threshold",
type=int,
default=512,
help="Minimum total size of adjacent free nodes to consider (bytes, default: 512)",
)
parser.add_argument(
"--top", type=int, default=None, help="Number of top fragments to show"
)
parser.add_argument(
"--no-backtrace",
action="store_true",
help="Do not print backtrace",
)
return parser
def __init__(self):
super().__init__("mm frag", gdb.COMMAND_USER)
utils.alias("memfrag", "mm frag")
self.parser = self.get_argparser()
def show_fragmentation_rate(self, heap: mm.MMHeap):
"""Calculate and display fragmentation rate for a heap"""
free_nodes = list(
sorted(heap.nodes_free(), key=lambda node: node.nodesize, reverse=True)
)
if not free_nodes:
gdb.write(f"{heap}: no free nodes\n")
return
total_free_size = sum(node.nodesize for node in free_nodes)
remaining = total_free_size
frag_rate = 0.0
for node in free_nodes:
frag_rate += (1 - (node.nodesize / remaining)) * (
node.nodesize / total_free_size
)
remaining -= node.nodesize
frag_rate *= 1000
gdb.write(
f"{heap.name}@{heap.address:#x}, fragmentation rate:{frag_rate:.2f},"
f" heapsize: {heap.heapsize}, free size: {total_free_size},"
f" free count: {len(free_nodes)}, largest: {free_nodes[0].nodesize}\n"
)
def show_memory_fragments(self, heap: mm.MMHeap, args):
"""Analyze and display fragmentation causes (orphan nodes between free nodes)"""
formatter = "{:>4} {:>8} {:>12} {:>12} {:>12} {:>14} {:>9} {:>18} {:}\n"
def print_fragment_header():
head = (
"Pool",
"PID",
"PrevSize",
"NodeSize",
"NextSize",
"TotalFreeSize",
"Seqno",
"Address",
"Backtrace",
)
gdb.write(formatter.format(*head))
def print_fragment_node(node: MMNodeDump, total_free, no_backtrace=False):
gdb.write(
formatter.format(
"P" if node.from_pool else "H",
node.pid,
node.prevnode.nodesize,
node.nodesize,
node.nextnode.nodesize,
total_free,
node.seqno,
hex(node.address),
"",
)
)
if (
mm.MM_RECORD_STACK_DEPTH > 0
and not no_backtrace
and node.backtrace
and node.backtrace[0]
):
leading = formatter.format("", "", "", "", "", "", "", "", "")[:-1]
bt_format = leading + "{1:<48}{2}\n"
gdb.write(
f"{backtrace.Backtrace(node.backtrace, formatter=bt_format)}\n"
)
def collect_significant_fragments():
heap_guards = [item for start, end in heap.regions for item in (start, end)]
for node in heap.nodes_used():
if node in heap_guards:
continue
if not (node.is_prev_free and node.nextnode and node.nextnode.is_free):
continue
total_free = sum(n.nodesize for n in [node.prevnode, node.nextnode])
if total_free < args.threshold:
continue
yield node, total_free
fragments = list(collect_significant_fragments())
if not fragments:
gdb.write(
f"No significant memory fragments found in {heap.name} "
f"(threshold: {args.threshold} bytes)\n"
)
return
gdb.write("-" * 106 + "\n")
print_fragment_header()
fragments.sort(key=lambda x: x[1], reverse=True)
for node, total_free in fragments[: args.top]:
print_fragment_node(node, total_free, args.no_backtrace)
@utils.dont_repeat_decorator
def invoke(self, args, from_tty):
parsed_args = self.parser.parse_args(gdb.string_to_argv(args))
if not parsed_args:
return
heaps = get_heaps(parsed_args.heap)
for heap in heaps:
self.show_fragmentation_rate(heap)
if parsed_args.verbose:
self.show_memory_fragments(heap, parsed_args)
def diagnose(self, *args, **kwargs):
return {
"title": "Memory Fragments Report",
"summary": "Display fragmentation causes (orphan nodes between free nodes)",
"command": "mm frag",
"result": "info",
"category": utils.DiagnoseCategory.memory,
"message": gdb.execute("mm frag --verbose", to_string=True),
}
@autocompeletion.complete
class MMMap(gdb.Command):
"""Generate memory map image to visualize memory layout"""
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument(
"-o",
"--output",
type=str,
metavar="file",
default=None,
help="img output file",
)
parser.add_argument(
"--heap",
type=str,
metavar="symbol",
help="Which heap's pool to show",
default=None,
)
return parser
def __init__(self):
self.np = utils.import_check("numpy", errmsg="Please pip install numpy\n")
self.plt = utils.import_check(
"matplotlib", "pyplot", errmsg="Please pip install matplotlib\n"
)
self.math = utils.import_check("math")
if not self.np or not self.plt or not self.math:
return
super().__init__("mm map", gdb.COMMAND_USER)
utils.alias("memmap", "mm map")
self.parser = self.get_argparser()
def save_memory_map(self, nodes: List[MMNodeDump], output_file):
mallinfo = sorted(nodes, key=lambda node: node.address)
start = mallinfo[0].address
size = mallinfo[-1].address - start
order = self.math.ceil(size**0.5)
img = self.np.zeros([order, order])
for node in mallinfo:
addr = node.address
size = node.nodesize
start_index = addr - start
end_index = start_index + size
img.flat[start_index:end_index] = 1 + self.math.log2(node.seqno + 1)
self.plt.imsave(output_file, img, cmap=self.plt.get_cmap("Greens"))
def parse_arguments(self, argv):
try:
args = self.parser.parse_args(argv)
except SystemExit:
return None
return args
@utils.dont_repeat_decorator
def invoke(self, args, from_tty):
if not (args := self.parse_arguments(gdb.string_to_argv(args))):
return
for heap in get_heaps(args.heap):
name = heap.name or f"heap@{heap.address:#x}"
output = args.output or f"{name}.png"
self.save_memory_map(heap.nodes_used(), output)
gdb.write(f"Memory map saved to {output}\n")
@autocompeletion.complete
class MMVisualize(gdb.Command):
"""Generates a memory treemap, showing all backtrace statistics"""
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument(
"-o",
"--output",
type=str,
metavar="file",
default="mm_visualize",
help="html output file",
)
parser.add_argument(
"--heap",
type=str,
metavar="symbol",
help="Which heap's pool to show",
default=None,
)
return parser
def __init__(self):
self.backtrace_depth = mm.MM_RECORD_STACK_DEPTH
if self.backtrace_depth <= 0:
gdb.write(
"Without mm record backtrace enabled, visualization is not possible\n"
)
return
super().__init__("mm visualize", gdb.COMMAND_USER)
utils.alias("memvisualize", "mm visualize")
self.px = utils.import_check(
"plotly.express", errmsg="Please pip install plotly\n"
)
self.pd = utils.import_check("pandas", errmsg="Please pip install pandas\n")
self.parser = self.get_argparser()
@utils.dont_repeat_decorator
def invoke(self, args, from_tty):
try:
args = self.parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
nodes = mm.get_nodes_dict()
df = self.pd.DataFrame.from_records(nodes)
df = df[~df["free"]]
df = (
df.groupby(["backtrace", "pid"], as_index=False)
.agg(
{
"name": lambda x: x.iloc[0],
"address": "any",
"size": "sum",
"pid": "first",
"from_pool": "first",
}
)
.reset_index()
)
for i in range(self.backtrace_depth):
df[f"stack_{i}"] = df["backtrace"].apply(
lambda x: (
"Unkown" if i >= len(x) or not x[i] else utils.Symbol(x[i]).func
)
)
df = df.drop(columns=["backtrace"])
stack_columns = [f"stack_{i}" for i in range(8)]
for index, row in df.iterrows():
stack_values = row[stack_columns].tolist()
unknowns = [val for val in stack_values if val == "Unkown"]
non_unknowns = [val for val in stack_values if val != "Unkown"]
new_stack_values = unknowns + non_unknowns
df.loc[index, stack_columns] = new_stack_values
stack_cols = [f"stack_{i}" for i in range(self.backtrace_depth - 1, -1, -1)]
fig = self.px.treemap(
df,
path=stack_cols,
values="size",
hover_data=["pid", "from_pool"],
title="Memory Allocation",
)
fig.update_layout(
margin=dict(t=50, l=25, r=25, b=25),
coloraxis_colorbar=dict(title="Size (bytes)"),
)
fig.update_traces(
maxdepth=3,
texttemplate="%{label}<br>%{value:,d} bytes",
textposition="middle center",
marker=dict(line=dict(width=1, color="DarkGray")),
)
path = args.output + ".html"
fig.write_html(path)
gdb.write(f"Memory map saved to Memory visualizations saved to {path}\n")
@autocompeletion.complete
class MMFree(gdb.Command):
"""Show heap statistics, same as device command free"""
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument(
"--heap",
type=str,
default=None,
metavar="symbol",
help="Which heap to inspect",
)
parser.add_argument(
"-t",
"--thread-usage",
action="store_true",
help="Show memory usage of each thread",
)
return parser
def __init__(self):
super().__init__("mm free", gdb.COMMAND_USER)
utils.alias("free", "mm free")
self.parser = self.get_argparser()
@utils.dont_repeat_decorator
def invoke(self, args, from_tty):
try:
args = self.parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
heaps = get_heaps(args.heap)
formatter = "{:<20} {:<10} {:<10} {:<10} {:<10} {:<10} {:<10} {:<10}"
header = (
"name",
"total",
"used",
"free",
"maxused",
"maxfree",
"nused",
"nfree",
)
print(formatter.format(*header))
mm_heap_s = utils.lookup_type("struct mm_heap_s")
summary = defaultdict(lambda: {"size": 0, "count": 0})
mempool_free = 0
for heap in heaps:
heap_free = heap_used = 0
total_size = max_free = nused = nfree = 0
for node in heap.nodes:
nodesize = node.nodesize
total_size += nodesize
if node.is_free:
nfree += 1
heap_free += nodesize
max_free = max(max_free, nodesize)
else:
heap_used += nodesize
nused += 1
summary[node.pid]["size"] += node.nodesize
summary[node.pid]["count"] += 1
mempool_total = 0
mempool_maxused = 0
mempool_used = 0
mempool_nused = 0
mempool_nfree = 0
mempool_free = 0
if mm_pool := heap.mm_mpool:
mpool = mm.MemPoolMultiple(mm_pool)
for chunk in mpool.chunks:
mempool_total += int(chunk.end) - int(chunk.start)
for pool in mpool.pools:
mempool_maxused += pool.total
for blk in pool.blks:
if blk.is_free:
mempool_free += blk.nodesize
mempool_nfree += 1
else:
mempool_used += blk.nodesize
mempool_nused += 1
summary[blk.pid]["size"] += node.nodesize
summary[blk.pid]["count"] += 1
total = heap.heapsize + mm_heap_s.sizeof
heap_used += mm_heap_s.sizeof
print(
formatter.format(
heap.name,
total,
heap_used - mempool_free,
heap_free + mempool_free,
int(heap.mm_maxused),
max_free,
nused,
nfree,
)
)
if mempool_total:
print(
formatter.format(
f"{heap.name}@mempool",
mempool_total,
mempool_used,
mempool_total - mempool_used,
mempool_maxused,
mempool_total - mempool_maxused,
mempool_nused,
mempool_nfree,
)
)
if args.thread_usage:
print("\nSummary of thread memory usage:")
formatter = "{:<10} {:<30} {:<16} {:<10} {:<10}"
print(formatter.format("PID", "TaskName", "Size", "Size(kB)", "Count"))
for pid, data in sorted(
summary.items(), key=lambda x: x[1]["size"], reverse=True
):
print(
formatter.format(
pid,
utils.get_task_name(pid) or "<noname>",
data["size"],
f"{data['size'] / 1024:.1f}kB",
data["count"],
)
)
def diagnose(self, *args, **kwargs):
return {
"title": "Memory Free Information",
"summary": "Memory free information",
"command": "free",
"result": "info",
"category": utils.DiagnoseCategory.memory,
"message": gdb.execute("mm free --thread-usage", to_string=True),
}
@autocompeletion.complete
class NxMemoryRange(gdb.Command):
"""Show RAM range of heap and sections"""
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument("--heap-only", action="store_true", help="Heap only")
parser.add_argument(
"--globals-only", action="store_true", help="Global variables only"
)
return parser
def __init__(self):
super().__init__("mm range", gdb.COMMAND_USER)
utils.alias("memrange", "mm range")
self.parser = self.get_argparser()
@utils.dont_repeat_decorator
def invoke(self, arg, from_tty):
try:
args = self.parser.parse_args(gdb.string_to_argv(arg))
except SystemExit:
return
memrange = mm.get_memrange(None, args.heap_only, args.globals_only)
if not memrange:
print("No memory range found")
return
header = ("start", "end", "size", "size(kB)")
formatter = "{:<20} {:<20} {:<20} {:<20}"
print(formatter.format(*header))
for start, end in memrange:
length = end - start
print(
formatter.format(hex(start), hex(end), length, f"{length / 1024: .1f}")
)
@autocompeletion.complete
class NxDumpRAM(gdb.Command):
"""Dump memory to file, similar to GDB dump memory"""
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument(
"-o",
"--output",
help="Memory dump output directory",
metavar="file",
default="memdump",
)
parser.add_argument("-r", "--memrange", type=str, default=None)
parser.add_argument("--heap-only", action="store_true", help="Heap only")
parser.add_argument(
"--globals-only", action="store_true", help="Global variables only"
)
return parser
def __init__(self):
super().__init__("dump ram", gdb.COMMAND_USER)
self.parser = self.get_argparser()
@utils.dont_repeat_decorator
def invoke(self, arg: str, from_tty: bool) -> None:
try:
args = self.parser.parse_args(gdb.string_to_argv(arg))
except SystemExit:
return
memrange = mm.get_memrange(args.memrange, args.heap_only, args.globals_only)
if not memrange:
print("No memory range found")
return
gdb.execute("set trust-readonly-sections on")
print(f"Dumping memory to {args.output}")
os.makedirs(args.output, exist_ok=True)
for start, end in memrange:
print(f"Dumping memory range {start:#x} - {end:#x}")
try:
data = gdb.selected_inferior().read_memory(start, end - start)
output = os.path.join(args.output, f"{start:#x}.bin")
except gdb.MemoryError:
print(f"Failed to read memory range {start:#x} - {end:#x}")
continue
with open(output, "wb") as f:
f.write(data)
@autocompeletion.complete
class NxMemoryFind(gdb.Command):
"""Find memory address by pattern"""
def get_argparser(self):
parser = argparse.ArgumentParser(description=self.__doc__)
parser.add_argument("pattern", type=str, help="Pattern to search")
parser.add_argument("-r", "--memrange", type=str, default=None)
parser.add_argument("--heap-only", action="store_true", help="Heap only")
parser.add_argument(
"--globals-only", action="store_true", help="Global variables only"
)
return parser
def __init__(self):
super().__init__("mm find", gdb.COMMAND_USER)
utils.alias("memfind", "mm find")
self.parser = self.get_argparser()
@utils.dont_repeat_decorator
def invoke(self, arg: str, from_tty: bool) -> None:
try:
args = self.parser.parse_args(gdb.string_to_argv(arg))
except SystemExit:
return
if args.pattern.startswith('"') and args.pattern.endswith('"'):
value = bytes(args.pattern[1:-1], "utf-8")
else:
value = utils.parse_arg(args.pattern)
value = value.to_bytes((value.bit_length() + 7) // 8, "little")
print(f"Searching for pattern {binascii.hexlify(value)} in memory")
memrange = mm.get_memrange(args.memrange, args.heap_only, args.globals_only)
for start, end in memrange:
try:
data = bytes(gdb.selected_inferior().read_memory(start, end - start))
except gdb.MemoryError:
print(f"Failed to read memory range {start:#x} - {end:#x}")
continue
offset = 0
while True:
offset = data.find(value, offset)
if offset == -1:
break
print(f"Found pattern @ {offset + start:#x}")
offset += 1
print("Done")