命令行工具的本质
在日常开发过程中,我们使用了很多命令行工具,例如cd、Clutch等等,这些工具都可以在终端执行。而这些工具的本质其实就是Mach-O类型的可执行文件。
命令行工具的开发
我们可以通过Xcode编译一个项目,在生成的.app包中就能找到该项目的可执行文件,通过这种方式生成的可执行文件可以直接运行在iPhone上。
- 创建ios项目,删除多余的文件

- 编译项目,在
Products/.app中可以找到生成的可执行文件 - 将可执行文件通过iFunBox拷贝到iPhone的/usr/bin目录下。
- 连接iPhone,在命令行输入工具名,执行命令,发现没有执行权限
5s:~ root# CommandToolDemo
-sh: /usr/bin/CommandToolDemo: Permission denied
- 给命令行工具增加可执行权限
5s:~ root# chmod +x /usr/bin/CommandToolDemo
- 执行命令,报下面错误
5s:~ root# CommandToolDemo
-sh: /usr/bin/CommandToolDemo: Bad CPU type in executable
- 原因是因为编译的时候没有选择真机,选择真机编译,重新拖入到iPhone的/usr/bin/目录下,再赋于可执行权限,该命令行工具就可以运行在终端了
获取可执行文件的架构
-
给定一个iPhone上的可执行文件路径,利用我们自己开发的命令行工具,读取可执行文件,并且判断出可执行文件包含的架构类型,并打印。
-
在完成需求之前,我们首先要了解Mach-O文件的内部构造,使用MachOView工具,查看Mach-O文件的内部构造

-
可以发现,Mach-O文件的头部Header里,第一行就表示当前Mach-O文件何种二进制文件。而第二行则表示当前Mach-O文件包含哪几种架构。所以我们想要判断当前的Mach-O文件是何种类型,只需要读取Mach-O文件的前四个字节的数据进行判断即可。
-
根据什么条件来判断Mach-O文件属于哪种架构类型?通过查看xnu源码,在EXTERNAL_HEADERS/mach-o/fat.h和EXTERNAL_HEADERS/mach-o/loader.h文件中可以发现对应几种常用架构类型的定义

- 上图中FAT_MAGIC和FAT_CIGAM代表通用二进制文件类型,通过以下结构体可以看出FAT_MAGIC和FAT_CIGAM是uint32_t类型

- 上图是loader.h中定义的文件类型,其中MH_MAGIC和MH_CIGAM代表非64位的架构类型,也就是armv7或者armv7s类型的架构,MH_MAGIC_64和MH_CIGAM_64代表64位类型的架构,也就是arm64架构
通过查看源码可以发现一个问题,上图类型定义都有两种,而且对应的值完全相反。其实这就是大小端模式:
大端模式:数据的高字节保存到内存的低地址中,数据的低字节保存到内存的高地址中。
小端模式:数据的高字节保存到内存的高地址中,数据的低字节保存到内存的低地址中。
这就导致在读取数据的时候可以从高地址向低地址读取,也可以从低地址向高地址读取,所以就存在上图两种定义
- 首先创建命令行项目,去除多余文件之后,在
main.m中添加如下代码
#import <UIKit/UIKit.h>
#import <mach-o/loader.h>
#import <mach-o/fat.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
NSString *pathStr = @"/var/containers/Bundle/Application/19EDB123-E540-4570-90FC-50768A917A43/WeChat.app/WeChat";
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:pathStr];
NSUInteger length = sizeof(uint32_t);
//读取uint32_t长度的字节数
NSData *magicData = [handle readDataOfLength:length];
//将data数据存放到magicNumber中
uint32_t magicNumber;
[magicData getBytes:&magicNumber length:length];
if (magicNumber == FAT_MAGIC || magicNumber == FAT_CIGAM) { //通用二进制文件
printf("Universal Binary\n");
}else if (magicNumber == MH_MAGIC_64 || magicNumber == MH_CIGAM_64){//64位架构
printf("64 bit Binary\n");
}else if (magicNumber == MH_MAGIC || magicNumber == MH_CIGAM){//非64位架构
printf("32 bit Binary\n");
}else{
printf("读取失败\n");
}
printf("magicNumber = 0x%x\n", magicNumber);
[handle closeFile];
return 0;
}
}
- 将可执行文件放到iPhone的/usr/bin/目录下,执行命令:
5s:~ root# CommandToolDemo
64 bit Binary
magicNumber = 0xfeedfacf
- 可以看出在我们手机上微信App的可执行文件是arm64架构的
给命令行工具增加参数

int main(int argc, char * argv[])
argc表示参数个数
argv[]存放参数(默认有一个参数,就是当前命令行工具的路径"/var/containers/Bundle/Application/615972A2-4B62-4B89-A572-590B4FA2485E/CommandToolDemo.app/CommandToolDemo")
命令行工具的权限问题
- 默认情况下开发的命令行工具是没有权限访问其他App的文件的
- 签名->给可执行文件签上一定的权限,让他可以访问其他App的可执行文件
- 常用的签名工具:ldib和codesign
权限
- 权限:entitlements
- 查看权限:使用ldid将CommandToolDemo的权限导出到CommandTool.entitlements文件中,CommandTool.entitlements其实就是一个plist,也就是一个xml文件。
uhoodeMacBook-Pro:命令行工具 pinba$ ldid -e CommandToolDemo > CommandTool.entitlements
- 给它增加权限就是在plist中增加对应的key-value
- 当我们不知道需要增加什么权限时,可以用系统的SpringBoard应用的权限(就是相对比较多的权限)
ldid -SSpringBoard.entitlements CommandToolDemo
-S 命令后要紧跟权限文件,不能有空格