feat(windows): add import error chain diagnostic

This commit is contained in:
boykopovar 2026-09-12 15:55:46 +03:00
parent 31943e9a62
commit 9d7c255316
6 changed files with 1032 additions and 5 deletions

View File

@ -10,6 +10,7 @@ add_executable(relinker
${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
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsDependencyStubBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsStubEmitter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/general/SegmentFilter.cpp
@ -53,6 +54,19 @@ target_include_directories(relinker PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/relinker/include
)
if(WIN32)
add_executable(windows_dependency_diagnostics_tests EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/tests/WindowsDependencyDiagnosticsTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsDependencyStubBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsStubEmitter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsImportBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeFormat.cpp
${CMAKE_CURRENT_SOURCE_DIR}/io/src/BufferUtils.cpp
)
target_include_directories(windows_dependency_diagnostics_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include ${CMAKE_CURRENT_SOURCE_DIR}/domain/include ${CMAKE_CURRENT_SOURCE_DIR}/io/include)
endif()
if(BUILD_TESTING)
add_executable(strict_nid_filter_tests
@ -79,4 +93,3 @@ if(BUILD_TESTING)
)
add_test(NAME strict_nid_filter COMMAND strict_nid_filter_tests)
endif()

View File

@ -0,0 +1,28 @@
#ifndef ELFPATCHER_WINDOWS_DEPENDENCYSTUBBUILDER_HPP
#define ELFPATCHER_WINDOWS_DEPENDENCYSTUBBUILDER_HPP
#include <elfpatcher/windows/WindowsImportBuilder.hpp>
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
#include <array>
#include <map>
namespace Elfpatcher::Windows {
struct WindowsDependencyStub {
std::uint32_t EntryRva;
std::vector<std::array<std::uint32_t, 3>> Functions;
};
class WindowsDependencyStubBuilder {
public:
explicit WindowsDependencyStubBuilder(PeSection& data);
WindowsDependencyStub Build(WindowsStubEmitter& code, const WindowsImports& imports) const;
private:
std::map<std::string, std::uint32_t> storage;
std::uint32_t unwindRva;
};
}
#endif

View File

@ -0,0 +1,763 @@
#include <elfpatcher/windows/WindowsDependencyStubBuilder.hpp>
#include <io/BufferUtils.hpp>
namespace Elfpatcher::Windows {
namespace {
enum Register : std::uint8_t { Ax, Cx, Dx, Bx, Sp, Bp, Si, Di, R8, R9, R10, R11, R12, R13, R14, R15 };
constexpr std::uint32_t PathCapacity = 32768;
constexpr std::uint32_t NodeSize = PathCapacity + 64;
constexpr std::uint32_t NodeLimit = 128;
class Assembler {
public:
Assembler(WindowsStubEmitter& code, const WindowsImports& imports, const std::map<std::string, std::uint32_t>& storage) : code(code), imports(imports), storage(storage) {}
void Mark(const std::string& label) {
if (!labels.emplace(label, code.GetRva()).second)
throw Domain::RelinkerException("Duplicate dependency stub label: " + label);
}
void Jump(const std::string& label, const std::uint8_t condition = 0) {
const auto branch = condition == 0 ? code.Branch({0xe9}) : code.Branch({0x0f, condition});
branches.emplace_back(branch, label);
}
void Call(const std::string& label) { branches.emplace_back(code.Branch({0xe8}), label); }
void Api(const std::string& name) { code.Rip({0xff, 0x15}, imports.Functions.at(name)); }
void Begin(const std::string& name) {
Mark(name);
starts.push_back(code.GetRva());
code.Emit({0x53, 0x55, 0x56, 0x57, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0x83, 0xec, 0x48});
}
void End() {
code.Emit({0x48, 0x83, 0xc4, 0x48, 0x41, 0x5f, 0x41, 0x5e, 0x41, 0x5d, 0x41, 0x5c, 0x5f, 0x5e, 0x5d, 0x5b, 0xc3});
ends.push_back(code.GetRva());
}
void Mov(const Register destination, const Register source) { binary(0x89, destination, source); }
void Add(const Register destination, const Register source) { binary(0x01, destination, source); }
void Sub(const Register destination, const Register source) { binary(0x29, destination, source); }
void Compare(const Register left, const Register right) { binary(0x39, left, right); }
void Test(const Register value) { binary(0x85, value, value); }
void Value(const Register destination, const std::uint64_t value) {
code.Emit({static_cast<std::uint8_t>(0x48 | (destination >> 3)), static_cast<std::uint8_t>(0xb8 | (destination & 7))});
code.U64(value);
}
void AddValue(const Register destination, const std::uint32_t value) { immediate(destination, 0, value); }
void SubValue(const Register destination, const std::uint32_t value) { immediate(destination, 5, value); }
void CompareValue(const Register destination, const std::uint32_t value) { immediate(destination, 7, value); }
void Mask(const Register destination, const std::uint32_t value) { immediate(destination, 4, value); }
void Load(const Register destination, const Register base, const std::uint32_t offset = 0, const std::uint8_t size = 8) { memory(destination, base, offset, size == 1 ? 0xb6 : size == 2 ? 0xb7 : 0x8b, size); }
void Store(const Register base, const std::uint32_t offset, const Register source) { memory(source, base, offset, 0x89, 8); }
void StoreByte(const Register base, const Register source) { memory(source, base, 0, 0x88, 1, false); }
void Address(const Register destination, const Register base, const std::uint32_t offset) { memory(destination, base, offset, 0x8d, 8); }
void Data(const Register destination, const std::string& name) {
code.Rip({static_cast<std::uint8_t>(0x48 | ((destination >> 3) << 2)), 0x8d, static_cast<std::uint8_t>(0x05 | ((destination & 7) << 3))}, storage.at(name));
}
void Global(const Register destination, const std::string& name) {
code.Rip({static_cast<std::uint8_t>(0x48 | ((destination >> 3) << 2)), 0x8b, static_cast<std::uint8_t>(0x05 | ((destination & 7) << 3))}, storage.at(name));
}
void Save(const std::string& name, const Register source) {
code.Rip({static_cast<std::uint8_t>(0x48 | ((source >> 3) << 2)), 0x89, static_cast<std::uint8_t>(0x05 | ((source & 7) << 3))}, storage.at(name));
}
void Text(const std::string& name) { Data(Cx, name); Call("write"); }
void Finish() {
for (const auto& [offset, label] : branches)
code.PatchBranch(offset, labels.at(label));
}
WindowsDependencyStub Result(const std::uint32_t unwind) const {
WindowsDependencyStub result{labels.at("diagnose"), {}};
for (std::size_t index = 0; index < starts.size(); ++index)
result.Functions.push_back({starts[index], ends.at(index), unwind});
return result;
}
private:
void binary(const std::uint8_t opcode, const Register destination, const Register source) {
code.Emit({static_cast<std::uint8_t>(0x48 | ((source >> 3) << 2) | (destination >> 3)), opcode, static_cast<std::uint8_t>(0xc0 | ((source & 7) << 3) | (destination & 7))});
}
void immediate(const Register destination, const std::uint8_t operation, const std::uint32_t value) {
code.Emit({static_cast<std::uint8_t>(0x48 | (destination >> 3)), 0x81, static_cast<std::uint8_t>(0xc0 | (operation << 3) | (destination & 7))});
code.U32(value);
}
void memory(const Register value, const Register base, const std::uint32_t offset, const std::uint8_t opcode, const std::uint8_t size, const bool extend = true) {
code.Emit({static_cast<std::uint8_t>((size == 8 ? 0x48 : 0x40) | ((value >> 3) << 2) | (base >> 3))});
if (size < 4 && extend)
code.Emit({0x0f});
code.Emit({opcode, static_cast<std::uint8_t>(0x80 | ((value & 7) << 3) | (base & 7))});
if ((base & 7) == Sp)
code.Emit({0x24});
code.U32(offset);
}
WindowsStubEmitter& code;
const WindowsImports& imports;
const std::map<std::string, std::uint32_t>& storage;
std::map<std::string, std::uint32_t> labels;
std::vector<std::pair<std::size_t, std::string>> branches;
std::vector<std::uint32_t> starts;
std::vector<std::uint32_t> ends;
};
}
WindowsDependencyStubBuilder::WindowsDependencyStubBuilder(PeSection& data) {
for (const auto& name : {"rootDirectory", "executableDirectory", "systemDirectory", "scratch", "forwarder"}) {
storage.emplace(name, CheckedRva(data.Rva + data.Data.size()));
data.Data.resize(data.Data.size() + PathCapacity);
}
for (const auto& name : {"nodes", "count", "root", "executable", "digits"}) {
storage.emplace(name, CheckedRva(data.Rva + data.Data.size()));
data.Data.resize(data.Data.size() + 32);
}
const std::map<std::string, std::string> messages = {
{"invalid", "FAIL: invalid or unsupported PE dependency metadata\n"},
{"limit", "FAIL: dependency diagnostics capacity exceeded\n"},
{"io", "FAIL: dependency diagnostics Windows API failed\n"},
{"missingModule", "FAIL: dependency module not found: "},
{"moduleImporter", "\nImporter: "},
{"importer", "FAIL: unresolved dependency import\nImporter: "},
{"provider", "\nProvider: "},
{"symbol", "\nSymbol: "},
{"chain", "\nChain: "},
{"arrow", " -> "},
{"newline", "\n"},
{"ordinal", "#"},
{"extension", ".dll"},
{"unresolved", "Dependency tables contain no missing imports; original load failure remains fatal.\n"}
};
for (const auto& [name, value] : messages) {
storage.emplace(name, CheckedRva(data.Rva + data.Data.size()));
Io::AppendString(data.Data, value);
}
Io::AlignBuffer(data.Data, 4);
unwindRva = CheckedRva(data.Rva + data.Data.size());
data.Data.insert(data.Data.end(), {1, 16, 9, 0, 16, 0x82, 12, 0xf0, 10, 0xe0, 8, 0xd0, 6, 0xc0, 4, 0x70, 3, 0x60, 2, 0x50, 1, 0x30, 0, 0});
}
WindowsDependencyStub WindowsDependencyStubBuilder::Build(WindowsStubEmitter& code, const WindowsImports& imports) const {
Assembler a(code, imports, storage);
a.Begin("write");
a.Mov(Si, Cx);
a.Api("lstrlenA");
a.Mov(Bx, Ax);
a.Value(Cx, 0xfffffff4u);
a.Api("GetStdHandle");
a.Mov(Cx, Ax);
a.Mov(Dx, Si);
a.Mov(R8, Bx);
a.Address(R9, Sp, 40);
a.Value(Ax, 0);
a.Store(Sp, 32, Ax);
a.Api("WriteFile");
a.Test(Ax);
a.Jump("terminate", 0x84);
a.Load(Ax, Sp, 40, 4);
a.Compare(Ax, Bx);
a.Jump("terminate", 0x85);
a.End();
a.Begin("fatal");
a.Call("write");
a.Mark("terminate");
a.Value(Cx, 0xc0000135u);
a.Value(Dx, 1);
a.Value(R8, 0);
a.Value(R9, 0);
a.Api("RaiseException");
code.Emit({0x0f, 0x0b});
a.Mark("invalid");
a.Text("invalid");
a.Jump("terminate");
a.Mark("io");
a.Text("io");
a.Jump("terminate");
a.Mark("limit");
a.Text("limit");
a.Jump("terminate");
a.End();
a.Begin("copy");
a.Mov(Bx, Cx);
a.Mov(Si, Dx);
a.Mov(Cx, Dx);
a.Api("lstrlenA");
a.CompareValue(Ax, PathCapacity);
a.Jump("limit", 0x83);
a.Mov(Cx, Bx);
a.Mov(Dx, Si);
a.Api("lstrcpyA");
a.Test(Ax);
a.Jump("io", 0x84);
a.End();
a.Begin("directory");
a.Mov(Bx, Cx);
a.Call("copy");
a.Mov(Cx, Bx);
a.Api("lstrlenA");
a.Mov(Si, Bx);
a.Add(Si, Ax);
a.Mark("directoryLoop");
a.Compare(Si, Bx);
a.Jump("invalid", 0x84);
a.SubValue(Si, 1);
a.Load(Ax, Si, 0, 1);
a.CompareValue(Ax, '\\');
a.Jump("directoryLoop", 0x85);
a.AddValue(Si, 1);
a.Value(Ax, 0);
a.StoreByte(Si, Ax);
a.End();
a.Begin("join");
a.Mov(Bx, Cx);
a.Mov(Si, Dx);
a.Mov(Di, R8);
a.Mov(Cx, Dx);
a.Api("lstrlenA");
a.Mov(Bp, Ax);
a.Mov(Cx, Di);
a.Api("lstrlenA");
a.Add(Ax, Bp);
a.CompareValue(Ax, PathCapacity);
a.Jump("limit", 0x83);
a.Mov(Cx, Bx);
a.Mov(Dx, Si);
a.Call("copy");
a.Mov(Cx, Bx);
a.Mov(Dx, Di);
a.Api("lstrcatA");
a.Test(Ax);
a.Jump("io", 0x84);
a.End();
a.Begin("bounds");
a.Load(Ax, Cx, 16);
a.Compare(Dx, Ax);
a.Jump("invalid", 0x87);
a.Sub(Ax, Dx);
a.Compare(R8, Ax);
a.Jump("invalid", 0x87);
a.Load(Ax, Cx);
a.Add(Ax, Dx);
a.End();
a.Begin("string");
a.Mov(Bx, Cx);
a.Value(R8, 1);
a.Call("bounds");
a.Load(Dx, Bx);
a.Load(Cx, Bx, 16);
a.Add(Dx, Cx);
a.Mov(Cx, Ax);
a.Mark("stringLoop");
a.Compare(Cx, Dx);
a.Jump("invalid", 0x83);
a.Load(R8, Cx, 0, 1);
a.Test(R8);
a.Jump("stringDone", 0x84);
a.AddValue(Cx, 1);
a.Jump("stringLoop");
a.Mark("stringDone");
a.End();
a.Begin("register");
a.Mov(Si, Cx);
a.Global(Bx, "nodes");
a.Global(Di, "count");
a.Mark("registerSearch");
a.Test(Di);
a.Jump("registerNew", 0x84);
a.Address(Cx, Bx, 64);
a.Mov(Dx, Si);
a.Api("lstrcmpiA");
a.Test(Ax);
a.Jump("registerDone", 0x84);
a.AddValue(Bx, NodeSize);
a.SubValue(Di, 1);
a.Jump("registerSearch");
a.Mark("registerNew");
a.Global(Ax, "count");
a.CompareValue(Ax, NodeLimit);
a.Jump("limit", 0x83);
a.AddValue(Ax, 1);
a.Save("count", Ax);
a.Address(Cx, Bx, 64);
a.Mov(Dx, Si);
a.Call("copy");
a.Address(Cx, Bx, 64);
a.Api("GetModuleHandleA");
a.Store(Bx, 8, Ax);
a.Test(Ax);
a.Jump("registerMapped", 0x85);
a.Address(Cx, Bx, 64);
a.Value(Dx, 0);
a.Value(R8, 0x20);
a.Api("LoadLibraryExA");
a.Test(Ax);
a.Jump("io", 0x84);
a.Mask(Ax, 0xfffffffcu);
a.Mark("registerMapped");
a.Store(Bx, 0, Ax);
a.Mov(Si, Ax);
a.Load(Ax, Si, 0, 2);
a.CompareValue(Ax, 0x5a4d);
a.Jump("invalid", 0x85);
a.Load(Di, Si, 0x3c, 4);
a.CompareValue(Di, 0x1000);
a.Jump("invalid", 0x83);
a.Add(Di, Si);
a.Load(Ax, Di, 0, 4);
a.CompareValue(Ax, 0x4550);
a.Jump("invalid", 0x85);
a.Load(Ax, Di, 24, 2);
a.CompareValue(Ax, 0x20b);
a.Jump("invalid", 0x85);
a.Load(Ax, Di, 80, 4);
a.Store(Bx, 16, Ax);
a.Load(Ax, Di, 144, 4);
a.Store(Bx, 24, Ax);
a.Load(Dx, Di, 148, 4);
a.Add(Dx, Ax);
a.Store(Bx, 32, Dx);
a.Mark("registerDone");
a.Mov(Ax, Bx);
a.End();
a.Begin("resolve");
a.Mov(Bx, Cx);
a.Mov(Si, Dx);
a.Api("GetModuleHandleA");
a.Test(Ax);
a.Jump("resolveLoaded", 0x85);
a.Mov(Cx, Bx);
a.Api("lstrlenA");
a.Test(Ax);
a.Jump("invalid", 0x84);
a.CompareValue(Ax, 4);
a.Jump("resolveDirectories", 0x82);
a.Load(Ax, Bx, 0, 4);
a.CompareValue(Ax, 0x2d697061);
a.Jump("resolveContract", 0x84);
a.CompareValue(Ax, 0x2d747865);
a.Jump("resolveContract", 0x84);
a.Mark("resolveDirectories");
for (const auto& directory : {"rootDirectory", "executableDirectory", "systemDirectory"}) {
a.Data(Cx, "scratch");
a.Data(Dx, directory);
a.Mov(R8, Bx);
a.Call("join");
a.Data(Cx, "scratch");
a.Api("GetFileAttributesA");
a.Value(Dx, 0xffffffffu);
a.Compare(Ax, Dx);
a.Jump(std::string("resolveNext") + directory, 0x84);
a.Mask(Ax, 0x10);
a.Test(Ax);
a.Jump("resolvePath", 0x84);
a.Mark(std::string("resolveNext") + directory);
}
a.Text("missingModule");
a.Mov(Cx, Bx);
a.Call("write");
a.Text("moduleImporter");
a.Address(Cx, Si, 64);
a.Call("write");
a.Text("chain");
a.Mov(Cx, Si);
a.Call("chain");
a.Text("arrow");
a.Mov(Cx, Bx);
a.Call("write");
a.Text("newline");
a.Jump("terminate");
a.Mark("resolveContract");
a.Mov(Cx, Bx);
a.Value(Dx, 0);
a.Value(R8, 0x800);
a.Api("LoadLibraryExA");
a.Test(Ax);
a.Jump("io", 0x84);
a.Mark("resolveLoaded");
a.Mov(Cx, Ax);
a.Data(Dx, "scratch");
a.Value(R8, PathCapacity);
a.Api("GetModuleFileNameA");
a.Test(Ax);
a.Jump("io", 0x84);
a.CompareValue(Ax, PathCapacity);
a.Jump("limit", 0x83);
a.Mark("resolvePath");
a.Data(Cx, "scratch");
a.Call("register");
a.End();
a.Begin("parent");
a.Global(Ax, "root");
a.Compare(Cx, Ax);
a.Jump("parentDone", 0x84);
a.Load(Ax, Cx, 40);
a.Test(Ax);
a.Jump("parentDone", 0x85);
a.Store(Cx, 40, Dx);
a.Mark("parentDone");
a.End();
a.Begin("chain");
a.Mov(Bx, Cx);
a.Load(Cx, Bx, 40);
a.Test(Cx);
a.Jump("chainRoot", 0x84);
a.Call("chain");
a.Jump("chainNode");
a.Mark("chainRoot");
a.Global(Cx, "executable");
a.Call("write");
a.Mark("chainNode");
a.Text("arrow");
a.Address(Cx, Bx, 64);
a.Call("write");
a.End();
a.Begin("report");
a.Mov(Bx, Cx);
a.Mov(Si, Dx);
a.Mov(Di, R8);
a.Text("importer");
a.Address(Cx, Bx, 64);
a.Call("write");
a.Text("provider");
a.Address(Cx, Si, 64);
a.Call("write");
a.Text("symbol");
a.CompareValue(Di, 0xffff);
a.Jump("reportName", 0x87);
a.Text("ordinal");
a.Mov(Ax, Di);
a.Data(Di, "digits");
a.AddValue(Di, 31);
a.Value(Dx, 0);
a.StoreByte(Di, Dx);
a.Value(R8, 10);
a.Mark("reportDigit");
code.Emit({0x31, 0xd2, 0x49, 0xf7, 0xf0});
a.AddValue(Dx, '0');
a.SubValue(Di, 1);
a.StoreByte(Di, Dx);
a.Test(Ax);
a.Jump("reportDigit", 0x85);
a.Mark("reportName");
a.Mov(Cx, Di);
a.Call("write");
a.Text("chain");
a.Mov(Cx, Bx);
a.Call("chain");
a.Text("arrow");
a.Address(Cx, Si, 64);
a.Call("write");
a.Text("newline");
a.Jump("terminate");
a.End();
a.Begin("export");
a.Mov(Bx, Cx);
a.Mov(Si, Dx);
a.Mov(Di, R9);
a.Store(Sp, 56, R8);
a.Test(R8);
a.Jump("limit", 0x84);
a.Load(Cx, Bx, 8);
a.Test(Cx);
a.Jump("exportStatic", 0x84);
a.Mov(Dx, Si);
a.Api("GetProcAddress");
a.Test(Ax);
a.Jump("exportMissing", 0x84);
a.Jump("exportDone");
a.Mark("exportStatic");
a.Load(Ax, Bx);
a.Load(Dx, Ax, 0x3c, 4);
a.Add(Ax, Dx);
a.Load(R12, Ax, 136, 4);
a.Load(R13, Ax, 140, 4);
a.Test(R12);
a.Jump("exportMissing", 0x84);
a.Mov(Cx, Bx);
a.Mov(Dx, R12);
a.Value(R8, 40);
a.Call("bounds");
a.Mov(Bp, Ax);
a.CompareValue(Si, 0xffff);
a.Jump("exportNames", 0x87);
a.Load(Ax, Bp, 16, 4);
a.Compare(Si, Ax);
a.Jump("exportMissing", 0x82);
a.Mov(R14, Si);
a.Sub(R14, Ax);
a.Jump("exportAddress");
a.Mark("exportNames");
a.Value(R14, 0);
a.Load(R15, Bp, 24, 4);
a.Mark("exportNameLoop");
a.Compare(R14, R15);
a.Jump("exportMissing", 0x83);
a.Load(Dx, Bp, 32, 4);
a.Mov(Ax, R14);
a.Add(Ax, Ax);
a.Add(Ax, Ax);
a.Add(Dx, Ax);
a.Mov(Cx, Bx);
a.Value(R8, 4);
a.Call("bounds");
a.Load(Dx, Ax, 0, 4);
a.Mov(Cx, Bx);
a.Call("string");
a.Mov(Cx, Ax);
a.Mov(Dx, Si);
a.Api("lstrcmpA");
a.Test(Ax);
a.Jump("exportNameFound", 0x84);
a.AddValue(R14, 1);
a.Jump("exportNameLoop");
a.Mark("exportNameFound");
a.Load(Dx, Bp, 36, 4);
a.Add(Dx, R14);
a.Add(Dx, R14);
a.Mov(Cx, Bx);
a.Value(R8, 2);
a.Call("bounds");
a.Load(R14, Ax, 0, 2);
a.Mark("exportAddress");
a.Load(Ax, Bp, 20, 4);
a.Compare(R14, Ax);
a.Jump("exportMissing", 0x83);
a.Load(Dx, Bp, 28, 4);
a.Add(R14, R14);
a.Add(R14, R14);
a.Add(Dx, R14);
a.Mov(Cx, Bx);
a.Value(R8, 4);
a.Call("bounds");
a.Load(R14, Ax, 0, 4);
a.Test(R14);
a.Jump("exportMissing", 0x84);
a.Compare(R14, R12);
a.Jump("exportDone", 0x82);
a.Mov(Ax, R14);
a.Sub(Ax, R12);
a.Compare(Ax, R13);
a.Jump("exportDone", 0x83);
a.Mov(Cx, Bx);
a.Mov(Dx, R14);
a.Call("string");
a.Mov(R14, Ax);
a.Data(Cx, "forwarder");
a.Mov(Dx, Ax);
a.Call("copy");
a.Mov(Cx, R14);
a.Api("lstrlenA");
a.Mov(R15, Ax);
a.Mark("forwardSeparator");
a.Test(R15);
a.Jump("invalid", 0x84);
a.SubValue(R15, 1);
a.Mov(Ax, R14);
a.Add(Ax, R15);
a.Load(Dx, Ax, 0, 1);
a.CompareValue(Dx, '.');
a.Jump("forwardSeparator", 0x85);
a.AddValue(Ax, 1);
a.Mov(R14, Ax);
a.Data(Ax, "forwarder");
a.Add(Ax, R15);
a.Value(Dx, 0);
a.StoreByte(Ax, Dx);
a.CompareValue(R15, PathCapacity - 5);
a.Jump("limit", 0x83);
a.Data(Cx, "forwarder");
a.Data(Dx, "extension");
a.Api("lstrcatA");
a.Data(Cx, "forwarder");
a.Mov(Dx, Bx);
a.Call("resolve");
a.Mov(R15, Ax);
a.Mov(Cx, Ax);
a.Mov(Dx, Bx);
a.Call("parent");
a.Load(Ax, R14, 0, 1);
a.CompareValue(Ax, '#');
a.Jump("forwardCall", 0x85);
a.AddValue(R14, 1);
a.Value(R12, 0);
a.Mark("forwardOrdinal");
a.Load(Ax, R14, 0, 1);
a.Test(Ax);
a.Jump("forwardOrdinalDone", 0x84);
a.SubValue(Ax, '0');
a.CompareValue(Ax, 9);
a.Jump("invalid", 0x87);
a.Mov(Dx, R12);
a.Add(R12, R12);
a.Add(Dx, Dx);
a.Add(Dx, Dx);
a.Add(Dx, Dx);
a.Add(R12, Dx);
a.Add(R12, Ax);
a.CompareValue(R12, 0xffff);
a.Jump("invalid", 0x87);
a.AddValue(R14, 1);
a.Jump("forwardOrdinal");
a.Mark("forwardOrdinalDone");
a.Test(R12);
a.Jump("invalid", 0x84);
a.Mov(R14, R12);
a.Mark("forwardCall");
a.Mov(Cx, R15);
a.Mov(Dx, R14);
a.Load(R8, Sp, 56);
a.SubValue(R8, 1);
a.Mov(R9, Bx);
a.Call("export");
a.Jump("exportDone");
a.Mark("exportMissing");
a.Mov(Cx, Di);
a.Mov(Dx, Bx);
a.Mov(R8, Si);
a.Call("report");
a.Mark("exportDone");
a.End();
a.Begin("walk");
a.Mov(Bx, Cx);
a.Load(Ax, Bx, 48);
a.Test(Ax);
a.Jump("walkDone", 0x85);
a.Value(Ax, 1);
a.Store(Bx, 48, Ax);
a.Load(Ax, Bx, 8);
a.Test(Ax);
a.Jump("walkDone", 0x85);
a.Load(Bp, Bx, 24);
a.Test(Bp);
a.Jump("walkDone", 0x84);
a.Mark("descriptor");
a.Mov(Ax, Bp);
a.AddValue(Ax, 20);
a.Load(Dx, Bx, 32);
a.Compare(Ax, Dx);
a.Jump("invalid", 0x87);
a.Mov(Cx, Bx);
a.Mov(Dx, Bp);
a.Value(R8, 20);
a.Call("bounds");
a.Load(Si, Ax, 0, 4);
a.Load(Dx, Ax, 12, 4);
a.Test(Dx);
a.Jump("walkDone", 0x84);
a.Test(Si);
a.Jump("invalid", 0x84);
a.Mov(Cx, Bx);
a.Call("string");
a.Mov(Cx, Ax);
a.Mov(Dx, Bx);
a.Call("resolve");
a.Mov(Di, Ax);
a.Mov(Cx, Di);
a.Mov(Dx, Bx);
a.Call("parent");
a.Mark("thunk");
a.Mov(Cx, Bx);
a.Mov(Dx, Si);
a.Value(R8, 8);
a.Call("bounds");
a.Load(R12, Ax);
a.Test(R12);
a.Jump("walkProvider", 0x84);
a.Jump("thunkOrdinal", 0x88);
a.Mov(Dx, R12);
a.AddValue(Dx, 2);
a.Mov(Cx, Bx);
a.Call("string");
a.Mov(R12, Ax);
a.Jump("thunkCheck");
a.Mark("thunkOrdinal");
a.Mask(R12, 0xffff);
a.Mark("thunkCheck");
a.Mov(Cx, Di);
a.Mov(Dx, R12);
a.Value(R8, 64);
a.Mov(R9, Bx);
a.Call("export");
a.AddValue(Si, 8);
a.Jump("thunk");
a.Mark("walkProvider");
a.Mov(Cx, Di);
a.Call("walk");
a.AddValue(Bp, 20);
a.Jump("descriptor");
a.Mark("walkDone");
a.End();
a.Begin("diagnose");
a.Mov(Bx, Cx);
a.Mov(Si, Dx);
a.Save("executable", Dx);
a.Data(Cx, "rootDirectory");
a.Mov(Dx, Bx);
a.Call("directory");
a.Data(Cx, "executableDirectory");
a.Mov(Dx, Si);
a.Call("directory");
a.Data(Cx, "systemDirectory");
a.Value(Dx, PathCapacity - 1);
a.Api("GetSystemDirectoryA");
a.Test(Ax);
a.Jump("io", 0x84);
a.CompareValue(Ax, PathCapacity - 1);
a.Jump("limit", 0x83);
a.Data(Dx, "systemDirectory");
a.Add(Dx, Ax);
a.Value(Ax, '\\');
a.StoreByte(Dx, Ax);
a.AddValue(Dx, 1);
a.Value(Ax, 0);
a.StoreByte(Dx, Ax);
a.Value(Cx, 0);
a.Value(Dx, NodeSize * NodeLimit);
a.Value(R8, 0x3000);
a.Value(R9, 4);
a.Api("VirtualAlloc");
a.Test(Ax);
a.Jump("io", 0x84);
a.Save("nodes", Ax);
a.Mov(Cx, Bx);
a.Call("register");
a.Save("root", Ax);
a.Mov(Cx, Ax);
a.Call("walk");
a.Text("unresolved");
a.Jump("terminate");
a.End();
a.Finish();
return a.Result(unwindRva);
}
}

View File

@ -1,5 +1,6 @@
#include <elfpatcher/windows/WindowsEntryStubBuilder.hpp>
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
#include <elfpatcher/windows/WindowsDependencyStubBuilder.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
@ -52,7 +53,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
const auto programPath = reserve(PathCapacity);
const auto modulePath = reserve(PathCapacity);
const auto handles = reserve(libraries.size() * 8);
const auto functionTable = reserve(12);
const auto functionTable = reserve(12 * 32);
const auto unwindRva = CheckedRva(dataRva + data.size());
data.insert(data.end(), {1, 10, 6, 0, 10, 0xb2, 6, 0xc0, 4, 0x70, 3, 0x60, 2, 0x50, 1, 0x30});
@ -82,7 +83,6 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
const auto searched = addString("Searched libraries:\n");
const auto indent = addString(" ");
const auto enteringElf = addString("Transferring control to ELF entry point\n");
std::vector<std::uint32_t> resolvedPaths;
for (std::size_t index = 0; index < libraries.size(); ++index)
resolvedPaths.push_back(reserve(PathCapacity));
@ -97,6 +97,8 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
if (data.size() <= diagnosticsOffset)
throw Domain::RelinkerException("Empty startup diagnostics");
const WindowsDependencyStubBuilder dependencyBuilder(result.Data);
std::vector<std::size_t> dependencyCalls;
result.Code.Rva = AlignRva(dataRva + data.size());
WindowsStubEmitter code(result.Code.Rva);
@ -245,6 +247,9 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
captureLastError();
writeString(loadFailed, true);
writeLastError();
code.Rip({0x48, 0x8d, 0x0d}, resolvedPaths[index]);
code.Rip({0x48, 0x8d, 0x15}, programPath);
dependencyCalls.push_back(code.Branch({0xe8}));
raise(0xc0000135u);
code.PatchBranch(loadSucceeded, code.GetRva());
code.Rip({0x48, 0x89, 0x05}, CheckedRva(handles + index * 8));
@ -327,7 +332,16 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
Io::WriteU32(data, functionTable - dataRva, result.Code.Rva);
Io::WriteU32(data, functionTable - dataRva + 4, functionEnd);
Io::WriteU32(data, functionTable - dataRva + 8, unwindRva);
result.ExceptionDirectory = {functionTable, 12};
const auto dependency = dependencyBuilder.Build(code, nativeImports);
for (const auto offset : dependencyCalls)
code.PatchBranch(offset, dependency.EntryRva);
if (dependency.Functions.size() >= 32)
throw Domain::RelinkerException("Too many dependency diagnostic routines");
for (std::size_t index = 0; index < dependency.Functions.size(); ++index) {
for (std::size_t field = 0; field < 3; ++field)
Io::WriteU32(data, functionTable - dataRva + (index + 1) * 12 + field * 4, dependency.Functions[index][field]);
}
result.ExceptionDirectory = {functionTable, CheckedRva((dependency.Functions.size() + 1) * 12)};
result.Code.Data = code.TakeBytes();
return result;
}

View File

@ -5,7 +5,7 @@
namespace Elfpatcher::Windows {
WindowsImports WindowsImportBuilder::Build(const std::uint32_t sectionRva) const {
const std::vector<std::string> names = {"ExitProcess", "FormatMessageA", "GetLastError", "GetModuleFileNameA", "GetProcAddress", "GetStdHandle", "LoadLibraryExA", "RaiseException", "WriteFile", "lstrlenA"};
const std::vector<std::string> names = {"ExitProcess", "FormatMessageA", "GetFileAttributesA", "GetLastError", "GetModuleFileNameA", "GetModuleHandleA", "GetProcAddress", "GetStdHandle", "GetSystemDirectoryA", "LoadLibraryExA", "RaiseException", "VirtualAlloc", "WriteFile", "lstrcatA", "lstrcmpA", "lstrcmpiA", "lstrcpyA", "lstrlenA"};
WindowsImports result{{".idata", sectionRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(40)}, {sectionRva, 40}, {}, {}};
auto& bytes = result.Section.Data;
const auto lookupOffset = bytes.size();

View File

@ -0,0 +1,209 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <elfpatcher/windows/WindowsDependencyStubBuilder.hpp>
#include <elfpatcher/windows/WindowsPeWriter.hpp>
#include <io/BufferUtils.hpp>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <stdexcept>
namespace {
namespace Fs = std::filesystem;
using namespace Elfpatcher::Windows;
void writeFile(const Fs::path& path, const std::vector<std::uint8_t>& bytes) {
std::ofstream stream(path, std::ios::binary);
if (!stream.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size())))
throw std::runtime_error("Cannot write diagnostic fixture");
}
void createImage(const Fs::path& path, const std::vector<std::pair<std::string, std::string>>& imports, const std::string& exported, const std::string& forwarded = {}) {
PeSection section{".fixture", LoadRva, SectionRead | SectionExecute | 0x20u, std::vector<std::uint8_t>(4096)};
auto& bytes = section.Data;
const auto put = [&](const std::size_t offset, const std::uint64_t value, const std::size_t size = 4) {
for (std::size_t index = 0; index < size; ++index)
bytes.at(offset + index) = static_cast<std::uint8_t>(value >> (index * 8));
};
std::size_t cursor = 0x600;
const auto string = [&](const std::string& text) {
const auto start = cursor;
for (const auto character : text)
bytes.at(cursor++) = static_cast<std::uint8_t>(character);
bytes.at(cursor++) = 0;
return LoadRva + start;
};
std::array<PeDirectory, 16> directories{};
if (!exported.empty()) {
directories[0] = {LoadRva + 0x200, 0x100};
put(0x210, 7);
put(0x214, 1);
put(0x218, 1);
put(0x21c, LoadRva + 0x240);
put(0x220, LoadRva + 0x248);
put(0x224, LoadRva + 0x250);
put(0x240, LoadRva + (forwarded.empty() ? 0x800 : 0x260));
put(0x248, string(exported));
for (std::size_t index = 0; index < forwarded.size(); ++index)
bytes.at(0x260 + index) = static_cast<std::uint8_t>(forwarded[index]);
}
if (!imports.empty()) {
directories[1] = {LoadRva + 0x300, CheckedRva((imports.size() + 1) * 20)};
for (std::size_t index = 0; index < imports.size(); ++index) {
const auto& [library, symbol] = imports[index];
const auto thunk = 0x400 + index * 16;
put(0x300 + index * 20, LoadRva + thunk);
put(0x300 + index * 20 + 12, string(library));
put(0x300 + index * 20 + 16, LoadRva + thunk);
if (symbol.starts_with('#')) {
put(thunk, (std::uint64_t{1} << 63) | std::stoull(symbol.substr(1)), 8);
} else {
cursor += 2;
put(thunk, string(symbol) - 2, 8);
}
}
}
bytes[0x800] = 0xc3;
auto file = WindowsPeWriter().Write({section}, LoadRva + 0x800, directories);
Io::WriteU16(file, 0x96, 0x2022);
Io::WriteU32(file, 0xa8, 0);
writeFile(path, file);
}
void createRunner(const Fs::path& path, const Fs::path& root) {
auto imports = WindowsImportBuilder().Build(LoadRva);
PeSection data{".startup", AlignRva(LoadRva + imports.Section.Data.size()), SectionRead | SectionWrite | 0x40u, {}};
const auto libraryPath = data.Rva;
Io::AppendString(data.Data, root.string());
const auto executablePath = CheckedRva(data.Rva + data.Data.size());
Io::AppendString(data.Data, path.string());
Io::AlignBuffer(data.Data, 4);
const auto table = CheckedRva(data.Rva + data.Data.size());
data.Data.resize(data.Data.size() + 32 * 12);
const auto unwind = CheckedRva(data.Rva + data.Data.size());
data.Data.insert(data.Data.end(), {1, 4, 1, 0, 4, 0x42, 0, 0});
WindowsDependencyStubBuilder builder(data);
const auto codeRva = AlignRva(data.Rva + data.Data.size());
WindowsStubEmitter code(codeRva);
code.Emit({0x48, 0x83, 0xec, 0x28});
code.Rip({0x48, 0x8d, 0x0d}, libraryPath);
code.Rip({0x48, 0x8d, 0x15}, executablePath);
const auto call = code.Branch({0xe8});
code.Emit({0x48, 0x83, 0xc4, 0x28, 0xc3});
const auto wrapperEnd = code.GetRva();
const auto diagnostic = builder.Build(code, imports);
code.PatchBranch(call, diagnostic.EntryRva);
Io::WriteU32(data.Data, table - data.Rva, codeRva);
Io::WriteU32(data.Data, table - data.Rva + 4, wrapperEnd);
Io::WriteU32(data.Data, table - data.Rva + 8, unwind);
for (std::size_t index = 0; index < diagnostic.Functions.size(); ++index) {
for (std::size_t field = 0; field < 3; ++field)
Io::WriteU32(data.Data, table - data.Rva + (index + 1) * 12 + field * 4, diagnostic.Functions[index][field]);
}
std::array<PeDirectory, 16> directories{};
directories[1] = imports.Directory;
directories[3] = {table, CheckedRva((diagnostic.Functions.size() + 1) * 12)};
directories[12] = imports.AddressTable;
PeSection executable{".entry", codeRva, SectionRead | SectionExecute | 0x20u, code.TakeBytes()};
writeFile(path, WindowsPeWriter().Write({imports.Section, data, executable}, codeRva, directories));
}
void expectDiagnostic(const Fs::path& runner, const std::vector<std::string>& expected) {
SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
HANDLE input = nullptr;
HANDLE output = nullptr;
if (!CreatePipe(&input, &output, &security, 0) || !SetHandleInformation(input, HANDLE_FLAG_INHERIT, 0))
throw std::runtime_error("Cannot create diagnostic test pipe");
STARTUPINFOA startup{};
startup.cb = sizeof(startup);
startup.dwFlags = STARTF_USESTDHANDLES;
startup.hStdOutput = output;
startup.hStdError = output;
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
PROCESS_INFORMATION process{};
const auto filename = runner.string();
if (!CreateProcessA(filename.c_str(), nullptr, nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, runner.parent_path().string().c_str(), &startup, &process))
throw std::runtime_error("Cannot start diagnostic test: " + std::to_string(GetLastError()));
CloseHandle(output);
if (WaitForSingleObject(process.hProcess, 20000) != WAIT_OBJECT_0) {
TerminateProcess(process.hProcess, 1);
throw std::runtime_error("Diagnostic test timed out");
}
DWORD status = 0;
if (!GetExitCodeProcess(process.hProcess, &status))
throw std::runtime_error("Cannot read diagnostic test exit code");
std::string message;
char buffer[4096];
DWORD size = 0;
while (ReadFile(input, buffer, sizeof(buffer), &size, nullptr) && size != 0)
message.append(buffer, size);
CloseHandle(input);
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
if (status != 0xc0000135u)
throw std::runtime_error("Wrong diagnostic exit code: " + std::to_string(status) + "\n" + message);
for (const auto& text : expected) {
if (message.find(text) == std::string::npos)
throw std::runtime_error("Missing diagnostic field: " + text + "\nActual: " + message);
}
}
}
int main() {
try {
std::vector<char> filename(32768);
const auto size = GetModuleFileNameA(nullptr, filename.data(), static_cast<DWORD>(filename.size()));
if (size == 0 || size >= filename.size())
throw std::runtime_error("Cannot locate diagnostic tests");
const auto directory = Fs::path(filename.data()).parent_path() / "windows-diagnostic-fixtures";
Fs::create_directories(directory);
const auto root = directory / "diagnostic-root.prx";
const auto middle = directory / "diagnostic-middle.dll";
const auto leaf = directory / "diagnostic-leaf.dll";
const auto runner = directory / "diagnostic-runner.exe";
createRunner(runner, root);
createImage(root, {{middle.filename().string(), "Middle"}}, {});
createImage(middle, {{leaf.filename().string(), "Missing"}}, "Middle");
createImage(leaf, {}, "Present");
expectDiagnostic(runner, {"Importer: " + middle.string(), "Provider: " + leaf.string(), "Symbol: Missing", runner.string() + " -> " + root.string() + " -> " + middle.string() + " -> " + leaf.string()});
createImage(middle, {{leaf.filename().string(), "#8"}}, "Middle");
expectDiagnostic(runner, {"Symbol: #8"});
createImage(middle, {{leaf.filename().string(), "#7"}}, "Middle");
expectDiagnostic(runner, {"Dependency tables contain no missing imports"});
createImage(middle, {}, "Middle", "diagnostic-leaf.Missing");
expectDiagnostic(runner, {"Symbol: Missing", "Importer: " + middle.string()});
createImage(middle, {}, "Middle", "diagnostic-leaf.#7");
expectDiagnostic(runner, {"Dependency tables contain no missing imports"});
createImage(leaf, {}, "Present", "diagnostic-middle.Middle");
expectDiagnostic(runner, {"capacity exceeded"});
createImage(middle, {{leaf.filename().string(), "Present"}}, "Middle");
createImage(leaf, {{middle.filename().string(), "Middle"}}, "Present");
expectDiagnostic(runner, {"Dependency tables contain no missing imports"});
createImage(leaf, {{middle.filename().string(), "MissingInCycle"}}, "Present");
expectDiagnostic(runner, {"Symbol: MissingInCycle", "Importer: " + leaf.string()});
createImage(root, {{"diagnostic-absent.dll", "Missing"}}, {});
expectDiagnostic(runner, {"dependency module not found", "Importer: " + root.string()});
createImage(root, {{"KERNEL32.dll", "CreateRemoteThreadEx"}}, {});
expectDiagnostic(runner, {"Dependency tables contain no missing imports"});
createImage(root, {{"KERNEL32.dll", "RelinkerDiagnosticsMissingExport"}}, {});
expectDiagnostic(runner, {"Symbol: RelinkerDiagnosticsMissingExport"});
{
std::fstream corrupt(root, std::ios::binary | std::ios::in | std::ios::out);
const std::array<char, 4> invalidRva{static_cast<char>(0xf0), static_cast<char>(0xff), static_cast<char>(0xff), static_cast<char>(0xff)};
corrupt.seekp(LoadRva + 0x300);
if (!corrupt.write(invalidRva.data(), invalidRva.size()))
throw std::runtime_error("Cannot corrupt diagnostic fixture");
}
expectDiagnostic(runner, {"invalid or unsupported PE dependency metadata"});
writeFile(root, {0, 1, 2});
expectDiagnostic(runner, {"Windows API failed"});
std::cout << "Windows dependency machine-code tests passed\n";
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
return 0;
}