前言
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
工具链中的一部分。
前置工具
在我来介绍这套工具包其他工具之前,我先来介绍两个工具,我称它们为前置工具,因为有了这两个工具,我们才能更好的使用其他的工具。
xcode-select
这个工具可以帮助我们下载及安装 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 工具包一块下载下来的。
xcrun
回想我们过去在使用一些 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 可以知道更详细流程,这里只说结论:
libxcselect.dylib
👇🏻
_xcselect_invoke_xcrun
👇🏻
libxcrun.dylib
👇🏻
xcrun_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 符号化解析。
dwarfdump
作用:解析目标文件,存档和.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。
dsymutil
作用:可以使用 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
复制代码
symbolicatecrash
作用:是一个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
复制代码
atos
作用: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
复制代码
构建相关
xcodebuild
作用:我们可以使用其对 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);
altool
作用:使用其验证 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
复制代码
工具链相关
swiftc
作用:swift 语言的编译前端; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc
;
swiftc
只是一个替身,原身是swift-frontend
。
clang
作用:oc 语言的编译前端; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
;
sourcekit-lsp
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
工具相关
actool
作用:对 项目中 Assets 的文件进行压缩、处理,生成.car
文件; 路径:/Applications/Xcode.app/Contents/Developer/usr/bin/actool
;
actool
并非一个脚本,而是一个编译完成的二进制文件,所以compile asset catalog
的过程是一个黑盒。
swift-demangle
在 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 操作相关
其实关于 Mach-O 操作的大部分工具都是 LLVM 下面的,包括otool
、objdump
、nm
、dwarfdump
等等,其命令本质上都是一个替身,背后实际上都是llvm-XXX
命令的原身。
nm
作用: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
复制代码
上述中间的大写字母就是后面对应符号的类型,其中全部的类型包括:
- A 该符号的值在今后的链接中将不再改变;
- B 该符号放在 BSS 段中,通常是那些未初始化的全局变量;
- D 该符号放在普通的数据段中,通常是那些已经初始化的全局变量;
- T 该符号放在代码段中,通常是那些全局非静态函数;
- U 该符号未定义过,需要自其他对象文件中链接进来;
- W 未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。
otool & objdump
为什么要把这两个工具放到一起说呢?因为这两个工具之间有一定的关系。其实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 这三个位置,如果没有自己的动态库,就没有后面的 @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
复制代码
objdump
路径:/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
也可以输出符号表。
strings
作用:查看二进制文件中的字符串; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strings
;
# 查看指定字符串
strings XXX | grep "xxx"
复制代码
lipo
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
复制代码
ar
作用:建立、修改静态库; 路径:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool
;
ar -x XXX
-d 删除备存文件中的成员文件。
-m 变更成员文件在备存文件中的次序。
-p 显示备存文件中的成员文件内容。
-q 将问家附加在备存文件末端。
-r 将文件插入备存文件中。
-t 显示备存文件中所包含的文件。
-x 自备存文件中取出成员文件。
复制代码
file
我们可以使用file
命令来区分动态库与静态库。
如file Flutter
得到,我们可以很容易看到dynamically
关键字,其为一个动态库
Flutter: Mach-O 64-bit dynamically linked shared library arm64
复制代码
如 file CSPickerView
,得到结果如下:CSPickerView 为一个静态库
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
复制代码
class-dump
这是一个命令行实用程序,用于检查存储在 Mach-O
文件中的 Objective-C
运行时信息。它为类、类别和协议生成声明。这与使用 'otool -ov' 提供的信息相同,但呈现为普通的 Objective-C 声明,因此更加紧凑和可读。
如果安装了
MonkeyDev
,内置了class-dump
,就不用再特意去安装了。
最后
当然,CLI 命令还有很多,这里只是列举了一些常见的,对于其他的,大家可以直接通过开头提到的一些路径去查找。
要更加努力呀!
Let's be CoderStar!
作者:CoderStar
链接:https://juejin.cn/post/7060142847609012255
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。