https://blog.csdn.net/box_kun/article/details/122841727
Hi Coder,我是 CoderStar!
在新的一年里,祝小伙伴们工作顺利,升职加薪。
在咱们日常开发中,或多或少都会用到 Xcode 内置的一些CLI工具,但是大部分小伙伴可能只是会用到一些具体的命令,今天我们就一起来聊一聊 Xcode 内置的常见Command Lines Tool。
介绍的可能不全,大家可以去文中出现的路径下查看更多的工具。
Command Line Tool本质是一个命令行工具包,内部有很多有用的工具,如Apple LLVM compiler、Make等等。并且并不是只有开发 Apple 应用程序才需要用到这些工具包,当我们使用Homebrew在安装一些python库或者js库时,都会提示需要Command Line Tool。
下文会对Command Line Tool直接缩写成 CLI,XXX 一般情况是指对应路径地址。
我们在开发者官网 Command Line Tool 对其单独下载,当然每个版本的 Xcode 安装包内也会包含这套工具包。
其实下列有一部分工具属于 LLVM 序列,比如dwarfdump、ar,启动本质其实为llvm-dwarfdump、llvm-ar,都属于 LLVM 工具链中的一部分。
在我来介绍这套工具包其他工具之前,我先来介绍两个工具,我称它们为前置工具,因为有了这两个工具,我们才能更好的使用其他的工具。
这个工具可以帮助我们下载及安装 CLI,比手动下载更便捷。并且还能解决另外问题,就是如果我们装有多个 Xcode,我们在使用 CLI 相关工具时,系统就会不知道该去使用哪个版本或者哪个位置的 CLI,使用这个工具可以帮助我们设置及切换当前默认使用的 CLI。
介绍该工具常见的命令:
xcode-select --install: 安装 CLI,会安装到/Library/Developer/CommandLineTools/
xcode-select -p: 显示当前指定的工具包所在 Xcode 路径
xcode-select -s
xcode-select -r: 重置工具包所在 Xcode 路径
xcode-select提供了一个环境变量,让你能临时使用其他环境来执行xcode command,env DEVELOPER_DIR="/Applications/Xcode-beta.app" /usr/bin/xcodebuild
xcode-select 选择路径不是直接选择的 CLI 路径,而是选择所在 Xcode 的路径,继而使用该 Xcode 对应的 CLI,默认情况会选择到该 Xcode 包内包含的 CLI,但是如果我们通过 Xcode Preferences 调整过该 Xcode 对应的 CLI,就会使用调整后的 CLI。
这个工具应该是 Mac 自带的工具,位于/usr/bin/xcode-select,并不是跟随 CLI 工具包一块下载下来的。
回想我们过去在使用一些 CLI 命令的时候,会直接在终端上执行xcodebuild ...这样的方式,没有指定具体的 CLI 路径,并且我们执行which xcodebuild得到的结果是/usr/bin/xcodebuild。那这个命令是怎么执行到我们通过xcode-select设置的默认 CLI 路径下呢?那就得提到我们马上要介绍的这个工具了 -- xcrun。
我们就以xcodebuild举例,我们通过which xcodebuild得到的结果是/usr/bin/xcodebuild,也就是说我们在执行xcodebuild的时候实际上在执行usr/bin/xcodebuild,那再让我们看看/usr/bin/xcodebuild 下的指令是怎么配合xcode-select找到 /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild的?
我们先通过otool -tV /usr/bin/xcodebuild查看其textsection,得到:
(__TEXT,__text) section_main:0000000100003f63 pushq %rbp0000000100003f64 movq %rsp, %rbp0000000100003f67 pushq %r140000000100003f69 pushq %rbx0000000100003f6a movq %rsi, %r140000000100003f6d movl %edi, %ebx0000000100003f6f callq 0x100003f88 ## symbol stub for: __NSGetProgname0000000100003f74 movq (%rax), %rdi0000000100003f77 leal -0x1(%rbx), %esi0000000100003f7a leaq 0x8(%r14), %rdx0000000100003f7e movl $0x1, %ecx0000000100003f83 callq 0x100003f8e ## symbol stub for: _xcselect_invoke_xcrun复制代码
我们可以发现该命令调用_xcselect_invoke_xcrun函数。
然后我们通过nm /usr/bin/xcodebuild查看其name list
U __NSGetProgname0000000100008018 d __dyld_private0000000100000000 T __mh_execute_header0000000100003f63 t _main0000000100008010 s _shim_marker U _xcselect_invoke_xcrun U dyld_stub_binder复制代码
通过_xcselect_invoke_xcrun前面的U标识我们可以知道该函数是一个外部符号,是另外一个动态库去处理的。
后面我们通过 Swift-Swiftc 可以知道更详细流程,这里只说结论:
libxcselect.dylib_xcselect_invoke_xcrunlibxcrun.dylibxcrun_main复制代码
我们后面最后实际上会调用到xcrun_main函数,其内部就会根据xcode-select等设置的情况选择合适的 CLI 路径,具体执行的顺序可见 Developer Binaries on OS X, xcode-select and xcrun 。
xcrun(Xcode Command Line Tool Runner) 是 Xcode 基本的命令行工具,使用它来调用其他 CLI 工具,这时候你应该就知道为啥需要它来调用其他 CLI 工具了。
我们执行xcrun命令时实际上也是走的/usr/bin/xcrun,其内部也是上面一套流程,准确而言,在这套 CLI 工具包中位于/usr/bin路径下的命令都是上面那个流程,也就是说下面几个命令是等价的:
xcodebuild
/usr/bin/xcodebuild
xcrun xcodebuild
Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild
当然这套工具包有些命令不在/usr/bin路径下,我们就需要在命令前加上xcrun了,如swift-demangle,如果我们直接使用swift-demangle就会出现命令找不到的错误,使用xcrun swift-demangle这种方式即可。
相关命令:
xcrun --find clang // 找到指定工具路径
xcrun --sdk iphoneos --find pngcrush
xcrun --sdk macosx --show-sdk-path
通过xcode-select安装的 CLI 路径位于:/Library/Developer/CommandLineTools/。 Xcode 内嵌的 CLI 路径位于:/Applications/Xcode.app/Contents/Developer/usr/bin
还有一点需要注意的是,xcrun 并不是只会寻找xcode-select设置下的路径,还会寻找 Xcode 另外的一些路径来执行命令,包括
Developer/usr
Developer/Platforms
Developer/ToolChain
例子如下:
xcodebuild:/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild swift-demangle:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-demangle
深入浅出 Xcode Command Lines Tool - 初探 深入浅出 Xcode 命令列 - libxcselect.dylib 深入浅出 Xcode 命令列 - xcrun
关于这两个工具有开源的实现xcode-tools。
先简单介绍一下 DWARF 以及 dSYM。
DWARF 与 dSYM 的关系是,DWARF 是文件格式,而 dSYM 往往指一个单独的文件。在 Xcode 中如果不做特殊指定,debug information 是被保存在 executable 文件中。因为DWARF的存在我们才可以在 debug 时看到函数名称等信息,因为dSYM文件的存在,我们才可以符号化,解 Crash。
关于符号解析之前有过一篇文章 iOS 符号化解析。
作用:解析目标文件,存档和.dSYM 包中的 DWARF 节,并以人类可读的形式打印其内容; 使用场景:Crash 符号化; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dwarfdump;
# 使用示例dwarfdump -h# 查看 xx.app 文件的 UUIDdwarfdump --uuid xx.app/xx# 查看 xx.app.dSYM 文件的 UUIDdwarfdump --uuid xx.app.dSYM# 导出debug_info 的信息到文件 debug_line.txt 中dwarfdump --debug-info xx.app.dSYM > debug_info.txt# 出debug_line 的信息到文件 debug_line.txt 中dwarfdump --debug-line xx.app.dSYM > debug_line.txt# 校验DWARF的有效性dwarfdump --verify iOSTest.app.dSYM# 查找指定地址的相关信息# 一般用在Crash解析时dwarfdump --arch arm64 --lookup 0x100006694 iOSTest.app.dSYM复制代码
更多命令可见 llvm-dwarfdump。
作用:可以使用 dsymutil 从 二进制中 中提取 dSYM 文件以及对 dSYM 文件进行一些操作; 使用场景:当dSYM文件丢失后,可以将其作为找回dSYM文件的一种方式; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dsymutil;
# 从二进制文件中还有`DSYM`信息的二进制包中抽取形成`.dysm`文件dsymutil XXX# 使用指定的符号映射更新现有的 dSYM# 处理开启bitcode选项的dsym文件dsymutil -symbol-map /Users/XXXXX/Library/Developer/Xcode/Archives/2019-09-27/YYYY.xcarchive/BCSymbolMaps 0f1e9458-9741-36fb-b47c-694546728ea1.dSYM复制代码
作用:是一个perl脚本,里面整合了逐步解析的操作(可以将命令拷贝出来,直接进行调用); 场景:Crash 文件符号化; 路径:/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash;
# 需要先运行该命令,不然下面 symbolicatecrash命令会出现# Error: "DEVELOPER_DIR"isnot defined at ./symbolicatecrash line69.export DEVELOPER_DIR="/Applications/XCode.App/Contents/Developer"# 运行命令前需要将崩溃日志、 dSYM 以及 symbolicatecrash 复制到同一个目录下symbolicatecrash log.crash -d xxx.app.dSYM > symbol.log复制代码
作用:Crash 符号化; 路径:/Applications/Xcode.app/Contents/Developer/usr/bin/atos;
# 0x0000000100298000为 load address; 0x000000010029e694为 symbol address# 最后一个i表示显示内联函数atos -arch arm64 -o iOSTest.app.dSYM/Contents/Resources/DWARF/iOSTest -l 0x00000001002980000x000000010029e694 -i复制代码
作用:我们可以使用其对 Xcode 工程进行清理,分析,构建,测试,存档; 场景:CI 构建等; 路径:/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild;
可以通过man xcodebuild查看手册。
其中man命令就是用来查看指定命令的使用手册。
# 清理xcodebuild clean -workspace ${WORKSPACE_PATH} -scheme ${SCHEME_NAME} -configuration ${BUILD_TYPE}# 构建xcodebuild archive -workspace ${WORKSPACE_PATH} -scheme ${SCHEME_NAME} -archivePath ${ARCHIVE_PATH}## 存档xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath ${IPA_PATH} -exportOptionsPlist ${EXPORTOPTIONSPLIST_PATH}复制代码
xctool:xctool 是 facebook 推出的用于替换 xcodebuild 的更易于测试 iOS 和 mac 应用程序的命令行工具,特别适用于 iOS App 的持续集成;
xcbuild:xcbuild 是一个兼容 Xcode 的编译工具,它能使编译更快快速,更友好的编译过程日志,可以运行在多个平台(主要指 OS X 和 Linux);
作用:使用其验证 ipa 以及上传 ipa 到 Store; 路径:/Applications/Xcode.app/Contents/Developer/usr/bin/altool
# 验证# version、build号是否正确等casexcrun altool --validate-app -f xxx.ipa -t ios --apiKey xxx --apiIssuer xxx --verbose# 上传xcrun altool --upload-app -f xxx.ipa -t ios --apiKey xxx --apiIssuer xxx --verbose复制代码
作用:swift 语言的编译前端; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc;
swiftc只是一个替身,原身是swift-frontend。
作用:oc 语言的编译前端; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang;
LSP(Language-Server-Protocol)开源的语言服务器协定。由红帽、微软和 Codenvy 联合推出,可以让不同的程序编辑器与集成开发环境(IDE)方便嵌入各种程序语言,允许开发人员在最喜爱的工具中使用各种语言来撰写程序,SourceKit-LSP 是 Apple 维护的用于 Swift 的 LSP;其的存在允许我们使用其他 IDE 开发 Swift,如 VSCode;
路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp
作用:对 项目中 Assets 的文件进行压缩、处理,生成.car文件; 路径:/Applications/Xcode.app/Contents/Developer/usr/bin/actool;
actool 并非一个脚本,而是一个编译完成的二进制文件,所以compile asset catalog的过程是一个黑盒。
在 Swift 中因为命名空间的原因,需要对类名进行mangle,如果需要显示正确名称,自然也需要demangle。其实两个方法实现大家可以通过以下链接查看,
mangle:copySwiftV1MangledName 函数,
demangle:copySwiftV1DemangledName
当然 Apple 本身也为我们特意准备了一个 CLI 工具 --swift-demangle来方便我们。
命令:xcrun swift-demangle _TtC7iOSTest27PickImageDemoViewController结果:_TtC7iOSTest27PickImageDemoViewController ---> iOSTest.PickImageDemoViewController命令:xcrun swift-demangle --compact _TtC7iOSTest27PickImageDemoViewController结果:iOSTest.PickImageDemoViewController复制代码
我们还可以在 SwiftDemangle.h 看到 swift 底层该函数名称 -- swift_demangle_getDemangledName,项目中需要加入libswiftDemangle.tbd。
其实关于 Mach-O 操作的大部分工具都是 LLVM 下面的,包括otool、objdump、nm、dwarfdump等等,其命令本质上都是一个替身,背后实际上都是llvm-XXX命令的原身。
作用:nm 命令是 linux 下自带的特定文件分析工具,一般用来检查分析二进制文件、库文件、可执行文件中的符号表,返回二进制文件中各段的信息,查看二进制目标文件的符号,主要就是函数名称以及全局变量; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/nm;
# 得到XXX中的程序符号表nm XXX# 查看所有符号,会打印出符号来源哪个地方nm -nm XXX# 找到未定义的符号,也就是外部符号nm -u XXX复制代码
前面我们曾经查看过xcodebuild的符号,输出如下。
U __NSGetProgname0000000100008018 d __dyld_private0000000100000000 T __mh_execute_header0000000100003f63 t _main0000000100008010 s _shim_marker U _xcselect_invoke_xcrun U dyld_stub_binder复制代码
上述中间的大写字母就是后面对应符号的类型,其中全部的类型包括:
A 该符号的值在今后的链接中将不再改变;
B 该符号放在 BSS 段中,通常是那些未初始化的全局变量;
D 该符号放在普通的数据段中,通常是那些已经初始化的全局变量;
T 该符号放在代码段中,通常是那些全局非静态函数;
U 该符号未定义过,需要自其他对象文件中链接进来;
W 未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。
为什么要把这两个工具放到一起说呢?因为这两个工具之间有一定的关系。其实otool本质上就是objdump的一层 wrapper,底层其实都是使用objdump的实现。
比如 otool -L XXX 本质就等价 objdump --macho -dylibs-used XXX,更多详细的转换规则可见otool.html。
两者作用: 针对目标文件的展示工具,用来发现应用中使用到了哪些系统库,调用了其中哪些方法,使用了库中哪些对象及属性。
otool
路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool
MachOView 算是这个 CLI 工具的 GUI 工具了。
# 查看使用到哪些动态库,一般是涉及到 /usr/lib/ /System/Library/Frameworks/ @rpath 这三个位置,如果没有自己的动态库,就没有后面的 @rpathotool -L XXX# 筛选是否链接了xxx库otool -L XXX | grep "xxx"# 查看汇编码otool -tV XXX# 输出Object-C类结构以及定义的方法otool -ov XXX# 查看头部内容otool -h XXX# 查看 load commandsotool -l XXX# 查看该应用是否砸壳# 看输出结果的cryptid参数,其中0:砸壳、1:未砸壳。otool -l XXX | grep -B 2 crypt# 查看代码段起始地址otool -l iOSTest.app.dSYM/Contents/Resources/DWARF/iOSTest | grep __TEXT -C 5# 查看重定位符号表otool -r XXX复制代码
objdump
路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/objdump
# 查看mach-headerobjdump --macho -private-header XXX# 查看text段objdump --macho -d XXX# 查看符号表objdump --macho--syms XXX# 查看导出符号表objdump --macho--exports-trie XXX# 查看间接符号表objdump --macho--indirect-symbols XXX# 查看重定位符号表objdump --macho--reloc XXX复制代码
其实objdump的功能之一可以代替nm命令,其中objdump --macho --syms XXX也可以输出符号表。
作用:查看二进制文件中的字符串; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strings;
# 查看指定字符串strings XXX | grep"xxx"复制代码
lipo 源于 mac 系统要制作兼容 powerpc 平台和 intel 平台的程序,lipo 是一个在 Mac OS X 中处理通用程序(Universal Binaries)的工具。
### 查看查看静态库支持的 CPU 架构lipo -info frameworkName.framework/frameworkNamelipo -info frameworkName.a### 合并静态库lipo -create 静态库存放路径 1 静态库存放路径 2 ... -output 整合后存放的路径lipo -create frameworkName-armv7.a frameworkName-armv7s.a frameworkName-i386.a -output frameworkName.alipo -create frameworkNameOne.framework/frameworkNameOne frameworkNameTwo.framework/frameworkNameTwo -output frameworkName.framework### 静态库拆分lipo 静态库源文件路径 -thin CPU 架构名称 -output 拆分后文件存放路径lipo libname.a -thin armv7 -output libname-armv7.a### 擦除指定架构lipo -remove XXX.a arm64 -output XXX.a复制代码
作用:建立、修改静态库; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool;
ar -x XXX-d 删除备存文件中的成员文件。-m 变更成员文件在备存文件中的次序。-p 显示备存文件中的成员文件内容。-q 将问家附加在备存文件末端。-r 将文件插入备存文件中。-t 显示备存文件中所包含的文件。-x 自备存文件中取出成员文件。复制代码
我们可以使用file命令来区分动态库与静态库。
如file Flutter得到,我们可以很容易看到dynamically关键字,其为一个动态库
Flutter: Mach-O 64-bit dynamically linked shared library arm64复制代码
如 file CSPickerView,得到结果如下:CSPickerView 为一个静态库
CSPickerView: Mach-O universalbinarywith5 architectures: [i386:current ar archive] [arm_v7] [arm_v7s] [x86_64] [arm64]CSPickerView (for architecture i386): current ar archiveCSPickerView (for architecture armv7): current ar archiveCSPickerView (for architecture armv7s): current ar archiveCSPickerView (for architecture x86_64): current ar archiveCSPickerView (for architecture arm64): current ar archive复制代码
下载地址
这是一个命令行实用程序,用于检查存储在 Mach-O 文件中的 Objective-C 运行时信息。它为类、类别和协议生成声明。这与使用 'otool -ov' 提供的信息相同,但呈现为普通的 Objective-C 声明,因此更加紧凑和可读。
如果安装了MonkeyDev,内置了class-dump,就不用再特意去安装了。
当然,CLI 命令还有很多,这里只是列举了一些常见的,对于其他的,大家可以直接通过开头提到的一些路径去查找。
要更加努力呀!
Let's be CoderStar!
iOS 开发中常用命令工具(xcode-select、lipo、xcrun 等
Xcode 相关终端工具使用
Building from the Command Line with Xcode FAQ