Affix

 view release on metacpan or  search on metacpan

infix/src/jit/executor.c  view on Meta::CPAN

 *     which is used to protect the `infix_reverse_t` context from runtime memory
 *     corruption. It also implements "guard pages" on freed memory to immediately
 *     catch use-after-free bugs.
 *
 * 3.  **Universal Dispatch:** It contains the `infix_internal_dispatch_callback_fn_impl`,
 *     the universal C entry point that is the final target of all reverse trampoline
 *     stubs. This function is the bridge between the low-level JIT code and the
 *     high-level user-provided C handlers.
 */
#include "common/infix_internals.h"
#include "common/utility.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Platform-Specific Includes
#if defined(INFIX_OS_WINDOWS)
#include <windows.h>
#else
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#if defined(INFIX_OS_MACOS)
#include <dlfcn.h>
#include <libkern/OSCacheControl.h>
#endif
// Polyfills for mmap flags for maximum POSIX compatibility.
#if defined(INFIX_ENV_POSIX) && !defined(INFIX_OS_WINDOWS)
#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS)
#define MAP_ANON MAP_ANONYMOUS
#endif
static pthread_mutex_t g_dwarf_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif

#if defined(INFIX_OS_WINDOWS) && defined(INFIX_ARCH_X64)
// SEH Unwind Info Opcodes and Structures for JIT code on Windows x64.
// These are defined in winnt.h but we redefine them here for clarity and to ensure availability.
#define UWOP_PUSH_NONVOL 0
#define UWOP_ALLOC_LARGE 1
#define UWOP_ALLOC_SMALL 2
#define UWOP_SET_FPREG 3

#pragma pack(push, 1)
typedef struct _UNWIND_CODE {
    uint8_t CodeOffset;
    uint8_t UnwindOp : 4;
    uint8_t OpInfo : 4;
} UNWIND_CODE;

typedef struct _UNWIND_INFO {
    uint8_t Version : 3;
    uint8_t Flags : 5;
    uint8_t SizeOfPrologue;
    uint8_t CountOfCodes;
    uint8_t FrameRegister : 4;
    uint8_t FrameOffset : 4;
    UNWIND_CODE UnwindCode[1];  // Variable length array
} UNWIND_INFO;

// We reserve 512 bytes at the end of every JIT block for SEH metadata.
#define INFIX_SEH_METADATA_SIZE 256
#elif defined(INFIX_OS_WINDOWS) && defined(INFIX_ARCH_AARCH64)
#pragma pack(push, 1)
typedef struct _UNWIND_INFO_ARM64 {
    uint32_t FunctionLength : 18;
    uint32_t Version : 2;
    uint32_t X : 1;
    uint32_t E : 1;
    uint32_t EpilogueCount : 5;
    uint32_t CodeWords : 5;
} UNWIND_INFO_ARM64;
#pragma pack(pop)
#define INFIX_SEH_METADATA_SIZE 256
#else
#define INFIX_SEH_METADATA_SIZE 0
#endif

// macOS JIT Security Hardening Logic
#if defined(INFIX_OS_MACOS)
/**
 * @internal
 * @brief macOS-specific function pointers and types for checking JIT entitlements.
 *
 * @details To support hardened runtimes on Apple platforms (especially Apple Silicon),
 * `infix` must use special APIs like `MAP_JIT` and `pthread_jit_write_protect_np`.
 * However, these are only effective if the host application has been granted the
 * `com.apple.security.cs.allow-jit` entitlement.
 *
 * This logic performs a runtime check for these APIs and the entitlement, gracefully
 * falling back to the legacy (but less secure) `mprotect` method if they are not
 * available. This provides maximum security for production apps while maintaining
 * maximum convenience for developers who may not have codesigned their test executables.
 */
typedef const struct __CFString * CFStringRef;
typedef const void * CFTypeRef;
typedef struct __SecTask * SecTaskRef;
typedef struct __CFError * CFErrorRef;
#define kCFStringEncodingUTF8 0x08000100
// A struct to hold dynamically loaded function pointers from macOS frameworks.
static struct {
    void (*CFRelease)(CFTypeRef);
    bool (*CFBooleanGetValue)(CFTypeRef boolean);
    CFStringRef (*CFStringCreateWithCString)(CFTypeRef allocator, const char * cStr, uint32_t encoding);
    CFTypeRef kCFAllocatorDefault;
    SecTaskRef (*SecTaskCreateFromSelf)(CFTypeRef allocator);
    CFTypeRef (*SecTaskCopyValueForEntitlement)(SecTaskRef task, CFStringRef entitlement, CFErrorRef * error);
    void (*pthread_jit_write_protect_np)(int enabled);
    void (*sys_icache_invalidate)(void * start, size_t len);
} g_macos_apis;
/**
 * @internal
 * @brief One-time initialization to dynamically load macOS framework functions.
 * @details Uses `dlopen` and `dlsym` to find the necessary CoreFoundation and Security
 * framework functions at runtime. This avoids a hard link-time dependency,
 * making the library more portable and resilient if these frameworks change.
 */
static void initialize_macos_apis(void) {

infix/src/jit/executor.c  view on Meta::CPAN

    else {
        // push rbp; mov rbp, rsp; push r12; push r13; push r14; push r15
        *p++ = 0x41;  // loc +1 (after push rbp)
        *p++ = 0x0e;
        *p++ = 16;  // def_cfa_offset 16
        *p++ = 0x86;
        *p++ = 0x02;  // offset rbp (6), 2
        *p++ = 0x43;  // loc +3 (after mov rbp, rsp)
        *p++ = 0x0d;
        *p++ = 0x06;  // def_cfa_register rbp (6)
        *p++ = 0x42;  // loc +2 (after push r12)
        *p++ = 0x8c;
        *p++ = 0x03;  // offset r12, 3
        *p++ = 0x42;  // loc +2 (after push r13)
        *p++ = 0x8d;
        *p++ = 0x04;  // offset r13, 4
        *p++ = 0x42;  // loc +2 (after push r14)
        *p++ = 0x8e;
        *p++ = 0x05;  // offset r14, 5
        *p++ = 0x42;  // loc +2 (after push r15)
        *p++ = 0x8f;
        *p++ = 0x06;  // offset r15, 6
    }

    while ((size_t)(p - eh) < (cie_size + fde_size))
        *p++ = 0;
    *(uint32_t *)p = 0;  // Terminator

    extern void __register_frame(void *);
    pthread_mutex_lock(&g_dwarf_mutex);
    __register_frame(eh);
    pthread_mutex_unlock(&g_dwarf_mutex);

    exec->eh_frame_ptr = eh;
    INFIX_DEBUG_PRINTF("Registered DWARF .eh_frame at %p for JIT code at %p", (void *)eh, exec->rx_ptr);
}
#elif defined(INFIX_OS_LINUX) && defined(INFIX_ARCH_AARCH64)
/**
 * @internal
 * @brief Registers DWARF unwind information for a JIT-compiled block on ARM64 Linux.
 * @details This allows the C++ exception unwinder to correctly walk through
 *          JIT-compiled frames. We manually construct a Common Information Entry (CIE)
 *          and a Frame Description Entry (FDE) that match the stack behavior
 *          of our ARM64 trampolines.
 */
static void _infix_register_eh_frame_arm64(infix_executable_t * exec, infix_executable_category_t category) {
    // Simplified .eh_frame layout: [ CIE | FDE | Terminator ]
    const size_t cie_size = 32;
    const size_t fde_size = 64;
    const size_t total_size = cie_size + fde_size + 4;  // +4 for null terminator

    uint8_t * eh = infix_malloc(total_size);
    if (!eh)
        return;
    infix_memset(eh, 0, total_size);

    uint8_t * p = eh;

    // CIE (Common Information Entry)
    *(uint32_t *)p = (uint32_t)(cie_size - 4);
    p += 4;  // length
    *(uint32_t *)p = 0;
    p += 4;       // cie_id (0)
    *p++ = 1;     // version
    *p++ = '\0';  // augmentation string ("")
    *p++ = 4;     // code_alignment_factor (AArch64 instructions are 4 bytes)
    *p++ = 0x78;  // data_alignment_factor (-8 in SLEB128)
    *p++ = 30;    // return_address_register (30 = lr on arm64)

    // CIE Instructions: Initial state
    // DW_CFA_def_cfa sp, 0
    *p++ = 0x0c;
    *p++ = 31;
    *p++ = 0;
    while ((size_t)(p - eh) < cie_size)
        *p++ = 0;

    // FDE (Frame Description Entry)
    uint8_t * fde_start = eh + cie_size;
    p = fde_start;
    *(uint32_t *)p = (uint32_t)(fde_size - 4);
    p += 4;  // length
    *(uint32_t *)p = (uint32_t)(p - eh);
    p += 4;  // cie_pointer (back-offset)

    *(void **)p = exec->rx_ptr;
    p += 8;  // pc_begin (absolute)
    *(uint64_t *)p = (uint64_t)exec->size;
    p += 8;    // pc_range (absolute)
    *p++ = 0;  // aug data len

    // Instructions: match our trampoline prologue
    if (category == INFIX_EXECUTABLE_REVERSE) {
        // stp x29, x30, [sp, #-16]!; mov x29, sp
        *p++ = 0x41;  // loc +1 (4 bytes, after stp)
        *p++ = 0x0e;
        *p++ = 16;  // def_cfa_offset 16
        *p++ = 0x9d;
        *p++ = 2;  // offset r29 (x29), 2 (CFA - 16)
        *p++ = 0x9e;
        *p++ = 1;     // offset r30 (x30/lr), 1 (CFA - 8)
        *p++ = 0x41;  // loc +1 (4 bytes, after mov)
        *p++ = 0x0d;
        *p++ = 29;  // def_cfa_register r29
    }
    else {
        // stp x29, x30, [sp, #-16]!; stp x19, x20, ...; stp x21, x22, ...; mov x29, sp
        *p++ = 0x41;  // after stp x29, x30
        *p++ = 0x0e;
        *p++ = 16;
        *p++ = 0x9d;
        *p++ = 2;  // x29 at CFA - 16
        *p++ = 0x9e;
        *p++ = 1;     // x30 at CFA - 8
        *p++ = 0x41;  // after stp x19, x20
        *p++ = 0x0e;
        *p++ = 32;
        *p++ = 0x93;
        *p++ = 4;  // x19 at CFA - 32
        *p++ = 0x94;
        *p++ = 3;     // x20 at CFA - 24
        *p++ = 0x41;  // after stp x21, x22
        *p++ = 0x0e;
        *p++ = 48;
        *p++ = 0x95;
        *p++ = 6;  // x21 at CFA - 48
        *p++ = 0x96;
        *p++ = 5;     // x22 at CFA - 40
        *p++ = 0x41;  // after mov x29, sp
        *p++ = 0x0d;
        *p++ = 29;  // def_cfa_register x29 (offset remains 48)
    }

    while ((size_t)(p - eh) < (cie_size + fde_size))
        *p++ = 0;
    *(uint32_t *)p = 0;  // Terminator

    // Register the frame with the runtime.
    extern void __register_frame(void *);
    pthread_mutex_lock(&g_dwarf_mutex);
    __register_frame(eh);
    pthread_mutex_unlock(&g_dwarf_mutex);



( run in 0.598 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )