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[1] 对其单独下载,当然每个版本的 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 <path>
: 切换默认工具包所在 Xcode 路径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 %rbp
0000000100003f64 movq %rsp, %rbp
0000000100003f67 pushq %r14
0000000100003f69 pushq %rbx
0000000100003f6a movq %rsi, %r14
0000000100003f6d movl %edi, %ebx
0000000100003f6f callq 0x100003f88 ## symbol stub for: __NSGetProgname
0000000100003f74 movq (%rax), %rdi
0000000100003f77 leal -0x1(%rbx), %esi
0000000100003f7a leaq 0x8(%r14), %rdx
0000000100003f7e movl $0x1, %ecx
0000000100003f83 callq 0x100003f8e ## symbol stub for: _xcselect_invoke_xcrun
我们可以发现该命令调用_xcselect_invoke_xcrun
函数。
然后我们通过nm /usr/bin/xcodebuild
查看其name list
U __NSGetProgname
0000000100008018 d __dyld_private
0000000100000000 T __mh_execute_header
0000000100003f63 t _main
0000000100008010 s _shim_marker
U _xcselect_invoke_xcrun
U dyld_stub_binder
通过_xcselect_invoke_xcrun
前面的U
标识我们可以知道该函数是一个外部符号,是另外一个动态库去处理的。
后面我们通过 Swift-Swiftc[2] 可以知道更详细流程,这里只说结论:
libxcselect.dylib
👇🏻
_xcselect_invoke_xcrun
👇🏻
libxcrun.dylib
👇🏻
xcrun_main
我们后面最后实际上会调用到xcrun_main
函数,其内部就会根据xcode-select
等设置的情况选择合适的 CLI 路径,具体执行的顺序可见 Developer Binaries on OS X, xcode-select and xcrun[3] 。
xcrun(Xcode Command Line Tool Runner
) 是 Xcode 基本的命令行工具,使用它来调用其他 CLI 工具,这时候你应该就知道为啥需要它来调用其他 CLI 工具了。
我们执行xcrun
命令时实际上也是走的/usr/bin/xcrun
,其内部也是上面一套流程,准确而言,在这套 CLI 工具包中位于/usr/bin
路径下的命令都是上面那个流程,也就是说下面几个命令是等价的:
当然这套工具包有些命令不在/usr/bin
路径下,我们就需要在命令前加上xcrun
了,如swift-demangle
,如果我们直接使用swift-demangle
就会出现命令找不到的错误,使用xcrun swift-demangle
这种方式即可。
相关命令:
通过xcode-select
安装的 CLI 路径位于:/Library/Developer/CommandLineTools/
。Xcode 内嵌的 CLI 路径位于:/Applications/Xcode.app/Contents/Developer/usr/bin
还有一点需要注意的是,xcrun 并不是只会寻找xcode-select
设置下的路径,还会寻找 Xcode 另外的一些路径来执行命令,包括
例子如下:
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 - 初探[4]深入浅出 Xcode 命令列 - libxcselect.dylib[5]深入浅出 Xcode 命令列 - xcrun[6]
关于这两个工具有开源的实现xcode-tools[7]。
先简单介绍一下 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 文件的 UUID
dwarfdump --uuid xx.app/xx
# 查看 xx.app.dSYM 文件的 UUID
dwarfdump --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[8]。
作用:可以使用 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" is not defined at ./symbolicatecrash line 69.
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 0x0000000100298000 0x000000010029e694 -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号是否正确等case
xcrun 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 函数[9],
demangle
:copySwiftV1DemangledName[10]
当然 Apple 本身也为我们特意准备了一个 CLI 工具 --swift-demangle
来方便我们。
命令:xcrun swift-demangle _TtC7iOSTest27PickImageDemoViewController
结果:_TtC7iOSTest27PickImageDemoViewController ---> iOSTest.PickImageDemoViewController
命令:xcrun swift-demangle --compact _TtC7iOSTest27PickImageDemoViewController
结果:iOSTest.PickImageDemoViewController
我们还可以在 SwiftDemangle.h[11] 看到 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 __NSGetProgname
0000000100008018 d __dyld_private
0000000100000000 T __mh_execute_header
0000000100003f63 t _main
0000000100008010 s _shim_marker
U _xcselect_invoke_xcrun
U dyld_stub_binder
上述中间的大写字母就是后面对应符号的类型,其中全部的类型包括:
为什么要把这两个工具放到一起说呢?因为这两个工具之间有一定的关系。其实otool
本质上就是objdump
的一层 wrapper,底层其实都是使用objdump
的实现。
比如 otool -L XXX
本质就等价 objdump --macho -dylibs-used XXX
,更多详细的转换规则可见otool.html[12]。
两者作用:针对目标文件的展示工具,用来发现应用中使用到了哪些系统库,调用了其中哪些方法,使用了库中哪些对象及属性。
路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool
MachOView 算是这个 CLI 工具的 GUI 工具了。
# 查看使用到哪些动态库,一般是涉及到 /usr/lib/ /System/Library/Frameworks/ @rpath 这三个位置,如果没有自己的动态库,就没有后面的 @rpath
otool -L XXX
# 筛选是否链接了xxx库
otool -L XXX | grep "xxx"
# 查看汇编码
otool -tV XXX
# 输出Object-C类结构以及定义的方法
otool -ov XXX
# 查看头部内容
otool -h XXX
# 查看 load commands
otool -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
路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/objdump
# 查看mach-header
objdump --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/frameworkName
lipo -info frameworkName.a
### 合并静态库
lipo -create 静态库存放路径 1 静态库存放路径 2 ... -output 整合后存放的路径
lipo -create frameworkName-armv7.a frameworkName-armv7s.a frameworkName-i386.a -output frameworkName.a
lipo -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[13] 为一个静态库
CSPickerView: Mach-O universal binary with 5 architectures: [i386:current ar archive] [arm_v7] [arm_v7s] [x86_64] [arm64]
CSPickerView (for architecture i386): current ar archive
CSPickerView (for architecture armv7): current ar archive
CSPickerView (for architecture armv7s): current ar archive
CSPickerView (for architecture x86_64): current ar archive
CSPickerView (for architecture arm64): current ar archive
下载地址[14]
这是一个命令行实用程序,用于检查存储在 Mach-O
文件中的 Objective-C
运行时信息。它为类、类别和协议生成声明。这与使用 'otool -ov' 提供的信息相同,但呈现为普通的 Objective-C 声明,因此更加紧凑和可读。
如果安装了
MonkeyDev
,内置了class-dump
,就不用再特意去安装了。
当然,CLI 命令还有很多,这里只是列举了一些常见的,对于其他的,大家可以直接通过开头提到的一些路径去查找。
要更加努力呀!
Let's be CoderStar!
[1]Command Line Tool: https://developer.apple.com/download/all/?q=command
[2]Swift-Swiftc: https://dongaxis.github.io/2016/04/28/Swift-Swiftc/
[3]Developer Binaries on OS X, xcode-select and xcrun: https://macops.ca/developer-binaries-on-os-x-xcode-select-and-xcrun/
[4]深入浅出 Xcode Command Lines Tool - 初探: https://juejin.cn/post/6844904052271087624
[5]深入浅出 Xcode 命令列 - libxcselect.dylib: https://juejin.cn/post/6844904052271087623
[6]深入浅出 Xcode 命令列 - xcrun: https://juejin.cn/post/6844904052275298317
[7]xcode-tools: https://github.com/b-man/xcode-tools
[8]llvm-dwarfdump: https://llvm.liuxfe.com/docs/man/llvm-dwarfdump
[9]copySwiftV1MangledName 函数: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-runtime-new.mm#L859
[10]copySwiftV1DemangledName: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-runtime-new.mm#L813
[11]SwiftDemangle.h: https://github.com/apple/swift/blob/main/include/swift/SwiftDemangle/SwiftDemangle.h
[12]otool.html: https://wangwangok.gitbooks.io/mac-terminal-tool/content/otool.html
[13]CSPickerView: https://github.com/Coder-Star/CSPickerView
[14]下载地址: http://stevenygard.com/projects/class-dump/
[15]iOS 开发中常用命令工具(xcode-select、lipo、xcrun 等: https://www.jianshu.com/p/a4731527ca73
[16]Xcode 相关终端工具使用: https://www.hanleylee.com/usage-of-xcode-terminal-tools.html
[17]Building from the Command Line with Xcode FAQ: https://developer.apple.com/library/archive/technotes/tn2339/_index.html