import contextlib import errno import io import os import struct import subprocess import sys import tempfile import unittest from pathlib import Path from unittest import mock sys.path.insert(0, str(Path(__file__).resolve().parents[1])) import unwrap_self SELF_MAGIC = 0x1D3D154F ELF_HEADER_OFFSET = 0x1A0 PROGRAM_TABLE_OFFSET = 0x40 PT_LOAD = 1 PT_DYNAMIC = 2 PT_TLS = 7 PT_NOTE = 4 SCE_COMMENT = 0x6FFFFF00 SCE_VERSION = 0x6FFFFF01 FIRST_REGION = 0x400 HEADER = b"\x7fELF\x02\x01\x01\x09" + b"\x00" * 56 PAGE = 0x1000 def pattern(seed, size): return bytes((seed + index * 7) % 251 for index in range(size)) def build_elf(segments, program_offset=PROGRAM_TABLE_OFFSET): header = bytearray(HEADER) struct.pack_into(" 1: self.assertEqual(offset % align, vaddr % align, hex(kind)) self.assertEqual([data[header[2]:header[2] + header[5]] for header in headers], [pattern(1, 0x100), pattern(9, 0x88)] + blocks) def test_file_backed_load_uses_the_first_congruent_offset_after_the_headers(self): cases = ((PROGRAM_TABLE_OFFSET, 0, 0x1400, 0x400), (PROGRAM_TABLE_OFFSET, 1, 0x1400, 0x400), (PROGRAM_TABLE_OFFSET, 8, 0x1003, 0x403), (PROGRAM_TABLE_OFFSET, PAGE, 0x1400, 0x400), (PROGRAM_TABLE_OFFSET, PAGE, 0x1480, 0x480), (PROGRAM_TABLE_OFFSET, PAGE, 0x1200, 0x1200), (PROGRAM_TABLE_OFFSET, PAGE, 0x1000, 0x1000), (PROGRAM_TABLE_OFFSET, PAGE, 0x1FFF, 0xFFF), (0xFF0, PAGE, 0x2100, 0x1100), (PROGRAM_TABLE_OFFSET, 1 << 63, 0x480, 0x480)) payload = pattern(3, 0x20) * 8 for program_offset, alignment, address, expected in cases: with self.subTest(program_offset=program_offset, alignment=alignment, address=address): segments = [(PT_LOAD, 5, 0x1234, address, len(payload), alignment)] container = build_container(segments, [(0x2804, 0x2000, len(payload))], {0x2000: payload}, program_offset=program_offset) result, output = self.convert(container) self.assertEqual(result.returncode, 0, result.stderr) data, headers = read_program_headers(output) load = headers[0] self.assertEqual(load[2], expected) self.assertGreaterEqual(load[2], max(FIRST_REGION, program_offset + 56)) self.assertEqual(load[2] % max(alignment, 1), address % max(alignment, 1)) self.assertEqual(load[3:], (address, address, len(payload), len(payload), alignment)) self.assertEqual(data[load[2]:load[2] + load[5]], payload) self.assertEqual(len(data), expected + len(payload)) self.assertEqual((Path(self.directory) / "input.self").read_bytes(), container) def test_recorded_non_load_region_can_start_at_an_already_congruent_cursor(self): load_payload = pattern(3, 0x20) * 8 + b"tail" separate_payload = pattern(9, 0x10) for kind in (PT_DYNAMIC, PT_TLS): for alignment in (8, 0x10): with self.subTest(kind=kind, alignment=alignment): segments = [(PT_LOAD, 5, 0x1234, 0x1000, len(load_payload), PAGE), (kind, 4, 0x2000, 0x1004, len(separate_payload), alignment)] records = [(0x2804, 0x800, len(load_payload)), (0x102804, 0xA00, len(separate_payload))] container = build_container(segments, records, {0x800: load_payload, 0xA00: separate_payload}) result, output = self.convert(container) self.assertEqual(result.returncode, 0, result.stderr) data, headers = read_program_headers(output) load, separate = headers self.assertEqual(load[2], PAGE) self.assertEqual(separate[2], load[2] + len(load_payload)) self.assertEqual(separate[2] % alignment, separate[3] % alignment) self.assertEqual(data[load[2]:load[2] + load[5]], load_payload) self.assertEqual(data[separate[2]:separate[2] + separate[5]], separate_payload) self.assertEqual(len(data), separate[2] + len(separate_payload)) def test_declared_offsets_are_used_when_the_container_has_no_records(self): segments = [(PT_LOAD, 1, 0x800, 0x0, 0x40, 0x1000), (PT_LOAD, 4, 0x900, 0x1000, 0x40, 0x1000)] payloads = {0x800: pattern(3, 0x40), 0x900: pattern(5, 0x40)} container = build_container(segments, [], payloads) result, output = self.convert(container) self.assertEqual(result.returncode, 0, result.stderr) data, headers = read_program_headers(output) loads = [header for header in headers if header[0] == PT_LOAD] self.assertEqual(data[loads[0][2]:loads[0][2] + 0x40], pattern(3, 0x40)) self.assertEqual(data[loads[1][2]:loads[1][2] + 0x40], pattern(5, 0x40)) for kind, flags, offset, vaddr, _, size, _, align in loads: self.assertEqual(offset % align, vaddr % align) def test_program_headers_do_not_overwrite_the_first_load_payload(self): for count, program_offset in ((74, PROGRAM_TABLE_OFFSET), (3, 0xFF0)): with self.subTest(count=count, program_offset=program_offset): payload = pattern(3, 0x20) * 8 segments = [(PT_LOAD, 5, 0x1234, 0, len(payload), PAGE)] segments.extend([(0, 0, 0, 0, 0, 0)] * (count - 1)) container = build_container(segments, [(0x2804, 0x2000, len(payload))], {0x2000: payload}, program_offset=program_offset) result, output = self.convert(container) self.assertEqual(result.returncode, 0, result.stderr) data, headers = read_program_headers(output) load = headers[0] self.assertEqual(data[load[2]:load[2] + load[5]], payload) self.assertGreaterEqual(load[2], program_offset + count * 56) self.assertEqual(load[2] % PAGE, load[3] % PAGE) self.assertEqual(len(headers), count) self.assertTrue(all(header[0] == 0 for header in headers[1:])) def test_the_verbose_report_names_every_program_header(self): container = build_container(default_segments(), default_records(), default_payloads()) directory = Path(self.directory) source = directory / "input.self" output = directory / "output.elf" source.write_bytes(container) result = subprocess.run([sys.executable, str(Path(unwrap_self.__file__)), "--verbose", str(source), str(output)], capture_output=True, text=True, timeout=30) self.assertEqual(result.returncode, 0, result.stderr) self.assertIn("PT_LOAD", result.stdout) self.assertIn("PT_DYNAMIC", result.stdout) def test_a_file_that_is_not_a_container_is_refused(self): result, output = self.convert(b"\x7fCNT" + bytes(FIRST_REGION), "input.pkg") self.assertEqual(result.returncode, 2) self.assertIn("not a SELF container", result.stderr) self.assertFalse(output.exists()) def test_a_container_without_an_inner_elf_is_refused(self): container = build_container(default_segments(), default_records(), default_payloads(), inner_elf=False) result, output = self.convert(container) self.assertEqual(result.returncode, 2) self.assertIn("no inner ELF header", result.stderr) self.assertFalse(output.exists()) def test_incomplete_elf_metadata_is_refused_without_replacing_output(self): truncated_header = bytearray(FIRST_REGION) struct.pack_into("