命令行工具

290 阅读4分钟

命令行工具的本质

在日常开发过程中,我们使用了很多命令行工具,例如cdClutch等等,这些工具都可以在终端执行。而这些工具的本质其实就是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 命令后要紧跟权限文件,不能有空格