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

440 lines
11 KiB
C++

#include "check-structures-sanity.h"
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#include <windows.h>
#endif
bool Checker::is_in_global(const QueueItem & item)
{
auto fields = df::global::_identity.getFields();
for (auto field = fields; field->mode != struct_field_info::END; field++)
{
size_t size = CheckedStructure(field).full_size();
auto start = *reinterpret_cast<const void * const*>(field->offset);
auto offset = uintptr_t(item.ptr) - uintptr_t(start);
if (offset < size)
{
return true;
}
}
return false;
}
bool Checker::is_valid_dereference(const QueueItem & item, const CheckedStructure & cs, size_t size, bool quiet)
{
auto base = const_cast<void *>(item.ptr);
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
}
int64_t Checker::get_int_value(const QueueItem & item, type_identity *type, bool quiet)
{
if (type == df::identity_traits<int32_t>::get())
{
return validate_and_dereference<int32_t>(item, quiet);
}
else if (type == df::identity_traits<uint32_t>::get())
{
return validate_and_dereference<uint32_t>(item, quiet);
}
else if (type == df::identity_traits<int16_t>::get())
{
return validate_and_dereference<int16_t>(item, quiet);
}
else if (type == df::identity_traits<uint16_t>::get())
{
return validate_and_dereference<uint16_t>(item, quiet);
}
else if (type == df::identity_traits<int64_t>::get())
{
return validate_and_dereference<int64_t>(item, quiet);
}
else if (type == df::identity_traits<uint64_t>::get())
{
return int64_t(validate_and_dereference<uint64_t>(item, quiet));
}
else if (type == df::identity_traits<int8_t>::get())
{
return validate_and_dereference<int8_t>(item, quiet);
}
else if (type == df::identity_traits<uint8_t>::get())
{
return validate_and_dereference<uint8_t>(item, quiet);
}
else
{
UNEXPECTED;
return 0;
}
}
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(const_cast<void *>(reinterpret_cast<const void *>(info)), &base))
return nullptr;
const char *typeinfo = reinterpret_cast<const char *>(base) + reinterpret_cast<const 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 *, CheckedStructure> Checker::validate_vector_size(const QueueItem & item, const CheckedStructure & cs, bool quiet)
{
using ret_type = std::pair<const void *, CheckedStructure>;
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.identity ? cs.identity->byte_size() : 0;
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 ret_type();
}
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;
}
if (!local_ok)
{
return ret_type();
}
CheckedStructure ret_cs(cs.identity, ulength / item_size);
ret_cs.allocated_count = ucapacity / item_size;
return std::make_pair(start_ptr, ret_cs);
}
size_t Checker::get_allocated_size(const QueueItem & item)
{
if (!sizes)
{
return 0;
}
if (uintptr_t(item.ptr) % 32 != 16)
{
return 0;
}
uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(item.ptr, -8));
if (tag == 0xdfdf4ac8)
{
return *reinterpret_cast<const size_t *>(PTR_ADD(item.ptr, -16));
}
return 0;
}
#ifndef WIN32
const std::string *Checker::validate_stl_string_pointer(const void *const* base)
{
std::string empty_string;
if (*base == *reinterpret_cast<void **>(&empty_string))
{
return reinterpret_cast<const std::string *>(base);
}
const struct string_data_inner
{
size_t length;
size_t capacity;
int32_t refcount;
} *str_data = static_cast<const string_data_inner *>(*base) - 1;
if (!is_valid_dereference(QueueItem("str", PTR_ADD(str_data, -16)), 16, true))
{
return nullptr;
}
uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(str_data, -8));
if (tag == 0xdfdf4ac8)
{
size_t allocated_size = *reinterpret_cast<const size_t *>(PTR_ADD(str_data, -16));
size_t expected_size = sizeof(*str_data) + str_data->capacity + 1;
if (allocated_size != expected_size)
{
return nullptr;
}
}
else
{
return nullptr;
}
if (str_data->capacity < str_data->length)
{
return nullptr;
}
const char *ptr = reinterpret_cast<const char *>(*base);
for (size_t i = 0; i < str_data->length; i++)
{
if (!*ptr++)
{
return nullptr;
}
}
if (*ptr++)
{
return nullptr;
}
return reinterpret_cast<const std::string *>(base);
}
#endif
const char *const *Checker::get_enum_item_key(enum_identity *identity, int64_t value)
{
return get_enum_item_attr_or_key(identity, value, nullptr);
}
const char *const *Checker::get_enum_item_attr_or_key(enum_identity *identity, int64_t value, const char *attr_name)
{
size_t index;
if (auto cplx = identity->getComplex())
{
auto it = cplx->value_index_map.find(value);
if (it == cplx->value_index_map.cend())
{
return nullptr;
}
index = it->second;
}
else
{
if (value < identity->getFirstItem() || value > identity->getLastItem())
{
return nullptr;
}
index = value - identity->getFirstItem();
}
if (attr_name)
{
auto attrs = identity->getAttrs();
auto attr_type = identity->getAttrType();
if (!attrs || !attr_type)
{
return nullptr;
}
attrs = PTR_ADD(attrs, attr_type->byte_size() * index);
for (auto field = attr_type->getFields(); field->mode != struct_field_info::END; field++)
{
if (!strcmp(field->name, attr_name))
{
return reinterpret_cast<const char *const *>(PTR_ADD(attrs, field->offset));
}
}
return nullptr;
}
return &identity->getKeys()[index];
}