iOS - 底层知识学习之路-Mach-o 、lldb、 dyld

论坛 期权论坛 编程之家     
选择匿名的用户   2021-5-31 19:28   228   0

MACHO与重定位符号表认识。

命令回顾

1. 将源代码编译成可执行文件命令

Clang 文件名 -o 输出的可执行文件名称

如:
Clang test.m - o test 

2. 查看可执行文件的代码段命令

objdump --macho -d 可执行文件名称

如:
objdump --macho -d test

3. 将文件编译生.o文件命令

clang -c 文件名 -o .o文件名
如:
clang -c test.m -o test.o

4. 分析.o文件的代码段命令

源码:


void test() {
    
}
void test_1() {
    
}
int global = 10;
int main(){
    global = 21;
    global = 20;
    test();
    test_1();
    return 0;
}

执行:

objdump --macho -d test.o

结果:

(__TEXT,__text) section
_test:
       0: 55 pushq %rbp
       1: 48 89 e5 movq %rsp, %rbp
       4: 5d popq %rbp
       5: c3 retq
       6: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:_test(%rax,%rax)
_test_1:
      10: 55 pushq %rbp
      11: 48 89 e5 movq %rsp, %rbp
      14: 5d popq %rbp
      15: c3 retq
      16: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:_test(%rax,%rax)
_main:
      20: 55 pushq %rbp
      21: 48 89 e5 movq %rsp, %rbp
      24: 48 83 ec 10 subq $16, %rsp
      28: c7 45 fc 00 00 00 00 movl $_test, -4(%rbp)
      2f: c7 05 fc ff ff ff 15 00 00 00 movl $21, _global-4(%rip)
      39: c7 05 fc ff ff ff 14 00 00 00 movl $20, _global-4(%rip)
      43: e8 00 00 00 00 callq _test
      48: e8 00 00 00 00 callq _test_1
      4d: 31 c0 xorl %eax, %eax
      4f: 48 83 c4 10 addq $16, %rsp
      53: 5d popq %rbp
      54: c3 retq

得出结论:

1. 看汇编可以得出编译顺序与代码的书写顺序是一致的。

2. 看 43 与 48 行地址为 00 ,这是虚拟地址, 起占位作用, 通过重定位符号表来确定最终的内存地址。

3. 查重定位符号表

objdump --macho --reloc .o文件名
如:
objdump --macho --reloc test.o

结果:如下面结果 (43 , 48 的下一位)49 与 44 分别就是上面占位地址的最终内存地址 。

address  pcrel length extern type    scattered symbolnum/value
00000049 True  long   True   BRANCH  False     _test_1
00000044 True  long   True   BRANCH  False     _test
0000003b True  long   True   SIGNED4 False     _global
00000031 True  long   True   SIGNED4 False     _global
Relocation information (__LD,__compact_unwind) 3 entries
address  pcrel length extern type    scattered symbolnum/value
00000040 False quad   False  UNSIGND False     1 (__TEXT,__text)
00000020 False quad   False  UNSIGND False     1 (__TEXT,__text)
00000000 False quad   False  UNSIGND False     1 (__TEXT,__text)

分析可执行文件的代码段命令

objdump --macho -d test

输出:

(__TEXT,__text) section
_test:
100003f60: 55 pushq %rbp
100003f61: 48 89 e5 movq %rsp, %rbp
100003f64: 5d popq %rbp
100003f65: c3 retq
100003f66: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
_test_1:
100003f70: 55 pushq %rbp
100003f71: 48 89 e5 movq %rsp, %rbp
100003f74: 5d popq %rbp
100003f75: c3 retq
100003f76: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
_main:
100003f80: 55 pushq %rbp
100003f81: 48 89 e5 movq %rsp, %rbp
100003f84: 48 83 ec 10 subq $16, %rsp
100003f88: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
100003f8f: c7 05 67 40 00 00 15 00 00 00 movl $21, 16487(%rip)
100003f99: c7 05 5d 40 00 00 14 00 00 00 movl $20, 16477(%rip)
100003fa3: e8 b8 ff ff ff callq _test
100003fa8: e8 c3 ff ff ff callq _test_1
100003fad: 31 c0 xorl %eax, %eax
100003faf: 48 83 c4 10 addq $16, %rsp
100003fb3: 5d popq %rbp
100003fb4: c3 retq

mac OS 是小端。 地址从右到左查看 。 右是高位

DWARF与DSYM

dsym 文件就是保存按照DWARF格式保存的调试信息的文件 。

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

dsym文件的生成过程:

1. 读取debug map

2. 从.o文件中加载 dwarf

3. 重新定位所有的地址

4. 最后将全部的 dwarf 打包成 dsym bundle

生成调试信息命令

clang -g -c test.m -o test.o

理解第2步的过程: 终端命令 查看 test.o

otool -l test.o

结果: 下面就有dwarf 字段 。

Load command 0
      cmd LC_SEGMENT_64
  cmdsize 1112
  segname 
   vmaddr 0x0000000000000000
   vmsize 0x00000000000004c9
  fileoff 1272
 filesize 1225
  maxprot 0x00000007
 initprot 0x00000007
   nsects 13
    flags 0x0
Section
  sectname __text
   segname __TEXT
      addr 0x0000000000000000
      size 0x0000000000000055
    offset 1272
     align 2^4 (16)
    reloff 2500
    nreloc 4
     flags 0x80000400
 reserved1 0
 reserved2 0
Section
  sectname __data
   segname __DATA
      addr 0x0000000000000058
      size 0x0000000000000004
    offset 1360
     align 2^2 (4)
    reloff 0
    nreloc 0
     flags 0x00000000
 reserved1 0
 reserved2 0
Section
  sectname __objc_imageinfo
   segname __DATA
      addr 0x000000000000005c
      size 0x0000000000000008
    offset 1364
     align 2^0 (1)
    reloff 0
    nreloc 0
     flags 0x10000000
 reserved1 0
 reserved2 0
Section
  sectname __debug_str
   segname __DWARF
      addr 0x0000000000000064
      size 0x0000000000000111
    offset 1372
     align 2^0 (1)
    reloff 0
    nreloc 0
     flags 0x02000000
 reserved1 0
 reserved2 0
Section
  sectname __debug_abbrev
   segname __DWARF
      addr 0x0000000000000175
      size 0x0000000000000061
    offset 1645
     align 2^0 (1)
    reloff 0
    nreloc 0
     flags 0x02000000
 reserved1 0
 reserved2 0
Section
  sectname __debug_info
   segname __DWARF
      addr 0x00000000000001d6
      size 0x0000000000000093
    offset 1742
     align 2^0 (1)
    reloff 2532
    nreloc 5
     flags 0x02000000
 reserved1 0
 reserved2 0
Section
  sectname __apple_names
   segname __DWARF
      addr 0x0000000000000269
      size 0x0000000000000090
    offset 1889
     align 2^0 (1)
    reloff 0
    nreloc 0
     flags 0x02000000
 reserved1 0
 reserved2 0
Section
  sectname __apple_objc
   segname __DWARF
      addr 0x00000000000002f9
      size 0x0000000000000024
    offset 2033
     align 2^0 (1)
    reloff 0
    nreloc 0
     flags 0x02000000
 reserved1 0
 reserved2 0
Section
  sectname __apple_namespac
   segname __DWARF
      addr 0x000000000000031d
      size 0x0000000000000024
    offset 2069
     align 2^0 (1)
    reloff 0
    nreloc 0
     flags 0x02000000
 reserved1 0
 reserved2 0
Section
  sectname __apple_types
   segname __DWARF
      addr 0x0000000000000341
      size 0x0000000000000047
    offset 2105
     align 2^0 (1)
    reloff 0
    nreloc 0
     flags 0x02000000
 reserved1 0
 reserved2 0

如果是查看可执行文件,是找不到drarf字段的, 他会单独放在一个地方 ,通过命令查看:

nm -pa 可执行命令文件名
如:
nm -pa test

生成dsym文件命令:

clang -g1 test.m -o test

查看 dsym 命令:

dwarfdump test.dSYM

dsym 内文件中保存的是没有偏移的虚拟地址,所以能够进行bug的现场恢复。

计算虚拟地址

ViewController.m文件如下:

1. 获取 ASLR

2. 计算虚拟地址 。

#import "ViewController.h"
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>
#import <objc/runtime.h>

@interface ViewController ()

@end

@implementation ViewController

// 获取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)viewDidLoad {
    [super viewDidLoad];
    [self getMethodVMA];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self test_dwarf];
    });
    NSLog(@"123");
}

- (void)test_dwarf {
    NSArray *array = @[];
    array[1];
}

- (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;
}
@end

注意: 需要在building setting里面打开生成dysm文件的配置 。 debug默认不生成这个文件 。

xcode 终端格式输入十六进制 lldb调试输入

e -f x -- 计算出的虚拟内存地址

结论:

使用的所有的二进制文件 = ipa + 动态库

ASLR Macho 二进制文件 image 偏移

dyld学习 - 后期通过源码深入学习

dyld 调试:

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

方式二 : 通过在dyld文件上设置断点。 设置断点的方法有两种。

lldb保留了一个库列表(白名单), 避免在按名称设置短点时出现问题, 而 dyld 正好在这个名单上,所以需要强制设置短点, 方式如下两种:

-s 含义: 指定在哪个文件里面设置断点。

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

2. set set target.breakpoints-use-platform-avoid-list 0 (通过lldb)

终端输入: 打印dyld所有的调用过程 、 其他配置自行查询实践。

 DYLD_PRINT_APIS=1 ./ 可执行文件
如:

 DYLD_PRINT_APIS=1 ./test 

dyld 到底做了什么?

dyld: 动态链接程序

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

具体执行步骤如下:

插入动态库与插入函数: hook

插入函数 :

__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 };;

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP