""" ELF binary patching utilities for testing edge cases. This module provides tools to create modified ELF binaries for testing scenarios that are difficult to reproduce with real compiled binaries, such as binaries with unusually small .hip_fatbin sections. Background: ----------- The kpack artifact splitter processes "fat binaries" - ELF files containing GPU device code in a .hip_fatbin section. The splitter: 1. Extracts device code to separate .kpack files 2. Adds a .rocm_kpack_ref marker section pointing to the kpack 3. Zero-pages the .hip_fatbin section to reclaim space The zero-paging optimization works by removing full 4KB pages from the .hip_fatbin section. This creates edge cases when: - Section is < 4KB: No full pages exist to zero-page - Section is slightly > 4KB: Structural overhead (padding, PHDR relocation) may exceed the ~4KB savings These edge cases occur in practice with small test binaries or validation utilities that have minimal GPU code. Usage: ------ from tests.elf_test_utils import patch_hip_fatbin_size # Create a binary with a tiny .hip_fatbin (will fail zero-paging) small_binary = patch_hip_fatbin_size( source_binary, output_path, new_size=3000 # Less than one 4KB page ) # Create a binary where overhead exceeds savings marginal_binary = patch_hip_fatbin_size( source_binary, output_path, new_size=5000 # Just over one page - saves 4KB but adds ~5KB overhead ) """ import struct from pathlib import Path from typing import Optional class ElfPatchError(Exception): """Raised when ELF patching fails.""" pass def patch_hip_fatbin_size( source_path: Path, output_path: Path, new_size: int, ) -> Path: """ Create a copy of an ELF binary with a modified .hip_fatbin section size. This patches the section header to report a smaller size, which is sufficient to trigger the edge cases in zero-paging logic. The actual section content is not modified (the size field just tells tools how much of the section to consider valid). Args: source_path: Path to source ELF binary with .hip_fatbin section output_path: Path for the patched output binary new_size: New size in bytes for .hip_fatbin section. Use < 4096 for "too small to zero-page" case. Use ~5000 for "overhead exceeds savings" case. Returns: Path to the created output file Raises: ElfPatchError: If source is not a valid ELF or has no .hip_fatbin FileNotFoundError: If source file doesn't exist Example section sizes and their effects: 3000 bytes: Cannot zero any pages (< 4KB), zero-paging fails 5000 bytes: Can zero 1 page (4KB), but padding/relocation adds ~5KB 9000 bytes: Can zero 2 pages (8KB), usually net savings """ if not source_path.exists(): raise FileNotFoundError(f"Source binary not found: {source_path}") data = bytearray(source_path.read_bytes()) # Verify ELF magic if data[:4] != b"\x7fELF": raise ElfPatchError(f"Not an ELF file: {source_path}") # Parse ELF64 header to find section headers # ELF64 header layout: # 0x00: e_ident[16] - ELF identification # 0x10: e_type[2] - Object file type # 0x12: e_machine[2] - Architecture # 0x14: e_version[4] - Object file version # 0x18: e_entry[8] - Entry point virtual address # 0x20: e_phoff[8] - Program header table offset # 0x28: e_shoff[8] - Section header table offset # 0x30: e_flags[4] - Processor-specific flags # 0x34: e_ehsize[2] - ELF header size # 0x36: e_phentsize[2] - Program header entry size # 0x38: e_phnum[2] - Program header count # 0x3a: e_shentsize[2] - Section header entry size # 0x3c: e_shnum[2] - Section header count # 0x3e: e_shstrndx[2] - Section name string table index e_shoff = struct.unpack(" original_size: raise ElfPatchError( f"Cannot increase section size: {new_size} > {original_size}" ) struct.pack_into(" Optional[int]: """ Get the size of the .hip_fatbin section in an ELF binary. Args: binary_path: Path to ELF binary Returns: Size in bytes, or None if section not found """ if not binary_path.exists(): return None data = binary_path.read_bytes() if data[:4] != b"\x7fELF": return None e_shoff = struct.unpack("