MacOS下用程序获取vmmap级别的进程内存区域问题
获取与vmmap一致的完整进程内存区域信息
要还原vmmap展示的所有细分内存区域(包括Submap下的Mach-O段/节),需要结合多层Submap遍历和Mach-O文件结构解析,以下是具体实现步骤:
1. 递归遍历多层Submap
vmmap展示的细分区域大多来自嵌套的Submap,仅设置depth>0不够,需要递归遍历所有层级的Submap:
- 使用
mach_vm_region_recurse时,将初始depth设为足够大的值(如32,覆盖绝大多数嵌套场景) - 检查返回的
vm_region_recurse_info_data_64中的is_submap标志,若为真,则递归遍历该Submap的地址范围
示例C代码片段:
#include <mach/mach.h> #include <mach/mach_vm.h> void traverse_submap(mach_port_t target_task, mach_vm_address_t base_addr, mach_vm_size_t size, natural_t max_depth) { mach_vm_address_t addr = base_addr; mach_vm_size_t sub_size; vm_region_recurse_info_data_64_t sub_info; mach_msg_type_number_t info_count = VM_REGION_RECURSE_INFO_COUNT_64; natural_t depth = max_depth; while (addr < base_addr + size) { kern_return_t kr = mach_vm_region_recurse(target_task, &addr, &sub_size, &depth, (vm_region_info_t)&sub_info, &info_count); if (kr != KERN_SUCCESS) break; // 处理当前Submap内的内存区域(打印地址、大小、保护属性等) printf("Submap Region: 0x%lx - 0x%lx, Size: 0x%lx\n", addr, addr + sub_size, sub_size); if (sub_info.is_submap && depth > 0) { // 递归遍历更深层级的Submap traverse_submap(target_task, addr, sub_size, depth - 1); } addr += sub_size; depth = max_depth; // 重置depth,处理下一个同级区域 } } void traverse_all_regions(mach_port_t target_task) { mach_vm_address_t addr = 0; mach_vm_size_t size; vm_region_recurse_info_data_64_t info; mach_msg_type_number_t info_count = VM_REGION_RECURSE_INFO_COUNT_64; natural_t depth = 32; while (addr < MACH_VM_MAX_ADDRESS) { kern_return_t kr = mach_vm_region_recurse(target_task, &addr, &size, &depth, (vm_region_info_t)&info, &info_count); if (kr != KERN_SUCCESS) break; if (info.is_submap) { // 进入Submap遍历 traverse_submap(target_task, addr, size, depth - 1); } else { // 处理普通内存区域 printf("Normal Region: 0x%lx - 0x%lx, Size: 0x%lx\n", addr, addr + size, size); } addr += size; depth = 32; // 重置depth } }
2. 解析Mach-O文件的段/节信息
对于映射了Mach-O文件的区域(如系统共享库、可执行文件),需要读取文件并解析其segment_command_64和section_64结构体,以获取__TEXT、__DATA等细分段:
- 通过
vm_region_recurse_info_data_64中的object_name字段获取映射的文件路径 - 打开文件并映射到内存,解析Mach-O头部和加载命令,提取段和节的虚拟地址、大小等信息
- 注意:Mach-O文件中的
vmaddr是相对偏移,需要加上进程中的实际映射基址(即mach_vm_region返回的addr)得到实际地址
示例C代码片段:
#include <mach-o/loader.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> void parse_macho_sections(const char *file_path, mach_vm_address_t map_base) { int fd = open(file_path, O_RDONLY); if (fd == -1) return; struct stat st; if (fstat(fd, &st) == -1) { close(fd); return; } void *map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) { close(fd); return; } mach_header_64_t *hdr = (mach_header_64_t *)map; uintptr_t cmd_ptr = (uintptr_t)map + sizeof(mach_header_64_t); for (uint32_t i = 0; i < hdr->ncmds; i++) { load_command_t *cmd = (load_command_t *)cmd_ptr; if (cmd->cmd == LC_SEGMENT_64) { segment_command_64_t *seg_cmd = (segment_command_64_t *)cmd; // 计算段在进程中的实际起始地址 mach_vm_address_t seg_addr = map_base + seg_cmd->vmaddr; printf("Segment: %s, Address: 0x%lx, Size: 0x%lx\n", seg_cmd->segname, seg_addr, seg_cmd->vmsize); // 遍历段内的节 section_64_t *sect = (section_64_t *)(seg_cmd + 1); for (uint32_t j = 0; j < seg_cmd->nsects; j++) { mach_vm_address_t sect_addr = map_base + sect[j].addr; printf(" Section: %s, Address: 0x%lx, Size: 0x%lx\n", sect[j].sectname, sect_addr, sect[j].size); } } cmd_ptr += cmd->cmdsize; } munmap(map, st.st_size); close(fd); }
3. 整合逻辑
在遍历内存区域时,对每个非Submap区域,判断是否为Mach-O文件映射:
- 若
info.object_name不为空且是合法文件路径,调用parse_macho_sections解析段/节信息 - 结合Submap遍历和Mach-O解析,即可得到与vmmap一致的完整内存区域详情
内容的提问来源于stack exchange,提问作者Aayush
相关产品推荐
相关产品推荐

