Mach-O初探和链接
1、Mach-O定义
- Mach-O(Mach Object)是macOS、iOS、iPadOS存储程序和库的⽂件格式。对应系统通过应⽤⼆进制接⼝(application binary interface,缩写为ABI)来运⾏该格式的⽂件。
- Mach-O格式⽤来替代BSD系统的a.out格式。Mach-O⽂件格式保存了在编译过程和链接过程中产⽣的机器代码和数据,从⽽为静态链接和动态链接的代码提供了单⼀⽂件格式
- 可执⾏⽂件(Mach-O)调⽤过程:
1. 调⽤`fork`函数,创建⼀个`process`
2. 调⽤`execve`或其衍⽣函数,在该进程上加载,执⾏我们的`Mach-O`⽂件
// 注:当我们调⽤时`execve`(程序加载器),内核实际上在执⾏以下操作:1. 将⽂件加载到内存 2. 开始分析`Mach-O`中的`mach_header`,以确认它是有效的`Mach-O`⽂件
- 问题:Mach-O中是不是都是二进制代码?是的,就是二进制代码。
- 终端中解析Mach-O文件命令:
objdump --macho --private-headers /users/zengca/..(Mach-o文件路径)
2、 链接
- 链接的本质就是把多个⽬标⽂件组合成⼀个⽂件
-
链接过程分析图
3、Symbol Table(符号表)
- 1、符号表定义:
符号表可以提供应用软件及其动态加载的库中引用的函数、类、方法等信息。通常,符号表中包含比类转储更多的信息,并且还包含了应用软件使用的C或者C++ 组件的信息。就好比你要去某个城市某个地方,首先你得需要一张地图才能找得到某个地方吧。符号表就是这张地图。
Symbol Table:就是⽤来保存符号。
String Table:就是⽤来保存符号的名称。
Indirect Symbol Table:间接符号表。保存使⽤的外部符号。更准确⼀点就是使⽤的外部动态库的符号。是Symbol Table的⼦集。
- 2、nm命令
- 作用:nm命令作用就是显示目标文件的符号表,文件对象可以是静态库、动态库、编译目标文件、可执行程序等任何包含符号信息的文件类型。
- 功能:nm命令可以列出一个函数库文件(.o .a .so)中的符号表。所谓符号,通常指定义出的函数,全局变量等等。它对于静态的函数库和共享的函数库都起作用。nm命令显示的信息用于调试库和可执行文件,对于一个给定的函数库,nm命令可以列出函数库中定义的所有符号,包括每个符号的值和类型。还可以给出在原程序中这个函数(符号)是在多少行定义的,不过这必须要求编译该函数库的时候加“-l”选项。
- 使用:nm [选项] [文件列表(库文件/目标文件/可执行文件)]
$ nm helloworld
0000bf08 s stub helpers
0000be00 t -[SaySomething say:]
U _NSClassFromString
U _NSSelectorFromString
0000c110 S _NXArgc
0000c114 S _NXArgv
U _OBJC_CLASS_$_NSObject
0000c0fc S _OBJC_CLASS_$_SaySomething
U _OBJC_METACLASS_$_NSObject
0000c0e8 S _OBJC_METACLASS_$_SaySomething
U ___CFConstantStringClassReference
0000c11c S ___progname
00004000 T __mh_execute_header
U __objc_empty_cache
0000c118 S _environ
U _exit
0000be44 T _main
U _objc_autoreleasePoolPop
U _objc_autoreleasePoolPush
U _objc_msgSend
U _printf
0000c018 s _pvars
U dyld_stub_binder
0000bdd4 T start
- 有用的options:
第一列就是内存地址,没有什么好解释的。
第二列这些符号都什么意思呢?
对于每一个富豪来说,其类型如果是小写的,则表明符号是local的;大写则表明符号是global 的。
A 该符号的值是绝对的,在以后的链接过程中,不允许进行改变,这样的符号之,尝尝出现在中断向量(终端服务程序的入口地址)中,列入用符号来表示中断向量函数在中断 向量表中的位置。
B 该符号的值出现在非初始化数据段中(bss)。例如,在一个文件中定义全局static int a。则该响亮符号 a 的类型为b,位于bss section 中。
C 该符号为common。common symbol 是未初始化数据段。该符号没有包含于一个普通section 中。只有在连接过程中才进行分配。符号的指标是该符号的字节数。
D 该符号位于初始化数据段中。一般来说。分配到data section 中。例如,定义 int arr[5] = {9600,19200,28400,57600,115200},会分配到初始化数据段中。
G 该符号也位于初始化数据段中。主要用于small Object 提高访问small Object 的一种方式。
I 该符号是对另一个符号的间接引用。
N 该符号位于只读数据区,例如定义全局const int test[] = {123,123};则test 就是一个只读数据区的符号。
S 该符号位于非初始化数据区,用于small Object。
T 该符号位于代码区 text section 。
U 该符号在当前文件中是未定义的,即该符号的定义在别的文件中。
V 该符号是一个weak Object 。
好了,现在来解释一下上边的一个具体信息。
0000be00 t -[SaySomething say:]
U _NSClassFromString
U _NSSelectorFromString
1、表示位于0x0000be00 地址中,say 方法是在本文件有定义的,所以是小写的t,其次,由于OC 是种动态语言,在调用方法时,根据方法名称来确定方法的唯一性,因此类和方法的名称必须存放在一个定法 ,如上SaySomething 类和say 方法存放在代码区,因此,他们的符号为t。
2、NSClassFromString 和NSSelectorFromString 方法是从Objective-C 语言库中动态加载起来的。在这个文件中没有定义,所以他们的符号为U。
4、重定向
Target—>Build Phases—>Run Script—>echo "hello" >/dev/ttys000
注:/dev/ttys000 是当前终端的名称,在终端中执行tty就可以看到当前终端名称
5、使用脚本运行命令
- (1)这里提前做好了一个脚本文件xcode_run_cmd.sh,内容如下:
#!/bin/sh
RunCommand() {
#判断全局字符串VERBOSE_SCRIPT_LOGGING是否为空。-n string判断字符串是否非空
#[[是 bash 程序语言的关键字。用于判断
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
#作为一个字符串输出所有参数。使用时加引号"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数
if [[ -n "$TTY" ]]; then
echo "♦ $@" 1>$TTY
else
echo "♦ $*"
fi
echo "------------------------------------------------------------------------------" 1>$TTY
fi
#与$*相同。但是使用时加引号,并在引号中返回每个参数。"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数
if [[ -n "$TTY" ]]; then
echo `$@ &>$TTY`
else
"$@"
fi
#显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
return $?
}
EchoError() {
#在shell脚本中,默认情况下,总是有三个文件处于打开状态,标准输入(键盘输入)、标准输出(输出到屏幕)、标准错误(也是输出到屏幕),它们分别对应的文件描述符是0,1,2
# > 默认为标准输出重定向,与 1> 相同
# 2>&1 意思是把 标准错误输出 重定向到 标准输出.
# &>file 意思是把标准输出 和 标准错误输出 都重定向到文件file中
# 1>&2 将标准输出重定向到标准错误输出。实际上就是打印所有参数已标准错误格式
if [[ -n "$TTY" ]]; then
echo "$@" 1>&2>$TTY
else
echo "$@" 1>&2
fi
}
RunCMDToTTY() {
if [[ ! -e "$TTY" ]]; then
EchoError "=========================================="
EchoError "ERROR: Not Config tty to output."
exit -1
fi
if [[ -n "$CMD" ]]; then
RunCommand "$CMD" ${CMD_FLAG}
else
EchoError "=========================================="
EchoError "ERROR:Failed to run CMD. THE CMD must not null"
fi
}
RunCMDToTTY
-
(2)将脚本文件配置到Run Script中如下图:
- (3)xcconfig文件中配置脚本文件需要的参数
- 创建xcconfig文件,创建过程如上述,xcconfig创建和配置。这里我们创建的是RunCMD.xcconfig文件,将其配置在Debug模式下。内容如下:
// TTY = 终端
// CMD = 运行的命令
// CMD_FLAG = 运行命令的参数
CMD = nm
CMD_FLAG = -pa ${MACHO_PATH}
TTY = /dev/ttys000
MACHO_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/*
// nm
// -p: 不排序
// -a: 显示所有符号,除了调试符号
// -m:显示(N_SECT)符号,格式(seg-ment_name, section_name) (external non-
// external) (undefined), (common), (absolute) (indirect)
// objdump
// --macho --syms
// --exports-trie :导出符号
// -Xlinker 在GCC中用来给连接器传递参数,每次只能传递一个参数(如:-Xlinker -S ),如果传递多个参数需多次传递(如:-Xlinker -S -Xlinker _cat_NSLog或可简写为-Xlinker -S=_cat_NSLog)。
// -Wl 在GCC中用来给连接器传递参数,可以传递多个参数(如:-Wl,-Map,output.map)。
// -S 去除输出文件中的调试信息
// -O1 -Oz 生成目标文件 编译时优化
// 后面两步就是在操作符号
// dead code strip 死代码剥离实际上也是在剥离符号 链接时
// strip 剥离符号 修改已经生成的Mach-O文件
运行结果:
Last login: Mon Jan 18 13:42:42 on ttys000
zengcai@Mac-mini ~ % 0000000100003f40 t _weak_hidden_function
0000000100008008 d __dyld_private
0000000100008014 d _static_init_value
0000000100008018 d _hidden_y
0000000100008030 b _static_uninit_value
0000000100008040 s _default_x
0000000000000000 - 00 0000 SO /Users/zengcai/Downloads/逻辑学习/强化班第一节课资料/完成代码/MachOAndSymbol/
0000000000000000 - 00 0000 SO main.m
0000000060050b13 - 03 0001 OSO /Users/zengcai/Library/Developer/Xcode/DerivedData/MachOAndSymbol-aiyqlpfqaarxnvcresdhogckdoqi/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
0000000100003ec0 - 01 0000 BNSYM
0000000100003ec0 - 01 0000 FUN _main
000000000000003f - 00 0000 FUN
000000000000003f - 01 0000 ENSYM
0000000000000000 - 00 0000 GSYM _global_init_value
0000000100008014 - 0a 0000 STSYM _static_init_value
0000000100008030 - 0b 0000 STSYM _static_uninit_value
0000000000000000 - 00 0000 GSYM _global_uninit_value
0000000000000000 - 00 0000 GSYM _default_x
0000000000000000 - 01 0000 SO
0000000000000000 - 00 0000 SO /Users/zengcai/Downloads/逻辑学习/强化班第一节课资料/完成代码/MachOAndSymbol/
0000000000000000 - 00 0000 SO WeakImportSymbol.m
0000000060052666 - 03 0001 OSO /Users/zengcai/Library/Developer/Xcode/DerivedData/MachOAndSymbol-aiyqlpfqaarxnvcresdhogckdoqi/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/WeakImportSymbol.o
0000000100003f00 - 01 0000 BNSYM
0000000100003f00 - 01 0000 FUN _weak_import_function
0000000000000017 - 00 0000 FUN
0000000000000017 - 01 0000 ENSYM
0000000000000000 - 01 0000 SO
0000000000000000 - 00 0000 SO /Users/zengcai/Downloads/逻辑学习/强化班第一节课资料/完成代码/MachOAndSymbol/
0000000000000000 - 00 0000 SO WeakSymbol.m
0000000060050b12 - 03 0001 OSO /Users/zengcai/Library/Developer/Xcode/DerivedData/MachOAndSymbol-aiyqlpfqaarxnvcresdhogckdoqi/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/WeakSymbol.o
0000000100003f20 - 01 0000 BNSYM
0000000100003f20 - 01 0000 FUN _weak_function
0000000000000020 - 00 0000 FUN
0000000000000020 - 01 0000 ENSYM
0000000100003f40 - 01 0000 BNSYM
0000000100003f40 - 01 0000 FUN _weak_hidden_function
0000000000000017 - 00 0000 FUN
0000000000000017 - 01 0000 ENSYM
0000000000000000 - 01 0000 SO
0000000000000000 - 00 0000 SO /Users/zengcai/Downloads/逻辑学习/强化班第一节课资料/完成代码/MachOAndSymbol/
0000000000000000 - 00 0000 SO VisibilitySymbol.m
0000000060050b0f - 03 0001 OSO /Users/zengcai/Library/Developer/Xcode/DerivedData/MachOAndSymbol-aiyqlpfqaarxnvcresdhogckdoqi/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/VisibilitySymbol.o
0000000000000000 - 00 0000 GSYM _hidden_y
0000000000000000 - 00 0000 GSYM _default_y
0000000000000000 - 00 0000 GSYM _protected_y
0000000000000000 - 01 0000 SO
0000000100000000 T __mh_execute_header
0000000100008020 D _default_y
0000000100008010 D _global_init_value
0000000100008038 S _global_uninit_value
0000000100003ec0 T _main
0000000100008028 D _protected_y
0000000100003f20 T _weak_function
0000000100003f00 T _weak_import_function
U _NSLog
U ___CFConstantStringClassReference
U dyld_stub_binder
0000000100003f40 t _weak_hidden_function
0000000100008008 d __dyld_private
0000000100008014 d _static_init_value
0000000100008018 d _hidden_y
0000000100008030 b _static_uninit_value
0000000100008040 s _default_x
0000000000000000 - 00 0000 SO /Users/zengcai/Downloads/逻辑学习/强化班第一节课资料/完成代码/MachOAndSymbol/
0000000000000000 - 00 0000 SO main.m
000000006005268b - 03 0001 OSO /Users/zengcai/Library/Developer/Xcode/DerivedData/MachOAndSymbol-aiyqlpfqaarxnvcresdhogckdoqi/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
0000000100003ec0 - 01 0000 BNSYM
0000000100003ec0 - 01 0000 FUN _main
000000000000003f - 00 0000 FUN
000000000000003f - 01 0000 ENSYM
0000000000000000 - 00 0000 GSYM _global_init_value
0000000100008014 - 0a 0000 STSYM _static_init_value
0000000100008030 - 0b 0000 STSYM _static_uninit_value
0000000000000000 - 00 0000 GSYM _global_uninit_value
0000000000000000 - 00 0000 GSYM _default_x
0000000000000000 - 01 0000 SO
0000000000000000 - 00 0000 SO /Users/zengcai/Downloads/逻辑学习/强化班第一节课资料/完成代码/MachOAndSymbol/
0000000000000000 - 00 0000 SO WeakImportSymbol.m
000000006005268b - 03 0001 OSO /Users/zengcai/Library/Developer/Xcode/DerivedData/MachOAndSymbol-aiyqlpfqaarxnvcresdhogckdoqi/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/WeakImportSymbol.o
0000000100003f00 - 01 0000 BNSYM
0000000100003f00 - 01 0000 FUN _weak_import_function
0000000000000017 - 00 0000 FUN
0000000000000017 - 01 0000 ENSYM
0000000000000000 - 01 0000 SO
0000000000000000 - 00 0000 SO /Users/zengcai/Downloads/逻辑学习/强化班第一节课资料/完成代码/MachOAndSymbol/
0000000000000000 - 00 0000 SO WeakSymbol.m
000000006005268b - 03 0001 OSO /Users/zengcai/Library/Developer/Xcode/DerivedData/MachOAndSymbol-aiyqlpfqaarxnvcresdhogckdoqi/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/WeakSymbol.o
0000000100003f20 - 01 0000 BNSYM
0000000100003f20 - 01 0000 FUN _weak_function
0000000000000020 - 00 0000 FUN
0000000000000020 - 01 0000 ENSYM
0000000100003f40 - 01 0000 BNSYM
0000000100003f40 - 01 0000 FUN _weak_hidden_function
0000000000000017 - 00 0000 FUN
0000000000000017 - 01 0000 ENSYM
0000000000000000 - 01 0000 SO
0000000000000000 - 00 0000 SO /Users/zengcai/Downloads/逻辑学习/强化班第一节课资料/完成代码/MachOAndSymbol/
0000000000000000 - 00 0000 SO VisibilitySymbol.m
000000006005268b - 03 0001 OSO /Users/zengcai/Library/Developer/Xcode/DerivedData/MachOAndSymbol-aiyqlpfqaarxnvcresdhogckdoqi/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/VisibilitySymbol.o
0000000000000000 - 00 0000 GSYM _hidden_y
0000000000000000 - 00 0000 GSYM _default_y
0000000000000000 - 00 0000 GSYM _protected_y
0000000000000000 - 01 0000 SO
0000000100000000 T __mh_execute_header
0000000100008020 D _default_y
0000000100008010 D _global_init_value
0000000100008038 S _global_uninit_value
0000000100003ec0 T _main
0000000100008028 D _protected_y
0000000100003f20 T _weak_function
0000000100003f00 T _weak_import_function
U _NSLog
U ___CFConstantStringClassReference
U dyld_stub_binder
- 其中包含太多调试信息,我们可以使用-S命令去除调试信息,所以在RunCMD.xcconfig文件添加:OTHER_LDFLAGS = -Xlinker -S 具体如下:
CMD = nm
CMD_FLAG = -pa ${MACHO_PATH}
TTY = /dev/ttys000
OTHER_LDFLAGS = -Xlinker -S
MACHO_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/*
运行结果:
0000000100003f40 t _weak_hidden_function
0000000100008008 d __dyld_private
0000000100008014 d _static_init_value
0000000100008018 d _hidden_y
0000000100008030 b _static_uninit_value
0000000100008040 s _default_x
0000000100000000 T __mh_execute_header
0000000100008020 D _default_y
0000000100008010 D _global_init_value
0000000100008038 S _global_uninit_value
0000000100003ec0 T _main
0000000100008028 D _protected_y
0000000100003f20 T _weak_function
0000000100003f00 T _weak_import_function
U _NSLog
U ___CFConstantStringClassReference
U dyld_stub_binder
- 总结:上面是通过运行脚本的方式执行命令,需要通过xcconfig文件配置脚本所需参数,同时可以在xcconfig文件添加符号来进行操作。通过符号可以用来优化包体大小等多种用图。