kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

macos.c (7264B)


      1 /* macOS-specific env bits: mach_vm_remap dual-mapped exec memory,
      2  * sys_icache_invalidate for the dbg flush path, st_mtimespec mtime,
      3  * dlsym leading-underscore handling, host_target OS=MACOS/OBJ=MACHO.
      4  *
      5  * Compiled only on Darwin; the Makefile selects this file via
      6  * HOST_UNAME=Darwin. No #ifdef inside.
      7  */
      8 
      9 #include <dlfcn.h>
     10 #include <libkern/OSCacheControl.h>
     11 #include <mach-o/dyld.h>
     12 #include <mach/mach.h>
     13 #include <mach/mach_vm.h>
     14 #include <mach/vm_map.h>
     15 #include <stdint.h>
     16 #include <stdlib.h>
     17 #include <string.h>
     18 #include <sys/mman.h>
     19 #include <sys/stat.h>
     20 
     21 #include "env_posix.h"
     22 
     23 /* ---------------- dual-mapped exec memory ---------------- */
     24 /* mach_vm_remap creates a second VA pointing at the same physical memory.
     25  * The write alias is RW; the runtime alias is left at PROT_READ until
     26  * protect() flips it to PROT_READ|PROT_EXEC. No MAP_JIT, no per-thread
     27  * mode bit, no entitlement coupling for unsigned dev binaries (signed
     28  * hardened-runtime binaries need com.apple.security.cs.allow-unsigned-
     29  * executable-memory). */
     30 KitStatus os_execmem_reserve_exec(size_t size, KitExecMemRegion* out) {
     31   void* w;
     32   mach_vm_address_t r_addr = 0;
     33   vm_prot_t cur = 0, max = 0;
     34   kern_return_t kr;
     35   ExecMemToken* tok;
     36 
     37   w = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
     38   if (w == MAP_FAILED) return KIT_NOMEM;
     39 
     40   /* copy=FALSE: the new VA shares the same physical pages.
     41    * VM_INHERIT_NONE: child processes don't observe the executable alias. */
     42   kr = mach_vm_remap(mach_task_self(), &r_addr, (mach_vm_size_t)size,
     43                      /*mask=*/0, VM_FLAGS_ANYWHERE, mach_task_self(),
     44                      (mach_vm_address_t)(uintptr_t)w,
     45                      /*copy=*/FALSE, &cur, &max, VM_INHERIT_NONE);
     46   if (kr != KERN_SUCCESS) {
     47     munmap(w, size);
     48     return KIT_ERR;
     49   }
     50 
     51   /* Drop the runtime alias to PROT_READ until protect() flips it. No
     52    * window of writable+executable: write alias has W (no X), runtime
     53    * alias has R only (no W, no X yet). */
     54   if (mprotect((void*)(uintptr_t)r_addr, size, PROT_READ) != 0) {
     55     munmap((void*)(uintptr_t)r_addr, size);
     56     munmap(w, size);
     57     return KIT_ERR;
     58   }
     59 
     60   tok = (ExecMemToken*)malloc(sizeof(*tok));
     61   if (!tok) {
     62     munmap((void*)(uintptr_t)r_addr, size);
     63     munmap(w, size);
     64     return KIT_NOMEM;
     65   }
     66   tok->write_addr = w;
     67   tok->runtime_addr = (void*)(uintptr_t)r_addr;
     68   tok->size = size;
     69 
     70   exec_dual_register(w, (void*)(uintptr_t)r_addr, size);
     71 
     72   out->write = w;
     73   out->runtime = (void*)(uintptr_t)r_addr;
     74   out->size = size;
     75   out->token = tok;
     76   return KIT_OK;
     77 }
     78 
     79 /* ---------------- dbg W^X dance ---------------- */
     80 /* Dual-mapped reservation: the write alias is a separate VA already RW.
     81  * Translate via the registry; no protect flip required, so end is no-op. */
     82 KitStatus os_dbg_code_write_begin(void* user, void* runtime_addr, size_t n,
     83                                   void** write_out) {
     84   (void)user;
     85   if (!runtime_addr || !n || !write_out) return KIT_INVALID;
     86   return exec_dual_lookup(runtime_addr, n, write_out) == 0 ? KIT_OK : KIT_ERR;
     87 }
     88 
     89 void os_dbg_code_write_end(void* user, void* runtime_addr, size_t n) {
     90   (void)user;
     91   (void)runtime_addr;
     92   (void)n;
     93 }
     94 
     95 /* sys_icache_invalidate is the documented Apple primitive on aarch64 and
     96  * is a no-op on x86_64 (correctly modeling that x86 fetches are coherent
     97  * with stores). Use it across arches for symmetry. */
     98 void os_dbg_flush_icache(void* user, void* runtime_addr, size_t n) {
     99   (void)user;
    100   sys_icache_invalidate(runtime_addr, n);
    101 }
    102 
    103 /* ---------------- st_mtim/mtimespec ---------------- */
    104 
    105 int os_stat_mtime_ns(const struct stat* sb, int64_t* out) {
    106   *out = (int64_t)sb->st_mtimespec.tv_sec * 1000000000LL +
    107          (int64_t)sb->st_mtimespec.tv_nsec;
    108   return 0;
    109 }
    110 
    111 /* ---------------- dlsym name mangling ---------------- */
    112 /* On Mach-O the linker hands us C names with a leading underscore
    113  * (obj_format_c_mangle), but dlsym(RTLD_DEFAULT) expects the source-level
    114  * name. Try the stripped form first to avoid a wasted dlsym call per
    115  * libc symbol. */
    116 void* os_dlsym(const char* name) {
    117   void* p;
    118   if (name[0] == '_' && name[1] != '\0') {
    119     p = dlsym(RTLD_DEFAULT, name + 1);
    120     if (p) return p;
    121   }
    122   return dlsym(RTLD_DEFAULT, name);
    123 }
    124 
    125 /* ---------------- host_target os/obj ---------------- */
    126 
    127 void os_host_target_fill(KitTargetSpec* t) {
    128   t->os = KIT_OS_MACOS;
    129   t->obj = KIT_OBJ_MACHO;
    130 }
    131 
    132 /* ---------------- self executable path ---------------- */
    133 /* _NSGetExecutablePath yields the path as invoked (may contain symlinks or
    134  * `..`); realpath canonicalizes it to a stable absolute path so installed
    135  * links keep resolving regardless of how kit was launched. */
    136 int driver_self_exe_path(DriverEnv* env, char** out, size_t* out_size) {
    137   char stackbuf[1024];
    138   char* nsbuf = stackbuf;
    139   uint32_t cap = (uint32_t)sizeof stackbuf;
    140   size_t heap_cap = 0;
    141   char* resolved;
    142   size_t size;
    143 
    144   if (!env || !out || !out_size) return 1;
    145   if (_NSGetExecutablePath(nsbuf, &cap) != 0) {
    146     /* cap was set to the required size; allocate and retry once. */
    147     heap_cap = cap;
    148     nsbuf = (char*)driver_alloc(env, heap_cap);
    149     if (!nsbuf) return 1;
    150     if (_NSGetExecutablePath(nsbuf, &cap) != 0) {
    151       driver_free(env, nsbuf, heap_cap);
    152       return 1;
    153     }
    154   }
    155   resolved = realpath(nsbuf, NULL); /* malloc'd; caller frees with free() */
    156   if (heap_cap) driver_free(env, nsbuf, heap_cap);
    157   if (!resolved) return 1;
    158 
    159   size = driver_strlen(resolved) + 1u;
    160   *out = (char*)driver_alloc(env, size);
    161   if (!*out) {
    162     free(resolved);
    163     return 1;
    164   }
    165   driver_memcpy(*out, resolved, size);
    166   *out_size = size;
    167   free(resolved);
    168   return 0;
    169 }
    170 
    171 /* ---------------- default hosted dirs probe ---------------- */
    172 /* With no --sysroot or KIT_SYSROOT, locate the macOS SDK by stat'ing the
    173  * canonical Command Line Tools and Xcode.app SDK roots -- the same locations
    174  * `xcrun --show-sdk-path` reports, without a subprocess. Pure on-disk
    175  * discovery: the env-var override is KIT_SYSROOT, handled uniformly in
    176  * driver/lib/hosted.c. Only the macOS host target is probed; cross-targets get
    177  * nothing. The <sdk>/usr/{include,lib} mapping mirrors
    178  * hosted_dirs_from_sysroot's macOS case in driver/lib/hosted.c (layering
    179  * forbids calling into it from the env TU). */
    180 int driver_default_hosted_dirs(DriverEnv* env, KitTargetSpec target,
    181                                DriverHostedDirs* out) {
    182   static const char* const candidates[] = {
    183       "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
    184       "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/"
    185       "Developer/SDKs/MacOSX.sdk",
    186   };
    187   const char* sdk = NULL;
    188   size_t i;
    189   (void)env;
    190   if (target.os != KIT_OS_MACOS) return 1;
    191   for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) {
    192     if (driver_path_exists(candidates[i])) {
    193       sdk = candidates[i];
    194       break;
    195     }
    196   }
    197   if (!sdk) return 1;
    198   /* The SDK is a single sysroot-shaped tree, so record it as the root (a static
    199    * literal -- stable for the process). -print-sysroot reports it. */
    200   out->root = sdk;
    201   if (driver_hosted_dirs_add_inc_join(out, sdk, "usr/include") != 0 ||
    202       driver_hosted_dirs_add_lib_join(out, sdk, "usr/lib") != 0)
    203     return 1;
    204   return out->nlibdirs ? 0 : 1;
    205 }