Skip to content

Commit c1f9c73

Browse files
XuNeoxiaoxiang781216
authored andcommitted
gdb: add tool to check heap memory corruption
Only heap nodes are checked for now. Signed-off-by: xuxingliang <[email protected]>
1 parent 8f9c1cb commit c1f9c73

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

tools/gdb/nuttxgdb/memcheck.py

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
############################################################################
2+
# tools/gdb/nuttxgdb/memcheck.py
3+
#
4+
# Licensed to the Apache Software Foundation (ASF) under one or more
5+
# contributor license agreements. See the NOTICE file distributed with
6+
# this work for additional information regarding copyright ownership. The
7+
# ASF licenses this file to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance with the
9+
# License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16+
# License for the specific language governing permissions and limitations
17+
# under the License.
18+
#
19+
############################################################################
20+
21+
import traceback
22+
from collections import defaultdict
23+
from typing import Dict, List, Tuple
24+
25+
import gdb
26+
27+
from . import memdump, mm, utils
28+
29+
30+
class MMCheck(gdb.Command):
31+
"""Check memory manager and pool integrity"""
32+
33+
def __init__(self):
34+
super().__init__("mm check", gdb.COMMAND_USER)
35+
utils.alias("memcheck", "mm check")
36+
37+
def check_heap(self, heap: mm.MMHeap) -> Dict[int, List[str]]: # noqa: C901
38+
"""Check heap integrity and return list of issues in string"""
39+
issues = defaultdict(list) # key: address, value: list of issues
40+
41+
def report(e, heap, node: mm.MMNode) -> None:
42+
gdb.write(f"Error happened during heap check: {e}\n")
43+
try:
44+
gdb.write(f" heap: {heap}\n")
45+
gdb.write(f"current node: {node}")
46+
if node.prevnode:
47+
gdb.write(f" prev node: {node.prevnode}")
48+
if node.nextnode:
49+
gdb.write(f" next node: {node.nextnode}")
50+
51+
gdb.write("\n")
52+
except gdb.error as e:
53+
gdb.write(f"Error happened during report: {e}\n")
54+
55+
def is_node_corrupted(node: mm.MMNode) -> Tuple[bool, str]:
56+
# Must be in this heap
57+
if not heap.contains(node.address):
58+
return True, f"node@{hex(node.address)} not in heap"
59+
60+
# Check next node
61+
if node.nodesize > node.MM_SIZEOF_ALLOCNODE:
62+
nextnode = node.nextnode
63+
if not heap.contains(nextnode.address):
64+
return True, f"nexnode@{hex(nextnode.address)} not in heap"
65+
if node.is_free:
66+
if not nextnode.is_prev_free:
67+
# This node is free, then next node must have prev free set
68+
return (
69+
True,
70+
f"nextnode@{hex(nextnode.address)} not marked as prev free",
71+
)
72+
73+
if nextnode.prevsize != node.nodesize:
74+
return (
75+
True,
76+
f"nextnode @{hex(nextnode.address)} prevsize not match",
77+
)
78+
79+
if node.is_free:
80+
if node.nodesize < node.MM_MIN_CHUNK:
81+
return True, f"nodesize {int(node.nodesize)} too small"
82+
83+
if node.flink and node.flink.blink != node:
84+
return (
85+
True,
86+
f"flink not intact: {hex(node.flink.blink)}, node: {hex(node.address)}",
87+
)
88+
89+
if node.blink.flink != node:
90+
return (
91+
True,
92+
f"blink not intact: {hex(node.blink.flink)}, node: {hex(node.address)}",
93+
)
94+
95+
# Node should be in correctly sorted order
96+
if (blinksize := mm.MMNode(node.blink).nodesize) > node.nodesize:
97+
return (
98+
True,
99+
f"blink node not in sorted order: {blinksize} > {node.nodesize}",
100+
)
101+
102+
fnode = mm.MMNode(node.flink) if node.flink else None
103+
if fnode and fnode.nodesize and fnode.nodesize < node.nodesize:
104+
return (
105+
True,
106+
f"flink node not in sorted order: {fnode.nodesize} < {node.nodesize}",
107+
)
108+
else:
109+
# Node is allocated.
110+
if node.nodesize < node.MM_SIZEOF_ALLOCNODE:
111+
return True, f"nodesize {node.nodesize} too small"
112+
113+
return False, ""
114+
115+
try:
116+
# Check nodes in physical memory order
117+
for node in heap.nodes:
118+
corrupted, reason = is_node_corrupted(node)
119+
if corrupted:
120+
issues[node.address].append(reason)
121+
122+
# Check free list
123+
for node in utils.ArrayIterator(heap.mm_nodelist):
124+
# node is in type of gdb.Value, struct mm_freenode_s
125+
while node:
126+
address = int(node.address)
127+
if node["flink"] and not heap.contains(node["flink"]):
128+
issues[address].append(
129+
f"flink {hex(node['flink'])} not in heap"
130+
)
131+
break
132+
133+
if address in issues or node["size"] == 0:
134+
# This node is already checked or size is 0, which is a node in node table
135+
node = node["flink"]
136+
continue
137+
138+
# Check if this node is corrupted
139+
corrupted, reason = is_node_corrupted(mm.MMNode(node))
140+
if corrupted:
141+
issues[address].append(reason)
142+
break
143+
144+
# Continue to it's flink
145+
node = node["flink"]
146+
147+
except Exception as e:
148+
report(e, heap, node)
149+
traceback.print_exc()
150+
151+
return issues
152+
153+
def dump_issues(self, heap, issues: Dict[int, List[str]]) -> None:
154+
for address, reasons in issues.items():
155+
gdb.write(
156+
f"{len(reasons)} issues @{hex(address)}: " f"{','.join(reasons)}\n"
157+
)
158+
159+
def invoke(self, arg: str, from_tty: bool) -> None:
160+
try:
161+
heaps = memdump.get_heaps()
162+
for heap in heaps:
163+
issues = self.check_heap(heap)
164+
if not issues:
165+
continue
166+
167+
print(f"Found {len(issues)} issues in heap {heap}")
168+
self.dump_issues(heap, issues)
169+
except Exception as e:
170+
print(f"Error happened during check: {e}")
171+
traceback.print_exc()
172+
173+
print("Check done.")

0 commit comments

Comments
 (0)