mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-05 13:20:11 +00:00
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:
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()
|
||||
Reference in new issue
Block a user