Skip to content

Commit fa75ad6

Browse files
committedNov 23, 2020
8256725: Metaspace: better blocktree and binlist asserts
Reviewed-by: shade, rrich, lkorinth
1 parent 9de5d09 commit fa75ad6

File tree

5 files changed

+160
-44
lines changed

5 files changed

+160
-44
lines changed
 

‎src/hotspot/share/memory/metaspace/binList.hpp

+15-12
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class BinListImpl {
8585
{}
8686
};
8787

88+
#define BLOCK_FORMAT "Block @" PTR_FORMAT ": size: " SIZE_FORMAT ", next: " PTR_FORMAT
89+
#define BLOCK_FORMAT_ARGS(b) p2i(b), (b)->_word_size, p2i((b)->_next)
90+
8891
// Smallest block size must be large enough to hold a Block structure.
8992
STATIC_ASSERT(smallest_word_size * sizeof(MetaWord) >= sizeof(Block));
9093
STATIC_ASSERT(num_lists > 0);
@@ -150,19 +153,16 @@ class BinListImpl {
150153
int index = index_for_word_size(word_size);
151154
index = index_for_next_non_empty_list(index);
152155
if (index != -1) {
153-
assert(_blocks[index] != NULL &&
154-
_blocks[index]->_word_size >= word_size, "sanity");
155-
156-
MetaWord* const p = (MetaWord*)_blocks[index];
156+
Block* b = _blocks[index];
157157
const size_t real_word_size = word_size_for_index(index);
158-
159-
_blocks[index] = _blocks[index]->_next;
160-
158+
assert(b != NULL, "Sanity");
159+
assert(b->_word_size >= word_size &&
160+
b->_word_size == real_word_size,
161+
"bad block size in list[%d] (" BLOCK_FORMAT ")", index, BLOCK_FORMAT_ARGS(b));
162+
_blocks[index] = b->_next;
161163
_counter.sub(real_word_size);
162164
*p_real_word_size = real_word_size;
163-
164-
return p;
165-
165+
return (MetaWord*)b;
166166
} else {
167167
*p_real_word_size = 0;
168168
return NULL;
@@ -182,8 +182,11 @@ class BinListImpl {
182182
MemRangeCounter local_counter;
183183
for (int i = 0; i < num_lists; i++) {
184184
const size_t s = MinWordSize + i;
185-
for (Block* b = _blocks[i]; b != NULL; b = b->_next) {
186-
assert(b->_word_size == s, "bad block size");
185+
int pos = 0;
186+
for (Block* b = _blocks[i]; b != NULL; b = b->_next, pos++) {
187+
assert(b->_word_size == s,
188+
"bad block size in list[%d] at pos %d (" BLOCK_FORMAT ")",
189+
i, pos, BLOCK_FORMAT_ARGS(b));
187190
local_counter.add(s);
188191
}
189192
}

‎src/hotspot/share/memory/metaspace/blockTree.cpp

+84-30
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525

2626
#include "precompiled.hpp"
27+
#include "memory/metaspace/chunklevel.hpp"
2728
#include "memory/metaspace/blockTree.hpp"
2829
#include "memory/resourceArea.hpp"
2930
#include "utilities/debug.hpp"
@@ -36,27 +37,43 @@ namespace metaspace {
3637
// Needed to prevent linker errors on MacOS and AIX
3738
const size_t BlockTree::MinWordSize;
3839

40+
#define NODE_FORMAT \
41+
"@" PTR_FORMAT \
42+
": canary " INTPTR_FORMAT \
43+
", parent " PTR_FORMAT \
44+
", left " PTR_FORMAT \
45+
", right " PTR_FORMAT \
46+
", next " PTR_FORMAT \
47+
", size " SIZE_FORMAT
48+
49+
#define NODE_FORMAT_ARGS(n) \
50+
p2i(n), \
51+
(n)->_canary, \
52+
p2i((n)->_parent), \
53+
p2i((n)->_left), \
54+
p2i((n)->_right), \
55+
p2i((n)->_next), \
56+
(n)->_word_size
57+
3958
#ifdef ASSERT
4059

4160
// Tree verification
4261

43-
// These asserts prints the tree, then asserts
44-
#define assrt(cond, format, ...) \
62+
// This assert prints the tree too
63+
#define tree_assert(cond, format, ...) \
4564
do { \
4665
if (!(cond)) { \
66+
tty->print("Error in tree @" PTR_FORMAT ": ", p2i(this)); \
67+
tty->print_cr(format, __VA_ARGS__); \
68+
tty->print_cr("Tree:"); \
4769
print_tree(tty); \
4870
assert(cond, format, __VA_ARGS__); \
4971
} \
5072
} while (0)
5173

52-
// This assert prints the tree, then stops (generic message)
53-
#define assrt0(cond) \
54-
do { \
55-
if (!(cond)) { \
56-
print_tree(tty); \
57-
assert(cond, "sanity"); \
58-
} \
59-
} while (0)
74+
// Assert, prints tree and specific given node
75+
#define tree_assert_invalid_node(cond, failure_node) \
76+
tree_assert(cond, "Invalid node: " NODE_FORMAT, NODE_FORMAT_ARGS(failure_node))
6077

6178
// walkinfo keeps a node plus the size corridor it and its children
6279
// are supposed to be in.
@@ -67,12 +84,24 @@ struct BlockTree::walkinfo {
6784
size_t lim2; // )
6885
};
6986

87+
// Helper for verify()
88+
void BlockTree::verify_node_pointer(const Node* n) const {
89+
tree_assert(os::is_readable_pointer(n),
90+
"Invalid node: @" PTR_FORMAT " is unreadable.", p2i(n));
91+
// If the canary is broken, this is either an invalid node pointer or
92+
// the node has been overwritten. Either way, print a hex dump, then
93+
// assert away.
94+
if (n->_canary != Node::_canary_value) {
95+
os::print_hex_dump(tty, (address)n, (address)n + sizeof(Node), 1);
96+
tree_assert(false, "Invalid node: @" PTR_FORMAT " canary broken or pointer invalid", p2i(n));
97+
}
98+
}
99+
70100
void BlockTree::verify() const {
71101
// Traverse the tree and test that all nodes are in the correct order.
72102

73103
MemRangeCounter counter;
74104
int longest_edge = 0;
75-
76105
if (_root != NULL) {
77106

78107
ResourceMark rm;
@@ -90,27 +119,29 @@ void BlockTree::verify() const {
90119
info = stack.pop();
91120
const Node* n = info.n;
92121

122+
verify_node_pointer(n);
123+
93124
// Assume a (ridiculously large) edge limit to catch cases
94125
// of badly degenerated or circular trees.
95-
assrt0(info.depth < 10000);
126+
tree_assert(info.depth < 10000, "too deep (%d)", info.depth);
96127
counter.add(n->_word_size);
97128

98-
// Verify node.
99129
if (n == _root) {
100-
assrt0(n->_parent == NULL);
130+
tree_assert_invalid_node(n->_parent == NULL, n);
101131
} else {
102-
assrt0(n->_parent != NULL);
132+
tree_assert_invalid_node(n->_parent != NULL, n);
103133
}
104134

105135
// check size and ordering
106-
assrt(n->_word_size >= MinWordSize, "bad node size " SIZE_FORMAT, n->_word_size);
107-
assrt0(n->_word_size > info.lim1);
108-
assrt0(n->_word_size < info.lim2);
136+
tree_assert_invalid_node(n->_word_size >= MinWordSize, n);
137+
tree_assert_invalid_node(n->_word_size <= chunklevel::MAX_CHUNK_WORD_SIZE, n);
138+
tree_assert_invalid_node(n->_word_size > info.lim1, n);
139+
tree_assert_invalid_node(n->_word_size < info.lim2, n);
109140

110141
// Check children
111142
if (n->_left != NULL) {
112-
assrt0(n->_left != n);
113-
assrt0(n->_left->_parent == n);
143+
tree_assert_invalid_node(n->_left != n, n);
144+
tree_assert_invalid_node(n->_left->_parent == n, n);
114145

115146
walkinfo info2;
116147
info2.n = n->_left;
@@ -121,8 +152,8 @@ void BlockTree::verify() const {
121152
}
122153

123154
if (n->_right != NULL) {
124-
assrt0(n->_right != n);
125-
assrt0(n->_right->_parent == n);
155+
tree_assert_invalid_node(n->_right != n, n);
156+
tree_assert_invalid_node(n->_right->_parent == n, n);
126157

127158
walkinfo info2;
128159
info2.n = n->_right;
@@ -135,26 +166,33 @@ void BlockTree::verify() const {
135166
// If node has same-sized siblings check those too.
136167
const Node* n2 = n->_next;
137168
while (n2 != NULL) {
138-
assrt0(n2 != n);
139-
assrt0(n2->_word_size == n->_word_size);
169+
verify_node_pointer(n2);
170+
tree_assert_invalid_node(n2 != n, n2); // catch simple circles
171+
tree_assert_invalid_node(n2->_word_size == n->_word_size, n2);
140172
counter.add(n2->_word_size);
141173
n2 = n2->_next;
142174
}
143175
}
144176
}
145177

146178
// At the end, check that counters match
179+
// (which also verifies that we visited every node, or at least
180+
// as many nodes as are in this tree)
147181
_counter.check(counter);
182+
183+
#undef assrt0n
148184
}
149185

150186
void BlockTree::zap_range(MetaWord* p, size_t word_size) {
151187
memset(p, 0xF3, word_size * sizeof(MetaWord));
152188
}
153189

154-
#undef assrt
155-
#undef assrt0
156-
157190
void BlockTree::print_tree(outputStream* st) const {
191+
192+
// Note: we do not print the tree indented, since I found that printing it
193+
// as a quasi list is much clearer to the eye.
194+
// We print the tree depth-first, with stacked nodes below normal ones
195+
// (normal "real" nodes are marked with a leading '+')
158196
if (_root != NULL) {
159197

160198
ResourceMark rm;
@@ -168,11 +206,27 @@ void BlockTree::print_tree(outputStream* st) const {
168206
while (stack.length() > 0) {
169207
info = stack.pop();
170208
const Node* n = info.n;
209+
171210
// Print node.
172-
for (int i = 0; i < info.depth; i++) {
173-
st->print("---");
211+
st->print("%4d + ", info.depth);
212+
if (os::is_readable_pointer(n)) {
213+
st->print_cr(NODE_FORMAT, NODE_FORMAT_ARGS(n));
214+
} else {
215+
st->print_cr("@" PTR_FORMAT ": unreadable (skipping subtree)", p2i(n));
216+
continue; // don't print this subtree
217+
}
218+
219+
// Print same-sized-nodes stacked under this node
220+
for (Node* n2 = n->_next; n2 != NULL; n2 = n2->_next) {
221+
st->print_raw(" ");
222+
if (os::is_readable_pointer(n2)) {
223+
st->print_cr(NODE_FORMAT, NODE_FORMAT_ARGS(n2));
224+
} else {
225+
st->print_cr("@" PTR_FORMAT ": unreadable (skipping rest of chain).", p2i(n2));
226+
break; // stop printing this chain.
227+
}
174228
}
175-
st->print_cr("<" PTR_FORMAT " (size " SIZE_FORMAT ")", p2i(n), n->_word_size);
229+
176230
// Handle children.
177231
if (n->_right != NULL) {
178232
walkinfo info2;

‎src/hotspot/share/memory/metaspace/blockTree.hpp

+31-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,18 @@ class BlockTree: public CHeapObj<mtMetaspace> {
7777

7878
struct Node {
7979

80+
static const intptr_t _canary_value =
81+
NOT_LP64(0x4e4f4445) LP64_ONLY(0x4e4f44454e4f4445ULL); // "NODE" resp "NODENODE"
82+
83+
// Note: we afford us the luxury of an always-there canary value.
84+
// The space for that is there (these nodes are only used to manage larger blocks,
85+
// see FreeBlocks::MaxSmallBlocksWordSize).
86+
// It is initialized in debug and release, but only automatically tested
87+
// in debug.
88+
const intptr_t _canary;
89+
8090
// Normal tree node stuff...
91+
// (Note: all null if this is a stacked node)
8192
Node* _parent;
8293
Node* _left;
8394
Node* _right;
@@ -91,18 +102,31 @@ class BlockTree: public CHeapObj<mtMetaspace> {
91102
const size_t _word_size;
92103

93104
Node(size_t word_size) :
105+
_canary(_canary_value),
94106
_parent(NULL),
95107
_left(NULL),
96108
_right(NULL),
97109
_next(NULL),
98110
_word_size(word_size)
99111
{}
100112

113+
#ifdef ASSERT
114+
bool valid() const {
115+
return _canary == _canary_value &&
116+
_word_size >= sizeof(Node) &&
117+
_word_size < chunklevel::MAX_CHUNK_WORD_SIZE;
118+
}
119+
#endif
101120
};
102121

103122
// Needed for verify() and print_tree()
104123
struct walkinfo;
105124

125+
#ifdef ASSERT
126+
// Run a quick check on a node; upon suspicion dive into a full tree check.
127+
void check_node(const Node* n) const { if (!n->valid()) verify(); }
128+
#endif
129+
106130
public:
107131

108132
// Minimum word size a block has to be to be added to this structure (note ceil division).
@@ -196,9 +220,10 @@ class BlockTree: public CHeapObj<mtMetaspace> {
196220
}
197221

198222
// Given a node n and an insertion point, insert n under insertion point.
199-
static void insert(Node* insertion_point, Node* n) {
223+
void insert(Node* insertion_point, Node* n) {
200224
assert(n->_parent == NULL, "Sanity");
201225
for (;;) {
226+
DEBUG_ONLY(check_node(insertion_point);)
202227
if (n->_word_size == insertion_point->_word_size) {
203228
add_to_list(n, insertion_point); // parent stays NULL in this case.
204229
break;
@@ -222,9 +247,10 @@ class BlockTree: public CHeapObj<mtMetaspace> {
222247

223248
// Given a node and a wish size, search this node and all children for
224249
// the node closest (equal or larger sized) to the size s.
225-
static Node* find_closest_fit(Node* n, size_t s) {
250+
Node* find_closest_fit(Node* n, size_t s) {
226251
Node* best_match = NULL;
227252
while (n != NULL) {
253+
DEBUG_ONLY(check_node(n);)
228254
if (n->_word_size >= s) {
229255
best_match = n;
230256
if (n->_word_size == s) {
@@ -311,6 +337,8 @@ class BlockTree: public CHeapObj<mtMetaspace> {
311337

312338
#ifdef ASSERT
313339
void zap_range(MetaWord* p, size_t word_size);
340+
// Helper for verify()
341+
void verify_node_pointer(const Node* n) const;
314342
#endif // ASSERT
315343

316344
public:
@@ -339,6 +367,7 @@ class BlockTree: public CHeapObj<mtMetaspace> {
339367
Node* n = find_closest_fit(word_size);
340368

341369
if (n != NULL) {
370+
DEBUG_ONLY(check_node(n);)
342371
assert(n->_word_size >= word_size, "sanity");
343372

344373
if (n->_next != NULL) {

‎src/hotspot/share/memory/metaspace/freeBlocks.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ class FreeBlocks : public CHeapObj<mtMetaspace> {
6969
// to fit into _smallblocks.
7070
BlockTree _tree;
7171

72+
// This verifies that blocks too large to go into the binlist can be
73+
// kept in the blocktree.
74+
STATIC_ASSERT(BinList32::MaxWordSize >= BlockTree::MinWordSize);
75+
7276
// Cutoff point: blocks larger than this size are kept in the
7377
// tree, blocks smaller than or equal to this size in the bin list.
7478
const size_t MaxSmallBlocksWordSize = BinList32::MaxWordSize;

‎test/hotspot/gtest/metaspace/test_blocktree.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,33 @@ TEST_VM(metaspace, BlockTree_print_test) {
215215
bt.print_tree(&ss);
216216

217217
LOG("%s", ss.as_string());
218+
}
219+
220+
// Test that an overwritten node would result in an assert and a printed tree
221+
TEST_VM_ASSERT_MSG(metaspace, BlockTree_overwriter_test, "Invalid node") {
222+
static const size_t sizes1[] = { 30, 17, 0 };
223+
static const size_t sizes2[] = { 12, 12, 0 };
224+
225+
BlockTree bt;
226+
FeederBuffer fb(4 * K);
227+
228+
// some nodes...
229+
create_nodes(sizes1, fb, bt);
230+
231+
// a node we will break...
232+
MetaWord* p_broken = fb.get(12);
233+
bt.add_block(p_broken, 12);
234+
235+
// some more nodes...
236+
create_nodes(sizes2, fb, bt);
218237

238+
// overwrite node memory (only the very first byte), then verify tree.
239+
// Verification should catch the broken canary, print the tree,
240+
// then assert.
241+
LOG("Will break node at " PTR_FORMAT ".", p2i(p_broken));
242+
tty->print_cr("Death test, please ignore the following \"Invalid node\" printout.");
243+
*((char*)p_broken) = '\0';
244+
bt.verify();
219245
}
220246
#endif
221247

0 commit comments

Comments
 (0)
Please sign in to comment.