aboutsummaryrefslogtreecommitdiffstats
path: root/psp_emu
diff options
context:
space:
mode:
authoruser <user@clank>2026-10-09 16:11:31 +0200
committeruser <user@clank>2026-10-09 16:11:31 +0200
commitbc43149d914325e15dc0490dee9d982e21287623 (patch)
treea632cf70de742d5a60d01c9c0d806bf5b9257538 /psp_emu
parentGCP->ASD 0x403: ASD-internal chain PROVEN end-to-end in emulator (real handlers) (diff)
downloadamd-psp-re-main.tar.gz
amd-psp-re-main.tar.xz
amd-psp-re-main.zip
Record PSP security investigation artifacts and probesHEADmain
Diffstat (limited to 'psp_emu')
-rw-r--r--psp_emu/harness/measure_sysdrv_svc78.py72
-rw-r--r--psp_emu/harness/probe_asd163_100_type1_error_record.py35
-rw-r--r--psp_emu/harness/probe_asd163_101_ownerless_record_zero.py67
-rw-r--r--psp_emu/harness/probe_asd163_10a_10b_dispatch.py46
-rw-r--r--psp_emu/harness/probe_asd163_160_srm_state_bypass.py83
-rw-r--r--psp_emu/harness/probe_asd163_18f_cross_channel.py105
-rw-r--r--psp_emu/harness/probe_asd163_1a1_failed_rng_prefix.py51
-rw-r--r--psp_emu/harness/probe_asd163_1a2_last_channel_sweep.py50
-rw-r--r--psp_emu/harness/probe_asd163_1a3_1a4_scalar_setters.py53
-rw-r--r--psp_emu/harness/probe_asd163_1a7_1a8_bitmap_release.py59
-rw-r--r--psp_emu/harness/probe_asd163_1a9_sparse_enumeration.py58
-rw-r--r--psp_emu/harness/probe_asd163_1aa_alloc_cleanup.py72
-rw-r--r--psp_emu/harness/probe_asd163_1aa_allocator_blacklist.py60
-rw-r--r--psp_emu/harness/probe_asd163_1aa_duplicate_tail.py65
-rw-r--r--psp_emu/harness/probe_asd163_200_asic20_output.py39
-rw-r--r--psp_emu/harness/probe_asd163_203_dio_config.py38
-rw-r--r--psp_emu/harness/probe_asd163_203_dio_config_write.py44
-rw-r--r--psp_emu/harness/probe_asd163_204_asic20_output.py44
-rw-r--r--psp_emu/harness/probe_asd163_205_206_invalid_side_effect.py51
-rw-r--r--psp_emu/harness/probe_asd163_207_asic20_output.py33
-rw-r--r--psp_emu/harness/probe_asd163_208_sysdrv1079_forward.py44
-rw-r--r--psp_emu/harness/probe_asd163_20b_private_store.py61
-rw-r--r--psp_emu/harness/probe_asd163_srm_hdcp1_multi_revocation.py85
-rw-r--r--psp_emu/harness/probe_asd163_srm_multi_revocation.py93
-rw-r--r--psp_emu/harness/probe_asd20b_model_pte.py92
-rw-r--r--psp_emu/harness/probe_hdcp52_alloc_failure.py68
-rw-r--r--psp_emu/harness/probe_hdcp52_connected_zero_handle.py93
-rw-r--r--psp_emu/harness/probe_opm51_cmd11_global_flag.py34
-rw-r--r--psp_emu/harness/probe_sos54_6c_missing_resource.py35
-rw-r--r--psp_emu/harness/probe_sos54_resource0_model.py91
-rw-r--r--psp_emu/harness/probe_sysdrv_1001_owner_cleanup.py61
-rw-r--r--psp_emu/harness/probe_sysdrv_100b_nested_span.py51
-rw-r--r--psp_emu/harness/probe_sysdrv_100c_slot_release.py39
-rw-r--r--psp_emu/harness/probe_sysdrv_100e_100f_ccp_natural.py75
-rw-r--r--psp_emu/harness/probe_sysdrv_100e_100f_second_span.py120
-rw-r--r--psp_emu/harness/probe_sysdrv_1010_nested_gate.py33
-rw-r--r--psp_emu/harness/probe_sysdrv_1016_short_input.py101
-rw-r--r--psp_emu/harness/probe_sysdrv_1017_short_outputs.py45
-rw-r--r--psp_emu/harness/probe_sysdrv_1018_synthetic_range.py50
-rw-r--r--psp_emu/harness/probe_sysdrv_101a_count_wrap.py44
-rw-r--r--psp_emu/harness/probe_sysdrv_101c_state_gate.py66
-rw-r--r--psp_emu/harness/probe_sysdrv_101c_wrapped_output.py125
-rw-r--r--psp_emu/harness/probe_sysdrv_101d_nested_gate.py28
-rw-r--r--psp_emu/harness/probe_sysdrv_1021_global_flags.py68
-rw-r--r--psp_emu/harness/probe_sysdrv_1023_ccp_natural.py63
-rw-r--r--psp_emu/harness/probe_sysdrv_1023_short_output.py93
-rw-r--r--psp_emu/harness/probe_sysdrv_1027_sos94_block.py27
-rw-r--r--psp_emu/harness/probe_sysdrv_1028_base_readonly.py18
-rw-r--r--psp_emu/harness/probe_sysdrv_1028_synthetic_window.py44
-rw-r--r--psp_emu/harness/probe_sysdrv_102c_label_reach.py45
-rw-r--r--psp_emu/harness/probe_sysdrv_102f_fixed_map.py35
-rw-r--r--psp_emu/harness/probe_sysdrv_1030_fixed_mode_flag.py29
-rw-r--r--psp_emu/harness/probe_sysdrv_1030_flash_class_gate.py52
-rw-r--r--psp_emu/harness/probe_sysdrv_1031_mode_oracle.py30
-rw-r--r--psp_emu/harness/probe_sysdrv_1032_status_flag.py34
-rw-r--r--psp_emu/harness/probe_sysdrv_1033_short_input.py95
-rw-r--r--psp_emu/harness/probe_sysdrv_1034_failed_status_store.py49
-rw-r--r--psp_emu/harness/probe_sysdrv_1038_length_gate.py38
-rw-r--r--psp_emu/harness/probe_sysdrv_103e_1047_key_store.py87
-rw-r--r--psp_emu/harness/probe_sysdrv_1041_104f_count_boundary.py38
-rw-r--r--psp_emu/harness/probe_sysdrv_1044_default_gate.py26
-rw-r--r--psp_emu/harness/probe_sysdrv_104d_property_prefix.py36
-rw-r--r--psp_emu/harness/probe_sysdrv_1058_default_gate.py34
-rw-r--r--psp_emu/harness/probe_sysdrv_1059_error_side_effect.py75
-rw-r--r--psp_emu/harness/probe_sysdrv_105a_error_flag.py50
-rw-r--r--psp_emu/harness/probe_sysdrv_105d_zero_length.py49
-rw-r--r--psp_emu/harness/probe_sysdrv_105f_reg_tables.py23
-rw-r--r--psp_emu/harness/probe_sysdrv_105f_short_input.py51
-rw-r--r--psp_emu/harness/probe_sysdrv_1063_short_output.py35
-rw-r--r--psp_emu/harness/probe_sysdrv_1067_failed_lock_count.py62
-rw-r--r--psp_emu/harness/probe_sysdrv_106a_short_spans.py43
-rw-r--r--psp_emu/harness/probe_sysdrv_106c_length_wrap.py45
-rw-r--r--psp_emu/harness/probe_sysdrv_1076_dispatch.py96
-rw-r--r--psp_emu/harness/probe_sysdrv_1079_fixed_write_gate.py48
-rw-r--r--psp_emu/harness/probe_sysdrv_107b_dispatch.py96
-rw-r--r--psp_emu/harness/probe_sysdrv_107b_multi_record_loss.py43
-rw-r--r--psp_emu/harness/probe_sysdrv_107b_output_size.py72
-rw-r--r--psp_emu/harness/probe_sysdrv_1082_privileged_scalar.py46
-rw-r--r--psp_emu/harness/probe_sysdrv_1084_short_output_inline.py37
-rw-r--r--psp_emu/harness/probe_sysdrv_1094_cross_partition_helper.py50
-rw-r--r--psp_emu/harness/probe_sysdrv_1097_wait_flag.py39
-rw-r--r--psp_emu/harness/probe_sysdrv_1098_short_input.py98
-rw-r--r--psp_emu/harness/probe_sysdrv_109d_label_span.py61
-rw-r--r--psp_emu/harness/probe_sysdrv_10a2_label_reach.py45
-rw-r--r--psp_emu/harness/probe_sysdrv_10a3_error_flag_clear.py34
-rw-r--r--psp_emu/harness/probe_sysdrv_33_zero_handle.py50
-rw-r--r--psp_emu/harness/probe_sysdrv_35_ignored_rng_failure.py44
-rw-r--r--psp_emu/harness/probe_sysdrv_53_log_prevalidate.py93
-rw-r--r--psp_emu/harness/probe_sysdrv_underflow_copy.py136
89 files changed, 5121 insertions, 0 deletions
diff --git a/psp_emu/harness/measure_sysdrv_svc78.py b/psp_emu/harness/measure_sysdrv_svc78.py
new file mode 100644
index 0000000..5e2cde9
--- /dev/null
+++ b/psp_emu/harness/measure_sysdrv_svc78.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+"""Measure SYS_DRV's crypto workspace using the signed sOS SVC 0x78 path.
+
+Offline full-system emulator: let sOS boot the real SYS_DRV, then observe the
+return of SYS_DRV sub_E04A28's SVC 0x78. This reads the base written by sOS to
+SYS_DRV's 0xE257D8 and the allocation size written to SYS_DRV's stack. Sample
+adjacent CPU-MMU mappings; these are emulator results, not hardware PTEs.
+
+Run from the repository root: python3 psp_emu/harness/measure_sysdrv_svc78.py
+"""
+from __future__ import annotations
+
+import sys
+
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_CPSR, UC_ARM_REG_R0, UC_ARM_REG_SP
+
+import boot_sos_and_tas as boot
+from pspemu import armmmu
+
+
+def main() -> int:
+ comp, _ccp, _exceptions = boot.build()
+ # The key DB is needed for SYS_DRV initialization; no other TA is needed.
+ boot.stage_all(comp, slots=boot.SLOTS[:2], verbose=False)
+ seen: list[tuple[int, int, int, int, tuple[int | None, ...], bool]] = []
+
+ def after_svc78(uc, _address, _size, _user_data):
+ sp = uc.reg_read(UC_ARM_REG_SP)
+ word = lambda va: int.from_bytes(armmmu.read_va(uc, va, 4), 'little')
+ status, size, base = uc.reg_read(UC_ARM_REG_R0), word(sp), word(0xE257D8)
+ offsets = (0, 0x1000, size - 1, size, size + 0x1000)
+ pas = tuple(armmmu.translate_live(uc, base + off) for off in offsets)
+ print('signed SYS_DRV after SVC 0x78: status=%#x base=%#x size=%#x SP=%#x'
+ % (status, base, size, sp), flush=True)
+ for offset, pa in zip(offsets, pas):
+ print(' VA %#x -> %s' % (base + offset, 'UNMAPPED' if pa is None
+ else 'PA %#x' % pa), flush=True)
+ null_pa = armmmu.translate_live(uc, 0)
+ print(' NULL source VA 0 -> %s' % ('UNMAPPED' if null_pa is None
+ else 'PA %#x (permission decode below)' % null_pa),
+ flush=True)
+ l1base = armmmu.ttbr0(uc)
+ desc0 = int.from_bytes(uc.mem_read(l1base, 4), 'little')
+ l2zero = int.from_bytes(uc.mem_read(desc0 & ~0x3FF, 4), 'little') if desc0 & 3 == 1 else 0
+ dacr = armmmu.read_cp15(uc, 3, 0, 0, 0)
+ mode = uc.reg_read(UC_ARM_REG_CPSR) & 0x1F
+ domain = (desc0 >> 5) & 0xF
+ ap = ((l2zero >> 9) & 1) << 2 | ((l2zero >> 4) & 3)
+ domain_mode = (dacr >> (2 * domain)) & 3
+ # ARM short-descriptor AP=0b101 means privileged read-only, USER no access.
+ denied = (mode == 0x10 and domain_mode == 1 and ap == 5)
+ seen.append((status, size, base, sp, pas, denied))
+ print(' VA0 L1=%#x L2=%#x AP=%d domain=%d DACR-domain=%d CPU-mode=%#x user-denied=%s'
+ % (desc0, l2zero, ap, domain, domain_mode, mode, denied), flush=True)
+
+ # Return from sOS SVC 0x78, just before SYS_DRV checks size >= 0x11FC.
+ comp.uc.hook_add(UC_HOOK_CODE, after_svc78, begin=0xE04A36, end=0xE04A36)
+ comp.uc.reg_write(UC_ARM_REG_SP, 0x3F000)
+ boot.run_system(comp, seconds=30, done=lambda: bool(seen), settle=0, verbose=False)
+ if (len(seen) != 1 or seen[0][:3] != (0, 0x2000, 0xECC000)
+ or seen[0][4] != (0x10000, 0x11000, 0x11FFF, None, None)
+ or not seen[0][5]):
+ print('Unexpected or missing sOS/SYS_DRV workspace result:', seen, file=sys.stderr)
+ return 1
+ # These PTEs were checked inside the hook, in the right process context.
+ print('MATCH: two mapped workspace pages; next VA unmapped in this emulator')
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/psp_emu/harness/probe_asd163_100_type1_error_record.py b/psp_emu/harness/probe_asd163_100_type1_error_record.py
new file mode 100644
index 0000000..ec15226
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_100_type1_error_record.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 0x100 type-1 ASIC20 callback: error leaves an active record.
+Isolated synthetic descriptor and callback installation; no card/link operation.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE=0xD33510; SLOT=BASE+769*4; STATE=0xD29108
+REQ=0x400040;DESC=0x400100
+
+def main():
+ asd=Component('asd',image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000,0x3000)
+ prev={p:asd.rd32(p) for p in (SLOT,STATE)}
+ # A *single signed* ASIC20/25 type-1 thunk, not an ASIC boot state.
+ asd.wr32(SLOT,0xD10441)
+ def run(state):
+ asd.write(BASE,bytes(92));asd.wr32(STATE,state)
+ asd.write(DESC,bytes(0x40));asd.wr32(DESC,1)
+ asd.write(REQ,bytes(0x40));asd.wr32(REQ,0x100)
+ asd.wr32(REQ+4,0);asd.wr32(REQ+8,DESC)
+ ret=asd.run_function(0xD013B8,(REQ,),thumb=True,count=150_000,timeout=2_000_000)
+ return ret,tuple(asd.rd32(BASE+n) for n in (4,8,64,68,76))
+ try:
+ fail,rec=run(0)
+ ok,rec2=run(1)
+ assert (fail,rec)==(1,(1,1,6,4,5)),(fail,rec)
+ assert (ok,rec2)==(0,(1,1,6,2,5)),(ok,rec2)
+ assert not asd.faults,asd.faults
+ print('signed ASD 0x100: synthetic type-1 descriptor leaves active type-6 record even when startup predicate makes callback return 1; success case state=1; no actual ASIC admission')
+ finally:
+ for p,v in prev.items():asd.wr32(p,v)
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_101_ownerless_record_zero.py b/psp_emu/harness/probe_asd163_101_ownerless_record_zero.py
new file mode 100644
index 0000000..260105a
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_101_ownerless_record_zero.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+"""ASD .163 0x101 record-zero ordering with callback effects blocked.
+
+Signed dispatcher and zeroing leaf run unmodified; two ASIC/resource helper
+calls are intercepted to return errors. Synthetic private records and caller
+identities: not a live victim or hardware teardown.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+DISPATCH, REQ, TABLE, STRIDE = 0xD013B8, 0x400040, 0xD33510, 92
+CALLBACKS = (0xD0A7A8, 0xD0AEB0)
+
+
+def main() -> None:
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1000)
+ visits: list[int] = []
+
+ def deny_helper(uc, ea, size, data):
+ visits.append(ea)
+ uc.reg_write(UC_ARM_REG_R0, 0x1234)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+
+ for ea in CALLBACKS:
+ asd.uc.hook_add(UC_HOOK_CODE, deny_helper, begin=ea, end=ea+1)
+ victim, control = 5, 6
+ marker = bytes((i*3+1)&0xFF for i in range(STRIDE))
+ asd.write(TABLE+victim*STRIDE, marker)
+ asd.write(TABLE+control*STRIDE, marker)
+
+ def invoke(channel: int, caller: int) -> int:
+ asd.write(REQ, bytes(0x40))
+ asd.wr32(REQ, 0x101)
+ asd.wr32(REQ+4, channel)
+ ret = asd.run_function(DISPATCH, (REQ, caller), thumb=True,
+ count=200_000, timeout=3_000_000)
+ assert not asd.faults, (channel, ret, asd.faults)
+ return ret
+
+ denied = invoke(32, 2)
+ assert denied == 5 and not visits
+ assert asd.read(TABLE+victim*STRIDE, STRIDE) == marker
+ assert invoke(victim, 2) == 0
+ assert visits == list(CALLBACKS), [hex(x) for x in visits]
+ assert asd.read(TABLE+victim*STRIDE, STRIDE) == bytes(STRIDE)
+ assert asd.read(TABLE+control*STRIDE, STRIDE) == marker
+ print('signed 0x101 channel 5: zeroed 92 bytes despite two modeled helper failures')
+ print('index-32 negative: return 5; adjacent channel 6 unchanged')
+ # Restore the synthetic record and change only the opaque caller scalar.
+ asd.write(TABLE+victim*STRIDE, marker)
+ visits.clear()
+ assert invoke(victim, 7) == 0 and visits == list(CALLBACKS)
+ assert asd.read(TABLE+victim*STRIDE, STRIDE) == bytes(STRIDE)
+ print('different synthetic caller scalar: same signed record-zero effect')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_asd163_10a_10b_dispatch.py b/psp_emu/harness/probe_asd163_10a_10b_dispatch.py
new file mode 100644
index 0000000..73291d5
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_10a_10b_dispatch.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+"""Synthetic signed ASD .163 dispatch: 0x10A/B are handlers, not aborts.
+
+The table is zero-initialized in this isolated emulation; this does not measure
+live card callbacks or sOS caller-window routing.
+"""
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+DISPATCH, REQ = 0xD013B8, 0x400040
+
+
+def call(asd: Component, op: int, *args: int) -> int:
+ asd.write(REQ, b'\0' * 0x40)
+ asd.wr32(REQ, op)
+ for i, arg in enumerate(args):
+ asd.wr32(REQ + 4 + 4*i, arg)
+ status = asd.run_function(DISPATCH, (REQ,), thumb=True, count=30000, timeout=2_000_000)
+ assert not asd.faults, (hex(op), status, asd.faults)
+ return status
+
+
+def run() -> None:
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1C000)
+ assert asd.rd32(0xD33510 + 777 * 4) == 0
+ assert asd.rd32(0xD33510 + 778 * 4) == 0
+ asd.write(0x400080, b'\0' * 12)
+ for op, args in ((0x10A, (0, 0x400080)),
+ (0x10B, (0, 0x400080, 0x400084, 0x400088))):
+ status = call(asd, op, *args)
+ assert status == 7, (hex(op), status)
+ print(f'signed 0x{op:03x}: valid caller pointer(s), null callback returns 7')
+ assert call(asd, 0x10A, 0, 0xD34000) == 2
+ assert call(asd, 0x10B, 0, 0xD34000, 0x400084, 0x400088) == 2
+ print('negative controls: private pointer rejected with 2 in both leaves')
+ assert call(asd, 0x106) == 4
+ print('invalid-table control: opcode 0x106 returns 4')
+
+
+if __name__ == '__main__':
+ run()
diff --git a/psp_emu/harness/probe_asd163_160_srm_state_bypass.py b/psp_emu/harness/probe_asd163_160_srm_state_bypass.py
new file mode 100644
index 0000000..f17ace7
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_160_srm_state_bypass.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 0x160 state scalar gates HDCP1/HDCP2 SRM cleanup.
+
+Synthetic signed 0x18E allocation, planted UNSIGNED SRM, and stubbed ASIC
+callback. No signature acceptance, natural victim or card effect.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE, REQ, SRC, MASK, SRM = 0xD33510, 0x400040, 0x401000, 0x402000, 0xD15030
+CHANNEL = 5
+
+
+def one_case(clear_by_caller: bool, hdcp2: bool) -> tuple[int, int, int]:
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1C000)
+
+ def request(op: int, *args: int) -> int:
+ asd.write(REQ, bytes(0x40))
+ asd.wr32(REQ, op)
+ for i, arg in enumerate(args):
+ asd.wr32(REQ + 4 + 4*i, arg)
+ rv = asd.run_function(0xD013B8, (REQ,), thumb=True,
+ count=100_000, timeout=3_000_000)
+ assert not asd.faults, (hex(op), rv, asd.faults)
+ return rv
+
+ bitmap = BASE + (0xDF4 if hdcp2 else 4092)
+ count = BASE + (0xDF8 if hdcp2 else 4108)
+ asd.write(SRC, b'ABCDE' + bytes((0xA0 if hdcp2 else 0x280)-5))
+ asd.write(MASK, bytes(16))
+ assert request(0x1A7 if hdcp2 else 0x18E, CHANNEL, SRC, 1, MASK) == 0
+ assert asd.rd32(bitmap) == 1 and asd.read(count,1) == b'\x01'
+ asd.wr32(BASE+92*CHANNEL+4, 1) # synthetic active record
+ asd.wr32(BASE+92*CHANNEL+88, 1) # synthetic live-link annotation
+ if clear_by_caller:
+ assert request(0x160, CHANNEL, 0) == 0
+ assert asd.rd32(BASE+92*CHANNEL+88) == 0
+
+ # HDCP1 reverses the stored ID, ABCDE -> EDCBA; HDCP2 compares ABCDE.
+ # This SRM is NOT signed, and its bytes are planted past the 0x301 gate.
+ asd.write(SRM, bytes(0x1400))
+ asd.write(SRM, b'\x91')
+ asd.write(SRM+6, b'\x01\x88')
+ asd.write(SRM+8, (1 << 6).to_bytes(2,'little'))
+ asd.write(SRM+12, b'ABCDE' if hdcp2 else b'EDCBA')
+ calls: list[int] = []
+ def block_callback(uc, ea, size, data):
+ calls.append(ea)
+ uc.reg_write(UC_ARM_REG_R0, 0)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+ callback = 0xD0AEB0 if hdcp2 else 0xD0A7A8
+ asd.uc.hook_add(UC_HOOK_CODE, block_callback, begin=callback, end=callback+1)
+ rv = asd.run_function(0xD0CB70, (0,), thumb=True,
+ count=200_000, timeout=4_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+ bitmap_value = asd.rd32(bitmap)
+ count_value = asd.read(count,1)[0]
+ print('HDCP2' if hdcp2 else 'HDCP1', 'caller cleared?',clear_by_caller,
+ 'bitmap',bin(bitmap_value),'count',count_value,
+ 'ASIC callback visits',len(calls))
+ return bitmap_value, count_value, len(calls)
+
+
+def main() -> None:
+ for hdcp2 in (False, True):
+ good = one_case(False, hdcp2)
+ bypass = one_case(True, hdcp2)
+ assert good == (0,0,1), good
+ assert bypass == (1,1,0), bypass
+ print('signed 0x160 clears cleanup predicates in both loops; unsigned single-match SRMs then skip revocation')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_asd163_18f_cross_channel.py b/psp_emu/harness/probe_asd163_18f_cross_channel.py
new file mode 100644
index 0000000..aee13ea
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_18f_cross_channel.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 0x18F: caller bitmap releases a different channel's record.
+
+Synthetic global table and caller window only. No card invocation, sOS caller
+window, real channel ownership, or live impact is established here.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE = 0xD33510
+REQ = 0x400040
+BITS = 0x400080
+DISPATCH = 0xD013B8
+COUNT = BASE + 4108
+
+
+def record(slot: int) -> int:
+ return BASE + 4109 + 5 * slot
+
+
+def run() -> None:
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1C000) # synthetic GCP caller window
+ asd.write(REQ, b'\0' * 0x40)
+ asd.wr32(REQ, 0x18F)
+ asd.wr32(REQ + 4, BITS)
+ asd.write(BITS, b'\0' * 16)
+
+ # Slot 7 belongs to another channel (5); caller claims it by bit number
+ # alone. Slot 8 is a control for collateral stores.
+ asd.wr32(BASE + 4092, 1 << 7)
+ asd.write(COUNT, b'\x01')
+ asd.write(record(7), b'ABCDE')
+ asd.write(record(8), b'VWXYZ')
+ asd.wr32(BASE + 3580 + 4 * 7, 5)
+ asd.wr32(BITS, 1 << 7)
+ rv = asd.run_function(DISPATCH, (REQ,), thumb=True, count=30000, timeout=2_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+ assert asd.rd32(BASE + 4092) == 0
+ assert asd.read(COUNT, 1) == b'\x00'
+ assert asd.read(record(7), 5) == b'\0' * 5
+ assert asd.rd32(BASE + 3580 + 4 * 7) == 5 # owner field NOT checked/cleared
+ assert asd.read(record(8), 5) == b'VWXYZ'
+ print('allocated slot 7: arbitrary caller bitmap clears another channel record, '
+ 'frees global bit, decrements count; owner word remains 5')
+
+ # The five-byte zero operation is unconditional even for an unset bit.
+ asd.write(record(9), b'12345')
+ asd.wr32(BITS, 1 << 9)
+ rv = asd.run_function(DISPATCH, (REQ,), thumb=True, count=30000, timeout=2_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+ assert asd.rd32(BASE + 4092) == 0 and asd.read(COUNT, 1) == b'\x00'
+ assert asd.read(record(9), 5) == b'\0' * 5
+ assert asd.read(record(8), 5) == b'VWXYZ'
+ print('unallocated slot 9: caller bitmap clears five bytes despite unset global bit')
+
+ asd.wr32(REQ + 4, 0xD34000) # private, not in synthetic caller window
+ rv = asd.run_function(DISPATCH, (REQ,), thumb=True, count=30000, timeout=2_000_000)
+ assert rv == 2 and not asd.faults, (rv, asd.faults)
+ assert asd.read(record(8), 5) == b'VWXYZ'
+ print('negative control: private bitmap pointer rejected with status 2')
+
+
+def connected_allocation() -> None:
+ """Let the signed 0x18E allocator create the record, then forge its release."""
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1C000)
+ input_buf, output_buf = 0x401000, 0x402000
+ asd.write(REQ, b'\0' * 0x40)
+ asd.write(input_buf, b'abcde' + b'\0' * (0x280 - 5))
+ asd.write(output_buf, b'\0' * 0x10)
+ asd.wr32(REQ, 0x18E)
+ asd.wr32(REQ + 4, 5) # channel index 5
+ asd.wr32(REQ + 8, input_buf)
+ asd.wr32(REQ + 12, 1)
+ asd.wr32(REQ + 16, output_buf)
+ rv = asd.run_function(DISPATCH, (REQ,), thumb=True, count=40000, timeout=2_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+ mask = asd.rd32(output_buf)
+ assert mask == 1 and asd.rd32(BASE + 4092) == 1, hex(mask)
+ assert asd.read(record(0), 5) == b'abcde'
+ assert asd.rd32(BASE + 3580) == 5 and asd.read(COUNT, 1) == b'\x01'
+ print('signed 0x18E allocated slot 0, record abcde, owner/channel 5')
+
+ # Free request has only bitmap pointer, not a channel/owner parameter.
+ asd.write(BITS, mask.to_bytes(4, 'little') + b'\0' * 12)
+ asd.wr32(REQ, 0x18F)
+ asd.wr32(REQ + 4, BITS)
+ rv = asd.run_function(DISPATCH, (REQ,), thumb=True, count=30000, timeout=2_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+ assert asd.rd32(BASE + 4092) == 0 and asd.read(COUNT, 1) == b'\x00'
+ assert asd.read(record(0), 5) == b'\0' * 5
+ assert asd.rd32(BASE + 3580) == 5
+ print('signed 0x18F freed channel-5 record using caller bitmap alone; owner stale')
+
+
+if __name__ == '__main__':
+ connected_allocation()
+ run()
diff --git a/psp_emu/harness/probe_asd163_1a1_failed_rng_prefix.py b/psp_emu/harness/probe_asd163_1a1_failed_rng_prefix.py
new file mode 100644
index 0000000..c953cab
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_1a1_failed_rng_prefix.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 ASIC-20 0x1A1 callback prefix only: model no-output RNG failure.
+Stops BEFORE ASD's link-table or fabric operations; does not emulate a live session.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_PC,UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+REC=0xD33510; OUT=0x400100; INPUT=0x400200
+
+def main():
+ asd=Component('asd',image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000,0x3000)
+ asd.write(OUT,b'K'*16);asd.write(INPUT,b'P'*8)
+ prev=asd.rd32(REC+64);asd.wr32(REC+64,6) # synthetic ASIC-20 type-6 record
+ hits=[]
+ def failed(uc,ea,size,_):
+ hits.append('rpc35')
+ # Model the already independently verified signed SYS_DRV first-fetch
+ # failure: no bytes written to the destination, dispatcher reports 0.
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,0xD10BEF)
+ def copy(uc,ea,size,_):
+ # Isolated emulator does not handle this Thumb->ARM BLX memcpy;
+ # copy only the exact registers of the signed call, not later state.
+ dst,src,n=[uc.reg_read(x) for x in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2)]
+ assert dst==OUT and n==16,(hex(dst),hex(n))
+ asd.write(dst,asd.read(src,n))
+ hits.append('intercepted_memcpy')
+ uc.reg_write(UC_ARM_REG_PC,0xD10BF9)
+ def stop(uc,ea,size,_):
+ hits.append('copy_done');uc.emu_stop()
+ asd.uc.hook_add(UC_HOOK_CODE,failed,begin=0xD10BEA,end=0xD10BEA)
+ asd.uc.hook_add(UC_HOOK_CODE,copy,begin=0xD10BF4,end=0xD10BF4)
+ asd.uc.hook_add(UC_HOOK_CODE,stop,begin=0xD10BF8,end=0xD10BF8)
+ try:
+ try:
+ asd.run_function(0xD10BB8,(REC,OUT,INPUT),thumb=True,count=50_000,timeout=2_000_000)
+ except Exception:
+ print('hits',hits,'PC',hex(asd.uc.reg_read(UC_ARM_REG_PC)),'R0',hex(asd.uc.reg_read(UC_ARM_REG_R0)))
+ raise
+ assert hits==['rpc35','intercepted_memcpy','copy_done'],hits
+ assert asd.read(OUT,16)==bytes(16),asd.read(OUT,16).hex()
+ assert asd.read(INPUT,8)==b'P'*8
+ print('signed ASD ASIC20 0x1a1 callback prefix: modeled failed RNG plus intercepted memcpy replaced caller 16-byte marker with zero; STOP before global/fabric stores; no live entropy failure')
+ finally:
+ asd.wr32(REC+64,prev)
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_1a2_last_channel_sweep.py b/psp_emu/harness/probe_asd163_1a2_last_channel_sweep.py
new file mode 100644
index 0000000..d578931
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_1a2_last_channel_sweep.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 0x1A2: selected group sweep visits 0..30, omits 31.
+Synthetic active records only; all ASIC/link callback callsites intercepted.
+No real HDCP session, stateful callback, or card request.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE=0xD33510;STRIDE=92;REQ=0x400040
+
+def main():
+ asd=Component('asd',image_path=str(SIGNED))
+ assert Path(asd.image_path).read_bytes()==SIGNED.read_bytes()
+ asd.uc.mem_map(0x400000,0x1000)
+ visits=[]; final=[]
+ for ea in (0xD0AF40,0xD0AF4E,0xD0AF86,0xD0AF94,0xD0AF9C):
+ def intercept(uc,addr,size,user):
+ idx=(uc.reg_read(UC_ARM_REG_R0)-BASE)//STRIDE
+ (visits if addr in (0xD0AF40,0xD0AF4E) else final).append((addr,idx))
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,(addr+size)|1)
+ asd.uc.hook_add(UC_HOOK_CODE,intercept,begin=ea,end=ea+1)
+ def setup(peer):
+ # Source channel 0, same keyed group for exactly one synthetic peer.
+ for idx in (0,30,31):asd.write(BASE+idx*STRIDE,bytes(STRIDE))
+ for idx in (0,peer):
+ b=BASE+idx*STRIDE
+ for off,v in ((4,1),(20,0x1234),(64,5),(84,idx+1)):
+ asd.wr32(b+off,v)
+ asd.wr32(BASE+60,0)
+ def run(peer):
+ setup(peer);visits.clear();final.clear()
+ asd.write(REQ,bytes(0x40));asd.wr32(REQ,0x1A2);asd.wr32(REQ+4,0)
+ try:rv=asd.run_function(0xD013B8,(REQ,),thumb=True,count=70000,timeout=2_000_000)
+ except Exception:
+ print('model stop',peer,hex(asd.uc.reg_read(UC_ARM_REG_PC)),visits,final,asd.faults[-2:])
+ raise
+ assert not asd.faults,(peer,rv,asd.faults)
+ return rv,[i for ea,i in visits if ea==0xD0AF40],list(final)
+ rv30,seen30,fin30=run(30)
+ rv31,seen31,fin31=run(31)
+ assert rv30==rv31==0,(rv30,rv31,seen30,seen31,fin30,fin31)
+ assert 30 in seen30 and 31 not in seen31,(seen30,seen31)
+ assert seen31==[0] and fin30 and fin31,(seen30,seen31,fin30,fin31)
+ print('signed 0x1A2: synthetic matching peer30 reached captured sweep callback; peer31 did not; selected-channel final callbacks reached in both, all callbacks intercepted; no natural HDCP victim')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_1a3_1a4_scalar_setters.py b/psp_emu/harness/probe_asd163_1a3_1a4_scalar_setters.py
new file mode 100644
index 0000000..6daa0c7
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_1a3_1a4_scalar_setters.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 service-2 0x1A3/0x1A4 selected private scalar stores.
+
+Synthetic active channel records only. No HDCP victim, ASIC callback, or card
+request. Deliberately does not claim cross-client impact from a selected store.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE, REQ, STRIDE = 0xD33510, 0x400040, 92
+
+
+def main() -> None:
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1000)
+ record = BASE + 5*STRIDE
+ control = BASE + 6*STRIDE
+ asd.write(record, bytes(STRIDE))
+ asd.write(control, bytes(STRIDE))
+ asd.wr32(record+4, 1)
+ asd.wr32(control+4, 1)
+
+ def call(op: int, channel: int, scalar: int) -> int:
+ asd.write(REQ, bytes(0x40))
+ for i, v in enumerate((op, channel, scalar)):
+ asd.wr32(REQ+4*i, v)
+ rv = asd.run_function(0xD013B8, (REQ,), thumb=True,
+ count=30000, timeout=2_000_000)
+ assert not asd.faults, (hex(op),rv,asd.faults)
+ return rv
+
+ for op, off, val in ((0x1A3, 84, 0xDEADBEEF),
+ (0x1A4, 80, 0xFFFFFFFF)):
+ assert call(op, 32, val) == 5
+ assert asd.rd32(record+off) == 0
+ assert call(op, 5, val) == 0
+ assert asd.rd32(record+off) == val
+ assert asd.rd32(control+off) == 0
+ asd.wr32(record+4, 0)
+ assert call(op, 5, 0) == 5
+ assert asd.rd32(record+off) == val
+ asd.wr32(record+4, 1)
+ print(f'signed 0x{op:x}: active channel 5 +{off} = 0x{val:08x}; invalid/inactive negatives, adjacent unchanged')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_asd163_1a7_1a8_bitmap_release.py b/psp_emu/harness/probe_asd163_1a7_1a8_bitmap_release.py
new file mode 100644
index 0000000..8131b40
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_1a7_1a8_bitmap_release.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 hidden 0x1A7/0x1A8 allocator/release, synthetic state.
+
+No hardware state changes, live channel ownership proof, or sOS policy emulation.
+"""
+from pathlib import Path
+import sys
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+DISPATCH = 0xD013B8
+BASE = 0xD33510
+REQ, INPUT, OUT, BAD = 0x400040, 0x401000, 0x402000, 0xD33550
+BITMAP = BASE + 3572
+COUNT = BASE + 3576
+OWNER = BASE + 3444
+RECORD = BASE + 3124
+
+
+def run() -> None:
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1C000)
+ asd.write(REQ, b'\x00' * 0x40)
+ asd.write(INPUT, b'abcde' + b'\x00' * (0xA0 - 5))
+ asd.wr32(OUT, 0)
+ asd.wr32(REQ, 0x1A7)
+ asd.wr32(REQ + 4, 5) # synthetic channel index
+ asd.wr32(REQ + 8, INPUT)
+ asd.wr32(REQ + 12, 1) # byte count, dispatcher masks to byte
+ asd.wr32(REQ + 16, OUT)
+ rv = asd.run_function(DISPATCH, (REQ,), thumb=True, count=70000, timeout=2_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+ assert asd.rd32(OUT) == asd.rd32(BITMAP) == 1
+ assert asd.read(COUNT, 1) == b'\x01'
+ assert asd.read(RECORD, 5) == b'abcde'
+ assert asd.rd32(OWNER) == 5
+ print('signed 0x1A7: allocated global bit 0, record abcde, channel owner 5')
+
+ # Release only takes a pointer to a mask; no channel argument accompanies it.
+ asd.wr32(REQ, 0x1A8)
+ asd.wr32(REQ + 4, OUT)
+ rv = asd.run_function(DISPATCH, (REQ,), thumb=True, count=30000, timeout=2_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+ assert asd.rd32(OUT) == asd.rd32(BITMAP) == 0
+ assert asd.read(COUNT, 1) == b'\x00'
+ assert asd.rd32(OWNER) == 5 and asd.read(RECORD, 5) == b'abcde'
+ print('signed 0x1A8: caller mask frees global bit without checking owner; metadata stays')
+
+ asd.wr32(REQ + 4, BAD) # rejected private pointer in synthetic caller window
+ rv = asd.run_function(DISPATCH, (REQ,), thumb=True, count=30000, timeout=2_000_000)
+ assert rv == 2 and not asd.faults, (rv, asd.faults)
+ assert asd.rd32(BITMAP) == 0 and asd.read(COUNT, 1) == b'\x00'
+ print('negative control: private bitmap pointer rejected with status 2')
+
+
+if __name__ == '__main__':
+ run()
diff --git a/psp_emu/harness/probe_asd163_1a9_sparse_enumeration.py b/psp_emu/harness/probe_asd163_1a9_sparse_enumeration.py
new file mode 100644
index 0000000..bcce031
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_1a9_sparse_enumeration.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 0x1A9 enumeration after a normal sparse bitmap release.
+
+Synthetic ASD state/caller region only; no hardware 0x1A7/0x1A8/0x1A9 calls.
+"""
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE, DISPATCH = 0xD33510, 0xD013B8
+REQ, SOURCE, MASK, DEST, COUNT_PTR = 0x400040, 0x401000, 0x402000, 0x403000, 0x403200
+
+
+def rpc(asd: Component, op: int, *args: int) -> int:
+ asd.write(REQ, b'\0' * 0x40)
+ asd.wr32(REQ, op)
+ for i, arg in enumerate(args):
+ asd.wr32(REQ + 4 + 4*i, arg)
+ rv = asd.run_function(DISPATCH, (REQ,), thumb=True, count=70000, timeout=2_000_000)
+ assert not asd.faults, (op, rv, asd.faults)
+ return rv
+
+
+def read_list(asd: Component) -> tuple[int, bytes]:
+ asd.write(DEST, b'\xA5' * 0xA0)
+ asd.write(COUNT_PTR, b'\xA5')
+ rv = rpc(asd, 0x1A9, DEST, COUNT_PTR)
+ assert rv == 0, (rv, asd.faults)
+ return asd.read(COUNT_PTR, 1)[0], asd.read(DEST, 10)
+
+
+def run() -> None:
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1C000)
+ asd.write(SOURCE, b'abcdeFGHIJ' + b'\0' * (0xA0-10))
+ asd.wr32(MASK, 0)
+ assert rpc(asd, 0x1A7, 5, SOURCE, 2, MASK) == 0
+ assert asd.rd32(MASK) == 3 and asd.rd32(BASE+0xDF4) == 3
+ assert asd.read(BASE+0xDF8, 1) == b'\x02'
+ cnt, data = read_list(asd)
+ assert cnt == 2 and data == b'abcdeFGHIJ', (cnt, data.hex())
+ print('control: contiguous slots 0 and 1 enumerate 2 records correctly')
+
+ asd.wr32(MASK, 1) # release first slot; second remains allocated
+ assert rpc(asd, 0x1A8, MASK) == 0
+ assert asd.rd32(BASE+0xDF4) == 2 and asd.read(BASE+0xDF8, 1) == b'\x01'
+ assert asd.read(BASE+0xC34+5, 5) == b'FGHIJ'
+ cnt, data = read_list(asd)
+ assert cnt == 1 and data == b'\xA5' * 10, (cnt, data.hex())
+ print('sparse bitmap: status=0, advertised count=1, no record copied; '
+ 'live slot-1 record remains FGHIJ')
+
+
+if __name__ == '__main__':
+ run()
diff --git a/psp_emu/harness/probe_asd163_1aa_alloc_cleanup.py b/psp_emu/harness/probe_asd163_1aa_alloc_cleanup.py
new file mode 100644
index 0000000..8a88cc6
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_1aa_alloc_cleanup.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+"""Connected signed 0x1AA ID-list update -> signed 0x1A7 allocation cleanup.
+
+Synthetic channel/ID ownership. No accepted SRM, card effect or actual ASIC
+teardown: sub_D0AEB0 is stubbed; signed list and bitmap logic execute.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE, REQ, SOURCE, MASK = 0xD33510, 0x400040, 0x401000, 0x402000
+BITMAP, COUNT, LISTCOUNT = BASE+0xDF4, BASE+0xDF8, BASE+3577
+CHANNEL = 6
+
+
+def scenario(batch: bytes, expected_bitmap: int, expected_listcount: int,
+ expected_callbacks: int) -> None:
+ asd=Component('asd',image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000,0x1C000)
+ visits: list[int] = []
+ def skip_asic_teardown(uc,ea,size,data):
+ visits.append(ea)
+ uc.reg_write(UC_ARM_REG_R0,9) # modeled failure; signed sweep ignores result
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ asd.uc.hook_add(UC_HOOK_CODE,skip_asic_teardown,begin=0xD0AEB0,end=0xD0AEB1)
+
+ def request(op: int, *args: int, caller: int = 2) -> int:
+ asd.write(REQ,bytes(0x40))
+ for i,v in enumerate((op,*args)):
+ asd.wr32(REQ+4*i,v)
+ rv=asd.run_function(0xD013B8,(REQ,caller),thumb=True,
+ count=300_000,timeout=4_000_000)
+ assert not asd.faults,(hex(op),rv,asd.faults)
+ return rv
+
+ # One signed HDCP2 allocation, distinct from the seeded ID ABCDE.
+ asd.write(SOURCE,b'FGHIJ'+bytes(0xA0-5))
+ asd.wr32(MASK,0)
+ assert request(0x1A7,CHANNEL,SOURCE,1,MASK)==0
+ assert asd.rd32(BITMAP)==1 and asd.read(COUNT,1)==b'\x01'
+ assert asd.rd32(BASE+0xD74)==CHANNEL
+ asd.wr32(BASE+92*CHANNEL+4,1)
+ asd.wr32(BASE+92*CHANNEL+88,1)
+ asd.write(SOURCE,b'ABCDE'+bytes(155))
+ assert request(0x1AA,SOURCE,1,caller=7)==0
+ assert asd.rd32(BITMAP)==1 and not visits
+ assert asd.read(LISTCOUNT,1)==b'\x01'
+ asd.write(SOURCE,batch+bytes(160-len(batch)))
+ assert request(0x1AA,SOURCE,len(batch)//5,caller=7)==0
+ assert asd.rd32(BITMAP)==expected_bitmap
+ assert asd.read(COUNT,1)[0]==expected_bitmap
+ assert asd.read(LISTCOUNT,1)[0]==expected_listcount
+ assert len(visits)==expected_callbacks
+ print(f'batch={batch!r} ID count={expected_listcount} alloc bitmap={expected_bitmap} '
+ f'teardown-error stub visits={len(visits)} (synthetic caller 2 allocated, 7 updated)')
+
+
+def main() -> None:
+ scenario(b'FGHIJ',0,2,1) # single unique ID revokes signed allocation
+ scenario(b'ABCDEFGHIJ',1,1,0) # duplicate ABCDE suppresses FGHIJ
+ print('signed allocation remains allocated only when list-update duplicate drops its ID')
+
+
+if __name__=='__main__':
+ main()
diff --git a/psp_emu/harness/probe_asd163_1aa_allocator_blacklist.py b/psp_emu/harness/probe_asd163_1aa_allocator_blacklist.py
new file mode 100644
index 0000000..4cbe6e5
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_1aa_allocator_blacklist.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+"""ASD .163 0x1AA duplicate-drop changes signed 0x1A7 ID admission.
+
+Synthetic ID list; stub final 0x1AA revocation sweep. No accepted SRM,
+real HDCP client, ASIC callback, card request, or cross-client effect.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE,REQ,SOURCE,MASK = 0xD33510,0x400040,0x401000,0x402000
+LISTCOUNT,BITMAP,ALLOCCOUNT = BASE+3577,BASE+0xDF4,BASE+0xDF8
+
+
+def scenario(batch: bytes, expected_status: int, expected_allocs: int) -> None:
+ asd=Component('asd',image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000,0x1C000)
+ def skip_sweep(uc,ea,size,data):
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ asd.uc.hook_add(UC_HOOK_CODE,skip_sweep,begin=0xD08404,end=0xD08405)
+ def request(op:int,*args:int) -> int:
+ asd.write(REQ,bytes(0x40))
+ for i,v in enumerate((op,*args)):asd.wr32(REQ+4*i,v)
+ status=asd.run_function(0xD013B8,(REQ,),thumb=True,
+ count=160_000,timeout=3_000_000)
+ assert not asd.faults,(hex(op),status,asd.faults)
+ return status
+ def add_ids(data:bytes) -> None:
+ asd.write(SOURCE,data+bytes(160-len(data)))
+ assert request(0x1AA,SOURCE,len(data)//5)==0
+
+ add_ids(b'ABCDE')
+ add_ids(batch)
+ assert asd.read(LISTCOUNT,1)[0] == (2 if batch==b'FGHIJ' else 1)
+ asd.write(SOURCE,b'FGHIJ'+bytes(0xA0-5))
+ asd.wr32(MASK,0)
+ status=request(0x1A7,6,SOURCE,1,MASK)
+ assert status==expected_status,(status,batch)
+ assert asd.rd32(BITMAP)==expected_allocs
+ assert asd.read(ALLOCCOUNT,1)[0]==expected_allocs
+ print(f'list batch={batch!r}: signed 0x1A7 ID FGHIJ status={status}, '
+ f'allocated={expected_allocs}')
+
+
+def main() -> None:
+ scenario(b'FGHIJ',23,0) # listed token rejected
+ scenario(b'ABCDEFGHIJ',0,1) # duplicate silently drops FGHIJ, then admitted
+ print('conditional signed list-update defect propagates to allocation admission')
+
+
+if __name__=='__main__':
+ main()
diff --git a/psp_emu/harness/probe_asd163_1aa_duplicate_tail.py b/psp_emu/harness/probe_asd163_1aa_duplicate_tail.py
new file mode 100644
index 0000000..1ef30c3
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_1aa_duplicate_tail.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+"""ASD .163 0x1AA: a pre-existing duplicate suppresses later unique IDs.
+
+Signed dispatcher/dedup body execute; only final stateful revocation sweep
+sub_D08404 is stubbed. Synthetic ASD-private list, no accepted SRM/card run.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE, LIST, COUNT = 0xD33510, 0xD33510+3284, 0xD33510+3577
+REQ, SRC = 0x400040, 0x401000
+
+
+def scenario(batch: bytes, expect: bytes) -> None:
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1C000)
+ sweeps: list[int] = []
+ def skip_sweep(uc, ea, size, data):
+ sweeps.append(ea)
+ uc.reg_write(UC_ARM_REG_R0, 0)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+ asd.uc.hook_add(UC_HOOK_CODE, skip_sweep, begin=0xD08404, end=0xD08405)
+
+ def request(entries: bytes, count: int, ptr: int = SRC) -> int:
+ assert len(entries)==count*5 and count<=32
+ asd.write(SRC, entries + bytes(160-len(entries)))
+ asd.write(REQ,bytes(0x40))
+ for i,v in enumerate((0x1AA,ptr,count)):
+ asd.wr32(REQ+4*i,v)
+ rv=asd.run_function(0xD013B8,(REQ,),thumb=True,
+ count=200_000,timeout=3_000_000)
+ assert not asd.faults,(rv,asd.faults)
+ return rv
+
+ assert request(b'ABCDE',1)==0
+ assert asd.read(COUNT,1)==b'\x01' and asd.read(LIST,5)==b'ABCDE'
+ assert request(batch,len(batch)//5)==0
+ observed=asd.read(LIST,len(expect))
+ assert asd.read(COUNT,1)[0]==len(expect)//5,(observed,asd.read(COUNT,1))
+ assert observed==expect,(observed,expect)
+ assert len(sweeps)==2,sweeps
+ before=asd.read(LIST,20)
+ assert request(b'KLMNO',1,ptr=BASE)==2 # not an admitted caller input
+ assert asd.read(LIST,20)==before
+ print(f'input batch={batch!r} retained={observed!r}; sweep stubbed twice')
+
+
+def main() -> None:
+ scenario(b'FGHIJ',b'ABCDEFGHIJ') # solo unique must append
+ scenario(b'ABCDEFGHIJ',b'ABCDE') # duplicate first suppresses unique tail
+ scenario(b'FGHIJABCDEKLMNO',b'ABCDEFGHIJ') # duplicate suppresses later third
+ scenario(b'FGHIJFGHIJ',b'ABCDEFGHIJFGHIJ') # new duplicate within batch not detected
+ print('signed 0x1AA: earlier duplicate drops tail; in-batch duplicate is appended twice')
+
+
+if __name__=='__main__':
+ main()
diff --git a/psp_emu/harness/probe_asd163_200_asic20_output.py b/psp_emu/harness/probe_asd163_200_asic20_output.py
new file mode 100644
index 0000000..d31f07f
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_200_asic20_output.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+"""ASD .163 0x200 signed ASIC-20 output footprint; SYS_DRV RPC modeled.
+No live chip ID, real SYS_DRV result, or hardware effect.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE,UC_HOOK_MEM_WRITE
+from unicorn.arm_const import UC_ARM_REG_LR,UC_ARM_REG_PC,UC_ARM_REG_R0
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+REQ,OUT,CFG=0x400040,0x401000,0xD397B0
+
+def scenario(rpc_status:int,expected_offsets:list[int]):
+ asd=Component('asd',image_path=str(SIGNED));asd.uc.mem_map(0x400000,0x20000)
+ asd.wr32(CFG+32,20);asd.write(OUT,bytes([0xA5])*40)
+ asd.write(REQ,bytes(0x40));asd.wr32(REQ,0x200);asd.wr32(REQ+4,OUT)
+ hits=[];writes=[]
+ def model_rpc(uc,ea,size,_):
+ hits.append(ea)
+ uc.reg_write(UC_ARM_REG_R0,rpc_status)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ def record(uc,access,addr,size,value,_):
+ if OUT<=addr<OUT+40:writes.append((addr-OUT,size))
+ asd.uc.hook_add(UC_HOOK_CODE,model_rpc,begin=0xD019A4,end=0xD019A5)
+ asd.uc.hook_add(UC_HOOK_MEM_WRITE,record)
+ rv=asd.run_function(0xD013B8,(REQ,),thumb=True,count=130_000,timeout=2_000_000)
+ assert not asd.faults,(rv,asd.faults)
+ assert rv==0 and hits==[0xD019A4],(rv,hits)
+ assert sorted(writes)==sorted((o,1 if o==0 else 4)
+ for o in expected_offsets),writes
+ assert asd.read(OUT+24,16)==bytes([0xA5])*16
+ assert (asd.read(OUT+16,8)==bytes(8))==(rpc_status==0)
+ print(f'RPC modeled status {rpc_status}; signed 0x200 output stores {writes}; tail untouched')
+
+def main():
+ scenario(1,[0,4,8])
+ scenario(0,[0,4,8,16,20])
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_203_dio_config.py b/psp_emu/harness/probe_asd163_203_dio_config.py
new file mode 100644
index 0000000..5d554f7
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_203_dio_config.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+"""ASD .163 signed 0x205→0x203 bounded DIO record *read* branch.
+Synthetic global records only; intentionally avoids the 0x203 one-bit
+callback branch and any card/SVC fabric operation.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+REQ,SRC,BASE=0x400040,0x401000,0xD29080
+
+def main():
+ asd=Component('asd',image_path=str(SIGNED));asd.uc.mem_map(0x400000,0x20000)
+ def rpc(op,*args):
+ asd.write(REQ,bytes(0x40))
+ for i,x in enumerate((op,*args)):asd.wr32(REQ+4*i,x)
+ rv=asd.run_function(0xD013B8,(REQ,),thumb=True,count=100_000,timeout=2_000_000)
+ assert not asd.faults,(hex(op),rv,asd.faults)
+ return rv
+ ownmask,ownpayload=BASE+16,BASE+20
+ adjacent=BASE+40
+ marker=bytes(range(1,17));asd.write(ownpayload,marker)
+ asd.write(adjacent,bytes([0xCC])*16)
+ asd.write(SRC,bytes([0xA5])*16)
+ assert rpc(0x203,4,SRC,16)==2 # bounded index, no callback
+ assert asd.read(SRC,16)==bytes([0xA5])*16
+ assert rpc(0x205,0,1)==0 and rpc(0x205,0,2)==0
+ assert asd.rd32(ownmask)==3
+ assert rpc(0x203,0,SRC,16)==0
+ assert asd.read(SRC,16)==marker
+ assert asd.read(ownpayload,16)==marker
+ assert asd.read(adjacent,16)==bytes([0xCC])*16
+ asd.write(SRC,bytes(16))
+ assert rpc(0x203,0,SRC,16)==0 and asd.read(SRC,16)==bytes(16)
+ print('signed 0x205 one-bit setters select 0x203 global→caller copy; '
+ 'zero caller checksum skips it, adjacent synthetic record unchanged')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_203_dio_config_write.py b/psp_emu/harness/probe_asd163_203_dio_config_write.py
new file mode 100644
index 0000000..7babd03
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_203_dio_config_write.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+"""ASD .163 0x203 one-bit caller->global DIO record copy.
+The two post-copy stateful link callbacks are intercepted, not executed;
+synthetic record only, no live ASIC, hardware or second-client effect.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_PC,UC_ARM_REG_LR,UC_ARM_REG_R0
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+REQ,SRC,BASE=0x400040,0x401000,0xD29080
+
+def main():
+ asd=Component('asd',image_path=str(SIGNED));asd.uc.mem_map(0x400000,0x20000)
+ visits=[]
+ def skip_stateful(uc,ea,size,_):
+ visits.append(ea)
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ for ea in (0xD0BC80,0xD0DDDC):
+ asd.uc.hook_add(UC_HOOK_CODE,skip_stateful,begin=ea,end=ea+1)
+ def rpc(op,*args):
+ asd.write(REQ,bytes(0x40))
+ for i,x in enumerate((op,*args)):asd.wr32(REQ+4*i,x)
+ rv=asd.run_function(0xD013B8,(REQ,),thumb=True,count=100_000,timeout=2_000_000)
+ assert not asd.faults,(hex(op),rv,asd.faults)
+ return rv
+ payload,neighbor=BASE+20,BASE+40
+ asd.write(payload,bytes([0xAA])*16)
+ asd.write(neighbor,bytes([0xBB])*16)
+ marker=bytes(range(1,17));asd.write(SRC,marker)
+ assert rpc(0x203,0,SRC,16)==2 # no active bit: no write
+ assert asd.read(payload,16)==bytes([0xAA])*16 and not visits
+ assert rpc(0x205,0,1)==0
+ assert asd.rd32(BASE+16)==1
+ assert rpc(0x203,0,SRC,16)==0
+ assert asd.read(payload,16)==marker
+ assert asd.read(SRC,16)==marker and asd.read(neighbor,16)==bytes([0xBB])*16
+ assert visits==[0xD0BC80,0xD0DDDC],visits
+ print('signed 0x205->0x203 copied caller 16 bytes to synthetic global record; '
+ 'two later stateful helpers modeled, adjacent record untouched')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_204_asic20_output.py b/psp_emu/harness/probe_asd163_204_asic20_output.py
new file mode 100644
index 0000000..7a80368
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_204_asic20_output.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 0x204 ASIC-20 bounded-output synthetic callback check.
+
+Directly seeds the signed callback slot (does not run startup), explicitly
+models only SVC 0x7B fixed-register reads. No card or real fabric effect.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE, UC_HOOK_MEM_WRITE
+from unicorn.arm_const import UC_ARM_REG_PC, UC_ARM_REG_R1, UC_ARM_REG_R0
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+REQ,OUT,CFG=0x400040,0x401000,0xD397B0
+
+def main():
+ asd=Component('asd',image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000,0x20000)
+ asd.wr32(CFG+32,20)
+ asd.wr32(CFG+56,0xD0C5C5) # signed ASIC-20 sub_D0E10C slot
+ asd.wr32(0xD29048,0)
+ asd.write(OUT,bytes([0xA5])*32)
+ asd.write(REQ,bytes(0x40));asd.wr32(REQ,0x204);asd.wr32(REQ+4,OUT)
+ reads=[];writes=[]
+ def svc_read(uc,ea,size,_):
+ assert uc.reg_read(UC_ARM_REG_R1)!=0
+ value=(0x1234abcd,0x55556666,0x77778888)[len(reads)]
+ uc.mem_write(uc.reg_read(UC_ARM_REG_R1),value.to_bytes(4,'little'))
+ reads.append(ea)
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,ea+3) # skip only 16-bit Thumb SVC
+ def record_write(uc,access,address,size,value,_):
+ if OUT<=address<OUT+32:writes.append((address,size))
+ for ea in (0xD0C832,0xD0C854,0xD0C862):
+ asd.uc.hook_add(UC_HOOK_CODE,svc_read,begin=ea,end=ea+1)
+ asd.uc.hook_add(UC_HOOK_MEM_WRITE,record_write)
+ result=asd.run_function(0xD013B8,(REQ,),thumb=True,count=150_000,timeout=3_000_000)
+ assert not asd.faults,(result,asd.faults)
+ assert result==0 and reads==[0xD0C832,0xD0C854,0xD0C862],(result,reads)
+ assert writes==[(OUT+i,4) for i in (0,4,8,12)],writes
+ assert asd.read(OUT+16,16)==bytes([0xA5])*16
+ print('signed 0x204 ASIC-20 path: three modeled fixed-register reads, '
+ 'four dword caller stores within validated 32 bytes; tail untouched')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_205_206_invalid_side_effect.py b/psp_emu/harness/probe_asd163_205_206_invalid_side_effect.py
new file mode 100644
index 0000000..98c182d
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_205_206_invalid_side_effect.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+"""Signed .163 0x205/0x206 invalid mask error-return state mutation.
+
+Only synthetic ASD globals and one modeled SVC 0x57; no card, HDCP or
+independent hardware security effect. Uses signed dispatcher and leaves.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_PC
+
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+REQ,GLOBAL,STATE,DEADLINE=0x400040,0xD29080+16+20,0xD397B0+32,0xD1BA24
+
+def scenario(op:int, mask:int, initial_state:int, expected_state:int,
+ expected_deadline:int, expected_status:int):
+ asd=Component('asd',image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000,0x2000)
+ svc57=[]
+ def skip_svc57(uc,ea,size,user_data):
+ svc57.append(ea)
+ uc.reg_write(UC_ARM_REG_PC,0xD0B767) # model only SVC; signed STR already ran
+ asd.uc.hook_add(UC_HOOK_CODE,skip_svc57,begin=0xD0B764,end=0xD0B765)
+ asd.wr32(STATE,20) # signed sub_D0EB3C uses status-table entry zero
+ asd.wr32(GLOBAL,initial_state)
+ asd.wr32(DEADLINE,0x12345678)
+ asd.write(REQ,bytes(0x40))
+ asd.wr32(REQ,op)
+ asd.wr32(REQ+4,1)
+ asd.wr32(REQ+8,mask)
+ status=asd.run_function(0xD013B8,(REQ,),thumb=True,
+ count=100_000,timeout=3_000_000)
+ assert not asd.faults,(hex(op),status,asd.faults)
+ assert status==expected_status,(hex(op),status)
+ assert asd.rd32(GLOBAL)==expected_state,(hex(op),hex(asd.rd32(GLOBAL)))
+ assert asd.rd32(DEADLINE)==expected_deadline,(hex(op),hex(asd.rd32(DEADLINE)))
+ assert len(svc57)==(1 if op==0x205 else 0),(hex(op),svc57)
+ print(f'op={op:#x} mask={mask:#x} return={status:#x} '
+ f'word={asd.rd32(GLOBAL):#x} deadline={asd.rd32(DEADLINE):#x}')
+
+
+def main():
+ scenario(0x206,0,0,0,0xffffffff,2) # invalid bit, timeout side effect
+ scenario(0x206,1,1,0,0xffffffff,0) # valid clear, zero state
+ scenario(0x206,0,1,1,0x12345678,2) # still-active state: no disable
+ scenario(0x205,0,0,0,1000,2) # invalid bit, session-op deadline write
+ scenario(0x205,1,0,1,1000,0) # valid set control
+ print('signed dispatcher does not gate session/time side effects on bitmask validation')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_207_asic20_output.py b/psp_emu/harness/probe_asd163_207_asic20_output.py
new file mode 100644
index 0000000..4da06e4
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_207_asic20_output.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 0x207 ASIC-20 callback output width with synthetic
+config and source pointer. No card, real private values or fabric access.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+REQ,OUT,SOURCE=0x400040,0x400100,0x400200
+SLOT,PTR=0xD397B0+0x3C,0xD39848
+
+def main():
+ asd=Component('asd',image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000,0x3000)
+ oldslot=asd.rd32(SLOT);oldptr=asd.rd32(PTR)
+ def run(out):
+ asd.write(REQ,bytes(0x40));asd.wr32(REQ,0x207);asd.wr32(REQ+4,out)
+ return asd.run_function(0xD013B8,(REQ,),thumb=True,count=100_000,timeout=2_000_000)
+ try:
+ asd.wr32(SLOT,0xD0C351) # signed ASIC-20 thunk to signed getter
+ asd.wr32(PTR,SOURCE)
+ asd.wr32(SOURCE,0x12345678) # synthetic public marker
+ asd.write(OUT,b'Z'*16)
+ ok=run(OUT)
+ assert ok==0 and asd.read(OUT,16)==bytes.fromhex('78 56 34 12')+b'Z'*12,(ok,asd.read(OUT,16).hex())
+ bad=run(PTR)
+ assert bad==2 and asd.rd32(PTR)==SOURCE and asd.rd32(SOURCE)==0x12345678,bad
+ assert not asd.faults,asd.faults
+ print('signed ASD 0x207 ASIC20 synthetic callback: validated output received exactly 4 public bytes, +4..+15 intact; ASD-private output pointer rejected; no live pointer value')
+ finally:
+ asd.wr32(SLOT,oldslot);asd.wr32(PTR,oldptr)
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_208_sysdrv1079_forward.py b/psp_emu/harness/probe_asd163_208_sysdrv1079_forward.py
new file mode 100644
index 0000000..16a8d08
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_208_sysdrv1079_forward.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+"""Connect signed ASD .163 0x208 event packing to signed SYS_DRV .54
+0x1079, capturing SVC7C. Synthetic SVCF2/sOS caps; no real fabric.
+"""
+from pathlib import Path
+import sys
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component,sysdrv_env
+ROOT=Path(__file__).resolve().parents[2]
+ASD=ROOT/'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+SYSDRV=ROOT/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+REQ=0x400040
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SYSDRV.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1 and sd.rd32(0xE26314)==2
+ sd.svc.register(0x76,lambda c,n:int((c.uc.reg_read(UC_ARM_REG_R0)&~0x10)==0))
+ writes=[];sent=[]
+ sd.svc.register(0x7C,lambda c,n:writes.append(tuple(c.uc.reg_read(reg) for reg in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2))) or 0)
+ sr=sd.alloc(0x100)
+ asd=Component('asd',image_path=str(ASD))
+ asd.uc.mem_map(0x400000,0x2000)
+ def forward(c,n):
+ service=c.uc.reg_read(UC_ARM_REG_R0)
+ msg=c.uc.reg_read(UC_ARM_REG_R1)
+ op=asd.rd32(msg);value=asd.rd32(msg+14*4)
+ sent.append((service,op,value))
+ assert service==1 and op==0x1079,(service,hex(op))
+ sd.write(sr,bytes(0x100))
+ for i,v in ((0,op),(1,2),(14,value)):
+ sd.wr32(sr+4*i,v)
+ return sd.run_function(0xE087E4,(sr,2,0,0),count=200_000,timeout=2_000_000)
+ asd.svc.register(0xF2,forward)
+ def run(value):
+ asd.write(REQ,bytes(0x40));asd.wr32(REQ,0x208);asd.wr32(REQ+4,value)
+ return asd.run_function(0xD013B8,(REQ,),thumb=True,count=200_000,timeout=2_000_000)
+ assert run(0x11223344)==0 and sent==[(1,0x1079,0x44331122)] and writes==[(0x3B30600,0x44331122,4)],(sent,writes)
+ assert run(0x112233EE)==0 and sent[-1]==(1,0x1079,0xEE331122) and len(writes)==1,(sent,writes)
+ assert not asd.faults and not sd.faults,(asd.faults,sd.faults)
+ print('signed ASD 0x208 packed 0x11223344 -> signed SYS_DRV 0x1079 requested fixed SVC7C word 0x44331122; low-byte EE input -> SYS_DRV top-byte EE skip; only synthetic relay/capture, no hardware')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_asd163_20b_private_store.py b/psp_emu/harness/probe_asd163_20b_private_store.py
new file mode 100644
index 0000000..31f0e5a
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_20b_private_store.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+"""Isolated signed ASD .163 RPC 0x20B: missing nested-output pointer check.
+
+Synthetic state and ASD-private mapping; does NOT emulate sOS SVC 0xF2,
+GCP ROP, or live card PTE/permissions. No device or secret state touched.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_MEM_WRITE
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+DISPATCH = 0xD013B8
+OUTPUT_HANDLER = 0xD0DC14
+STATUS_GLOBAL = 0xD1BA28
+CALLER_REQ = 0x400040
+SYNTH_PRIVATE = 0xD4AF00 # isolated emulator's ASD RAM, not a measured card address
+
+
+def test_state(value: int) -> None:
+ asd = Component('asd', image_path=str(SIGNED))
+ assert Path(asd.image_path).read_bytes() == SIGNED.read_bytes()
+ asd.uc.mem_map(0x400000, 0x1C000) # synthetic GCP-sized caller window
+ asd.wr32(STATUS_GLOBAL, value) # bypass optional state-255 callback
+ asd.wr32(SYNTH_PRIVATE, 0xA5A5A5A5)
+ asd.write(CALLER_REQ, b'\x00' * 0x40)
+ asd.wr32(CALLER_REQ + 0, 0x20B)
+ asd.wr32(CALLER_REQ + 4, SYNTH_PRIVATE)
+ writes = []
+
+ def observe(uc, access, addr, size, val, data):
+ if addr == SYNTH_PRIVATE:
+ writes.append((addr, size, val & 0xFFFFFFFF))
+ asd.uc.hook_add(UC_HOOK_MEM_WRITE, observe, begin=SYNTH_PRIVATE,
+ end=SYNTH_PRIVATE + 3)
+ rv = asd.run_function(DISPATCH, (CALLER_REQ,), thumb=True,
+ count=30000, timeout=2_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+ assert writes == [(SYNTH_PRIVATE, 4, value)] and asd.rd32(SYNTH_PRIVATE) == value, writes
+ print('signed dispatch 0x20B: state', value, 'private store at synthetic ASD RAM:',
+ 'size=4 value=', value, 'status=', hex(rv))
+
+ # Negative control: signed 0x207 explicitly checks the same pointer with
+ # asd_validate_ptr(ptr,4) and rejects this ASD-private destination.
+ asd.wr32(CALLER_REQ, 0x207)
+ asd.wr32(SYNTH_PRIVATE, 0xA5A5A5A5)
+ before = len(writes)
+ rejected = asd.run_function(DISPATCH, (CALLER_REQ,), thumb=True,
+ count=30000, timeout=2_000_000)
+ assert rejected == 2 and not asd.faults and len(writes) == before
+ assert asd.rd32(SYNTH_PRIVATE) == 0xA5A5A5A5
+ print('control opcode 0x207: private pointer rejected', rejected)
+
+
+if __name__ == '__main__':
+ test_state(0)
+ test_state(1)
diff --git a/psp_emu/harness/probe_asd163_srm_hdcp1_multi_revocation.py b/psp_emu/harness/probe_asd163_srm_hdcp1_multi_revocation.py
new file mode 100644
index 0000000..a06b986
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_srm_hdcp1_multi_revocation.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+"""Signed ASD .163 HDCP1/0x18E SRM bitmap cleanup, synthetic only.
+
+Uses signed 0x18E allocator and signed SRM update helper. Plants UNSIGNED v9
+SRM records in synthetic ASD RAM, bypassing signature acceptance. Executes the
+signed channel-state helper and stubs only the ASIC callback. No card request.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE, SRM = 0xD33510, 0xD15030
+BITMAP, COUNT = BASE+4092, BASE+4108 # HDCP1 128-slot table, distinct from HDCP2 +0xDF4
+REQ, SOURCE, MASK = 0x400040, 0x401000, 0x402000
+
+
+def allocate(asd: Component, channel: int, token: bytes) -> None:
+ assert len(token) == 5
+ asd.write(SOURCE, token + b'\0' * (0x280 - 5))
+ asd.write(MASK, b'\0' * 16)
+ asd.write(REQ, b'\0' * 0x40)
+ for i, v in enumerate((0x18E, channel, SOURCE, 1, MASK)):
+ asd.wr32(REQ + 4*i, v)
+ rv = asd.run_function(0xD013B8, (REQ,), thumb=True,
+ count=60000, timeout=2_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+
+
+def run_case(slot0_match: bool, expected_bitmap: int, expected_calls: int) -> None:
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1C000) # synthetic caller window, NOT live sOS PTEs
+ allocate(asd, 5, b'ABCDE')
+ allocate(asd, 6, b'FGHIJ')
+ assert asd.rd32(BITMAP) == 3 and asd.read(COUNT, 1) == b'\x02'
+ assert [asd.rd32(BASE+3580+4*i) for i in (0, 1)] == [5, 6]
+ for channel in (5, 6):
+ asd.wr32(BASE+92*channel+4, 1)
+ asd.wr32(BASE+92*channel+88, 1) # synthetic active link state
+
+ # The signed HDCP1 SRM matcher reverses each stored five-byte record for
+ # v9 membership: ABCDE -> EDCBA, FGHIJ -> JIHGF.
+ revoked = (b'EDCBA' if slot0_match else b'') + b'JIHGF'
+ asd.write(SRM, b'\0' * 0x1400)
+ asd.write(SRM, b'\x91')
+ asd.write(SRM+6, b'\x01\x88')
+ asd.write(SRM+8, (len(revoked)//5 << 6).to_bytes(2, 'little'))
+ asd.write(SRM+12, revoked)
+
+ states, callbacks = [], []
+ asd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, size, data: states.append(ea),
+ begin=0xD0B438, end=0xD0B439)
+ def skip_asic(uc, ea, size, data):
+ callbacks.append(ea)
+ uc.reg_write(UC_ARM_REG_R0, 0)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+ asd.uc.hook_add(UC_HOOK_CODE, skip_asic, begin=0xD0A7A8, end=0xD0A7A9)
+
+ rv = asd.run_function(0xD0CB70, (0,), thumb=True,
+ count=200000, timeout=4_000_000)
+ assert rv == 0 and not asd.faults, (rv, asd.faults)
+ assert len(states) == len(callbacks) == expected_calls, (states, callbacks)
+ for channel in ((5, 6) if slot0_match else (6,)):
+ assert asd.rd32(BASE+92*channel+88) == 0
+ assert asd.rd32(BITMAP) == expected_bitmap and asd.read(COUNT, 1) == b'\x01', (
+ hex(asd.rd32(BITMAP)), asd.read(COUNT, 1).hex())
+ print(f'HDCP1 signed helper: {expected_calls} match(es), '
+ f'bitmap={expected_bitmap:02b}, count=1, ASIC callback stubbed')
+
+
+def main() -> None:
+ run_case(False, expected_bitmap=1, expected_calls=1)
+ run_case(True, expected_bitmap=2, expected_calls=2)
+ print('single slot-1 match clears bit1; dual match visits both channel '
+ 'helpers but leaves bit1 allocated (UNSIGNED planted SRM only)')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_asd163_srm_multi_revocation.py b/psp_emu/harness/probe_asd163_srm_multi_revocation.py
new file mode 100644
index 0000000..34251fc
--- /dev/null
+++ b/psp_emu/harness/probe_asd163_srm_multi_revocation.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+"""Synthetic ASD .163 SRM revocation: two matches, only first bitmap bit cleared.
+
+Direct signed helper entry with a planted UNSIGNED SRM header. The signed
+channel-state helper runs; only the per-channel ASIC/link callback is stubbed
+at entry to avoid fabric activity. The signed bitmap cleanup is unmodified. This is
+NOT a valid signed-SRM or live-device exploit test.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/versions_win/elf_all/asd_33.0.0.163_18debcc4.elf'
+BASE, SRM = 0xD33510, 0xD15030
+BITMAP, COUNT = BASE+0xDF4, BASE+0xDF8
+REQ, SOURCE, MASK = 0x400040, 0x401000, 0x402000
+
+
+def allocate(asd: Component, owner: int, token: bytes) -> None:
+ assert len(token) == 5
+ asd.write(SOURCE, token + b'\0' * (0xA0 - 5))
+ asd.wr32(MASK, 0)
+ asd.write(REQ, b'\0' * 0x40)
+ for i, v in enumerate((0x1A7, owner, SOURCE, 1, MASK)):
+ asd.wr32(REQ + 4*i, v)
+ status = asd.run_function(0xD013B8, (REQ,), thumb=True,
+ count=50000, timeout=2_000_000)
+ assert status == 0 and not asd.faults, (status, asd.faults)
+
+
+def run_case(revoked: bytes, expected_bitmap: int, expected_stubs: int) -> None:
+ assert len(revoked) in (5, 10)
+ asd = Component('asd', image_path=str(SIGNED))
+ asd.uc.mem_map(0x400000, 0x1C000)
+ allocate(asd, 5, b'ABCDE')
+ allocate(asd, 6, b'FGHIJ')
+ assert asd.rd32(BITMAP) == 3 and asd.read(COUNT, 1) == b'\x02'
+ assert [asd.rd32(BASE+0xD74+4*i) for i in range(2)] == [5, 6]
+ for owner in (5, 6):
+ asd.wr32(BASE + 92*owner + 4, 1) # synthetic active channel
+ asd.wr32(BASE + 92*owner + 88, 1) # nonzero -> revocation queue
+
+ # Directly plant synthetic SRM v9 records. Signature verification is NOT
+ # run; this data must never be described as an accepted/valid signed SRM.
+ asd.write(SRM, b'\0' * 0x1400)
+ asd.write(SRM, b'\x91')
+ asd.write(SRM + 6, b'\x01\x88') # declared v9 length 0x188 (>0x186)
+ asd.write(SRM + 8, (len(revoked)//5 << 6).to_bytes(2, 'little'))
+ asd.write(SRM + 12, revoked)
+
+ state_visits: list[int] = []
+ link_stubs: list[int] = []
+ def observe_signed_state_helper(uc, address, size, data):
+ state_visits.append(address) # execute the actual instruction afterward
+ def skip_link_callback(uc, address, size, data):
+ link_stubs.append(address)
+ uc.reg_write(UC_ARM_REG_R0, 0)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+ asd.uc.hook_add(UC_HOOK_CODE, observe_signed_state_helper,
+ begin=0xD0B438, end=0xD0B439)
+ asd.uc.hook_add(UC_HOOK_CODE, skip_link_callback,
+ begin=0xD0AEB0, end=0xD0AEB1)
+
+ status = asd.run_function(0xD0CB70, (0,), thumb=True,
+ count=180000, timeout=4_000_000)
+ assert status == 0 and not asd.faults, (status, asd.faults)
+ assert state_visits.count(0xD0B438) == expected_stubs, state_visits
+ assert link_stubs.count(0xD0AEB0) == expected_stubs, link_stubs
+ for owner in ((6,) if expected_stubs == 1 else (5, 6)):
+ assert asd.rd32(BASE + 92*owner + 88) == 0 # signed state-helper effect
+ assert asd.rd32(BITMAP) == expected_bitmap and asd.read(COUNT, 1) == b'\x01', (
+ hex(asd.rd32(BITMAP)), asd.read(COUNT, 1).hex())
+ print(f'signed helper: {expected_stubs} SRM record match(es), '
+ f'cleanup bitmap=0b{expected_bitmap:02b} count=1; '
+ 'channel-state helper ran, ASIC callback stubbed, no signature checked')
+
+
+def run() -> None:
+ run_case(b'FGHIJ', expected_bitmap=1, expected_stubs=1)
+ run_case(b'ABCDEFGHIJ', expected_bitmap=2, expected_stubs=2)
+ print('controls: allocator installed owners 5/6 both times; '
+ 'a single slot-1 revocation clears slot 1, but two revocations '
+ 'clear only slot 0')
+
+
+if __name__ == '__main__':
+ run()
diff --git a/psp_emu/harness/probe_asd20b_model_pte.py b/psp_emu/harness/probe_asd20b_model_pte.py
new file mode 100644
index 0000000..6012b51
--- /dev/null
+++ b/psp_emu/harness/probe_asd20b_model_pte.py
@@ -0,0 +1,92 @@
+"""Read-only emulator estimate for the hardware order: LOAD_ASD before LOAD_TA GCP."""
+import sys, struct
+sys.path.insert(0,'/home/user/dev/amd-psp-re/psp_emu/harness')
+sys.path.insert(0,'/home/user/dev/amd-psp-re/psp_emu')
+import gcp_fe08_fullsys as H
+from pspemu import armmmu
+from unicorn import UC_HOOK_BLOCK
+BM, M = H.BM, H.M
+c, ccp, irqs, exc = BM.build()
+run, _ = M.make_det_runner(c, irqs)
+run(6_000_000)
+M.install_debug_gpio(c)
+box = M.Mailbox(c, irqs)
+c.uc.mem_write(BM.RING_PA,b'\x00'*BM.RING_SZ)
+rp = bytearray(0x1c)
+struct.pack_into('<I', rp,0x10,BM.RING_PA)
+struct.pack_into('<I', rp,0x18,BM.RING_SZ)
+st,_ = box.submit(2,bytes(rp),timeout=12,runner=run)
+assert st==0,st
+ring=M.Ring(c,irqs,BM.RING_PA,BM.RING_SZ,BM.CMDBUF_PA,run)
+c.uc.mem_write(H.PLATFORM_ID_BYTE,bytes([H.NAVI31_ASIC_ID]))
+asd=open(H.ASD_FULL,'rb').read()
+base=0x04600000
+for b in (base,base+M.Ring.ALIAS):
+ try:c.uc.mem_map(b&~0xfff,(len(asd)+0xffff)&~0xfff)
+ except Exception:pass
+ c.uc.mem_write(b,asd)
+obs=[];count=[0]
+def signed_sos_process_l2(uc,label):
+ # sOS .54 sub_6D84: 0xD00000..0xDFFFFF uses sub_4350(pid),
+ # whose per-process table lives through pointer at 0x8114.
+ rd=lambda a:int.from_bytes(uc.mem_read(a,4),'little')
+ context=rd(0x80D8)
+ implicit=uc.mem_read(context+75,1)[0]
+ override=uc.mem_read(context+76,1)[0]
+ pid=implicit if override==255 else override
+ proc_table=rd(0x8114)
+ l2=rd(proc_table+96*pid+92)
+ print('%s signed-sOS pid=%d implicit=%d override=%d L2=%#x'%(label,pid,implicit,override,l2),flush=True)
+ for va in (0xD1BA28,0xD29080,0xD39834,0xD4AF00):
+ pte=rd(l2+4*((va>>12)&255)) if l2 else 0
+ print('%s VA %#x proc-PTE %#x PA %#x AP=%d APX=%d'%(label,va,pte,(pte&0xfffff000)|(va&0xfff) if pte&3 else 0,(pte>>4)&3,(pte>>9)&1),flush=True)
+def on_asd(uc,a,size,user):
+ count[0]+=1
+ if count[0]==1:
+ signed_sos_process_l2(uc,'ASD first')
+ n=armmmu.ttbcr(uc)&7;tt=armmmu.read_cp15(uc,2,0,0,0)&~((1<<(14-n))-1)
+ for va0 in (0x41061C,0xD1BA28,0xD29080,0xD39834,0xD4AF00):
+ i1=tt+4*(va0>>20);e1=int.from_bytes(uc.mem_read(i1,4),'little')
+ if (e1&3)==1:
+ i2=(e1&0xfffffc00)+4*((va0>>12)&255)
+ e2=int.from_bytes(uc.mem_read(i2,4),'little')
+ pa0=(e2&0xfffff000)|(va0&4095)
+ print('ASD PTE VA %#x L1@%#x=%#x L2@%#x=%#x phys=%#x AP=%d APX=%d'%(va0,i1,e1,i2,e2,pa0,(e2>>4)&3,(e2>>9)&1),flush=True)
+ else:print('ASD PTE VA %#x L1=%#x type=%d'%(va0,e1,e1&3),flush=True)
+ if count[0]<=3 or count[0]%1024==0:
+ va=0xD4AF00
+ obs.append((a,armmmu.ttbr0(uc),armmmu.translate_live(uc,va)))
+c.uc.hook_add(UC_HOOK_BLOCK,on_asd,begin=0xD00100,end=0xDFFFFF)
+ld=bytearray(0x20)
+for off,val in ((0,base),(8,len(asd)),(12,H.GW.CMDBUF2_PA+0x10000),(20,0x1000)):
+ struct.pack_into('<I',ld,off,val)
+print('LOAD_ASD first, container',H.ASD_FULL,flush=True)
+ring.submit(4,bytes(ld),timeout=30,probe=[])
+BM.drain_until(run,lambda:ring.response()[1]!=0 or ring.response()[0]!=0,slices=4,blocks=2_000_000)
+print('ASD blocks',count[0],'sampled VA->PA',[(hex(a),hex(tt),hex(pa) if pa is not None else None) for a,tt,pa in obs][:15],flush=True)
+print('post-ASD ring',ring.response(),'service2',c.uc.mem_read(H.SERVICE_TABLE+2,1)[0],flush=True)
+print('post-ASD current TTBR0',hex(armmmu.ttbr0(c.uc)),'D4AF00 translation',hex(armmmu.translate_live(c.uc,0xD4AF00) or 0),flush=True)
+if not obs or obs[0][2] is None:raise SystemExit(1)
+# Now load GCP in the same order as the hardware script and sample the shared MMU map.
+cont=H.NT.pick_container('gcp')
+img=open(cont,'rb').read()
+for b in (H.FT.TA_IMAGE_PA,H.FT.TA_IMAGE_PA+M.Ring.ALIAS):
+ c.uc.mem_write(b,img)
+for b in (H.GW.CMDBUF2_PA,H.GW.CMDBUF2_PA+M.Ring.ALIAS):
+ try:c.uc.mem_map(b&~0xfff,0x8000)
+ except Exception:pass
+ld=bytearray(0x20)
+for off,val in ((0,H.FT.TA_IMAGE_PA),(8,len(img)),(12,H.GW.CMDBUF2_PA),(20,0x8000)):
+ struct.pack_into('<I',ld,off,val)
+gcp=[]
+def on_gcp(uc,a,size,user):
+ if not gcp:
+ signed_sos_process_l2(uc,'GCP first')
+ gcp.append((a,armmmu.ttbr0(uc),armmmu.translate_live(uc,0x41061C),armmmu.translate_live(uc,0xD4AF00)))
+c.uc.hook_add(UC_HOOK_BLOCK,on_gcp,begin=0x400100,end=0x40FFFF)
+ring.submit(1,bytes(ld),timeout=30,probe=[])
+BM.drain_until(run,lambda:bool(gcp) and ring.response()[1]!=0,slices=40,blocks=2_000_000)
+print('GCP first block TTBR0, GCP VA->PA, ASD VA->PA:',[(hex(a),hex(tt),hex(pg) if pg is not None else None,hex(pa) if pa is not None else None) for a,tt,pg,pa in gcp],flush=True)
+print('post-GCP ring',ring.response(),'service2',c.uc.mem_read(H.SERVICE_TABLE+2,1)[0],flush=True)
+print('post-GCP current TTBR0',hex(armmmu.ttbr0(c.uc)),'ASD target PA',hex(armmmu.translate_live(c.uc,0xD4AF00) or 0),flush=True)
+if not gcp:raise SystemExit(1)
diff --git a/psp_emu/harness/probe_hdcp52_alloc_failure.py b/psp_emu/harness/probe_hdcp52_alloc_failure.py
new file mode 100644
index 0000000..6594903
--- /dev/null
+++ b/psp_emu/harness/probe_hdcp52_alloc_failure.py
@@ -0,0 +1,68 @@
+"""Bounded signed-HDCP .52 allocation-failure trace (no hardware calls).
+
+The service-1 stub compares failing/successful opcode 0x30 responses,
+leaves the out-handle untouched only on failure, and logs the subsequent
+opcode 0x33 request. It does *not* emulate the real SYS_DRV fatal path;
+that remains signed-code evidence, separately.
+"""
+from __future__ import annotations
+
+import os
+import sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+from pspemu import Component, consts # noqa: E402
+
+SIGNED_HDCP = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), "..", "..", "psp_out", "versions_win", "elf_all",
+ "hdcp_23.0.0.52_c6b25da3.elf"))
+
+
+class ControlledCrypto:
+ def __init__(self, fail_allocate):
+ self.fail_allocate = fail_allocate
+ self.calls = []
+
+ def __call__(self, comp, req):
+ op = comp.rd32(req)
+ field38 = comp.rd32(req + 0x38) # type for 0x30, handle for 0x33
+ self.calls.append((op, field38))
+ if op == 0x30:
+ if self.fail_allocate:
+ # No write to req+0x40: matches the signed SYS_DRV failure case.
+ return 0x80000001
+ comp.wr32(req + 0x40, 0x10)
+ return 0
+ if op == 0x33:
+ return 0x80000001
+ if op == 0x31:
+ return 0 # successful allocation is retired on populate error
+ raise AssertionError(f"unexpected SYS_DRV opcode {op:#x}")
+
+
+def main():
+ for fail, expected_handle in ((True, 0), (False, 0x10)):
+ comp = Component("hdcp", image_path=SIGNED_HDCP)
+ crypto = ControlledCrypto(fail_allocate=fail)
+ comp.set_rpc_services({consts.SERVICE_CRYPTO: crypto})
+ src = comp.alloc(0x200)
+ comp.write(src + 0xC8, b"\x12" * 0x20)
+ # Four AAPCS args for the signed routine, with R0 non-null. This
+ # tests its local error path, not host HDCP message-11 reachability.
+ ret = comp.run_function(0x4042C4, (src, 0x20, 0, 0), thumb=True,
+ count=50000, timeout=2_000_000)
+ print("signed_hdcp_image:", os.path.basename(comp.image_path))
+ print("fail_allocate:", fail)
+ print("svc1_calls (opcode, field+0x38):",
+ [(hex(op), hex(field38)) for op, field38 in crypto.calls])
+ print("hdcp_return:", hex(ret))
+ print("faults:", comp.faults)
+ expected = [(0x30, 0xA0000004), (0x33, expected_handle)]
+ if not fail:
+ expected.append((0x31, 0x10))
+ assert crypto.calls == expected, crypto.calls
+ assert ret == 0x80000001 and not comp.faults
+
+
+if __name__ == "__main__":
+ main()
diff --git a/psp_emu/harness/probe_hdcp52_connected_zero_handle.py b/psp_emu/harness/probe_hdcp52_connected_zero_handle.py
new file mode 100644
index 0000000..8af1352
--- /dev/null
+++ b/psp_emu/harness/probe_hdcp52_connected_zero_handle.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+"""One connected signed-HDCP -> signed-SYS_DRV emulator path.
+
+Inject ONLY a no-free-slot return at the real SYS_DRV 0x30 allocator
+helper (no fabricated out-handle). Forward the resulting real signed HDCP
+0x33 request to the real signed SYS_DRV 0x33 handler, stopping before its
+fatal routine executes. This is a
+bounded two-component Python bridge, NOT full sOS boot, card reachability,
+or honest allocation exhaustion.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component, consts # noqa: E402
+
+REPO = Path(__file__).resolve().parents[2]
+HDCP = REPO / 'psp_out/versions_win/elf_all/hdcp_23.0.0.52_c6b25da3.elf'
+SYSDRV = REPO / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+HDCP_CALLER = 0x4042C4
+SYSDRV_ALLOCATE = 0xE1CAD4
+NO_FREE_SLOT = 0xE0243C
+SYSDRV_POPULATE = 0xE1EA54
+SYSDRV_FATAL = 0xE0F890
+
+
+def main() -> None:
+ sd = Component('sys_drv', image_path=str(SYSDRV))
+ hdcp = Component('hdcp', image_path=str(HDCP))
+ sd_req = sd.alloc(0x80)
+ sd_attrs = sd.alloc(0x20)
+ observed, fatal, no_slot, alloc_returns = [], [], [], []
+
+ def force_no_slot(uc, ea, size, user):
+ no_slot.append(ea)
+ uc.reg_write(UC_ARM_REG_R0, 0xFFFFFFFF)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+
+ sd.uc.hook_add(UC_HOOK_CODE, force_no_slot,
+ begin=NO_FREE_SLOT, end=NO_FREE_SLOT)
+
+ def stop_before_fatal(uc, ea, size, user):
+ fatal.append(ea)
+ uc.emu_stop()
+
+ sd.uc.hook_add(UC_HOOK_CODE, stop_before_fatal,
+ begin=SYSDRV_FATAL, end=SYSDRV_FATAL)
+
+ def service_one(caller: Component, request: int) -> int:
+ opcode, field38 = caller.rd32(request), caller.rd32(request + 0x38)
+ observed.append((opcode, field38))
+ if opcode == 0x30:
+ assert field38 == 0xA0000004, observed
+ sd.write(sd_req, caller.read(request, 0x7C))
+ rv = sd.run_function(SYSDRV_ALLOCATE, (sd_req + 0x38, 1),
+ thumb=True, count=30000, timeout=2_000_000)
+ alloc_returns.append(rv)
+ assert no_slot == [NO_FREE_SLOT] and rv != 0 and not sd.faults
+ assert sd.rd32(sd_req + 0x40) == 0 # failed allocator didn't issue handle
+ return rv
+ assert opcode == 0x33 and field38 == 0, observed
+ sd.write(sd_req, caller.read(request, 0x7C))
+ sd.run_function(SYSDRV_POPULATE, (sd_req, sd_attrs, 1, 1),
+ thumb=True, count=20000, timeout=2_000_000)
+ assert fatal == [SYSDRV_FATAL] and not sd.faults, (fatal, sd.faults)
+ # Halt HDCP as soon as its second RPC has reached the signed SYS_DRV
+ # fatal branch. We make no assertion about a later return or cleanup.
+ caller.uc.emu_stop()
+ return 0x80000001
+
+ hdcp.set_rpc_services({consts.SERVICE_CRYPTO: service_one})
+ source = hdcp.alloc(0x200)
+ hdcp.write(source + 0xC8, b'\x12' * 0x20)
+ hdcp.run_function(HDCP_CALLER, (source, 0x20, 0, 0),
+ thumb=True, count=50000, timeout=2_000_000)
+ assert observed == [(0x30, 0xA0000004), (0x33, 0)], observed
+ assert fatal == [SYSDRV_FATAL] and not hdcp.faults and not sd.faults
+ print('signed_hdcp:', HDCP.name, 'signed_sysdrv:', SYSDRV.name)
+ print('bridge requests:', [(hex(op), hex(field)) for op, field in observed])
+ print('real SYS_DRV 0x30 handler:', hex(SYSDRV_ALLOCATE),
+ 'forced no-slot helper:', [hex(x) for x in no_slot],
+ 'return:', [hex(x) for x in alloc_returns])
+ print('real SYS_DRV 0x33 handler:', hex(SYSDRV_POPULATE),
+ 'fatal entry (stopped):', [hex(x) for x in fatal])
+ print('HDCP stopped at second RPC; no host path, honest pool exhaustion, or hardware effect modeled')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_opm51_cmd11_global_flag.py b/psp_emu/harness/probe_opm51_cmd11_global_flag.py
new file mode 100644
index 0000000..837df6e
--- /dev/null
+++ b/psp_emu/harness/probe_opm51_cmd11_global_flag.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+"""Signed OPM .51 cmd 11 global flag write and entry-length controls.
+Synthetic host memref. Never invokes this hanging command on a card.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import Component,memmap
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/versions_win/elf_all/opm_25.0.0.51_97f44863.elf'
+PARAM,HOST,FLAG=0x500000,0x501000,0x418F84
+
+def scenario(size:int,expect:int):
+ # Synthetic OPM component map, not a measured live loader layout.
+ def opm_map():
+ cm=memmap.ComponentMap('opm',0x400100)
+ cm.add('image',0x400000,0x20000)
+ return memmap._common(cm,0x4F0000,0x10000,0x600000,0x20000)
+ memmap.MAPS['opm']=opm_map
+ opm=Component('opm',image_path=str(SIGNED))
+ opm.uc.mem_map(0x500000,0x10000)
+ opm.write(PARAM,bytes(0x40));opm.write(HOST,bytes(0x3D80))
+ opm.wr32(PARAM,HOST);opm.wr32(PARAM+4,size)
+ opm.wr32(HOST,11);opm.wr32(HOST+0x10,0x12345678)
+ opm.wr32(FLAG,0xAABBCCDD)
+ rv=opm.run_function(0x402AE4,(0,0xF00,7,PARAM),thumb=True,
+ count=160_000,timeout=2_000_000)
+ assert not opm.faults,(rv,opm.faults)
+ assert opm.rd32(FLAG)==expect,(hex(rv),hex(opm.rd32(FLAG)))
+ print(f'declared size={size:#x} result={rv:#x} global flag={opm.rd32(FLAG):#x}')
+
+def main():
+ scenario(0x3D53,0xAABBCCDD) # signed entry rejects before cmd 11 store
+ scenario(0x3D54,0x12345678) # signed cmd 11 stores host scalar
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sos54_6c_missing_resource.py b/psp_emu/harness/probe_sos54_6c_missing_resource.py
new file mode 100644
index 0000000..ad3b3d0
--- /dev/null
+++ b/psp_emu/harness/probe_sos54_6c_missing_resource.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+"""Signed sOS .54 SVC6C leaf with a synthetic absent resource 0.
+The prior teardown helper is stubbed to its signed not-found status 43;
+page zero is deliberately protected to pinpoint the unchecked dereference.
+No hardware or real kernel resource is touched.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE, UC_PROT_NONE
+from unicorn.arm_const import UC_ARM_REG_R0, UC_ARM_REG_PC, UC_ARM_REG_LR
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu.component import Component, Fault
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sos_0.49.0.54_3dcb63e8.elf'
+
+def main():
+ sos=Component('sos',image_path=str(SIGNED),config={'randomize_canary':False})
+ assert Path(sos.image_path).read_bytes()==SIGNED.read_bytes()
+ table=sos.alloc(0x120)
+ sos.wr32(0x8170,table)
+ sos.write(table,bytes(0x120)) # synthetic resource 0 has a null descriptor pointer
+ seen=[]
+ def not_found(uc,ea,size,user):
+ seen.append(uc.reg_read(UC_ARM_REG_R0))
+ uc.reg_write(UC_ARM_REG_R0,43)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ sos.uc.hook_add(UC_HOOK_CODE,not_found,begin=0x6918,end=0x6919)
+ sos.uc.mem_protect(0,0x1000,UC_PROT_NONE)
+ assert sos.run_function(0x6828,(32,),count=12000,thumb=True)==43 and not seen
+ try:
+ sos.run_function(0x6828,(0,),count=12000,timeout=3_000_000,thumb=True)
+ raise AssertionError('missing resource reached unexpected normal return')
+ except Fault as fault:
+ assert (fault.addr,fault.pc)==(0,0x68A2) and seen==[0],(fault,seen)
+ print('signed sOS .54 sub_6828(0): stubbed prior not-found(43) ignored; synthetic null resource pointer dereferenced at 0x68A2; id32 returned43 without deref; no live resource evidence')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sos54_resource0_model.py b/psp_emu/harness/probe_sos54_resource0_model.py
new file mode 100644
index 0000000..33bdd3d
--- /dev/null
+++ b/psp_emu/harness/probe_sos54_resource0_model.py
@@ -0,0 +1,91 @@
+"""Read-only emulator estimate for the hardware order: LOAD_ASD before LOAD_TA GCP."""
+import sys, struct
+sys.path.insert(0,'/home/user/dev/amd-psp-re/psp_emu/harness')
+sys.path.insert(0,'/home/user/dev/amd-psp-re/psp_emu')
+import gcp_fe08_fullsys as H
+from pspemu import armmmu
+from unicorn import UC_HOOK_BLOCK
+BM, M = H.BM, H.M
+c, ccp, irqs, exc = BM.build()
+run, _ = M.make_det_runner(c, irqs)
+run(6_000_000)
+M.install_debug_gpio(c)
+box = M.Mailbox(c, irqs)
+c.uc.mem_write(BM.RING_PA,b'\x00'*BM.RING_SZ)
+rp = bytearray(0x1c)
+struct.pack_into('<I', rp,0x10,BM.RING_PA)
+struct.pack_into('<I', rp,0x18,BM.RING_SZ)
+st,_ = box.submit(2,bytes(rp),timeout=12,runner=run)
+assert st==0,st
+ring=M.Ring(c,irqs,BM.RING_PA,BM.RING_SZ,BM.CMDBUF_PA,run)
+def resource_state(label):
+ rd=lambda a:int.from_bytes(c.uc.mem_read(a,4),'little')
+ root=rd(0x8170) # signed sOS .54 sub_6870's resource-table pointer
+ if not root:
+ print(label,'sOS resource-table pointer is zero',flush=True);return
+ pa=armmmu.translate_live(c.uc,root)
+ if pa is None:
+ print(label,'sOS table root',hex(root),'not translated by generic TTBR0',flush=True);return
+ try:ptrs=[rd(pa+12+4*i) for i in range(32)]
+ except Exception as exc:
+ print(label,'sOS table root VA',hex(root),'PA',hex(pa),'not backed:',type(exc).__name__,flush=True);return
+ print(label,'sOS table VA',hex(root),'model PA',hex(pa),'count',rd(pa),'assigned slots',sum(bool(x) for x in ptrs),
+ 'id0 assigned',bool(ptrs[0]),'id31 assigned',bool(ptrs[31]),flush=True)
+ if ptrs[0]:
+ desc_pa=armmmu.translate_live(c.uc,ptrs[0])
+ if desc_pa is None:
+ print(label,'id0 descriptor not translated in generic TTBR0',flush=True)
+ else:
+ desc=struct.unpack('<6I',c.uc.mem_read(desc_pa,24))
+ print(label,'id0 descriptor segment_count',desc[0],
+ 'descriptor_word1',desc[1],'owner_byte',desc[3]&255,
+ 'first_segment_len',desc[5],flush=True)
+resource_state('after ring init')
+c.uc.mem_write(H.PLATFORM_ID_BYTE,bytes([H.NAVI31_ASIC_ID]))
+asd=open(H.ASD_FULL,'rb').read()
+base=0x04600000
+for b in (base,base+M.Ring.ALIAS):
+ try:c.uc.mem_map(b&~0xfff,(len(asd)+0xffff)&~0xfff)
+ except Exception:pass
+ c.uc.mem_write(b,asd)
+obs=[];count=[0]
+def on_asd(uc,a,size,user):
+ count[0]+=1
+ if count[0]<=3 or count[0]%1024==0:
+ va=0xD3355C
+ obs.append((a,armmmu.ttbr0(uc),armmmu.translate_live(uc,va)))
+c.uc.hook_add(UC_HOOK_BLOCK,on_asd,begin=0xD00100,end=0xDFFFFF)
+ld=bytearray(0x20)
+for off,val in ((0,base),(8,len(asd)),(12,H.GW.CMDBUF2_PA+0x10000),(20,0x1000)):
+ struct.pack_into('<I',ld,off,val)
+print('LOAD_ASD first, container',H.ASD_FULL,flush=True)
+ring.submit(4,bytes(ld),timeout=30,probe=[])
+BM.drain_until(run,lambda:ring.response()[1]!=0 or ring.response()[0]!=0,slices=4,blocks=2_000_000)
+print('ASD blocks',count[0],'sampled VA->PA',[(hex(a),hex(tt),hex(pa) if pa is not None else None) for a,tt,pa in obs][:15],flush=True)
+print('post-ASD ring',ring.response(),'service2',c.uc.mem_read(H.SERVICE_TABLE+2,1)[0],flush=True)
+resource_state('post ASD')
+print('post-ASD current TTBR0',hex(armmmu.ttbr0(c.uc)),'translation',hex(armmmu.translate_live(c.uc,0xD3355C) or 0),flush=True)
+if not obs or obs[0][2] is None:raise SystemExit(1)
+# Now load GCP in the same order as the hardware script and sample the shared MMU map.
+cont=H.NT.pick_container('gcp')
+img=open(cont,'rb').read()
+for b in (H.FT.TA_IMAGE_PA,H.FT.TA_IMAGE_PA+M.Ring.ALIAS):
+ c.uc.mem_write(b,img)
+for b in (H.GW.CMDBUF2_PA,H.GW.CMDBUF2_PA+M.Ring.ALIAS):
+ try:c.uc.mem_map(b&~0xfff,0x8000)
+ except Exception:pass
+ld=bytearray(0x20)
+for off,val in ((0,H.FT.TA_IMAGE_PA),(8,len(img)),(12,H.GW.CMDBUF2_PA),(20,0x8000)):
+ struct.pack_into('<I',ld,off,val)
+gcp=[]
+def on_gcp(uc,a,size,user):
+ if not gcp:
+ gcp.append((a,armmmu.ttbr0(uc),armmmu.translate_live(uc,0x410800),armmmu.translate_live(uc,0xD3355C)))
+c.uc.hook_add(UC_HOOK_BLOCK,on_gcp,begin=0x400100,end=0x40FFFF)
+ring.submit(1,bytes(ld),timeout=30,probe=[])
+BM.drain_until(run,lambda:bool(gcp) and ring.response()[1]!=0,slices=40,blocks=2_000_000)
+print('GCP first block TTBR0, GCP VA->PA, ASD VA->PA:',[(hex(a),hex(tt),hex(pg) if pg is not None else None,hex(pa) if pa is not None else None) for a,tt,pg,pa in gcp],flush=True)
+print('post-GCP ring',ring.response(),'service2',c.uc.mem_read(H.SERVICE_TABLE+2,1)[0],flush=True)
+resource_state('post GCP')
+print('post-GCP current TTBR0',hex(armmmu.ttbr0(c.uc)),'ASD target PA',hex(armmmu.translate_live(c.uc,0xD3355C) or 0),flush=True)
+if not gcp:raise SystemExit(1)
diff --git a/psp_emu/harness/probe_sysdrv_1001_owner_cleanup.py b/psp_emu/harness/probe_sysdrv_1001_owner_cleanup.py
new file mode 100644
index 0000000..ba7ddf3
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1001_owner_cleanup.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+"""Signed .54 free 0x1001: caller-selected identity cleanup; modeled sOS
+SVC6C returns error43. Synthetic records and default-empty slot control.
+No kernel resource teardown or hardware interaction.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0, UC_ARM_REG_LR, UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+SLOT=0xE57538;MSG=0xE575B8;SMALL=0xE58038
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req=sd.alloc(0x100);calls=[]
+ def reject_sos(comp,n):
+ calls.append(comp.uc.reg_read(UC_ARM_REG_R0))
+ return 43 # synthetic kernel failure; signed sOS not run
+ sd.svc.register(0x6C,reject_sos)
+ snapshot=(sd.read(SLOT,0x80),sd.read(MSG,2*84),sd.read(SMALL,16))
+ def run(identity):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x1001),(1,2),(14,identity)):
+ sd.wr32(req+4*i,v)
+ return sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ try:
+ empty=sum(sd.read(SLOT+8*i,1)==b'\x00' for i in range(16))
+ assert empty==16,empty
+ assert run(0)==0 and calls==[0]*16,(calls,sd.read(SLOT,0x10).hex())
+ assert sd.read(SLOT,0x80)==snapshot[0]
+ calls.clear()
+ # Two synthetic identities in three private tables; not real clients.
+ sd.write(SLOT,bytes.fromhex('07 1e 00 00 33 33 33 33 08 1d 00 00 44 44 44 44')+snapshot[0][16:])
+ m7=bytearray(84);m7[32]=1;m7[34]=7
+ m8=bytearray(84);m8[32]=1;m8[34]=8
+ sd.write(MSG,bytes(m7)+bytes(m8))
+ sd.write(SMALL,bytes.fromhex('01 07 00 00 11 11 11 11 01 08 00 00 22 22 22 22'))
+ fatal=[]
+ def capture_fatal(uc,ea,size,data):
+ fatal.append(uc.reg_read(UC_ARM_REG_R0))
+ uc.reg_write(UC_ARM_REG_R0,0xDEAD)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE,capture_fatal,begin=0xE0F890,end=0xE0F891)
+ assert sd.run_function(0xE0B148,(16,7,0,0))==MSG and not fatal
+ assert sd.run_function(0xE0B148,(17,8,0,0))==MSG+84 and not fatal
+ assert run(7)==0 and calls==[30],calls
+ assert sd.read(SLOT,8)==bytes(8) and sd.read(SLOT+8,8)==bytes.fromhex('08 1d 00 00 44 44 44 44')
+ assert sd.read(MSG+32,1)==b'\x00' and sd.read(MSG+34,1)==b'\x00' and sd.read(MSG+84+32,1)==b'\x01'
+ assert sd.read(SMALL,8)==bytes(8) and sd.read(SMALL+8,8)==bytes.fromhex('01 08 00 00 22 22 22 22')
+ sd.run_function(0xE0B148,(16,7,0,0))
+ assert len(fatal)==1 # captured fatal; the modeled hook returns, not signed normal control flow
+ assert sd.run_function(0xE0B148,(17,8,0,0))==MSG+84 and len(fatal)==1
+ print('signed 0x1001: default empty slots identity0 requested sixteen SVC6C(0) despite modeled error43; synthetic identity7 cleared three tables and its consumer reached captured fatal; identity8 stayed usable; no real victim/kernel call')
+ finally:
+ sd.write(SLOT,snapshot[0]);sd.write(MSG,snapshot[1]);sd.write(SMALL,snapshot[2])
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_100b_nested_span.py b/psp_emu/harness/probe_sysdrv_100b_nested_span.py
new file mode 100644
index 0000000..97b52f2
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_100b_nested_span.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+"""Signed .54 0x100B reads nested +16/+20/+24 past four-byte declaration.
+Artificial scratch-page boundary and sOS window; no hardware effect.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+from pspemu.component import Fault
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,nested,dummy=[sd.alloc(n) for n in (0x100,0x400,0x40,0x40)]
+ sd.write(cfg,bytes(0x400));sd.write(nested,bytes(0x40));sd.write(dummy,bytes(0x40))
+ sd.wr32(nested+16,0x12340000);sd.wr32(nested+20,1);sd.wr32(nested+24,0x1000)
+ seen=[];allocator=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:seen.append(tuple(uc.reg_read(x) for x in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2))),begin=0xE086FC,end=0xE086FD)
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:allocator.append(ea),begin=0xE1BBAE,end=0xE1BBAF)
+ def run(ptr,declared):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x100B),(1,2),(2,ptr),(3,declared),(4,dummy),(5,4),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ try:return sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ except Fault as exc:return exc
+ mapped=run(nested,4)
+ assert seen and seen[-1]==(1,0x12340000,0x1000),(mapped,seen[-1] if seen else None)
+ # The next page after scratch is deliberately UNMAPPED in this model.
+ edge=sd.cmap.scratch+sd.cmap.scratch_size-4
+ sd.write(edge,b'ABCD')
+ def svc_window(comp,n):
+ p=comp.uc.reg_read(UC_ARM_REG_R1)
+ for i,v in enumerate((sd.cmap.scratch,sd.cmap.scratch+sd.cmap.scratch_size,0,0)):
+ comp.wr32(p+4*i,v)
+ return 0
+ sd.svc.register(0xB5,svc_window)
+ before=len(seen)
+ fault=run(edge,4)
+ assert isinstance(fault,Fault) and fault.addr==edge+24 and fault.pc==0xE1BB80,(fault,seen)
+ assert len(seen)==before
+ denied=run(edge,28)
+ assert not isinstance(denied,Fault) and denied==0xFFFF000F and len(seen)==before,(denied,seen)
+ assert not allocator,allocator
+ print(f'signed 0x100B: nested +16/+20/+24 read after declared4; synthetic edge fault={fault.addr:#x}, declared28 denied={denied:#x}; no mapped hardware claim')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_100c_slot_release.py b/psp_emu/harness/probe_sysdrv_100c_slot_release.py
new file mode 100644
index 0000000..d928cc5
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_100c_slot_release.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+"""Signed .54 free 0x100C: selected synthetic private slot cleared even if
+modeled sOS SVC 0x6C rejects the associated resource. No live records.
+"""
+from pathlib import Path
+import sys
+from unicorn.arm_const import UC_ARM_REG_R0
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+SLOTS=0xE57538
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req=sd.alloc(0x100);seen=[]
+ def reject_svc(comp,n):
+ seen.append(comp.uc.reg_read(UC_ARM_REG_R0))
+ return 43 # synthetic failed resource removal; not sOS execution
+ sd.svc.register(0x6C,reject_svc)
+ prior=sd.read(SLOTS,0x80)
+ slot=bytes.fromhex('17 1e 00 00 de c0 ad de')
+ def run(idx):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x100C),(1,2),(14,idx)):
+ sd.wr32(req+4*i,v)
+ return sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ try:
+ sd.write(SLOTS,slot+prior[8:])
+ assert run(15)==0 and not seen and sd.read(SLOTS,8)==slot
+ assert run(32)==0 and not seen and sd.read(SLOTS,8)==slot
+ selected=run(16)
+ assert selected==0 and seen==[0x1E] and sd.read(SLOTS,8)==bytes(8),(selected,seen,sd.read(SLOTS,8).hex())
+ assert sd.read(SLOTS+8,0x78)==prior[8:]
+ print('signed 0x100c: synthetic slot16 -> SVC6C(resource30) returned modeled error43 yet descriptor zeroed/status0; 15/32 untouched, neighboring slots preserved')
+ finally:sd.write(SLOTS,prior)
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_100e_100f_ccp_natural.py b/psp_emu/harness/probe_sysdrv_100e_100f_ccp_natural.py
new file mode 100644
index 0000000..e0abd56
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_100e_100f_ccp_natural.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 0x100E/0x100F with modeled CCP, no job hook.
+
+Synthetic type-5 descriptor, identity SVC 0x79, default-success SVCs and
+modeled CCP queue: NOT real sOS PTEs, real card completion or victim impact.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn.arm_const import UC_ARM_REG_R1
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.ccp import CCP # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+BASE, END = 0x400000, 0x800000
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ ccp = CCP(sd, base=0x0CB00000)
+ assert sd.run_function(0xE05008, (0x0CB00000,)) == 0
+ assert sd.run_function(0xE050F0) == 0
+ ring = sd.alloc(16*32, align=16*32)
+ assert sd.run_function(0xE0503C, (0, ring, ring, 16)) == 0
+ for addr, value in ((0xE257D0, 0xE50000), (0xE257D4, 0xE51000),
+ (0xE257D8, ring), (0xE257DC, ring)):
+ sd.wr32(addr, value)
+ sd.uc.mem_map(BASE, 0x3000)
+
+ def svc_window(comp, n):
+ out = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, word in enumerate((BASE, END, 0, 0)):
+ comp.wr32(out+4*i, word)
+ return 0
+
+ sd.svc.register(0xB5, svc_window)
+ sd.svc.register(0x68, lambda comp, n: 0)
+ src, dst = BASE+0x2000, BASE+0x1000
+ planted = bytes(range(64))
+ sd.write(src, planted)
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, bytes(0x400))
+ for op, flag in ((0x100E, 0), (0x100F, 0), (0x100F, 1)):
+ blobs = []
+ for declared in (64, 4):
+ sd.write(dst, b'\xA5'*64)
+ sd.write(req, bytes(0x100))
+ for i, value in ((0, op), (2, dst), (3, 64), (4, src),
+ (5, declared), (14, flag),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req+4*i, value)
+ before = len(ccp.executed)
+ status = sd.run_function(0xE087E4, (req, 2, 0, 0), count=3_000_000,
+ timeout=6_000_000)
+ changed = bytes(sd.uc.mem_read(dst, 64))
+ recent = ccp.executed[before:]
+ assert status == 0 and changed == planted, (hex(op), declared,
+ hex(status), changed[:8], [str(x) for x in recent])
+ assert any(d.engine == 5 and d.length == 64 and d.src_lo == src
+ and d.dst_lo == dst for d in recent), recent
+ blobs.append(changed)
+ print(hex(op),'flag',flag,'input declared',declared,
+ 'signed status',hex(status),'model executed',len(recent),
+ 'source bytes copied',len(changed))
+ assert blobs[0] == blobs[1]
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_100e_100f_second_span.py b/psp_emu/harness/probe_sysdrv_100e_100f_second_span.py
new file mode 100644
index 0000000..0168a6b
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_100e_100f_second_span.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 0x100E/0x100F: second pointer's declared size vs job span.
+
+Stop at the signed job-submission helper before any modeled crypto/device
+operation. SVC 0x79 is synthetic identity translation, 0xB5 supplies a
+synthetic type-5 window, and SVC 0x68 is a default-success cache stub.
+"""
+from __future__ import annotations
+
+import struct
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE, UC_PROT_NONE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.ccp import CCP, CCPAccessFault, Descriptor # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY, JOB = 0xE087E4, 0xE04F1C
+BASE, END = 0x400000, 0x800000
+SENTINEL = 0x8000000A
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ sd.uc.mem_map(BASE, 0x3000)
+ if not any(lo <= END-4 <= hi for lo, hi, _ in sd.uc.mem_regions()):
+ sd.uc.mem_map(END-0x1000, 0x1000)
+ if any(lo <= END <= hi for lo, hi, _ in sd.uc.mem_regions()):
+ sd.uc.mem_protect(END, 0x1000, UC_PROT_NONE)
+
+ def svc_window(comp, n):
+ out = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, word in enumerate((BASE, END, 0, 0)):
+ comp.wr32(out + 4*i, word)
+ return 0
+
+ sd.svc.register(0xB5, svc_window)
+ sd.svc.register(0x68, lambda comp, n: 0)
+ captures: list[tuple[int, ...]] = []
+
+ def stop_job(uc, ea, size, data):
+ arg = uc.reg_read(UC_ARM_REG_R2)
+ captures.append(tuple(sd.rd32(arg + 4*i) for i in range(8)))
+ uc.reg_write(UC_ARM_REG_R0, SENTINEL)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+
+ sd.uc.hook_add(UC_HOOK_CODE, stop_job, begin=JOB, end=JOB+1)
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, bytes(0x400))
+ output, src, edge = BASE+0x1000, BASE+0x2000, END-4
+ sd.write(src, b'Y'*64)
+ sd.write(edge, b'Z'*4)
+
+ def request(opcode: int, ptr: int, declared: int, flag: int = 0) -> int:
+ sd.write(req, bytes(0x100))
+ for i, word in ((0, opcode), (2, output), (3, 64), (4, ptr),
+ (5, declared), (14, flag),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + 4*i, word)
+ return sd.run_function(ENTRY, (req, 2, 0, 0), count=5_000_000,
+ timeout=4_000_000)
+
+ baseline_desc = edge_desc = None
+ for opcode, flag in ((0x100E, 0), (0x100F, 0), (0x100F, 1)):
+ old = len(captures)
+ normal = request(opcode, src, 64, flag)
+ assert len(captures) == old+1, (hex(opcode), hex(normal), captures)
+ expected = captures[-1]
+ old = len(captures)
+ short = request(opcode, edge, 4, flag)
+ assert len(captures) == old+1, (hex(opcode), hex(short), captures)
+ got = captures[-1]
+ assert got[1] == 64 and got[2] == edge and got[4] == output, (hex(opcode), got)
+ if opcode == 0x100E:
+ baseline_desc, edge_desc = expected, got
+ old = len(captures)
+ denied = request(opcode, edge, 64, flag)
+ assert denied != 0 and len(captures) == old, (hex(opcode), denied)
+ print(hex(opcode),'flag',flag,'baseline',hex(normal),'short',hex(short),
+ 'denied',hex(denied),'job words',tuple(hex(x) for x in got[:6]),
+ 'baseline words',tuple(hex(x) for x in expected[:6]))
+
+ # Only the separate CCP model interprets the signed descriptor here;
+ # signed SYS_DRV execution stopped before queue submission above.
+ assert baseline_desc is not None and edge_desc is not None
+ # Unicorn's host-side mem_read used by the CCP model does not enforce
+ # UC_PROT_NONE (which CPU execution does). Unmap that next page only
+ # after the signed-dispatch runs so the isolated model has a real edge.
+ if any(lo <= END <= hi for lo, hi, _ in sd.uc.mem_regions()):
+ sd.uc.mem_unmap(END, 0x1000)
+ ccp = CCP(sd)
+ def desc(words: tuple[int, ...]) -> Descriptor:
+ return Descriptor(struct.pack('<8I', *words))
+ good = desc(baseline_desc)
+ assert good.engine == 5 and good.src_mem == 2 and good.dst_mem == 2
+ assert ccp.execute(good) == 64
+ assert bytes(sd.uc.mem_read(output, 64)) == b'Y'*64
+ try:
+ ccp.execute(desc(edge_desc))
+ except CCPAccessFault as exc:
+ assert exc.code == 0x13, exc
+ print('separate CCP model: 64-byte source at unmapped synthetic edge yields IDMA fault',
+ hex(exc.code))
+ else:
+ raise AssertionError('modeled CCP unexpectedly read across synthetic boundary')
+ bounded = list(edge_desc)
+ bounded[1] = 4
+ assert ccp.execute(desc(tuple(bounded))) == 4
+ assert bytes(sd.uc.mem_read(output, 4)) == b'Z'*4
+ print('separate CCP model: 4-byte source at edge copies 4 bytes')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_1010_nested_gate.py b/psp_emu/harness/probe_sysdrv_1010_nested_gate.py
new file mode 100644
index 0000000..28d630a
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1010_nested_gate.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1010 short pairs: default emulator stops before nested +68 read.
+No artificial crypto session or hardware request.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+from pspemu.component import Fault
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,p1,p2,p3,out=[sd.alloc(n) for n in (0x100,0x400,0x100,0x100,0x100,0x100)]
+ sd.write(cfg,bytes(0x400))
+ for ptr in (p1,p2,p3,out):sd.write(ptr,bytes(0x100))
+ nested=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:nested.append(ea),begin=0xE09458,end=0xE09459)
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x1010),(1,2),(2,p1),(3,4),(4,p2),(5,4),(6,p3),(7,4),(8,out),(9,4),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ try:rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ except Fault as exc:
+ assert (exc.pc,exc.addr)==(0xE019F8,0x113C) and not nested,(exc.pc,exc.addr,nested)
+ print(f'signed 0x1010 default emulator: missing shared crypto base fault_pc={exc.pc:#x} FAR={exc.addr:#x}; nested+68 not reached')
+ else:
+ print(f'signed 0x1010 default emulator: status={rv:#x} nested+68_reached={bool(nested)}; no crypto bytes printed')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1016_short_input.py b/psp_emu/harness/probe_sysdrv_1016_short_input.py
new file mode 100644
index 0000000..ac04274
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1016_short_input.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1016 declared-input-size versus 24+72-byte leaf reads.
+
+Synthetic caller mapping and kernel SVC returns: this is NOT a live sOS
+caller-window, hardware read, accepted firmware configuration, or safe card
+probe. In signed sOS .54, the SVC 0x6B mode-zero path returns error 9 before
+the leaf copies; default emulator success for this SVC is a KNOWN departure.
+The SYS_DRV request validator, dispatcher and copy leaf are unmodified.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R1
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.component import Fault # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY, LEAF, CACHE = 0xE087E4, 0xE19BD8, 0xE7D5D0
+BASE, END = 0x400000, 0x41C000 # synthetic type-5 caller window
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ sd.uc.mem_map(BASE, END-BASE)
+
+ def svc_window(comp, n):
+ out = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, val in enumerate((BASE, END, 0, 0)):
+ comp.wr32(out + 4*i, val)
+ return 0
+
+ sd.svc.register(0xB5, svc_window)
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, b'\x00' * 0x400)
+ first, normal, edge = BASE+0x1000, BASE+0x2000, END-4
+ sd.write(first, b'\x01' + b'\x00' * 23)
+ second = bytes(0xA0 + i % 0x20 for i in range(72))
+ sd.write(normal, second)
+ sd.write(edge, second[:4])
+ visited: list[int] = []
+ sd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, n, data: visited.append(ea),
+ begin=LEAF, end=LEAF+1)
+
+ def request(second_ptr: int, declared: int) -> int:
+ sd.write(req, b'\x00' * 0x100)
+ for i, word in ((0, 0x1016), (2, first), (3, 4), (4, second_ptr),
+ (5, declared), (14, BASE+0x3000), (15, 0), (16, 104),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + i*4, word)
+ return sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+
+ sd.write(CACHE, b'\xCC' * 96)
+ before = len(visited)
+ short_rv = request(normal, 4)
+ assert len(visited) == before + 1, (hex(short_rv), visited)
+ copied = sd.read(CACHE, 96)
+ assert copied[:24] == sd.read(first, 24)
+ # Leaf intentionally permutes the first two 24-byte chunks from input 2.
+ assert copied[24:96] == second[24:48] + second[:24] + second[48:72], copied.hex()
+ print('declared second input 4: reached signed leaf, copied all 72 bytes;',
+ 'final status', hex(short_rv))
+
+ # Signed sOS .54 SVC 0x6B mode zero returns 9. Model THAT result and
+ # confirm the signed SYS_DRV leaf exits before the mismatched reads.
+ sd.svc.register(0x6B, lambda comp, n: 9)
+ sd.write(CACHE, b'\xCC' * 96)
+ before = len(visited)
+ blocked = request(normal, 4)
+ assert blocked == 0x80000703 and len(visited) == before + 1
+ assert sd.read(CACHE, 96) == b'\xCC' * 96
+ print('signed sOS .54 SVC 0x6B result 9: leaf returns', hex(blocked),
+ 'before any cache copy')
+ sd.svc.register(0x6B, lambda comp, n: 0) # return to synthetic-success experiment
+
+ sd.write(CACHE, b'\xCC' * 96)
+ before = len(visited)
+ rejected = request(edge, 72)
+ assert rejected != 0 and len(visited) == before
+ assert sd.read(CACHE, 96) == b'\xCC' * 96
+ print('synthetic window-edge declared 72: rejected before leaf, status', hex(rejected))
+
+ try:
+ request(edge, 4)
+ except Fault as exc:
+ assert len(visited) == before + 1
+ assert END <= exc.addr < END+72, (exc, sd.faults)
+ print('synthetic window-edge declared 4: reached leaf, fault', hex(exc.addr))
+ else:
+ raise AssertionError('edge read unexpectedly completed without a fault')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_1017_short_outputs.py b/psp_emu/harness/probe_sysdrv_1017_short_outputs.py
new file mode 100644
index 0000000..3d1e849
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1017_short_outputs.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 0x1017: two four-byte stores despite one-byte declarations.
+Private transport state and response are synthetic, and the device call is intercepted.
+No card, real device transaction, or cross-allocation overwrite.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_LR,UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ sd.write(0xE26259,b'\x00') # conditional transport branch
+ cb=sd.alloc(0x100);req=sd.alloc(0x100);out1=sd.alloc(0x20);out2=sd.alloc(0x20)
+ sd.wr32(0xE26340,cb);sd.wr32(0xE2632C,0x100)
+ sd.wr32(0xE7D5D0,1);sd.wr32(0xE7D5D4,1)
+ hits=[]
+ def transport(uc,ea,size,data):
+ hits.append((uc.reg_read(UC_ARM_REG_R0),uc.reg_read(UC_ARM_REG_R1)))
+ sd.wr32(cb+12,0) # v12[1], transport status
+ sd.wr32(cb+8+6*4,0x11223344)
+ sd.wr32(cb+8+8*4,0x55667788)
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE,transport,begin=0xE1F470,end=0xE1F471)
+ sd.write(out1,b'A'*32);sd.write(out2,b'B'*32);sd.write(req,bytes(0x100))
+ for i,v in ((0,0x1017),(1,2),(2,out1),(3,1),(4,out2),(5,1),(14,0x123),(15,0x456)):
+ sd.wr32(req+4*i,v)
+ status=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ assert status==0 and len(hits)==1,(hex(status),hits)
+ assert sd.read(out1,8)==bytes.fromhex('44332211')+b'A'*4,sd.read(out1,8)
+ assert sd.read(out2,8)==bytes.fromhex('88776655')+b'B'*4,sd.read(out2,8)
+ # The isolated signed init's default mode=1 rejects this path before transport.
+ sd.write(0xE26259,b'\x01');sd.write(out1,b'A'*32);sd.write(out2,b'B'*32)
+ rejected=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ assert rejected==0xFFFF000A and len(hits)==1,(hex(rejected),hits)
+ assert sd.read(out1,8)==b'A'*8 and sd.read(out2,8)==b'B'*8
+ print('signed 0x1017: synthetic mode=0: two declared-one-byte outputs each got four-byte stores; default mode=1 rejects before transport; no card effect')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1018_synthetic_range.py b/psp_emu/harness/probe_sysdrv_1018_synthetic_range.py
new file mode 100644
index 0000000..9081763
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1018_synthetic_range.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1018: compare two encodings of one device address.
+
+Private class row/upper bound are PLANTED; backend is capture-only.
+No natural class policy, storage contents, or card reads are established.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_R3,UC_ARM_REG_LR,UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ assert sd.read(0xE26259,1)==b'\x01'
+ # Fake root: one class-34 row with base 0x2000; global upper 0x10000.
+ # The chosen address is not a CPU pointer and no backend is allowed to run.
+ root=0xE7904C
+ sd.wr32(root+16,0x10000)
+ sd.wr32(root+72,1)
+ sd.wr32(root+76,34)
+ sd.wr32(root+80,0x2000)
+ req=sd.alloc(0x100);out=sd.alloc(0x100);seen=[];trace=[]
+ def log_entry(uc,ea,size,data):
+ trace.append((hex(ea),tuple(uc.reg_read(x) for x in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_R3))))
+ for addr in (0xE0AE90,0xE15234):
+ sd.uc.hook_add(UC_HOOK_CODE,log_entry,begin=addr,end=addr+1)
+ def capture(uc,ea,size,data):
+ seen.append(tuple(uc.reg_read(x) for x in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_R3)))
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE,capture,begin=0xE1AAB4,end=0xE1AAB5)
+ def run(index,offset):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x1018),(1,2),(2,out),(3,4),(14,1),(15,index),(16,offset)):
+ sd.wr32(req+4*i,v)
+ return sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ a=run(0,0x1000)
+ b=run(1,0)
+ blocked=run(0,0xE000) # address=upper; four-byte read exceeds upper
+ assert a==b==0 and blocked!=0,(hex(a),hex(b),hex(blocked),seen,trace)
+ assert len(seen)==2 and seen[0][1]==seen[1][1]==0x3000,seen
+ assert all(x[0]==2 and x[2]==out and x[3]==4 for x in seen),seen
+ print('signed 0x1018 mode=1: page0+0x1000 and page1+0 target same 0x3000; upper-bound negative stops before captured backend; planted descriptor, no hardware effect')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_101a_count_wrap.py b/psp_emu/harness/probe_sysdrv_101a_count_wrap.py
new file mode 100644
index 0000000..8f9e5c5
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_101a_count_wrap.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+"""Signed .54 free 0x101A: capture 32-bit shift of caller block count.
+Fixed-image lookup and hardware sink are stubbed; no flash writes.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_R3,UC_ARM_REG_LR,UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ assert sd.read(0xE26259,1)==b'\x01'
+ req=sd.alloc(0x100);captures=[];lookups=[]
+ def jump(uc,value):
+ uc.reg_write(UC_ARM_REG_R0,value)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ def lookup(uc,ea,size,data):
+ lookups.append(tuple(uc.reg_read(x) for x in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2)))
+ sd.wr32(uc.reg_read(UC_ARM_REG_R3),0x10000)
+ jump(uc,0)
+ def sink(uc,ea,size,data):
+ captures.append(tuple(uc.reg_read(x) for x in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_R3)))
+ jump(uc,0x80001004)
+ sd.uc.hook_add(UC_HOOK_CODE,lookup,begin=0xE0AE90,end=0xE0AE91)
+ sd.uc.hook_add(UC_HOOK_CODE,sink,begin=0xE15074,end=0xE15075)
+ def run(count):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x101A),(1,2),(14,1),(15,0),(16,count)):
+ sd.wr32(req+4*i,v)
+ return sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ assert run(16)==0x80001004
+ assert run(0x100010)==0x80001004
+ assert run(0x100000)==0x80001004
+ assert len(lookups)==3 and len(captures)==3
+ assert [x[2] for x in captures]==[0x10000,0x10000,0],captures
+ assert all(x[:2]==(2,0x10000) for x in captures),captures
+ print('signed 0x101a: msg16=16 and 0x100010 both passed truncated 0x10000-byte length to stubbed sink; 0x100000 became zero; synthetic lookup, no real metadata/flash')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_101c_state_gate.py b/psp_emu/harness/probe_sysdrv_101c_state_gate.py
new file mode 100644
index 0000000..3c799d2
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_101c_state_gate.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+"""Synthetic sOS-style per-slot state setup -> signed 0x101C gate only.
+
+No hardware, no real key material, and no output-overflow request. This calls
+SYS_DRV's signed 0x3105 leaf directly (bypassing sOS/SVC ACL) with synthetic
+bytes for caller slot 2, then exercises the unhooked signed crypto-state gate.
+Model CCP and unhandled-SVC behavior are not evidence of card completion.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.ccp import CCP, CCPError # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+SETTER, GATE = 0xE0E74C, 0xE0E7B8
+FLAG = 0xEB1280 + 66 * 2 + 64
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1
+ assert sd.read(FLAG, 2) == b'\0\0'
+ ccp = CCP(sd, base=0x0CB00000)
+ assert sd.run_function(0xE05008, (0x0CB00000,)) == 0
+ assert sd.run_function(0xE050F0) == 0
+ ring = sd.alloc(16 * 32, align=16 * 32)
+ assert sd.run_function(0xE0503C, (0, ring, ring, 16)) == 0
+ for addr, value in ((0xE257D0, 0xE50000), (0xE257D4, 0xE51000),
+ (0xE257D8, ring), (0xE257DC, ring)):
+ sd.wr32(addr, value)
+ before = sd.run_function(GATE, (2,), count=100_000, timeout=1_000_000)
+ assert before == 0xFFFF000B, hex(before)
+ key32, iv16 = sd.alloc(32), sd.alloc(16)
+ sd.write(key32, bytes(range(32))) # fake, public, synthetic test bytes
+ sd.write(iv16, bytes(range(32, 48)))
+ set_rv = sd.run_function(SETTER, (key32, 32, iv16, 16), stack_args=(2,),
+ count=200_000, timeout=2_000_000)
+ assert set_rv == 0 and sd.read(FLAG, 2) == b'\x01\0', (hex(set_rv), sd.read(FLAG, 2))
+ print('synthetic 0x3105 leaf set caller-2 flag:', sd.read(FLAG, 2).hex(), flush=True)
+ try:
+ gate_rv = sd.run_function(GATE, (2,), count=3_000_000,
+ timeout=10_000_000)
+ except CCPError as exc:
+ # The model does not implement this signed setup's SHA descriptor.
+ # This is NOT evidence of either success or failure on hardware.
+ assert str(exc) == 'unsupported SHA type 12', str(exc)
+ assert sd.read(FLAG, 2) == b'\x01\x00', sd.read(FLAG, 2)
+ print('unhooked gate entered CCP; emulator blocked on:', exc,
+ '; descriptor:', ccp.errors[-1] if ccp.errors else 'not logged',
+ '; gate completion UNKNOWN', flush=True)
+ return
+ print('unhooked signed crypto gate return:', hex(gate_rv),
+ 'flags:', sd.read(FLAG, 2).hex(), flush=True)
+ assert gate_rv == 0 and sd.read(FLAG, 2) == b'\x01\x01', (
+ hex(gate_rv), sd.read(FLAG, 2), sd.faults[-2:])
+ _ = ccp # retain device hook
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_101c_wrapped_output.py b/psp_emu/harness/probe_sysdrv_101c_wrapped_output.py
new file mode 100644
index 0000000..89d6061
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_101c_wrapped_output.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+"""Signed .54 SYS_DRV 0x101C wrapped output guard, conditional crypto gate.
+
+Synthetic B5 window and protected page; only the preceding crypto-state gate
+is forced to success. Stop just before the gigantic source copy, *after* the
+signed output memset/header writes. This is not a card effect or full crypto.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE, UC_PROT_NONE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0, UC_ARM_REG_R1
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.component import Fault # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY, LEAF, STATE_GATE, BEFORE_BULK_COPY = 0xE087E4, 0xE215A8, 0xE0E7B8, 0xE2160C
+FAIL_EPILOGUE = 0xE215DC # load error code, restore stack and return; skip huge source copy
+BASE, END = 0x400000, 0x800000
+WRAPPED = 0xFFFFFFC0 # (WRAPPED + 80) mod 2**32 == 16
+STATE_FLAG_BASE = 0xEB1280 # signed sub_E0E7B8 per-caller gate, +66*caller+64
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ gate_flag = sd.read(STATE_FLAG_BASE+66*2+64, 1)[0]
+ assert gate_flag == 0, f'modeled GCP crypto-state flag unexpectedly enabled: {gate_flag}'
+ natural_gate = sd.run_function(STATE_GATE, (2, 0, 0, 0), count=100_000,
+ timeout=1_000_000)
+ assert natural_gate == 0xFFFF000B, hex(natural_gate)
+ print('modeled init GCP crypto-state flag = 0; signed gate returned',
+ hex(natural_gate),'(forced-success test below)',flush=True)
+ sd.uc.mem_map(BASE, 0x2000)
+ if not any(lo <= END-16 <= hi for lo, hi, _ in sd.uc.mem_regions()):
+ sd.uc.mem_map(END-0x1000, 0x1000)
+ if any(lo <= END <= hi for lo, hi, _ in sd.uc.mem_regions()):
+ sd.uc.mem_protect(END, 0x1000, UC_PROT_NONE) # synthetic, NOT live PTE
+
+ def svc_window(comp, n):
+ out = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, val in enumerate((BASE, END, 0, 0)):
+ comp.wr32(out + 4*i, val)
+ return 0
+ sd.svc.register(0xB5, svc_window)
+
+ leaves, gates, prebulk = [], [], []
+ sd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, size, data: leaves.append(ea),
+ begin=LEAF, end=LEAF+1)
+ def force_gate(uc, ea, size, data):
+ gates.append(ea)
+ uc.reg_write(UC_ARM_REG_R0, 0)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE, force_gate, begin=STATE_GATE, end=STATE_GATE+1)
+ def stop_before_bulk_copy(uc, ea, size, data):
+ prebulk.append(ea)
+ uc.reg_write(UC_ARM_REG_PC, FAIL_EPILOGUE | 1)
+ sd.uc.hook_add(UC_HOOK_CODE, stop_before_bulk_copy,
+ begin=BEFORE_BULK_COPY, end=BEFORE_BULK_COPY+1)
+
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, b'\x00' * 0x400)
+ source, dest, edge = BASE+0x1000, BASE+0x1800, END-16
+ sd.write(source, b'Q'*128)
+
+ def request(input_len: int, outptr: int, outlen: int, input_ptr: int) -> int:
+ sd.write(req, b'\x00' * 0x100)
+ for i, word in ((0, 0x101C), (2, input_ptr), (3, input_len),
+ (4, outptr), (5, outlen),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + 4*i, word)
+ return sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+
+ sd.write(dest, b'\xA5'*128)
+ guard_negative = request(4, dest, 16, source)
+ assert len(leaves) == len(gates) == 1 and not prebulk
+ assert sd.read(dest, 128) == b'\xA5'*128
+ print('small input 4/output 16: signed guard rejected, output unchanged',
+ hex(guard_negative),flush=True)
+
+ sd.write(dest, b'\xA5'*128)
+ validator_denied = request(WRAPPED, dest, 16, source)
+ assert validator_denied == 0xFFFF000F and len(leaves) == len(gates) == 1
+ assert sd.read(dest,128) == b'\xA5'*128
+ print('wrapped mapped input: pre-dispatch length cap rejected',
+ hex(validator_denied),flush=True)
+
+ # A NULL input pointer skips the dispatcher's pointer-pair validation,
+ # even with the same oversized declared length. The leaf writes output
+ # before it tries to read from that NULL source. Synthetic-only.
+ wrapped_rv = request(WRAPPED, dest, 16, 0)
+ got = sd.read(dest, 128)
+ assert len(leaves) == len(gates) == 2 and len(prebulk) == 1, (
+ hex(wrapped_rv),len(leaves),len(gates),len(prebulk),got[:80].hex(),sd.faults[-2:])
+ assert got[:48] == bytes(48) and got[48:52] == (16).to_bytes(4,'little')
+ assert got[52:56] == WRAPPED.to_bytes(4,'little') and got[56:80] == bytes(24)
+ assert got[80:] == b'\xA5'*48
+ print('wrapped input/output declared 16: signed leaf changed bytes 0..79, '
+ 'bytes 80..127 untouched; stopped before gigantic bulk copy',
+ hex(wrapped_rv),flush=True)
+
+ n = (len(leaves),len(gates),len(prebulk))
+ rejected = request(WRAPPED, edge, 80, 0)
+ assert rejected != 0 and (len(leaves),len(gates),len(prebulk)) == n
+ print('synthetic edge output declared 80: rejected before leaf',hex(rejected),flush=True)
+
+ try:
+ request(WRAPPED, edge, 16, 0)
+ except Fault as exc:
+ assert END <= exc.addr < END+80 and any(f['addr'] == END for f in sd.faults), (exc, sd.faults)
+ assert (len(leaves),len(gates),len(prebulk)) == (n[0]+1,n[1]+1,n[2])
+ print('synthetic edge output declared 16: signed memset fault',hex(exc.addr),
+ 'before bulk copy',flush=True)
+ else:
+ raise AssertionError('wrapped short output unexpectedly completed at edge')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_101d_nested_gate.py b/psp_emu/harness/probe_sysdrv_101d_nested_gate.py
new file mode 100644
index 0000000..03d881b
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_101d_nested_gate.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+"""Signed .54 0x101D: determine if default per-caller crypto gate reaches +52 read.
+No crypto/session state is planted or modified on hardware.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,inp,out=[sd.alloc(n) for n in (0x100,0x400,0x80,0x40)]
+ sd.write(cfg,bytes(0x400));sd.write(inp,bytes(0x80));sd.write(out,bytes(0x40))
+ reads=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:reads.append(ea),begin=0xE1F6EA,end=0xE1F6EB)
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x101D),(1,2),(2,inp),(3,4),(4,out),(5,4),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ assert not reads and not sd.faults,(rv,reads)
+ print(f'signed 0x101D default emulator: gate_status={rv:#x}, nested+52_read_reached=False; conditional signed path remains untested')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1021_global_flags.py b/psp_emu/harness/probe_sysdrv_1021_global_flags.py
new file mode 100644
index 0000000..98af852
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1021_global_flags.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 free 0x1021: synthetic global flag stores, no card.
+
+State-changing command: emulator only. Assert direct globals and restore the
+emulator snapshot; no live post-load trigger of the downstream bringup is
+claimed.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn.arm_const import UC_ARM_REG_R1
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+FLAGS, ENTRY = 0xE252D8, 0xE087E4
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, bytes(0x400))
+ sd.uc.mem_map(0x400000, 0x1000)
+ out = 0x400100
+
+ def svc_window(comp, n):
+ ptr = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, word in enumerate((0x400000, 0x800000, 0, 0)):
+ comp.wr32(ptr+4*i, word)
+ return 0
+
+ sd.svc.register(0xB5, svc_window)
+ before = sd.read(FLAGS, 3)
+
+ def request(mode: int, value: int = 0) -> tuple[int, bytes]:
+ sd.write(req, bytes(0x100))
+ sd.wr32(out, 0)
+ for i, word in ((0, 0x1021), (2, out), (3, 4), (14, mode), (15, value),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + 4*i, word)
+ ret = sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+ assert sd.rd32(out) == 1, (mode, hex(ret), hex(sd.rd32(out)))
+ return ret, sd.read(FLAGS, 3)
+
+ try:
+ for mode, value, offset, expected in ((0, 0x41, 0, 0x41),
+ (1, 0, 2, 0),
+ (2, 0, 2, 1),
+ (5, 0, 1, 0)):
+ ret, after = request(mode, value)
+ assert ret == 0 and after[offset] == expected, (mode, hex(ret), after)
+ if mode == 0:
+ assert after[1] == 1
+ print('mode',mode,'signed status',hex(ret),'globals',after.hex())
+ finally:
+ sd.write(FLAGS, before)
+ assert sd.read(FLAGS, 3) == before
+ print('synthetic globals restored:',before.hex())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_1023_ccp_natural.py b/psp_emu/harness/probe_sysdrv_1023_ccp_natural.py
new file mode 100644
index 0000000..ca19f9f
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1023_ccp_natural.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+"""One bounded signed 0x1023 run with the emulator's CCP model, no crypto hook.
+
+The SYS_DRV boot configuration, sOS SVC handlers and CCP MMIO are still
+synthetic. This is only a feasibility discriminator; no card claim.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.ccp import CCP # noqa: E402
+from pspemu.component import Fault # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ ccp = CCP(sd, base=0x0CB00000)
+ assert sd.run_function(0xE05008, (0x0CB00000,)) == 0
+ assert sd.run_function(0xE050F0) == 0
+ ring = sd.alloc(16 * 32, align=16 * 32)
+ assert sd.run_function(0xE0503C, (0, ring, ring, 16)) == 0
+ for addr, value in ((0xE257D0, 0xE50000), (0xE257D4, 0xE51000),
+ (0xE257D8, ring), (0xE257DC, ring)):
+ sd.wr32(addr, value)
+ src1, src2, output, req, cfg = (sd.alloc(n) for n in (64, 4, 128, 0x100, 0x400))
+ sd.write(src1, b'\x43'*64)
+ sd.write(src2, b'\x58'*4)
+ sd.write(cfg, b'\x00'*0x400)
+ sd.write(req, b'\x00'*0x100)
+ for i, val in ((0, 0x1023), (2, src1), (3, 64), (4, src2), (5, 4),
+ (8, output), (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + i*4, val)
+ outputs = []
+ for declared in (64, 4):
+ sd.write(output, b'\xA5'*128)
+ sd.wr32(req + 9*4, declared)
+ try:
+ status = sd.run_function(0xE087E4, (req, 2, 0, 0), count=2_000_000,
+ timeout=6_000_000)
+ except Fault as exc:
+ raise AssertionError(f'bounded signed crypto fault at declared {declared}') from exc
+ blob = sd.read(output, 128)
+ changed = [i for i, b in enumerate(blob) if b != 0xA5]
+ assert status == 0 and changed == list(range(64)), (declared, hex(status), changed)
+ outputs.append(blob[:64])
+ print('signed crypto with emulator CCP: declared', declared,
+ 'status', hex(status), 'changed bytes 0..63; no crypto hook')
+ assert outputs[0] == outputs[1]
+ print('controls: both declared sizes yielded same 64-byte output under '
+ 'synthetic device/config/SVC environment; NOT hardware proof')
+ _ = ccp # keep device hook alive until both requests finish
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_1023_short_output.py b/psp_emu/harness/probe_sysdrv_1023_short_output.py
new file mode 100644
index 0000000..4534065
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1023_short_output.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+"""SYS_DRV .54 opcode 0x1023: 64-byte output vs declared 4-byte window.
+
+Synthetic PSP caller window and stubbed *final crypto completion* only. Signed
+request validation, crypto descriptor setup, and final output-copy leaf run.
+No claim of a successful live crypto operation or hardware write.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0, UC_ARM_REG_R1
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.component import Fault # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY, LEAF, CRYPTO = 0xE087E4, 0xE05140, 0xE1C85C
+BASE, END = 0x400000, 0x41C000 # synthetic type-5 caller window
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ sd.uc.mem_map(BASE, END-BASE)
+
+ def svc_window(comp, n):
+ out = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, val in enumerate((BASE, END, 0, 0)):
+ comp.wr32(out + i*4, val)
+ return 0
+
+ sd.svc.register(0xB5, svc_window)
+ leaves: list[int] = []
+ crypto_stubs: list[int] = []
+ sd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, sz, ctx: leaves.append(ea),
+ begin=LEAF, end=LEAF+1)
+
+ def complete_crypto(uc, ea, size, ctx):
+ crypto_stubs.append(ea)
+ uc.reg_write(UC_ARM_REG_R0, 0)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE, complete_crypto, begin=CRYPTO, end=CRYPTO+1)
+
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, b'\x00' * 0x400)
+ src1, src2, normal, edge = BASE+0x1000, BASE+0x2000, BASE+0x3000, END-4
+ sd.write(src1, b'\x43' * 64)
+ sd.write(src2, b'\x58' * 4)
+
+ def request(output: int, declared: int) -> int:
+ sd.write(req, b'\x00' * 0x100)
+ for i, word in ((0, 0x1023), (2, src1), (3, 64), (4, src2), (5, 4),
+ (8, output), (9, declared),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + i*4, word)
+ return sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+
+ sd.write(normal, b'\xA5' * 0x80)
+ before_l, before_c = len(leaves), len(crypto_stubs)
+ status = request(normal, 4)
+ changed = [i for i, b in enumerate(sd.read(normal, 0x80)) if b != 0xA5]
+ assert status == 0 and len(leaves) == before_l + 1
+ assert len(crypto_stubs) == before_c + 1
+ assert changed == list(range(64)), changed
+ print('synthetic crypto success: declared output 4, signed leaf stored all 64 bytes')
+
+ sd.write(edge, b'\xA5' * 4)
+ before_l, before_c = len(leaves), len(crypto_stubs)
+ rejected = request(edge, 64)
+ assert rejected != 0 and len(leaves) == before_l
+ assert len(crypto_stubs) == before_c and sd.read(edge, 4) == b'\xA5' * 4
+ print('synthetic window-edge declared 64 rejected before leaf:', hex(rejected))
+
+ try:
+ request(edge, 4)
+ except Fault as exc:
+ assert len(leaves) == before_l + 1
+ assert len(crypto_stubs) == before_c + 1
+ assert END <= exc.addr < END+64, (exc, sd.faults)
+ print('synthetic window-edge declared 4 reached output store and faulted:',
+ hex(exc.addr))
+ else:
+ raise AssertionError('synthetic edge unexpectedly completed')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_1027_sos94_block.py b/psp_emu/harness/probe_sysdrv_1027_sos94_block.py
new file mode 100644
index 0000000..2bc68e6
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1027_sos94_block.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+"""Signed .54 SYS_DRV 0x1027 with signed sOS .54 SVC0x94=9.
+Read-only model: demonstrates first lookup stops before inline output.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ calls=[]
+ def signed_sos_case67(comp,n):
+ calls.append(n)
+ return 9
+ sd.svc.register(0x94,signed_sos_case67)
+ req=sd.alloc(0x100);sd.write(req,bytes(0x100))
+ for i,v in ((0,0x1027),(1,2),(14,0x12345678)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ assert rv==9 and len(calls)==1 and sd.rd32(req+14*4)==0x12345678,(rv,calls,hex(sd.rd32(req+14*4)))
+ print('signed 0x1027: connected sOS .54 SVC94 case67 returned9 on first fixed descriptor, inline output unchanged; no fabric reads/writes')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1028_base_readonly.py b/psp_emu/harness/probe_sysdrv_1028_base_readonly.py
new file mode 100644
index 0000000..993aa01
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1028_base_readonly.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+"""Read-only modeled .54 0x1028 base pointer; no leaf invocation or data values."""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ pre=sd.rd32(0xE2579C)
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ base=sd.rd32(0xE2579C)
+ regions=[(lo,hi,perms) for lo,hi,perms in sd.uc.mem_regions() if lo<=base+0x500<=hi]
+ print(f'signed modeled init: *E2579C {pre:#x}->{base:#x}; 0x1028 indexed window {base+0x500:#x}..{base+0x53f:#x}; emulator-mapped={bool(regions)} (address arithmetic only, no word contents read)')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1028_synthetic_window.py b/psp_emu/harness/probe_sysdrv_1028_synthetic_window.py
new file mode 100644
index 0000000..4e9ad45
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1028_synthetic_window.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1028 getter/setter into a swapped synthetic buffer only.
+The original CPU window is never read or written and pointer is restored.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+BASEPTR=0xE2579C
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ old=sd.rd32(BASEPTR);old2=sd.rd32(0xE257A0)
+ fake=sd.alloc(0x800);req=sd.alloc(0x100)
+ sd.write(fake,bytes(0x800));sd.wr32(BASEPTR,fake);sd.wr32(0xE257A0,fake)
+ def run(index,mode,value,opcode=0x1028):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,opcode),(1,2),(14,index),(15,value),(16,mode)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ return rv,sd.rd32(req+15*4)
+ try:
+ w=run(3,0,0x2468ACE0)
+ assert w[0]==0 and sd.rd32(fake+0x50C)==0x2468ACE0 and sd.rd32(fake+0x508)==0 and sd.rd32(fake+0x510)==0,w
+ read=run(3,1,0)
+ assert read==(0,0x2468ACE0),read
+ invalid=run(16,0,0xA5A5A5A5)
+ assert invalid[0]!=0 and sd.rd32(fake+0x540)==0,invalid
+ fixed=run(0,0,0,opcode=0x1029)
+ assert fixed[0]==0 and sd.rd32(fake+0x69C)==0xFFFFFFFF and sd.rd32(fake+0x698)==0 and sd.rd32(fake+0x6A0)==0,fixed
+ set2=run(0x13579BDF,0,0,opcode=0x102E)
+ assert set2[0]==0 and sd.rd32(fake+0xD8)==0x13579BDF and sd.rd32(fake+0xD4)==0 and sd.rd32(fake+0xDC)==0,set2
+ get2=run(0,0,1,opcode=0x102E)
+ assert get2[0]==0 and sd.rd32(req+14*4)==0x13579BDF,get2
+ print(f'signed 0x1028: synthetic +0x50c chosen store/read, index16 rejected={invalid[0]:#x}; 0x1029 fixed +0x69c=ffffffff; 0x102e chosen +0xd8 store/read; original bases {old:#x}/{old2:#x} untouched')
+ finally:
+ sd.wr32(BASEPTR,old)
+ sd.wr32(0xE257A0,old2)
+ assert sd.rd32(BASEPTR)==old and sd.rd32(0xE257A0)==old2
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_102c_label_reach.py b/psp_emu/harness/probe_sysdrv_102c_label_reach.py
new file mode 100644
index 0000000..af6f2b3
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_102c_label_reach.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+"""Signed .54 0x102C: check whether caller label reaches strlen past msg[3].
+Any early crypto/model fault remains separate from reached label reads.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+from pspemu.component import Fault
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,label,out=[sd.alloc(n) for n in (0x100,0x400,0x40,0x40)]
+ sd.write(cfg,bytes(0x400));sd.write(label,b'NAME_XYZ\0'+bytes(0x37));sd.write(out,bytes(0x40))
+ entry=[];length=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:entry.append(uc.reg_read(UC_ARM_REG_R0)),begin=0xE01864,end=0xE01865)
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:length.append(uc.reg_read(UC_ARM_REG_R0)),begin=0xE07AD0,end=0xE07AD1)
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x102C),(1,2),(2,label),(3,4),(4,out),(5,32),(14,0x28),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ def dispatch():
+ try:
+ return sd.run_function(0xE087E4,(req,2,0,0),count=4_000_000,timeout=4_000_000)
+ except Fault as exc:
+ return exc
+ cold=dispatch()
+ assert isinstance(cold,Fault) and (cold.pc,cold.addr)==(0xE11712,0x200) and label not in entry,(cold,entry)
+ old=[sd.rd32(a) for a in (0xE257D8,0xE257DC)]
+ base=sd.alloc(0x6000,align=0x1000)
+ sd.write(base,bytes(0x6000))
+ try:
+ for a in (0xE257D8,0xE257DC):sd.wr32(a,base)
+ modeled=dispatch()
+ finally:
+ for a,v in zip((0xE257D8,0xE257DC),old):sd.wr32(a,v)
+ status=(f'fault_pc={modeled.pc:#x} FAR={modeled.addr:#x}' if isinstance(modeled,Fault) else f'return={modeled:#x}')
+ print(f'signed 0x102C: cold missing-base fault=0x200; synthetic shared-base {status}; label_seen={label in entry}, label_lengths={length}; derived bytes redacted')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_102f_fixed_map.py b/psp_emu/harness/probe_sysdrv_102f_fixed_map.py
new file mode 100644
index 0000000..5be2dac
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_102f_fixed_map.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+"""Signed .54 0x102F fixed 128KiB mapping-copy path; no real source map.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,out=sd.alloc(0x100),sd.alloc(0x400),sd.alloc(0x20010)
+ sd.write(cfg,bytes(0x400))
+ counts={x:0 for x in (0xE06314,0xE06328,0xE06338)}
+ for ea in counts:
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,pc,size,data:counts.__setitem__(pc,counts[pc]+1),begin=ea,end=ea+1)
+ def run(size):
+ sd.write(req,bytes(0x100));sd.write(out,b'\xA5'*0x20010)
+ for i,v in ((0,0x102F),(1,2),(2,out),(3,size),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ assert not sd.faults
+ return rv,sd.read(out,0x20010)
+ short,untouched=run(0x1FFFF)
+ assert short==0xFFFF0010 and untouched==b'\xA5'*0x20010 and not any(counts.values())
+ full,still=run(0x20000)
+ assert counts[0xE06314]==1 and counts[0xE06328]==counts[0xE06338]==32,(counts,full)
+ assert full==0 and still[:0x20000]!=b'\xA5'*0x20000 and still[0x20000:]==b'\xA5'*16
+ print(f'signed 0x102F: short gate={short:#x}, modeled fixed-map completion={full:#x}, page_copies=32; output bytes redacted')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1030_fixed_mode_flag.py b/psp_emu/harness/probe_sysdrv_1030_fixed_mode_flag.py
new file mode 100644
index 0000000..9c09da1
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1030_fixed_mode_flag.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1030 decimal selector 25 sets fixed private mode byte.
+No firmware loading or fabric calls; private state restored only in emulator.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+FLAG=0xE2663D
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ ret,fault,_=sysdrv_env.run_init(sd)
+ assert ret==0 and not fault and sysdrv_env.init_flag(sd)==1
+ req,cfg=sd.alloc(0x100),sd.alloc(0x400)
+ sd.write(req,bytes(0x100));sd.write(cfg,bytes(0x400))
+ old=sd.read(FLAG,1)
+ assert old==b'\0'
+ sd.wr32(req,0x1030);sd.wr32(req+4,2)
+ sd.wr32(req+4*sysdrv_env.REQ_CONFIG_DWORD,cfg)
+ sd.wr32(req+56,25);sd.wr32(req+60,0)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert rv==0 and sd.read(FLAG,1)==b'\x01' and not sd.faults
+ sd.write(FLAG,old) # emulator snapshot only; NOT a card restoration oracle
+ assert sd.read(FLAG,1)==old
+ print('signed free 0x1030 selector 25 set private mode byte 0->1; restored emulator snapshot')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1030_flash_class_gate.py b/psp_emu/harness/probe_sysdrv_1030_flash_class_gate.py
new file mode 100644
index 0000000..0251f9c
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1030_flash_class_gate.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+"""Signed .54 free 0x1030 changes the two flash-class selector bytes.
+The table is synthetic and lookup-only; NO SPI operation/card mutation.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY,LOOKUP,TABLE=0xE087E4,0xE0AE90,0xE7904C
+GATE0,GATE5=0xE257A8,0xE257AC
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ init,fault,_=sysdrv_env.run_init(sd)
+ assert init==0 and not fault and sysdrv_env.init_flag(sd)==1
+ before=(sd.rd32(GATE0),sd.rd32(GATE5))
+ assert (before[0]&255,before[1]&255)==(255,255),before
+ # Artificial nonoverlapping flash-table rows; no signed ROM/RA2 provenance.
+ sd.write(TABLE+72,bytes(0x100));sd.wr32(TABLE+72,3)
+ for i,(tag,base) in enumerate(((34,0x10000),(4,0x20000),(84,0x30000))):
+ at=TABLE+76+16*i
+ sd.wr32(at,tag);sd.wr32(at+4,base);sd.wr32(at+8,0x1000)
+ req,cfg,out=sd.alloc(0x100),sd.alloc(0x400),sd.alloc(4)
+ sd.write(cfg,bytes(0x400));sd.write(req,bytes(0x100))
+ sd.wr32(req,0x1030);sd.wr32(req+4,2)
+ sd.wr32(req+4*sysdrv_env.REQ_CONFIG_DWORD,cfg)
+ def lookup(cls):
+ sd.wr32(out,0xAAAAAAAA)
+ rc=sd.run_function(LOOKUP,(cls,0,0x10,out),count=100_000,timeout=1_000_000)
+ return rc,sd.rd32(out)
+ assert lookup(0)[0]!=0 and lookup(5)[0]!=0
+ assert lookup(1)==(0,0x10010)
+ def setter(sel,val):
+ sd.wr32(req+56,sel);sd.wr32(req+60,val)
+ return sd.run_function(ENTRY,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert setter(5,0)==0 and sd.rd32(GATE0)==0
+ assert lookup(0)==(0,0x20010)
+ # SVC 0x66 returns *SYS_DRV's* identity (modeled as 0), not class 5.
+ assert setter(13,0)==0 and sd.rd32(GATE5)==0
+ # The selector-0 comparison runs first. Both matching gates still
+ # reject class 5; disabling gate 0 lets the class-5 branch run.
+ assert lookup(5)[0]!=0
+ assert setter(5,before[0])==0
+ got=lookup(5)
+ assert got==(0,0x30010),(got,hex(sd.rd32(GATE5)),hex(sd.rd32(TABLE+72)))
+ assert lookup(0x20)[0]!=0
+ assert setter(5,before[0])==0 and setter(13,before[1])==0
+ assert (sd.rd32(GATE0),sd.rd32(GATE5))==before and not sd.faults
+ print('signed 0x1030 selectors 5/13 enabled synthetic flash classes 0/5; restored modeled bytes')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1031_mode_oracle.py b/psp_emu/harness/probe_sysdrv_1031_mode_oracle.py
new file mode 100644
index 0000000..4387aba
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1031_mode_oracle.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 free 0x1031 selector 0xf returns mode byte inline.
+Only emulator-private value is changed in the second control; no card work.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ assert sd.read(0xE26259,1)==b'\x01'
+ req=sd.alloc(0x100)
+ def query(mode):
+ sd.write(req,bytes(0x100))
+ sd.wr32(req,0x1031);sd.wr32(req+4,2)
+ sd.wr32(req+14*4,mode);sd.wr32(req+15*4,0xAA55AA55)
+ ret=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ return ret,sd.rd32(req+15*4)
+ assert query(0xF)==(0,1)
+ sd.write(0xE26259,b'\x00')
+ assert query(0xF)==(0,0)
+ status,marker=query(0x19)
+ assert status!=0 and marker==0xAA55AA55,(hex(status),hex(marker))
+ print('signed 0x1031 selector15 returns isolated mode byte 1 then planted 0 inline; invalid selector leaves marker; no card query')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1032_status_flag.py b/psp_emu/harness/probe_sysdrv_1032_status_flag.py
new file mode 100644
index 0000000..5c10b4b
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1032_status_flag.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 0x1032 / 0x1077 private flag roundtrip (emulator only)."""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+FLAG=0xE26150
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg=sd.alloc(0x100),sd.alloc(0x400)
+ sd.write(cfg,bytes(0x400))
+ original=sd.read(FLAG,1)
+ def run(op):
+ sd.write(req,bytes(0x100))
+ sd.wr32(req,op);sd.wr32(req+4,2)
+ sd.wr32(req+sysdrv_env.REQ_CONFIG_DWORD*4,cfg)
+ result=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ return result,sd.read(req+14*4,16)
+ before=run(0x1077)
+ set_result,_=run(0x1032)
+ after=run(0x1077)
+ try:
+ assert original==b'\x00' and sd.read(FLAG,1)==b'\x01'
+ assert set_result==0xFFFF0008,(hex(set_result),sd.faults)
+ assert before[0]==after[0]==0 and before[1][8]==0 and after[1][8]==1,(before,after)
+ print('signed 0x1032 returned 0xffff0008 after setting E26150=1; 0x1077 status byte+8 changed 0->1 (model only)')
+ finally:sd.write(FLAG,original)
+ assert sd.read(FLAG,1)==original
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1033_short_input.py b/psp_emu/harness/probe_sysdrv_1033_short_input.py
new file mode 100644
index 0000000..5e67621
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1033_short_input.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+"""SYS_DRV .54 0x1033: signed 40-byte read despite 4-byte input declaration.
+
+Synthetic caller window with the main type-5 0x400000..0x800000 bounds from
+signed sOS SVC 0xB5 (not its live PCB state or PTEs). State-changing helper
+sub_E09E70 is intercepted after the signed 40-byte copy. Never infer a
+hardware or out-of-process read from the synthetic edge fault.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE, UC_PROT_NONE
+from unicorn.arm_const import UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0, UC_ARM_REG_R1
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.component import Fault # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY, LEAF, MUTATOR = 0xE087E4, 0xE134B8, 0xE09E70
+BASE, END = 0x400000, 0x800000
+SENTINEL = 0x8000000A
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ sd.uc.mem_map(BASE, 0x2000)
+ regions = sd.uc.mem_regions()
+ if not any(lo <= END-4 <= hi for lo, hi, _ in regions):
+ sd.uc.mem_map(END-0x1000, 0x1000)
+ # sysdrv_env's synthetic flash mapping can overlap the 8-MB type-5
+ # descriptor boundary. Deliberately remove read permission on the next
+ # emulator page; this models a boundary, NOT the card's real PTEs.
+ if any(lo <= END <= hi for lo, hi, _ in sd.uc.mem_regions()):
+ sd.uc.mem_protect(END, 0x1000, UC_PROT_NONE)
+
+ def svc_window(comp, n):
+ out = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, val in enumerate((BASE, END, 0, 0)):
+ comp.wr32(out + 4*i, val)
+ return 0
+
+ sd.svc.register(0xB5, svc_window)
+ leaf_visits: list[int] = []
+ captured: list[bytes] = []
+ sd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, size, data: leaf_visits.append(ea),
+ begin=LEAF, end=LEAF+1)
+
+ def block_mutation(uc, ea, size, data):
+ captured.append(bytes(uc.mem_read(uc.reg_read(UC_ARM_REG_R1), 40)))
+ uc.reg_write(UC_ARM_REG_R0, SENTINEL)
+ uc.reg_write(UC_ARM_REG_PC, uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE, block_mutation, begin=MUTATOR, end=MUTATOR+1)
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, b'\x00' * 0x400)
+ src, edge = BASE+0x1000, END-4
+ planted = bytes(0x31 + i for i in range(40))
+ sd.write(src, planted)
+ sd.write(edge, planted[:4])
+
+ def request(ptr: int, declared: int) -> int:
+ sd.write(req, b'\x00' * 0x100)
+ for i, word in ((0, 0x1033), (2, ptr), (3, declared), (14, 0),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + 4*i, word)
+ return sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+
+ rv = request(src, 4)
+ assert rv == SENTINEL and len(leaf_visits) == 1 and captured == [planted]
+ print('declared input 4: signed leaf copied 40 bytes before state helper (blocked)')
+
+ before_leaf, before_cap = len(leaf_visits), len(captured)
+ denied = request(edge, 40)
+ assert denied != 0 and len(leaf_visits) == before_leaf
+ assert len(captured) == before_cap
+ print('synthetic window edge declared 40: rejected before leaf:', hex(denied))
+
+ try:
+ request(edge, 4)
+ except Fault as exc:
+ assert len(leaf_visits) == before_leaf + 1 and len(captured) == before_cap
+ assert END <= exc.addr < END+40, (exc, sd.faults)
+ print('synthetic edge declared 4: signed leaf fault before state helper:',
+ hex(exc.addr))
+ else:
+ raise AssertionError('short-declared window edge unexpectedly completed')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_1034_failed_status_store.py b/psp_emu/harness/probe_sysdrv_1034_failed_status_store.py
new file mode 100644
index 0000000..77092ba
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1034_failed_status_store.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1034: simulated config success + status-refresh failure
+still writes fixed +0x994 word. Swapped CPU window, no hardware.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_LR,UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+BASEPTR=0xE2579C
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ old=sd.rd32(BASEPTR);fake=sd.alloc(0x1000);req=sd.alloc(0x100)
+ sd.write(fake,bytes(0x1000));sd.wr32(fake+0x990,0x11111111)
+ sd.wr32(fake+0x994,0xA5A5A5A5);sd.wr32(fake+0x998,0x22222222)
+ sd.wr32(BASEPTR,fake)
+ calls=[];modes=[]
+ def ret(uc,value):
+ uc.reg_write(UC_ARM_REG_R0,value)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ def synth_getter(uc,ea,size,data):
+ calls.append(uc.reg_read(UC_ARM_REG_R0))
+ if len(calls)==1:
+ sd.wr32(uc.reg_read(UC_ARM_REG_R0),0)
+ sd.wr32(uc.reg_read(UC_ARM_REG_R1),0)
+ ret(uc,0)
+ else:ret(uc,9)
+ def synth_config(uc,ea,size,data):
+ modes.append(uc.reg_read(UC_ARM_REG_R0))
+ ret(uc,0) # synthetic success, NOT actual CCP/hardware
+ sd.uc.hook_add(UC_HOOK_CODE,synth_getter,begin=0xE0A352,end=0xE0A353)
+ sd.uc.hook_add(UC_HOOK_CODE,synth_config,begin=0xE09E70,end=0xE09E71)
+ try:
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x1034),(1,2),(14,0)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ assert rv==9 and len(calls)==2 and modes==[1],(hex(rv),calls,modes)
+ assert [sd.rd32(fake+x) for x in (0x990,0x994,0x998)]==[0x11111111,0,0x22222222]
+ print(f'signed 0x1034: synthetic config success + status getter error9 -> fixed synthetic CPU word +0x994 cleared despite error; original base {old:#x} untouched')
+ finally:sd.wr32(BASEPTR,old)
+ assert sd.rd32(BASEPTR)==old
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1038_length_gate.py b/psp_emu/harness/probe_sysdrv_1038_length_gate.py
new file mode 100644
index 0000000..69dc404
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1038_length_gate.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+"""Signed .54 free 0x1038 forwards declared output length as loop count.
+No large loop is run: default emulator lacks shared crypto base.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+from pspemu.component import Fault
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,out=[sd.alloc(n) for n in (0x100,0x400,0x40)]
+ sd.write(cfg,bytes(0x400));entry=[];crypto=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:entry.append(tuple(uc.reg_read(x) for x in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2))),begin=0xE07C80,end=0xE07C81)
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:crypto.append(ea),begin=0xE146A0,end=0xE146A1)
+ def request(width,selector):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x1038),(1,2),(2,out),(3,width),(16,selector),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ try:return sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ except Fault as exc:return exc
+ invalid=request(16,0)
+ assert not isinstance(invalid,Fault) and len(entry)==1 and len(crypto)==0
+ valid=request(16,1)
+ assert isinstance(valid,Fault) and len(entry)==2 and len(crypto)==1
+ wide=request(0x800000,1)
+ assert isinstance(wide,Fault) and len(entry)==3 and len(crypto)==2
+ assert entry[-1]==(out,0x800000,1),entry
+ print('signed 0x1038: length 0x800000 passed modeled role window into crypto loop; first helper faulted on missing shared base; no long loop or card effect')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_103e_1047_key_store.py b/psp_emu/harness/probe_sysdrv_103e_1047_key_store.py
new file mode 100644
index 0000000..6c5de28
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_103e_1047_key_store.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 shared-key store: modeled 0x2A000 SVC 0x7D mapping.
+
+Synthetic pool, caller windows and second caller index. This is NOT a card
+experiment, natural sOS mapping, cross-process impact or persistence proof.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn.arm_const import UC_ARM_REG_R0, UC_ARM_REG_R1
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+POOL_SIZE, HEADER_OFF, MAGIC = 0x2A000, 0x29FF0, 0x50535024
+ENTRY, WINDOW, LIMIT = 0xE087E4, 0x400000, 0x800000
+KEY = 0x28
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ pool = sd.alloc(POOL_SIZE)
+ sd.write(pool, bytes(POOL_SIZE))
+ visits: list[int] = []
+
+ def svc_pool(comp, num):
+ visits.append(num)
+ return pool
+
+ def svc_window(comp, num):
+ out = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, word in enumerate((WINDOW, LIMIT, 0, 0)):
+ comp.wr32(out + 4*i, word)
+ return 0
+
+ sd.svc.register(0x7D, svc_pool)
+ sd.svc.register(0x7E, lambda comp, num: comp.uc.reg_read(UC_ARM_REG_R0))
+ sd.svc.register(0xB5, svc_window)
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ # The SVC 0x7D mapping contents have no modeled signed sOS producer.
+ # Plant only the fixed header and a 16-byte initial used-size; subsequent
+ # record creation and overwrite execute signed SYS_DRV code unmodified.
+ sd.wr32(pool + HEADER_OFF, MAGIC)
+ sd.wr32(pool + HEADER_OFF + 4, 0)
+ sd.wr32(pool + HEADER_OFF + 8, 16)
+ sd.uc.mem_map(WINDOW, 0x3000)
+ a, b = WINDOW + 0x1000, WINDOW + 0x2000
+ sd.write(a, b'AAAA')
+ sd.write(b, b'BBBB!')
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, bytes(0x400))
+
+ def request(opcode: int, caller: int, key: int, src: int, length: int) -> int:
+ sd.write(req, bytes(0x100))
+ for i, word in ((0, opcode), (2, src), (3, length), (14, key),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + 4*i, word)
+ return sd.run_function(ENTRY, (req, caller, 0, 0), count=5_000_000,
+ timeout=4_000_000)
+
+ r1 = request(0x1047, 2, KEY, a, 4)
+ count1 = sd.rd32(pool + HEADER_OFF + 4)
+ before = bytes(sd.uc.mem_read(pool, 4))
+ assert r1 == 0 and count1 == 1 and before == b'AAAA', (hex(r1), count1, before)
+ r2 = request(0x1047, 3, KEY, b, 4)
+ count2 = sd.rd32(pool + HEADER_OFF + 4)
+ after = bytes(sd.uc.mem_read(pool, 4))
+ assert r2 == 0 and count2 == 1 and after == b'BBBB', (hex(r2), count2, after)
+ r3 = request(0x1047, 3, KEY, b, 5)
+ assert r3 != 0 and bytes(sd.uc.mem_read(pool, 4)) == b'BBBB'
+ bad_low_mode = request(0x103E, 2, KEY, a, 4)
+ bad_high_mode = request(0x1047, 2, 0x80000028, a, 4)
+ assert bad_low_mode != 0 and bad_high_mode != 0
+ assert sd.rd32(pool + HEADER_OFF + 4) == 1
+ assert bytes(sd.uc.mem_read(pool, 4)) == b'BBBB'
+ print('signed dispatcher: caller 2 creates low-key record, caller 3 replaces same-key bytes')
+ print('count',count1,'->',count2,'data',before,'->',after,
+ 'wrong-length status',hex(r3),'mode mismatch',hex(bad_low_mode),
+ hex(bad_high_mode),'SVC 0x7D visits',len(visits))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_1041_104f_count_boundary.py b/psp_emu/harness/probe_sysdrv_1041_104f_count_boundary.py
new file mode 100644
index 0000000..0e405b5
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1041_104f_count_boundary.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1041/0x104F: 64-entry status depends on index placement.
+All 97 activity bytes and values synthetic; no live firmware-table data.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+FLAGS,VALUES_A,VALUES_B=0xE60A48,0xE60BB0,0xE60D38
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ init,fault,_=sysdrv_env.run_init(sd)
+ assert init==0 and not fault and sysdrv_env.init_flag(sd)==1
+ req,cfg,out=sd.alloc(0x100),sd.alloc(0x400),sd.alloc(0x240,fill=b'\xA5')
+ sd.write(cfg,bytes(0x400))
+ for i in range(1,98):
+ sd.wr32(VALUES_A+4*i,0x10000+i)
+ sd.wr32(VALUES_B+4*i,0x20000+i)
+ for op in (0x1041,0x104F):
+ for label,indices,expected in (
+ ('first63',range(1,64),0),
+ ('first64',range(1,65),0x80000309),
+ ('last64',range(34,98),0),
+ ):
+ sd.write(FLAGS,bytes(100))
+ for i in indices:sd.write(FLAGS+i,b'\x01')
+ sd.write(req,bytes(0x100));sd.write(out,b'\xA5'*0x240)
+ for i,val in ((0,op),(1,2),(2,out),(3,0x204),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,val)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert rv==expected and sd.rd32(out)==len(indices) and not sd.faults,(op,label,rv,sd.rd32(out))
+ assert sd.read(out+0x204,0x3C)==b'\xA5'*0x3C
+ print(f'{op:#x} {label}: status={rv:#x} count={sd.rd32(out)}')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1044_default_gate.py b/psp_emu/harness/probe_sysdrv_1044_default_gate.py
new file mode 100644
index 0000000..75a1c26
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1044_default_gate.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 0x1044 default resource gate only; never call
+stateful SVC 0x8B or plant a resource descriptor.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ rv,f,_=sysdrv_env.run_init(sd)
+ assert rv==0 and not f and sysdrv_env.init_flag(sd)==1
+ assert sd.rd32(0xE45AA0)==0
+ def forbidden(comp,n):
+ raise AssertionError('stateful SVC 0x8B reached')
+ sd.svc.register(0x8B,forbidden)
+ req=sd.alloc(0x100);sd.write(req,bytes(0x100))
+ for i,v in ((0,0x1044),(1,2),(14,0x12345678),(15,0x87654321)):
+ sd.wr32(req+4*i,v)
+ status=sd.run_function(0xE087E4,(req,2,0,0),count=200_000,timeout=2_000_000)
+ assert status==0xFFFF0007 and sd.rd32(req+14*4)==0x12345678 and sd.rd32(req+15*4)==0x87654321
+ print('signed 0x1044: isolated init E45AA0=0 -> 0xffff0007 before stateful SVC8B; inline sentinel unchanged; no live mapping')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_104d_property_prefix.py b/psp_emu/harness/probe_sysdrv_104d_property_prefix.py
new file mode 100644
index 0000000..660020d
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_104d_property_prefix.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 0x104D: static type-1 property accepts short prefixes.
+Do not print property values or treat embedded ELF properties as secrets.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ ret,fault,_=sysdrv_env.run_init(sd)
+ assert ret==0 and not fault and sysdrv_env.init_flag(sd)==1
+ req,cfg,inp=sd.alloc(0x100),sd.alloc(0x400),sd.alloc(0x100)
+ sd.write(cfg,bytes(0x400))
+ def query(key):
+ sd.write(req,bytes(0x100));sd.write(inp,bytes(0x100))
+ sd.write(inp,key)
+ for i,v in ((0,0x104D),(1,2),(2,inp),(3,len(key)),(14,0xFFFFFFFD),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert not sd.faults
+ return rv,sd.rd32(req+60)
+ full=b'gpd.tee.systemTime.protectionLevel\x00'
+ prefix=b'gpd.tee.systemTime.'
+ rv,value=query(full)
+ assert rv==0
+ pval=query(prefix)
+ assert pval==(0,value),('prefix unexpectedly rejected',pval[0])
+ assert query(b'not_a_property')[0]!=0
+ assert query(b'')[0]!=0 # first embedded property is type 4, not type 1
+ print('signed 0x104D(-3) accepted short prefix of ELF-embedded type-1 property; value redacted')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1058_default_gate.py b/psp_emu/harness/probe_sysdrv_1058_default_gate.py
new file mode 100644
index 0000000..6b059e9
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1058_default_gate.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1058 default-state gate, no fabric mapping or card traffic.
+This does NOT prove the gate stays zero in a real session.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY,LEAF,FABRIC=0xE087E4,0xE13170,0xE131B8
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ rv,fault,_=sysdrv_env.run_init(sd)
+ assert rv==0 and not fault and sysdrv_env.init_flag(sd)==1
+ assert sd.read(0xE256F1,1)==b'\0'
+ assert sd.read(0xE256F6,1)!=b'\x18'
+ req,cfg=sd.alloc(0x100),sd.alloc(0x400)
+ sd.write(req,bytes(0x100));sd.write(cfg,bytes(0x400))
+ sd.wr32(req,0x1058);sd.wr32(req+4,2)
+ sd.wr32(req+4*sysdrv_env.REQ_CONFIG_DWORD,cfg)
+ seen=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,n,data:seen.append(ea),begin=LEAF,end=LEAF)
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,n,data:seen.append(ea),begin=FABRIC,end=FABRIC)
+ def no_mapping(comp,num):
+ raise AssertionError('0x1058 unexpectedly attempted fabric map')
+ sd.svc.register(0x7D,no_mapping)
+ status=sd.run_function(ENTRY,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert status==0xFFFF0007 and not sd.faults and seen==[LEAF],(hex(status),sd.faults,seen)
+ print('signed default init: gate+1=0, 0x1058 returns 0xffff0007 before SVC 0x7d')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1059_error_side_effect.py b/psp_emu/harness/probe_sysdrv_1059_error_side_effect.py
new file mode 100644
index 0000000..bbf1e4f
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1059_error_side_effect.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+"""SYS_DRV .54 0x1059: synthetic descriptor-bit mutation before error return.
+
+Signed dispatcher and leaf execute; SVC 0x8B returns modeled failure. This
+never maps a hardware region or establishes live ownership/restoration.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY, TABLE = 0xE087E4, 0xE45A20
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ ret, fault, _ = sysdrv_env.run_init(sd)
+ assert ret == 0 and fault is None and sysdrv_env.init_flag(sd) == 1
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, bytes(0x400))
+ calls: list[int] = []
+ def deny_region(comp, number):
+ calls.append(number)
+ return 9
+ sd.svc.register(0x8B, deny_region)
+ slot, neighbor, last = TABLE + 8*3, TABLE+8*4, TABLE+8*15
+ old0, old1, old15 = sd.rd32(slot), sd.rd32(neighbor), sd.rd32(last)
+ sd.wr32(slot, 0)
+ sd.wr32(neighbor, 0x11223344)
+ sd.wr32(last, 0)
+
+ def request(index: int, flag: int) -> int:
+ sd.write(req, bytes(0x100))
+ for i,word in ((0,0x1059),(14,0x1234),(15,index),(16,flag),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req + 4*i, word)
+ rv = sd.run_function(ENTRY,(req,2,0,0),count=3_000_000,
+ timeout=4_000_000)
+ assert not sd.faults, (hex(rv),sd.faults)
+ return rv & 0xffffffff
+
+ try:
+ rv=request(3,1)
+ assert rv == 0xffff000a and sd.rd32(slot)==0 and not calls, (hex(rv),calls)
+ rv=request(16,0)
+ assert rv == 0xffff000a and sd.rd32(slot)==0 and not calls, (hex(rv),calls)
+ rv=request(3,0)
+ print('status',hex(rv),'selected',hex(sd.rd32(slot)),
+ 'neighbor',hex(sd.rd32(neighbor)),'SVC 0x8B calls',len(calls))
+ assert rv==0xffff000a and sd.rd32(slot)==5
+ assert sd.rd32(neighbor)==0x11223344 and calls==[0x8b], calls
+ rv=request(15,0)
+ assert rv==0xffff000a and sd.rd32(last)==5 and len(calls)==2
+ # Only exercise the signed 0x311A leaf's early invalid-index branch;
+ # never invoke the stateful clear/unmap path for an accepted selector.
+ denied=sd.run_function(0xE1B48C,(15,),count=10_000,timeout=1_000_000)
+ assert not sd.faults and denied != 0 and sd.rd32(last)==5
+ assert len(calls)==2, calls
+ print('slot 15 set to',hex(sd.rd32(last)),
+ 'signed 0x311A early guard returns',hex(denied & 0xffffffff))
+ finally:
+ sd.wr32(slot,old0)
+ sd.wr32(neighbor,old1)
+ sd.wr32(last,old15)
+ assert sd.rd32(slot)==old0 and sd.rd32(neighbor)==old1 and sd.rd32(last)==old15
+ print('synthetic descriptor snapshot restored; no card request')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_105a_error_flag.py b/psp_emu/harness/probe_sysdrv_105a_error_flag.py
new file mode 100644
index 0000000..9f0da97
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_105a_error_flag.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+"""Signed .54 0x105A selector3: model only fixed-register helper failure
+and check whether private global status is still changed. No card I/O.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_LR,UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+FLAG=0xE26154;GATE=0xE26164
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req=sd.alloc(0x100);record=sd.alloc(0x40)
+ sd.write(record,bytes(0x40));sd.wr32(record,4);sd.wr32(record+4,528)
+ before=sd.rd32(FLAG);gate=sd.rd32(GATE)
+ assert gate==0x100 and before==0,(hex(gate),hex(before)) # default signed-model gate is closed
+ seen=[];consumed=[]
+ def fail_hw(uc,ea,size,data):
+ seen.append(uc.reg_read(UC_ARM_REG_R0))
+ uc.reg_write(UC_ARM_REG_R0,0x80000306)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE,fail_hw,begin=0xE02A38,end=0xE02A39)
+ def capture_consumer(uc,ea,size,data):
+ consumed.append(uc.reg_read(UC_ARM_REG_R0))
+ uc.reg_write(UC_ARM_REG_R0,0x4242)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE,capture_consumer,begin=0xE201BC,end=0xE201BD)
+ try:
+ assert sd.run_function(0xE20108,(record,),count=200_000,timeout=200_000)==0 and not consumed
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x105A),(1,2),(14,3)):
+ sd.wr32(req+4*i,v)
+ blocked=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ assert blocked!=0 and not seen and sd.rd32(FLAG)==before,(hex(blocked),seen)
+ sd.wr32(GATE,0) # synthetic firmware-derived gate, restored below
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ assert seen==[528] and rv==0x80000306 and sd.rd32(FLAG)==before|8,(seen,hex(rv),hex(before),hex(sd.rd32(FLAG)))
+ consumer=sd.run_function(0xE20108,(record,),count=200_000,timeout=200_000)
+ assert consumer==0x4242 and consumed==[528],(hex(consumer),consumed)
+ print(f'signed 0x105a(3): default gate={gate:#x} blocked={blocked:#x}; planted gate0 + modeled helper failure={rv:#x} changed flag {before:#x}->{sd.rd32(FLAG):#x}; signed type4/528 consumer then reached stubbed helper (was gated before); no hardware')
+ finally:
+ sd.wr32(FLAG,before)
+ sd.wr32(GATE,gate)
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_105d_zero_length.py b/psp_emu/harness/probe_sysdrv_105d_zero_length.py
new file mode 100644
index 0000000..5cdc40d
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_105d_zero_length.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+"""Signed .54 0x105D zero-length range: capture modeled fixed TMR writes only.
+No card, fabric, or external client's descriptor is modified.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_R6,UC_ARM_REG_LR,UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def run(start,length):
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ # Bypass only the model's external-timebase-dependent mutex helper;
+ # the signed validation, descriptor allocator and TMR writes still run.
+ def mutex_success(uc,ea,size,data):
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE,mutex_success,begin=0xE16210,end=0xE16211)
+ hits=[];entered=[];types=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,pc,size,data:types.append((uc.reg_read(UC_ARM_REG_R6),uc.reg_read(UC_ARM_REG_R1),sd.rd32(uc.reg_read(UC_ARM_REG_R6)+16))),begin=0xE14550,end=0xE14551)
+ for ea in (0xE1447C,0xE144D2,0xE144F2,0xE144FA,0xE14500,0xE14508,0xE145E2,0xE17EF4,0xE218FC):
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,pc,size,data:entered.append(pc),begin=ea,end=ea+1)
+ def on_write(comp,n):
+ hits.append(tuple(comp.uc.reg_read(reg) for reg in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2)))
+ return 0
+ sd.svc.register(0x7C,on_write)
+ req=sd.alloc(0x100)
+ sd.write(req,bytes(0x100))
+ # msg[18] is the subtype for 0x105D. The config pointer at the
+ # same index is needed only for init (0x1000), not this request.
+ for i,v in ((0,0x105D),(1,2),(14,start),(15,0),(16,length),(17,0),(18,49)):
+ sd.wr32(req+i*4,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ return rv,hits,sd.rd32(req+19*4),entered,types
+
+def main():
+ for start,length,want in ((0,0,(0,0xffffffff,0x37,0x30038)),
+ (0,0x10000,(0,0,0x37,0x30038)),
+ (0x10000,0,(1,0,0x37,0x30038))):
+ rv,hits,slot,entered,types=run(start,length)
+ assert rv==0 and types and types[0][1:]==(49,49) and 0xE17EF4 in entered,(rv,entered,types)
+ assert hits==[(0x49007a00+4*i,v,4) for i,v in enumerate(want)],hits
+ print(f'start={start:#x} length={length:#x} status={rv:#x} slot={slot:#x} model-fabric={[hex(v) for v in want]}')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_105f_reg_tables.py b/psp_emu/harness/probe_sysdrv_105f_reg_tables.py
new file mode 100644
index 0000000..1233056
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_105f_reg_tables.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+"""Read only modeled SYS_DRV .54 0x105F indexed register-address tables.
+Local signed init uses synthetic flash and default-success unrelated SVCs.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ groups=((0xE2664C,16),(0xE2668C,16),(0xE266CC,16),(0xE2670C,16),
+ (0xE2674C,16),(0xE2678C,16),(0xE267CC,16),
+ (0xE2685C,18),(0xE268A4,18))
+ before=[tuple(sd.rd32(addr+4*i) for i in range(n)) for addr,n in groups]
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ after=[tuple(sd.rd32(addr+4*i) for i in range(n)) for addr,n in groups]
+ for (addr,n),pre,post in zip(groups,before,after):
+ print(f'table={addr:#x} n={n} pre-nonzero={sum(bool(x) for x in pre)} post-nonzero={sum(bool(x) for x in post)} changed={sum(a!=b for a,b in zip(pre,post))} addresses={[hex(x) for x in post]}')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_105f_short_input.py b/psp_emu/harness/probe_sysdrv_105f_short_input.py
new file mode 100644
index 0000000..27bd803
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_105f_short_input.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+"""Signed .54 0x105F reads +32/+61 of a four-byte-declared input.
+Synthetic caller-window edge; no card/fabric write.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+from pspemu.component import Fault
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,inp=sd.alloc(0x100),sd.alloc(0x80)
+ sd.write(inp,bytes(0x80))
+ writes=[]
+ sd.svc.register(0x7C,lambda c,n:writes.append(tuple(c.uc.reg_read(reg) for reg in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2))) or 0)
+ def run(ptr,length):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x105F),(1,2),(2,ptr),(3,length)):
+ sd.wr32(req+4*i,v)
+ try:return sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ except Fault as exc:return exc
+ mapped=run(inp,4)
+ assert mapped==0 and not writes,(mapped,writes)
+ edge=sd.cmap.scratch+sd.cmap.scratch_size-4
+ sd.write(edge,b'ABCD')
+ def svc_window(comp,n):
+ p=comp.uc.reg_read(UC_ARM_REG_R1)
+ for i,v in enumerate((sd.cmap.scratch,sd.cmap.scratch+sd.cmap.scratch_size,0,0)):
+ comp.wr32(p+4*i,v)
+ return 0
+ sd.svc.register(0xB5,svc_window)
+ fault=run(edge,4)
+ assert isinstance(fault,Fault) and (fault.pc,fault.addr)==(0xE148EA,edge+32),(fault,writes)
+ denied=run(edge,63)
+ assert denied==0xFFFF000F and not writes,(denied,writes)
+ # A separate, wholly GCP-owned mapped control demonstrates the later
+ # signed register command: modeled SVC 0x7C captures but never executes it.
+ sd.wr32(inp+24,0x13579BDF);sd.wr32(inp+28,0x2468ACE0)
+ rv_short=run(inp,4);short=tuple(writes);writes.clear()
+ rv_full=run(inp,64);full=tuple(writes)
+ expected=((0x69EAC,0x13579BDF,4),(0x69EB0,0x2468ACE0,4),
+ (0x69F2C,0,4),(0x69F30,0,4),(0x69FAC,0,4),(0x69FB0,0,4))
+ assert rv_short==rv_full==0 and short==full==expected,(rv_short,rv_full,short,full)
+ print(f'signed 0x105F: four-byte-declared first read fault pc={fault.pc:#x} FAR={fault.addr:#x}; declared63 edge rejected {denied:#x}; modeled declared4/64 controls captured identical six fixed-address writes (no hardware)')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1063_short_output.py b/psp_emu/harness/probe_sysdrv_1063_short_output.py
new file mode 100644
index 0000000..d79bf9c
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1063_short_output.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 free 0x1063 writes 172 bytes with 4 declared output.
+All destinations are synthetic GCP-owned mapped memory; no card effect.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,out=sd.alloc(0x100),sd.alloc(0x400),sd.alloc(0xC0,fill=b'\xA5')
+ sd.write(cfg,bytes(0x400))
+ def run(declared,count):
+ sd.write(req,bytes(0x100));sd.write(out,b'\xA5'*0xC0)
+ for i,v in ((0,0x1063),(1,2),(2,out),(3,declared),(14,count),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert not sd.faults
+ return rv,sd.read(out,0xC0)
+ err,untouched=run(4,5)
+ assert err!=0 and untouched==b'\xA5'*0xC0
+ rv,short=run(4,6)
+ assert rv==0 and short[168:172]!=b'\xA5'*4 and short[172:]==b'\xA5'*20
+ assert int.from_bytes(short[:4],'little')==1
+ assert sd.rd32(req+56)==6 # count field updated, independent of output length
+ full,wide=run(172,6)
+ assert full==0 and wide[:172]==short[:172] and wide[172:]==b'\xA5'*20
+ print('signed 0x1063: 4-byte declared output, selected store through +171; full-length and count-5 controls passed')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1067_failed_lock_count.py b/psp_emu/harness/probe_sysdrv_1067_failed_lock_count.py
new file mode 100644
index 0000000..c40a2a9
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1067_failed_lock_count.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1067: bounded modeled lock failure still increments count.
+Synthetic fixed-register SVCs and time yield; no card/fabric effects.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_LR,UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+COUNT=0xE252D0
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ original=sd.read(COUNT,1)
+ assert original==b'\x00',original
+ req=sd.alloc(0x100)
+ reads=[];writes=[];clears=[]
+ def read_fixed(comp,n):
+ addr=comp.uc.reg_read(UC_ARM_REG_R0)
+ dest=comp.uc.reg_read(UC_ARM_REG_R1)
+ reads.append(addr)
+ assert addr in (0x3B10024,0x49005300),hex(addr)
+ sd.wr32(dest,0 if addr==0x3B10024 or reads.count(0x49005300)==1 else 1)
+ return 0
+ def write_fixed(comp,n):
+ writes.append(tuple(comp.uc.reg_read(reg) for reg in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2)))
+ return 0
+ def masked_clear(comp,n):
+ clears.append(tuple(comp.uc.reg_read(reg) for reg in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2)))
+ return 0
+ sd.svc.register(0x7B,read_fixed)
+ sd.svc.register(0x7C,write_fixed)
+ sd.svc.register(0xA6,masked_clear)
+ def model_yield(uc,ea,size,data):
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE,model_yield,begin=0xE1ECEE,end=0xE1ECEF)
+ def run(selector,timeout):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x1067),(1,2),(14,selector),(15,timeout)):
+ sd.wr32(req+4*i,v)
+ return sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ try:
+ failed=run(0,1)
+ assert failed==0x80000306 and sd.read(COUNT,1)==b'\x01',(hex(failed),sd.read(COUNT,1),reads,writes)
+ assert writes==[(0x49005300,0x20,4)],writes
+ read_count=len(reads)
+ false_success=run(0,1)
+ assert false_success==0 and sd.read(COUNT,1)==b'\x02' and len(reads)==read_count and len(writes)==1,(false_success,reads,writes)
+ first_release=run(1,1)
+ assert first_release==0 and sd.read(COUNT,1)==b'\x01' and not clears,(first_release,clears)
+ second_release=run(1,1)
+ assert second_release==0 and sd.read(COUNT,1)==b'\x00',(second_release,clears)
+ assert clears==[(0x49005300,0,0x20)],clears
+ print('signed 0x1067: modeled timeout1 returned 0x80000306 yet count 0->1; next acquire returned false success 0 and count2 without register read; two releases reached fixed SVC A6 bit5 clear (no hardware)')
+ finally:sd.write(COUNT,original)
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_106a_short_spans.py b/psp_emu/harness/probe_sysdrv_106a_short_spans.py
new file mode 100644
index 0000000..d024752
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_106a_short_spans.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 0x106A selector 2: input/output declared span mismatch.
+Artificial gate only if natural emulator init leaves E25010 disabled.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+GATE=0xE25010
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,inp,out=sd.alloc(0x100),sd.alloc(0x400),sd.alloc(0x40),sd.alloc(0x80)
+ sd.write(cfg,bytes(0x400));sd.write(inp,bytes(0x40))
+ sd.wr32(inp+4,2);sd.wr32(inp+12,0)
+ old=sd.rd32(GATE)
+ def run(inlen,outlen):
+ sd.write(req,bytes(0x100));sd.write(out,b'\xA5'*0x80)
+ for i,v in ((0,0x106A),(1,2),(2,inp),(3,inlen),(4,out),(5,outlen),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert not sd.faults
+ return rv,sd.read(out,0x80)
+ if old==0:
+ rv,unchanged=run(16,4)
+ assert rv==0x80000310 and unchanged==b'\xA5'*0x80
+ sd.wr32(GATE,1) # synthetic private state; never done on hardware
+ try:
+ rv,short=run(16,4)
+ assert rv==0 and short[20:24]!=b'\xA5'*4 and short[64:68]!=b'\xA5'*4 and short[68:]==b'\xA5'*0x3C
+ rv,wide=run(16,68)
+ assert rv==0 and wide[:68]==short[:68]
+ rv,shortboth=run(4,4)
+ assert rv==0 and shortboth[:68]==short[:68]
+ finally:
+ sd.wr32(GATE,old)
+ print(f'signed 0x106A(2): gate-init={old}, declared input/output 4/4 still reaches +12 read and +64 store; restored modeled gate')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_106c_length_wrap.py b/psp_emu/harness/probe_sysdrv_106c_length_wrap.py
new file mode 100644
index 0000000..8513598
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_106c_length_wrap.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+"""Signed .54 free 0x106C: rounded request wraps to zero, repeats same result.
+Plants private pool base only in emulator and restores snapshots.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+BASE_LO,BASE_HI,USED=0xE5FE84,0xE5FE88,0xE25790
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ initial=[sd.rd32(a) for a in (BASE_LO,BASE_HI,USED)]
+ req,cfg=sd.alloc(0x100),sd.alloc(0x400)
+ sd.write(cfg,bytes(0x400))
+ def run(length):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x106C),(1,2),(16,length),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert not sd.faults
+ return rv,sd.rd32(req+56),sd.rd32(req+60),sd.rd32(USED)
+ default_status=run(0xFFFFF000)[0]
+ assert [sd.rd32(a) for a in (BASE_LO,BASE_HI,USED)]==initial
+ try:
+ sd.wr32(BASE_LO,0x10000000);sd.wr32(BASE_HI,0);sd.wr32(USED,0)
+ normal=run(0x1000)
+ assert normal[0]==0xFFFF000C and normal[3]==0
+ assert run(0xFFFFE000)[0]==0xFFFF000C
+ assert run(0xFFFFE001)[0]==0
+ first=run(0xFFFFF000)
+ second=run(0xFFFFF000)
+ assert first[0]==second[0]==0 and first[1:]==second[1:] and first[3]==0
+ assert run(0xFFFFF001)[0]==0xFFFF000C
+ sd.wr32(USED,1)
+ assert run(0xFFFFF000)[0]==0xFFFF000C
+ finally:
+ for a,v in zip((BASE_LO,BASE_HI,USED),initial):sd.wr32(a,v)
+ print(f'signed 0x106C: default base_present={int(bool(initial[0] or initial[1]))} default_status={default_status:#x}; wrapped length repeated success with zero progress, controls passed')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1076_dispatch.py b/psp_emu/harness/probe_sysdrv_1076_dispatch.py
new file mode 100644
index 0000000..61f1189
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1076_dispatch.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 opcode 0x1076 short-output test, synthetic caller region.
+
+No sOS/GCP transport or hardware effects are modeled. The successful selector-1
+path also clears SYS_DRV internal state and invokes SVC 0x7B: never use this
+harness as a justification for an unplanned hardware request.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R1
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.component import Fault # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY = 0xE087E4
+LEAF = 0xE2005C
+TABLE = 0xE6873C
+END_OF_GCP = 0x41C000
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes(), 'image differs from signed .54'
+ init_ret, init_fault, _ = sysdrv_env.run_init(sd)
+ assert init_ret == 0 and not init_fault and sysdrv_env.init_flag(sd) == 1
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, b'\x00' * 0x400)
+ seen = []
+ sd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, size, data: seen.append(ea),
+ begin=LEAF, end=LEAF)
+
+ sd.write(req, b'\x00' * 0x100)
+ for i, v in ((0, 0x1076), (1, 2), (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + 4 * i, v)
+ for declared in (4, 52):
+ out = sd.alloc(0x80, fill=b'\xA5')
+ sd.wr32(out, 1) # selector 1 selects the fixed 24-byte zeroing branch
+ sd.wr32(req + 8, out)
+ sd.wr32(req + 12, declared)
+ sd.wr32(TABLE + 4, 0x55667788)
+ before = len(seen)
+ rv = sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+ after = sd.read(out, 0x80)
+ assert rv == 0 and len(seen) == before + 1 and not sd.faults, (rv, sd.faults)
+ assert after[:4] == b'\x01\x00\x00\x00'
+ assert after[4:28] == b'\xA5' * 24 and after[28:52] == b'\x00' * 24
+ assert after[52:] == b'\xA5' * (0x80 - 52)
+ assert sd.rd32(TABLE + 4) == 0
+ print('declared', declared, 'status', hex(rv), 'leaf', len(seen) - before,
+ 'changed offsets 28..51; synthetic table cleared')
+
+ # Caller-window model, not the live sOS/GCP mapping. The declaration of 52
+ # must be rejected, while 4 may pass and then fault at the first store.
+ base, end = 0x400000, END_OF_GCP
+ sd.uc.mem_map(base, end - base)
+ edge = end - 4
+ sd.wr32(edge, 1)
+ sd.wr32(req + 8, edge)
+ sd.wr32(TABLE + 4, 0x55667788)
+
+ def caller_window(comp, num):
+ descriptor = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, val in enumerate((base, end, 0, 0)):
+ comp.wr32(descriptor + 4 * i, val)
+ return 0
+
+ sd.svc.register(0xB5, caller_window)
+ before = len(seen)
+ sd.wr32(req + 12, 52)
+ rejected = sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+ assert rejected != 0 and len(seen) == before
+ assert sd.rd32(edge) == 1 and sd.rd32(TABLE + 4) == 0x55667788
+ sd.wr32(req + 12, 4)
+ try:
+ sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+ except Fault as exc:
+ assert end <= exc.addr < end + 0x40, (exc, sd.faults)
+ assert len(seen) == before + 1 and sd.rd32(TABLE + 4) == 0x55667788
+ assert sd.rd32(edge) == 1
+ print('synthetic window: length52 rejected', hex(rejected),
+ 'length4 first out-of-range store faults at', hex(exc.addr))
+ else:
+ raise AssertionError('short length did not fault at synthetic window edge')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_1079_fixed_write_gate.py b/psp_emu/harness/probe_sysdrv_1079_fixed_write_gate.py
new file mode 100644
index 0000000..0b69e2a
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1079_fixed_write_gate.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1079 fixed-fabric path with restricted vs privileged
+synthetic SVC 0x76 caps. Captures SVC 0x7C; no hardware effects.
+"""
+from pathlib import Path
+import sys
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+GATE=0xE26314
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ original=sd.rd32(GATE)
+ req=sd.alloc(0x100);writes=[]
+ caps=[0x10] # Synthetic restricted profile, NOT measured GCP rights.
+ sd.svc.register(0x76,lambda c,n:int((c.uc.reg_read(UC_ARM_REG_R0)&~caps[0])==0))
+ sd.svc.register(0x7C,lambda c,n:writes.append(tuple(c.uc.reg_read(reg) for reg in (UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2))) or 0)
+ def run(op,value):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,op),(1,2),(14,value)):
+ sd.wr32(req+4*i,v)
+ return sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ try:
+ assert original==2,hex(original)
+ assert run(0x1079,0x12345678)==0 and writes==[(0x3B30600,0x12345678,4)],writes
+ denied=run(0x3131,1)
+ assert denied==0xFFFF0001 and sd.rd32(GATE)==original,(denied,sd.rd32(GATE))
+ assert run(0x1079,0xEE000123)==0 and len(writes)==1,writes
+ assert run(0x1079,0xEF000123)==0 and len(writes)==1,writes
+ caps[0]=0xFFFF0 # Synthetic privileged issuer, NOT GCP.
+ setter=run(0x3131,0)
+ assert setter==0 and sd.rd32(GATE)==0,(setter,sd.rd32(GATE))
+ caps[0]=0x10
+ assert run(0x1079,0x12345678)==0 and len(writes)==1
+ caps[0]=0xFFFF0
+ assert run(0x3131,original)==0 and sd.rd32(GATE)==original
+ print(f'signed modeled init gate={original:#x}: restricted caps=0x10 0x1079 emitted chosen-value SVC7C (captured); 0x3131 denied={denied:#x}, EE/EF skipped; privileged gate0 suppressed it, restored original; no hardware')
+ finally:
+ caps[0]=0xFFFF0
+ if sd.rd32(GATE)!=original:
+ run(0x3131,original)
+ assert sd.rd32(GATE)==original
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_107b_dispatch.py b/psp_emu/harness/probe_sysdrv_107b_dispatch.py
new file mode 100644
index 0000000..9fe208b
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_107b_dispatch.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+"""Bounded signed SYS_DRV .54 dispatcher/validator test for 0x107B output size.
+
+Synthetic SVC 0xB5 reports a permissive region: this is NOT a full sOS/GCP
+service-1 test or a hardware mapping/oracle. No hardware state is changed.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.component import Fault # noqa: E402
+from unicorn.arm_const import UC_ARM_REG_R1
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY = 0xE087E4
+LEAF = 0xE0AB74
+HEADER = 0xE26170
+TABLE = 0xE6873C
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes(), 'emulated image is not signed .54'
+ init_ret, init_fault, _ = sysdrv_env.run_init(sd)
+ assert init_ret == 0 and not init_fault and sysdrv_env.init_flag(sd) == 1, (init_ret, init_fault)
+ print('signed image:', Path(sd.image_path).name, 'init gate:', sysdrv_env.init_flag(sd))
+ req = sd.alloc(0x100)
+ cfg = sd.alloc(0x400)
+ sd.write(cfg, b'\x00' * 0x400)
+ seen = []
+ sd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, n, data: seen.append(ea),
+ begin=LEAF, end=LEAF)
+ for n in (4, 52):
+ out = sd.alloc(0x80, fill=b'\xA5')
+ sd.write(HEADER, b'\x00' * 8)
+ sd.write(TABLE, b'\x00' * 2048)
+ sd.wr32(TABLE + 4, 0x55667788)
+ sd.write(HEADER + 4, b'\x01\x00')
+ sd.write(req, b'\x00' * 0x100)
+ for i, v in ((0, 0x107B), (1, 2), (2, out), (3, n),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + 4 * i, v)
+ before = len(seen)
+ rv = sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+ changed = [i for i, b in enumerate(sd.read(out, 0x80)) if b != 0xA5]
+ print('declared', n, 'status', hex(rv), 'leaf entry', len(seen) - before,
+ 'changed', (min(changed), max(changed)) if changed else None,
+ 'table cleared', sd.rd32(TABLE + 4) == 0)
+ assert rv == 0 and len(seen) - before == 1 and changed
+ assert min(changed) == 28 and max(changed) == 51
+ assert sd.rd32(TABLE + 4) == 0
+
+ # Substitute only the SVC B5 caller-region descriptor with the previously
+ # measured GCP-window model. This is STILL synthetic, not real sOS/GCP.
+ base, end = 0x400000, 0x41C000
+ sd.uc.mem_map(base, end - base)
+ edge = end - 4
+ sd.write(edge, b'\xA5' * 4)
+ sd.wr32(TABLE + 4, 0x55667788)
+ sd.write(HEADER + 2, b'\x00\x00\x01\x00')
+
+ def svc_gcp_window(comp, n):
+ descriptor = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, val in enumerate((base, end, 0, 0)):
+ comp.wr32(descriptor + i * 4, val)
+ return 0
+
+ sd.svc.register(0xB5, svc_gcp_window)
+ sd.wr32(req + 2 * 4, edge)
+ before = len(seen)
+ sd.wr32(req + 3 * 4, 52)
+ too_long = sd.run_function(ENTRY, (req, 2, 0, 0),
+ count=3_000_000, timeout=4_000_000)
+ assert too_long != 0 and len(seen) == before
+ assert sd.read(edge, 4) == b'\xA5' * 4
+ sd.wr32(req + 3 * 4, 4)
+ try:
+ sd.run_function(ENTRY, (req, 2, 0, 0),
+ count=3_000_000, timeout=4_000_000)
+ except Fault as exc:
+ assert end <= exc.addr < end + 0x40, (exc, sd.faults)
+ assert len(seen) == before + 1
+ assert sd.rd32(TABLE + 4) == 0x55667788
+ print('synthetic GCP-window edge: length52 rejected', hex(too_long),
+ 'length4 reaches leaf then faults', hex(exc.addr))
+ else:
+ raise AssertionError('short length unexpectedly returned at synthetic window edge')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_107b_multi_record_loss.py b/psp_emu/harness/probe_sysdrv_107b_multi_record_loss.py
new file mode 100644
index 0000000..25aa497
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_107b_multi_record_loss.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+"""Signed .54 0x107B multi-record snapshot loss, synthetic helper inputs/count.
+No live RAP session or card mutation. Signed dispatcher/leaf run unchanged;
+SYS_DRV init retains emulator's unrelated default-success SVCs.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY,INSERT,HEADER,TABLE=0xE087E4,0xE0D60C,0xE26170,0xE6873C
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ ret,fault,_=sysdrv_env.run_init(sd)
+ assert ret==0 and fault is None and sysdrv_env.init_flag(sd)==1
+ req,cfg,out=sd.alloc(0x100),sd.alloc(0x400),sd.alloc(0x80,fill=b'\xA5')
+ sd.write(cfg,bytes(0x400));sd.write(req,bytes(0x100))
+ for i,x in ((0,0x107B),(1,2),(2,out),(3,52),(sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,x)
+ def scenario(records):
+ sd.write(HEADER,bytes(8));sd.write(TABLE,bytes(2048))
+ sd.write(out,bytes([0xA5])*0x80)
+ # Drive the signed table-insertion helper; synthetic keys/values, not
+ # signed RAP/ASIC producers. The caller must account for count as the
+ # real selector does; the helper itself tracks the high-water index.
+ for vals in records:
+ result=sd.run_function(INSERT,(vals[0],vals[1],vals[2],vals[3]),
+ count=100_000,timeout=1_000_000)
+ assert result==0,(result,vals)
+ sd.write(HEADER+4,len(records).to_bytes(2,'little'))
+ rv=sd.run_function(ENTRY,(req,2,0,0),count=3_000_000,timeout=4_000_000)
+ assert not sd.faults and rv==0,(rv,sd.faults)
+ assert tuple(sd.rd32(out+4*i) for i in range(7,13))==(
+ len(records),0,records[-1][1],records[-1][2],records[-1][3],records[-1][0])
+ assert sd.read(TABLE,32)==bytes(32) and sd.read(HEADER+2,4)==bytes(4)
+ print(f'signed-helper records={len(records)}: returned count={sd.rd32(out+28)}, '
+ f'only last key={sd.rd32(out+36):#x}; table zeroed')
+ a=(0x11111111,0xAAAA0001,0x11112222,0x11113333)
+ b=(0x22222222,0xBBBB0002,0x22223333,0x22224444)
+ scenario([a]);scenario([a,b])
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_107b_output_size.py b/psp_emu/harness/probe_sysdrv_107b_output_size.py
new file mode 100644
index 0000000..2aff30e
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_107b_output_size.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+"""Isolated signed SYS_DRV .54 0x107B leaf: actual stores vs declared length.
+
+Does NOT emulate the sOS/SYS_DRV/GCP service transport or establish a hardware
+cross-process write. Executes only signed sub_E0AB74 with synthetic globals.
+"""
+from __future__ import annotations
+
+from pathlib import Path
+import sys
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import Component # noqa: E402
+from pspemu.component import Fault # noqa: E402
+
+REPO = Path(__file__).resolve().parents[2]
+IMAGE = REPO / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+LEAF = 0xE0AB74
+HEADER = 0xE26170
+TABLE = 0xE6873C
+DECLARED = 4
+
+
+def main() -> None:
+ comp = Component('sys_drv', image_path=str(IMAGE))
+ output = comp.alloc(0x80, fill=b'\xA5')
+ comp.write(HEADER, b'\x00' * 8)
+ comp.write(TABLE, b'\x00' * 2048)
+ comp.wr32(TABLE, 0x11223344)
+ comp.wr32(TABLE + 4, 0x55667788)
+ comp.wr32(TABLE + 8, 0x99AABBCC)
+ comp.wr32(TABLE + 12, 0xDDEEFF00)
+ comp.write(HEADER + 2, b'\x00\x00') # last occupied slot = 0
+ comp.write(HEADER + 4, b'\x01\x00') # occupied count = 1
+
+ ret = comp.run_function(LEAF, (output,), thumb=True, count=20000,
+ timeout=2_000_000)
+ after = comp.read(output, 0x80)
+ changed = [i for i, (a, b) in enumerate(zip(after, b'\xA5' * 0x80)) if a != b]
+ assert ret == 0 and not comp.faults, (ret, comp.faults)
+ assert all(b == 0xA5 for b in after[:DECLARED])
+ assert changed and min(changed) == 28 and max(changed) == 51, changed
+ assert int.from_bytes(after[48:52], 'little') == 0x11223344
+ assert comp.read(TABLE, 2048) == b'\x00' * 2048
+ assert comp.read(HEADER + 2, 4) == b'\x00' * 4
+ print('signed leaf', hex(LEAF), 'declared length', DECLARED,
+ 'changed output offsets', min(changed), '..', max(changed),
+ 'table/reset yes; no service transport modeled')
+
+ # A separate synthetic caller-window edge: the nominal four-byte output
+ # is mapped, but the later fixed-size writes are not. This deliberately
+ # models an unmapped adjacent page, not the card's real next-page PTE.
+ edge = Component('sys_drv', image_path=str(IMAGE))
+ guard = edge.cmap.scratch + 0x3000
+ edge.wr32(TABLE + 4, 0x55667788)
+ edge.write(HEADER + 2, b'\x00\x00\x01\x00')
+ edge.write(guard - DECLARED, b'\xA5' * DECLARED)
+ edge.uc.mem_unmap(guard, 0x1000)
+ try:
+ edge.run_function(LEAF, (guard - DECLARED,), thumb=True,
+ count=20000, timeout=2_000_000)
+ except Fault as exc:
+ assert guard <= exc.addr < guard + 0x40, (exc, edge.faults)
+ assert edge.rd32(TABLE + 4) == 0x55667788 # reset was not reached
+ print('synthetic adjacent guard fault', hex(exc.addr),
+ 'while declared four bytes remained mapped')
+ else:
+ raise AssertionError('missing guard-page fault')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_1082_privileged_scalar.py b/psp_emu/harness/probe_sysdrv_1082_privileged_scalar.py
new file mode 100644
index 0000000..d532dda
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1082_privileged_scalar.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+"""Signed .54 0x1082 reads global word set by privileged 0x3133.
+Synthetic sOS SVC76 profiles; public marker only, no card access.
+"""
+from pathlib import Path
+import sys
+from unicorn.arm_const import UC_ARM_REG_R0
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+STATE=0xE2584C
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ original=sd.rd32(STATE)
+ assert original==0,hex(original)
+ caps=[0x10] # synthetic restricted, not measured GCP caps
+ sd.svc.register(0x76,lambda c,n:int((c.uc.reg_read(UC_ARM_REG_R0)&~caps[0])==0))
+ req=sd.alloc(0x100)
+ def run(op,val=0):
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,op),(1,2),(14,val)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ return rv,sd.rd32(req+14*4)
+ try:
+ assert run(0x1082)==(0,0)
+ denied=run(0x3133,0x12345678)
+ assert denied[0]==0xFFFF0001 and sd.rd32(STATE)==0,denied
+ caps[0]=0xFFFF0 # synthetic privileged issuer, NOT GCP
+ assert run(0x3133,0x12345678)[0]==0 and sd.rd32(STATE)==0x12345678
+ caps[0]=0x10
+ read=run(0x1082)
+ assert read==(0,0x12345678),read
+ caps[0]=0xFFFF0
+ assert run(0x3133,original)[0]==0 and sd.rd32(STATE)==original
+ print('signed 0x3133 restricted denied; synthetic privileged marker set; free 0x1082 restricted read marker inline; signed privileged restoration, no live state')
+ finally:
+ caps[0]=0xFFFF0
+ if sd.rd32(STATE)!=original:
+ run(0x3133,original)
+ assert sd.rd32(STATE)==original
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1084_short_output_inline.py b/psp_emu/harness/probe_sysdrv_1084_short_output_inline.py
new file mode 100644
index 0000000..290e731
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1084_short_output_inline.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+"""Signed .54 free 0x1084 inline status words vs declared output length.
+Plants only public synthetic bytes in SYS_DRV private state, restores model.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+WORD_A=0xE6027C;SOURCE=0xE60280;WORD_B=0xE60384
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req=sd.alloc(0x100);out=sd.alloc(0x40)
+ old=(sd.read(WORD_A,20),sd.read(WORD_B,4))
+ source=bytes(range(0x10))
+ def run(n):
+ sd.write(req,bytes(0x100));sd.write(out,b'Z'*0x40)
+ for i,v in ((0,0x1084),(1,2),(2,out),(3,n)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ return rv,sd.rd32(req+14*4),sd.rd32(req+15*4),sd.read(out,0x20)
+ try:
+ sd.wr32(WORD_A,0x13579BDF)
+ sd.write(SOURCE,source)
+ sd.wr32(WORD_B,0x2468ACE0)
+ short=run(4)
+ assert short[:3]==(0xFFFF300F,0x13579BDF,0x2468ACE0) and short[3]==b'Z'*0x20,short
+ full=run(16)
+ assert full[:3]==(0,0x13579BDF,0x2468ACE0) and full[3]==source+b'Z'*0x10,full
+ print('signed 0x1084: 4-byte output declaration returned 0xffff300f but inline synthetic metadata, no output copy; 16-byte declaration copied synthetic 16 bytes within span; no live disclosure')
+ finally:
+ sd.write(WORD_A,old[0]);sd.write(WORD_B,old[1])
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1094_cross_partition_helper.py b/psp_emu/harness/probe_sysdrv_1094_cross_partition_helper.py
new file mode 100644
index 0000000..134adf1
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1094_cross_partition_helper.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+"""Signed .54 flash parser/helpers permit a tag-34 offset in another row.
+Planted unsigned metadata; no real firmware table, SPI command or hardware mutation.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+from unicorn import UC_HOOK_CODE
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+PARSE,LOOKUP,CHECK,TABLE,SRC=0xE067F8,0xE0AE90,0xE207A4,0xE7904C,0xE5FA48
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ ret,fault,_=sysdrv_env.run_init(sd)
+ assert ret==0 and not fault and sysdrv_env.init_flag(sd)==1
+ assert sd.read(0xE26258,2)==b'\0\x01' # gate-1 SPI route, no region-check override
+ # The real parser's input is a private firmware metadata area. Plant only
+ # two artificial rows, then let signed sub_E067F8 validate and copy them.
+ sd.write(TABLE,bytes(0x160))
+ def plant_ra2():
+ sd.write(SRC+1676,bytes(256))
+ sd.wr32(SRC+1668,0x32415224);sd.wr32(SRC+1672,2)
+ for i,(tag,base,length) in enumerate(((34,0x10000,0x1000),(4,0x20000,0x1000))):
+ at=SRC+1676+16*i
+ sd.wr32(at,tag);sd.wr32(at+4,base);sd.wr32(at+8,length)
+ # sysdrv_env's earlier hook overwrites $RA2 with an EMPTY synthetic table
+ # on every parser entry. Override that fixture only for this isolated call.
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,n,data:plant_ra2(),begin=PARSE,end=PARSE)
+ parse=sd.run_function(PARSE,(),count=200_000,timeout=1_000_000)
+ assert parse==0 and sd.rd32(TABLE+72)==2,(parse,sd.rd32(TABLE+72),sd.rd32(SRC+1672))
+ assert [sd.rd32(TABLE+76+16*i) for i in range(2)]==[34,4]
+ result=sd.alloc(4)
+ def resolve(offset):
+ sd.wr32(result,0xAAAAAAAA)
+ rc=sd.run_function(LOOKUP,(1,0,offset,result),count=100_000,timeout=1_000_000)
+ return rc,sd.rd32(result)
+ rc,first=resolve(0x10)
+ assert rc==0 and first==0x10010
+ assert sd.run_function(CHECK,(first,0x10),count=100_000,timeout=1_000_000)==0
+ rc,other=resolve(0x10010)
+ assert rc==0 and other==0x20010
+ assert sd.run_function(CHECK,(other,0x10),count=100_000,timeout=1_000_000)==0
+ rc,gap=resolve(0x8010)
+ assert rc==0 and gap==0x18010
+ denied=sd.run_function(CHECK,(gap,0x10),count=100_000,timeout=1_000_000)
+ assert denied!=0 and not sd.faults,(hex(denied),sd.faults)
+ print(f'tag34 own {first:#x} accepted; offset to tag4 {other:#x} accepted; gap {gap:#x} rejected {denied:#x}')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1097_wait_flag.py b/psp_emu/harness/probe_sysdrv_1097_wait_flag.py
new file mode 100644
index 0000000..8048c39
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1097_wait_flag.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+"""Signed .54 free 0x1097 writes shared wait flag; no mailbox/flash call.
+"""
+from pathlib import Path
+import sys
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+FLAG=0xE26648
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg=sd.alloc(0x100),sd.alloc(0x400)
+ sd.write(cfg,bytes(0x400));old=sd.rd32(FLAG)
+ def read_flag():
+ sd.write(req,bytes(0x100))
+ for i,x in ((0,0x1096),(1,2),(14,0xA5A5A5A5),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,x)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert rv==0 and not sd.faults
+ return sd.rd32(req+56)
+ assert read_flag()==old
+ def set_flag(v):
+ sd.write(req,bytes(0x100))
+ for i,x in ((0,0x1097),(1,2),(14,v),(sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,x)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=1_000_000,timeout=2_000_000)
+ assert rv==0 and sd.rd32(FLAG)==v and not sd.faults,(rv,sd.rd32(FLAG))
+ assert read_flag()==v
+ try:
+ set_flag(1);set_flag(0xFFFFFFFF)
+ finally:
+ set_flag(old) # signed opcode, emulator only
+ print(f'signed 0x1096 getter verified free 0x1097 setter/restoration; default_zero={old==0}, emulator only')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_1098_short_input.py b/psp_emu/harness/probe_sysdrv_1098_short_input.py
new file mode 100644
index 0000000..6840b04
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_1098_short_input.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+"""Signed SYS_DRV .54 0x1098: fixed 1024-byte first read vs declared input.
+
+The B5 type-5 descriptor and page protection are synthetic; SVC 0x79 and
+other unmodeled SVCs are emulator defaults. This does NOT show card mappings,
+a hardware fault, a signed-image acceptance, or out-of-process disclosure.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE, UC_PROT_NONE
+from unicorn.arm_const import UC_ARM_REG_R1, UC_ARM_REG_R7
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.component import Fault # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY, LEAF, AFTER_COPY = 0xE087E4, 0xE1217C, 0xE06570
+BASE, END = 0x400000, 0x800000
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ sd.uc.mem_map(BASE, 0x2000)
+ if not any(lo <= END-4 <= hi for lo, hi, _ in sd.uc.mem_regions()):
+ sd.uc.mem_map(END-0x1000, 0x1000)
+ # The emulator's synthetic flash mapping overlaps END: force a modeled
+ # protection edge, NOT a real CPU PTE or neighboring process.
+ if any(lo <= END <= hi for lo, hi, _ in sd.uc.mem_regions()):
+ sd.uc.mem_protect(END, 0x1000, UC_PROT_NONE)
+
+ def svc_window(comp, n):
+ out = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, val in enumerate((BASE, END, 0, 0)):
+ comp.wr32(out + 4*i, val)
+ return 0
+ sd.svc.register(0xB5, svc_window)
+
+ visited, copied = [], []
+ sd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, size, data: visited.append(ea),
+ begin=LEAF, end=LEAF+1)
+ def observe_copy(uc, ea, size, data):
+ copied.append(bytes(uc.mem_read(uc.reg_read(UC_ARM_REG_R7), 1024)))
+ sd.uc.hook_add(UC_HOOK_CODE, observe_copy, begin=AFTER_COPY, end=AFTER_COPY+1)
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, b'\x00' * 0x400)
+ src, edge = BASE+0x1000, END-4
+ marker = bytes((0x40 + (i % 47)) for i in range(1024))
+ sd.write(src, marker)
+ sd.write(edge, marker[:4])
+
+ def request(ptr: int, declared: int, chosen_output: int = 0) -> int:
+ sd.write(req, b'\x00' * 0x100)
+ # 0x1098's signed pointer-role row declares only slot 0 (type 2).
+ # msg[8] is NOT a validated output slot. The chosen-output control
+ # below must reject; normal requests keep it zero and use invalid
+ # image marker bytes, so no successful-result write is attempted.
+ for i, word in ((0, 0x1098), (2, ptr), (3, declared),
+ (8, chosen_output), (9, 4 if chosen_output else 0),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + 4*i, word)
+ return sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+
+ status = request(src, 4)
+ assert len(visited) == len(copied) == 1 and copied[0] == marker, (
+ hex(status), len(visited), len(copied),
+ copied[0][:16].hex() if copied else '<not copied>', sd.faults[-2:])
+ print('normal mapped input: declared 4, signed 0x1098 copied 1024 bytes;',
+ 'subsequent parser status', hex(status), flush=True)
+
+ before = len(visited), len(copied)
+ chosen_denied = request(src, 4, BASE+0x1800)
+ assert chosen_denied == 0xFFFF000F and (len(visited),len(copied)) == before
+ print('nonzero msg[8] chosen-output control rejected before leaf:',
+ hex(chosen_denied),flush=True)
+
+ denied = request(edge, 1024)
+ assert denied != 0 and (len(visited),len(copied)) == before
+ print('synthetic edge declared 1024 rejected before leaf:',hex(denied),flush=True)
+ try:
+ request(edge, 4)
+ except Fault as exc:
+ assert (len(visited),len(copied)) == (before[0]+1,before[1])
+ assert exc.addr == END, (exc, sd.faults)
+ print('synthetic edge declared 4: signed first read faulted at',
+ hex(exc.addr),'before parser or crypto',flush=True)
+ else:
+ raise AssertionError('short-declared protected edge unexpectedly completed')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_109d_label_span.py b/psp_emu/harness/probe_sysdrv_109d_label_span.py
new file mode 100644
index 0000000..b8cf043
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_109d_label_span.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+"""Signed .54 0x109D reaches label strlen past declared four-byte span.
+Synthetic GCP-owned buffers; do not print derived crypto bytes.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+from pspemu.component import Fault
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,key,label,salt,out=[sd.alloc(n) for n in (0x100,0x400,0x40,0x40,0x40,0x40)]
+ sd.write(cfg,bytes(0x400));sd.write(key,b'K'*32+bytes(32))
+ sd.write(label,b'NAME_XYZ\0'+bytes(0x37));sd.write(salt,b'SALT'+bytes(0x3C))
+ entry=[];strlen=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:entry.append(uc.reg_read(UC_ARM_REG_R0)),begin=0xE01864,end=0xE01865)
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:strlen.append(uc.reg_read(UC_ARM_REG_R0)),begin=0xE07AD0,end=0xE07AD1)
+ def run(declared,ptr=label):
+ sd.write(req,bytes(0x100));sd.write(out,bytes(0x40))
+ for i,v in ((0,0x109D),(1,2),(2,key),(3,32),(4,ptr),(5,declared),
+ (6,salt),(7,4),(8,out),(9,32),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ try:
+ return sd.run_function(0xE087E4,(req,2,0,0),count=4_000_000,timeout=4_000_000)
+ except Fault as exc:
+ if ptr==label:
+ assert (exc.pc,exc.addr)==(0xE01940,0x680),(exc.pc,exc.addr)
+ return None
+ return exc
+ short=run(4)
+ assert label in entry and 8 in strlen,(short,entry,strlen)
+ assert short is None
+ full=run(9)
+ assert full is None and entry.count(label)==2 and strlen.count(8)==2,(full,entry,strlen)
+ # Artificial boundary oracle: only four bytes remain in this synthetic
+ # caller window; this is NOT a measured sOS PTE or another TA allocation.
+ edge=sd.cmap.scratch+sd.cmap.scratch_size-4
+ sd.write(edge,b'ZZZZ')
+ def svc_window(comp,n):
+ p=comp.uc.reg_read(UC_ARM_REG_R1)
+ for i,v in enumerate((sd.cmap.scratch,sd.cmap.scratch+sd.cmap.scratch_size,0,0)):
+ comp.wr32(p+4*i,v)
+ return 0
+ sd.svc.register(0xB5,svc_window)
+ before=len(entry)
+ fault=run(4,edge)
+ assert isinstance(fault,Fault) and fault.addr==edge+4 and 0xE01864<=fault.pc<0xE01900,(fault,entry)
+ assert edge in entry[before:]
+ before=len(entry)
+ denied=run(9,edge)
+ assert denied!=0 and not isinstance(denied,Fault) and len(entry)==before,(denied,entry)
+ print(f'signed 0x109D: declared4 label strlen=8, synthetic edge fault={fault.addr:#x}, declared9 edge denied={denied:#x}; no live PTE claim, derived bytes redacted')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_10a2_label_reach.py b/psp_emu/harness/probe_sysdrv_10a2_label_reach.py
new file mode 100644
index 0000000..fc62927
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_10a2_label_reach.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+"""Check if signed 0x10A2 reaches input-label strlen after natural crypto gate.
+No crypto result bytes are printed. Early return is a useful negative control.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_SP,UC_ARM_REG_LR
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+from pspemu.component import Fault
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+STRLEN=0xE01864
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,cfg,label,salt,out=[sd.alloc(n) for n in (0x100,0x400,0x40,0x40,0x40)]
+ sd.write(cfg,bytes(0x400));sd.write(label,b'NAME_XYZ\0'+bytes(0x37))
+ sd.write(salt,b'ABCD'+bytes(0x3C));sd.write(out,bytes(0x40))
+ seen=[];suspicious_copies=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,size,data:seen.append(uc.reg_read(UC_ARM_REG_R0)),
+ begin=STRLEN,end=STRLEN+1)
+ def copy_entry(uc,ea,size,data):
+ dest=uc.reg_read(UC_ARM_REG_R0)
+ if dest<0x10000:
+ suspicious_copies.append((dest,uc.reg_read(UC_ARM_REG_R1),
+ uc.reg_read(UC_ARM_REG_R2),uc.reg_read(UC_ARM_REG_LR)))
+ sd.uc.hook_add(UC_HOOK_CODE,copy_entry,begin=0xE0192C,end=0xE0192D)
+ sd.write(req,bytes(0x100))
+ for i,v in ((0,0x10A2),(1,2),(2,label),(3,4),(4,salt),(5,4),(6,out),(7,32),
+ (sysdrv_env.REQ_CONFIG_DWORD,cfg)):
+ sd.wr32(req+4*i,v)
+ try:
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=4_000_000,timeout=4_000_000)
+ print(f'signed 0x10A2 natural emulator: status={rv:#x}, label_strlen_seen={label in seen}, strlen_calls={len(seen)}')
+ except Fault as exc:
+ assert exc.pc==0xE01940 and exc.addr==0x680 and label not in seen,(exc.pc,exc.addr,len(seen))
+ assert suspicious_copies==[(0x680,0xE23050,32,0xE146E5)],suspicious_copies
+ print('signed 0x10A2 emulator stopped before label strlen: '
+ f'copy_caller={suspicious_copies[0][3]:#x}, FAR={exc.addr:#x}, '
+ 'zero synthetic crypto base; no key material printed')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_10a3_error_flag_clear.py b/psp_emu/harness/probe_sysdrv_10a3_error_flag_clear.py
new file mode 100644
index 0000000..a366eff
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_10a3_error_flag_clear.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+"""Signed .54 0x10A3: error from modeled mailbox map clears global flag.
+Synthetic pre-existing flag and SVC 0x7D failure; no live mailbox effect.
+"""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY,LEAF,FLAG=0xE087E4,0xE12670,0xE25844
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ init,fault,_=sysdrv_env.run_init(sd)
+ assert init==0 and not fault and sysdrv_env.init_flag(sd)==1
+ req,cfg=sd.alloc(0x100),sd.alloc(0x400)
+ sd.write(req,bytes(0x100));sd.write(cfg,bytes(0x400))
+ sd.wr32(req,0x10A3);sd.wr32(req+4,2)
+ sd.wr32(req+4*sysdrv_env.REQ_CONFIG_DWORD,cfg)
+ sd.write(FLAG,b'\x01')
+ sd.write(req+56,b'\xA5\xA5')
+ seen=[];maps=[]
+ sd.uc.hook_add(UC_HOOK_CODE,lambda uc,ea,n,data:seen.append(ea),begin=LEAF,end=LEAF)
+ def unavailable(comp,num):
+ maps.append(num)
+ return 0
+ sd.svc.register(0x7D,unavailable)
+ status=sd.run_function(ENTRY,(req,2,0,0),count=2_000_000,timeout=3_000_000)
+ assert status==0xFFFF000C and seen==[LEAF] and maps==[0x7D]
+ assert sd.read(FLAG,1)==b'\x00' and sd.read(req+56,2)==b'\x00\x00' and not sd.faults
+ print('signed 0x10A3 returned 0xffff000c on modeled SVC 0x7d failure; global status flag 1->0')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_33_zero_handle.py b/psp_emu/harness/probe_sysdrv_33_zero_handle.py
new file mode 100644
index 0000000..36017fa
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_33_zero_handle.py
@@ -0,0 +1,50 @@
+"""Bounded .54 SYS_DRV opcode-0x33 handler trace for object handle zero.
+
+This is a *separate* signed-image emulator observation from the HDCP caller
+failure-injection test. Stop before the fatal function executes; do not turn
+a simulated branch into a claimed hardware effect.
+"""
+from __future__ import annotations
+
+import os
+import sys
+
+from unicorn import UC_HOOK_CODE
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+from pspemu import Component # noqa: E402
+
+SIGNED_SYSDRV = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), "..", "..", "psp_out", "elf",
+ "sys_drv_0.49.0.54_18bd9d26.elf"))
+HANDLER_33 = 0xE1EA54
+FATAL = 0xE0F890
+
+
+def main():
+ comp = Component("sys_drv", image_path=SIGNED_SYSDRV)
+ req = comp.alloc(0x80)
+ attrs = comp.alloc(0x20)
+ comp.wr32(req + 0x38, 0)
+ seen = []
+
+ def stop_at_fatal(uc, addr, size, user_data):
+ seen.append(addr)
+ uc.emu_stop()
+
+ # The callback marks the *first instruction* of sub_E0F890. Stopping
+ # there avoids executing its uninitialized standalone-emulator globals
+ # and prevents a deliberate infinite loop from obscuring the branch.
+ comp.uc.hook_add(UC_HOOK_CODE, stop_at_fatal, begin=FATAL, end=FATAL)
+ comp.run_function(HANDLER_33, (req, attrs, 1, 1), thumb=True,
+ count=20000, timeout=2_000_000)
+ print("signed_sysdrv_image:", os.path.basename(comp.image_path))
+ print("handler:", hex(HANDLER_33), "object_handle:", hex(comp.rd32(req + 0x38)))
+ print("fatal_entry_reached:", [hex(a) for a in seen])
+ print("faults:", comp.faults)
+ assert seen == [FATAL], seen
+ assert not comp.faults
+
+
+if __name__ == "__main__":
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_35_ignored_rng_failure.py b/psp_emu/harness/probe_sysdrv_35_ignored_rng_failure.py
new file mode 100644
index 0000000..b7b5bb5
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_35_ignored_rng_failure.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+"""Signed .54 low 0x35 discards its RNG helper failure (synthetic RNG only)."""
+from pathlib import Path
+import sys
+from unicorn import UC_HOOK_CODE
+from unicorn.arm_const import UC_ARM_REG_R0,UC_ARM_REG_LR,UC_ARM_REG_PC
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env
+SIGNED=Path(__file__).resolve().parents[2]/'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+
+def main():
+ sd=sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes()==SIGNED.read_bytes()
+ r,f,_=sysdrv_env.run_init(sd)
+ assert r==0 and not f and sysdrv_env.init_flag(sd)==1
+ req,out=sd.alloc(0x100),sd.alloc(0x40)
+ print('signed default RNG gate E269B8=',sd.read(0xE269B8,1).hex())
+ mode={'fail_after':0,'calls':0}
+ def modeled_entropy(uc,ea,size,data):
+ mode['calls']+=1
+ if mode['calls']>mode['fail_after']:
+ uc.reg_write(UC_ARM_REG_R0,49)
+ else:
+ sd.wr32(uc.reg_read(UC_ARM_REG_R0),0x11223344)
+ uc.reg_write(UC_ARM_REG_R0,0)
+ uc.reg_write(UC_ARM_REG_PC,uc.reg_read(UC_ARM_REG_LR))
+ sd.uc.hook_add(UC_HOOK_CODE,modeled_entropy,begin=0xE0BA34,end=0xE0BA35)
+ def run(fail_after):
+ mode.update(fail_after=fail_after,calls=0)
+ sd.write(req,bytes(0x100));sd.write(out,b'\xa5'*0x40)
+ for i,v in ((0,0x35),(1,2),(2,out),(3,4)):
+ sd.wr32(req+4*i,v)
+ rv=sd.run_function(0xE087E4,(req,2,0,0),count=2_000_000,timeout=2_000_000)
+ return rv,sd.read(out,8),mode['calls']
+ mode.update(fail_after=0,calls=0)
+ sd.write(out,b'\xa5'*0x40)
+ helper_status=sd.run_function(0xE04F38,(out,4,0,0),count=2_000_000,timeout=2_000_000)
+ assert helper_status==0xFFFF000B and sd.read(out,8)==b'\xa5'*8,(helper_status,mode)
+ first=run(0);second=run(1);success=run(2)
+ assert first==(0,b'\xa5'*8,1),first
+ assert second==(0,b'\x11\x22\x33\x44'+b'\xa5'*4,2),second
+ assert success==(0,b'\x11\x22\x33\x44'+b'\xa5'*4,2),success
+ print('signed helper returned 0xffff000b on modeled RNG failure, but low 0x35 dispatch returned 0 with marker unchanged; failure on extra call2 likewise masked after full four bytes; no real entropy sampled')
+if __name__=='__main__':main()
diff --git a/psp_emu/harness/probe_sysdrv_53_log_prevalidate.py b/psp_emu/harness/probe_sysdrv_53_log_prevalidate.py
new file mode 100644
index 0000000..4f0f03c
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_53_log_prevalidate.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+"""Scope signed .54 low opcode 0x53 logging before caller-string validation.
+
+Synthetic SVC/PCB/caller window only. No card request or cross-process claim.
+Signed SYS_DRV init/dispatch/logging string copy run; MMIO/log-drain effects
+are not hardware evidence.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from unicorn import UC_HOOK_CODE, UC_PROT_NONE
+from unicorn.arm_const import UC_ARM_REG_R1
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+from pspemu import sysdrv_env # noqa: E402
+from pspemu.component import Fault # noqa: E402
+
+SIGNED = Path(__file__).resolve().parents[2] / 'psp_out/elf/sys_drv_0.49.0.54_18bd9d26.elf'
+ENTRY = 0xE087E4
+BASE, END = 0x400000, 0x800000
+LOG_FLAG, LOG_LEVEL, BACKEND_FLAG = 0xE264E0, 0xE264E4, 0xE269CC
+STRING_HELPER, FORMATTER, DRAIN = 0xE2232C, 0xE01A34, 0xE21D44
+LOG_BUFFER = 0xEB5FB0
+
+
+def main() -> None:
+ sd = sysdrv_env.make()
+ assert Path(sd.image_path).read_bytes() == SIGNED.read_bytes()
+ rv, fault, _ = sysdrv_env.run_init(sd)
+ assert rv == 0 and fault is None and sysdrv_env.init_flag(sd) == 1, (rv, fault)
+ flags = (sd.read(LOG_FLAG, 1)[0], sd.rd32(LOG_LEVEL), sd.read(BACKEND_FLAG, 1)[0])
+ print('signed init logging (frontend, level, backend)=', flags, flush=True)
+ assert flags[0] == 1 and flags[2] == 1, 'both modeled init log gates required'
+ sd.uc.mem_map(BASE, 0x3000)
+ regs = sd.uc.mem_regions()
+ if not any(lo <= END-4 <= hi for lo, hi, _ in regs):
+ sd.uc.mem_map(END-0x1000, 0x1000)
+ if any(lo <= END <= hi for lo, hi, _ in sd.uc.mem_regions()):
+ sd.uc.mem_protect(END, 0x1000, UC_PROT_NONE)
+
+ def svc_window(comp, n):
+ out = comp.uc.reg_read(UC_ARM_REG_R1)
+ for i, v in enumerate((BASE, END, 0, 0)):
+ comp.wr32(out + 4*i, v)
+ return 0
+ sd.svc.register(0xB5, svc_window)
+ req, cfg = sd.alloc(0x100), sd.alloc(0x400)
+ sd.write(cfg, b'\x00' * 0x400)
+ src, edge = BASE+0x1000, END-4
+ sd.write(src, b'J' * 40)
+ sd.write(edge, b'K' * 4)
+ seen, fmt, before_drain = [], [], []
+ sd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, size, data: seen.append(ea),
+ begin=STRING_HELPER, end=STRING_HELPER+1)
+ sd.uc.hook_add(UC_HOOK_CODE, lambda uc, ea, size, data: fmt.append(ea),
+ begin=FORMATTER, end=FORMATTER+1)
+ sd.uc.hook_add(UC_HOOK_CODE,
+ lambda uc, ea, size, data: before_drain.append(bytes(
+ uc.mem_read(LOG_BUFFER+16, 39))),
+ begin=DRAIN, end=DRAIN+1)
+
+ def request(ptr: int, declared: int) -> int:
+ sd.write(req, b'\x00' * 0x100)
+ for i, word in ((0, 0x53), (2, ptr), (3, declared), (14, 16),
+ (sysdrv_env.REQ_CONFIG_DWORD, cfg)):
+ sd.wr32(req + i*4, word)
+ return sd.run_function(ENTRY, (req, 2, 0, 0), count=3_000_000,
+ timeout=4_000_000)
+
+ r = request(src, 4)
+ assert r == 0 and len(seen) == len(fmt) == 1
+ assert before_drain == [b'J' * 39], before_drain
+ print('mapped non-NUL input: declared4, signed logger copied 39 bytes '
+ 'before later NUL check; return', hex(r), flush=True)
+
+ n, m, d = len(seen), len(fmt), len(before_drain)
+ denied = request(edge, 40)
+ assert denied == 0xFFFF000F and (len(seen),len(fmt),len(before_drain)) == (n,m,d)
+ print('synthetic edge declared40 rejected before logger',hex(denied),flush=True)
+ try:
+ request(edge, 4)
+ except Fault as exc:
+ assert exc.addr == END, (exc, sd.faults)
+ assert (len(seen),len(fmt),len(before_drain)) == (n+1,m+1,d)
+ print('synthetic edge declared4: signed logger fault at',hex(exc.addr),
+ 'before later NUL check and log drain',flush=True)
+ else:
+ raise AssertionError('short-declared synthetic edge unexpectedly completed')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/psp_emu/harness/probe_sysdrv_underflow_copy.py b/psp_emu/harness/probe_sysdrv_underflow_copy.py
new file mode 100644
index 0000000..3d9dc57
--- /dev/null
+++ b/psp_emu/harness/probe_sysdrv_underflow_copy.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+"""Execute signed SYS_DRV's memcpy AND count consumer under a 2-page workspace model.
+
+Bounded and invalid-source controls distinguish a fault before any second-copy
+store from a mapped-source fault at the workspace boundary. A final probe runs
+sub_E04B68 through both *real* copies with count 0x500. Only the SVC lock is
+stubbed; the caller/ownership gates and sOS MMU protections are NOT emulated.
+
+Run: python3 psp_emu/harness/probe_sysdrv_underflow_copy.py
+"""
+from __future__ import annotations
+
+from pathlib import Path
+from unicorn import Uc, UcError, UC_ARCH_ARM, UC_MODE_ARM, UC_MODE_THUMB, UC_PROT_ALL
+from unicorn import UC_HOOK_INTR, UC_HOOK_MEM_INVALID
+from unicorn.arm_const import (UC_ARM_REG_LR, UC_ARM_REG_PC, UC_ARM_REG_R0,
+ UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3,
+ UC_ARM_REG_SP)
+
+REPO = Path(__file__).resolve().parents[2]
+IMAGE = (REPO / 'psp_out/sys_drv_code.bin').read_bytes()
+IMAGE_BASE = 0xE00100
+HELPER = 0xE018A0 # ARM entry via BLX from Thumb sub_E04B68
+WORK = 0xECC000 # hardware base; emulated SVC78 size 0x2000
+DST = WORK + 0x6C0 + 0x500 # second-copy destination for count 0x500
+SRC = 0x23000000
+STACK = 0x22000000
+RET = 0xF0000000
+
+
+def probe(src_pages: int, length: int, source: int, marker: int) -> tuple[str, int, bytes]:
+ uc = Uc(UC_ARCH_ARM, UC_MODE_ARM)
+ img_start = IMAGE_BASE & ~0xFFF
+ img_size = (IMAGE_BASE + len(IMAGE) - img_start + 0xFFF) & ~0xFFF
+ uc.mem_map(img_start, img_size, UC_PROT_ALL)
+ uc.mem_write(IMAGE_BASE, IMAGE)
+ uc.mem_map(WORK, 0x2000, UC_PROT_ALL)
+ uc.mem_write(WORK, b'\xA5' * 0x2000)
+ uc.mem_map(STACK, 0x1000, UC_PROT_ALL)
+ if src_pages:
+ uc.mem_map(SRC, src_pages * 0x1000, UC_PROT_ALL)
+ uc.mem_write(SRC, bytes([marker]) * (src_pages * 0x1000))
+ faults = []
+
+ def invalid(_uc, access, addr, size, _value, _data):
+ faults.append((access, addr, size))
+ return False
+
+ uc.hook_add(UC_HOOK_MEM_INVALID, invalid)
+ uc.reg_write(UC_ARM_REG_SP, STACK + 0x800)
+ uc.reg_write(UC_ARM_REG_LR, RET)
+ uc.reg_write(UC_ARM_REG_R0, DST)
+ uc.reg_write(UC_ARM_REG_R1, source)
+ uc.reg_write(UC_ARM_REG_R2, length)
+ try:
+ uc.emu_start(HELPER, RET, timeout=5_000_000, count=50000)
+ outcome = 'returned'
+ except UcError as e:
+ outcome = '%s at %#x, pc=%#x' % (type(e).__name__, faults[0][1] if faults else 0,
+ uc.reg_read(UC_ARM_REG_PC))
+ return outcome, (faults[0][1] if faults else -1), bytes(uc.mem_read(DST, 0x1440))
+
+
+def probe_full_function() -> None:
+ """Run sub_E04B68 with real memcpy, rather than stubbing its copy calls."""
+ uc = Uc(UC_ARCH_ARM, UC_MODE_THUMB)
+ uc.mem_map(0xE00000, 0x30000, UC_PROT_ALL)
+ uc.mem_write(IMAGE_BASE, IMAGE)
+ staging, context = 0x20000000, 0x21000000
+ for base, size in ((WORK, 0x2000), (staging, 0x1000),
+ (context, 0x1000), (STACK, 0x1000)):
+ uc.mem_map(base, size, UC_PROT_ALL)
+ uc.mem_write(staging, b'\x41' * 0x1000)
+ uc.mem_write(staging + 0x88, (0x500).to_bytes(4, 'little'))
+ uc.mem_write(WORK, b'\xA5' * 0x2000)
+ uc.mem_write(0xE257D8, WORK.to_bytes(4, 'little'))
+ uc.reg_write(UC_ARM_REG_SP, STACK + 0x800)
+ uc.reg_write(UC_ARM_REG_LR, RET | 1)
+ uc.reg_write(UC_ARM_REG_R0, context)
+ uc.reg_write(UC_ARM_REG_R1, staging)
+ uc.reg_write(UC_ARM_REG_R2, 0) # NULL nominal source
+ uc.reg_write(UC_ARM_REG_R3, 0) # zero nominal length
+ faults, svcs = [], []
+
+ def invalid(uc_, access, addr, size, value, user):
+ faults.append((addr, uc_.reg_read(UC_ARM_REG_PC)))
+ return False
+
+ def on_svc(uc_, intno, user):
+ # Only SVC 0x5E (the SYS_DRV workspace lock) is reached before fault.
+ svcs.append(uc_.reg_read(UC_ARM_REG_PC))
+ uc_.reg_write(UC_ARM_REG_R0, 0)
+ uc_.reg_write(UC_ARM_REG_PC, uc_.reg_read(UC_ARM_REG_PC) | 1)
+
+ uc.hook_add(UC_HOOK_MEM_INVALID, invalid)
+ uc.hook_add(UC_HOOK_INTR, on_svc)
+ try:
+ uc.emu_start(0xE04B69, RET, timeout=5_000_000, count=50000)
+ except UcError:
+ pass
+ copied = bytes(uc.mem_read(WORK + 0x6C0, 0x500))
+ print('real sub_E04B68(count=0x500, source=NULL): SVCs=%s fault=%s'
+ % ([hex(pc) for pc in svcs], [(hex(addr), hex(pc)) for addr, pc in faults]))
+ assert svcs == [0xE04B8E] and faults == [(0, 0xE01938)]
+ assert copied[:0x80] == b'\x41' * 0x80
+ assert copied[0x80:0x84] == (0x500).to_bytes(4, 'little')
+ assert copied[0x84:] == b'\x41' * (0x500 - 0x84)
+ assert bytes(uc.mem_read(WORK + 0xBC0, 0x2000 - 0xBC0)) == b'\xA5' * (0x2000 - 0xBC0)
+ print(' first copy filled +0x6C0..+0xBC0; second copy read NULL before any store')
+
+
+def main() -> int:
+ # Control: the real helper copies normally for the safe size.
+ short, sfault, dst = probe(1, 0x40, SRC, 0x41)
+ print('bounded copy length=0x40:', short, 'first=%#x' % int.from_bytes(dst[:4], 'little'))
+ assert short == 'returned' and sfault == -1 and dst[:0x40] == b'\x41' * 0x40
+ assert dst[0x40:] == b'\xA5' * (0x1440 - 0x40)
+
+ # One actual underflowed call with NULL source: the helper reads source 0
+ # before storing anything (the first copy in sub_E04B68 ran before this).
+ null, nfault, dst = probe(0, 0xFFFFFF00, 0, 0x41)
+ print('underflow, NULL source:', null, 'workspace unchanged=', dst == b'\xA5' * len(dst))
+ assert nfault == 0 and dst == b'\xA5' * len(dst)
+
+ # With a separately mapped source, writes reach the two-page workspace
+ # boundary in this model. This does not establish live hardware PTEs.
+ mapped, mfault, dst = probe(3, 0xFFFFFF00, SRC, 0x43)
+ print('underflow, mapped source:', mapped, 'filled-to-guard=', dst == b'\x43' * len(dst))
+ assert mfault == WORK + 0x2000 and dst == b'\x43' * len(dst)
+ probe_full_function()
+ print('PASS: source mapping determines first fault; synthetic guard contains the write')
+ return 0
+
+
+if __name__ == '__main__':
+ raise SystemExit(main())