-
作为iOS开发人员,经常会碰到符号的问题,确切的说在调试,收集溃散时,防止不了
-
乍一看,是个抽象的东西,不像咱们处理逻辑业务问题那样,直接依托于语言本身,逻辑漏洞剖析
-
你面临的项目不可能是独立的很小的项目,往往依赖许多库,动态库等等,抵触无可防止,至于如何处理,那么条件你就需求多多少少了解符号的本质了
-
虽然研究符号首要为了处理符号抵触问题
-
但在剖析符号问题阶段,你会涉及到编译,链接的基本常识,在此博客里,你会复习到这些常识,当然就不会很细节展开了,可是对于iOS开发人员剖析来讲,是够用的,这些相关常识需求深化的话,就需求靠自己了
了解符号的品种与效果
按照功用区分
Type | 阐明 |
---|---|
f | File |
F | Function |
O | Data |
d | Debug |
‘ABS’ | Absolute |
‘COM’ | Common |
‘UND’ | ? |
按照符号品种区分
Symbol Type | 阐明 ①:小写 代表local symbol |
---|---|
U | undefined (未界说) |
A | absolute (绝对符号) |
T① | text section symbol(__TEXT.__text) |
D① | data section symbol(__DATA.__text) |
B① | bss section symbol(__DATA.__bss) |
C | common symbol(只能出现在 MH_OBJECT 类型的 Mach-O 文件中) |
– | debugger symbol table |
S① | 除了上面所述的,存放在其他 section 的内容,例如未初始化的大局变量存放在(__DATA.__common)中 |
I | indirect symbol(符号信息相同,代表同一符号) |
U | 动态同享库总的小写u表示一个未界说引用对同一库中另一个模块中私有外部符号 |
检查符号的两个指令 nm & objdump
objdump 成果更便于阅览
- g: 大局
- l: 静态本地文件
这样履行指令好像有点繁琐
- 需求进入终端输入指令
- 参数:可履行二进制文件的路径
简化一点
build 就会履行添加的脚本了, 输出信息就会出现在编译信息里了
还有个问题,每次检查的时分需求时不时该脚本,改完之后还需求 在编译信息列表里选中当时编译的时刻版别,
这仍是有点繁琐
更好的方法,把指令写入装备文件,在文件里能够随时修正指令,并且把指令履行的成果 直接输出到终端
也便是利用xcconfig装备文件装备相关变量,结合 script
编译,直接输出到终端
SO OSO 归于调试符号,但这种符号是咱们不需求的,更好的方法当然是终端输出信息不包括这些了
脱去(strip)不需求的符号
看下装备
终端输出的符号信息并没有脱去符号,是由于还没装备
xcconfig 文件中装备 OTHER_LDFLAGS = -Xlinker -S 能够脱去干扰的调试符号
-
弥补常识
strip指令 履行的时机
strip究竟是在编译.o 仍是链接可履行文件 脱去符号呢?
都不是。
而是在
生成exe可履行文件之后,再去对可履行文件里边的符号表进行修正
Xcode中,strip装备 在咱们打包ipa包的时分才起效果,默许debug下是不收效的
此刻咱们把上面 截图里 Strip Debug Symbols During Copy 改为 YES
但我希望的是有条件的strip符号,不然就得 来回切换挑选Strip Style 3种方法
- All Symbols
- Non-Global Symbols
- Debugging Symbols
-
更好的strip装备方法
持续回到.xcconfig文件 (后缀全程 – xcode装备文件)
修正手动的繁琐的 build setting 手动设置
换成利用 xccondig 声明好各种条件的装备项 一了百了
现在测验下这种方法
终端 – 进入工程根目录 – 履行指令 xcodebuild -showBuildSettings | grep DEPLOY
发现 DEPLOYMENT_POSTPROCESSING = NO
STRIP_STYLE = all
然后在装备文件里添加 DEPLOYMENT_POSTPROCESSING = YES
咱们再看下装备
装备已经依据 装备文件里的设置 变更了过来
经过xcode装备文件修正setting 比起 咱们直接手动修正灵活了许多,并且详细做了哪些装备 也比较明确
有了这些衬托,接下来咱们就能够剖析符号本质及抵触问题了
了解strip指令的实际效果
一个大局函数
在底层被解析出来,也是一个大局符号
那么大局符号的效果域有多大 当时文件?当时app?当时进程?答案是当时进程
这儿能够验证一下
- 静态库里只声明一个大局函数,没有完成
- app里声明大局函数
你会发现,静态库里能拜访到 app里的大局符号,验证了大局符号的效果域应该是当时进程
static 函数 界说在什么地方,它的效果域就效果在什么规模 也便是文件规模
脱去干扰符号,你会发现
- 界说的 global_func 归于大局符号
- static_func 归于本地符号
- 界说的OC符号,既有大局的,又有本地的
app当时进程里,除了app之外,还有没有其他的Mach-O格局的东西
还有动态库
那么当时app 当时进程 就不太好区分了
此刻咱们把 STRIP_STYLE 设置为all,脱去一切符号,可是 成果依然存在像 _NSLog 这样的符号
_NSLog 归于 Foundation 动态库
app运转时分,才会链接动态库,
所以在编译阶段,把整个的运用动态库的符号 保存下来
那么为什么编译阶段,运用的其他的动态库的符号,没有被脱掉?
-
当咱们在函数里调用另外一个函数时
实际上履行的是汇编, 经过汇编直接找到调用函数的入口地址就能够了
这个过程中不需求用到符号,没有符号也能够调用
符号最大的效果 供给 可视化 使程序猿知道调用的地址 是什么名称
可是 咱们运用的动态库的符号 只有在运转时 才去找到动态库符号的真实地址,编译阶段不关注详细地址 所以编译时需求先保存下来 不能脱掉
像这种运用了动态库的符号,咱们放在另一个符号表里存储,这个符号表 叫 直接符号表, 这儿的符号是咱们不能脱掉的 运转的时分还要盼望 直接符号去动态库里找到符号的地址
-
依据以上剖析
-
Non-Global Symbols non-global 脱去非大局符号, 应该是供给给哪品种型的mach-o运用
当然是供给给动态库来运用,动态库里供给给外界的一切符号都是 大局符号,动态库本地符号脱掉是为了减小动态库的大小
-
Debugging Symbols – debugging 是给静态库来运用
静态库是.o的合集,.o经过下一步 链接器ld 的操作,才会变成可履行文件(动态库)
编译器 经过 汇编器 生成.o时,汇编器会把符号都保存下来,可是没有给符号分配虚拟内存地址,假如脱掉了,今后经过链接器链接动态库的时分,就没方法依据符号找虚拟内存地址了
所以静态库只能脱 调试符号
-
All Symbols – all 给 app运用
all 并不是一切符号都脱掉
而是除了动态库符号(也便是动态库露出出来的大局符号),其他的符号都能够脱掉
其他的符号在app运转期过程中 只是地址
-
-
动态库也能拜访 app的大局符号
大局符号效果域是 整个进程
动态库能拜访到 app大局符号,由于动态库也在当时app进程里
- dyld把app 动态库 加载到进程之后,当发现未界说的符号之后,会在当时进程里挨个扫描app 和 动态库,查找有没有app或动态库拥有这个符号,直到找到为止
处理符号抵触
把之前 静态库里的global_func完成注释翻开
编译app报错,提示 duplicate symbol 符号抵触了
假如 把静态库 改为 动态库 编译正常
动态库跟 app包括相同的大局符号,可是没有出现抵触
可能困惑了,为什么动态库反而没有抵触呢,如何了解?
既然大局符号的效果域为当时进程,为什么不报错呢
由于dyld在查找符号的时分,链接器ld引入了一个规矩,ld在把.o文件链接生成可履行文件(或动态库)时,依据两级命名空间来查找符号
-
dyld拜访符号,先拜访符号地点的Mach-o文件,再去拜访mach-o里的符号
也便是 app.global_func 与 dylib.global_func,不会抵触
而静态库 .o文件的合集,只经历了汇编器,跟app的.o合并在一起,最后生成app可履行文件, 最后都放到了app里,能够了解为前缀都是app,所以当然会抵触了, 重复界说的symbol
-
切回静态库,咱们把app中运用静态库的代码注释
静态库与app都界说了相同的大局函数,可是没有抵触
为什么不运用静态库代码的时分,不会抵触呢?
连接器ld 在链接静态库的时分,专门针对静态库提出一个规矩
-
ld会去判别app运用的静态库的代码,发现假如没有用到静态库代码的时分,就不会把静态库代码加载进app
-
编译时,并没有把静态库代码链接进去,同名的函数当然不会抵触
-
好处便是能够减小app的大小
-
害处:对于开发中的分类,分类是运转时才动态创建加载的
假如判别到app没有运用静态库里的代码,分类的代码就会被优化掉了,就会产生问题
针对这个问题,ld供给了一个规矩,经过参数控制 -ObjC
- OTHER_LDFLAGS = $(inherited) -ObjC
回归到链接器ld本身
man ld
依据链接器手册 看下 静态库 动态库的描绘
- 静态库 .o文件的集合,.o文件里包括大局符号
- 动态库 终究链接的镜像
持续在ld手册里搜索 ObjC
4种load方法
- all_load 一切静态库里的一切内容全部链接 只要能编译成macho,就全部链接
- ObjC 静态库里的OC class 或分类 链接
- force_load 多个静态库,只需求指定一个静态库里的一切内容链接到当时app
- load_hidden
- 当时app链接静态库,静态库里正好有一个大局符号,原本在静态库里是大局符号,链接到app后依然是大局符号
- 导致问题:原本不想露出给外边运用,经过app一链接,直接露出给一切外部运用了
- load_hidden 在链接到app之后,就能够把一切静态库里的大局符号变成本地符号,这样就能够躲藏符号,一起能够减小app体积
防止抵触剖析
咱们能够修正大局符号 添加前缀
可是假如咱们拿到的是他人打包好的静态库,咱们底子不可能修正源文件
许多情况下,静态库里包括分类,这个时分 参数 -ObjC 是一定要运用的
那么经过链接器参数控制的方法也不可取
- 只能考虑其他的方法 把静态库里产生抵触的符号给修正掉 llvm-objcopy
llvm源码编译东西 – llvm-objcopy
为了不至于显得突兀,稍微简略说下 东西的编译
由于之前利用llvm做过一些插件,所以我本地已经有编译过的llvm xcode工程
找到llvm源码 llvm-objcopy 路径
在相同目录下 翻开 CMakeLists.txt
添加 add_llvm_tool_subdirectory(llvm-objcopy)
在编译的xcode工程目录下 履行指令 cmake -G Xcode ../llvm-project/llvm
编译 llvm-objcopy
源码很大,编译后20多个G,并且下载源码不是轻松的事情,今后有时机,关于llvm源码的编译独自更新一篇博客出来
然后履行编译后的东西指令
- llvm-objcopy –redefine-sym _global_func=_libIFLTestStaticLib_global_func libIFLTestStaticLib.a
成果出错 unsupported load command
源码做了些小调整,可直接下载运用 修正编译后的llvm-objcopy东西
运用 llvm-objcopy 之前,静态库 libIFLTestStaticLib.a 有个大局符号 _global_func
运用 llvm-objcopy 之后,大局符号 _global_func 被修正 为 _libIFLTestStaticLib_global_func
工程集成调整过的静态库编译经过,并没有发生抵触,静态库中的同名大局函数正常履行
直接经过符号调用
-
文中测验项目链接地址
-
iOS中的符号抵触(二)- 了解进阶
-
我正在参与技术社区创作者签约计划招募活动,点击链接报名投稿