| /* |
| * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| */ |
| |
| #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; |
| } |