主要内容:
Mach-O的读写特性- 查看命令文档
- 查看
Mach-O - 查看
Symbol - 配置
Mach-O终端调试工具
一、Mach-O的读写特性
Mach-O文件其实等价于: 文件配置 + 二进制代码;
Mach-O文件是可读可写的,分析Mach-O文件的结构,可以解释程序运行中的许多问题;
1. Mach-O可读
Header:提供读取所需的身份信息,包括CPU类型、文件类型等;Load Commands:提供读取所需的文件的信息,以Load Command _TEXT为例,其中记录有_TEXT代码段的起始位置;_TEXT代码段的大小;
__TEXT,__DATA,符号表等,这些都是代码编译后的文件、数据存放的位置;
2.Mach-O可写
Mach-O能被执行是因为有签名,当我们修改了Mach-O文件,需要重新签名才能被苹果系统所接受;- 破解软件都需要重新签名,正是这种原因;
二、查看命令文档
有时候,需要在终端查询一些命令的具体用法,有两种方式(以查询nm命令为例):
man nm //推荐使用
nm --help
当终端里显示nm的使用文档后,还可快速定位查询,以查询"-a"参数为例:
/-a:快速匹配文档中-a的位置;n:向下定位-a位置;N:向上定位-a位置;q:退出文档查看;
三、查看Mach-O
1.常用的查看命令
// 查看Header(${MACH_PATH}表示Mach-O文件位置)
objdump --macho --private-header ${MACH_PATH}
// 查看Header 和 Load commands
objdump --macho --private-headers ${MACH_PATH}
// 查看__TEXT
objdump --macho -d ${MACH_PATH}
// 查看符号表
objdump --macho --syms ${MACH_PATH}
// 查看导出符号
objdump --macho --exports-trie ${MACH_PATH}
// 查看间接符号表
objdump --macho --indirect-symbols ${MACH_PATH}
2.测试查看命令
为了避免过多文件的干扰,创建测试工程时,选择选择MacOS ->Command Line Tool;
进入工程后,在main.m文件中写入代码如下:
#import <Foundation/Foundation.h>
int global_init_value = 10;
static int static_init_value = 9;
int main(int argc, const char * argv[]) {
NSLog(@"%d", static_init_value);
return 0;
}
Xcode运行成功后,Products下的黑色文件即Mach-O可执行文件,如下图:
切换到可执行文件的目录下,使用objdump命令,查看Header:
objdump --macho --private-header MachOTest
终端的打印结果如下:
使用objdump命令,查看Header以及Load commands:
objdump --macho --private-headers MachOTest
终端的打印结果如下:
如上,我们在Mach-O文件的Load Commands中,可以找到程序入口函数以及依赖系统动态库的相关信息;
四、查看Symbol
查看符号,除了可以使用objdump命令以外,还可以使用nm命令;
1.nm命令简介
nm是name的缩写,它显示指定文件中的符号信息,文件可以是对象文件、可执行文件或对象文件库;- 如果文件中没有包含符号信息,
nm报告该情况,但不把他解释为出错; nm缺省情况下报告十进制符号表示法下的数字值;
可以在终端使用man nm命令查看其具体用法:
% man nm
nm - display name list (symbol table)
即,nm是一个可以查看展示符号表信息的命令。
2.常用命令参数
-a //显示符号表所有内容
-p //不排序,显示符号本来的顺序
-g //显示全局符号
-r //逆转排序
-u //显示未定义符号
-m //显示N_SECT类型的符号(Mach-O符号)显示
3.使用举例
使用nm命令,查看MachO可执行文件中的符号:
nm -pa ${MACH_PATH}
终端的打印结果如下:
其中,第二列符号标签的含义如下:
注:标记①的Type,⼩写代表本地符号(local symbol);
五、配置Mach-O终端测试工具
如上的调试过程十分繁琐,每次都需要等到程序运行完成,然后再切换到终端测试命令。为了更方便分析Mach-O,这里可以对工程进一步配置,使其运行时可直接将结果打印在终端里,具体的步骤如下:
1.测试将Xcode打印重定向到终端
1.新建一个终端,使用tty命令获取其标识位置:
2.在Xcode中依次打开: Build Phases ->Run Script,输入脚本命令使其能够在运行时打印:
3.运行工程,可以看到终端的打印如下
2.测试Xcode脚本命令执行项目中的代码
虽然成功打印信息到终端,但是"Hello world"这段信息却是固定的,为了动态获取执行参数,我们可以自定义配置文件,并在其中定义如下的参数:
DEBUG_URL = www.debug.com
然后,修改Run Script:
echo "Hello World" > /dev/ttys000
echo ${DEBUG_URL} > /dev/ttys000
终端打印结果:
Hello World
www.debug.com
这里就说明,Xcode脚本执行与配置文件是在同一个环境下,我们可以做到将配置文件中的各种参数传递给脚本中使用。
3. 实现通过脚本命令打印符号信息
在具备以上条件后,可以实现通过脚本命令打印符号信息到终端,具体的做法如下:
将执行命令的操作定义在一个脚本文件中,并起名xcode_run_cmd.sh,其中的关键代码如下:
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
}
EchoError() {
if [[ -n "$TTY" ]]; then
echo "$@" 1>&2>$TTY
else
echo "$@" 1>&2
fi
}
RunCommand() {
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
if [[ -n "$TTY" ]]; then
echo "♦ $@" 1>$TTY
else
echo "♦ $*"
fi
echo "-----------------------------------------" 1>$TTY
fi
if [[ -n "$TTY" ]]; then
echo `$@ &>$TTY`
else
"$@"
fi
return $?
}
RunCMDToTTY
然后,在配置文件里增加三个参数:CMD,CMD_FLAG,TTY,即需要在.xcconfig中进行定义:
// Config-TestProject.debug.xcconfig
// DEBUG_URL = www.debug.com
// MACHO_PATH:Mach-O文件路径,即可执行文件路径,这里用到了环境变量来表示具体路径
// TTY = 终端位置标识
// CMD:运行命令
// CMD_FLAG = 命令参数
MACHO_PATH = ${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PRODUCT_NAME}.app/${PRODUCT_NAME}
TTY = /dev/ttys000
CMD = nm
CMD_FLAG = -pa ${MACHO_PATH}
配置完成后的工程如下:
最后,我们需要更改Run Script指令,使其执行脚本文件:
此时,再次运行项目,就可以直接在控制台看到使用nm命令分析得到的Mach-O信息了,而且我们可以更换CMD,CMD_FLAG参数,用以调试更多的其他命令。