mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-23 05:32:42 -04:00
Mods: Embed symdb in linked executable (#2216)
* Mods: Embed symdb in linked executable * Update to symgen v1.2.3
This commit is contained in:
@@ -290,7 +290,6 @@ jobs:
|
||||
path: |
|
||||
build/install/*.exe
|
||||
build/install/*.dll
|
||||
build/install/*.symdb
|
||||
build/install/res/
|
||||
build/install/mods/
|
||||
build/install/debug.7z
|
||||
|
||||
@@ -519,6 +519,8 @@ endif ()
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||
target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1")
|
||||
target_link_libraries(dusklight PRIVATE dl)
|
||||
elseif (ANDROID)
|
||||
target_link_options(dusklight PRIVATE "-Wl,--build-id=sha1")
|
||||
endif ()
|
||||
|
||||
if (NOT APPLE)
|
||||
|
||||
@@ -19,9 +19,6 @@ for install_path in build/install/*; do
|
||||
[[ "$(basename "$install_path")" == *.* ]] && continue
|
||||
cp -r "$install_path" build/appdir/usr/bin
|
||||
done
|
||||
if [[ -f build/install/dusklight.symdb ]]; then
|
||||
cp build/install/dusklight.symdb build/appdir/usr/bin
|
||||
fi
|
||||
cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor
|
||||
cp platforms/freedesktop/dev.twilitrealm.dusk.desktop build/appdir/usr/share/applications
|
||||
|
||||
|
||||
+13
-13
@@ -2,7 +2,7 @@ include_guard(GLOBAL)
|
||||
|
||||
get_filename_component(_SYMBOL_MANIFEST_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
|
||||
|
||||
set(_SYMGEN_VERSION "1.2.0")
|
||||
set(_SYMGEN_VERSION "1.2.3")
|
||||
set(_SYMGEN_RELEASE_BASE_URL "https://github.com/encounter/symgen/releases/download/v${_SYMGEN_VERSION}")
|
||||
set(SYMGEN_PATH "" CACHE FILEPATH "Path to a symgen executable; empty downloads the pinned release")
|
||||
mark_as_advanced(SYMGEN_PATH)
|
||||
@@ -108,21 +108,21 @@ function(setup_symbol_manifest target)
|
||||
|
||||
if (WIN32)
|
||||
set(_input --pdb "$<TARGET_PDB_FILE:${target}>")
|
||||
set(_out "$<TARGET_FILE_DIR:${target}>/dusklight.symdb")
|
||||
else ()
|
||||
set(_input --binary "$<TARGET_FILE:${target}>")
|
||||
if (APPLE)
|
||||
set(_out "$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Resources/dusklight.symdb")
|
||||
else ()
|
||||
set(_out "$<TARGET_FILE_DIR:${target}>/dusklight.symdb")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND "${SYMGEN_EXE}" manifest ${_input} --out "${_out}"
|
||||
COMMENT "Generating symbol manifest"
|
||||
VERBATIM)
|
||||
if (NOT APPLE)
|
||||
install(FILES "${_out}" DESTINATION .)
|
||||
if (APPLE)
|
||||
# Room for the load command `symgen manifest --embed` inserts post-link.
|
||||
target_link_options(${target} PRIVATE "LINKER:-headerpad,0x200")
|
||||
endif ()
|
||||
|
||||
# The manifest is embedded into the image as a new section, located at runtime through
|
||||
# the descriptor manifest.cpp reserves. On Apple platforms this command must stay
|
||||
# attached before the ad-hoc codesign POST_BUILD command: the patch invalidates any
|
||||
# existing signature.
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND "${SYMGEN_EXE}" manifest ${_input} --embed "$<TARGET_FILE:${target}>"
|
||||
COMMENT "Embedding symbol manifest"
|
||||
VERBATIM)
|
||||
endfunction()
|
||||
|
||||
+86
-59
@@ -6,18 +6,14 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
|
||||
#include "dusk/io.hpp"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
@@ -63,6 +59,31 @@ struct Entry {
|
||||
};
|
||||
static_assert(sizeof(Entry) == 24);
|
||||
|
||||
/*
|
||||
* `symgen manifest --embed` appends the symbol manifest to the linked executable as an added
|
||||
* section and patches this descriptor with its location.
|
||||
*/
|
||||
struct SymdbDescriptor {
|
||||
volatile uint64_t magic;
|
||||
volatile uint64_t rva;
|
||||
volatile uint64_t size;
|
||||
};
|
||||
constexpr uint64_t kDescriptorMagic = 0x52444842444d5953ull; // "SYMDBHDR"
|
||||
#if defined(_WIN32)
|
||||
#pragma section(".symdbh", read)
|
||||
__declspec(allocate(".symdbh"))
|
||||
#if defined(__clang__)
|
||||
__attribute__((used))
|
||||
#endif
|
||||
constinit const SymdbDescriptor s_symdbDescriptor{kDescriptorMagic, 0, 0};
|
||||
#elif defined(__APPLE__)
|
||||
__attribute__((section("__DATA,__symdbh"), used)) constinit const SymdbDescriptor
|
||||
s_symdbDescriptor{kDescriptorMagic, 0, 0};
|
||||
#else
|
||||
__attribute__((section("symdbh"), used)) constinit const SymdbDescriptor s_symdbDescriptor{
|
||||
kDescriptorMagic, 0, 0};
|
||||
#endif
|
||||
|
||||
struct State {
|
||||
std::vector<uint8_t> data;
|
||||
const Entry* entries = nullptr;
|
||||
@@ -149,13 +170,28 @@ bool running_image_identity(std::vector<uint8_t>& outId, uintptr_t& outBase) {
|
||||
#elif defined(__linux__)
|
||||
struct Ctx {
|
||||
std::vector<uint8_t>* id;
|
||||
uintptr_t probe;
|
||||
uintptr_t base = 0;
|
||||
bool found = false;
|
||||
} ctx{&outId};
|
||||
} ctx{&outId, reinterpret_cast<uintptr_t>(&s_symdbDescriptor)};
|
||||
dl_iterate_phdr(
|
||||
[](dl_phdr_info* info, size_t, void* data) -> int {
|
||||
auto* ctx = static_cast<Ctx*>(data);
|
||||
// The first callback is the main executable.
|
||||
// Select the image containing our descriptor: the game is not always the
|
||||
// first entry (on Android it is libmain.so, behind the app process).
|
||||
bool contains = false;
|
||||
for (int i = 0; i < info->dlpi_phnum; ++i) {
|
||||
const auto& phdr = info->dlpi_phdr[i];
|
||||
if (phdr.p_type == PT_LOAD && ctx->probe >= info->dlpi_addr + phdr.p_vaddr &&
|
||||
ctx->probe - (info->dlpi_addr + phdr.p_vaddr) < phdr.p_memsz)
|
||||
{
|
||||
contains = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contains) {
|
||||
return 0;
|
||||
}
|
||||
ctx->base = info->dlpi_addr;
|
||||
for (int i = 0; i < info->dlpi_phnum; ++i) {
|
||||
const auto& phdr = info->dlpi_phdr[i];
|
||||
@@ -178,7 +214,7 @@ bool running_image_identity(std::vector<uint8_t>& outId, uintptr_t& outBase) {
|
||||
p = desc + ((note->n_descsz + 3) & ~3u);
|
||||
}
|
||||
}
|
||||
return 1; // only inspect the main executable
|
||||
return 1; // matched our image; stop either way
|
||||
},
|
||||
&ctx);
|
||||
outBase = ctx.base;
|
||||
@@ -190,13 +226,6 @@ bool running_image_identity(std::vector<uint8_t>& outId, uintptr_t& outBase) {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path manifest_path() {
|
||||
const char* basePath = SDL_GetBasePath();
|
||||
std::filesystem::path dir =
|
||||
basePath != nullptr ? std::filesystem::path{basePath} : std::filesystem::current_path();
|
||||
return dir / "dusklight.symdb";
|
||||
}
|
||||
|
||||
std::string hex_string(const uint8_t* data, size_t len) {
|
||||
std::string out;
|
||||
out.reserve(len * 2);
|
||||
@@ -216,40 +245,12 @@ void initialize() {
|
||||
}
|
||||
s_state.initialized = true;
|
||||
|
||||
const auto path = manifest_path();
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(path, ec)) {
|
||||
Log.info("no symbol manifest at {}; by-name resolution unavailable",
|
||||
io::fs_path_to_string(path));
|
||||
if (s_symdbDescriptor.magic != kDescriptorMagic) {
|
||||
Log.error("symbol manifest descriptor is corrupt");
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> data;
|
||||
try {
|
||||
data = io::FileStream::ReadAllBytes(path);
|
||||
} catch (const std::exception& e) {
|
||||
Log.error("failed to read symbol manifest {}: {}", io::fs_path_to_string(path), e.what());
|
||||
return;
|
||||
}
|
||||
if (data.size() < sizeof(Header)) {
|
||||
Log.error(
|
||||
"symbol manifest {} is truncated ({} bytes)", io::fs_path_to_string(path), data.size());
|
||||
return;
|
||||
}
|
||||
|
||||
Header header{};
|
||||
std::memcpy(&header, data.data(), sizeof(header));
|
||||
if (std::memcmp(header.magic, kMagic, sizeof(kMagic)) != 0 || header.version != kVersion) {
|
||||
Log.error("symbol manifest {} has wrong magic/version", io::fs_path_to_string(path));
|
||||
return;
|
||||
}
|
||||
const auto compression = static_cast<Compression>(header.compression);
|
||||
if ((compression != Compression::None && compression != Compression::Zstd) ||
|
||||
header.buildIdLen > sizeof(header.buildId) ||
|
||||
header.compressedLen > data.size() - sizeof(Header) ||
|
||||
header.uncompressedLen > std::numeric_limits<size_t>::max() ||
|
||||
(compression == Compression::None && header.compressedLen != header.uncompressedLen))
|
||||
{
|
||||
Log.error("symbol manifest {} is malformed", io::fs_path_to_string(path));
|
||||
if (s_symdbDescriptor.rva == 0) {
|
||||
Log.info("no symbol manifest embedded; by-name resolution unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -259,41 +260,67 @@ void initialize() {
|
||||
Log.error("cannot determine the running image's build id; ignoring symbol manifest");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* blob = reinterpret_cast<const uint8_t*>(imageBase + s_symdbDescriptor.rva);
|
||||
const auto blobLen = static_cast<size_t>(s_symdbDescriptor.size);
|
||||
if (blobLen < sizeof(Header)) {
|
||||
Log.error("embedded symbol manifest is truncated ({} bytes)", blobLen);
|
||||
return;
|
||||
}
|
||||
|
||||
Header header{};
|
||||
std::memcpy(&header, blob, sizeof(header));
|
||||
if (std::memcmp(header.magic, kMagic, sizeof(kMagic)) != 0 || header.version != kVersion) {
|
||||
Log.error("embedded symbol manifest has wrong magic/version");
|
||||
return;
|
||||
}
|
||||
const auto compression = static_cast<Compression>(header.compression);
|
||||
if ((compression != Compression::None && compression != Compression::Zstd) ||
|
||||
header.buildIdLen > sizeof(header.buildId) ||
|
||||
header.compressedLen > blobLen - sizeof(Header) ||
|
||||
header.uncompressedLen > std::numeric_limits<size_t>::max() ||
|
||||
(compression == Compression::None && header.compressedLen != header.uncompressedLen))
|
||||
{
|
||||
Log.error("embedded symbol manifest is malformed");
|
||||
return;
|
||||
}
|
||||
|
||||
// The manifest travels inside the image it describes, so a mismatch here means broken
|
||||
// build tooling rather than a stale file — but it still guards resolved addresses.
|
||||
if (imageId.size() != header.buildIdLen ||
|
||||
std::memcmp(imageId.data(), header.buildId, imageId.size()) != 0)
|
||||
{
|
||||
Log.error("symbol manifest {} is stale: built for {}, running image is {}",
|
||||
io::fs_path_to_string(path), hex_string(header.buildId, header.buildIdLen),
|
||||
Log.error("embedded symbol manifest is stale: built for {}, running image is {}",
|
||||
hex_string(header.buildId, header.buildIdLen),
|
||||
hex_string(imageId.data(), imageId.size()));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto compressedLen = static_cast<size_t>(header.compressedLen);
|
||||
const auto uncompressedLen = static_cast<size_t>(header.uncompressedLen);
|
||||
std::vector<uint8_t> payload;
|
||||
const auto* storedPayload = data.data() + sizeof(Header);
|
||||
std::vector<uint8_t> data;
|
||||
const auto* storedPayload = blob + sizeof(Header);
|
||||
if (compression == Compression::None) {
|
||||
payload.assign(storedPayload, storedPayload + compressedLen);
|
||||
data.assign(storedPayload, storedPayload + compressedLen);
|
||||
} else {
|
||||
payload.resize(uncompressedLen);
|
||||
data.resize(uncompressedLen);
|
||||
const size_t decompressedLen =
|
||||
ZSTD_decompress(payload.data(), payload.size(), storedPayload, compressedLen);
|
||||
ZSTD_decompress(data.data(), data.size(), storedPayload, compressedLen);
|
||||
if (ZSTD_isError(decompressedLen)) {
|
||||
Log.error("failed to decompress symbol manifest {}: {}", io::fs_path_to_string(path),
|
||||
Log.error("failed to decompress embedded symbol manifest: {}",
|
||||
ZSTD_getErrorName(decompressedLen));
|
||||
return;
|
||||
}
|
||||
if (decompressedLen != payload.size()) {
|
||||
Log.error("symbol manifest {} decompressed to {} bytes, expected {}",
|
||||
io::fs_path_to_string(path), decompressedLen, payload.size());
|
||||
if (decompressedLen != data.size()) {
|
||||
Log.error("embedded symbol manifest decompressed to {} bytes, expected {}",
|
||||
decompressedLen, data.size());
|
||||
return;
|
||||
}
|
||||
}
|
||||
data = std::move(payload);
|
||||
|
||||
const uint64_t entriesEnd = uint64_t{header.entryCount} * sizeof(Entry);
|
||||
if (entriesEnd > data.size()) {
|
||||
Log.error("decompressed symbol manifest {} is malformed", io::fs_path_to_string(path));
|
||||
Log.error("decompressed embedded symbol manifest is malformed");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user