如何安全写入mmap映射区域至文件?解决SIGBUS异常问题
解决方案:避免mmap截断文件导致的SIGBUS问题
你遇到的SIGBUS本质是:当mmap映射的区域超出文件当前实际大小,访问超出范围的内存页时,内核会触发该信号终止进程。针对你的转储场景,以下是几个可行的安全解决方案:
方案一:用分块pread替代mmap(最直接解决当前代码问题)
放弃直接mmap整个大区域,改用pread分块读取原文件内容,再写入压缩文件。pread会自动处理文件大小变化,读取到文件末尾时会返回实际可读字节数,不会触发任何信号。
代码替换示例:
// 移除原mmap相关代码,替换为以下逻辑 size_t remaining = segment->size; off_t file_offset = segment->file_offset; const size_t chunk_size = 4096; // 按页大小分块,可根据性能调整 char buf[chunk_size]; while (remaining > 0) { ssize_t bytes_read = pread(mmap_fd, buf, chunk_size, file_offset); if (bytes_read <= 0) { // 读取失败或已到文件末尾,终止循环 break; } gzwrite(gzfile, buf, bytes_read); remaining -= bytes_read; file_offset += bytes_read; }
优缺点:
- ✅ 彻底规避SIGBUS问题,并发场景下文件继续缩小也不会出错
- ✅ 无需修改信号处理逻辑,不会导致压缩文件损坏
- ❌ 相比mmap的直接内存复制,有轻微的性能开销,但转储工具通常可接受
方案二:直接读取目标进程的内存(符合转储进程信息的核心需求)
你的工具核心是转储进程的内存信息,而非原文件内容。直接通过process_vm_readv或ptrace读取目标进程的内存,完全绕开原文件的状态变化问题。
代码示例(用process_vm_readv高效读取):
#include <sys/uio.h> #include <sys/ptrace.h> #include <sys/wait.h> // ... size_t remaining = segment->size; void* remote_addr = (void*)segment->addr; // 进程内存段的起始地址 const size_t chunk_size = 4096; char buf[chunk_size]; // 附加到目标进程 if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) { // 处理权限或进程不存在等错误 goto cleanup; } waitpid(pid, NULL, 0); // 等待进程进入停止状态 struct iovec local_iov = {.iov_base = buf, .iov_len = chunk_size}; struct iovec remote_iov = {.iov_base = remote_addr, .iov_len = chunk_size}; while (remaining > 0) { size_t read_len = remaining < chunk_size ? remaining : chunk_size; local_iov.iov_len = read_len; remote_iov.iov_len = read_len; remote_iov.iov_base = remote_addr; ssize_t bytes_read = process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0); if (bytes_read <= 0) { break; } gzwrite(gzfile, buf, bytes_read); remaining -= bytes_read; remote_addr += bytes_read; } // 分离目标进程,恢复其运行 ptrace(PTRACE_DETACH, pid, NULL, NULL); cleanup: close(mmap_fd);
优缺点:
- ✅ 直接获取进程当前内存中的真实内容,不受原文件修改/截断影响
- ✅ 对于MAP_PRIVATE类型的映射,能获取进程修改后的私有数据(原文件中没有)
- ❌ 需要足够的权限(通常是root或同用户组),附加进程会短暂暂停目标进程
方案三:安全映射+分段处理(兼容原mmap思路)
如果一定要保留mmap的使用,可以先获取文件当前大小,只映射安全范围的区域,超出部分根据场景处理:
- 用
fstat获取文件当前大小,计算安全映射长度:size_t safe_len = min(segment->size, stat_buf.st_size - segment->file_offset) - 仅映射
safe_len大小的区域,写入压缩文件 - 对于超出
safe_len的部分:- 如果是转储进程内存:用方案二的方式读取进程内存
- 如果仅需原文件内容:直接填充空字节或记录缺失
注意:
这个方案仍存在极小的并发风险(fstat后文件立即缩小),可以结合pread验证每块数据的可用性,进一步降低风险。
内容的提问来源于stack exchange,提问作者HardcoreHenry
相关产品推荐
相关产品推荐

