如何在_dyld回调中通过mach_header获取二进制镜像名称?
解决dyld加载回调中获取镜像名称的问题
好问题!既然你已经通过_dyld_register_func_for_add_image()跟踪镜像加载,又想规避遍历_dyld_image_count()/_dyld_get_image_header()带来的死锁风险,这里有两种可靠的方法,直接利用回调参数拿到镜像名称:
方法1:使用dyld_image_path_containing_address(推荐)
这是最简单且可靠的方式,dyld提供了dyld_image_path_containing_address()函数,它能直接返回包含指定内存地址的镜像路径。因为回调传入的mach_header指针本身就在该镜像的内存范围内,直接传入即可:
Objective-C 代码
#include <mach-o/dyld.h> #include <mach-o/loader.h> void imageAddedCallback(const struct mach_header* mh, intptr_t vmaddr_slide) { // 直接传入mach_header的地址,获取对应镜像路径 const char* imageName = dyld_image_path_containing_address(mh); if (imageName != NULL) { NSLog(@"Loaded image: %s", imageName); // 这里可以把名称存入你的跟踪集合中 } } // 注册回调 _dyld_register_func_for_add_image(imageAddedCallback);
Swift 代码(转换后)
import MachO import Darwin func imageAdded(mh: UnsafePointer<mach_header>, slide: Int) { guard let imageName = dyld_image_path_containing_address(mh) else { return } let imagePath = String(cString: imageName) print("Loaded image: \(imagePath)") // 加入你的跟踪逻辑 } // 注册回调 _dyld_register_func_for_add_image(imageAdded)
这个方法的优势在于:
- 无需手动解析Mach-O结构,代码简洁
- 自动兼容32位/64位架构,以及可执行文件、动态库等各种镜像类型
- 内部实现高效,不会触发遍历所有镜像的操作,规避死锁风险
方法2:手动解析Mach-O的Load Command
如果你想完全不依赖dyld的额外API,可以直接解析mach_header后的Load Command,找到存储镜像名称的字段:
Objective-C 代码
#include <mach-o/loader.h> #include <mach-o/dyld.h> void imageAddedCallback(const struct mach_header* mh, intptr_t vmaddr_slide) { // 判断是32位还是64位Mach-O bool is64Bit = (mh->magic == MH_MAGIC_64 || mh->magic == MH_CIGAM_64); uintptr_t cmdPointer = (uintptr_t)mh + (is64Bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header)); const struct load_command* loadCmd = (const struct load_command*)cmdPointer; // 遍历所有Load Command for (uint32_t i = 0; i < mh->ncmds; i++) { // 动态库的自身名称存储在LC_ID_DYLIB命令中 if (loadCmd->cmd == LC_ID_DYLIB) { const struct dylib_command* dylibCmd = (const struct dylib_command*)loadCmd; // 名称的偏移是相对于mach_header的,直接计算地址 const char* imageName = (const char*)((uintptr_t)dylibCmd + dylibCmd->dylib.name.offset); NSLog(@"Loaded image: %s", imageName); break; } // 移动到下一个Load Command cmdPointer += loadCmd->cmdsize; loadCmd = (const struct load_command*)cmdPointer; } } // 注册回调 _dyld_register_func_for_add_image(imageAddedCallback);
注意事项:
- 这种方法需要区分32/64位Mach-O结构,处理起来更繁琐
- 对于可执行文件(而非动态库),
LC_ID_DYLIB不存在,需要额外处理(比如通过_NSGetExecutablePath获取,但仅适用于主程序) - 兼容性稍差,若Apple修改Mach-O结构可能需要调整
额外提示
- 对于镜像卸载的回调(
_dyld_register_func_for_remove_image),同样可以使用上述两种方法获取名称,回调期间mach_header指针仍然有效 - 在回调中尽量避免耗时操作或持有锁,防止干扰dyld的加载流程,进一步降低死锁风险
内容的提问来源于stack exchange,提问作者Robin Macharg
相关产品推荐
相关产品推荐

