fix(relinker): support windows guest tls relocations and accesses

This commit is contained in:
boykopovar 2026-09-26 15:33:26 +03:00
parent 0c6765e6a6
commit 6b2db1d8a8
5 changed files with 72 additions and 6 deletions

View File

@ -12,6 +12,7 @@ add_executable(relinker
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeFormat.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsLoadImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTlsBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsRelocationBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsImportBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp

View File

@ -0,0 +1,15 @@
#ifndef ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP
#define ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP
#include <elfpatcher/windows/WindowsLoadImage.hpp>
namespace Elfpatcher::Windows {
class WindowsTlsTemplateBuilder {
public:
std::vector<std::uint8_t> Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector<PeSection>& sections, std::uint32_t templateRva, std::vector<std::uint32_t>& relocations) const;
};
}
#endif

View File

@ -26,7 +26,12 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestI
const auto& symbol = guest.Symbols.at(symbolIndex);
const auto rva = image.GetRva(target, 8);
for (const auto& header : guest.Headers) {
if (header.Type == 7 && target >= header.MappedAddress && target - header.MappedAddress < header.FileSize) throw Domain::RelinkerException("Windows guest TLS template relocations require explicit support", target);
if (header.Type != 7 || header.FileSize == 0) continue;
const auto templateRva = image.GetRva(header.MappedAddress, header.FileSize);
const auto templateEnd = static_cast<std::uint64_t>(templateRva) + header.FileSize;
if (rva >= templateEnd || static_cast<std::uint64_t>(rva) + 8 <= templateRva) continue;
if (rva < templateRva || templateEnd - rva < 8) throw Domain::RelinkerException("Guest relocation crosses the TLS template boundary", target);
if (type == 16 || (symbolIndex != 0 && symbol.Section == 0)) throw Domain::RelinkerException("Runtime-bound guest TLS template relocation is not supported", target);
}
const auto next = targets.lower_bound(target);
if ((next != targets.end() && next->first < target + 8) || (next != targets.begin() && std::prev(next)->second > target)) throw Domain::RelinkerException("Overlapping guest relocations", target);

View File

@ -1,5 +1,6 @@
#include <elfpatcher/windows/WindowsTlsBuilder.hpp>
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
#include <elfpatcher/windows/WindowsTlsTemplateBuilder.hpp>
#include <codegen/x86/X64InstructionDecoder.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
@ -14,6 +15,8 @@ namespace {
struct TlsAccess {
std::uint32_t Rva;
std::size_t Length;
bool StoreImmediate;
std::uint32_t Immediate;
};
void patchAccess(std::vector<PeSection>& sections, const TlsAccess& access, const std::uint32_t target) {
@ -63,9 +66,11 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
if (info.SegmentPrefix != 0) {
const auto position = info.OpcodeOffset;
if (info.SegmentPrefix != 0x64 || info.RexPrefix != 0x48 || info.Length - position != 7 || bytes[position] != 0x8b || bytes[position + 1] != 0x04 || bytes[position + 2] != 0x25 || Io::ReadU32(source, header.Offset + offset + position + 3) != 0)
const bool loadPointer = info.Length == 9 && position == 2 && bytes[0] == 0x64 && bytes[1] == 0x48 && bytes[2] == 0x8b && bytes[3] == 0x04 && bytes[4] == 0x25 && Io::ReadU32(source, header.Offset + offset + 5) == 0;
const bool storeImmediate = info.Length == 12 && position == 1 && bytes[0] == 0x64 && bytes[1] == 0xc7 && bytes[2] == 0x04 && bytes[3] == 0x25 && Io::ReadU32(source, header.Offset + offset + 4) == 0x28;
if (!loadPointer && !storeImmediate)
throw Domain::RelinkerException("Unsupported Windows guest TLS instruction", header.Offset + offset);
accesses.push_back({rva, info.Length});
accesses.push_back({rva, info.Length, storeImmediate, storeImmediate ? Io::ReadU32(source, header.Offset + offset + 8) : 0});
}
offset += info.Length;
}
@ -89,7 +94,8 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
const auto alignment = std::max<std::uint64_t>(tls->Alignment, 16);
const auto blockSize = CheckedRva((tls->MemorySize + alignment - 1) & ~(alignment - 1));
const auto templateOffset = CheckedRva((64 + alignment - 1) & ~(alignment - 1));
PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(templateOffset + blockSize + 16)};
constexpr std::uint32_t threadControlBlockSize = 0x30;
PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(templateOffset + blockSize + threadControlBlockSize)};
const auto indexRva = nextRva + 40;
if (tlsIndexRva != nullptr) *tlsIndexRva = indexRva;
@ -121,19 +127,26 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
throw Domain::RelinkerException("Branch enters a guest TLS instruction", *target);
patchAccess(sections, access, code.GetRva());
code.Emit({0x48, 0x8d, 0x64, 0x24, 0x80, 0x51});
if (access.StoreImmediate) code.Emit({0x50});
loadPointer();
if (access.StoreImmediate) {
code.Emit({0xc7, 0x40, 0x28});
code.U32(access.Immediate);
code.Emit({0x58});
}
code.Emit({0x59, 0x48, 0x8d, 0xa4, 0x24, 0x80, 0, 0, 0});
code.Rip({0xe9}, CheckedRva(access.Rva + access.Length));
}
std::copy_n(source.begin() + tls->Offset, tls->FileSize, data.Data.begin() + templateOffset);
const auto templateBytes = WindowsTlsTemplateBuilder().Build(*tls, image, sections, templateRva, relocations);
std::copy(templateBytes.begin(), templateBytes.end(), data.Data.begin() + templateOffset);
const auto writeAddress = [&](const std::size_t offset, const std::uint32_t rva) {
Io::WriteU64(data.Data, offset, ImageBase + rva);
relocations.push_back(CheckedRva(data.Rva + offset));
};
writeAddress(0, templateRva);
writeAddress(8, CheckedRva(templateRva + blockSize + 16));
writeAddress(8, CheckedRva(templateRva + blockSize + threadControlBlockSize));
writeAddress(16, indexRva);
writeAddress(24, callbackTableRva);
writeAddress(48, codeRva);

View File

@ -0,0 +1,32 @@
#include <elfpatcher/windows/WindowsTlsTemplateBuilder.hpp>
#include <algorithm>
namespace Elfpatcher::Windows {
std::vector<std::uint8_t> WindowsTlsTemplateBuilder::Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector<PeSection>& sections, const std::uint32_t templateRva, std::vector<std::uint32_t>& relocations) const {
if (tls.FileSize == 0) return {};
const auto sourceRva = image.GetRva(tls.MappedAddress, tls.FileSize);
const auto sourceEnd = static_cast<std::uint64_t>(sourceRva) + tls.FileSize;
std::vector<std::uint8_t> result;
result.reserve(static_cast<std::size_t>(tls.FileSize));
while (result.size() < tls.FileSize) {
const auto currentRva = static_cast<std::uint64_t>(sourceRva) + result.size();
const auto section = std::find_if(sections.begin(), sections.end(), [&](const auto& candidate) {
return currentRva >= candidate.Rva && currentRva - candidate.Rva < candidate.Data.size();
});
if (section == sections.end()) throw Domain::RelinkerException("TLS template is outside PE section data", currentRva);
const auto offset = static_cast<std::size_t>(currentRva - section->Rva);
const auto size = std::min<std::uint64_t>(tls.FileSize - result.size(), section->Data.size() - offset);
result.insert(result.end(), section->Data.begin() + offset, section->Data.begin() + offset + size);
}
const auto originalCount = relocations.size();
for (std::size_t index = 0; index < originalCount; ++index) {
const auto target = static_cast<std::uint64_t>(relocations[index]);
if (target >= sourceEnd || target + 8 <= sourceRva) continue;
if (target < sourceRva || sourceEnd - target < 8) throw Domain::RelinkerException("PE relocation crosses the TLS template boundary", target);
relocations.push_back(CheckedRva(static_cast<std::uint64_t>(templateRva) + target - sourceRva));
}
return result;
}
}