Apple Silicon平台mmap可执行页触发EXC_BAD_ACCESS(code=2)的解决方法
解决Apple Silicon上JIT内存执行EXC_BAD_ACCESS的问题
你的代码存在两个核心问题:指令写入错误和JIT内存权限/签名流程不规范,以下是具体解决步骤:
1. 修正代码中的指令写入错误
你当前的代码错误地将instr变量的地址写入JIT内存,而非预期的add x0, x0, x0指令:
memcpy(instr, &instr, 4); // 错误:写入instr的地址,不是目标指令的机器码
应改为:
memcpy(instr, &instr1, 4); // 正确:写入add指令的机器码
同时注意:你之前把instr指向mmap区域的+4084位置,这超出了分配的1024字节内存范围(合法范围是[instr, instr+1023]),直接使用mmap返回的起始地址即可。
2. 规范JIT内存的权限切换流程
Apple Silicon要求JIT内存遵循**W^X(写或执行二选一)**原则,正确流程如下:
- 分配JIT内存时先申请读写权限
- 关闭JIT写保护以写入指令
- 写入完成后重新开启写保护,再将内存权限修改为只读+执行
修正后的内存操作代码:
// 分配JIT内存,初始权限为RW instr = (uint8_t*)mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON|MAP_JIT, 0, 0); if(instr == MAP_FAILED){ perror("mmap"); exit(-1); } // 关闭JIT写保护,允许写入指令 pthread_jit_write_protect_np(0); memcpy(instr, &instr1, 4); memcpy(instr+4, &instr2, 4); // 开启JIT写保护,切换为执行状态 pthread_jit_write_protect_np(1); // 修改内存权限为RX(强化W^X原则) mprotect(instr, 1024, PROT_READ|PROT_EXEC);
3. 添加JIT权限签名
Apple Silicon上的程序要使用JIT,必须具备com.apple.security.cs.allow-jit权限,操作步骤如下:
- 创建
entitlements.plist文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.cs.allow-jit</key> <true/> </dict> </plist>
- 用
codesign重新签名可执行文件:
codesign -s - --entitlements entitlements.plist --force your_executable_name
如果是Xcode项目,可直接在「Signing & Capabilities」中添加「Hardened Runtime」,并勾选「Allow JIT」选项。
修正后的完整代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <pthread.h> #include <stdint.h> int main() { uint8_t *instr; uint32_t instr1 = 0x8b000000; // add x0, x0, x0 uint32_t instr2 = 0xd65f03c0; // ret if(pthread_jit_write_protect_supported_np() == 1) printf("jit write supported\n"); else printf("jit write not supported\n"); // 分配JIT内存,初始权限为RW instr = (uint8_t*)mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON|MAP_JIT, 0, 0); if(instr == MAP_FAILED){ perror("mmap"); exit(-1); } printf("instr addr : %lx\n", (uintptr_t)instr); // 关闭写保护,写入指令 pthread_jit_write_protect_np(0); memcpy(instr, &instr1, 4); memcpy(instr+4, &instr2, 4); // 开启写保护,切换为执行状态 pthread_jit_write_protect_np(1); // 修改内存权限为RX mprotect(instr, 1024, PROT_READ|PROT_EXEC); printf("instr1 is %x\n", *(uint32_t *)instr); printf("instr2 is %x\n", *(uint32_t *)(instr+4)); asm volatile( "eor x0, x0, x0\n" "eor x1, x1, x1\n" "eor x2, x2, x2\n" "eor x3, x3, x3\n" ); asm volatile( "ldr x1, %[ptr]\n" "br x1\n" ::[ptr]"m"(instr) ); // 释放内存(可选) munmap(instr, 1024); return 0; }
编译时需链接pthread库:
clang -o jit_test jit_test.c -lpthread
签名后运行即可正常执行JIT代码。
内容的提问来源于stack exchange,提问作者purple_potato
相关产品推荐
相关产品推荐

