AnyPS5/core/libs/prx/libkernel/Module/EhFrame.hpp

118 lines
5.1 KiB
C++

#ifndef CORE_LIBS_PRX_LIBKERNEL_MODULE_EHFRAME_HPP
#define CORE_LIBS_PRX_LIBKERNEL_MODULE_EHFRAME_HPP
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
namespace EhFrame {
struct Tables {
std::uintptr_t header = 0;
std::uint64_t headerSize = 0;
std::uintptr_t frames = 0;
std::uint64_t framesSize = 0;
};
[[noreturn]] inline void Fail(const char* caller, const char* reason) {
throw std::runtime_error(std::string(caller) + ": " + reason);
}
inline std::uint64_t EncodedSize(std::uint8_t encoding, const char* caller) {
switch (encoding & 0x0fu) {
case 0x00: case 0x04: case 0x0c: return 8;
case 0x02: case 0x0a: return 2;
case 0x03: case 0x0b: return 4;
default: Fail(caller, "unsupported eh_frame_hdr value format");
}
}
template<typename TContains>
std::uintptr_t EncodedValue(const TContains& contains, std::uintptr_t field, std::uint8_t encoding, const char* caller) {
if ((encoding & 0x80u) != 0) Fail(caller, "unsupported indirect eh_frame_hdr encoding");
const auto application = encoding & 0x70u;
if (application != 0x00 && application != 0x10) Fail(caller, "eh_frame_hdr encoding other than absptr or pcrel");
const auto size = EncodedSize(encoding, caller);
if (!contains(field, size)) Fail(caller, "eh_frame_hdr outside the image");
const auto* bytes = reinterpret_cast<const void*>(field);
std::int64_t value = 0;
switch (encoding & 0x0fu) {
case 0x02: { std::uint16_t data; std::memcpy(&data, bytes, sizeof(data)); value = data; break; }
case 0x0a: { std::int16_t data; std::memcpy(&data, bytes, sizeof(data)); value = data; break; }
case 0x03: { std::uint32_t data; std::memcpy(&data, bytes, sizeof(data)); value = data; break; }
case 0x0b: { std::int32_t data; std::memcpy(&data, bytes, sizeof(data)); value = data; break; }
default: std::memcpy(&value, bytes, sizeof(value)); break;
}
return application == 0x10 ? field + static_cast<std::uintptr_t>(value) : static_cast<std::uintptr_t>(value);
}
template<typename TContains>
std::uint64_t HeaderSize(const TContains& contains, std::uintptr_t header, const char* caller) {
const auto* bytes = reinterpret_cast<const std::uint8_t*>(header);
std::uint64_t size = 4 + EncodedSize(bytes[1], caller);
if (bytes[2] == 0xff) return size;
const auto count = static_cast<std::uint64_t>(EncodedValue(contains, header + size, bytes[2] & 0x0fu, caller));
size += EncodedSize(bytes[2], caller);
if (bytes[3] == 0xff) return size;
const auto entry = 2 * EncodedSize(bytes[3], caller);
if (count > (std::numeric_limits<std::uint64_t>::max() - size) / entry || !contains(header, size + count * entry))
Fail(caller, "eh_frame_hdr table outside the image");
return size + count * entry;
}
template<typename TContains>
std::uint64_t FramesSize(const TContains& contains, std::uintptr_t frames, const char* caller) {
std::uintptr_t position = frames;
for (;;) {
if (!contains(position, 4)) Fail(caller, "eh_frame has no terminator inside the image");
std::uint32_t length;
std::memcpy(&length, reinterpret_cast<const void*>(position), sizeof(length));
if (length == 0) return position - frames;
if (length == 0xffffffffu) Fail(caller, "unsupported eh_frame record with a 64-bit length");
if (!contains(position + 4, length)) Fail(caller, "eh_frame record outside the image");
position += 4 + length;
}
}
template<typename TContains>
Tables ReadTables(const TContains& contains, std::uintptr_t header, const char* caller) {
if (!contains(header, 4)) Fail(caller, "eh_frame_hdr outside the image");
if (reinterpret_cast<const std::uint8_t*>(header)[0] != 1) Fail(caller, "unsupported eh_frame_hdr version");
Tables tables;
tables.header = header;
tables.frames = EncodedValue(contains, header + 4, reinterpret_cast<const std::uint8_t*>(header)[1], caller);
tables.headerSize = HeaderSize(contains, header, caller);
tables.framesSize = FramesSize(contains, tables.frames, caller);
return tables;
}
#ifdef _WIN32
inline bool ReadEhmeta(std::uintptr_t base, const IMAGE_NT_HEADERS64& nt, const char* caller, Tables& tables) {
const std::uint64_t imageSize = nt.OptionalHeader.SizeOfImage;
const auto contains = [&](std::uintptr_t begin, std::uint64_t size) { return begin >= base && begin - base <= imageSize && size <= imageSize - (begin - base); };
const auto* sections = IMAGE_FIRST_SECTION(&nt);
for (unsigned i = 0; i < nt.FileHeader.NumberOfSections; ++i) {
if (std::strncmp(reinterpret_cast<const char*>(sections[i].Name), ".ehmeta", IMAGE_SIZEOF_SHORT_NAME) != 0) continue;
const auto start = base + sections[i].VirtualAddress;
if (!contains(start, 4)) Fail(caller, ".ehmeta outside the image");
std::uint32_t headerRva;
std::memcpy(&headerRva, reinterpret_cast<const void*>(start), sizeof(headerRva));
tables = ReadTables(contains, base + headerRva, caller);
return true;
}
return false;
}
#endif
} // namespace EhFrame
#endif