__builtin___clear_cache

张开发
2026/4/20 16:43:39 15 分钟阅读
__builtin___clear_cache
__builtin___clear_cache是 GCC/Clang 提供的内置函数用于刷新指定内存范围的指令缓存I-cache确保修改后的可执行代码能被 CPU 正确执行主要用于自修改代码、JIT、运行时代码生成、Hook 等场景硬件缓存现代 CPU 是哈佛架构I-cache指令缓存CPU 取指令用D-cache数据缓存CPU 读写数据用两者完全独立、硬件不自动同步。典型场景JIT / 热补丁你用数据写操作生成机器码 → 只更新D-cacheCPU 去取指令执行→ 读的是I-cache 旧内容结果执行错误、崩溃必须手动刷 D-cache→ 把数据写回内存刷 I-cache→ 让旧指令失效让 CPU 重新从内存取新指令指令缓存 I‑Cache只存机器指令哈佛架构与 D‑Cache 完全分离典型操作Invalidate让 CPU 重新取指对应刷新操作__builtin___clear_cache数据缓存 D‑Cache存程序数据、堆栈、全局变量最常需要手动操作给 DMA / 设备看数据 → Clean设备已写内存CPU 要读 → InvalidateARM64 指令dc cvac,dc civac,dc ivac统一缓存 Unified CacheL2 / L3既存指令也存数据软件操作方式一般和 D‑Cache 一致通常不需要单独区分 I/D微架构内部缓存这些也是硬件缓存但用户 / 内核基本不能精确刷新Micro-op Cache (μop cache)缓存解码后的微指令Loop Buffer循环指令缓存Return Stack Buffer (RSB)返回地址预测缓存Branch Target Buffer (BTB)分支目标地址缓存Instruction Prefetch Buffer指令预取缓冲区它们一般在异常 / 中断执行 ISB 指令分支预测失败时自动刷新没有单独指令只刷 BTB这种操作。3 个核心操作所有架构通用Clean D-cache把数据缓存回写到内存Invalidate I-cache把指令缓存标记为无效强制重新读取屏障指令确保刷新完成再执行后续代码函数原型void __builtin___clear_cache(void *begin, void *end);参数begin内存范围起始地址含end内存范围结束地址不含返回值无说明早期文档 / 部分编译器曾用char*现代 GCC/Clang 统一为void*。核心作用与原理现代 CPU尤其 ARM、MIPS、RISC-V采用哈佛架构指令缓存I-cache与数据缓存D-cache分离且非自动一致。你通过数据通路写内存修改代码 → 仅更新 D-cacheCPU 取指令时仍从 I-cache 读旧内容 → 执行错误必须显式同步 D-cache 到内存、并使 I-cache 对应行失效底层行为若目标架构不需要缓存刷新如 x86/x86_64统一缓存、硬件自动一致空操作NOP否则生成内联缓存指令或 调用libgcc的__clear_cache函数最终可能触发系统调用如 Linuxcacheflush跨架构行为差异架构行为底层实现x86/x86_64NOP硬件自动维护 I/D 一致性ARM/ARM64必须调用执行DC CIVAC清 回写IC IVAU指令缓存失效或系统调用RISC-V必须调用FENCE.I指令或系统调用MIPS必须调用cache指令或系统调用典型使用场景JIT 编译运行时生成机器码后必须调用以确保新代码被执行代码 Hook / 热补丁修改.text段或可执行内存后刷新嵌套函数 /trampoline编译器自动生成调用如 GCC 嵌套函数内核 / 驱动修改可执行内存后的缓存同步示例代码#include stdint.h #include sys/mman.h int main() { // 申请可写可执行内存 void *code mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); // 写入机器码示例x86_64 的 ret *(uint8_t*)code 0xC3; // 刷新指令缓存关键 __builtin___clear_cache(code, (uint8_t*)code 1); // 执行新代码 ((void(*)())code)(); munmap(code, 4096); return 0; }注意事项多核一致性仅刷新当前核心缓存多核场景需额外同步如内核smp_call_function内存权限目标内存必须有PROT_EXEC权限否则可能异常跨进程无效仅作用于当前进程地址空间跨进程需用进程间机制无错误返回失败时通常静默依赖系统 / 架构处理编译器支持GCC ≥ 4.x、Clang 全版本支持MSVC 不支持与__clear_cache的关系__builtin___clear_cache是编译器内置直接生成目标代码__clear_cache是libgcc提供的运行时函数编译器通常将__builtin___clear_cache展开为__clear_cache调用或内联指令ARM64刷新代码完整刷新 I D Cache// 地址在 x0 长度在 x1 dc civac, x0 // Clean Invalidate D-cache 行 dsb ish // 等待数据同步完成 ic ivau, x0 // Invalidate I-cache 行 dsb ish // 等待指令同步完成 isb // 刷新流水线确保新指令生效C 语言内联汇编static void flush_idcache(uintptr_t addr, size_t size) { uintptr_t end addr size; addr ~(64 - 1); // 按 64 字节 cache line 对齐 for (; addr end; addr 64) { __asm__ volatile( dc civac, %0\n dsb ish\n ic ivau, %0\n : : r(addr) : memory ); } __asm__ volatile( dsb ish\n isb\n :::memory ); }x86_64PC / Windows / Linux重点x86 硬件自动保证 I/D 一致性不需要手动刷 D-cache不需要手动刷 I-cache__builtin___clear_cache是空操作只有一种例外如果你用了自修改代码 多核需要// 刷缓存行可选 asm volatile (clflush 0(%0) : : r(addr) : memory);但绝大多数 JIT 都不需要。类似的功能函数cacheflush(老式系统调用ARM / MIPS 等)#include asm/unistd.h int cacheflush(char *addr, int nbytes, int flags);专门用于刷新指令缓存ARM / MIPS 传统接口64 位 ARM64 已不推荐__clear_cache(libgcc 运行时函数)void __clear_cache(void *begin, void *end);这是__builtin___clear_cache最终调用的真实函数几乎所有 GCC/Clang 环境都能用链接时会自动找到 libgccsyscall(SYS_cacheflush,...)(通用系统调用)#include unistd.h #include sys/syscall.h syscall(SYS_cacheflush, addr, length, 0);不依赖编译器内置直接进内核刷新

更多文章