iOS – 底层知识学习之路-Mach-o 、lldb、 dyld
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;