diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..4eb93594 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/lwmem"] + path = deps/lwmem + url = https://github.com/MaJerle/lwmem.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 836114a4..c3bf664b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,24 @@ if(ANDROID) list(APPEND CODEGEN_LIBS cpufeatures) endif() +# Détection iOS +if(APPLE) + if(IOS OR CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(TARGET_PLATFORM_IOS TRUE) + endif() +endif() + +# Configuration iOS JIT +if(TARGET_PLATFORM_IOS) + message(STATUS "Configuring Play--CodeGen for iOS with JIT support") + + # Ajouter lwmem + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/deps/lwmem/CMakeLists.txt") + add_subdirectory(deps/lwmem/lwmem) + list(APPEND CODEGEN_LIBS lwmem) + endif() +endif() + add_library(CodeGen src/AArch32Assembler.cpp src/AArch64Assembler.cpp @@ -80,6 +98,11 @@ add_library(CodeGen src/MemoryFunction.cpp src/ObjectFile.cpp src/WasmModuleBuilder.cpp + $<$:src/MemoryUtil_iOS.cpp> + $<$:src/MemoryUtil_iOS_Legacy.cpp> + $<$:src/MemoryUtil_iOS_LuckNoTXM.cpp> + $<$:src/MemoryUtil_iOS_LuckTXM.cpp> + $<$:src/JITMemoryTracker.cpp> src/X86Assembler.cpp src/X86Assembler_Avx.cpp src/X86Assembler_Fpu.cpp @@ -109,6 +132,8 @@ add_library(CodeGen include/MachoDefs.h include/MachoObjectFile.h include/MemoryFunction.h + $<$:include/MemoryUtil.h> + $<$:include/JITMemoryTracker.h> include/ObjectFile.h include/WasmDefs.h include/WasmModuleBuilder.h @@ -118,6 +143,23 @@ add_library(CodeGen target_link_libraries(CodeGen PUBLIC Framework ${CODEGEN_LIBS}) target_include_directories(CodeGen PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Configuration supplémentaire iOS +if(TARGET_PLATFORM_IOS) + # Include directories pour lwmem + if(TARGET lwmem) + target_include_directories(CodeGen PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/deps/lwmem/lwmem/src/include + ) + endif() + + # Définitions du compilateur + target_compile_definitions(CodeGen PRIVATE + IPHONEOS=1 + TARGET_OS_IOS=1 + ) +endif() + enable_testing() set(CodeGenTest_SRC diff --git a/build_ios/Play JIT implementation/JIT_IMPLEMENTATION_CODEGEN.md b/build_ios/Play JIT implementation/JIT_IMPLEMENTATION_CODEGEN.md new file mode 100644 index 00000000..d40560f2 --- /dev/null +++ b/build_ios/Play JIT implementation/JIT_IMPLEMENTATION_CODEGEN.md @@ -0,0 +1,870 @@ +# Guide d'implémentation JIT pour Play!-CodeGen + +**Version:** 1.0 +**Date:** 2026-01-17 +**Cible:** Repository Play--CodeGen (module de génération de code) + +--- + +## 📋 Table des matières + +1. [Vue d'ensemble](#vue-densemble) +2. [Architecture des 3 modes JIT](#architecture-des-3-modes-jit) +3. [Dépendances](#dépendances) +4. [Structure des fichiers](#structure-des-fichiers) +5. [Implémentation C++](#implémentation-c) +6. [Configuration CMake](#configuration-cmake) +7. [API d'utilisation](#api-dutilisation) +8. [Tests](#tests) + +--- + +## Vue d'ensemble + +Ce document décrit l'implémentation **bas niveau** du système JIT multi-mode pour le module **Play--CodeGen**. Ce module fournit les primitives d'allocation de mémoire exécutable pour les trois modes : + +- **Legacy** (iOS < 26) : Toggle W^X avec mprotect() +- **LuckNoTXM** (iOS 26+ sans TXM) : Miroirs RW/RX par allocation +- **LuckTXM** (iOS 26+ avec TXM) : Région de 512 MB pré-allouée + +### Responsabilités de CodeGen + +✅ Allocation de mémoire exécutable +✅ Gestion des miroirs RW/RX +✅ Toggle permissions Write/Execute (Legacy) +✅ Dispatcher entre les 3 modes + +❌ Acquisition du JIT (géré par Play!) +❌ Détection TXM (géré par Play!) +❌ Interface utilisateur + +--- + +## Architecture des 3 modes JIT + +``` +┌──────────────────────────────────────────────────┐ +│ CodeGen::SetJitType(type) │ +│ Appelé par Play! au démarrage │ +└─────────────────┬────────────────────────────────┘ + │ + ▼ + ┌───────────┴───────────┐ + │ g_jit_type global │ + └───────────┬───────────┘ + │ + ┌────────────┴────────────┐ + │ Dispatcher routage │ + └────────────┬────────────┘ + │ + ┌─────────────┼─────────────┐ + │ │ │ + ▼ ▼ ▼ +┌────────┐ ┌──────────┐ ┌──────────┐ +│Legacy │ │LuckNoTXM │ │ LuckTXM │ +│Impl. │ │ Impl. │ │ Impl. │ +└────────┘ └──────────┘ └──────────┘ +``` + +--- + +## Dépendances + +### lwmem - Lightweight Memory Manager + +**Pourquoi :** Gère l'allocation dynamique dans la région de 512 MB (mode LuckTXM uniquement). + +**Installation :** + +```bash +cd Play--CodeGen +git submodule add https://github.com/MaJerle/lwmem.git deps/lwmem +git submodule update --init --recursive +``` + +**Vérification :** + +```bash +ls deps/lwmem/lwmem/src/include/lwmem/lwmem.h +# Doit exister +``` + +### Headers système requis + +```cpp +#include // vm_remap(), vm_deallocate() +#include // mmap(), mprotect(), munmap() +#include // Types système +#include // sysconf(), getpid() +``` + +--- + +## Structure des fichiers + +Créez cette arborescence dans **Play--CodeGen** : + +``` +Play--CodeGen/ +├── deps/ +│ └── lwmem/ # Submodule Git +│ +├── include/ +│ ├── MemoryUtil.h # Interface principale (MODIFIER) +│ └── JITMemoryTracker.h # Tracker pour mode Legacy +│ +└── src/ + ├── MemoryUtil_iOS.cpp # Dispatcher + ├── MemoryUtil_iOS_Legacy.cpp # Implémentation Legacy + ├── MemoryUtil_iOS_LuckNoTXM.cpp # Implémentation LuckNoTXM + ├── MemoryUtil_iOS_LuckTXM.cpp # Implémentation LuckTXM + └── JITMemoryTracker.cpp # Tracking W^X pour Legacy +``` + +**Total :** 7 fichiers (1 modifié, 6 créés) + +--- + +## Implémentation C++ + +### 📄 `include/MemoryUtil.h` + +**Ajoutez** ces déclarations à votre fichier MemoryUtil.h existant : + +```cpp +#pragma once + +#include + +#ifdef __APPLE__ +#include +#endif + +namespace CodeGen +{ + +// ============================================================================ +// API PUBLIQUE +// ============================================================================ + +/// Alloue de la mémoire exécutable +/// @param size Taille en bytes (sera alignée sur page) +/// @return Pointeur vers mémoire exécutable, ou nullptr si échec +void* AllocateExecutableMemory(size_t size); + +/// Libère de la mémoire exécutable +/// @param ptr Pointeur retourné par AllocateExecutableMemory() +/// @param size Taille originale +void FreeExecutableMemory(void* ptr, size_t size); + +#if TARGET_OS_IOS + +// ============================================================================ +// TYPES ET CONFIGURATION (iOS uniquement) +// ============================================================================ + +/// Types de JIT supportés +enum class JitType { + Legacy, ///< iOS < 26 : Toggle W^X avec mprotect() + LuckNoTXM, ///< iOS 26+ sans TXM : Miroirs RW/RX par allocation + LuckTXM ///< iOS 26+ avec TXM : Région 512 MB pré-allouée +}; + +/// Configure le type de JIT à utiliser +/// @note DOIT être appelé avant toute allocation +/// @param type Type de JIT (détecté par Play!) +void SetJitType(JitType type); + +// ============================================================================ +// GESTION WRITE/EXECUTE (Legacy uniquement) +// ============================================================================ + +/// Active écriture, désactive exécution (Legacy) +void JITPageWriteEnableExecuteDisable(void* ptr); + +/// Désactive écriture, active exécution (Legacy) +void JITPageWriteDisableExecuteEnable(void* ptr); + +/// RAII wrapper pour accès en écriture sécurisé +struct ScopedJITPageWriteAndNoExecute { + void* ptr; + + ScopedJITPageWriteAndNoExecute(void* region) : ptr(region) { + JITPageWriteEnableExecuteDisable(ptr); + } + + ~ScopedJITPageWriteAndNoExecute() { + JITPageWriteDisableExecuteEnable(ptr); + } +}; + +// ============================================================================ +// GESTION RÉGIONS RW/RX (LuckNoTXM et LuckTXM) +// ============================================================================ + +/// Pré-alloue la région exécutable (LuckTXM uniquement) +/// @note Doit être appelé une fois au démarrage si mode LuckTXM +void AllocateExecutableMemoryRegion(); + +/// Obtient le décalage vers région writable +/// @param rx_ptr Pointeur exécutable +/// @param size Taille de la région +/// @return Décalage à ajouter pour obtenir pointeur writable +ptrdiff_t AllocateWritableRegionAndGetDiff(void* rx_ptr, size_t size); + +/// Libère la région writable (LuckNoTXM uniquement) +/// @param rx_ptr Pointeur exécutable +/// @param size Taille +/// @param diff Décalage retourné par AllocateWritableRegionAndGetDiff() +void FreeWritableRegion(void* rx_ptr, size_t size, ptrdiff_t diff); + +// ============================================================================ +// DÉCLARATIONS INTERNES (ne pas utiliser directement) +// ============================================================================ + +// LuckTXM +void* AllocateExecutableMemory_LuckTXM(size_t size); +void FreeExecutableMemory_LuckTXM(void* ptr); +void AllocateExecutableMemoryRegion_LuckTXM(); +ptrdiff_t AllocateWritableRegionAndGetDiff_LuckTXM(); + +// LuckNoTXM +void* AllocateExecutableMemory_LuckNoTXM(size_t size); +void FreeExecutableMemory_LuckNoTXM(void* ptr, size_t size); +ptrdiff_t AllocateWritableRegionAndGetDiff_LuckNoTXM(void* rx_ptr, size_t size); +void FreeWritableRegion_LuckNoTXM(void* rx_ptr, size_t size, ptrdiff_t diff); + +// Legacy +void* AllocateExecutableMemory_Legacy(size_t size); +void FreeExecutableMemory_Legacy(void* ptr, size_t size); +void JITPageWriteEnableExecuteDisable_Legacy(void* ptr); +void JITPageWriteDisableExecuteEnable_Legacy(void* ptr); + +#endif // TARGET_OS_IOS + +} // namespace CodeGen +``` + +--- + +### 📄 `src/MemoryUtil_iOS.cpp` + +**Dispatcher principal** qui route tous les appels : + +```cpp +#include "MemoryUtil.h" + +namespace CodeGen +{ + +// Variable globale stockant le type JIT actif +static JitType g_jit_type = JitType::LuckTXM; + +void SetJitType(JitType type) { + g_jit_type = type; +} + +void* AllocateExecutableMemory(size_t size) { + if (g_jit_type == JitType::LuckTXM) + return AllocateExecutableMemory_LuckTXM(size); + else if (g_jit_type == JitType::LuckNoTXM) + return AllocateExecutableMemory_LuckNoTXM(size); + else if (g_jit_type == JitType::Legacy) + return AllocateExecutableMemory_Legacy(size); + + return nullptr; +} + +void FreeExecutableMemory(void* ptr, size_t size) { + if (g_jit_type == JitType::LuckTXM) + FreeExecutableMemory_LuckTXM(ptr); + else if (g_jit_type == JitType::LuckNoTXM) + FreeExecutableMemory_LuckNoTXM(ptr, size); + else if (g_jit_type == JitType::Legacy) + FreeExecutableMemory_Legacy(ptr, size); +} + +void AllocateExecutableMemoryRegion() { + if (g_jit_type == JitType::LuckTXM) { + AllocateExecutableMemoryRegion_LuckTXM(); + } +} + +ptrdiff_t AllocateWritableRegionAndGetDiff(void* rx_ptr, size_t size) { + if (g_jit_type == JitType::LuckTXM) + return AllocateWritableRegionAndGetDiff_LuckTXM(); + else if (g_jit_type == JitType::LuckNoTXM) + return AllocateWritableRegionAndGetDiff_LuckNoTXM(rx_ptr, size); + + return 0; +} + +void FreeWritableRegion(void* rx_ptr, size_t size, ptrdiff_t diff) { + if (g_jit_type == JitType::LuckNoTXM) { + FreeWritableRegion_LuckNoTXM(rx_ptr, size, diff); + } +} + +void JITPageWriteEnableExecuteDisable(void* ptr) { + if (g_jit_type == JitType::Legacy) { + JITPageWriteEnableExecuteDisable_Legacy(ptr); + } +} + +void JITPageWriteDisableExecuteEnable(void* ptr) { + if (g_jit_type == JitType::Legacy) { + JITPageWriteDisableExecuteEnable_Legacy(ptr); + } +} + +} // namespace CodeGen +``` + +--- + +### 📄 `src/MemoryUtil_iOS_LuckTXM.cpp` + +**Mode le plus performant** : 512 MB pré-alloués avec miroir RW permanent. + +```cpp +#include "MemoryUtil.h" + +#include +#include +#include +#include +#include + +// 512 MiB de région exécutable pré-allouée +constexpr size_t EXECUTABLE_REGION_SIZE = 536870912; + +static uint8_t* g_rx_region = nullptr; // Pointeur région RX (exécutable) +static ptrdiff_t g_rw_region_diff = 0; // Décalage RW - RX + +namespace CodeGen +{ + +void AllocateExecutableMemoryRegion_LuckTXM() { + if (g_rx_region) return; // Déjà alloué + + const size_t size = EXECUTABLE_REGION_SIZE; + + // 1. Allouer région RX (read-execute) + uint8_t* rx_ptr = static_cast( + mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0) + ); + + if (!rx_ptr || rx_ptr == MAP_FAILED) { + // TODO: Log error + return; + } + + // 2. Signal TXM avec breakpoint spécial (ARM64) + // Cette instruction informe le Trusted Execution Monitor de la région JIT + asm ("mov x0, %0\n" + "mov x1, %1\n" + "brk #0x69" :: "r" (rx_ptr), "r" (size) : "x0", "x1"); + + // 3. Créer miroir RW de la même mémoire physique + vm_address_t rw_region = 0; + vm_address_t target = reinterpret_cast(rx_ptr); + vm_prot_t cur_protection = 0; + vm_prot_t max_protection = 0; + + kern_return_t retval = vm_remap( + mach_task_self(), // Task cible + &rw_region, // Adresse de sortie (miroir) + size, // Taille + 0, // Mask + true, // Anywhere (laisse kernel choisir l'adresse) + mach_task_self(), // Task source + target, // Adresse source (rx_ptr) + false, // Copy (false = partage mémoire physique) + &cur_protection, + &max_protection, + VM_INHERIT_DEFAULT + ); + + if (retval != KERN_SUCCESS) { + munmap(rx_ptr, size); + return; + } + + uint8_t* rw_ptr = reinterpret_cast(rw_region); + + // 4. Forcer permissions RW sur le miroir + if (mprotect(rw_ptr, size, PROT_READ | PROT_WRITE) != 0) { + munmap(rx_ptr, size); + vm_deallocate(mach_task_self(), rw_region, size); + return; + } + + // 5. Initialiser lwmem pour gérer allocation dynamique + lwmem_region_t regions[] = { + { (void*)rw_ptr, size }, + { NULL, 0 } + }; + + if (lwmem_assignmem(regions) == 0) { + munmap(rx_ptr, size); + vm_deallocate(mach_task_self(), rw_region, size); + return; + } + + g_rx_region = rx_ptr; + g_rw_region_diff = rw_ptr - rx_ptr; +} + +ptrdiff_t AllocateWritableRegionAndGetDiff_LuckTXM() { + return g_rw_region_diff; +} + +void* AllocateExecutableMemory_LuckTXM(size_t size) { + if (g_rx_region == nullptr) return nullptr; + + const size_t pagesize = sysconf(_SC_PAGESIZE); + + // Allouer via lwmem avec espace pour alignement + métadonnées + void* raw = lwmem_malloc(size + pagesize - 1 + sizeof(void*)); + + if (!raw) return nullptr; + + // Aligner sur page boundary + uintptr_t raw_addr = (uintptr_t)raw + sizeof(void*); + uintptr_t aligned = (raw_addr + pagesize - 1) & ~(pagesize - 1); + + // Stocker pointeur raw pour lwmem_free() + ((void**)aligned)[-1] = raw; + + // Retourner pointeur RX (exécutable) au lieu du RW + return (uint8_t*)aligned - g_rw_region_diff; +} + +void FreeExecutableMemory_LuckTXM(void* ptr) { + if (!ptr) return; + + // Convertir ptr RX en RW, récupérer raw, libérer + uint8_t* rw_ptr = static_cast(ptr) + g_rw_region_diff; + void* raw = ((void**)rw_ptr)[-1]; + lwmem_free(raw); +} + +} // namespace CodeGen +``` + +**Points clés :** +- Une seule allocation de 512 MB au démarrage +- Pas de mprotect() pendant l'exécution (ultra-rapide) +- lwmem gère l'allocation dynamique dans cette région +- Deux pointeurs : RX pour exécuter, RW pour écrire + +--- + +### 📄 `src/MemoryUtil_iOS_LuckNoTXM.cpp` + +**Mode flexible** : Crée un miroir RW pour chaque allocation. + +```cpp +#include "MemoryUtil.h" + +#include +#include +#include +#include + +namespace CodeGen +{ + +void* AllocateExecutableMemory_LuckNoTXM(size_t size) { + // Allouer région RX + uint8_t* rx_ptr = static_cast( + mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0) + ); + + if (!rx_ptr || rx_ptr == MAP_FAILED) return nullptr; + return rx_ptr; +} + +void FreeExecutableMemory_LuckNoTXM(void* ptr, size_t size) { + if (ptr) { + munmap(ptr, size); + } +} + +ptrdiff_t AllocateWritableRegionAndGetDiff_LuckNoTXM(void* rx_ptr, size_t size) { + // Créer miroir RW de rx_ptr + vm_address_t rw_region = 0; + vm_address_t target = reinterpret_cast(rx_ptr); + vm_prot_t cur_protection = 0; + vm_prot_t max_protection = 0; + + kern_return_t retval = vm_remap( + mach_task_self(), &rw_region, size, 0, true, + mach_task_self(), target, false, + &cur_protection, &max_protection, VM_INHERIT_DEFAULT + ); + + if (retval != KERN_SUCCESS) return 0; + + uint8_t* rw_ptr = reinterpret_cast(rw_region); + + if (mprotect(rw_ptr, size, PROT_READ | PROT_WRITE) != 0) { + vm_deallocate(mach_task_self(), rw_region, size); + return 0; + } + + return rw_ptr - static_cast(rx_ptr); +} + +void FreeWritableRegion_LuckNoTXM(void* rx_ptr, size_t size, ptrdiff_t diff) { + uint8_t* rw_ptr = static_cast(rx_ptr) + diff; + vm_deallocate(mach_task_self(), reinterpret_cast(rw_ptr), size); +} + +} // namespace CodeGen +``` + +**Points clés :** +- Miroir RW créé pour chaque allocation +- Plus flexible : pas de limite de 512 MB +- Légèrement moins performant que LuckTXM + +--- + +### 📄 `src/MemoryUtil_iOS_Legacy.cpp` + +**Mode compatible** : Toggle W^X avec mprotect(). + +```cpp +#include "MemoryUtil.h" +#include "JITMemoryTracker.h" + +#include +#include + +namespace CodeGen +{ + +static JITMemoryTracker g_jit_memory_tracker; + +void* AllocateExecutableMemory_Legacy(size_t size) { + void* ptr = mmap(nullptr, size, PROT_READ | PROT_EXEC, + MAP_ANON | MAP_PRIVATE, -1, 0); + + if (ptr == MAP_FAILED) ptr = nullptr; + if (ptr == nullptr) return nullptr; + + g_jit_memory_tracker.RegisterJITRegion(ptr, size); + return ptr; +} + +void FreeExecutableMemory_Legacy(void* ptr, size_t size) { + if (ptr) { + munmap(ptr, size); + g_jit_memory_tracker.UnregisterJITRegion(ptr); + } +} + +void JITPageWriteEnableExecuteDisable_Legacy(void* ptr) { + g_jit_memory_tracker.JITRegionWriteEnableExecuteDisable(ptr); +} + +void JITPageWriteDisableExecuteEnable_Legacy(void* ptr) { + g_jit_memory_tracker.JITRegionWriteDisableExecuteEnable(ptr); +} + +} // namespace CodeGen +``` + +--- + +### 📄 `include/JITMemoryTracker.h` + +Gère le tracking des régions et le toggle W^X : + +```cpp +#pragma once + +#include +#include +#include + +namespace CodeGen +{ + +class JITMemoryTracker { +public: + void RegisterJITRegion(void* ptr, size_t size); + void UnregisterJITRegion(void* ptr); + void JITRegionWriteEnableExecuteDisable(void* ptr); + void JITRegionWriteDisableExecuteEnable(void* ptr); + +private: + struct JITRegion { + void* start; + size_t size; + int nesting_counter; // Support appels imbriqués + }; + + std::map m_regions; + std::mutex m_mutex; + + JITRegion* FindRegion(void* ptr); +}; + +} // namespace CodeGen +``` + +--- + +### 📄 `src/JITMemoryTracker.cpp` + +```cpp +#include "JITMemoryTracker.h" +#include + +namespace CodeGen +{ + +void JITMemoryTracker::RegisterJITRegion(void* ptr, size_t size) { + std::lock_guard lock(m_mutex); + m_regions[ptr] = {ptr, size, 0}; +} + +void JITMemoryTracker::UnregisterJITRegion(void* ptr) { + std::lock_guard lock(m_mutex); + m_regions.erase(ptr); +} + +JITMemoryTracker::JITRegion* JITMemoryTracker::FindRegion(void* ptr) { + for (auto& pair : m_regions) { + JITRegion& region = pair.second; + if (ptr >= region.start && + ptr < static_cast(region.start) + region.size) { + return ®ion; + } + } + return nullptr; +} + +void JITMemoryTracker::JITRegionWriteEnableExecuteDisable(void* ptr) { + std::lock_guard lock(m_mutex); + JITRegion* region = FindRegion(ptr); + if (region) { + if (region->nesting_counter == 0) { + mprotect(region->start, region->size, PROT_READ | PROT_WRITE); + } + region->nesting_counter++; + } +} + +void JITMemoryTracker::JITRegionWriteDisableExecuteEnable(void* ptr) { + std::lock_guard lock(m_mutex); + JITRegion* region = FindRegion(ptr); + if (region) { + region->nesting_counter--; + if (region->nesting_counter == 0) { + mprotect(region->start, region->size, PROT_READ | PROT_EXEC); + } + } +} + +} // namespace CodeGen +``` + +--- + +## Configuration CMake + +Modifiez `CMakeLists.txt` de Play--CodeGen : + +```cmake +project(PlayCodeGen) + +# Détection iOS +if(APPLE) + if(IOS OR CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(TARGET_PLATFORM_IOS TRUE) + endif() +endif() + +# Configuration iOS +if(TARGET_PLATFORM_IOS) + message(STATUS "Configuring Play--CodeGen for iOS with JIT support") + + # Ajouter lwmem + add_subdirectory(deps/lwmem) + + # Sources iOS JIT + set(IOS_JIT_SOURCES + src/MemoryUtil_iOS.cpp + src/MemoryUtil_iOS_Legacy.cpp + src/MemoryUtil_iOS_LuckNoTXM.cpp + src/MemoryUtil_iOS_LuckTXM.cpp + src/JITMemoryTracker.cpp + ) + + target_sources(PlayCodeGen PRIVATE ${IOS_JIT_SOURCES}) + + # Link lwmem + target_link_libraries(PlayCodeGen PRIVATE lwmem) + + # Include directories + target_include_directories(PlayCodeGen PRIVATE + ${CMAKE_SOURCE_DIR}/deps/lwmem/lwmem/src/include + ) + + # Defines + target_compile_definitions(PlayCodeGen PRIVATE + IPHONEOS=1 + TARGET_OS_IOS=1 + ) +endif() +``` + +--- + +## API d'utilisation + +### Initialisation (au démarrage de l'app) + +```cpp +#include "MemoryUtil.h" + +void InitializeJIT(CodeGen::JitType type) { + // 1. Configurer le type + CodeGen::SetJitType(type); + + // 2. Pré-allouer région si LuckTXM + if (type == CodeGen::JitType::LuckTXM) { + CodeGen::AllocateExecutableMemoryRegion(); + } +} +``` + +### Utilisation mode LuckTXM / LuckNoTXM + +```cpp +void CompileBlock() { + const size_t codeSize = 4096; + + // 1. Allouer + void* rx_ptr = CodeGen::AllocateExecutableMemory(codeSize); + if (!rx_ptr) return; + + // 2. Obtenir pointeur writable + ptrdiff_t diff = CodeGen::AllocateWritableRegionAndGetDiff(rx_ptr, codeSize); + void* rw_ptr = static_cast(rx_ptr) + diff; + + // 3. Écrire code via rw_ptr + GenerateCode(rw_ptr, codeSize); + + // 4. Exécuter via rx_ptr + typedef void (*Func)(); + Func f = reinterpret_cast(rx_ptr); + f(); + + // 5. Libérer + CodeGen::FreeWritableRegion(rx_ptr, codeSize, diff); + CodeGen::FreeExecutableMemory(rx_ptr, codeSize); +} +``` + +### Utilisation mode Legacy + +```cpp +void CompileBlock_Legacy() { + const size_t codeSize = 4096; + + // 1. Allouer + void* code = CodeGen::AllocateExecutableMemory(codeSize); + if (!code) return; + + // 2. Activer écriture + { + CodeGen::ScopedJITPageWriteAndNoExecute guard(code); + + // Écrire code + GenerateCode(code, codeSize); + + // Destructeur réactive exécution + } + + // 3. Exécuter + typedef void (*Func)(); + Func f = reinterpret_cast(code); + f(); + + // 4. Libérer + CodeGen::FreeExecutableMemory(code, codeSize); +} +``` + +--- + +## Tests + +### Test unitaire basique + +```cpp +#include "MemoryUtil.h" +#include +#include + +void TestJITAllocation() { + const size_t testSize = 4096; + + // Test allocation + void* ptr = CodeGen::AllocateExecutableMemory(testSize); + assert(ptr != nullptr); + + // Test writable region + ptrdiff_t diff = CodeGen::AllocateWritableRegionAndGetDiff(ptr, testSize); + void* rw_ptr = static_cast(ptr) + diff; + + // Test écriture : ARM64 "ret" instruction + uint32_t* code = static_cast(rw_ptr); + code[0] = 0xD65F03C0; // ret + + // Test exécution + typedef void (*EmptyFunc)(); + EmptyFunc func = reinterpret_cast(ptr); + func(); // Doit juste retourner + + // Cleanup + CodeGen::FreeWritableRegion(ptr, testSize, diff); + CodeGen::FreeExecutableMemory(ptr, testSize); + + printf("✅ JIT allocation test passed\n"); +} +``` + +### Intégration dans CMake + +```cmake +if(BUILD_TESTING) + add_executable(jit_test tests/jit_test.cpp) + target_link_libraries(jit_test PRIVATE PlayCodeGen) + add_test(NAME JITAllocation COMMAND jit_test) +endif() +``` + +--- + +## Checklist de validation + +- [ ] lwmem ajouté comme submodule +- [ ] 7 fichiers créés/modifiés +- [ ] CMakeLists.txt configuré +- [ ] Compilation réussie sur iOS +- [ ] Test unitaire qui passe +- [ ] Testé sur simulateur (mode Unrestricted) +- [ ] Testé sur device réel + +--- + +## Conclusion + +Vous avez maintenant tous les fichiers nécessaires pour le module **Play--CodeGen**. + +**Prochaine étape :** Consultez `JIT_INTEGRATION_PLAY.md` pour l'intégration dans l'app Play! principale (acquisition JIT, détection TXM, UI). diff --git a/build_ios/Play JIT implementation/JIT_INTEGRATION_PLAY.md b/build_ios/Play JIT implementation/JIT_INTEGRATION_PLAY.md new file mode 100644 index 00000000..b6d9c0fe --- /dev/null +++ b/build_ios/Play JIT implementation/JIT_INTEGRATION_PLAY.md @@ -0,0 +1,941 @@ +# Guide d'intégration JIT pour Play! (App iOS) + +**Version:** 1.0 +**Date:** 2026-01-17 +**Cible:** Repository Play (application iOS principale) + +--- + +## 📋 Table des matières + +1. [Vue d'ensemble](#vue-densemble) +2. [Prérequis](#prérequis) +3. [Structure des fichiers](#structure-des-fichiers) +4. [Implémentation Objective-C/Swift](#implémentation-objective-cswift) +5. [Intégration dans l'app](#intégration-dans-lapp) +6. [Configuration Xcode](#configuration-xcode) +7. [Tests et validation](#tests-et-validation) +8. [Troubleshooting](#troubleshooting) + +--- + +## Vue d'ensemble + +Ce document décrit l'implémentation de la **couche haute** du système JIT pour l'application iOS **Play!**. Cette couche est responsable de : + +✅ Acquisition du JIT (PTrace, AltServer, Debugger) +✅ Détection de TXM (Trusted Execution Monitor) +✅ Détection de la version iOS +✅ Configuration du module Play--CodeGen +✅ Interface utilisateur (messages d'erreur) + +### Responsabilités de Play! (App) + +✅ Détecter la version iOS (< 26 ou >= 26) +✅ Détecter la présence de TXM sur l'appareil +✅ Acquérir le JIT au démarrage +✅ Configurer Play--CodeGen avec le bon type JIT +✅ Afficher les erreurs à l'utilisateur + +❌ Allocation mémoire bas niveau (géré par CodeGen) +❌ Gestion miroirs RW/RX (géré par CodeGen) + +--- + +## Prérequis + +### 1. Play--CodeGen configuré + +Vous devez d'abord avoir complété l'implémentation de **Play--CodeGen** selon le document `JIT_IMPLEMENTATION_CODEGEN.md`. + +**Vérification :** + +```bash +# Dans Play--CodeGen +ls deps/lwmem/lwmem/src/include/lwmem/lwmem.h +ls include/MemoryUtil.h +ls src/MemoryUtil_iOS.cpp +# Tous doivent exister +``` + +### 2. Linking avec CodeGen + +Votre app iOS doit linker avec la bibliothèque Play--CodeGen : + +```ruby +# Podfile (si vous utilisez CocoaPods) +target 'Play' do + use_frameworks! + + # Link vers Play--CodeGen + pod 'PlayCodeGen', :path => '../Play--CodeGen' +end +``` + +**OU** dans Xcode : + +``` +Target Play → Build Phases → Link Binary With Libraries +→ Ajouter PlayCodeGen.framework +``` + +--- + +## Structure des fichiers + +Créez cette arborescence dans votre projet **Play!** (app iOS) : + +``` +Play/ +└── Source/ + └── iOS/ + └── JIT/ + ├── JitManager.h + ├── JitManager.m + ├── JitManager+Debugger.h + ├── JitManager+Debugger.m + ├── JitManager+PTrace.h + ├── JitManager+PTrace.m + ├── JitManager+AltServer.h # Optionnel + ├── JitManager+AltServer.m # Optionnel + └── JitAcquisitionService.swift +``` + +**Total :** 7-9 fichiers à créer + +--- + +## Implémentation Objective-C/Swift + +### 📄 `Source/iOS/JIT/JitManager.h` + +Interface principale du gestionnaire JIT : + +```objc +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface JitManager : NSObject + +/// Indique si le JIT a été acquis avec succès +@property (readonly, assign) BOOL acquiredJit; + +/// Message d'erreur si acquisition a échoué (nil si succès) +@property (nonatomic, nullable) NSString* acquisitionError; + +/// Indique si l'appareil possède TXM (iOS 26+ uniquement) +@property (readonly, assign) BOOL deviceHasTxm; + +/// Singleton ++ (JitManager*)shared; + +/// Revérifie si le JIT est acquis (via debugger) +- (void)recheckIfJitIsAcquired; + +@end + +NS_ASSUME_NONNULL_END +``` + +--- + +### 📄 `Source/iOS/JIT/JitManager.m` + +Implémentation principale : + +```objc +#import "JitManager.h" +#import "JitManager+Debugger.h" + +typedef NS_ENUM(NSInteger, PLAYJitType) { + PLAYJitTypeDebugger, // Détection via csops() + PLAYJitTypeUnrestricted // Simulateur (pas de restrictions) +}; + +@interface JitManager () +@property (readwrite, assign) BOOL acquiredJit; +@property (readwrite, assign) BOOL deviceHasTxm; +@end + +@implementation JitManager { + PLAYJitType _jitType; +} + ++ (JitManager*)shared { + static JitManager* sharedInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + + return sharedInstance; +} + +- (id)init { + if (self = [super init]) { +#if TARGET_OS_SIMULATOR + _jitType = PLAYJitTypeUnrestricted; +#else + _jitType = PLAYJitTypeDebugger; +#endif + + self.acquiredJit = NO; + + // Détecter TXM uniquement sur iOS 26+ + if (@available(iOS 26, *)) { + self.deviceHasTxm = [self checkIfDeviceUsesTXM]; + } else { + self.deviceHasTxm = NO; + } + } + + return self; +} + +- (void)recheckIfJitIsAcquired { + if (_jitType == PLAYJitTypeDebugger) { + // Sur iOS 26 + TXM, bloquer Xcode (crash garanti) + if (self.deviceHasTxm) { + NSDictionary* environment = [[NSProcessInfo processInfo] environment]; + + if ([environment objectForKey:@"XCODE"] != nil) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + self.acquisitionError = @"JIT cannot be enabled while running within Xcode on iOS 26 with TXM. Use StikDebug or sideload via AltStore."; + }); + return; + } + } + + // Vérifier si processus est débogué + self.acquiredJit = [self checkIfProcessIsDebugged]; + + // Warning si debugger attaché sur TXM (sauf StikDebug) + if (self.deviceHasTxm && self.acquiredJit) { + self.acquisitionError = @"⚠️ A debugger is attached. If not StikDebug, Play! will crash when emulation starts."; + } + } else if (_jitType == PLAYJitTypeUnrestricted) { + // Simulateur : JIT toujours disponible + self.acquiredJit = YES; + } +} + +@end +``` + +--- + +### 📄 `Source/iOS/JIT/JitManager+Debugger.h` + +Extension pour détection debugger et TXM : + +```objc +#import "JitManager.h" + +@interface JitManager (Debugger) + +/// Vérifie si le processus est marqué comme "debugged" +- (BOOL)checkIfProcessIsDebugged; + +/// Vérifie si l'appareil possède TXM (iOS 26+) +- (BOOL)checkIfDeviceUsesTXM; + +@end +``` + +--- + +### 📄 `Source/iOS/JIT/JitManager+Debugger.m` + +```objc +#import "JitManager+Debugger.h" + +#define CS_OPS_STATUS 0 +#define CS_DEBUGGED 0x10000000 + +extern int csops(pid_t pid, unsigned int ops, void* useraddr, size_t usersize); + +@implementation JitManager (Debugger) + +- (BOOL)checkIfProcessIsDebugged { + int flags; + if (csops(getpid(), CS_OPS_STATUS, &flags, sizeof(flags)) != 0) { + return NO; + } + return flags & CS_DEBUGGED; +} + +// Helper : cherche fichier de longueur spécifique dans un dossier +- (nullable NSString*)filePathAtPath:(NSString*)path withLength:(NSUInteger)length { + NSError *error = nil; + NSArray *items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; + if (!items) return nil; + + for (NSString *entry in items) { + if (entry.length == length) { + return [path stringByAppendingPathComponent:entry]; + } + } + return nil; +} + +- (BOOL)checkIfDeviceUsesTXM { + // Méthode basée sur StikDebug + // Cherche : /System/Volumes/Preboot/<36 chars>/boot/<96 chars>/usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4 + + // Primary path + NSString* bootUUID = [self filePathAtPath:@"/System/Volumes/Preboot" withLength:36]; + if (bootUUID) { + NSString* bootDir = [bootUUID stringByAppendingPathComponent:@"boot"]; + NSString* ninetySixCharPath = [self filePathAtPath:bootDir withLength:96]; + if (ninetySixCharPath) { + NSString* img = [ninetySixCharPath stringByAppendingPathComponent: + @"usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4"]; + return access(img.fileSystemRepresentation, F_OK) == 0; + } + } + + // Fallback path + NSString* fallback = [self filePathAtPath:@"/private/preboot" withLength:96]; + if (fallback) { + NSString* img = [fallback stringByAppendingPathComponent: + @"usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4"]; + return access(img.fileSystemRepresentation, F_OK) == 0; + } + + return NO; +} + +@end +``` + +**Comment fonctionne la détection TXM :** + +1. Cherche un dossier UUID de 36 caractères dans `/System/Volumes/Preboot/` +2. Cherche un dossier de 96 caractères dans `/boot/` +3. Vérifie l'existence du fichier firmware TXM +4. Si trouvé → TXM présent + +--- + +### 📄 `Source/iOS/JIT/JitManager+PTrace.h` + +Extension pour acquisition via PTrace : + +```objc +#import "JitManager.h" + +/// Argument CLI utilisé pour lancer le processus enfant +extern const char* _Nonnull PLAYJitPTraceChildProcessArgument; + +@interface JitManager (PTrace) + +/// Vérifie si PTrace peut être utilisé (nécessite entitlement) +- (BOOL)checkCanAcquireJitByPTrace; + +/// Exécute les tâches de démarrage PTrace (appelé par processus enfant) +- (void)runPTraceStartupTasks; + +/// Acquiert le JIT via technique PTrace +- (void)acquireJitByPTrace; + +@end +``` + +--- + +### 📄 `Source/iOS/JIT/JitManager+PTrace.m` + +**Technique PTrace** : Spawne un processus enfant qui appelle `PT_TRACE_ME`, marquant le parent comme "debugged". + +```objc +#import "JitManager+PTrace.h" +#import "JitManager+Debugger.h" + +#import + +// API privées (nécessaires) +void* SecTaskCreateFromSelf(CFAllocatorRef allocator); +CFTypeRef SecTaskCopyValueForEntitlement(void* task, CFStringRef entitlement, CFErrorRef* _Nullable error); + +#define PT_TRACE_ME 0 +#define PT_DETACH 11 +int ptrace(int request, pid_t pid, caddr_t caddr, int data); + +extern char** environ; + +const char* _Nonnull PLAYJitPTraceChildProcessArgument = "ptraceChild"; + +@implementation JitManager (PTrace) + +- (BOOL)checkCanAcquireJitByPTrace { + // PTrace nécessite l'entitlement "platform-application" + // (disponible sur jailbreak ou sideload avec entitlements spéciaux) + + void* task = SecTaskCreateFromSelf(NULL); + CFTypeRef entitlementValue = SecTaskCopyValueForEntitlement(task, CFSTR("platform-application"), NULL); + + if (entitlementValue == NULL) { + CFRelease(task); + return NO; + } + + BOOL result = (entitlementValue == kCFBooleanTrue); + + CFRelease(entitlementValue); + CFRelease(task); + + return result; +} + +- (void)runPTraceStartupTasks { + // Appelé par le processus enfant + // PT_TRACE_ME marque le parent comme "debugged" + ptrace(PT_TRACE_ME, 0, NULL, 0); +} + +- (void)acquireJitByPTrace { + if (![self checkCanAcquireJitByPTrace]) { + self.acquisitionError = @"PTrace not available (missing platform-application entitlement)"; + return; + } + + const char* executablePath = [[[NSBundle mainBundle] executablePath] UTF8String]; + const char* arguments[] = { executablePath, PLAYJitPTraceChildProcessArgument, NULL }; + + pid_t childPid; + int result = posix_spawnp(&childPid, executablePath, NULL, NULL, (char* const*)arguments, environ); + + if (result == 0) { + // Attendre que l'enfant soit arrêté + waitpid(childPid, NULL, WUNTRACED); + + // Détacher et tuer l'enfant + ptrace(PT_DETACH, childPid, NULL, 0); + kill(childPid, SIGTERM); + wait(NULL); + + // Revérifier si JIT acquis + [self recheckIfJitIsAcquired]; + + if (self.acquiredJit) { + NSLog(@"✅ JIT acquired via PTrace"); + } + } else { + self.acquisitionError = [NSString stringWithFormat:@"Failed to spawn PTrace child process (errno %d)", errno]; + } +} + +@end +``` + +**Flux PTrace :** +1. Vérifie entitlement `platform-application` +2. Spawne processus enfant avec argument `"ptraceChild"` +3. Enfant appelle `PT_TRACE_ME` +4. Parent devient "debugged" → JIT acquis +5. Parent detach et kill l'enfant + +--- + +### 📄 `Source/iOS/JIT/JitAcquisitionService.swift` + +Service d'acquisition automatique au lancement : + +```swift +import UIKit + +/// Service responsable de l'acquisition automatique du JIT au démarrage +class JitAcquisitionService: UIResponder, UIApplicationDelegate { + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil + ) -> Bool { + + let manager = JitManager.shared() + + // 1. Vérifier si JIT déjà acquis (ex: debugger attaché) + manager.recheckIfJitIsAcquired() + + // 2. Si pas encore acquis, tenter PTrace + if !manager.acquiredJit { + manager.acquireJitByPTrace() + + #if NONJAILBROKEN + // 3. Si toujours pas acquis, tenter AltServer (optionnel) + if !manager.acquiredJit { + // manager.acquireJitByAltServer() + } + #endif + } + + // 4. Logger le résultat + if manager.acquiredJit { + NSLog("✅ JIT acquired successfully") + } else { + NSLog("❌ JIT not acquired: \(manager.acquisitionError ?? "Unknown error")") + } + + return true + } +} +``` + +--- + +## Intégration dans l'app + +### 1. Modification du `main.m` + +Modifiez le point d'entrée de votre app : + +```objc +#import +#import "JitAcquisitionService.h" +#import "JitManager+PTrace.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + // Si lancé en mode PTrace child, exécuter puis exit + if (argc > 1 && strcmp(argv[1], PLAYJitPTraceChildProcessArgument) == 0) { + [[JitManager shared] runPTraceStartupTasks]; + return 0; + } + + // Lancement normal de l'app + return UIApplicationMain( + argc, + argv, + nil, + NSStringFromClass([JitAcquisitionService class]) + ); + } +} +``` + +**Important :** +- Le `if (argc > 1...)` détecte si c'est le processus enfant PTrace +- Si oui, exécute `PT_TRACE_ME` puis exit +- Sinon, lance l'app normalement + +--- + +### 2. Configuration de Play--CodeGen + +Dans votre code d'initialisation de l'émulateur (ex: `AppDelegate` ou `EmulatorViewController`) : + +```objc +#import "JitManager.h" +#import // Depuis Play--CodeGen + +- (void)configureJIT { + JitManager* jitManager = [JitManager shared]; + [jitManager recheckIfJitIsAcquired]; + + if (!jitManager.acquiredJit) { + // Afficher alerte à l'utilisateur + [self showJITErrorAlert:jitManager.acquisitionError]; + return; + } + + // Configurer Play--CodeGen selon iOS version et TXM + if (@available(iOS 26, *)) { + if (jitManager.deviceHasTxm) { + // iOS 26+ avec TXM : mode le plus performant + NSLog(@"Configuring JIT: LuckTXM mode"); + CodeGen::SetJitType(CodeGen::JitType::LuckTXM); + + // IMPORTANT: Pré-allouer la région de 512 MB + CodeGen::AllocateExecutableMemoryRegion(); + } else { + // iOS 26+ sans TXM + NSLog(@"Configuring JIT: LuckNoTXM mode"); + CodeGen::SetJitType(CodeGen::JitType::LuckNoTXM); + } + } else { + // iOS < 26 + NSLog(@"Configuring JIT: Legacy mode"); + CodeGen::SetJitType(CodeGen::JitType::Legacy); + } + + NSLog(@"✅ JIT configured successfully"); +} + +- (void)showJITErrorAlert:(NSString*)errorMessage { + UIAlertController* alert = [UIAlertController + alertControllerWithTitle:@"JIT Not Available" + message:errorMessage ?: @"Play! requires JIT to run. Please sideload via AltStore or use a debugger." + preferredStyle:UIAlertControllerStyleAlert + ]; + + [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; + + [self.window.rootViewController presentViewController:alert animated:YES completion:nil]; +} +``` + +--- + +### 3. Exemple d'utilisation dans le recompiler + +Une fois JIT configuré, votre recompiler peut utiliser l'API CodeGen : + +```cpp +#include + +class PS2Recompiler { +public: + void* CompileBlock(uint32_t ps2Address, const uint8_t* ps2Code, size_t codeSize) { + // 1. Allouer mémoire exécutable + void* rx_ptr = CodeGen::AllocateExecutableMemory(codeSize); + if (!rx_ptr) return nullptr; + + // 2. Obtenir pointeur writable + ptrdiff_t diff = CodeGen::AllocateWritableRegionAndGetDiff(rx_ptr, codeSize); + void* rw_ptr = static_cast(rx_ptr) + diff; + + // 3. Compiler code PS2 → ARM64 via pointeur RW + size_t compiledSize = EmitARMCode(rw_ptr, ps2Code, codeSize); + + // 4. Libérer miroir RW (ignoré sur LuckTXM, nécessaire sur LuckNoTXM) + CodeGen::FreeWritableRegion(rx_ptr, codeSize, diff); + + // 5. Retourner pointeur exécutable + m_compiledBlocks[ps2Address] = rx_ptr; + return rx_ptr; + } + + void ExecuteBlock(uint32_t ps2Address) { + void* code = m_compiledBlocks[ps2Address]; + if (!code) return; + + // Exécuter via pointeur RX + typedef void (*JitFunc)(); + JitFunc func = reinterpret_cast(code); + func(); + } + +private: + std::unordered_map m_compiledBlocks; +}; +``` + +--- + +## Configuration Xcode + +### 1. Build Settings + +Ajoutez dans **Target Play → Build Settings** : + +| Setting | Value | +|---------|-------| +| **Other Linker Flags** | `-framework Foundation -framework UIKit` | +| **Header Search Paths** | `$(PROJECT_DIR)/../Play--CodeGen/include` | +| **Library Search Paths** | `$(PROJECT_DIR)/../Play--CodeGen/build` | + +### 2. Entitlements (Info.plist ou .entitlements) + +Pour PTrace et JIT : + +```xml + + + + + + com.apple.security.cs.allow-jit + + + + com.apple.security.get-task-allow + + + + com.apple.security.cs.allow-unsigned-executable-memory + + + +``` + +**Note :** Ces entitlements nécessitent sideload via AltStore, pas disponibles sur App Store. + +--- + +### 3. Bridging Header (si Swift ↔ Objective-C) + +Créez `Play-Bridging-Header.h` : + +```objc +#import "JitManager.h" +#import "JitManager+PTrace.h" +#import "JitManager+Debugger.h" +``` + +Dans Build Settings : +``` +Objective-C Bridging Header: $(PROJECT_DIR)/Play-Bridging-Header.h +``` + +--- + +## Tests et validation + +### Test 1 : Logs de démarrage + +Au lancement de l'app, vous devriez voir : + +``` +=== JIT Configuration === +iOS Version: 26.0 +Device has TXM: YES +JIT Acquired: YES +JIT Mode: LuckTXM +✅ JIT configured successfully +``` + +### Test 2 : Vérification programmatique + +Ajoutez un écran de debug dans votre app : + +```swift +import UIKit + +class DebugViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + + let manager = JitManager.shared() + let info = """ + === JIT Status === + iOS Version: \(UIDevice.current.systemVersion) + JIT Acquired: \(manager.acquiredJit ? "YES" : "NO") + Device has TXM: \(manager.deviceHasTxm ? "YES" : "NO") + Error: \(manager.acquisitionError ?? "None") + + JIT Mode: \(getJITModeName()) + """ + + print(info) + } + + func getJITModeName() -> String { + if #available(iOS 26, *) { + return JitManager.shared().deviceHasTxm ? "LuckTXM" : "LuckNoTXM" + } else { + return "Legacy" + } + } +} +``` + +### Test 3 : Compilation et exécution + +Test minimal de JIT : + +```objc +- (void)testJIT { + const size_t size = 4096; + + void* rx = CodeGen::AllocateExecutableMemory(size); + assert(rx != nullptr); + + ptrdiff_t diff = CodeGen::AllocateWritableRegionAndGetDiff(rx, size); + void* rw = (uint8_t*)rx + diff; + + // ARM64: ret instruction + ((uint32_t*)rw)[0] = 0xD65F03C0; + + typedef void (*Func)(); + Func f = (Func)rx; + f(); // Doit retourner sans crash + + CodeGen::FreeWritableRegion(rx, size, diff); + CodeGen::FreeExecutableMemory(rx, size); + + NSLog(@"✅ JIT test passed"); +} +``` + +--- + +## Troubleshooting + +### Problème 1 : JIT non acquis + +**Symptôme :** +``` +❌ JIT not acquired: nil +``` + +**Diagnostic :** +```objc +NSLog(@"Acquisition error: %@", [JitManager shared].acquisitionError); +``` + +**Solutions :** + +| Erreur | Cause | Solution | +|--------|-------|----------| +| `nil` | Debugger non attaché | Lancer via Xcode debug ou PTrace | +| "PTrace not available" | Entitlement manquant | Vérifier `.entitlements` | +| "JIT cannot be enabled while running within Xcode on iOS 26" | Xcode + iOS 26 + TXM | Utiliser StikDebug ou AltStore | + +--- + +### Problème 2 : Crash au lancement émulation + +**Symptôme :** +``` +Thread 1: EXC_BAD_ACCESS (code=2, address=0x...) +``` + +**Causes possibles :** + +1. **TXM + debugger non-StikDebug :** + ```objc + if (manager.deviceHasTxm && manager.acquiredJit) { + NSLog(@"⚠️ Warning: %@", manager.acquisitionError); + // Afficher warning utilisateur + } + ``` + +2. **Région 512 MB non pré-allouée (LuckTXM) :** + ```objc + if (type == CodeGen::JitType::LuckTXM) { + CodeGen::AllocateExecutableMemoryRegion(); // IMPORTANT + } + ``` + +3. **Mauvais pointeur (RX vs RW) :** + ```cpp + // ❌ FAUX : écrire via RX + memcpy(rx_ptr, code, size); + + // ✅ BON : écrire via RW + void* rw_ptr = (uint8_t*)rx_ptr + diff; + memcpy(rw_ptr, code, size); + ``` + +--- + +### Problème 3 : "lwmem_malloc returned nullptr" + +**Cause :** Région de 512 MB épuisée (mode LuckTXM). + +**Solutions :** + +1. **Réduire allocations :** + ```cpp + // Éviter d'allouer plus que nécessaire + void* code = CodeGen::AllocateExecutableMemory(actualSize); + ``` + +2. **Implémenter cache avec eviction :** + ```cpp + if (m_compiledBlocks.size() > MAX_BLOCKS) { + // Libérer le bloc le plus ancien + auto oldest = m_compiledBlocks.begin(); + CodeGen::FreeExecutableMemory(oldest->second, ...); + m_compiledBlocks.erase(oldest); + } + ``` + +3. **Augmenter taille région (avec précaution) :** + ```cpp + // Dans MemoryUtil_iOS_LuckTXM.cpp + constexpr size_t EXECUTABLE_REGION_SIZE = 1073741824; // 1 GB + ``` + +--- + +### Problème 4 : Performance dégradée + +**Diagnostic :** + +1. Vérifier le mode JIT actif : + ```objc + if (@available(iOS 26, *)) { + if ([JitManager shared].deviceHasTxm) { + NSLog(@"Mode: LuckTXM (optimal)"); + } else { + NSLog(@"Mode: LuckNoTXM (bon)"); + } + } else { + NSLog(@"Mode: Legacy (moins performant)"); + } + ``` + +2. Mode Legacy : minimiser les toggles W^X : + ```cpp + // ❌ INEFFICACE + for (int i = 0; i < 100; i++) { + CodeGen::JITPageWriteEnableExecuteDisable(code); + WriteOneInstruction(code); + CodeGen::JITPageWriteDisableExecuteEnable(code); + } + + // ✅ EFFICACE + { + CodeGen::ScopedJITPageWriteAndNoExecute guard(code); + for (int i = 0; i < 100; i++) { + WriteOneInstruction(code); + } + } + ``` + +--- + +## Checklist de validation + +- [ ] Tous les fichiers créés (7-9 fichiers) +- [ ] `main.m` modifié pour PTrace child +- [ ] Entitlements configurés +- [ ] Build Settings corrects +- [ ] Test sur simulateur (mode Unrestricted) +- [ ] Test sur device iOS < 26 (mode Legacy) +- [ ] Test sur device iOS 26+ sans TXM (mode LuckNoTXM) +- [ ] Test sur device iOS 26+ avec TXM (mode LuckTXM) +- [ ] Logs de debug affichent le bon mode +- [ ] Émulation fonctionne sans crash + +--- + +## Ressources + +### Projets de référence + +- **Dolphin iOS** : Implémentation source + - [GitHub](https://github.com/oatmealdome/dolphin) + +- **StikDebug** : Debugger iOS pour JIT + - [GitHub](https://github.com/StephenDev0/StikDebug) + +- **PojavLauncher** : Technique PTrace + - [GitHub](https://github.com/PojavLauncherTeam/PojavLauncher_iOS) + +### Documentation Apple + +- [Code Signing Entitlements](https://developer.apple.com/documentation/bundleresources/entitlements) +- [Process and Thread Programming Guide](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html) + +--- + +## Conclusion + +Vous disposez maintenant de tous les éléments pour intégrer le système JIT dans l'application **Play!** iOS. + +### Workflow complet + +1. ✅ Implémenter Play--CodeGen (voir `JIT_IMPLEMENTATION_CODEGEN.md`) +2. ✅ Créer les fichiers JitManager dans Play! (ce document) +3. ✅ Configurer Xcode et entitlements +4. ✅ Modifier `main.m` pour PTrace +5. ✅ Initialiser JIT au démarrage +6. ✅ Configurer Play--CodeGen selon mode détecté +7. ✅ Tester sur device réel + +**Bonne chance avec votre intégration!** 🎮🚀 diff --git a/build_ios/Play JIT implementation/JIT_MIGRATION_GUIDE_PLAY.md b/build_ios/Play JIT implementation/JIT_MIGRATION_GUIDE_PLAY.md new file mode 100644 index 00000000..641ebee5 --- /dev/null +++ b/build_ios/Play JIT implementation/JIT_MIGRATION_GUIDE_PLAY.md @@ -0,0 +1,1525 @@ +# Guide de migration JIT : Dolphin iOS → Play! PS2 Emulator + +**Version:** 1.0 +**Date:** 2026-01-17 +**Auteur:** Analyse basée sur Dolphin iOS JIT Implementation + +--- + +## 📋 Table des matières + +1. [Vue d'ensemble](#vue-densemble) +2. [Architecture du système JIT](#architecture-du-système-jit) +3. [Dépendances et prérequis](#dépendances-et-prérequis) +4. [Structure des fichiers](#structure-des-fichiers) +5. [Implémentation C++](#implémentation-c) +6. [Implémentation Objective-C/Swift](#implémentation-objective-cswift) +7. [Intégration dans Play!](#intégration-dans-play) +8. [Configuration CMake](#configuration-cmake) +9. [Tests et validation](#tests-et-validation) +10. [Troubleshooting](#troubleshooting) + +--- + +## Vue d'ensemble + +Ce guide vous permet d'implémenter dans **Play! PS2 Emulator** le système JIT avancé de Dolphin iOS qui supporte: + +- ✅ **iOS < 26** : Mode Legacy avec toggle de permissions (W^X) +- ✅ **iOS 26+ sans TXM** : Miroirs RW/RX créés dynamiquement par allocation +- ✅ **iOS 26+ avec TXM** : Région pré-allouée de 512 MB avec miroir RW/RX permanent + +### Avantages de cette implémentation + +- **Performance optimale** sur iOS 26+ avec TXM (allocation unique, pas de toggle) +- **Compatibilité rétroactive** avec toutes les versions iOS +- **Détection automatique** du matériel (TXM) et de la version iOS +- **Acquisition JIT automatique** via PTrace, AltServer, JitStreamer, ou Debugger + +--- + +## Architecture du système JIT + +### Schéma de décision + +``` +┌─────────────────────────────────────┐ +│ Démarrage de l'application │ +└──────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ JitManager.shared() détecte: │ +│ - Version iOS (< 26 ou >= 26) │ +│ - Présence TXM (fichier firmware) │ +│ - JIT acquis (debugger actif?) │ +└──────────────┬──────────────────────┘ + │ + ▼ + ┌──────┴──────┐ + │ iOS >= 26 ? │ + └──────┬──────┘ + │ + ┌───────┴────────┐ + NO YES + │ │ + ▼ ▼ + ┌─────────┐ ┌──────────┐ + │ Legacy │ │ TXM ? │ + │ Mode │ └────┬─────┘ + └─────────┘ │ + ┌───┴────┐ + NO YES + │ │ + ▼ ▼ + ┌──────────┐ ┌──────────┐ + │LuckNoTXM │ │ LuckTXM │ + └──────────┘ └──────────┘ +``` + +### Détail des 3 modes + +| Mode | iOS Version | TXM | Allocation | Miroir RW | Performance | +|------|-------------|-----|------------|-----------|-------------| +| **Legacy** | < 26 | N/A | mmap() par allocation | Non (toggle mprotect) | ⭐⭐⭐ | +| **LuckNoTXM** | >= 26 | ❌ | mmap() par allocation | vm_remap() par alloc | ⭐⭐⭐⭐ | +| **LuckTXM** | >= 26 | ✅ | Région de 512MB unique | vm_remap() global | ⭐⭐⭐⭐⭐ | + +--- + +## Dépendances et prérequis + +### 1. lwmem - Lightweight Memory Manager + +**Pourquoi:** Gère l'allocation dynamique dans la région de 512 MB en mode LuckTXM. + +**Installation:** + +```bash +cd Play--CodeGen +git submodule add https://github.com/MaJerle/lwmem.git deps/lwmem +git submodule update --init --recursive +``` + +**Vérification:** + +```bash +ls deps/lwmem/lwmem/src/include/lwmem/lwmem.h +# Doit exister +``` + +### 2. Headers système requis + +Votre code aura besoin de: + +```cpp +#include // vm_remap(), vm_deallocate() +#include // mmap(), mprotect(), munmap() +#include // posix_spawnp() +#include // pid_t +#include // sysconf(), getpid() +``` + +**⚠️ Fonction non documentée nécessaire:** + +```cpp +extern int csops(pid_t pid, unsigned int ops, void* useraddr, size_t usersize); +``` + +--- + +## Structure des fichiers + +Créez l'arborescence suivante dans **Play--CodeGen**: + +``` +Play--CodeGen/ +├── deps/ +│ └── lwmem/ # Submodule Git +│ +├── include/ +│ ├── MemoryUtil.h # Interface principale (MODIFIER) +│ ├── JITMemoryTracker.h # Tracker pour mode Legacy +│ └── ios/ +│ ├── JitManager.h +│ ├── JitManager+Debugger.h +│ ├── JitManager+PTrace.h +│ ├── JitManager+AltServer.h # Optionnel +│ └── JitAcquisitionService.swift +│ +└── src/ + ├── MemoryUtil_iOS.cpp # Dispatcher (routage) + ├── MemoryUtil_iOS_Legacy.cpp # Implémentation iOS < 26 + ├── MemoryUtil_iOS_LuckNoTXM.cpp # Implémentation iOS 26+ sans TXM + ├── MemoryUtil_iOS_LuckTXM.cpp # Implémentation iOS 26+ avec TXM + ├── JITMemoryTracker.cpp # Tracking pour Legacy + └── ios/ + ├── JitManager.m + ├── JitManager+Debugger.m + ├── JitManager+PTrace.m + └── JitManager+AltServer.m # Optionnel +``` + +**Total:** 17 fichiers à créer/modifier + +--- + +## Implémentation C++ + +### 📄 `include/MemoryUtil.h` + +Ajoutez ces déclarations à votre fichier existant: + +```cpp +#pragma once + +#include + +#ifdef __APPLE__ +#include +#endif + +namespace CodeGen +{ + +// Fonctions principales +void* AllocateExecutableMemory(size_t size); +void FreeExecutableMemory(void* ptr, size_t size); + +#if TARGET_OS_IOS + +// Types de JIT supportés +enum class JitType { + Legacy, // iOS < 26 + LuckNoTXM, // iOS 26+ sans TXM + LuckTXM // iOS 26+ avec TXM +}; + +void SetJitType(JitType type); + +// Gestion write/execute pour iOS +void JITPageWriteEnableExecuteDisable(void* ptr); +void JITPageWriteDisableExecuteEnable(void* ptr); + +// RAII wrapper pour accès en écriture sécurisé +struct ScopedJITPageWriteAndNoExecute { + void* ptr; + + ScopedJITPageWriteAndNoExecute(void* region) : ptr(region) { + JITPageWriteEnableExecuteDisable(ptr); + } + + ~ScopedJITPageWriteAndNoExecute() { + JITPageWriteDisableExecuteEnable(ptr); + } +}; + +// Gestion des régions mémoire +void AllocateExecutableMemoryRegion(); +ptrdiff_t AllocateWritableRegionAndGetDiff(void* rx_ptr, size_t size); +void FreeWritableRegion(void* rx_ptr, size_t size, ptrdiff_t diff); + +// Déclarations des fonctions spécifiques par type +// LuckTXM +void* AllocateExecutableMemory_LuckTXM(size_t size); +void FreeExecutableMemory_LuckTXM(void* ptr); +void AllocateExecutableMemoryRegion_LuckTXM(); +ptrdiff_t AllocateWritableRegionAndGetDiff_LuckTXM(); + +// LuckNoTXM +void* AllocateExecutableMemory_LuckNoTXM(size_t size); +void FreeExecutableMemory_LuckNoTXM(void* ptr, size_t size); +ptrdiff_t AllocateWritableRegionAndGetDiff_LuckNoTXM(void* rx_ptr, size_t size); +void FreeWritableRegion_LuckNoTXM(void* rx_ptr, size_t size, ptrdiff_t diff); + +// Legacy +void* AllocateExecutableMemory_Legacy(size_t size); +void FreeExecutableMemory_Legacy(void* ptr, size_t size); +void JITPageWriteEnableExecuteDisable_Legacy(void* ptr); +void JITPageWriteDisableExecuteEnable_Legacy(void* ptr); + +#endif // TARGET_OS_IOS + +} // namespace CodeGen +``` + +--- + +### 📄 `src/MemoryUtil_iOS.cpp` (Dispatcher) + +Ce fichier route tous les appels vers l'implémentation appropriée: + +```cpp +#include "MemoryUtil.h" + +namespace CodeGen +{ + +static JitType g_jit_type = JitType::LuckTXM; + +void SetJitType(JitType type) { + g_jit_type = type; +} + +void* AllocateExecutableMemory(size_t size) { + if (g_jit_type == JitType::LuckTXM) + return AllocateExecutableMemory_LuckTXM(size); + else if (g_jit_type == JitType::LuckNoTXM) + return AllocateExecutableMemory_LuckNoTXM(size); + else if (g_jit_type == JitType::Legacy) + return AllocateExecutableMemory_Legacy(size); + + return nullptr; +} + +void FreeExecutableMemory(void* ptr, size_t size) { + if (g_jit_type == JitType::LuckTXM) + FreeExecutableMemory_LuckTXM(ptr); + else if (g_jit_type == JitType::LuckNoTXM) + FreeExecutableMemory_LuckNoTXM(ptr, size); + else if (g_jit_type == JitType::Legacy) + FreeExecutableMemory_Legacy(ptr, size); +} + +void AllocateExecutableMemoryRegion() { + if (g_jit_type == JitType::LuckTXM) { + AllocateExecutableMemoryRegion_LuckTXM(); + } +} + +ptrdiff_t AllocateWritableRegionAndGetDiff(void* rx_ptr, size_t size) { + if (g_jit_type == JitType::LuckTXM) + return AllocateWritableRegionAndGetDiff_LuckTXM(); + else if (g_jit_type == JitType::LuckNoTXM) + return AllocateWritableRegionAndGetDiff_LuckNoTXM(rx_ptr, size); + + return 0; +} + +void FreeWritableRegion(void* rx_ptr, size_t size, ptrdiff_t diff) { + if (g_jit_type == JitType::LuckNoTXM) { + FreeWritableRegion_LuckNoTXM(rx_ptr, size, diff); + } +} + +void JITPageWriteEnableExecuteDisable(void* ptr) { + if (g_jit_type == JitType::Legacy) { + JITPageWriteEnableExecuteDisable_Legacy(ptr); + } +} + +void JITPageWriteDisableExecuteEnable(void* ptr) { + if (g_jit_type == JitType::Legacy) { + JITPageWriteDisableExecuteEnable_Legacy(ptr); + } +} + +} // namespace CodeGen +``` + +--- + +### 📄 `src/MemoryUtil_iOS_LuckTXM.cpp` + +**Mode le plus performant** : Région de 512 MB pré-allouée avec miroir RW permanent. + +```cpp +#include "MemoryUtil.h" + +#include +#include +#include +#include +#include + +// 512 MiB - région pré-allouée +constexpr size_t EXECUTABLE_REGION_SIZE = 536870912; + +static uint8_t* g_rx_region = nullptr; +static ptrdiff_t g_rw_region_diff = 0; + +namespace CodeGen +{ + +void AllocateExecutableMemoryRegion_LuckTXM() { + if (g_rx_region) return; // Déjà alloué + + const size_t size = EXECUTABLE_REGION_SIZE; + + // Allouer région RX (read-execute) + uint8_t* rx_ptr = static_cast( + mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0) + ); + + if (!rx_ptr) { + // Gérer l'erreur (log, exception, etc.) + return; + } + + // Signal TXM avec breakpoint spécial (instruction ARM64) + // Cette instruction informe le Trusted Execution Monitor de la région JIT + asm ("mov x0, %0\n" + "mov x1, %1\n" + "brk #0x69" :: "r" (rx_ptr), "r" (size) : "x0", "x1"); + + // Créer miroir RW (read-write) de la même mémoire physique + vm_address_t rw_region = 0; + vm_address_t target = reinterpret_cast(rx_ptr); + vm_prot_t cur_protection = 0; + vm_prot_t max_protection = 0; + + kern_return_t retval = vm_remap( + mach_task_self(), // Task cible + &rw_region, // Adresse de sortie (miroir) + size, // Taille + 0, // Mask + true, // Anywhere (laisse le kernel choisir l'adresse) + mach_task_self(), // Task source + target, // Adresse source (rx_ptr) + false, // Copy (false = partage la même mémoire physique) + &cur_protection, + &max_protection, + VM_INHERIT_DEFAULT + ); + + if (retval != KERN_SUCCESS) return; + + uint8_t* rw_ptr = reinterpret_cast(rw_region); + + // Forcer permissions RW sur le miroir + if (mprotect(rw_ptr, size, PROT_READ | PROT_WRITE) != 0) return; + + // Initialiser lwmem pour gérer l'allocation dynamique dans cette région + lwmem_region_t regions[] = { + { (void*)rw_ptr, size }, + { NULL, 0 } + }; + + if (lwmem_assignmem(regions) == 0) return; + + g_rx_region = rx_ptr; + g_rw_region_diff = rw_ptr - rx_ptr; +} + +ptrdiff_t AllocateWritableRegionAndGetDiff_LuckTXM() { + return g_rw_region_diff; +} + +void* AllocateExecutableMemory_LuckTXM(size_t size) { + if (g_rx_region == nullptr) return nullptr; + + const size_t pagesize = sysconf(_SC_PAGESIZE); + + // Allouer via lwmem avec espace pour alignement + métadonnées + void* raw = lwmem_malloc(size + pagesize - 1 + sizeof(void*)); + + if (!raw) return nullptr; + + // Aligner sur page boundary + uintptr_t raw_addr = (uintptr_t)raw + sizeof(void*); + uintptr_t aligned = (raw_addr + pagesize - 1) & ~(pagesize - 1); + + // Stocker le pointeur raw pour lwmem_free() + ((void**)aligned)[-1] = raw; + + // Retourner le pointeur RX (executable) au lieu du RW + return (uint8_t*)aligned - g_rw_region_diff; +} + +void FreeExecutableMemory_LuckTXM(void* ptr) { + // Récupérer le pointeur raw et libérer + lwmem_free(((void**)ptr)[-1]); +} + +} // namespace CodeGen +``` + +**Points clés:** +- 🔒 **Une seule allocation** de 512 MB au démarrage +- ⚡ **Pas de mprotect()** pendant l'exécution (ultra-rapide) +- 🧠 **lwmem** gère l'allocation dynamique dans cette région +- 📍 **Deux pointeurs** : RX pour exécuter, RW pour écrire + +--- + +### 📄 `src/MemoryUtil_iOS_LuckNoTXM.cpp` + +**Mode flexible** : Crée un miroir RW pour chaque allocation. + +```cpp +#include "MemoryUtil.h" + +#include +#include +#include +#include + +namespace CodeGen +{ + +void* AllocateExecutableMemory_LuckNoTXM(size_t size) { + uint8_t* rx_ptr = static_cast( + mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0) + ); + + if (!rx_ptr) return nullptr; + return rx_ptr; +} + +void FreeExecutableMemory_LuckNoTXM(void* ptr, size_t size) { + if (ptr) { + munmap(ptr, size); + } +} + +ptrdiff_t AllocateWritableRegionAndGetDiff_LuckNoTXM(void* rx_ptr, size_t size) { + vm_address_t rw_region = 0; + vm_address_t target = reinterpret_cast(rx_ptr); + vm_prot_t cur_protection = 0; + vm_prot_t max_protection = 0; + + kern_return_t retval = vm_remap( + mach_task_self(), &rw_region, size, 0, true, + mach_task_self(), target, false, + &cur_protection, &max_protection, VM_INHERIT_DEFAULT + ); + + if (retval != KERN_SUCCESS) return 0; + + uint8_t* rw_ptr = reinterpret_cast(rw_region); + + if (mprotect(rw_ptr, size, PROT_READ | PROT_WRITE) != 0) return 0; + + return rw_ptr - static_cast(rx_ptr); +} + +void FreeWritableRegion_LuckNoTXM(void* rx_ptr, size_t size, ptrdiff_t diff) { + uint8_t* rw_ptr = static_cast(rx_ptr) + diff; + vm_deallocate(mach_task_self(), reinterpret_cast(rw_ptr), size); +} + +} // namespace CodeGen +``` + +**Points clés:** +- 🔁 **Miroir par allocation** : chaque mmap() a son propre vm_remap() +- 📦 **Plus flexible** : pas de limite de 512 MB +- 🐌 **Légèrement moins performant** que LuckTXM (overhead par allocation) + +--- + +### 📄 `src/MemoryUtil_iOS_Legacy.cpp` + +**Mode compatible** : Toggle entre WRITE et EXECUTE via mprotect(). + +```cpp +#include "MemoryUtil.h" +#include "JITMemoryTracker.h" + +#include +#include + +namespace CodeGen +{ + +static JITMemoryTracker g_jit_memory_tracker; + +void* AllocateExecutableMemory_Legacy(size_t size) { + void* ptr = mmap(nullptr, size, PROT_READ | PROT_EXEC, + MAP_ANON | MAP_PRIVATE, -1, 0); + + if (ptr == MAP_FAILED) ptr = nullptr; + if (ptr == nullptr) return nullptr; + + g_jit_memory_tracker.RegisterJITRegion(ptr, size); + return ptr; +} + +void FreeExecutableMemory_Legacy(void* ptr, size_t size) { + if (ptr) { + munmap(ptr, size); + g_jit_memory_tracker.UnregisterJITRegion(ptr); + } +} + +void JITPageWriteEnableExecuteDisable_Legacy(void* ptr) { + g_jit_memory_tracker.JITRegionWriteEnableExecuteDisable(ptr); +} + +void JITPageWriteDisableExecuteEnable_Legacy(void* ptr) { + g_jit_memory_tracker.JITRegionWriteDisableExecuteEnable(ptr); +} + +} // namespace CodeGen +``` + +--- + +### 📄 `include/JITMemoryTracker.h` + +Gère le tracking des régions JIT et le toggle W^X: + +```cpp +#pragma once + +#include +#include +#include + +namespace CodeGen +{ + +class JITMemoryTracker { +public: + void RegisterJITRegion(void* ptr, size_t size); + void UnregisterJITRegion(void* ptr); + void JITRegionWriteEnableExecuteDisable(void* ptr); + void JITRegionWriteDisableExecuteEnable(void* ptr); + +private: + struct JITRegion { + void* start; + size_t size; + int nesting_counter; // Support pour appels imbriqués + }; + + std::map m_regions; + std::mutex m_mutex; + + JITRegion* FindRegion(void* ptr); +}; + +} // namespace CodeGen +``` + +--- + +### 📄 `src/JITMemoryTracker.cpp` + +```cpp +#include "JITMemoryTracker.h" +#include + +namespace CodeGen +{ + +void JITMemoryTracker::RegisterJITRegion(void* ptr, size_t size) { + std::lock_guard lock(m_mutex); + m_regions[ptr] = {ptr, size, 0}; +} + +void JITMemoryTracker::UnregisterJITRegion(void* ptr) { + std::lock_guard lock(m_mutex); + m_regions.erase(ptr); +} + +JITMemoryTracker::JITRegion* JITMemoryTracker::FindRegion(void* ptr) { + for (auto& pair : m_regions) { + JITRegion& region = pair.second; + if (ptr >= region.start && + ptr < static_cast(region.start) + region.size) { + return ®ion; + } + } + return nullptr; +} + +void JITMemoryTracker::JITRegionWriteEnableExecuteDisable(void* ptr) { + std::lock_guard lock(m_mutex); + JITRegion* region = FindRegion(ptr); + if (region) { + if (region->nesting_counter == 0) { + // Seulement toggle si pas déjà en mode write + mprotect(region->start, region->size, PROT_READ | PROT_WRITE); + } + region->nesting_counter++; + } +} + +void JITMemoryTracker::JITRegionWriteDisableExecuteEnable(void* ptr) { + std::lock_guard lock(m_mutex); + JITRegion* region = FindRegion(ptr); + if (region) { + region->nesting_counter--; + if (region->nesting_counter == 0) { + // Seulement toggle si plus d'appels imbriqués + mprotect(region->start, region->size, PROT_READ | PROT_EXEC); + } + } +} + +} // namespace CodeGen +``` + +**Points clés:** +- 🔢 **Nesting counter** : supporte les appels imbriqués +- 🔒 **Thread-safe** : utilise std::mutex +- 📍 **Lookup dynamique** : trouve la région contenant un pointeur donné + +--- + +## Implémentation Objective-C/Swift + +### 📄 `include/ios/JitManager.h` + +Interface principale du gestionnaire JIT: + +```objc +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface JitManager : NSObject + +@property (readonly, assign) BOOL acquiredJit; +@property (nonatomic, nullable) NSString* acquisitionError; +@property (readonly, assign) BOOL deviceHasTxm; + ++ (JitManager*)shared; +- (void)recheckIfJitIsAcquired; + +@end + +NS_ASSUME_NONNULL_END +``` + +--- + +### 📄 `src/ios/JitManager.m` + +```objc +#import "JitManager.h" +#import "JitManager+Debugger.h" + +typedef NS_ENUM(NSInteger, PLAYJitType) { + PLAYJitTypeDebugger, + PLAYJitTypeUnrestricted +}; + +@interface JitManager () +@property (readwrite, assign) BOOL acquiredJit; +@property (readwrite, assign) BOOL deviceHasTxm; +@end + +@implementation JitManager { + PLAYJitType _jitType; +} + ++ (JitManager*)shared { + static JitManager* sharedInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + + return sharedInstance; +} + +- (id)init { + if (self = [super init]) { +#if TARGET_OS_SIMULATOR + _jitType = PLAYJitTypeUnrestricted; +#else + _jitType = PLAYJitTypeDebugger; +#endif + + self.acquiredJit = NO; + + if (@available(iOS 26, *)) { + self.deviceHasTxm = [self checkIfDeviceUsesTXM]; + } else { + self.deviceHasTxm = NO; + } + } + + return self; +} + +- (void)recheckIfJitIsAcquired { + if (_jitType == PLAYJitTypeDebugger) { + if (self.deviceHasTxm) { + NSDictionary* environment = [[NSProcessInfo processInfo] environment]; + + // Bloquer Xcode sur iOS 26 + TXM (crash garanti) + if ([environment objectForKey:@"XCODE"] != nil) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + self.acquisitionError = @"JIT cannot be enabled while running within Xcode on iOS 26."; + }); + return; + } + } + + self.acquiredJit = [self checkIfProcessIsDebugged]; + + if (self.deviceHasTxm && self.acquiredJit) { + self.acquisitionError = @"A debugger is attached. However, if the debugger is not StikDebug, Play! will crash when emulation starts."; + } + } else if (_jitType == PLAYJitTypeUnrestricted) { + self.acquiredJit = YES; + } +} + +@end +``` + +--- + +### 📄 `include/ios/JitManager+Debugger.h` + +```objc +#import "JitManager.h" + +@interface JitManager (Debugger) +- (BOOL)checkIfProcessIsDebugged; +- (BOOL)checkIfDeviceUsesTXM; +@end +``` + +--- + +### 📄 `src/ios/JitManager+Debugger.m` + +```objc +#import "JitManager+Debugger.h" + +#define CS_OPS_STATUS 0 +#define CS_DEBUGGED 0x10000000 + +extern int csops(pid_t pid, unsigned int ops, void* useraddr, size_t usersize); + +@implementation JitManager (Debugger) + +- (BOOL)checkIfProcessIsDebugged { + int flags; + if (csops(getpid(), CS_OPS_STATUS, &flags, sizeof(flags)) != 0) { + return NO; + } + return flags & CS_DEBUGGED; +} + +- (nullable NSString*)filePathAtPath:(NSString*)path withLength:(NSUInteger)length { + NSError *error = nil; + NSArray *items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; + if (!items) return nil; + + for (NSString *entry in items) { + if (entry.length == length) { + return [path stringByAppendingPathComponent:entry]; + } + } + return nil; +} + +- (BOOL)checkIfDeviceUsesTXM { + // Primary: /System/Volumes/Preboot/<36>/boot/<96>/usr/.../Ap,TrustedExecutionMonitor.img4 + NSString* bootUUID = [self filePathAtPath:@"/System/Volumes/Preboot" withLength:36]; + if (bootUUID) { + NSString* bootDir = [bootUUID stringByAppendingPathComponent:@"boot"]; + NSString* ninetySixCharPath = [self filePathAtPath:bootDir withLength:96]; + if (ninetySixCharPath) { + NSString* img = [ninetySixCharPath stringByAppendingPathComponent: + @"usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4"]; + return access(img.fileSystemRepresentation, F_OK) == 0; + } + } + + // Fallback: /private/preboot/<96>/usr/.../Ap,TrustedExecutionMonitor.img4 + NSString* fallback = [self filePathAtPath:@"/private/preboot" withLength:96]; + if (fallback) { + NSString* img = [fallback stringByAppendingPathComponent: + @"usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4"]; + return access(img.fileSystemRepresentation, F_OK) == 0; + } + + return NO; +} + +@end +``` + +**Comment fonctionne la détection TXM:** + +1. Cherche un dossier de 36 caractères dans `/System/Volumes/Preboot/` (UUID) +2. Cherche un dossier de 96 caractères dans `/boot/` +3. Vérifie l'existence de `usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4` +4. Si trouvé → TXM présent, sinon → pas de TXM + +--- + +### 📄 `include/ios/JitManager+PTrace.h` + +```objc +#import "JitManager.h" + +extern const char* _Nonnull PLAYJitPTraceChildProcessArgument; + +@interface JitManager (PTrace) +- (BOOL)checkCanAcquireJitByPTrace; +- (void)runPTraceStartupTasks; +- (void)acquireJitByPTrace; +@end +``` + +--- + +### 📄 `src/ios/JitManager+PTrace.m` + +**Technique PTrace** : Lance un processus enfant qui appelle `PT_TRACE_ME`, ce qui marque le parent comme "debugged". + +```objc +#import "JitManager+PTrace.h" +#import "JitManager+Debugger.h" + +#import + +void* SecTaskCreateFromSelf(CFAllocatorRef allocator); +CFTypeRef SecTaskCopyValueForEntitlement(void* task, CFStringRef entitlement, CFErrorRef* _Nullable error); + +#define PT_TRACE_ME 0 +#define PT_DETACH 11 +int ptrace(int request, pid_t pid, caddr_t caddr, int data); + +extern char** environ; + +const char* _Nonnull PLAYJitPTraceChildProcessArgument = "ptraceChild"; + +@implementation JitManager (PTrace) + +- (BOOL)checkCanAcquireJitByPTrace { + // Vérifie l'entitlement "platform-application" (jailbreak/sideload) + void* task = SecTaskCreateFromSelf(NULL); + CFTypeRef entitlementValue = SecTaskCopyValueForEntitlement(task, CFSTR("platform-application"), NULL); + + if (entitlementValue == NULL) return NO; + + BOOL result = entitlementValue == kCFBooleanTrue; + + CFRelease(entitlementValue); + CFRelease(task); + + return result; +} + +- (void)runPTraceStartupTasks { + // Appelé par le processus enfant au démarrage + ptrace(PT_TRACE_ME, 0, NULL, 0); +} + +- (void)acquireJitByPTrace { + if (![self checkCanAcquireJitByPTrace]) return; + + const char* executablePath = [[[NSBundle mainBundle] executablePath] UTF8String]; + const char* arguments[] = { executablePath, PLAYJitPTraceChildProcessArgument, NULL }; + + pid_t childPid; + if (posix_spawnp(&childPid, executablePath, NULL, NULL, (char* const*)arguments, environ) == 0) { + waitpid(childPid, NULL, WUNTRACED); + ptrace(PT_DETACH, childPid, NULL, 0); + kill(childPid, SIGTERM); + wait(NULL); + + [self recheckIfJitIsAcquired]; + } else { + self.acquisitionError = [NSString stringWithFormat:@"Failed to spawn child process for PTrace style JIT, errno %d", errno]; + } +} + +@end +``` + +**Flux PTrace:** +1. Vérifie entitlement `platform-application` +2. Spawne processus enfant avec argument `"ptraceChild"` +3. Enfant appelle `PT_TRACE_ME` +4. Parent devient "debugged" +5. Parent detach et kill l'enfant +6. Recheck si JIT acquis + +--- + +### 📄 `include/ios/JitAcquisitionService.swift` + +Service d'acquisition automatique au démarrage de l'app: + +```swift +import UIKit + +class JitAcquisitionService: UIResponder, UIApplicationDelegate { + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil + ) -> Bool { + + let manager = JitManager.shared() + + // Vérifier si déjà acquis (ex: debugger attaché) + manager.recheckIfJitIsAcquired() + + if !manager.acquiredJit { + // Tenter acquisition via PTrace + manager.acquireJitByPTrace() + + #if NONJAILBROKEN + // Si vous implémentez AltServer (optionnel) + // manager.acquireJitByAltServer() + #endif + } + + return true + } +} +``` + +**Intégration dans `main()` de votre app:** + +```objc +int main(int argc, char* argv[]) { + @autoreleasepool { + // Si lancé en mode ptrace child, exécuter puis exit + if (argc > 1 && strcmp(argv[1], PLAYJitPTraceChildProcessArgument) == 0) { + [[JitManager shared] runPTraceStartupTasks]; + return 0; + } + + // Lancement normal de l'app + return UIApplicationMain(argc, argv, nil, + NSStringFromClass([JitAcquisitionService class])); + } +} +``` + +--- + +## Intégration dans Play! + +### Au démarrage de l'émulateur + +Dans votre code d'initialisation de l'émulateur (probablement dans `AppDelegate` ou équivalent): + +```objc +#import "JitManager.h" +#import "MemoryUtil.h" + +- (void)initializeEmulator { + JitManager* jitManager = [JitManager shared]; + [jitManager recheckIfJitIsAcquired]; + + if (jitManager.acquiredJit) { + if (@available(iOS 26, *)) { + if (jitManager.deviceHasTxm) { + // iOS 26+ avec TXM : mode le plus performant + CodeGen::SetJitType(CodeGen::JitType::LuckTXM); + + // IMPORTANT: Pré-allouer la région de 512 MB + CodeGen::AllocateExecutableMemoryRegion(); + } else { + // iOS 26+ sans TXM + CodeGen::SetJitType(CodeGen::JitType::LuckNoTXM); + } + } else { + // iOS < 26 + CodeGen::SetJitType(CodeGen::JitType::Legacy); + } + + NSLog(@"JIT configured successfully"); + } else { + NSLog(@"JIT not acquired: %@", jitManager.acquisitionError); + // Afficher une alerte à l'utilisateur + } +} +``` + +--- + +### Dans votre code de génération JIT + +#### **Cas 1 : Mode LuckTXM ou LuckNoTXM (iOS 26+)** + +```cpp +#include "MemoryUtil.h" + +void CompilePS2Code() { + const size_t codeSize = 4096; // Exemple + + // 1. Allouer mémoire exécutable + void* rx_ptr = CodeGen::AllocateExecutableMemory(codeSize); + if (!rx_ptr) { + // Gérer l'erreur + return; + } + + // 2. Obtenir pointeur writable + ptrdiff_t diff = CodeGen::AllocateWritableRegionAndGetDiff(rx_ptr, codeSize); + void* rw_ptr = static_cast(rx_ptr) + diff; + + // 3. Écrire le code compilé via rw_ptr + GenerateARMInstructions(rw_ptr, codeSize); + + // 4. Exécuter via rx_ptr + typedef void (*JitFunction)(); + JitFunction func = reinterpret_cast(rx_ptr); + func(); + + // 5. Libérer quand terminé + CodeGen::FreeWritableRegion(rx_ptr, codeSize, diff); + CodeGen::FreeExecutableMemory(rx_ptr, codeSize); +} +``` + +**Points clés:** +- ✅ **rx_ptr** : Utilisez pour exécuter +- ✅ **rw_ptr** : Utilisez pour écrire +- ❌ **Ne jamais écrire via rx_ptr** (crash) +- ❌ **Ne jamais exécuter via rw_ptr** (non exécutable) + +--- + +#### **Cas 2 : Mode Legacy (iOS < 26)** + +```cpp +#include "MemoryUtil.h" + +void CompilePS2Code_Legacy() { + const size_t codeSize = 4096; + + // 1. Allouer mémoire + void* code = CodeGen::AllocateExecutableMemory(codeSize); + if (!code) return; + + // 2. Activer écriture avec RAII wrapper + { + CodeGen::ScopedJITPageWriteAndNoExecute guard(code); + + // Le code est maintenant WRITABLE mais pas EXECUTABLE + GenerateARMInstructions(code, codeSize); + + // Le destructeur de 'guard' réactive automatiquement EXECUTE + } + + // 3. Exécuter (maintenant EXECUTABLE mais pas WRITABLE) + typedef void (*JitFunction)(); + JitFunction func = reinterpret_cast(code); + func(); + + // 4. Libérer + CodeGen::FreeExecutableMemory(code, codeSize); +} +``` + +**Avantages du RAII wrapper:** +- ✅ Pas d'oubli de restore execute +- ✅ Exception-safe +- ✅ Supporte l'imbrication + +--- + +### Exemple complet d'intégration + +```cpp +#include "MemoryUtil.h" + +class PS2Recompiler { +public: + void* CompileBlock(uint32_t ps2Address, size_t estimatedSize) { + void* rx_ptr = CodeGen::AllocateExecutableMemory(estimatedSize); + if (!rx_ptr) return nullptr; + + #if TARGET_OS_IOS + ptrdiff_t diff = CodeGen::AllocateWritableRegionAndGetDiff(rx_ptr, estimatedSize); + void* rw_ptr = static_cast(rx_ptr) + diff; + + // Compiler vers rw_ptr + size_t actualSize = EmitARMCode(rw_ptr, ps2Address); + + // Libérer miroir RW (LuckNoTXM uniquement, ignoré sur LuckTXM) + CodeGen::FreeWritableRegion(rx_ptr, estimatedSize, diff); + #else + // Desktop/autres plateformes + EmitARMCode(rx_ptr, ps2Address); + #endif + + // Retourner pointeur exécutable + m_compiledBlocks[ps2Address] = rx_ptr; + return rx_ptr; + } + + void FreeBlock(uint32_t ps2Address) { + auto it = m_compiledBlocks.find(ps2Address); + if (it != m_compiledBlocks.end()) { + CodeGen::FreeExecutableMemory(it->second, /* size */); + m_compiledBlocks.erase(it); + } + } + +private: + std::map m_compiledBlocks; + + size_t EmitARMCode(void* dest, uint32_t ps2Address); +}; +``` + +--- + +## Configuration CMake + +### Modification de `CMakeLists.txt` + +Ajoutez ceci à votre CMakeLists principal: + +```cmake +project(PlayCodeGen) + +# Détection de la plateforme +if(APPLE) + if(IOS OR CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(TARGET_PLATFORM_IOS TRUE) + endif() +endif() + +# Ajout de lwmem pour iOS +if(TARGET_PLATFORM_IOS) + message(STATUS "Configuring for iOS with JIT support") + + # Ajouter lwmem + add_subdirectory(deps/lwmem) + + # Fichiers source iOS + set(IOS_JIT_SOURCES + src/MemoryUtil_iOS.cpp + src/MemoryUtil_iOS_Legacy.cpp + src/MemoryUtil_iOS_LuckNoTXM.cpp + src/MemoryUtil_iOS_LuckTXM.cpp + src/JITMemoryTracker.cpp + src/ios/JitManager.m + src/ios/JitManager+Debugger.m + src/ios/JitManager+PTrace.m + ) + + target_sources(PlayCodeGen PRIVATE ${IOS_JIT_SOURCES}) + + # Link libraries + target_link_libraries(PlayCodeGen PRIVATE lwmem) + + # Include directories + target_include_directories(PlayCodeGen PRIVATE + ${CMAKE_SOURCE_DIR}/deps/lwmem/lwmem/src/include + ${CMAKE_SOURCE_DIR}/include/ios + ) + + # Compiler definitions + target_compile_definitions(PlayCodeGen PRIVATE + IPHONEOS=1 + TARGET_OS_IOS=1 + ) + + # Enable Objective-C++ + set_source_files_properties( + ${IOS_JIT_SOURCES} + PROPERTIES + COMPILE_FLAGS "-x objective-c++" + ) +endif() +``` + +--- + +### Configuration Xcode (si applicable) + +Si vous utilisez Xcode directement: + +1. **Build Settings → Other Linker Flags:** + ``` + -framework Foundation + -framework UIKit + ``` + +2. **Build Settings → Header Search Paths:** + ``` + $(PROJECT_DIR)/deps/lwmem/lwmem/src/include + $(PROJECT_DIR)/include/ios + ``` + +3. **Build Settings → Enable Modules:** + ``` + YES + ``` + +4. **Build Phases → Link Binary With Libraries:** + - Ajouter `liblwmem.a` + +--- + +## Tests et validation + +### Checklist de tests + +- [ ] **Compilation réussie** sur toutes les plateformes +- [ ] **Tests unitaires** pour chaque mode JIT +- [ ] **Test simulateur iOS** (doit utiliser mode Unrestricted) +- [ ] **Test iPhone iOS < 26** (doit utiliser mode Legacy) +- [ ] **Test iPhone iOS 26+ sans TXM** (doit utiliser mode LuckNoTXM) +- [ ] **Test iPhone iOS 26+ avec TXM** (doit utiliser mode LuckTXM) + +--- + +### Code de test simple + +Ajoutez ce test dans votre suite de tests: + +```cpp +#include "MemoryUtil.h" +#include + +void TestJITAllocation() { + const size_t testSize = 4096; + + // Test allocation + void* ptr = CodeGen::AllocateExecutableMemory(testSize); + assert(ptr != nullptr); + + // Test writable region + ptrdiff_t diff = CodeGen::AllocateWritableRegionAndGetDiff(ptr, testSize); + void* rw_ptr = static_cast(ptr) + diff; + + // Test écriture + uint32_t* code = static_cast(rw_ptr); + code[0] = 0xD65F03C0; // ARM64: ret + + // Test exécution + typedef void (*EmptyFunc)(); + EmptyFunc func = reinterpret_cast(ptr); + func(); // Doit simplement retourner + + // Cleanup + CodeGen::FreeWritableRegion(ptr, testSize, diff); + CodeGen::FreeExecutableMemory(ptr, testSize); + + printf("✅ JIT allocation test passed\n"); +} +``` + +--- + +### Logs de debug + +Ajoutez des logs pour diagnostiquer: + +```objc +JitManager* mgr = [JitManager shared]; +NSLog(@"=== JIT Status ==="); +NSLog(@"iOS Version: %@", [[UIDevice currentDevice] systemVersion]); +NSLog(@"JIT Acquired: %d", mgr.acquiredJit); +NSLog(@"Device has TXM: %d", mgr.deviceHasTxm); +NSLog(@"Acquisition Error: %@", mgr.acquisitionError ?: @"None"); + +if (@available(iOS 26, *)) { + NSLog(@"JIT Mode: %@", mgr.deviceHasTxm ? @"LuckTXM" : @"LuckNoTXM"); +} else { + NSLog(@"JIT Mode: Legacy"); +} +``` + +**Exemple de sortie attendue:** + +``` +=== JIT Status === +iOS Version: 26.0 +JIT Acquired: 1 +Device has TXM: 1 +Acquisition Error: None +JIT Mode: LuckTXM +``` + +--- + +## Troubleshooting + +### Erreur: "lwmem_malloc returned nullptr" + +**Cause:** Région de 512 MB épuisée (mode LuckTXM uniquement). + +**Solutions:** +1. Réduire la taille des allocations JIT +2. Réutiliser les blocs compilés +3. Implémenter un cache avec eviction +4. Augmenter `EXECUTABLE_REGION_SIZE` (avec précaution) + +--- + +### Erreur: "vm_remap failed" (retval != KERN_SUCCESS) + +**Cause:** Permissions insuffisantes ou iOS trop ancien. + +**Solutions:** +1. Vérifier iOS >= 14.0 +2. Vérifier que JIT est acquis (`acquiredJit == YES`) +3. Sur simulateur, vérifier qu'il est en mode Debug +4. Vérifier les entitlements (`get-task-allow`, `dynamic-codesigning`) + +--- + +### Crash lors de l'exécution du code JIT + +**Cause:** Mauvais pointeur (rx vs rw). + +**Diagnostic:** +```cpp +void* rx_ptr = CodeGen::AllocateExecutableMemory(size); +ptrdiff_t diff = CodeGen::AllocateWritableRegionAndGetDiff(rx_ptr, size); +void* rw_ptr = static_cast(rx_ptr) + diff; + +NSLog(@"RX ptr: %p", rx_ptr); +NSLog(@"RW ptr: %p", rw_ptr); +NSLog(@"Diff: %td", diff); + +// Vérifier que vous écrivez via rw_ptr +// Vérifier que vous exécutez via rx_ptr +``` + +**Solutions:** +- ✅ Toujours **écrire via rw_ptr** +- ✅ Toujours **exécuter via rx_ptr** +- ✅ Garder track du `diff` pour chaque allocation + +--- + +### Crash avec "killed by TXM" sur iOS 26 + +**Cause:** Xcode debugger attaché ou code non signé correctement. + +**Solutions:** +1. **Ne PAS lancer depuis Xcode** sur iOS 26+ avec TXM +2. Utiliser **StikDebug** ou sideload via **AltStore** +3. Vérifier que `brk #0x69` est bien exécuté + +--- + +### JIT non acquis (acquiredJit == NO) + +**Diagnostic:** +```objc +NSLog(@"Acquisition error: %@", [JitManager shared].acquisitionError); +``` + +**Causes possibles:** + +| Erreur | Cause | Solution | +|--------|-------|----------| +| `nil` (pas d'erreur) | Debugger non attaché | Lancer via Xcode en debug ou PTrace | +| "Failed to spawn child process" | Entitlement manquant | Vérifier `platform-application` | +| "JIT cannot be enabled while running within Xcode on iOS 26" | Xcode + iOS 26 + TXM | Utiliser StikDebug ou AltStore | + +--- + +### Problème de performance (lent) + +**Diagnostic:** +1. Vérifier le mode JIT actif: + ```objc + if (@available(iOS 26, *)) { + if ([JitManager shared].deviceHasTxm) { + NSLog(@"Mode: LuckTXM (le plus rapide)"); + } else { + NSLog(@"Mode: LuckNoTXM (moyen)"); + } + } else { + NSLog(@"Mode: Legacy (lent)"); + } + ``` + +2. Vérifier le nombre de `mprotect()` calls (Legacy uniquement): + ```cpp + // Ajouter un compteur dans JITMemoryTracker + static int g_mprotect_count = 0; + g_mprotect_count++; + NSLog(@"mprotect calls: %d", g_mprotect_count); + ``` + +**Solutions:** +- Mode Legacy: Minimiser les appels à `JITPageWrite*` +- Mode LuckNoTXM: Réutiliser les miroirs RW si possible +- Mode LuckTXM: Déjà optimal + +--- + +## Ressources et références + +### Documentation Apple + +- [Mach VM Interface](https://developer.apple.com/documentation/kernel/vm_remap) +- [Memory Management Programming Guide](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html) + +### Projets similaires + +- **Dolphin iOS** : Source de cette implémentation + - [GitHub](https://github.com/oatmealdome/dolphin) + +- **PojavLauncher** : Technique PTrace + - [GitHub](https://github.com/PojavLauncherTeam/PojavLauncher_iOS) + +- **StikDebug** : Debugger iOS pour JIT + - [GitHub](https://github.com/StephenDev0/StikDebug) + +### lwmem + +- **Documentation** : [GitHub lwmem](https://github.com/MaJerle/lwmem) +- **API Reference** : Voir `deps/lwmem/docs/` + +--- + +## Annexe : Comparaison des modes + +### Performance + +| Opération | Legacy | LuckNoTXM | LuckTXM | +|-----------|--------|-----------|---------| +| Allocation (première fois) | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| Écriture code | ⭐⭐ (mprotect) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| Exécution code | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| Libération | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | + +### Complexité d'utilisation + +| Aspect | Legacy | LuckNoTXM | LuckTXM | +|--------|--------|-----------|---------| +| Setup initial | Simple | Moyen | Complexe | +| Utilisation courante | RAII wrapper | RX/RW pointers | RX/RW pointers | +| Gestion mémoire | Automatique | Manuelle (diff) | Manuelle (diff) | + +### Limites + +| Mode | Limite principale | +|------|-------------------| +| **Legacy** | Performance (mprotect syscalls) | +| **LuckNoTXM** | Overhead par allocation (vm_remap) | +| **LuckTXM** | Taille fixe 512 MB | + +--- + +## Conclusion + +Vous disposez maintenant d'une méthodologie complète pour implémenter le système JIT avancé de Dolphin iOS dans Play! PS2 Emulator. + +### Étapes suivantes recommandées + +1. ✅ Installer lwmem +2. ✅ Créer tous les fichiers listés +3. ✅ Modifier CMakeLists.txt +4. ✅ Compiler et tester sur simulateur +5. ✅ Tester sur appareil réel iOS < 26 +6. ✅ Tester sur appareil réel iOS 26+ +7. ✅ Mesurer les performances avant/après + +### Support + +Si vous rencontrez des problèmes: +1. Consultez la section [Troubleshooting](#troubleshooting) +2. Vérifiez les logs avec la section Debug +3. Comparez avec l'implémentation Dolphin iOS de référence + +--- + +**Bonne chance avec votre implémentation!** 🚀 diff --git a/deps/lwmem b/deps/lwmem new file mode 160000 index 00000000..42b67349 --- /dev/null +++ b/deps/lwmem @@ -0,0 +1 @@ +Subproject commit 42b67349a1a8d40b6f9492b241e52e51bf6c4c00 diff --git a/include/JITMemoryTracker.h b/include/JITMemoryTracker.h new file mode 100644 index 00000000..f2531f22 --- /dev/null +++ b/include/JITMemoryTracker.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +namespace CodeGen +{ + + class JITMemoryTracker + { + public: + void RegisterJITRegion(void* ptr, size_t size); + void UnregisterJITRegion(void* ptr); + void JITRegionWriteEnableExecuteDisable(void* ptr); + void JITRegionWriteDisableExecuteEnable(void* ptr); + + private: + struct JITRegion + { + void* start; + size_t size; + int nesting_counter; // Support appels imbriqués + }; + + std::map m_regions; + std::mutex m_mutex; + + JITRegion* FindRegion(void* ptr); + }; + +} // namespace CodeGen diff --git a/include/MemoryFunction.h b/include/MemoryFunction.h index fca4a0f2..02bf670c 100644 --- a/include/MemoryFunction.h +++ b/include/MemoryFunction.h @@ -37,6 +37,13 @@ class CMemoryFunction void* m_code; size_t m_size; + +#ifdef __APPLE__ + bool m_ios26TxmMode = false; + void* m_rxMemory = nullptr; + void* m_rwAliasMemory = nullptr; +#endif + #if defined(__EMSCRIPTEN__) emscripten::val m_wasmModule; #endif diff --git a/include/MemoryUtil.h b/include/MemoryUtil.h new file mode 100644 index 00000000..177bc574 --- /dev/null +++ b/include/MemoryUtil.h @@ -0,0 +1,116 @@ +#pragma once + +#include + +#ifdef __APPLE__ +#include +#endif + +namespace CodeGen +{ + + // ============================================================================ + // API PUBLIQUE + // ============================================================================ + + /// Alloue de la mémoire exécutable + /// @param size Taille en bytes (sera alignée sur page) + /// @return Pointeur vers mémoire exécutable, ou nullptr si échec + void* AllocateExecutableMemory(size_t size); + + /// Libère de la mémoire exécutable + /// @param ptr Pointeur retourné par AllocateExecutableMemory() + /// @param size Taille originale + void FreeExecutableMemory(void* ptr, size_t size); + +#if TARGET_OS_IOS + + // ============================================================================ + // TYPES ET CONFIGURATION (iOS uniquement) + // ============================================================================ + + /// Types de JIT supportés + enum class JitType + { + Legacy, ///< iOS < 26 : Toggle W^X avec mprotect() + LuckNoTXM, ///< iOS 26+ sans TXM : Miroirs RW/RX par allocation + LuckTXM ///< iOS 26+ avec TXM : Région 512 MB pré-allouée + }; + + /// Configure le type de JIT à utiliser + /// @note DOIT être appelé avant toute allocation + /// @param type Type de JIT (détecté par Play!) + void SetJitType(JitType type); + + // ============================================================================ + // GESTION WRITE/EXECUTE (Legacy uniquement) + // ============================================================================ + + /// Active écriture, désactive exécution (Legacy) + void JITPageWriteEnableExecuteDisable(void* ptr); + + /// Désactive écriture, active exécution (Legacy) + void JITPageWriteDisableExecuteEnable(void* ptr); + + /// RAII wrapper pour accès en écriture sécurisé + struct ScopedJITPageWriteAndNoExecute + { + void* ptr; + + ScopedJITPageWriteAndNoExecute(void* region) + : ptr(region) + { + JITPageWriteEnableExecuteDisable(ptr); + } + + ~ScopedJITPageWriteAndNoExecute() + { + JITPageWriteDisableExecuteEnable(ptr); + } + }; + + // ============================================================================ + // GESTION RÉGIONS RW/RX (LuckNoTXM et LuckTXM) + // ============================================================================ + + /// Pré-alloue la région exécutable (LuckTXM uniquement) + /// @note Doit être appelé une fois au démarrage si mode LuckTXM + void AllocateExecutableMemoryRegion(); + + /// Obtient le décalage vers région writable + /// @param rx_ptr Pointeur exécutable + /// @param size Taille de la région + /// @return Décalage à ajouter pour obtenir pointeur writable + ptrdiff_t AllocateWritableRegionAndGetDiff(void* rx_ptr, size_t size); + + /// Libère la région writable (LuckNoTXM uniquement) + /// @param rx_ptr Pointeur exécutable + /// @param size Taille + /// @param diff Décalage retourné par AllocateWritableRegionAndGetDiff() + void FreeWritableRegion(void* rx_ptr, size_t size, ptrdiff_t diff); + + // ============================================================================ + // DÉCLARATIONS INTERNES (ne pas utiliser directement) + // ============================================================================ + + // LuckTXM + void* AllocateExecutableMemory_LuckTXM(size_t size); + void FreeExecutableMemory_LuckTXM(void* ptr); + void AllocateExecutableMemoryRegion_LuckTXM(); + ptrdiff_t AllocateWritableRegionAndGetDiff_LuckTXM(); + + // LuckNoTXM + void* AllocateExecutableMemory_LuckNoTXM(size_t size); + void FreeExecutableMemory_LuckNoTXM(void* ptr, size_t size); + ptrdiff_t AllocateWritableRegionAndGetDiff_LuckNoTXM(void* rx_ptr, size_t size); + void FreeWritableRegion_LuckNoTXM(void* rx_ptr, size_t size, ptrdiff_t diff); + + // Legacy + void* AllocateExecutableMemory_Legacy(size_t size); + void FreeExecutableMemory_Legacy(void* ptr, size_t size); + void JITPageWriteEnableExecuteDisable_Legacy(void* ptr); + void JITPageWriteDisableExecuteEnable_Legacy(void* ptr); + +#endif // TARGET_OS_IOS + +} // namespace CodeGen diff --git a/src/JITMemoryTracker.cpp b/src/JITMemoryTracker.cpp new file mode 100644 index 00000000..205fc10b --- /dev/null +++ b/src/JITMemoryTracker.cpp @@ -0,0 +1,61 @@ +#include "JITMemoryTracker.h" +#include + +namespace CodeGen +{ + + void JITMemoryTracker::RegisterJITRegion(void* ptr, size_t size) + { + std::lock_guard lock(m_mutex); + m_regions[ptr] = {ptr, size, 0}; + } + + void JITMemoryTracker::UnregisterJITRegion(void* ptr) + { + std::lock_guard lock(m_mutex); + m_regions.erase(ptr); + } + + JITMemoryTracker::JITRegion* JITMemoryTracker::FindRegion(void* ptr) + { + for(auto& pair : m_regions) + { + JITRegion& region = pair.second; + if(ptr >= region.start && + ptr < static_cast(region.start) + region.size) + { + return ®ion; + } + } + return nullptr; + } + + void JITMemoryTracker::JITRegionWriteEnableExecuteDisable(void* ptr) + { + std::lock_guard lock(m_mutex); + JITRegion* region = FindRegion(ptr); + if(region) + { + if(region->nesting_counter == 0) + { + mprotect(region->start, region->size, PROT_READ | PROT_WRITE); + } + region->nesting_counter++; + } + } + + void JITMemoryTracker::JITRegionWriteDisableExecuteEnable(void* ptr) + { + std::lock_guard lock(m_mutex); + JITRegion* region = FindRegion(ptr); + if(region) + { + region->nesting_counter--; + if(region->nesting_counter == 0) + { + mprotect(region->start, region->size, PROT_READ | PROT_EXEC); + } + } + } + +} // namespace CodeGen diff --git a/src/MemoryFunction.cpp b/src/MemoryFunction.cpp index 73a3dabf..9b3b3c7c 100644 --- a/src/MemoryFunction.cpp +++ b/src/MemoryFunction.cpp @@ -13,21 +13,30 @@ #ifdef _WIN32 #define MEMFUNC_USE_WIN32 #elif defined(__APPLE__) - #include "TargetConditionals.h" - #include - - #if TARGET_OS_OSX - #define MEMFUNC_USE_MMAP - #define MEMFUNC_MMAP_ADDITIONAL_FLAGS (MAP_JIT) - #if TARGET_CPU_ARM64 - #define MEMFUNC_MMAP_REQUIRES_JIT_WRITE_PROTECT - #endif - #else - #define MEMFUNC_USE_MACHVM - #if TARGET_OS_IPHONE - #define MEMFUNC_MACHVM_STRICT_PROTECTION - #endif - #endif +#include "TargetConditionals.h" +#include +#include +#include + +// BreakpointJIT pour iOS 26 TXM +#if TARGET_OS_IPHONE +extern "C" { + void* BreakGetJITMapping(void* addr, size_t len) __attribute__((weak_import)); +} +#endif + +#if TARGET_OS_OSX +#define MEMFUNC_USE_MMAP +#define MEMFUNC_MMAP_ADDITIONAL_FLAGS (MAP_JIT) +#if TARGET_CPU_ARM64 +#define MEMFUNC_MMAP_REQUIRES_JIT_WRITE_PROTECT +#endif +#else +#define MEMFUNC_USE_MACHVM +#if TARGET_OS_IPHONE +#define MEMFUNC_MACHVM_STRICT_PROTECTION +#endif +#endif #elif defined(__EMSCRIPTEN__) #include #define MEMFUNC_USE_WASM @@ -89,7 +98,89 @@ CMemoryFunction::CMemoryFunction() CMemoryFunction::CMemoryFunction(const void* code, size_t size) : m_code(nullptr) +, m_size(0) { +#ifdef __APPLE__ + // Détecter mode TXM iOS 26 + const char* hasTxm = getenv("PLAY_HAS_TXM"); + m_ios26TxmMode = (hasTxm != nullptr) && (hasTxm[0] == '1'); + +#if TARGET_OS_IPHONE + if(m_ios26TxmMode && BreakGetJITMapping != nullptr) + { + // Calculer taille alignée sur page + vm_size_t page_size = 0; + host_page_size(mach_task_self(), &page_size); + if(page_size == 0) page_size = 16384; // Fallback ARM64 + size_t allocSize = ((size + page_size - 1) / page_size) * page_size; + + // Obtenir mémoire RX via BreakpointJIT + void* rx = BreakGetJITMapping(nullptr, allocSize); + + if(rx != nullptr) + { + // Créer alias RW temporaire via vm_remap + vm_address_t rwAddress = 0; + vm_prot_t curProt = VM_PROT_NONE; + vm_prot_t maxProt = VM_PROT_NONE; + + kern_return_t kr = vm_remap( + mach_task_self(), + &rwAddress, + allocSize, + 0, + VM_FLAGS_ANYWHERE, + mach_task_self(), + (vm_address_t)rx, + FALSE, + &curProt, + &maxProt, + VM_INHERIT_NONE + ); + + if(kr == KERN_SUCCESS) + { + // Rendre l'alias writable + kern_return_t protResult = vm_protect( + mach_task_self(), + rwAddress, + allocSize, + FALSE, + VM_PROT_READ | VM_PROT_WRITE + ); + + if(protResult == KERN_SUCCESS) + { + // Copier le code JIT dans l'alias RW + memcpy((void*)rwAddress, code, size); + + // Libérer l'alias RW (on garde seulement RX) + vm_deallocate(mach_task_self(), rwAddress, allocSize); + + // Stocker le pointeur RX pour exécution + m_code = rx; + m_rxMemory = rx; + m_size = allocSize; + ClearCache(); + return; // Succès - ne pas exécuter le code legacy + } + + // Échec vm_protect - cleanup + vm_deallocate(mach_task_self(), rwAddress, allocSize); + } + + // Échec vm_remap - la mémoire RX reste allouée par BreakpointJIT + // On ne peut pas la libérer, mais on peut réessayer en mode legacy + } + + // Échec BreakpointJIT - désactiver TXM mode + m_ios26TxmMode = false; + } +#endif // TARGET_OS_IPHONE +#endif // __APPLE__ + + // ========== CODE LEGACY ========== + #if defined(MEMFUNC_USE_WIN32) m_size = size; m_code = framework_aligned_alloc(size, BLOCK_ALIGN); @@ -157,22 +248,37 @@ void CMemoryFunction::ClearCache() void CMemoryFunction::Reset() { - if(m_code != nullptr) - { + if(m_code != nullptr) + { +#ifdef __APPLE__ +#if TARGET_OS_IPHONE + if(m_ios26TxmMode) + { + m_rxMemory = nullptr; + m_rwAliasMemory = nullptr; + m_code = nullptr; + m_size = 0; + return; + } +#endif +#endif + #if defined(MEMFUNC_USE_WIN32) - framework_aligned_free(m_code); + framework_aligned_free(m_code); #elif defined(MEMFUNC_USE_MACHVM) - vm_deallocate(mach_task_self(), reinterpret_cast(m_code), m_size); + vm_deallocate(mach_task_self(), reinterpret_cast(m_code), m_size); #elif defined(MEMFUNC_USE_MMAP) - munmap(m_code, m_size); + munmap(m_code, m_size); #elif defined(MEMFUNC_USE_WASM) - WasmDeleteFunction(reinterpret_cast(m_code)); + WasmDeleteFunction(reinterpret_cast(m_code)); #endif - } - m_code = nullptr; - m_size = 0; + } + + m_code = nullptr; + m_size = 0; + #if defined(MEMFUNC_USE_WASM) - m_wasmModule = emscripten::val(); + m_wasmModule = emscripten::val(); #endif } @@ -212,24 +318,53 @@ size_t CMemoryFunction::GetSize() const void CMemoryFunction::BeginModify() { #if defined(MEMFUNC_USE_MACHVM) && defined(MEMFUNC_MACHVM_STRICT_PROTECTION) - kern_return_t result = vm_protect(mach_task_self(), reinterpret_cast(m_code), m_size, 0, VM_PROT_READ | VM_PROT_WRITE); - assert(result == 0); +#ifdef __APPLE__ + + if(m_ios26TxmMode) + { + return; + } +#endif + kern_return_t result = vm_protect( + mach_task_self(), + reinterpret_cast(m_code), + m_size, + 0, + VM_PROT_READ | VM_PROT_WRITE + ); + assert(result == 0); #elif defined(MEMFUNC_USE_MMAP) && defined(MEMFUNC_MMAP_REQUIRES_JIT_WRITE_PROTECT) - pthread_jit_write_protect_np(false); + pthread_jit_write_protect_np(false); #endif } + void CMemoryFunction::EndModify() { #if defined(MEMFUNC_USE_MACHVM) && defined(MEMFUNC_MACHVM_STRICT_PROTECTION) - kern_return_t result = vm_protect(mach_task_self(), reinterpret_cast(m_code), m_size, 0, VM_PROT_READ | VM_PROT_EXECUTE); - assert(result == 0); +#ifdef __APPLE__ + // En mode TXM, la mémoire est déjà RX + if(m_ios26TxmMode) + { + ClearCache(); + return; + } +#endif + kern_return_t result = vm_protect( + mach_task_self(), + reinterpret_cast(m_code), + m_size, + 0, + VM_PROT_READ | VM_PROT_EXECUTE + ); + assert(result == 0); #elif defined(MEMFUNC_USE_MMAP) && defined(MEMFUNC_MMAP_REQUIRES_JIT_WRITE_PROTECT) - pthread_jit_write_protect_np(true); + pthread_jit_write_protect_np(true); #endif - ClearCache(); + ClearCache(); } + CMemoryFunction CMemoryFunction::CreateInstance() { #if defined(MEMFUNC_USE_WASM) diff --git a/src/MemoryUtil_iOS.cpp b/src/MemoryUtil_iOS.cpp new file mode 100644 index 00000000..50aae60c --- /dev/null +++ b/src/MemoryUtil_iOS.cpp @@ -0,0 +1,78 @@ +#include "MemoryUtil.h" + +namespace CodeGen +{ + + // Variable globale stockant le type JIT actif + static JitType g_jit_type = JitType::LuckTXM; + + void SetJitType(JitType type) + { + g_jit_type = type; + } + + void* AllocateExecutableMemory(size_t size) + { + if(g_jit_type == JitType::LuckTXM) + return AllocateExecutableMemory_LuckTXM(size); + else if(g_jit_type == JitType::LuckNoTXM) + return AllocateExecutableMemory_LuckNoTXM(size); + else if(g_jit_type == JitType::Legacy) + return AllocateExecutableMemory_Legacy(size); + + return nullptr; + } + + void FreeExecutableMemory(void* ptr, size_t size) + { + if(g_jit_type == JitType::LuckTXM) + FreeExecutableMemory_LuckTXM(ptr); + else if(g_jit_type == JitType::LuckNoTXM) + FreeExecutableMemory_LuckNoTXM(ptr, size); + else if(g_jit_type == JitType::Legacy) + FreeExecutableMemory_Legacy(ptr, size); + } + + void AllocateExecutableMemoryRegion() + { + if(g_jit_type == JitType::LuckTXM) + { + AllocateExecutableMemoryRegion_LuckTXM(); + } + } + + ptrdiff_t AllocateWritableRegionAndGetDiff(void* rx_ptr, size_t size) + { + if(g_jit_type == JitType::LuckTXM) + return AllocateWritableRegionAndGetDiff_LuckTXM(); + else if(g_jit_type == JitType::LuckNoTXM) + return AllocateWritableRegionAndGetDiff_LuckNoTXM(rx_ptr, size); + + return 0; + } + + void FreeWritableRegion(void* rx_ptr, size_t size, ptrdiff_t diff) + { + if(g_jit_type == JitType::LuckNoTXM) + { + FreeWritableRegion_LuckNoTXM(rx_ptr, size, diff); + } + } + + void JITPageWriteEnableExecuteDisable(void* ptr) + { + if(g_jit_type == JitType::Legacy) + { + JITPageWriteEnableExecuteDisable_Legacy(ptr); + } + } + + void JITPageWriteDisableExecuteEnable(void* ptr) + { + if(g_jit_type == JitType::Legacy) + { + JITPageWriteDisableExecuteEnable_Legacy(ptr); + } + } + +} // namespace CodeGen diff --git a/src/MemoryUtil_iOS_Legacy.cpp b/src/MemoryUtil_iOS_Legacy.cpp new file mode 100644 index 00000000..9274887b --- /dev/null +++ b/src/MemoryUtil_iOS_Legacy.cpp @@ -0,0 +1,43 @@ +#include "MemoryUtil.h" +#include "JITMemoryTracker.h" + +#include +#include + +namespace CodeGen +{ + + static JITMemoryTracker g_jit_memory_tracker; + + void* AllocateExecutableMemory_Legacy(size_t size) + { + void* ptr = mmap(nullptr, size, PROT_READ | PROT_EXEC, + MAP_ANON | MAP_PRIVATE, -1, 0); + + if(ptr == MAP_FAILED) ptr = nullptr; + if(ptr == nullptr) return nullptr; + + g_jit_memory_tracker.RegisterJITRegion(ptr, size); + return ptr; + } + + void FreeExecutableMemory_Legacy(void* ptr, size_t size) + { + if(ptr) + { + munmap(ptr, size); + g_jit_memory_tracker.UnregisterJITRegion(ptr); + } + } + + void JITPageWriteEnableExecuteDisable_Legacy(void* ptr) + { + g_jit_memory_tracker.JITRegionWriteEnableExecuteDisable(ptr); + } + + void JITPageWriteDisableExecuteEnable_Legacy(void* ptr) + { + g_jit_memory_tracker.JITRegionWriteDisableExecuteEnable(ptr); + } + +} // namespace CodeGen diff --git a/src/MemoryUtil_iOS_LuckNoTXM.cpp b/src/MemoryUtil_iOS_LuckNoTXM.cpp new file mode 100644 index 00000000..c07f5d38 --- /dev/null +++ b/src/MemoryUtil_iOS_LuckNoTXM.cpp @@ -0,0 +1,61 @@ +#include "MemoryUtil.h" + +#include +#include +#include +#include + +namespace CodeGen +{ + + void* AllocateExecutableMemory_LuckNoTXM(size_t size) + { + // Allouer région RX + uint8_t* rx_ptr = static_cast( + mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0)); + + if(!rx_ptr || rx_ptr == MAP_FAILED) return nullptr; + return rx_ptr; + } + + void FreeExecutableMemory_LuckNoTXM(void* ptr, size_t size) + { + if(ptr) + { + munmap(ptr, size); + } + } + + ptrdiff_t AllocateWritableRegionAndGetDiff_LuckNoTXM(void* rx_ptr, size_t size) + { + // Créer miroir RW de rx_ptr + vm_address_t rw_region = 0; + vm_address_t target = reinterpret_cast(rx_ptr); + vm_prot_t cur_protection = 0; + vm_prot_t max_protection = 0; + + kern_return_t retval = vm_remap( + mach_task_self(), &rw_region, size, 0, true, + mach_task_self(), target, false, + &cur_protection, &max_protection, VM_INHERIT_DEFAULT); + + if(retval != KERN_SUCCESS) return 0; + + uint8_t* rw_ptr = reinterpret_cast(rw_region); + + if(mprotect(rw_ptr, size, PROT_READ | PROT_WRITE) != 0) + { + vm_deallocate(mach_task_self(), rw_region, size); + return 0; + } + + return rw_ptr - static_cast(rx_ptr); + } + + void FreeWritableRegion_LuckNoTXM(void* rx_ptr, size_t size, ptrdiff_t diff) + { + uint8_t* rw_ptr = static_cast(rx_ptr) + diff; + vm_deallocate(mach_task_self(), reinterpret_cast(rw_ptr), size); + } + +} // namespace CodeGen diff --git a/src/MemoryUtil_iOS_LuckTXM.cpp b/src/MemoryUtil_iOS_LuckTXM.cpp new file mode 100644 index 00000000..42d4a7ba --- /dev/null +++ b/src/MemoryUtil_iOS_LuckTXM.cpp @@ -0,0 +1,130 @@ +#include "MemoryUtil.h" + +#include +#include +#include +#include +#include + +// 512 MiB de région exécutable pré-allouée +constexpr size_t EXECUTABLE_REGION_SIZE = 536870912; + +static uint8_t* g_rx_region = nullptr; // Pointeur région RX (exécutable) +static ptrdiff_t g_rw_region_diff = 0; // Décalage RW - RX + +namespace CodeGen +{ + + void AllocateExecutableMemoryRegion_LuckTXM() + { + if(g_rx_region) return; // Déjà alloué + + const size_t size = EXECUTABLE_REGION_SIZE; + + // 1. Allouer région RX (read-execute) + uint8_t* rx_ptr = static_cast( + mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0)); + + if(!rx_ptr || rx_ptr == MAP_FAILED) + { + // TODO: Log error + return; + } + + // 2. Signal TXM avec breakpoint spécial (ARM64) + // Cette instruction informe le Trusted Execution Monitor de la région JIT + asm("mov x0, %0\n" + "mov x1, %1\n" + "brk #0x69" ::"r"(rx_ptr), + "r"(size) + : "x0", "x1"); + + // 3. Créer miroir RW de la même mémoire physique + vm_address_t rw_region = 0; + vm_address_t target = reinterpret_cast(rx_ptr); + vm_prot_t cur_protection = 0; + vm_prot_t max_protection = 0; + + kern_return_t retval = vm_remap( + mach_task_self(), // Task cible + &rw_region, // Adresse de sortie (miroir) + size, // Taille + 0, // Mask + true, // Anywhere (laisse kernel choisir l'adresse) + mach_task_self(), // Task source + target, // Adresse source (rx_ptr) + false, // Copy (false = partage mémoire physique) + &cur_protection, + &max_protection, + VM_INHERIT_DEFAULT); + + if(retval != KERN_SUCCESS) + { + munmap(rx_ptr, size); + return; + } + + uint8_t* rw_ptr = reinterpret_cast(rw_region); + + // 4. Forcer permissions RW sur le miroir + if(mprotect(rw_ptr, size, PROT_READ | PROT_WRITE) != 0) + { + munmap(rx_ptr, size); + vm_deallocate(mach_task_self(), rw_region, size); + return; + } + + // 5. Initialiser lwmem pour gérer allocation dynamique + lwmem_region_t regions[] = { + {(void*)rw_ptr, size}, + {NULL, 0}}; + + if(lwmem_assignmem(regions) == 0) + { + munmap(rx_ptr, size); + vm_deallocate(mach_task_self(), rw_region, size); + return; + } + + g_rx_region = rx_ptr; + g_rw_region_diff = rw_ptr - rx_ptr; + } + + ptrdiff_t AllocateWritableRegionAndGetDiff_LuckTXM() + { + return g_rw_region_diff; + } + + void* AllocateExecutableMemory_LuckTXM(size_t size) + { + if(g_rx_region == nullptr) return nullptr; + + const size_t pagesize = sysconf(_SC_PAGESIZE); + + // Allouer via lwmem avec espace pour alignement + métadonnées + void* raw = lwmem_malloc(size + pagesize - 1 + sizeof(void*)); + + if(!raw) return nullptr; + + // Aligner sur page boundary + uintptr_t raw_addr = (uintptr_t)raw + sizeof(void*); + uintptr_t aligned = (raw_addr + pagesize - 1) & ~(pagesize - 1); + + // Stocker pointeur raw pour lwmem_free() + ((void**)aligned)[-1] = raw; + + // Retourner pointeur RX (exécutable) au lieu du RW + return (uint8_t*)aligned - g_rw_region_diff; + } + + void FreeExecutableMemory_LuckTXM(void* ptr) + { + if(!ptr) return; + + // Convertir ptr RX en RW, récupérer raw, libérer + uint8_t* rw_ptr = static_cast(ptr) + g_rw_region_diff; + void* raw = ((void**)rw_ptr)[-1]; + lwmem_free(raw); + } + +} // namespace CodeGen