Linux平台下标记已分配内存检测非预期访问的方法
解决方案:Linux下标记内存空洞为不可访问的方法
以下是在Linux平台、GCC/Clang环境下,无需依赖Address Sanitizer即可标记内存空洞、触发类似段错误检测的实用方法:
1. 使用mmap + mprotect(推荐,直接触发段错误)
这是最贴合需求的方案:通过内存映射分配连续内存,将空洞区域设置为不可访问权限,一旦访问就会触发SIGSEGV信号(即段错误),完全模拟系统级内存越界行为。
核心原理
mmap可分配页对齐的连续内存块,mprotect能修改指定内存区域的访问权限。将空洞区域设为PROT_NONE后,任何读/写操作都会触发内核抛出段错误。
实现示例
#include <sys/mman.h> #include <unistd.h> #include <stdio.h> int main() { size_t page_size = sysconf(_SC_PAGESIZE); // 分配2页连续匿名内存(第一页正常使用,第二页作为空洞) void *mem = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (mem == MAP_FAILED) { perror("mmap failed"); return 1; } // 将第二页(空洞区域)标记为不可访问 if (mprotect((char*)mem + page_size, page_size, PROT_NONE) == -1) { perror("mprotect failed"); munmap(mem, 2 * page_size); return 1; } // 正常访问测试(无异常) *(int*)mem = 42; printf("Normal access: %d\n", *(int*)mem); // 访问空洞区域(直接触发段错误) *(int*)((char*)mem + page_size) = 100; munmap(mem, 2 * page_size); return 0; }
注意事项
- 必须操作页对齐的内存:
mprotect的起始地址和长度都必须是页大小的整数倍。如果你的空洞无法对齐到页边界,调试模式下可将包含空洞的整个页设为不可访问(牺牲少量正常内存,换取错误检测能力)。 - 匿名映射(
MAP_ANONYMOUS)无需关联文件,适合临时内存分配场景。
2. 使用posix_memalign + mprotect(适配malloc风格分配)
如果你更习惯类似malloc的内存分配方式,可以用posix_memalign分配页对齐内存,再通过mprotect标记空洞。
核心原理
posix_memalign支持指定对齐大小(需为页大小的倍数),分配后用mprotect修改权限的逻辑与上述方案一致。
实现示例
#include <stdlib.h> #include <sys/mman.h> #include <unistd.h> #include <stdio.h> int main() { size_t page_size = sysconf(_SC_PAGESIZE); void *mem; // 分配2页大小、页对齐的内存 if (posix_memalign(&mem, page_size, 2 * page_size) != 0) { perror("posix_memalign failed"); return 1; } // 标记第二页为不可访问 if (mprotect((char*)mem + page_size, page_size, PROT_NONE) == -1) { perror("mprotect failed"); free(mem); return 1; } // 正常访问测试 *(int*)mem = 42; printf("Normal access: %d\n", *(int*)mem); // 访问空洞触发段错误 *(int*)((char*)mem + page_size) = 100; free(mem); return 0; }
注意事项
posix_memalign分配的内存需用free释放,而非munmap。- 对齐参数必须是2的幂且不小于
sizeof(void*),直接使用页大小即可满足需求。
3. 调试模式填充特殊值+主动检查(轻量替代)
如果页对齐限制对你的数据结构不友好,可在调试模式下给空洞填充特殊标记值,通过主动检查检测意外访问。
核心思路
- 调试模式初始化时,将空洞区域填充
0xDEADBEEF或0xBAADF00D这类特殊值。 - 在关键操作节点(如数据结构修改后、函数返回前)遍历空洞区域,检查标记值是否被篡改。
- 若发现篡改,立即触发段错误或终止程序。
实现示例
#include <stdio.h> #include <string.h> #include <signal.h> #include <stdlib.h> #define DEBUG 1 typedef struct { int data[10]; // 假设该区域为永久空洞 char hole[100]; int more_data[5]; } MyStruct; #ifdef DEBUG #define HOLE_MARKER 0xDEADBEEF void check_hole(MyStruct *s) { for (int i = 0; i < sizeof(s->hole)/sizeof(int); i++) { if (*((int*)&s->hole[i*sizeof(int)]) != HOLE_MARKER) { fprintf(stderr, "Hole accessed unexpectedly!\n"); raise(SIGSEGV); // 触发段错误 } } } #endif int main() { MyStruct s; memset(&s, 0, sizeof(s)); #ifdef DEBUG // 填充空洞标记值 memset(s.hole, HOLE_MARKER, sizeof(s.hole)); #endif // 正常操作 s.data[0] = 42; s.more_data[0] = 100; #ifdef DEBUG check_hole(&s); #endif // 模拟越界写操作(调试模式下会被检测到) s.hole[0] = 'a'; #ifdef DEBUG check_hole(&s); #endif return 0; }
优缺点
- 优点:无需页对齐,适配任意大小的空洞。
- 缺点:无法实时触发错误,需主动检查;仅能检测写操作,无法捕获读访问。
内容的提问来源于stack exchange,提问作者alfC
相关产品推荐
相关产品推荐

