1. MachO
一个可执行文件;包括 代码层(汇编代码的逻辑层)、数据层、符号层;
1.1 符号
在开始符号,下面有几个问题,我们可以先看看
- 什么是符号?
- 符号存储在哪里?
- 符号的缺点?
- 重定位符号
- 全局符号 & 本地符号
- 导出符号
- 间接符号
- 弱引用 & 弱定义
1.1.1 重定位
// clang -arch x86_64 -c test.c -o test.o : 编译成.o 文件
// clang -arch x86_64 test.o -o test: 编译成 可执行文件
void test1() {
}
void test2() {
}
int main(int argc, char * argv[]) {
test1();
test2();
}
查看代码段:
重定位之前的:
objdump --macho -d test.o
重定位之后的:
objdump --macho -d test
查看重定位(reloc)符号 objdump --macho --reloc test.o
1.1.2 全局符号&本地符号
- 全局(静态或者非静态) 初始化的符号是存在
__Data __data区的; - 未初始化的 全局变量(非静态) 存在
__Data __common - 未初始化的 静态变量(全局或者局部) 存在
__Data __bss(例如:__bss _g_s_u_1,__bss _main.l_s_u)-
静态全局
未初始化 && 未被函数使用的g_s_u_2, 直接被忽略了; -
静态局部
未初始化 && 未被函数使用的l_s_u依然被存在__DATA,__bss _main.l_s_u
-
// #include "export.h"
#include <stdio.h>
int g_1 = 0x0101; // g O __DATA,__data _g_1
int g_2 = 0x0202;
int g_3 = 0x0303;
int g_4 = 0x0404;
int g_u_1; // g O __DATA,__common _g_u_1
int g_u_2;
static int g_s_1 = 0x0505; // l O __DATA,__data _g_s_1
static int g_s_2 = 0x0606;
static int g_s_3 = 0x0707;
static int g_s_4 = 0x0808;
static int g_s_u_1; // l O __DATA,__bss _g_s_u_1
static int g_s_u_2; // 会被忽略
/**
clang -arch x86_64 -c test.c -o test.o : 编译成.o 文件
clang -arch x86_64 test.o -o test : 编译成 可执行文件
*/
void test1() {
g_3 = g_1 + g_2;
g_s_u_1 = 0x2525;
}
void test2() {
g_s_3 = g_s_1 + g_s_2;
int a = g_s_u_1 ;
}
int main(int argc, char * argv[]) {
int l_1 = 0x1212; // 局部变量会被忽略
int l_2 = 0x1313;
int l_u;
static int l_s_1 = 0x1414; // l O __DATA,__data _main.l_s_1
static int l_s_u ; // l O __DATA,__bss _main.l_s_u
// g_s_6 = l_s_1;
test1();
test2();
printf("g_1 = %x \n",g_1);
}
查看 符号表:
objdump --macho -syms ./staticlib/static_test
1.1.3 导出符号
- 全局符号默认导出的:
objdump --macho --exports-trie static_test
1.1.4 间接符号
和动态库相关的
objdump --macho --indirect-symbols static_test
通过macho 查看 ,其中index 其实是执行符号表的序号,后面的 符号查找会详细介绍;
1.1.5 弱符号
弱定义
首先看一个经典的错误: 1 duplicate symbol,使用了多个 强引用的符号
解决: export.c 中 int g_1 attribute((weak)) = 0x0202; 或者 int g_1
弱导入
一个经典的错误 Undefined symbols ,使用了一个 只声明了头文件,没有实现的函数;
export.h
void weak_export_func() __attribute__((weak_import));
在test.c 中 使用,并且编译链接的时候 需要 把 weak_export_func 指定为 未定义
clang test.c -L./staticlib -lexport -Xlinker -U -Xlinker _weak_export_func -o ./static_test
if (weak_export_func){
weak_export_func();
printf("weak_export_func = null \n");
}
1.2 符号查找
dynamic symbol table (存储符号表的 index) => symbol table (存储了 string 表的index) => 找到string table
下面根据 列表来查找
0xe8 = 232
很显然 我们在 symbol 表里面找到了 NSLog
然后我们可以看到 0x12e = 302 这个值就是 string 表的偏移量
使用命令查看string 表 strings Demo1
string 表 每行 16个字符; 那么 302 / 16 = 18 ... 14;
2 静态库 & 动态库
-
库: 其实就是 .o 文件的集合
-
静态库
- 执行速度快;
- 静态库在编译链接后 会作为 可执行的一部分;
-
动态库
- iOS系统的动态库,NSFoundation、UIKit 等
- IOS自定义的动态库 会在 .app 的 内部,但不会合入 可执行文件中;
IOS会加快下载速度- 实现增量更新: 就是 如果用户要更新 app的时候,如果app 内部的 文件 (比如动态库)相同就不会下载: z.itpub.net/article/det…- 设计到 非懒加载 和 懒加载 ,分别会影响 启动速度 和 函数的执行速度(第一次会影响);
2.1 静态库
测试静态库:
exprot.c
int g_export_1 = 0xbb;
int g_export_2_hidden __attribute__((visibility("hidden"))) = 0xaa ; //改变全局属性
static int g_export_3 = 0xcc;
int g_export_3_u ;
int g_export_4_u_hidden __attribute__((visibility("hidden"))) ;
int g_1 __attribute__((weak)) = 0x1012;
// int g_1 = 0x1012;
static int s_export_func() {
return 20;
}
int export_func() __attribute__((visibility("hidden")));
int export_func() {
return s_export_func();
}
void weak_export_func() __attribute__((weak_import));
makeStatic.sh
# 编译成.o 文件:
clang -c export.c -o ./staticlib/export.o
# 制作静态库:
ar rcs ./staticlib/libexport.a ./staticlib/export.o
# 链接 静态库:
# clang test.c -weak_library ./staticlib/libexport.a -L./staticlib -lexport -o ./static_test
clang test.c -L./staticlib -lexport -Xlinker -U -Xlinker _weak_export_func -o ./static_test
可以看到 动态库里面的 全局变量符号 g_export_1 等 和 局部符号 g_export_4_u_hidden 等 已经在 可执行文件 static_test 中;
2.2 动态库
makeDy.sh
# 创建动态库, @rpath/dylib/libdyExport.dylib 会先存着 libdyExport.dylib 中 =>
# 后面宿主文件 链接 它的时候,会把这个路径注入到宿主文件中
clang -Xlinker -install_name -Xlinker @rpath/dylib/libdyExport.dylib -dynamiclib -o ./dylib/libdyExport.dylib export.c
# image not found
clang test.c -L./dylib -ldyExport -o dy_test_crash
# 修复 (宿主中提供 @rpath的路径)
clang test.c -Xlinker -rpath -Xlinker @executable_path -L./dylib -ldyExport -o dy_test
# Undefined symbols : 如果宿主中使用了 动态库的 局部变量会报 这个错误
为什么需要使用@rpath/dylib/libdyExport.dylib,而静态库不需要?
- 动态库是一个独立的文件,所以动态库 需要告诉 可执行文件,
我在哪里? - rpath: 其实是一个变量,
run path,需要 可执行文件 指定,才能合成一个完整的路径;- 如果path 错误,编译没问题,运行的时候,会出现一个比较经典的错误
image not found
- 如果path 错误,编译没问题,运行的时候,会出现一个比较经典的错误
可以看到,我们的动态库的符号 全局变量符号 g_export_1 等 和 局部符号 g_export_4_u_hidden 等 是没有加载到可执行文件中;
是在动态库自己的符号表里面:
2.3 绑定动态库符号
接下来,我们使用一个简单的例子 NSLog 来说明:
NSLog(@"hello1");
NSLog(@"hello2");
Mach-O
-
Xcode => debug => debug workflow => Always show disamble 在 nslog 之前打个断点
-
打印出 运行基址:
运行基址 = 虚拟地址 + ASLR偏移二进制地址(MachView 查看) = 【运行地址】 - 【运行基址】
image list | grep "Demo1"
运行基址: 0x000000010c796000 (已经包括了ASLR)
ASLR: 0x000000010c796000 - 0x100000000(Macho的运行虚拟地址) = 0xc796000
虚拟基地址:
- 找到 NSLog 的 位置
0x10c797b00 <+80>: callq 0x10c798318 ; symbol stub for: NSLog
找到 nslog地址: 0x10c798318;
获得二进制的NSLog地址: p/x 【nslog地址】 - [基地址] = 0x10c798318 - 0x000000010c796000 = 0x2318
=> 去MachoView中查看
- 我们通过 nslog 0x10c798318 (入口地址) 反汇编一下,看看内部逻辑
解读指令 FF25 E25c0000
- FF25: jmpq 指令
- e25c 0000(大端) => 小端 0x00005ce2 => 0x5ce2 大端、小端模式: 例如: 0x1234, 指针P 先存0x12(高位) 为大端模式,反之 先存 0x34(低位) 为小端模式; 小端模式的好处就是 比如 高字节(long: 0x0000000055667788 ) 转 低字节(int 0x55667788), 会直接抛弃高位的; 0x5ce2 + rip寄存器 => 0x000000010c798370
4.1 查看 0x000000010c798370 指针在 Mach-o 中的地址
p/x 0x000000010c798370 - 0x000000010c796000(运行基址) = 0x2370
会进入 __stub_helper
下一步会执行 jmp 0x100002360(Macho的虚拟地址)
运行地址 = 0x100002360 (虚拟地址) + ASLR (0xc796000) = 0x000000010c798360,
然后给 这个点 加个断点:
br set -a 0x000000010c798360
点击下一步断点(也可以通过 si,在汇编调试模式下进入)
我们就很显然的看到 会先进入 _dyld_private 再进入 dyld_stub_binder 去执行 NSLog 的加载:
到这里基本上就是 nglog的 第一次加载过程;
- 第一个NSLog 执行后,我们看看 第二个 NSLog
0x10c797b00 <+80>: callq 0x10c798318 ; symbol stub for: NSLog
很明显看到 已经没有了 第一次的 _dyld_private, 直接进入的 Foundtion NSLog的函数地址;