mirror of
https://github.com/apache/nuttx.git
synced 2026-08-18 20:18:17 +00:00
tools/gdb: optimize memleak speed
1. Avoid to_bytes by using memoryview directly. 2. No need to call gdb to cast to char *. 3. Cache the memory data without invoke gdb in every iteration. 4. Do code cleanup. memleak speed improved from 261.93 seconds to 29.9 seconds for x4b usecase. Signed-off-by: xuxingliang <xuxingliang@xiaomi.com>
This commit is contained in:
parent
4e067fd762
commit
9dbf58f1be
1 changed files with 91 additions and 103 deletions
|
|
@ -44,6 +44,13 @@ PID_MM_ALLOC = -3
|
|||
PID_MM_LEAK = -2
|
||||
PID_MM_MEMPOOL = -1
|
||||
|
||||
mm_allocnode_type = gdb.lookup_type("struct mm_allocnode_s")
|
||||
sizeof_size_t = gdb.lookup_type("size_t").sizeof
|
||||
mempool_backtrace_type = gdb.lookup_type("struct mempool_backtrace_s")
|
||||
|
||||
CONFIG_MM_BACKTRACE = get_symbol_value("CONFIG_MM_BACKTRACE")
|
||||
CONFIG_MM_DFAULT_ALIGNMENT = get_symbol_value("CONFIG_MM_DFAULT_ALIGNMENT")
|
||||
|
||||
|
||||
def align_up(size, align) -> int:
|
||||
"""Align the size to the specified alignment"""
|
||||
|
|
@ -57,26 +64,24 @@ def mm_nodesize(size) -> int:
|
|||
|
||||
def mm_foreach(heap):
|
||||
"""Iterate over a heap, yielding each node"""
|
||||
node = gdb.Value(heap["mm_heapstart"][0]).cast(
|
||||
gdb.lookup_type("struct mm_allocnode_s").pointer()
|
||||
)
|
||||
nregions = get_symbol_value("CONFIG_MM_REGIONS")
|
||||
heapstart = heap["mm_heapstart"]
|
||||
heapend = heap["mm_heapend"]
|
||||
|
||||
for region in range(0, get_symbol_value("CONFIG_MM_REGIONS")):
|
||||
while 1:
|
||||
for region in range(0, nregions):
|
||||
start = heapstart[region]
|
||||
end = heapend[region]
|
||||
node = start
|
||||
while node <= end:
|
||||
yield node
|
||||
next = gdb.Value(node).cast(gdb.lookup_type("char").pointer())
|
||||
next = gdb.Value(next + mm_nodesize(node["size"])).cast(
|
||||
gdb.lookup_type("struct mm_allocnode_s").pointer()
|
||||
)
|
||||
|
||||
if next >= heap["mm_heapend"][region] or next == node:
|
||||
break
|
||||
next = int(node) + mm_nodesize(node["size"])
|
||||
next = gdb.Value(next).cast(mm_allocnode_type.pointer())
|
||||
node = next
|
||||
|
||||
|
||||
def mm_dumpnode(node, count, align, simple, detail, alive):
|
||||
if node["size"] & MM_ALLOC_BIT != 0:
|
||||
charnode = gdb.Value(node).cast(gdb.lookup_type("char").pointer())
|
||||
charnode = int(node)
|
||||
if not alive:
|
||||
# if pid is not alive put a red asterisk.
|
||||
gdb.write("\x1b[33;1m*\x1b[m")
|
||||
|
|
@ -91,7 +96,7 @@ def mm_dumpnode(node, count, align, simple, detail, alive):
|
|||
mm_nodesize(node["size"]),
|
||||
node["seqno"],
|
||||
align,
|
||||
(int)(charnode + gdb.lookup_type("struct mm_allocnode_s").sizeof),
|
||||
charnode + mm_allocnode_type.sizeof,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -126,13 +131,13 @@ def mm_dumpnode(node, count, align, simple, detail, alive):
|
|||
)
|
||||
|
||||
else:
|
||||
charnode = gdb.Value(node).cast(gdb.lookup_type("char").pointer())
|
||||
charnode = int(node)
|
||||
gdb.write(
|
||||
"%12u%#*x"
|
||||
% (
|
||||
mm_nodesize(node["size"]),
|
||||
align,
|
||||
(int)(charnode + gdb.lookup_type("struct mm_allocnode_s").sizeof),
|
||||
charnode + mm_allocnode_type.sizeof,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -151,17 +156,14 @@ def mempool_multiple_foreach(mpool):
|
|||
def mempool_realblocksize(pool):
|
||||
"""Return the real block size of a mempool"""
|
||||
|
||||
if get_symbol_value("CONFIG_MM_DFAULT_ALIGNMENT") is None:
|
||||
mempool_align = 2 * gdb.lookup_type("size_t").sizeof
|
||||
if CONFIG_MM_DFAULT_ALIGNMENT:
|
||||
mempool_align = CONFIG_MM_DFAULT_ALIGNMENT
|
||||
else:
|
||||
mempool_align = get_symbol_value("CONFIG_MM_DFAULT_ALIGNMENT")
|
||||
mempool_align = 2 * sizeof_size_t
|
||||
|
||||
if mempool_align == 0:
|
||||
mempool_align = 2 * gdb.lookup_type("size_t").sizeof
|
||||
|
||||
if get_symbol_value("CONFIG_MM_BACKTRACE") >= 0:
|
||||
if CONFIG_MM_BACKTRACE >= 0:
|
||||
return align_up(
|
||||
pool["blocksize"] + gdb.lookup_type("struct mempool_backtrace_s").sizeof,
|
||||
pool["blocksize"] + mempool_backtrace_type.sizeof,
|
||||
mempool_align,
|
||||
)
|
||||
else:
|
||||
|
|
@ -204,25 +206,25 @@ def get_count(element):
|
|||
def mempool_foreach(pool):
|
||||
"""Iterate over all block in a mempool"""
|
||||
|
||||
sq_entry_type = gdb.lookup_type("sq_entry_t")
|
||||
|
||||
blocksize = mempool_realblocksize(pool)
|
||||
if pool["ibase"] != 0:
|
||||
nblk = pool["interruptsize"] / blocksize
|
||||
while nblk > 0:
|
||||
bufaddr = gdb.Value(pool["ibase"] + nblk * blocksize + pool["blocksize"])
|
||||
buf = bufaddr.cast(gdb.lookup_type("struct mempool_backtrace_s").pointer())
|
||||
buf = bufaddr.cast(mempool_backtrace_type.pointer())
|
||||
yield buf
|
||||
nblk -= 1
|
||||
|
||||
entry = sq_queue.get_type().pointer()
|
||||
for entry in sq_for_every(pool["equeue"], entry):
|
||||
nblk = (pool["expandsize"] - gdb.lookup_type("sq_entry_t").sizeof) / blocksize
|
||||
base = (
|
||||
gdb.Value(entry).cast(gdb.lookup_type("char").pointer()) - nblk * blocksize
|
||||
)
|
||||
nblk = (pool["expandsize"] - sq_entry_type.sizeof) / blocksize
|
||||
base = int(entry) - nblk * blocksize
|
||||
while nblk > 0:
|
||||
nblk -= 1
|
||||
bufaddr = gdb.Value(base + nblk * blocksize + pool["blocksize"])
|
||||
buf = bufaddr.cast(gdb.lookup_type("struct mempool_backtrace_s").pointer())
|
||||
buf = bufaddr.cast(mempool_backtrace_type.pointer())
|
||||
yield buf
|
||||
|
||||
|
||||
|
|
@ -280,11 +282,11 @@ def mempool_dumpbuf(buf, blksize, count, align, simple, detail, alive):
|
|||
gdb.write("\n")
|
||||
|
||||
|
||||
class Nxmemdump(gdb.Command):
|
||||
class Memdump(gdb.Command):
|
||||
"""Dump the heap and mempool memory"""
|
||||
|
||||
def __init__(self):
|
||||
super(Nxmemdump, self).__init__("memdump", gdb.COMMAND_USER)
|
||||
super(Memdump, self).__init__("memdump", gdb.COMMAND_USER)
|
||||
|
||||
def check_alive(self, pid):
|
||||
return self.pidhash[pid & self.npidhash - 1] != 0
|
||||
|
|
@ -311,9 +313,7 @@ class Nxmemdump(gdb.Command):
|
|||
and (buf["seqno"] >= seqmin and buf["seqno"] < seqmax)
|
||||
and buf["magic"] == MEMPOOL_MAGIC_ALLOC
|
||||
):
|
||||
charnode = gdb.Value(buf).cast(
|
||||
gdb.lookup_type("char").pointer()
|
||||
)
|
||||
charnode = int(buf)
|
||||
if detail:
|
||||
mempool_dumpbuf(
|
||||
buf,
|
||||
|
|
@ -329,10 +329,9 @@ class Nxmemdump(gdb.Command):
|
|||
buf, pool["blocksize"], self.backtrace_dict
|
||||
)
|
||||
if address and (
|
||||
address < int(charnode)
|
||||
and address >= (int)(charnode - pool["blocksize"])
|
||||
address < charnode
|
||||
and address >= charnode - pool["blocksize"]
|
||||
):
|
||||
|
||||
mempool_dumpbuf(
|
||||
buf,
|
||||
pool["blocksize"],
|
||||
|
|
@ -401,13 +400,10 @@ class Nxmemdump(gdb.Command):
|
|||
node, mm_nodesize(node["size"]), self.backtrace_dict
|
||||
)
|
||||
|
||||
charnode = gdb.Value(node).cast(gdb.lookup_type("char").pointer())
|
||||
charnode = int(node)
|
||||
if address and (
|
||||
address < int(charnode + node["size"])
|
||||
and address
|
||||
>= (int)(
|
||||
charnode + gdb.lookup_type("struct mm_allocnode_s").sizeof
|
||||
)
|
||||
address < charnode + node["size"]
|
||||
and address >= charnode + mm_allocnode_type.sizeof
|
||||
):
|
||||
mm_dumpnode(
|
||||
node,
|
||||
|
|
@ -448,10 +444,7 @@ class Nxmemdump(gdb.Command):
|
|||
|
||||
output.sort(key=get_count, reverse=True)
|
||||
for node in output:
|
||||
if (
|
||||
node["node"].type
|
||||
== gdb.lookup_type("struct mm_allocnode_s").pointer()
|
||||
):
|
||||
if node["node"].type == mm_allocnode_type.pointer():
|
||||
mm_dumpnode(
|
||||
node["node"],
|
||||
node["count"],
|
||||
|
|
@ -519,7 +512,7 @@ class Nxmemdump(gdb.Command):
|
|||
}
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
if gdb.lookup_type("size_t").sizeof == 4:
|
||||
if sizeof_size_t == 4:
|
||||
self.align = 11
|
||||
else:
|
||||
self.align = 19
|
||||
|
|
@ -536,7 +529,7 @@ class Nxmemdump(gdb.Command):
|
|||
pid = PID_MM_FREE
|
||||
elif arg["pid"]:
|
||||
pid = arg["pid"]
|
||||
if get_symbol_value("CONFIG_MM_BACKTRACE") <= 0:
|
||||
if CONFIG_MM_BACKTRACE <= 0:
|
||||
arg["detail"] = True
|
||||
|
||||
self.aordblks = 0
|
||||
|
|
@ -549,9 +542,6 @@ class Nxmemdump(gdb.Command):
|
|||
)
|
||||
|
||||
|
||||
Nxmemdump()
|
||||
|
||||
|
||||
class Memleak(gdb.Command):
|
||||
"""Memleak check"""
|
||||
|
||||
|
|
@ -562,28 +552,41 @@ class Memleak(gdb.Command):
|
|||
return self.pidhash[pid & self.npidhash - 1] != 0
|
||||
|
||||
def next_ptr(self):
|
||||
|
||||
inf = gdb.inferiors()[0]
|
||||
inf = gdb.selected_inferior()
|
||||
heap = gdb.parse_and_eval("g_mmheap")
|
||||
start = []
|
||||
end = []
|
||||
longsize = get_long_type().sizeof
|
||||
region = get_symbol_value("CONFIG_MM_REGIONS")
|
||||
regions = []
|
||||
|
||||
for i in range(0, region):
|
||||
start.append(int(gdb.Value(heap["mm_heapstart"][i])))
|
||||
end.append(int(gdb.Value(heap["mm_heapend"][i])))
|
||||
start = int(heap["mm_heapstart"][i])
|
||||
end = int(heap["mm_heapend"][i])
|
||||
mem = inf.read_memory(start, end - start)
|
||||
regions.append({"start": start, "end": end, "mem": mem})
|
||||
|
||||
# Serach in global variables
|
||||
sdata = gdb.parse_and_eval("(uintptr_t)&_sdata")
|
||||
ebss = gdb.parse_and_eval("(uintptr_t)&_ebss")
|
||||
# Search global variables
|
||||
sdata = int(gdb.parse_and_eval("(uintptr_t)&_sdata"))
|
||||
ebss = int(gdb.parse_and_eval("(uintptr_t)&_ebss"))
|
||||
global_size = (ebss - sdata) // longsize * longsize
|
||||
gdb.write(f"Searching in global variables {hex(sdata)} ~ {hex(ebss)}\n")
|
||||
mem = inf.read_memory(int(sdata), int(ebss) - int(sdata))
|
||||
global_mem = inf.read_memory(sdata, global_size)
|
||||
|
||||
def read_memory(addr, size):
|
||||
# check global variable
|
||||
if addr >= sdata and addr + size <= ebss:
|
||||
return global_mem[addr - sdata : addr - sdata + size]
|
||||
|
||||
for region in regions:
|
||||
start = region["start"]
|
||||
end = region["end"]
|
||||
if addr >= start and addr + size <= end:
|
||||
return region["mem"][addr - start : addr - start + size]
|
||||
|
||||
i = 0
|
||||
while i < int(ebss) - int(sdata):
|
||||
ptr = read_ulong(mem[i : i + longsize].tobytes(), 0)
|
||||
for j in range(0, region):
|
||||
if ptr >= start[j] and ptr < end[j]:
|
||||
while i < global_size:
|
||||
ptr = read_ulong(global_mem, i)
|
||||
for region in regions:
|
||||
if ptr >= region["start"] and ptr < region["end"]:
|
||||
yield ptr
|
||||
break
|
||||
|
||||
|
|
@ -592,29 +595,26 @@ class Memleak(gdb.Command):
|
|||
gdb.write("Searching in grey memory\n")
|
||||
for node in self.grey_list:
|
||||
addr = node["addr"]
|
||||
mem = inf.read_memory(addr, node["size"])
|
||||
mem = read_memory(addr, node["size"])
|
||||
i = 0
|
||||
while i < node["size"]:
|
||||
ptr = read_ulong(mem[i : i + longsize].tobytes(), 0)
|
||||
for j in range(0, region):
|
||||
if ptr >= start[j] and ptr < end[j]:
|
||||
ptr = read_ulong(mem, i)
|
||||
for region in regions:
|
||||
if ptr >= region["start"] and ptr < region["end"]:
|
||||
yield ptr
|
||||
break
|
||||
i = i + longsize
|
||||
|
||||
def collect_white_dict(self):
|
||||
white_dict = {}
|
||||
allocnode_size = gdb.lookup_type("struct mm_allocnode_s").sizeof
|
||||
allocnode_size = mm_allocnode_type.sizeof
|
||||
|
||||
# collect all user malloc ptr
|
||||
|
||||
heap = gdb.parse_and_eval("g_mmheap")
|
||||
for node in mm_foreach(heap):
|
||||
if node["size"] & MM_ALLOC_BIT != 0 and node["pid"] != PID_MM_MEMPOOL:
|
||||
addr = (
|
||||
gdb.Value(node).cast(gdb.lookup_type("char").pointer())
|
||||
+ allocnode_size
|
||||
)
|
||||
addr = int(node) + allocnode_size
|
||||
|
||||
node_dict = {}
|
||||
node_dict["node"] = node
|
||||
|
|
@ -626,10 +626,7 @@ class Memleak(gdb.Command):
|
|||
for pool in mempool_multiple_foreach(heap["mm_mpool"]):
|
||||
for buf in mempool_foreach(pool):
|
||||
if buf["magic"] == MEMPOOL_MAGIC_ALLOC:
|
||||
addr = (
|
||||
gdb.Value(buf).cast(gdb.lookup_type("char").pointer())
|
||||
- pool["blocksize"]
|
||||
)
|
||||
addr = int(buf) - pool["blocksize"]
|
||||
|
||||
buf_dict = {}
|
||||
buf_dict["node"] = buf
|
||||
|
|
@ -666,7 +663,7 @@ class Memleak(gdb.Command):
|
|||
return {"simple": args.simple, "detail": args.detail}
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
if gdb.lookup_type("size_t").sizeof == 4:
|
||||
if sizeof_size_t == 4:
|
||||
align = 11
|
||||
else:
|
||||
align = 19
|
||||
|
|
@ -676,34 +673,30 @@ class Memleak(gdb.Command):
|
|||
if arg is None:
|
||||
return
|
||||
|
||||
if get_symbol_value("CONFIG_MM_BACKTRACE") <= 0:
|
||||
if CONFIG_MM_BACKTRACE <= 0:
|
||||
gdb.write("Better use CONFIG_MM_BACKTRACE=16 or 8 get more information\n")
|
||||
|
||||
start = last = time.time()
|
||||
white_dict = self.collect_white_dict()
|
||||
|
||||
self.grey_list = []
|
||||
gdb.write("Searching for leaked memory, please wait a moment\n")
|
||||
start = time.time()
|
||||
last = time.time()
|
||||
|
||||
sorted_keys = sorted(white_dict.keys())
|
||||
|
||||
for ptr in self.next_ptr():
|
||||
# Find a closest addres in white_dict
|
||||
pos = bisect.bisect_right(sorted_keys, ptr)
|
||||
if pos == 0:
|
||||
continue
|
||||
grey_key = sorted_keys[pos - 1]
|
||||
if (
|
||||
grey_key in white_dict.keys()
|
||||
and ptr < grey_key + white_dict[grey_key]["size"]
|
||||
):
|
||||
if grey_key in white_dict and ptr < grey_key + white_dict[grey_key]["size"]:
|
||||
self.grey_list.append(white_dict[grey_key])
|
||||
del white_dict[grey_key]
|
||||
|
||||
# All white node is leak
|
||||
|
||||
end = time.time()
|
||||
gdb.write(f"Search all memory use {(end - start):.2f} seconds\n")
|
||||
gdb.write(f"Search all memory use {(time.time() - last):.2f} seconds\n")
|
||||
|
||||
gdb.write("\n")
|
||||
if len(white_dict) == 0:
|
||||
|
|
@ -712,7 +705,7 @@ class Memleak(gdb.Command):
|
|||
|
||||
gdb.write("Leak catch!, use '\x1b[33;1m*\x1b[m' mark pid is not exist:\n")
|
||||
|
||||
if get_symbol_value("CONFIG_MM_BACKTRACE") > 0 and arg["detail"] is False:
|
||||
if CONFIG_MM_BACKTRACE > 0 and not arg["detail"]:
|
||||
gdb.write("%6s" % ("CNT"))
|
||||
|
||||
gdb.write(
|
||||
|
|
@ -723,7 +716,7 @@ class Memleak(gdb.Command):
|
|||
self.npidhash = gdb.parse_and_eval("g_npidhash")
|
||||
self.pidhash = gdb.parse_and_eval("g_pidhash")
|
||||
|
||||
if get_symbol_value("CONFIG_MM_BACKTRACE") > 0 and arg["detail"] is False:
|
||||
if CONFIG_MM_BACKTRACE > 0 and not arg["detail"]:
|
||||
|
||||
# Filter same backtrace
|
||||
|
||||
|
|
@ -743,10 +736,7 @@ class Memleak(gdb.Command):
|
|||
|
||||
i = 0
|
||||
for node in leaklist:
|
||||
if (
|
||||
node["node"].type
|
||||
== gdb.lookup_type("struct mm_allocnode_s").pointer()
|
||||
):
|
||||
if node["node"].type == mm_allocnode_type.pointer():
|
||||
mm_dumpnode(
|
||||
node["node"],
|
||||
node["count"],
|
||||
|
|
@ -776,10 +766,7 @@ have {i} some backtrace leak, total leak memory is {int(leaksize)} bytes\n"
|
|||
else:
|
||||
leaksize = 0
|
||||
for node in white_dict.values():
|
||||
if (
|
||||
node["node"].type
|
||||
== gdb.lookup_type("struct mm_allocnode_s").pointer()
|
||||
):
|
||||
if node["node"].type == mm_allocnode_type.pointer():
|
||||
mm_dumpnode(
|
||||
node["node"],
|
||||
1,
|
||||
|
|
@ -804,8 +791,7 @@ have {i} some backtrace leak, total leak memory is {int(leaksize)} bytes\n"
|
|||
f"Alloc {len(white_dict)} count, total leak memory is {int(leaksize)} bytes\n"
|
||||
)
|
||||
|
||||
|
||||
Memleak()
|
||||
gdb.write(f"Finished in {(time.time() - start):.2f} seconds\n")
|
||||
|
||||
|
||||
class Memmap(gdb.Command):
|
||||
|
|
@ -849,7 +835,7 @@ class Memmap(gdb.Command):
|
|||
parser.add_argument(
|
||||
"-o", "--output", type=str, default="memmap", help="img output file"
|
||||
)
|
||||
if argv[0] == '':
|
||||
if argv[0] == "":
|
||||
argv = None
|
||||
try:
|
||||
args = parser.parse_args(argv)
|
||||
|
|
@ -863,4 +849,6 @@ class Memmap(gdb.Command):
|
|||
self.save_memory_map(meminfo, output_file + ".png")
|
||||
|
||||
|
||||
Memdump()
|
||||
Memleak()
|
||||
Memmap()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue