blob: 4fda2d350de890c4e89e8217193ccde0a4566a07 [file] [log] [blame]
/*
* Copyright 2017, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
#include <assert.h>
#include <autoconf.h>
#include <sel4vka/gen_config.h>
#include <sel4/sel4.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <vka/cspacepath_t.h>
#include <vka/vka.h>
#ifndef CONFIG_LIB_SEL4_VKA_DEBUG_LIVE_SLOTS_SZ
#define CONFIG_LIB_SEL4_VKA_DEBUG_LIVE_SLOTS_SZ 0
#endif
#ifndef CONFIG_LIB_SEL4_VKA_DEBUG_LIVE_OBJS_SZ
#define CONFIG_LIB_SEL4_VKA_DEBUG_LIVE_SLOTS_SZ 0
#endif
/* Kconfig-set sizes for buffers to track live slots and objects. */
static size_t live_slots_sz = CONFIG_LIB_SEL4_VKA_DEBUG_LIVE_SLOTS_SZ;
static size_t live_objs_sz = CONFIG_LIB_SEL4_VKA_DEBUG_LIVE_OBJS_SZ;
typedef struct {
/* The underlying allocator that we call to effect allocations. This is
* effectively our debugging target if we're troubleshooting problems with
* the allocator itself.
*/
vka_t *underlying;
/* Metadata related to currently live CSlots. The live_slots list contains
* non-null entries representing currently live CSlots. Note that these may
* be interspersed with null (dead) entries.
*/
seL4_CPtr *live_slots;
size_t live_slots_sz;
/* Metadata related to currently live objects. The live_objs list contains
* live entries with non-0 cookie members, potentially interspersed with
* dead entries with 0 cookies. The other members are used for confirming
* that a caller is freeing an object in the same way they allocated it.
*/
struct obj {
seL4_Word type;
seL4_Word size_bits;
seL4_Word cookie;
} *live_objs;
size_t live_objs_sz;
} state_t;
/* This is called when we encounter an allocator/caller error. */
#define fatal(args...) do { \
fprintf(stderr, "VKA debug: " args); \
fprintf(stderr, "\n"); \
abort(); \
} while (0)
/* This is called when we encounter a non-fatal problem. */
#define warn(args...) do { \
fprintf(stderr, "VKA debug: " args); \
fprintf(stderr, "\n"); \
} while (0)
/* Track a slot that has just become live. */
static void track_slot(state_t *state, seL4_CPtr slot)
{
assert(state != NULL);
if (state->live_slots_sz == 0) {
/* Disable tracking if we have no buffer. */
return;
}
if (slot == 0) {
fatal("allocator attempted to hand out the null slot");
}
/* Search the current list of live slots for this slot in case it's
* currently live. Pretty slow to do this on every allocate, but we're
* debugging so hey.
*/
unsigned int available = 0;
for (unsigned int i = 0; i < state->live_slots_sz; i++) {
if (state->live_slots[i] == slot) {
fatal("allocator attempted to hand out slot %lu that is currently "
"in use", (long)slot);
} else if (available == i && state->live_slots[i] != 0) {
available++;
}
}
if (available == state->live_slots_sz) {
/* The entire live slot list is full. */
warn("ran out of space for tracking slots; disabling tracking");
state->live_slots_sz = 0;
} else {
state->live_slots[available] = slot;
}
}
static int cspace_alloc(void *data, seL4_CPtr *res)
{
assert(data != NULL);
state_t *s = (state_t *)data;
vka_t *v = s->underlying;
int result = v->cspace_alloc(v->data, res);
if (result == 0 && res != NULL) {
track_slot(s, *res);
}
return result;
}
/* Stop tracking a slot that is now dead. */
static void untrack_slot(state_t *state, seL4_CPtr slot)
{
assert(state != NULL);
for (unsigned int i = 0; i < state->live_slots_sz; i++) {
if (state->live_slots[i] == slot) {
/* Found it. */
state->live_slots[i] = 0;
return;
}
}
fatal("attempt to free slot %lu that was not live (double free?)", (long)slot);
}
static void cspace_free(void *data, seL4_CPtr slot)
{
assert(data != NULL);
state_t *s = (state_t *)data;
if (slot != 0) {
/* Let's assume malloc semantics, i.e. that it's always OK to free
* NULL. Therefore the caller may have called this with 0 even if they
* were never handed out 0. We should ignore trying to untrack this.
*/
untrack_slot(s, slot);
}
vka_t *v = s->underlying;
v->cspace_free(v->data, slot);
}
/* No instrumentation required for this one. Just invoke the underlying
* allocator.
*/
static void cspace_make_path(void *data, seL4_CPtr slot, cspacepath_t *res)
{
assert(data != NULL);
state_t *s = (state_t *)data;
vka_t *v = s->underlying;
v->cspace_make_path(v->data, slot, res);
}
/* Track an object that has just become live. Logic is essentially the same as
* track_slot. Refer to the comments in it for explanations.
*/
static void track_obj(state_t *state, seL4_Word type, seL4_Word size_bits,
seL4_Word cookie)
{
assert(state != NULL);
if (state->live_objs_sz == 0) {
return;
}
if (cookie == 0) {
fatal("allocator attempted to hand out an object with no cookie");
}
unsigned int available = 0;
for (unsigned int i = 0; i < state->live_objs_sz; i++) {
if (state->live_objs[i].cookie == cookie) {
fatal("allocator attempted to hand out an object with a cookie %zu "
"that is currently in use", cookie);
} else if (available == i && state->live_objs[i].cookie != 0) {
available++;
}
}
if (available == state->live_objs_sz) {
warn("ran out of space for tracking objects; disabling tracking");
state->live_objs_sz = 0;
} else {
state->live_objs[available].type = type;
state->live_objs[available].size_bits = size_bits;
state->live_objs[available].cookie = cookie;
}
}
static int utspace_alloc(void *data, const cspacepath_t *dest, seL4_Word type,
seL4_Word size_bits, seL4_Word *res)
{
assert(data != NULL);
state_t *s = (state_t *)data;
/* At this point I guess we could check that dest is a path that was
* previously returned from cspace_make_path, but there seems to be nothing
* in the interface that spells out that dest needs to have originated from
* this allocator and not another co-existing one.
*/
vka_t *v = s->underlying;
int result = v->utspace_alloc(v->data, dest, type, size_bits, res);
if (result == 0 && res != NULL) {
track_obj(s, type, size_bits, *res);
}
return result;
}
static int utspace_alloc_maybe_device(void *data, const cspacepath_t *dest, seL4_Word type,
seL4_Word size_bits, bool can_use_dev, seL4_Word *res)
{
assert(data != NULL);
state_t *s = (state_t *)data;
vka_t *v = s->underlying;
int result = v->utspace_alloc_maybe_device(v->data, dest, type, size_bits, can_use_dev, res);
if (result == 0 && res != NULL) {
track_obj(s, type, size_bits, *res);
}
return result;
}
static int utspace_alloc_at(void *data, const cspacepath_t *dest, seL4_Word type,
seL4_Word size_bits, uintptr_t paddr, seL4_Word *cookie)
{
assert(data != NULL);
state_t *s = (state_t *)data;
/* At this point I guess we could check that dest is a path that was
* previously returned from cspace_make_path, but there seems to be nothing
* in the interface that spells out that dest needs to have originated from
* this allocator and not another co-existing one.
*/
vka_t *v = s->underlying;
int result = v->utspace_alloc_at(v->data, dest, type, size_bits, paddr, cookie);
if (result == 0 && cookie != NULL) {
track_obj(s, type, size_bits, *cookie);
}
return result;
}
/* Stop tracking an object that is now dead. */
static void untrack_obj(state_t *state, seL4_Word type, seL4_Word size_bits,
seL4_Word cookie)
{
assert(state != NULL);
for (unsigned int i = 0; i < state->live_objs_sz; i++) {
if (state->live_objs[i].cookie == cookie) {
if (state->live_objs[i].type != type) {
fatal("attempt to free object with type %d that was allocated "
"with type %d", (int)type, (int)state->live_objs[i].type);
}
if (state->live_objs[i].size_bits != size_bits) {
fatal("attempt to free object with size %d that was allocated "
"with size %d", (int)size_bits, (int)state->live_objs[i].size_bits);
}
state->live_objs[i].cookie = 0;
return;
}
}
fatal("attempt to free object %lu that was not live (double free?)",
(long)cookie);
}
static void utspace_free(void *data, seL4_Word type, seL4_Word size_bits,
seL4_Word target)
{
assert(data != NULL);
state_t *s = (state_t *)data;
vka_t *v = s->underlying;
if (target != 0) {
/* Again, assume malloc semantics that freeing NULL is fine. */
untrack_obj(s, type, size_bits, target);
}
v->utspace_free(v->data, type, size_bits, target);
}
int vka_init_debugvka(vka_t *vka, vka_t *tracee)
{
assert(vka != NULL);
state_t *s = (state_t *)malloc(sizeof(state_t));
if (s == NULL) {
goto fail;
}
s->underlying = tracee;
s->live_slots = NULL;
s->live_objs = NULL;
s->live_slots_sz = live_slots_sz;
if (live_slots_sz > 0) {
s->live_slots = (seL4_CPtr *)malloc(sizeof(seL4_CPtr) * live_slots_sz);
if (s->live_slots == NULL) {
goto fail;
}
}
s->live_objs_sz = live_objs_sz;
if (live_objs_sz > 0) {
s->live_objs = (struct obj *)malloc(sizeof(struct obj) * live_objs_sz);
if (s->live_objs == NULL) {
goto fail;
}
}
vka->data = (void *)s;
vka->cspace_alloc = cspace_alloc;
vka->cspace_make_path = cspace_make_path;
vka->utspace_alloc = utspace_alloc;
vka->utspace_alloc_maybe_device = utspace_alloc_maybe_device;
vka->utspace_alloc_at = utspace_alloc_at;
vka->cspace_free = cspace_free;
vka->utspace_free = utspace_free;
return 0;
fail:
if (s != NULL) {
if (s->live_slots != NULL) {
free(s->live_slots);
}
if (s->live_objs != NULL) {
free(s->live_objs);
}
free(s);
}
return -1;
}