MachO与lldb (10)

简介: 调试信息生成过程探究

调试信息生成过程探究


  1. 第一个工程
  2. clang test.m -o test -> .m生成可执行文件


  1. objdump --macho -d test -> 查看代码段 ->  汇编执行(虚拟内存地址+执行的指令)


  1. clang -c test.m -o test.o -> 生成.o文件


  1. objdump --macho -d test.o -> 查看.o文件 -> 与可执行文件不同的是(偏移量 + 执行的执行)
  2. 新增加test(),test_1()的函数, 并在mian里面调用 -> 重新生成.o文件并objdump来重新查看
  3. main的汇编里面 -> callq -> 前面都是e8开头, e8是固定机器码(代表callq指令)


  1. e8 后面 -> 进地址相对位移调用指令(偏移量)


  1. 偏移量 + 根据下一条指令地址 -> 就是这条指令的调用函数
  2. 此时偏移量都是00 00 00 00, 但是我们test,test_1明明有 -> 此时函数调用的并是不真实地址 -> 链接的时候, 分配虚拟内存地址 -> 需要告诉链接器,要把真实地址填进来
  3. test -> 重定位符号表 -> 告诉链接器,要把真实地址填进来 -> 在链接时


            2.objdump --macho --reloc test.o

微信图片_20220510025500.png

image.png


  1. 图片红框的address -> 需要重定位的地方


            3.clang test.m -o test -> 生成可执行文件


  1. objdump -> 查看生成的可执行文件
  2. 此时的可执行文件, 已经分配了虚拟内存地址
  3. macho地址要从右往左看(小端)


微信图片_20220510025504.png
image.png


               4.估码 -> 源码 -> 所以有的1相反然后加1, b8 ff ff ff -> 只需要看b8取反并+1就行


  1. lldb
  2. e -f b -- 0xb8 -> e, 执行一个表达式/f(format)/b(binary二进制)
  3. e -f b -- 0xb8 + 1 (e -f b -- 01000111 + 1) -> 真可爱,居然放弃计算,直接用计算器了 -> 0x48
  4. 验证了偏移地址 + 下一条指令的地址 -> 就是本条函数的调用地址


              4.objdump --macho -s test -> 显示当前所有二进制的内容

              5.汇编就是这样找函数的 偏移量 + 下条指令的地址 = 函数或对象的真实地址


调试信息dSYM


dSYM文件就是保存按DWARF格式保存调试信息的文件

DWARF是一种被众多编译器和调试器使用的用于支持源代码级别 调试的调试文件格式。


怎么生成dSYM文件的


  1. 读取debug map
  2. 从.o文件中加载__DWARF
  3. 重新定位所有地址
  4. 最后将全部的DWARF打包成dSYM Bundle


研究流程


  1. clang -g -c test.m -o test.o -> 生成.o目标文件, -g 生成调试信息(__DEARF)
  2. objdump --macho --private -headers test.o


  1. __DEARF -> 这个段就是保持的调试信息 -> 对应上面的第二条( 从.o文件中加载__DWARF)
  2. strip的时候会将这个段删掉 ->  把所有的调试信息放在符号表中


  1. clang -g test.m -o test -> 再编译成可执行文件
  2. objdump --macho --private -headers test
  3. nm -pa test -> 查看符号表,此时调试信息已经放在符号表里面了


微信图片_20220510025508.png
image.png


  1. clang -g1 test.m -o test -> 生成可执行文件的同时, 生成dSYM文件脚本
  2. dwarfdump test.dSYM -> 查看该文件


  1. 其实就是链接的时候, 将调试符号的信息抽取出来,生成dSYM文件来保存调试符号的信息(文件,文件名称,符号的名称,目录)


崩溃日志与dSYM


  1. 工程运行, 数组越界 -> 崩溃, 控制台打印日志
  2. 打开控制台 -> 崩溃报告 -> 刚才运行的崩溃日志
  3. Xcode控制台打印的很清晰 -> 是因为有保存当前项目的调试符号
  4. 如果想不保存 -> 脱符号


Build Settings -> Deployment Postprocessing -> Yes
Strip Style -> Debug -> All Symbols
再运行 -> 再去看控制台 -> 此时哪些VC, 以及方法都会变成地址, 而没有名字
怎么还原?
  1. 拿到macho的地址, 拿偏移后的地址 - 偏移量  = 没有偏移的地址


  1. 我们运行时调试到的地址实际上是: 调试地址 = 虚拟地址 + ASLR
  2. dSYM文件内保存的是没有偏倚的虚拟地址还是偏移后的地址? -> 没有偏移的


  1. e -f x -- 偏移的地址 - 偏移量 -> 会拿到一个地址


  1. 注意项目的脚本 Build Phases -> Copy dSYM的代码
  2. 终端进到该文件夹 -> dwarfdump --lookup 上一步算出的地址 TestInject.app.dSYM -> 查看该地址对应的信息 -> 找到了崩溃时的方法


微信图片_20220510025512.pngimage.png


Mach-o格式解析


重定位符号表作用


虚拟内存地址与ASLR与dSYM文件关系 /.crash文件符号恢复原理探究


  1. 打开工程3-ASLR与dSYM
  2. 获取ASLR


uintptr_t get_slide_address(void) {
    uintptr_t vmaddr_slide = 0;
    // 使用的所有的二进制文件 = ipa + 动态库
    // ASLR Macho 二进制文件 image 偏移
    for (uint32_t i = 0; i < _dyld_image_count(); i++) {
        // 遍历的是那个image名称
        const char *image_name = (char *)_dyld_get_image_name(i);
        const struct mach_header *header = _dyld_get_image_header(i);
        if (header->filetype == MH_EXECUTE) {
            vmaddr_slide = _dyld_get_image_vmaddr_slide(i);
        }
        NSString *str = [NSString stringWithUTF8String:image_name];
        if ([str containsString:@"TestInject"]) {
              NSLog(@"Image name %s at address 0x%llx and ASLR slide 0x%lx.\n", image_name, (mach_vm_address_t)header, vmaddr_slide);
                   break;
          }
    }
    // ASLR返回出去
    return (uintptr_t)vmaddr_slide;
}
- (void)getMethodVMA {
    // 运行中的地址(偏移)
    IMP imp = (IMP)class_getMethodImplementation(self.class, @selector(test_dwarf));
    unsigned long imppos = (unsigned long)imp;
    unsigned long slide =  get_slide_address();
    // 运行中的地址(偏移) - ASLR = 真正的虚拟内存地址
    unsigned long addr = imppos - slide;
}


然后开启DEARF
Build Settings -> dw -> Debug information Format -> Debug -> DEARF with dSYM File
检查是否脱调试符号 -> Build Settings -> Deployment -> Deployment Postprocessing -> NO(默认的,不脱调试符号)
运行, 断点获取addr -> 转16进制 -> e -f x -- 地址  (也可以用计算器试试)
dwarfdump --lookup 上一步算出的地址 TestInject.app.dSYM

dSYM文件的作用


  1. 打开代码调试工程
  2. 使用了自己的TestFramework文件
  3. TestFramework.podspec文件里面的写法


if ENV['Source'] -> 如果pod install时, 引入的是源码,否则就是编译好的.framework文件
终端来到PodFile文件夹下 -> pod install -> 引入的是framework
如果想引入源码 Source=1 pod install -> 让这个变量为真
  1. 然后运行工程, 通过终端下断点


微信图片_20220510025516.png

image.png


  1. 下断点后, 再往下走, 发现并没有进源码里面, 你的framework里面保存的有完整的调试信息的话, 你是可以进到源码里面的
  2. TestFramework.framework -> show in finder -> cd到该文件夹下


  1. nm -pa TestFramework
  2. 发现因为是里面的调试信息的目录路径不对
  3. 可以把源码放到上面的路径下试试 -> 最终重新编译的framewrok,意味着里面的路径也会重新生成
  4. 注意framework的脚本 -> 脚本最终会把编译的产物放在Products目录下
  5. 然后按上面的步骤,重新pod install, 运行项目, 下断点(注意写法时通过正则下的断点) -> 下断点成功后, 继续运行 ->  进入了framework组件的源码


  1. 思路:组件化或者二进制化的时候 -> 通过控制你的二进制文件有没有调试信息 -> 来达到调试源码的目的


视频5


dyld的调试与作用


如果想调试dyld源代码,需要准备带调试信息的dyld/libdyld.dylib/ libclosured.dylib,与系统做替换,⻛险较大


lldb保留了 一个库列表,避免在按名称设置断点时出现问题,而dyld与

libdyld.dylib就在该列表上。

有两种方式在可以强制在dyld上设置断点:


  1. br set -n dyldbootstrap::start -s dyld


  1. -s 在指定的二进制文件里设置断点


  1. set set target.breakpoints-use-platform-avoid-list 0


  1. 注重这种设置只在当前这次运行中生效
  2. 这种是通过lldb提供的环境变量
  3. 因为在库列表里的不能设置断点, 以防跟我们常用的冲突, 这个改环境变量就是将白名单禁掉(就是上面的库列表)



微信图片_20220510025520.png
image.png


dyld提供的环境变量


微信图片_20220510025524.png

image.png


使用举例:


微信图片_20220510025528.png

image.png


微信图片_20220510025532.png

image.png


以上设置环境变量在Xcode中的设置步骤为:


  1. Edit Scheme
  2. Arguments
  3. Environment Variables

    微信图片_20220510025537.png
    image.png


程序加载流程


微信图片_20220510025541.png

image.png



objdump --macho --private -headers test -> 查看Macho的信息
  1. dyld: 动态链接程序


  1. libdyld.dylib: 给我们的程序提供在Runtime期间能使用动态链接功能


  1. dyld做了什么 ->


微信图片_20220510025546.png

image.png


dyld加载流程


图注: 白色线往下走, 黄色线往上走

微信图片_20220510025550.png

image.png



微信图片_20220510025554.png

image.png



微信图片_20220510025558.png

image.png


  1. 正则下断点


微信图片_20220510025601.png
image.png


插入动态库与插入函数


插入测试动态库流程分析


  1. Inject.m -> 只有一个打印函数


  1. 编译包装成一个动态库


  1. 新打开一个工程


  1. 插入(图中没有打钩, 需要打勾)


微信图片_20220510025605.png
image.png


  1. 然后运行工程, 发现控制台打印的有插入的动态库(这里其实是逆向的知识点)


插入函数


这个是dyld给我提供的hook函数, 以下方法就是调用NSLog时, 改为调用my_NSLog


// 1.在__DATA 创建__interpose这个section
#define INTERPOSE(_replacement, _replacee) \
    __attribute__((used)) static struct { \
        const void* replacement; \
        const void* replacee; \
    } _interpose_##_replacee __attribute__ ((section("__DATA, __interpose"))) = { \
        (const void*) (unsigned long) &_replacement, \
        (const void*) (unsigned long) &_replacee \
    };
// hook function
// 国内大厂 面源码
// 国外 实际应用
void my_NSLog(NSString *format, ...) {
    NSLog(@"InjectFunction---%@", format);
}
// hook function
INTERPOSE(my_NSLog, NSLog);


  1. InjectFunction


  1. my_NSLog替换NSLog -> dyld提供的宏


  1. Preprocess -> 可以查看转化后代码


微信图片_20220510025610.png

image.png


  1. __attribute__((used))因为该方法没有使用 -> 告诉编译器,你要不管,这是我私下使用的,不要给我报警告


__attribute__((used)) static struct { const void* replacement; const void* replacee; } _interpose_NSLog __attribute__ ((section("__DATA, __interpose"))) = { (const void*) (unsigned long) &my_NSLog, (const void*) (unsigned long) &NSLog };;


  1. 上面的代码其实就是声明一个结构体类型,并同时在定义个结构体, 然后实例化了这个结构体
  2. 然后把创建的结构体放在了__DATA __interpose section
  3. 我们dyldy就知道从__interpose 这个section里面调用你插入的函数


  1. 然后编译, 将编译后的可执行文件, 直接通过Xcode配置来实现插入(截图为插入多个动态库的写法,通过:分割开)


微信图片_20220510025614.png

image.png




目录
相关文章
|
5月前
|
NoSQL Linux C语言
GDB:强大的GNU调试器
GDB:强大的GNU调试器
|
iOS开发
LLDB 调试命令、插件和技巧(上)
LLDB 调试命令、插件和技巧(上)
639 0
|
6月前
|
NoSQL IDE Linux
gdb和makefile的讲解
gdb和makefile的讲解
61 2
|
6月前
|
NoSQL Linux C语言
调试器gdb
调试器gdb
74 0
|
iOS开发
LLDB命令
LLDB命令
165 0
LLDB命令
|
iOS开发 C++ Python
LLDB 调试命令、插件和技巧(下)
LLDB 调试命令、插件和技巧(下)
472 0
|
iOS开发
LLDB 调试命令、插件和技巧(中)
LLDB 调试命令、插件和技巧(中)
451 0
|
网络协议 开发工具 Windows
Windows平台的Windbg/x64dbg/OllyDbg/VisualGDB调试器简介以及符号文件*.pdb总结(1)(★firecat推荐★)
Windows平台的Windbg/x64dbg/OllyDbg/VisualGDB调试器简介以及符号文件*.pdb总结(★firecat推荐★)
528 0
Windows平台的Windbg/x64dbg/OllyDbg/VisualGDB调试器简介以及符号文件*.pdb总结(1)(★firecat推荐★)
|
缓存 Linux 网络安全
Windows平台的Windbg/x64dbg/OllyDbg/VisualGDB调试器简介以及符号文件*.pdb总结(2)(★firecat推荐★)
Windows平台的Windbg/x64dbg/OllyDbg/VisualGDB调试器简介以及符号文件*.pdb总结(★firecat推荐★)
813 0
Windows平台的Windbg/x64dbg/OllyDbg/VisualGDB调试器简介以及符号文件*.pdb总结(2)(★firecat推荐★)