mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-06 13:30:00 +00:00
fix(relinker): leave zero-filled PT_LOAD memory out of Windows files
WindowsPeWriter stored every PE section with SizeOfRawData equal to its virtual size, so the zero-filled tail of a PT_LOAD segment (p_memsz above p_filesz, the .bss) was written to the executable byte for byte. A segment with 256 MiB of .bss produced a 269 MB .exe, while the Linux output of the same image is under 5 KB. Each section is now stored up to its last non-zero byte, aligned to the file alignment, and VirtualSize keeps the full size. The PE loader zero-fills the part of a section beyond SizeOfRawData (Microsoft PE format, section table: SizeOfRawData). A section with no non-zero byte gets no raw data and a zero PointerToRawData. windows_bss relinks an image with 64 MiB of .bss, checks the section table and the mapped image, and on Windows runs the executable, which reads the last word of the .bss.
This commit is contained in:
1 parent
f6af6d11d3
commit
6158444f71
3 files changed
+81
-4
No files matched your search
@@ -253,6 +253,8 @@ if(BUILD_TESTING)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_working_directory.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME windows_address_space COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_address_space.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME windows_bss COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_bss.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME windows_icon COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_icon.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_intel_trampolines COMMAND ${Python3_EXECUTABLE}
|
||||
|
||||
@@ -44,8 +44,9 @@ std::vector<std::uint8_t> WindowsPeWriter::Write(const std::vector<PeSection>& s
|
||||
const auto& section = sections[index];
|
||||
if (section.Name.empty() || section.Name.size() > 8 || section.Data.empty() || section.Rva != endRva)
|
||||
throw Domain::RelinkerException("Invalid PE section layout: " + section.Name, section.Rva);
|
||||
const auto rawSize = Io::AlignUp(CheckedRva(section.Data.size()), FileAlignment);
|
||||
const auto rawOffset = CheckedRva(result.size());
|
||||
const auto initialized = static_cast<std::size_t>(std::find_if(section.Data.rbegin(), section.Data.rend(), [](const std::uint8_t value) { return value != 0; }).base() - section.Data.begin());
|
||||
const auto rawSize = Io::AlignUp(CheckedRva(initialized), FileAlignment);
|
||||
const auto rawOffset = rawSize == 0 ? 0u : CheckedRva(result.size());
|
||||
const auto header = sectionTable + index * 40;
|
||||
std::copy(section.Name.begin(), section.Name.end(), result.begin() + static_cast<std::ptrdiff_t>(header));
|
||||
Io::WriteU32(result, header + 8, CheckedRva(section.Data.size()));
|
||||
@@ -61,8 +62,10 @@ std::vector<std::uint8_t> WindowsPeWriter::Write(const std::vector<PeSection>& s
|
||||
} else {
|
||||
dataSize = CheckedRva(static_cast<std::uint64_t>(dataSize) + rawSize);
|
||||
}
|
||||
result.insert(result.end(), section.Data.begin(), section.Data.end());
|
||||
result.resize(static_cast<std::size_t>(rawOffset) + rawSize);
|
||||
if (rawSize != 0) {
|
||||
result.insert(result.end(), section.Data.begin(), section.Data.begin() + static_cast<std::ptrdiff_t>(initialized));
|
||||
result.resize(static_cast<std::size_t>(rawOffset) + rawSize);
|
||||
}
|
||||
endRva = AlignRva(section.Rva + static_cast<std::uint64_t>(section.Data.size()));
|
||||
}
|
||||
if (!foundEntry)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Leave the zero-filled tail of PT_LOAD memory out of the Windows file and let the loader zero-fill it."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_optional_plt import fixture
|
||||
|
||||
BSS = 64 << 20
|
||||
FILE_ALIGNMENT = 0x200
|
||||
SECTION_ALIGNMENT = 0x1000
|
||||
LOAD_RVA = 0x10000
|
||||
|
||||
|
||||
def sections(pe):
|
||||
header = struct.unpack_from("<I", pe, 0x3c)[0]
|
||||
count = struct.unpack_from("<H", pe, header + 6)[0]
|
||||
optional = header + 24
|
||||
table = optional + struct.unpack_from("<H", pe, header + 20)[0]
|
||||
result = []
|
||||
for index in range(count):
|
||||
offset = table + index * 40
|
||||
name = pe[offset:offset + 8].rstrip(b"\0").decode()
|
||||
virtual_size, rva, raw_size, raw = struct.unpack_from("<IIII", pe, offset + 8)
|
||||
result.append((name, virtual_size, rva, raw_size, raw))
|
||||
return result, struct.unpack_from("<I", pe, optional + 56)[0]
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-windows-bss-") as directory:
|
||||
work = Path(directory)
|
||||
image = fixture()
|
||||
memory_size = struct.unpack_from("<Q", image, 64 + 40)[0] + BSS
|
||||
struct.pack_into("<Q", image, 64 + 40, memory_size)
|
||||
last = memory_size - 4
|
||||
image[0x210:0x21a] = b"\x8b\x05" + struct.pack("<i", last - 0x216) + b"\x83\xc0\x2a\xc3"
|
||||
source = work / "input.elf"
|
||||
source.write_bytes(image)
|
||||
output = work / "output.exe"
|
||||
result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
pe = output.read_bytes()
|
||||
assert len(pe) < BSS // 16, f"{len(pe)} byte executable for {BSS} bytes of zero-filled memory"
|
||||
table, image_size = sections(pe)
|
||||
first = next(section for section in table if section[2] == LOAD_RVA)
|
||||
name, virtual_size, rva, raw_size, raw = first
|
||||
assert virtual_size >= memory_size and rva + virtual_size <= image_size, first
|
||||
assert 0 < raw_size < SECTION_ALIGNMENT * 2, first
|
||||
for name, virtual_size, rva, raw_size, raw in table:
|
||||
assert raw_size % FILE_ALIGNMENT == 0 and raw % FILE_ALIGNMENT == 0, name
|
||||
assert raw_size <= -(-virtual_size // FILE_ALIGNMENT) * FILE_ALIGNMENT, name
|
||||
assert (raw_size == 0) == (raw == 0) and raw + raw_size <= len(pe), name
|
||||
mapped = bytearray(image_size)
|
||||
for name, virtual_size, rva, raw_size, raw in table:
|
||||
size = min(raw_size, virtual_size)
|
||||
mapped[rva:rva + size] = pe[raw:raw + size]
|
||||
loaded = mapped[LOAD_RVA:LOAD_RVA + memory_size]
|
||||
assert loaded[0x210:0x21a] == image[0x210:0x21a]
|
||||
assert not any(loaded[0x1000:])
|
||||
if os.name == "nt":
|
||||
executed = subprocess.run([str(output)], capture_output=True, text=True, timeout=20)
|
||||
assert executed.returncode == 42, f"exit code {executed.returncode:#x}"
|
||||
print("Windows zero-filled memory tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user