mirror of
https://github.com/doldecomp/melee.git
synced 2026-11-10 17:10:37 -10:00
313 lines
9.5 KiB
C
313 lines
9.5 KiB
C
/** @file Traces and readback; excluded from the production archive library. */
|
|
#include <dat/reader-internal.h>
|
|
uint64_t dat_reader_load_uint(const void* src, uint32_t size)
|
|
{
|
|
switch (size) {
|
|
case 1: {
|
|
uint8_t x;
|
|
memcpy(&x, src, 1);
|
|
return x;
|
|
}
|
|
case 2: {
|
|
uint16_t x;
|
|
memcpy(&x, src, 2);
|
|
return x;
|
|
}
|
|
case 4: {
|
|
uint32_t x;
|
|
memcpy(&x, src, 4);
|
|
return x;
|
|
}
|
|
case 8: {
|
|
uint64_t x;
|
|
memcpy(&x, src, 8);
|
|
return x;
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static const char* const issue_names[] = {
|
|
"unrelocated-pointer", "relocated-scalar", "out-of-bounds",
|
|
"ambiguous-union", "unknown-command",
|
|
};
|
|
void dat_test_init(DatArchive* a)
|
|
{
|
|
a->validation = calloc(1, sizeof(*a->validation));
|
|
if (!a->validation) {
|
|
abort();
|
|
}
|
|
bits_init(&a->validation->pointer, a->archive->size);
|
|
}
|
|
void dat_test_close(DatArchive* a)
|
|
{
|
|
DatValidation* d = a->validation;
|
|
free(d->pointer.words);
|
|
map_free(&d->extents);
|
|
map_free(&d->choices);
|
|
map_free(&d->array_counts);
|
|
map_free(&d->reached_seen);
|
|
free(d->reached.items);
|
|
free(d->chosen.items);
|
|
free(d->issues.items);
|
|
free(d);
|
|
}
|
|
void dat_test_reached(DatArchive* a, uint32_t offset, int32_t r)
|
|
{
|
|
uint32_t id = dat_reader_T(a, r)->id;
|
|
uint64_t* seen =
|
|
map_slot(&a->validation->reached_seen, key2(offset, id), true);
|
|
if (*seen == 0) {
|
|
*seen = 1;
|
|
DatReached x = { offset, id };
|
|
VEC_PUSH(a->validation->reached, x);
|
|
}
|
|
}
|
|
void dat_test_count(DatArchive* a, uint32_t offset, int32_t type,
|
|
uint64_t count)
|
|
{
|
|
*map_slot(&a->validation->array_counts,
|
|
key2(offset, dat_reader_T(a, type)->id), true) = count;
|
|
}
|
|
void dat_test_sentinel(DatArchive* a)
|
|
{
|
|
a->validation->sentinels++;
|
|
}
|
|
|
|
void dat_reader_mark_pointer(DatArchive* a, uint32_t offset, uint32_t size)
|
|
{
|
|
bits_set(&a->validation->pointer, offset, size);
|
|
}
|
|
|
|
void dat_reader_issue(DatArchive* a, DatIssueKind kind, uint32_t at,
|
|
uint32_t value)
|
|
{
|
|
DatIssue i = { (uint8_t) kind, at, value };
|
|
VEC_PUSH(a->validation->issues, i);
|
|
}
|
|
|
|
void dat_reader_record_extent(DatArchive* a, uint32_t offset, uint64_t end)
|
|
{
|
|
uint64_t* e = map_slot(&a->validation->extents, offset, true);
|
|
if (end > *e) {
|
|
*e = end;
|
|
}
|
|
}
|
|
|
|
void dat_reader_typed_extent(DatArchive* a, uint32_t offset, int32_t type,
|
|
uint64_t count)
|
|
{
|
|
if (dat_reader_T(a, type)->raw) {
|
|
return;
|
|
}
|
|
int32_t r = dat_reader_resolve(a, type);
|
|
if (r == DAT_NONE) {
|
|
return;
|
|
}
|
|
if (count == 1 && dat_reader_T(a, r)->conditioned) {
|
|
return;
|
|
}
|
|
uint64_t end = offset + (uint64_t) dat_reader_T(a, r)->size * count;
|
|
dat_reader_record_extent(a, offset, end);
|
|
}
|
|
|
|
void dat_reader_untyped(DatArchive* a)
|
|
{
|
|
a->validation->untyped_pointers++;
|
|
}
|
|
|
|
void dat_reader_relocated_words(DatArchive* a, uint32_t start, uint64_t end)
|
|
{
|
|
for (uint32_t i = 0; i < a->archive->nrelocs; i++) {
|
|
uint32_t at = a->archive->relocs[i];
|
|
if (at >= start && at < end) {
|
|
dat_reader_issue(a, ISSUE_RELOCATED_SCALAR, at, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int dat_reader_compare_pair(const void* x, const void* y)
|
|
{
|
|
const uint32_t* a = x;
|
|
const uint32_t* b = y;
|
|
for (int i = 0; i < 2; i++) {
|
|
if (a[i] != b[i]) {
|
|
return a[i] < b[i] ? -1 : 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int dat_reader_compare_triple(const void* x, const void* y)
|
|
{
|
|
const uint32_t* a = x;
|
|
const uint32_t* b = y;
|
|
for (int i = 0; i < 3; i++) {
|
|
if (a[i] != b[i]) {
|
|
return a[i] < b[i] ? -1 : 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const char* dat_reader_type_name(const DatArchive* a, uint32_t id)
|
|
{
|
|
int32_t t = dat_type_by_id(a->s, id);
|
|
return t == DAT_NONE ? "?" : a->s->types[t]->name;
|
|
}
|
|
|
|
void dat_trace(const DatArchive* a, FILE* out, unsigned what)
|
|
{
|
|
if (what & DAT_TRACE_OBJECTS) {
|
|
size_t n = a->validation->reached.len;
|
|
uint32_t* rows = malloc((n + 1) * 2 * sizeof(uint32_t));
|
|
for (size_t i = 0; i < n; i++) {
|
|
rows[2 * i] = a->validation->reached.items[i].offset;
|
|
rows[2 * i + 1] = a->validation->reached.items[i].id;
|
|
}
|
|
qsort(rows, n, 2 * sizeof(uint32_t), dat_reader_compare_pair);
|
|
for (size_t i = 0; i < n; i++) {
|
|
fprintf(out, "object 0x%X %u %s\n", rows[2 * i], rows[2 * i + 1],
|
|
dat_reader_type_name(a, rows[2 * i + 1]));
|
|
}
|
|
free(rows);
|
|
}
|
|
if (what & DAT_TRACE_POINTERS) {
|
|
for (uint32_t at = 0; at < a->archive->size; at++) {
|
|
if (bits_has(&a->validation->pointer, at, a->archive->size)) {
|
|
fprintf(out, "pointer 0x%X\n", at);
|
|
}
|
|
}
|
|
}
|
|
if (what & DAT_TRACE_EXTENTS) {
|
|
uint32_t* rows =
|
|
malloc((a->validation->extents.len + 1) * 2 * sizeof(uint32_t));
|
|
size_t n = 0;
|
|
for (size_t i = 0; i < a->validation->extents.cap; i++) {
|
|
if (a->validation->extents.keys[i] != UINT64_MAX) {
|
|
rows[2 * n] = (uint32_t) a->validation->extents.keys[i];
|
|
rows[2 * n + 1] = (uint32_t) a->validation->extents.values[i];
|
|
n++;
|
|
}
|
|
}
|
|
qsort(rows, n, 2 * sizeof(uint32_t), dat_reader_compare_pair);
|
|
for (size_t i = 0; i < n; i++) {
|
|
fprintf(out, "extent 0x%X 0x%X\n", rows[2 * i], rows[2 * i + 1]);
|
|
}
|
|
free(rows);
|
|
}
|
|
if (what & DAT_TRACE_CHOICES) {
|
|
size_t n = a->validation->chosen.len;
|
|
uint32_t* rows = malloc((n + 1) * 3 * sizeof(uint32_t));
|
|
for (size_t i = 0; i < n; i++) {
|
|
const DatChoice* c = &a->validation->chosen.items[i];
|
|
uint64_t index = 0;
|
|
map_get(&a->validation->choices, key2(c->offset, c->id), &index);
|
|
rows[3 * i] = c->offset;
|
|
rows[3 * i + 1] = c->id;
|
|
rows[3 * i + 2] = (uint32_t) index - 1;
|
|
}
|
|
qsort(rows, n, 3 * sizeof(uint32_t), dat_reader_compare_triple);
|
|
for (size_t i = 0; i < n; i++) {
|
|
fprintf(out, "choice 0x%X %u %u\n", rows[3 * i], rows[3 * i + 1],
|
|
rows[3 * i + 2]);
|
|
}
|
|
free(rows);
|
|
}
|
|
if (what & DAT_TRACE_ISSUES) {
|
|
size_t n = a->validation->issues.len;
|
|
uint32_t* rows = malloc((n + 1) * 3 * sizeof(uint32_t));
|
|
for (size_t i = 0; i < n; i++) {
|
|
rows[3 * i] = a->validation->issues.items[i].kind;
|
|
rows[3 * i + 1] = a->validation->issues.items[i].at;
|
|
rows[3 * i + 2] = a->validation->issues.items[i].value;
|
|
}
|
|
qsort(rows, n, 3 * sizeof(uint32_t), dat_reader_compare_triple);
|
|
for (size_t i = 0; i < n; i++) {
|
|
if (i > 0 && dat_reader_compare_triple(&rows[3 * i],
|
|
&rows[3 * (i - 1)]) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
fprintf(out, "issue %s 0x%X 0x%X\n", issue_names[rows[3 * i]],
|
|
rows[3 * i + 1], rows[3 * i + 2]);
|
|
}
|
|
free(rows);
|
|
}
|
|
if (what & DAT_TRACE_COUNTS) {
|
|
fprintf(out, "untyped-pointers %zu\nsentinels %zu\n",
|
|
a->validation->untyped_pointers, a->validation->sentinels);
|
|
}
|
|
}
|
|
|
|
void dat_reader_mismatch(DatVerify* v, uint32_t at, const char* what,
|
|
uint64_t want, uint64_t got)
|
|
{
|
|
v->mismatches++;
|
|
if (v->out != NULL) {
|
|
fprintf(v->out, "mismatch 0x%X %s: want 0x%llX, got 0x%llX\n", at,
|
|
what, (unsigned long long) want, (unsigned long long) got);
|
|
}
|
|
}
|
|
|
|
void dat_reader_verify(DatVerify* v, uint32_t offset, int32_t type,
|
|
const void* native, int depth)
|
|
{
|
|
const DatArchive* a = v->a;
|
|
int32_t r = dat_reader_resolve(a, type);
|
|
if (r == DAT_NONE || native == NULL || depth > 64) {
|
|
return;
|
|
}
|
|
const DatType* t = dat_reader_T(a, r);
|
|
if (!t->conditioned && (uint64_t) offset + t->size > a->archive->size) {
|
|
return;
|
|
}
|
|
uint64_t key = key2(offset, t->id), previous;
|
|
if (map_get(&v->checked, key, &previous) &&
|
|
previous == (uint64_t) (uintptr_t) native)
|
|
{
|
|
return;
|
|
}
|
|
*map_slot(&v->checked, key, true) = (uint64_t) (uintptr_t) native;
|
|
t->verify(v, offset, native, depth);
|
|
}
|
|
|
|
size_t dat_verify(const DatArchive* a, FILE* out)
|
|
{
|
|
DatVerify v = { .a = a, .out = out };
|
|
for (size_t i = 0; i < a->natives.cap; i++) {
|
|
if (a->natives.keys[i] != UINT64_MAX) {
|
|
*map_slot(&v.offsets, a->natives.values[i], true) =
|
|
a->natives.keys[i];
|
|
}
|
|
}
|
|
for (size_t i = 0; i < a->natives.cap; i++) {
|
|
if (a->natives.keys[i] == UINT64_MAX) {
|
|
continue;
|
|
}
|
|
uint32_t offset = (uint32_t) (a->natives.keys[i] >> 32);
|
|
uint32_t id = (uint32_t) a->natives.keys[i];
|
|
const void* native = (const void*) (uintptr_t) a->natives.values[i];
|
|
int32_t type = dat_type_by_id(a->s, id);
|
|
if (type != DAT_NONE && native != a->archive->data + offset) {
|
|
dat_reader_verify(&v, offset, type, native, 0);
|
|
}
|
|
}
|
|
map_free(&v.checked);
|
|
map_free(&v.offsets);
|
|
return v.mismatches;
|
|
}
|
|
|
|
void dat_reader_record_choice(DatArchive* a, uint32_t offset, int32_t type,
|
|
uint32_t index)
|
|
{
|
|
uint32_t id = dat_reader_T(a, type)->id;
|
|
DatChoice choice = { offset, id, index };
|
|
uint64_t* seen = map_slot(&a->validation->choices, key2(offset, id), true);
|
|
if (*seen == 0) {
|
|
VEC_PUSH(a->validation->chosen, choice);
|
|
}
|
|
*seen = index + 1;
|
|
}
|