Files
derek.holloway 7502bf64a4
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
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

449 lines
18 KiB
Python

"""Tests for zero-page optimization and PT_LOAD alignment.
These tests verify that:
1. conservative_zero_page() correctly adjusts e_phoff and e_shoff after
removing bytes from the file
2. get_min_content_offset() correctly excludes PT_LOAD segments covering
the PHDR table, allowing in-place PHDR writes when space is available
3. The full kpack_offload_binary pipeline produces binaries with valid
PT_LOAD alignment (p_offset % p_align == p_vaddr % p_align)
"""
from pathlib import Path
import pytest
from rocm_kpack.elf import (
ElfSurgery,
ElfVerifier,
ProgramHeaderManager,
map_section_to_load,
conservative_zero_page,
zero_page_section,
kpack_offload_binary,
add_kpack_ref_section,
rewrite_hipfatbin_magic,
set_pointer,
PT_LOAD,
PT_PHDR,
PAGE_SIZE,
)
from rocm_kpack.elf.types import ELF64_PHDR_SIZE
def assert_load_alignment(surgery: ElfSurgery, context: str = "") -> None:
"""Assert all PT_LOAD segments have correct mmap alignment.
The ELF spec requires: (p_offset % p_align) == (p_vaddr % p_align)
for all PT_LOAD segments with p_align > 0.
Args:
surgery: ElfSurgery instance to check
context: Description for error messages
"""
prefix = f" ({context})" if context else ""
for idx, phdr in surgery.iter_program_headers():
if phdr.p_type == PT_LOAD and phdr.p_align > 0:
offset_mod = phdr.p_offset % phdr.p_align
vaddr_mod = phdr.p_vaddr % phdr.p_align
assert offset_mod == vaddr_mod, (
f"PT_LOAD[{idx}] alignment violation{prefix}: "
f"p_offset=0x{phdr.p_offset:x} (mod 0x{offset_mod:x}), "
f"p_vaddr=0x{phdr.p_vaddr:x} (mod 0x{vaddr_mod:x}), "
f"p_align=0x{phdr.p_align:x}"
)
class TestGetMinContentOffset:
"""Tests for ElfSurgery.get_min_content_offset()."""
def test_excludes_load_at_offset_zero(self, test_assets_dir: Path):
"""Verify PT_LOAD at offset 0 is excluded from min calculation.
The first PT_LOAD typically starts at offset 0, covering the ELF
header and PHDR table. Including this in get_min_content_offset()
would make available space always negative, forcing unnecessary
PHDR table relocation.
"""
binary = test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
surgery = ElfSurgery.load(binary)
min_offset = surgery.get_min_content_offset()
e_phoff = surgery.ehdr.e_phoff
# min_offset must be > e_phoff (not 0 from PT_LOAD at offset 0)
assert min_offset > e_phoff, (
f"get_min_content_offset() returned {min_offset:#x} which is at or "
f"before e_phoff at {e_phoff:#x}. "
"PT_LOAD segments at/before e_phoff should be excluded."
)
def test_available_space_is_nonnegative(self, test_assets_dir: Path):
"""Verify available space at PHDR location is non-negative."""
binary = test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
surgery = ElfSurgery.load(binary)
min_offset = surgery.get_min_content_offset()
available = min_offset - surgery.ehdr.e_phoff
assert available >= 0, (
f"Available space for PHDR expansion is {available} (negative!). "
f"min_content_offset={min_offset:#x}, e_phoff={surgery.ehdr.e_phoff:#x}"
)
class TestZeroPageEphoffAdjustment:
"""Tests for correct e_phoff adjustment after byte removal."""
def test_ephoff_adjusted_after_removal(self, test_assets_dir: Path):
"""Verify e_phoff is adjusted when PHDR is after removed bytes.
When the PHDR table has been relocated to end of file (e.g., by
map_section_to_load) and then conservative_zero_page removes bytes
from the middle of the file, e_phoff must be adjusted by the number
of removed bytes. Without this, subsequent PHDR operations use a
stale offset.
"""
binary = test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
surgery = ElfSurgery.load(binary)
# Phase 0: Add a section (simulates add_kpack_ref_section)
surgery.add_section(
name=".rocm_kpack_ref",
content=b"test marker data for alignment verification",
)
# Phase 1: Map to PT_LOAD (this relocates PHDR to end of file)
map_result = map_section_to_load(surgery, ".rocm_kpack_ref")
assert map_result.success
# Record PHDR state before zero-page
pre_phoff = surgery.ehdr.e_phoff
pre_shoff = surgery.ehdr.e_shoff
# Phase 3: Zero-page .hip_fatbin
fatbin = surgery.find_section(".hip_fatbin")
assert fatbin is not None, "Test binary must have .hip_fatbin"
assert fatbin.size > PAGE_SIZE, "Test binary .hip_fatbin must be > 1 page"
result = conservative_zero_page(surgery)
assert result.success
assert result.bytes_saved > 0
# After zero-paging, e_phoff should have been adjusted
# (either adjusted down by bytes_saved, or relocated to a new location)
# The key invariant: e_phoff must point to valid PHDR data
post_phoff = surgery.ehdr.e_phoff
post_phnum = surgery.ehdr.e_phnum
# Verify we can actually read program headers at the reported e_phoff
assert post_phoff + post_phnum * ELF64_PHDR_SIZE <= len(surgery.data), (
f"e_phoff ({post_phoff:#x}) + PHDR table size "
f"({post_phnum * ELF64_PHDR_SIZE:#x}) exceeds file size "
f"({len(surgery.data):#x})"
)
class TestFullPipelineAlignment:
"""Tests for PT_LOAD alignment through the full kpack pipeline."""
def test_kpack_offload_alignment(self, test_assets_dir: Path, tmp_path: Path):
"""Verify kpack_offload_binary produces valid PT_LOAD alignment.
This is the end-to-end test for the alignment fix. It runs the
complete transform pipeline (add section, map to LOAD, rewrite
magic, zero-page) and verifies all PT_LOAD segments satisfy:
p_offset % p_align == p_vaddr % p_align
"""
input_binary = (
test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
)
output_binary = tmp_path / "aligned_output.exe"
result = kpack_offload_binary(
input_path=input_binary,
output_path=output_binary,
kpack_search_paths=["test.kpack"],
kernel_name="test_kernel",
)
assert output_binary.exists()
assert result["new_size"] > 0
# Load output and verify alignment
surgery = ElfSurgery.load(output_binary)
assert_load_alignment(surgery, "after kpack_offload_binary")
def test_kpack_offload_multi_binary_alignment(
self, test_assets_dir: Path, tmp_path: Path
):
"""Verify alignment for multi-kernel binary (multiple wrappers)."""
input_binary = (
test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_multi.exe"
)
output_binary = tmp_path / "aligned_multi_output.exe"
result = kpack_offload_binary(
input_path=input_binary,
output_path=output_binary,
kpack_search_paths=["test.kpack"],
kernel_name="test_kernel_multi",
)
assert output_binary.exists()
surgery = ElfSurgery.load(output_binary)
assert_load_alignment(surgery, "after kpack_offload_binary (multi)")
def test_kpack_offload_shared_library_alignment(
self, test_assets_dir: Path, tmp_path: Path
):
"""Verify alignment for shared library."""
input_binary = (
test_assets_dir / "bundled_binaries/linux/cov5/libtest_kernel_single.so"
)
output_binary = tmp_path / "aligned_output.so"
result = kpack_offload_binary(
input_path=input_binary,
output_path=output_binary,
kpack_search_paths=["test.kpack"],
kernel_name="lib/libtest_kernel_single.so",
)
assert output_binary.exists()
surgery = ElfSurgery.load(output_binary)
assert_load_alignment(surgery, "after kpack_offload_binary (shared lib)")
def test_verifier_passes_after_kpack(self, test_assets_dir: Path, tmp_path: Path):
"""Verify the full ELF verifier passes after kpack transform."""
input_binary = (
test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
)
output_binary = tmp_path / "verified_output.exe"
kpack_offload_binary(
input_path=input_binary,
output_path=output_binary,
kpack_search_paths=["test.kpack"],
kernel_name="test_kernel",
)
verify_result = ElfVerifier.verify(output_binary)
assert verify_result.passed, f"Verification errors: {verify_result.errors}"
def test_phdr_not_unnecessarily_relocated(
self, test_assets_dir: Path, tmp_path: Path
):
"""Verify zero-page doesn't force unnecessary PHDR relocation.
With the get_min_content_offset fix, conservative_zero_page should
be able to write the PHDR table in place when there's enough space,
rather than always relocating to end of file.
"""
input_binary = (
test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
)
surgery = ElfSurgery.load(input_binary)
# Do only Phase 3 (zero-page without prior PHDR relocation).
# The original PHDR at offset 0x40 should have plenty of space.
result = conservative_zero_page(surgery)
assert result.success
# When PHDR isn't relocated by a prior phase, zero-page shouldn't
# need to relocate it either (there's space at the original location).
# Note: this depends on test binary having enough existing PHDR slots.
# If the split creates too many new entries, relocation is legitimate.
# The key test is that get_min_content_offset doesn't return 0.
min_offset = surgery.get_min_content_offset()
assert min_offset > 0, "get_min_content_offset should not return 0"
def test_relocated_phdr_vaddr_equals_offset(
self, test_assets_dir: Path, tmp_path: Path
):
"""Relocated PHDR PT_LOAD must use p_vaddr == p_offset.
EL8's 4.18 kernel computes AT_PHDR = load_bias + e_phoff without
translating e_phoff through the covering segment, so a merely
page-congruent mapping (p_vaddr != p_offset) makes direct exec
SIGSEGV in ld.so before main. Regression test for ROCm/TheRock#5430.
"""
input_binary = (
test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
)
output_binary = tmp_path / "phdr_vaddr_output.exe"
kpack_offload_binary(
input_path=input_binary,
output_path=output_binary,
kpack_search_paths=["test.kpack"],
kernel_name="test_kernel",
)
surgery = ElfSurgery.load(output_binary)
e_phoff = surgery.ehdr.e_phoff
covering = [
phdr
for _, phdr in surgery.iter_program_headers()
if phdr.p_type == PT_LOAD
and phdr.p_offset <= e_phoff < phdr.p_offset + phdr.p_filesz
]
assert covering, "no PT_LOAD covers the program header table"
# The pipeline must have relocated the PHDR table into a dedicated
# trailing PT_LOAD that begins at e_phoff (otherwise this test would
# pass trivially on a non-relocated PIE whose first PT_LOAD already has
# p_vaddr == p_offset == 0, exercising nothing).
assert any(phdr.p_offset == e_phoff for phdr in covering), (
f"expected a dedicated PHDR PT_LOAD starting at e_phoff=0x{e_phoff:x}; "
"the table was not relocated as expected"
)
for phdr in covering:
assert phdr.p_vaddr == phdr.p_offset, (
"PHDR-covering PT_LOAD must have p_vaddr == p_offset for "
f"old-kernel exec; got p_vaddr=0x{phdr.p_vaddr:x}, "
f"p_offset=0x{phdr.p_offset:x}"
)
class TestNobitsCollision:
"""Tests for NOBITS offset collision when section ends on page boundary."""
def test_page_aligned_section_end_no_collision(
self, test_assets_dir: Path, tmp_path: Path
):
"""Verify no NOBITS collision when .hip_fatbin ends on a page boundary.
When .hip_fatbin's vaddr + size is page-aligned (suffix == 0), the
NOBITS piece and post-section piece would share the same file offset
after byte removal. The fix gives NOBITS segments a safe offset past
end of file.
Reproduces the CI failure on test_rocrand_scrambled_sobol32_qrng
(gfx1151) where .hip_fatbin was at vaddr=0x2e000, size=0x12000,
end=0x40000 (page-aligned).
"""
from elf_test_utils import patch_hip_fatbin_size
source = test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
# Test binary: .hip_fatbin at vaddr=0x1000, original size=0x2d30.
# Patch to 0x2000 so end = 0x1000 + 0x2000 = 0x3000 (page-aligned).
patched = patch_hip_fatbin_size(source, tmp_path / "page_aligned.exe", 0x2000)
surgery = ElfSurgery.load(patched)
result = conservative_zero_page(surgery)
assert result.success
assert result.bytes_saved > 0
# The key check: verifier must pass (no NOBITS collision)
verify_result = ElfVerifier.verify_data(surgery.data)
assert verify_result.passed, (
f"NOBITS collision after zero-paging page-aligned section: "
f"{verify_result.errors}"
)
def test_page_aligned_section_end_full_pipeline(
self, test_assets_dir: Path, tmp_path: Path
):
"""Full kpack pipeline with page-aligned .hip_fatbin end."""
from elf_test_utils import patch_hip_fatbin_size
source = test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
patched = patch_hip_fatbin_size(source, tmp_path / "page_aligned.exe", 0x2000)
output = tmp_path / "kpacked_output.exe"
result = kpack_offload_binary(
input_path=patched,
output_path=output,
kpack_search_paths=["test.kpack"],
kernel_name="test_kernel",
)
assert output.exists()
assert result["new_size"] > 0
# Verify output passes all checks
surgery = ElfSurgery.load(output)
assert_load_alignment(surgery, "page-aligned .hip_fatbin end")
verify_result = ElfVerifier.verify_data(surgery.data)
assert verify_result.passed, f"Verification errors: {verify_result.errors}"
def test_leaves_one_page_when_suffix_zero(
self, test_assets_dir: Path, tmp_path: Path
):
"""Verify that when suffix==0, one page is left behind to avoid collision.
The fix shrinks aligned_size by PAGE_SIZE so the last page remains
as file content. This means we save one fewer page, but the NOBITS
and post-section pieces have distinct file offsets.
"""
from elf_test_utils import patch_hip_fatbin_size
source = test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
# .hip_fatbin at vaddr=0x1000, patched to 0x2000 → end=0x3000 (page-aligned)
# Section spans 2 pages. With suffix==0 fix, only 1 page is removed.
patched = patch_hip_fatbin_size(source, tmp_path / "page_aligned.exe", 0x2000)
surgery = ElfSurgery.load(patched)
original_size = len(surgery.data)
result = conservative_zero_page(surgery)
assert result.success
assert (
result.pages_zeroed == 1
), f"Expected 1 page zeroed (2 pages minus 1 kept), got {result.pages_zeroed}"
assert (
result.bytes_saved == PAGE_SIZE
), f"Expected {PAGE_SIZE} bytes saved (one page), got {result.bytes_saved}"
class TestSteppedPipelineAlignment:
"""Tests that step through the pipeline manually to verify each phase."""
def test_alignment_preserved_through_each_phase(
self,
test_assets_dir: Path,
):
"""Step through the kpack pipeline, checking alignment after each phase.
This helps identify exactly which phase introduces alignment issues.
"""
binary = test_assets_dir / "bundled_binaries/linux/cov5/test_kernel_single.exe"
surgery = ElfSurgery.load(binary)
# Verify original binary alignment
assert_load_alignment(surgery, "original binary")
# Phase 0: Add .rocm_kpack_ref section
add_kpack_ref_section(
surgery,
kpack_search_paths=["test.kpack"],
kernel_name="test_kernel",
)
assert_load_alignment(surgery, "after Phase 0 (add_kpack_ref_section)")
# Phase 1: Map to PT_LOAD
map_result = map_section_to_load(surgery, ".rocm_kpack_ref")
assert map_result.success
assert_load_alignment(surgery, "after Phase 1 (map_section_to_load)")
# Phase 2: Rewrite magic
fatbin = surgery.find_section(".hip_fatbin")
assert fatbin is not None
segment = surgery.find_section(".hipFatBinSegment")
if segment is not None:
kpack_ref_vaddr = map_result.vaddr
num_wrappers = segment.header.sh_size // 24 # WRAPPER_SIZE
for i in range(num_wrappers):
wrapper_vaddr = segment.header.sh_addr + i * 24
pointer_vaddr = wrapper_vaddr + 8
set_pointer(surgery, pointer_vaddr, kpack_ref_vaddr)
rewrite_hipfatbin_magic(surgery)
assert_load_alignment(surgery, "after Phase 2 (rewrite magic)")
# Phase 3: Zero-page
result = conservative_zero_page(surgery)
assert result.success
assert_load_alignment(surgery, "after Phase 3 (conservative_zero_page)")