CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
220 lines
7.1 KiB
Python
220 lines
7.1 KiB
Python
"""
|
|
Zero-page optimization for PE/COFF binaries.
|
|
|
|
This module handles the removal of content from sections while maintaining
|
|
PE validity. The key operation is "zero-paging" - reducing SizeOfRawData
|
|
so that VirtualSize > SizeOfRawData, causing the loader to zero-fill.
|
|
|
|
The PE zero-page algorithm (simpler than ELF!):
|
|
1. Find page-aligned region within the section
|
|
2. Remove that content from the file
|
|
3. Update section's SizeOfRawData to remaining content size
|
|
4. Adjust PointerToRawData for all subsequent sections
|
|
5. VirtualSize and SizeOfImage stay unchanged (loader zero-fills)
|
|
|
|
Key advantages over ELF:
|
|
- No program header splitting (PE sections define their own virtual layout)
|
|
- PE naturally supports VirtualSize > SizeOfRawData
|
|
- No NOBITS section type needed - just reduce raw data
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
from .types import (
|
|
SectionHeader,
|
|
PAGE_SIZE,
|
|
round_up_to_page,
|
|
round_down_to_page,
|
|
round_up_to_alignment,
|
|
)
|
|
from .surgery import CoffSurgery, SectionInfo
|
|
|
|
|
|
@dataclass
|
|
class ZeroPageResult:
|
|
"""Result of zero-page optimization."""
|
|
|
|
success: bool
|
|
bytes_saved: int
|
|
pages_zeroed: int
|
|
error: str | None = None
|
|
|
|
|
|
def calculate_aligned_range(rva: int, size: int) -> tuple[int, int]:
|
|
"""Calculate the page-aligned range within a section.
|
|
|
|
Args:
|
|
rva: Section RVA (relative virtual address)
|
|
size: Section size
|
|
|
|
Returns:
|
|
Tuple of (aligned_rva, aligned_size) - the range of full pages
|
|
that can be zeroed. Returns (0, 0) if no full pages exist.
|
|
"""
|
|
end = rva + size
|
|
|
|
# Round up start to next page boundary
|
|
aligned_start = round_up_to_page(rva)
|
|
|
|
# Round down end to previous page boundary
|
|
aligned_end = round_down_to_page(end)
|
|
|
|
if aligned_start >= aligned_end:
|
|
# No full pages to zero
|
|
return (0, 0)
|
|
|
|
return (aligned_start, aligned_end - aligned_start)
|
|
|
|
|
|
def conservative_zero_page(
|
|
surgery: CoffSurgery,
|
|
section_name: str,
|
|
) -> ZeroPageResult:
|
|
"""Apply conservative zero-page optimization to a section.
|
|
|
|
This removes page-aligned content from the file while maintaining
|
|
PE validity. Only full pages are removed; partial pages are preserved.
|
|
|
|
Args:
|
|
surgery: CoffSurgery instance to operate on
|
|
section_name: Section to zero-page
|
|
|
|
Returns:
|
|
ZeroPageResult with operation outcome
|
|
"""
|
|
# Find the target section
|
|
section = surgery.find_section(section_name)
|
|
if section is None:
|
|
return ZeroPageResult(
|
|
success=False,
|
|
bytes_saved=0,
|
|
pages_zeroed=0,
|
|
error=f"Section '{section_name}' not found",
|
|
)
|
|
|
|
# Check if already zero-paged (VirtualSize > SizeOfRawData significantly)
|
|
if section.raw_size == 0:
|
|
return ZeroPageResult(
|
|
success=True,
|
|
bytes_saved=0,
|
|
pages_zeroed=0,
|
|
error=f"Section '{section_name}' already has zero raw data",
|
|
)
|
|
|
|
section_rva = section.rva
|
|
section_vsize = section.virtual_size
|
|
section_offset = section.file_offset
|
|
|
|
# Calculate aligned range
|
|
aligned_rva, aligned_size = calculate_aligned_range(section_rva, section_vsize)
|
|
|
|
if aligned_size == 0:
|
|
# Section is too small or misaligned - no optimization possible
|
|
return ZeroPageResult(
|
|
success=True,
|
|
bytes_saved=0,
|
|
pages_zeroed=0,
|
|
error="Section too small or misaligned - no full pages to zero",
|
|
)
|
|
|
|
# Calculate file offsets
|
|
offset_within_section = aligned_rva - section_rva
|
|
aligned_file_offset = section_offset + offset_within_section
|
|
pages_to_zero = aligned_size // PAGE_SIZE
|
|
|
|
# Calculate prefix and suffix sizes
|
|
prefix_size = offset_within_section # Bytes before aligned region
|
|
suffix_size = section_vsize - offset_within_section - aligned_size
|
|
|
|
# Defensive check: suffix should never be negative given our calculations
|
|
assert suffix_size >= 0, (
|
|
f"Internal error: negative suffix_size={suffix_size} "
|
|
f"(vsize={section_vsize}, offset={offset_within_section}, aligned={aligned_size})"
|
|
)
|
|
|
|
# Remove the aligned bytes from file
|
|
old_size = len(surgery.data)
|
|
del surgery.data[aligned_file_offset : aligned_file_offset + aligned_size]
|
|
bytes_removed = old_size - len(surgery.data)
|
|
|
|
# Update the target section header
|
|
# New SizeOfRawData = prefix + suffix, rounded to FileAlignment
|
|
new_raw_content_size = prefix_size + suffix_size
|
|
new_raw_size = round_up_to_alignment(new_raw_content_size, surgery.file_alignment)
|
|
|
|
# If new_raw_size is 0, we need to handle edge case
|
|
if new_raw_content_size == 0:
|
|
new_raw_size = 0
|
|
|
|
old_shdr = section.header
|
|
new_shdr = SectionHeader(
|
|
Name=old_shdr.Name,
|
|
VirtualSize=old_shdr.VirtualSize, # Unchanged - virtual layout same
|
|
VirtualAddress=old_shdr.VirtualAddress, # Unchanged
|
|
SizeOfRawData=new_raw_size, # Reduced
|
|
PointerToRawData=old_shdr.PointerToRawData, # Same start offset
|
|
PointerToRelocations=old_shdr.PointerToRelocations,
|
|
PointerToLinenumbers=old_shdr.PointerToLinenumbers,
|
|
NumberOfRelocations=old_shdr.NumberOfRelocations,
|
|
NumberOfLinenumbers=old_shdr.NumberOfLinenumbers,
|
|
Characteristics=old_shdr.Characteristics,
|
|
)
|
|
surgery.update_section_header(section.index, new_shdr)
|
|
|
|
# Update subsequent sections' PointerToRawData
|
|
for sect in surgery.iter_sections():
|
|
if sect.file_offset > section_offset:
|
|
old_offset = sect.file_offset
|
|
new_offset = old_offset - aligned_size
|
|
|
|
old_shdr = sect.header
|
|
new_shdr = SectionHeader(
|
|
Name=old_shdr.Name,
|
|
VirtualSize=old_shdr.VirtualSize,
|
|
VirtualAddress=old_shdr.VirtualAddress,
|
|
SizeOfRawData=old_shdr.SizeOfRawData,
|
|
PointerToRawData=new_offset, # Shifted down
|
|
PointerToRelocations=old_shdr.PointerToRelocations,
|
|
PointerToLinenumbers=old_shdr.PointerToLinenumbers,
|
|
NumberOfRelocations=old_shdr.NumberOfRelocations,
|
|
NumberOfLinenumbers=old_shdr.NumberOfLinenumbers,
|
|
Characteristics=old_shdr.Characteristics,
|
|
)
|
|
surgery.update_section_header(sect.index, new_shdr)
|
|
|
|
# SizeOfImage stays the same - virtual layout unchanged
|
|
# No need to update optional header
|
|
|
|
return ZeroPageResult(
|
|
success=True,
|
|
bytes_saved=bytes_removed,
|
|
pages_zeroed=pages_to_zero,
|
|
)
|
|
|
|
|
|
def zero_page_section(
|
|
input_path: Path,
|
|
output_path: Path,
|
|
section_name: str,
|
|
) -> ZeroPageResult:
|
|
"""Apply zero-page optimization to a file.
|
|
|
|
Convenience function that loads, modifies, and saves a PE binary.
|
|
|
|
Args:
|
|
input_path: Input PE binary
|
|
output_path: Output PE binary
|
|
section_name: Section to zero-page
|
|
|
|
Returns:
|
|
ZeroPageResult with operation outcome
|
|
"""
|
|
surgery = CoffSurgery.load(input_path)
|
|
result = conservative_zero_page(surgery, section_name)
|
|
|
|
if result.success:
|
|
surgery.save_preserving_mode(output_path, input_path.stat().st_mode)
|
|
|
|
return result
|