mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 06:53:33 +00:00
f4f4caab0e
I wanted a tiny scriptable meltdown proof way to run userspace programs and visualize how program execution impacts memory. It helps to explain how things like Actually Portable Executable works. It can show you how the GCC generated code is going about manipulating matrices and more. I didn't feel fully comfortable with Qemu and Bochs because I'm not smart enough to understand them. I wanted something like gVisor but with much stronger levels of assurances. I wanted a single binary that'll run, on all major operating systems with an embedded GPL barrier ZIP filesystem that is tiny enough to transpile to JavaScript and run in browsers too. https://justine.storage.googleapis.com/emulator625.mp4
247 lines
7.7 KiB
C
247 lines
7.7 KiB
C
#include "third_party/dlmalloc/dlmalloc.h"
|
|
|
|
/* Check properties of any chunk, whether free, inuse, mmapped etc */
|
|
forceinline void do_check_any_chunk(mstate m, mchunkptr p) {
|
|
assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));
|
|
assert(ok_address(m, p));
|
|
}
|
|
|
|
/* Check properties of top chunk */
|
|
void do_check_top_chunk(mstate m, mchunkptr p) {
|
|
msegmentptr sp = segment_holding(m, (char*)p);
|
|
size_t sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */
|
|
assert(sp != 0);
|
|
assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));
|
|
assert(ok_address(m, p));
|
|
assert(sz == m->topsize);
|
|
assert(sz > 0);
|
|
assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE);
|
|
assert(pinuse(p));
|
|
assert(!pinuse(chunk_plus_offset(p, sz)));
|
|
}
|
|
|
|
/* Check properties of (inuse) mmapped chunks */
|
|
void do_check_mmapped_chunk(mstate m, mchunkptr p) {
|
|
size_t sz = chunksize(p);
|
|
size_t len = (sz + (p->prev_foot) + MMAP_FOOT_PAD);
|
|
assert(is_mmapped(p));
|
|
assert(use_mmap(m));
|
|
assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));
|
|
assert(ok_address(m, p));
|
|
assert(!is_small(sz));
|
|
assert((len & (mparams.page_size - SIZE_T_ONE)) == 0);
|
|
assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD);
|
|
assert(chunk_plus_offset(p, sz + SIZE_T_SIZE)->head == 0);
|
|
}
|
|
|
|
/* Check properties of inuse chunks */
|
|
void do_check_inuse_chunk(mstate m, mchunkptr p) {
|
|
do_check_any_chunk(m, p);
|
|
assert(is_inuse(p));
|
|
assert(next_pinuse(p));
|
|
/* If not pinuse and not mmapped, previous chunk has OK offset */
|
|
assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p);
|
|
if (is_mmapped(p)) do_check_mmapped_chunk(m, p);
|
|
}
|
|
|
|
/* Check properties of free chunks */
|
|
void do_check_free_chunk(mstate m, mchunkptr p) {
|
|
size_t sz = chunksize(p);
|
|
mchunkptr next = chunk_plus_offset(p, sz);
|
|
do_check_any_chunk(m, p);
|
|
assert(!is_inuse(p));
|
|
assert(!next_pinuse(p));
|
|
assert(!is_mmapped(p));
|
|
if (p != m->dv && p != m->top) {
|
|
if (sz >= MIN_CHUNK_SIZE) {
|
|
assert((sz & CHUNK_ALIGN_MASK) == 0);
|
|
assert(is_aligned(chunk2mem(p)));
|
|
assert(next->prev_foot == sz);
|
|
assert(pinuse(p));
|
|
assert(next == m->top || is_inuse(next));
|
|
assert(p->fd->bk == p);
|
|
assert(p->bk->fd == p);
|
|
} else /* markers are always of size SIZE_T_SIZE */
|
|
assert(sz == SIZE_T_SIZE);
|
|
}
|
|
}
|
|
|
|
/* Check properties of malloced chunks at the point they are malloced */
|
|
void do_check_malloced_chunk(mstate m, void* mem, size_t s) {
|
|
if (mem != 0) {
|
|
mchunkptr p = mem2chunk(mem);
|
|
size_t sz = p->head & ~INUSE_BITS;
|
|
do_check_inuse_chunk(m, p);
|
|
assert((sz & CHUNK_ALIGN_MASK) == 0);
|
|
assert(sz >= MIN_CHUNK_SIZE);
|
|
assert(sz >= s);
|
|
/* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */
|
|
assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE));
|
|
}
|
|
}
|
|
|
|
/* Check a tree and its subtrees. */
|
|
static void do_check_tree(mstate m, tchunkptr t) {
|
|
tchunkptr head = 0;
|
|
tchunkptr u = t;
|
|
bindex_t tindex = t->index;
|
|
size_t tsize = chunksize(t);
|
|
bindex_t idx;
|
|
compute_tree_index(tsize, idx);
|
|
assert(tindex == idx);
|
|
assert(tsize >= MIN_LARGE_SIZE);
|
|
assert(tsize >= minsize_for_tree_index(idx));
|
|
assert((idx == NTREEBINS - 1) || (tsize < minsize_for_tree_index((idx + 1))));
|
|
|
|
do { /* traverse through chain of same-sized nodes */
|
|
do_check_any_chunk(m, ((mchunkptr)u));
|
|
assert(u->index == tindex);
|
|
assert(chunksize(u) == tsize);
|
|
assert(!is_inuse(u));
|
|
assert(!next_pinuse(u));
|
|
assert(u->fd->bk == u);
|
|
assert(u->bk->fd == u);
|
|
if (u->parent == 0) {
|
|
assert(u->child[0] == 0);
|
|
assert(u->child[1] == 0);
|
|
} else {
|
|
assert(head == 0); /* only one node on chain has parent */
|
|
head = u;
|
|
assert(u->parent != u);
|
|
assert(u->parent->child[0] == u || u->parent->child[1] == u ||
|
|
*((tbinptr*)(u->parent)) == u);
|
|
if (u->child[0] != 0) {
|
|
assert(u->child[0]->parent == u);
|
|
assert(u->child[0] != u);
|
|
do_check_tree(m, u->child[0]);
|
|
}
|
|
if (u->child[1] != 0) {
|
|
assert(u->child[1]->parent == u);
|
|
assert(u->child[1] != u);
|
|
do_check_tree(m, u->child[1]);
|
|
}
|
|
if (u->child[0] != 0 && u->child[1] != 0) {
|
|
assert(chunksize(u->child[0]) < chunksize(u->child[1]));
|
|
}
|
|
}
|
|
u = u->fd;
|
|
} while (u != t);
|
|
assert(head != 0);
|
|
}
|
|
|
|
/* Check all the chunks in a treebin. */
|
|
static void do_check_treebin(mstate m, bindex_t i) {
|
|
tbinptr* tb = treebin_at(m, i);
|
|
tchunkptr t = *tb;
|
|
int empty = (m->treemap & (1U << i)) == 0;
|
|
if (t == 0) assert(empty);
|
|
if (!empty) do_check_tree(m, t);
|
|
}
|
|
|
|
/* Check all the chunks in a smallbin. */
|
|
static void do_check_smallbin(mstate m, bindex_t i) {
|
|
sbinptr b = smallbin_at(m, i);
|
|
mchunkptr p = b->bk;
|
|
unsigned int empty = (m->smallmap & (1U << i)) == 0;
|
|
if (p == b) assert(empty);
|
|
if (!empty) {
|
|
for (; p != b; p = p->bk) {
|
|
size_t size = chunksize(p);
|
|
mchunkptr q;
|
|
/* each chunk claims to be free */
|
|
do_check_free_chunk(m, p);
|
|
/* chunk belongs in bin */
|
|
assert(small_index(size) == i);
|
|
assert(p->bk == b || chunksize(p->bk) == chunksize(p));
|
|
/* chunk is followed by an inuse chunk */
|
|
q = next_chunk(p);
|
|
if (q->head != FENCEPOST_HEAD) do_check_inuse_chunk(m, q);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Find x in a bin. Used in other check functions. */
|
|
static int bin_find(mstate m, mchunkptr x) {
|
|
size_t size = chunksize(x);
|
|
if (is_small(size)) {
|
|
bindex_t sidx = small_index(size);
|
|
sbinptr b = smallbin_at(m, sidx);
|
|
if (smallmap_is_marked(m, sidx)) {
|
|
mchunkptr p = b;
|
|
do {
|
|
if (p == x) return 1;
|
|
} while ((p = p->fd) != b);
|
|
}
|
|
} else {
|
|
bindex_t tidx;
|
|
compute_tree_index(size, tidx);
|
|
if (treemap_is_marked(m, tidx)) {
|
|
tchunkptr t = *treebin_at(m, tidx);
|
|
size_t sizebits = size << leftshift_for_tree_index(tidx);
|
|
while (t != 0 && chunksize(t) != size) {
|
|
t = t->child[(sizebits >> (SIZE_T_BITSIZE - SIZE_T_ONE)) & 1];
|
|
sizebits <<= 1;
|
|
}
|
|
if (t != 0) {
|
|
tchunkptr u = t;
|
|
do {
|
|
if (u == (tchunkptr)x) return 1;
|
|
} while ((u = u->fd) != t);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Traverse each chunk and check it; return total */
|
|
static size_t traverse_and_check(mstate m) {
|
|
size_t sum = 0;
|
|
if (is_initialized(m)) {
|
|
msegmentptr s = &m->seg;
|
|
sum += m->topsize + TOP_FOOT_SIZE;
|
|
while (s != 0) {
|
|
mchunkptr q = align_as_chunk(s->base);
|
|
mchunkptr lastq = 0;
|
|
assert(pinuse(q));
|
|
while (segment_holds(s, q) && q != m->top && q->head != FENCEPOST_HEAD) {
|
|
sum += chunksize(q);
|
|
if (is_inuse(q)) {
|
|
assert(!bin_find(m, q));
|
|
do_check_inuse_chunk(m, q);
|
|
} else {
|
|
assert(q == m->dv || bin_find(m, q));
|
|
assert(lastq == 0 || is_inuse(lastq)); /* Not 2 consecutive free */
|
|
do_check_free_chunk(m, q);
|
|
}
|
|
lastq = q;
|
|
q = next_chunk(q);
|
|
}
|
|
s = s->next;
|
|
}
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
/* Check all properties of malloc_state. */
|
|
void do_check_malloc_state(mstate m) {
|
|
bindex_t i;
|
|
size_t total;
|
|
/* check bins */
|
|
for (i = 0; i < NSMALLBINS; ++i) do_check_smallbin(m, i);
|
|
for (i = 0; i < NTREEBINS; ++i) do_check_treebin(m, i);
|
|
if (m->dvsize != 0) { /* check dv chunk */
|
|
do_check_any_chunk(m, m->dv);
|
|
assert(m->dvsize == chunksize(m->dv));
|
|
assert(m->dvsize >= MIN_CHUNK_SIZE);
|
|
assert(bin_find(m, m->dv) == 0);
|
|
}
|
|
if (m->top != 0) { /* check top chunk */
|
|
do_check_top_chunk(m, m->top);
|
|
/*assert(m->topsize == chunksize(m->top)); redundant */
|
|
assert(m->topsize > 0);
|
|
assert(bin_find(m, m->top) == 0);
|
|
}
|
|
total = traverse_and_check(m);
|
|
assert(total <= m->footprint);
|
|
assert(m->footprint <= m->max_footprint);
|
|
}
|