dfhack/plugins/devel/check-structures-sanity/validate.cpp

238 lines
6.2 KiB
C++

#include "check-structures-sanity.h"
bool Checker::is_valid_dereference(const QueueItem & item, const CheckedStructure & cs, bool quiet)
{
auto base = const_cast<void *>(item.ptr);
auto size = cs.full_size();
if (!base)
{
// cannot dereference null pointer, but not an error
return false;
}
// assumes MALLOC_PERTURB_=45
#ifdef DFHACK64
#define UNINIT_PTR 0xd2d2d2d2d2d2d2d2
#define FAIL_PTR(message) FAIL(stl_sprintf("0x%016zx: ", reinterpret_cast<uintptr_t>(base)) << message)
#else
#define UNINIT_PTR 0xd2d2d2d2
#define FAIL_PTR(message) FAIL(stl_sprintf("0x%08zx: ", reinterpret_cast<uintptr_t>(base)) << message)
#endif
if (uintptr_t(base) == UNINIT_PTR)
{
if (!quiet)
{
FAIL_PTR("uninitialized pointer");
}
return false;
}
bool found = true;
auto expected_start = base;
size_t remaining_size = size;
while (found)
{
found = false;
for (auto & range : mapped)
{
if (!range.isInRange(expected_start))
{
continue;
}
found = true;
if (!range.valid || !range.read)
{
if (!quiet)
{
FAIL_PTR("pointer to invalid memory range");
}
return false;
}
auto expected_end = const_cast<void *>(PTR_ADD(expected_start, remaining_size - 1));
if (size && !range.isInRange(expected_end))
{
auto next_start = PTR_ADD(range.end, 1);
remaining_size -= ptrdiff_t(next_start) - ptrdiff_t(expected_start);
expected_start = const_cast<void *>(next_start);
break;
}
return true;
}
}
if (quiet)
{
return false;
}
if (expected_start == base)
{
FAIL_PTR("pointer not in any mapped range");
}
else
{
FAIL_PTR("pointer exceeds mapped memory bounds (size " << size << ")");
}
return false;
#undef FAIL_PTR
}
const char *Checker::get_vtable_name(const QueueItem & item, const CheckedStructure & cs, bool quiet)
{
auto vtable = validate_and_dereference<const void *const*>(QueueItem(item, "?vtable?", item.ptr), quiet);
if (!vtable)
return nullptr;
auto info = validate_and_dereference<const char *const*>(QueueItem(item, "?vtable?.info", vtable - 1), quiet);
if (!info)
return nullptr;
#ifdef WIN32
#ifdef DFHACK64
void *base;
if (!RtlPcToFileHeader(info, &base))
return nullptr;
const char *typeinfo = reinterpret_cast<const char *>(base) + reinterpret_cast<int32_t *>(info)[3];
const char *name = typeinfo + 16;
#else
const char *name = reinterpret_cast<const char *>(info) + 8;
#endif
#else
auto name = validate_and_dereference<const char *>(QueueItem(item, "?vtable?.info.name", info + 1), quiet);
#endif
for (auto & range : mapped)
{
if (!range.isInRange(const_cast<char *>(name)))
{
continue;
}
if (!range.valid || !range.read)
{
if (!quiet)
{
FAIL("pointer to invalid memory range");
}
return nullptr;
}
const char *first_letter = nullptr;
bool letter = false;
for (const char *p = name; ; p++)
{
if (!range.isInRange(const_cast<char *>(p)))
{
return nullptr;
}
if ((*p >= 'a' && *p <= 'z') || *p == '_')
{
if (!letter)
{
first_letter = p;
}
letter = true;
}
else if (!*p)
{
return first_letter;
}
}
}
return nullptr;
}
std::pair<const void *, size_t> Checker::validate_vector_size(const QueueItem & item, const CheckedStructure & cs, bool quiet)
{
struct vector_data
{
uintptr_t start;
uintptr_t finish;
uintptr_t end_of_storage;
};
vector_data vector = *reinterpret_cast<const vector_data *>(item.ptr);
ptrdiff_t length = vector.finish - vector.start;
ptrdiff_t capacity = vector.end_of_storage - vector.start;
bool local_ok = true;
auto item_size = cs.full_size();
if (!item_size)
{
item_size = 1;
local_ok = false;
}
if (vector.start > vector.finish)
{
local_ok = false;
if (!quiet)
{
FAIL("vector length is negative (" << (length / ptrdiff_t(item_size)) << ")");
}
}
if (vector.start > vector.end_of_storage)
{
local_ok = false;
if (!quiet)
{
FAIL("vector capacity is negative (" << (capacity / ptrdiff_t(item_size)) << ")");
}
}
else if (vector.finish > vector.end_of_storage)
{
local_ok = false;
if (!quiet)
{
FAIL("vector capacity (" << (capacity / ptrdiff_t(item_size)) << ") is less than its length (" << (length / ptrdiff_t(item_size)) << ")");
}
}
size_t ulength = size_t(length);
size_t ucapacity = size_t(capacity);
if (ulength % item_size != 0)
{
local_ok = false;
if (!quiet)
{
FAIL("vector length is non-integer (" << (ulength / item_size) << " items plus " << (ulength % item_size) << " bytes)");
}
}
if (ucapacity % item_size != 0)
{
local_ok = false;
if (!quiet)
{
FAIL("vector capacity is non-integer (" << (ucapacity / item_size) << " items plus " << (ucapacity % item_size) << " bytes)");
}
}
if (local_ok && capacity && !vector.start)
{
if (!quiet)
{
FAIL("vector has null pointer but capacity " << (capacity / item_size));
}
return std::make_pair(nullptr, 0);
}
auto start_ptr = reinterpret_cast<const void *>(vector.start);
if (capacity && !is_valid_dereference(QueueItem(item, "?items?", start_ptr), CheckedStructure(cs.identity, capacity / item_size), quiet))
{
local_ok = false;
}
return local_ok ? std::make_pair(start_ptr, ulength / item_size) : std::make_pair(nullptr, 0);
}