zephyr/lib/heap/heap_validate.c

187 lines
4.9 KiB
C

/*
* 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;
}