fix(relinker): merge zero-filled Mach-O segments past the dyld segment limit

Backing only the pages a pointer touches splits a zero-filled run into
one section per backed block. Each becomes a Mach-O segment, and dyld's
bind and rebase opcodes address at most 16 segments, so a module with
pointer slots on many separate zero-filled pages converted on main but
failed with --macos. When the segments would pass that limit, adjacent
sections with the same protection are merged again, smallest gap first.

Reported by jotitadev in the review.
This commit is contained in:
Werner committed 2026-10-19 05:55:26 +00:00
1 parent d15c38b4de
commit 9babdd83fb
2 files changed
+57

No files matched your search

@@ -154,6 +154,33 @@ void PadTo(std::vector<std::uint8_t>& out, const std::size_t alignment) {
while (out.size() % alignment != 0) out.push_back(0);
}
void FitDyldSegmentLimit(std::vector<Windows::PeSection>& sections, const std::size_t limit) {
while (sections.size() > limit) {
auto best = sections.end();
std::uint64_t bestGap = 0;
for (auto section = sections.begin(); std::next(section) != sections.end(); ++section) {
const auto next = std::next(section);
if (section->Name == ".amdstub" || next->Name == ".amdstub" || section->Characteristics != next->Characteristics ||
section->Rva + section->MemorySize() != next->Rva)
continue;
const auto gap = next->Rva - section->Rva - section->Data.size();
if (best == sections.end() || gap < bestGap) {
best = section;
bestGap = gap;
}
}
if (best == sections.end()) return;
const auto next = std::next(best);
const auto memorySize = next->Rva + next->MemorySize() - best->Rva;
if (!next->Data.empty()) {
best->Data.resize(next->Rva - best->Rva);
best->Data.insert(best->Data.end(), next->Data.begin(), next->Data.end());
}
best->VirtualSize = static_cast<std::uint32_t>(memorySize);
sections.erase(next);
}
}
std::pair<std::uint8_t, std::uint64_t> Locate(const std::vector<Segment>& segments, const std::uint64_t rva, const std::uint64_t size) {
for (std::size_t index = 0; index < segments.size(); ++index) {
const auto& segment = segments[index];
@@ -420,6 +447,7 @@ std::vector<std::uint8_t> WriteMacOsImage(MacOsImageInput& input) {
auto stubRva = Windows::AlignRva(image.GetEndRva());
Windows::WindowsTrampolineBuilder().Build(*input.Trampolines, image, sections, stubRva);
}
FitDyldSegmentLimit(sections, 16 - guestSegments - 2);
std::uint64_t guestEnd = image.GetEndRva();
for (auto& section : sections) {
std::uint32_t protection = 0;
@@ -66,6 +66,7 @@ def main():
executed = subprocess.run([str(output)], capture_output=True, text=True, timeout=20)
assert executed.returncode == 42, f"exit code {executed.returncode:#x}"
check_relocated_zero_fill(relinker, work)
check_scattered_relocations_macos(relinker, work)
print("Windows zero-filled memory tests passed")
@@ -96,5 +97,33 @@ def check_relocated_zero_fill(relinker, work):
assert executed.returncode == 42, f"exit code {executed.returncode:#x}"
def check_scattered_relocations_macos(relinker, work):
image = fixture()
file_size = struct.unpack_from("<Q", image, 64 + 32)[0]
memory_size = file_size + (1 << 20)
struct.pack_into("<Q", image, 64 + 40, memory_size)
count = 40
for index in range(count):
struct.pack_into("<QQq", image, 0x700 + index * 24, file_size + index * 0x2000, 8, 0x210)
tags = image[0x400:0x500]
for offset in range(0, len(tags), 16):
if struct.unpack_from("<q", tags, offset)[0] == 8:
struct.pack_into("<Q", image, 0x400 + offset + 8, count * 24)
source = work / "scattered.elf"
source.write_bytes(image)
output = work / "scattered.macho"
result = subprocess.run([str(relinker), "--skip-sce-module", "--macos", str(source), str(output)],
capture_output=True, text=True, timeout=20)
assert result.returncode == 0, (result.stdout, result.stderr)
macho = output.read_bytes()
commands = struct.unpack_from("<I", macho, 16)[0]
offset, segments = 32, 0
for _ in range(commands):
command, size = struct.unpack_from("<II", macho, offset)
segments += command == 0x19
offset += size
assert segments <= 16, f"{segments} Mach-O segments"
if __name__ == "__main__":
main()