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.
186 lines
4.9 KiB
186 lines
4.9 KiB
/* |
|
* Copyright (c) 2019 Intel Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
#include <zephyr/sys/sys_heap.h> |
|
#include <zephyr/sys/util.h> |
|
#include <zephyr/kernel.h> |
|
#include "heap.h" |
|
|
|
/* White-box sys_heap validation code. Uses internal data structures. |
|
* Not expected to be useful in production apps. This checks every |
|
* header field of every chunk and returns true if the totality of the |
|
* data structure is a valid heap. It doesn't necessarily tell you |
|
* that it is the CORRECT heap given the history of alloc/free calls |
|
* that it can't inspect. In a pathological case, you can imagine |
|
* something scribbling a copy of a previously-valid heap on top of a |
|
* running one and corrupting it. YMMV. |
|
*/ |
|
|
|
#define VALIDATE(cond) do { if (!(cond)) { return false; } } while (0) |
|
|
|
static bool in_bounds(struct z_heap *h, chunkid_t c) |
|
{ |
|
VALIDATE(c >= right_chunk(h, 0)); |
|
VALIDATE(c < h->end_chunk); |
|
VALIDATE(chunk_size(h, c) < h->end_chunk); |
|
return true; |
|
} |
|
|
|
static bool valid_chunk(struct z_heap *h, chunkid_t c) |
|
{ |
|
VALIDATE(chunk_size(h, c) > 0); |
|
VALIDATE((c + chunk_size(h, c)) <= h->end_chunk); |
|
VALIDATE(in_bounds(h, c)); |
|
VALIDATE(right_chunk(h, left_chunk(h, c)) == c); |
|
VALIDATE(left_chunk(h, right_chunk(h, c)) == c); |
|
if (chunk_used(h, c)) { |
|
VALIDATE(!solo_free_header(h, c)); |
|
} else { |
|
VALIDATE(chunk_used(h, left_chunk(h, c))); |
|
VALIDATE(chunk_used(h, right_chunk(h, c))); |
|
if (!solo_free_header(h, c)) { |
|
VALIDATE(in_bounds(h, prev_free_chunk(h, c))); |
|
VALIDATE(in_bounds(h, next_free_chunk(h, c))); |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
/* Validate multiple state dimensions for the bucket "next" pointer |
|
* and see that they match. Probably should unify the design a |
|
* bit... |
|
*/ |
|
static inline void check_nexts(struct z_heap *h, int bidx) |
|
{ |
|
struct z_heap_bucket *b = &h->buckets[bidx]; |
|
|
|
bool emptybit = (h->avail_buckets & BIT(bidx)) == 0; |
|
bool emptylist = b->next == 0; |
|
bool empties_match = emptybit == emptylist; |
|
|
|
(void)empties_match; |
|
CHECK(empties_match); |
|
|
|
if (b->next != 0) { |
|
CHECK(valid_chunk(h, b->next)); |
|
} |
|
} |
|
|
|
bool sys_heap_validate(struct sys_heap *heap) |
|
{ |
|
struct z_heap *h = heap->heap; |
|
chunkid_t c; |
|
|
|
/* |
|
* Walk through the chunks linearly, verifying sizes and end pointer. |
|
*/ |
|
for (c = right_chunk(h, 0); c < h->end_chunk; c = right_chunk(h, c)) { |
|
if (!valid_chunk(h, c)) { |
|
return false; |
|
} |
|
} |
|
if (c != h->end_chunk) { |
|
return false; /* Should have exactly consumed the buffer */ |
|
} |
|
|
|
#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS |
|
/* |
|
* Validate sys_heap_runtime_stats_get API. |
|
* Iterate all chunks in sys_heap to get total allocated bytes and |
|
* free bytes, then compare with the results of |
|
* sys_heap_runtime_stats_get function. |
|
*/ |
|
size_t allocated_bytes, free_bytes; |
|
struct sys_memory_stats stat; |
|
|
|
get_alloc_info(h, &allocated_bytes, &free_bytes); |
|
sys_heap_runtime_stats_get(heap, &stat); |
|
if ((stat.allocated_bytes != allocated_bytes) || |
|
(stat.free_bytes != free_bytes)) { |
|
return false; |
|
} |
|
#endif |
|
|
|
/* Check the free lists: entry count should match, empty bit |
|
* should be correct, and all chunk entries should point into |
|
* valid unused chunks. Mark those chunks USED, temporarily. |
|
*/ |
|
for (int b = 0; b <= bucket_idx(h, h->end_chunk); b++) { |
|
chunkid_t c0 = h->buckets[b].next; |
|
uint32_t n = 0; |
|
|
|
check_nexts(h, b); |
|
|
|
for (c = c0; c != 0 && (n == 0 || c != c0); |
|
n++, c = next_free_chunk(h, c)) { |
|
if (!valid_chunk(h, c)) { |
|
return false; |
|
} |
|
set_chunk_used(h, c, true); |
|
} |
|
|
|
bool empty = (h->avail_buckets & BIT(b)) == 0; |
|
bool zero = n == 0; |
|
|
|
if (empty != zero) { |
|
return false; |
|
} |
|
|
|
if (empty && (h->buckets[b].next != 0)) { |
|
return false; |
|
} |
|
} |
|
|
|
/* |
|
* Walk through the chunks linearly again, verifying that all chunks |
|
* but solo headers are now USED (i.e. all free blocks were found |
|
* during enumeration). Mark all such blocks UNUSED and solo headers |
|
* USED. |
|
*/ |
|
chunkid_t prev_chunk = 0; |
|
|
|
for (c = right_chunk(h, 0); c < h->end_chunk; c = right_chunk(h, c)) { |
|
if (!chunk_used(h, c) && !solo_free_header(h, c)) { |
|
return false; |
|
} |
|
if (left_chunk(h, c) != prev_chunk) { |
|
return false; |
|
} |
|
prev_chunk = c; |
|
|
|
set_chunk_used(h, c, solo_free_header(h, c)); |
|
} |
|
if (c != h->end_chunk) { |
|
return false; /* Should have exactly consumed the buffer */ |
|
} |
|
|
|
/* Go through the free lists again checking that the linear |
|
* pass caught all the blocks and that they now show UNUSED. |
|
* Mark them USED. |
|
*/ |
|
for (int b = 0; b <= bucket_idx(h, h->end_chunk); b++) { |
|
chunkid_t c0 = h->buckets[b].next; |
|
int n = 0; |
|
|
|
if (c0 == 0) { |
|
continue; |
|
} |
|
|
|
for (c = c0; n == 0 || c != c0; n++, c = next_free_chunk(h, c)) { |
|
if (chunk_used(h, c)) { |
|
return false; |
|
} |
|
set_chunk_used(h, c, true); |
|
} |
|
} |
|
|
|
/* Now we are valid, but have managed to invert all the in-use |
|
* fields. One more linear pass to fix them up |
|
*/ |
|
for (c = right_chunk(h, 0); c < h->end_chunk; c = right_chunk(h, c)) { |
|
set_chunk_used(h, c, !chunk_used(h, c)); |
|
} |
|
return true; |
|
}
|
|
|