文章图片
作者:字节跳动终端技术——李翔
前言
静态链接(static linking)是程序构建中的一个重要环节,它负责分析 compiler 等模块输出的 .o、.a、.dylib 、经过对 symbol 的解析、重定向、聚合,组装出 executable 供运行时 loader 和 dynamic linker 来执行,有着承上启下的作用。
文章图片
对于 iOS 工程而言,目前负责静态链接的主要是 ld64。苹果对 ld64 加持了一些功能,以适配 iOS 项目的构建,比如:
-order_file 让 linker 按照指定顺序生成 Mach-O-exported_symbols_list 优化构建产物中 export info 占用的空间,减少包大小LoaDer 、Link eDitor。ld (/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld)默认就是 ld64。系统和 Xcode 自带的版本可以通过 ld -version_details 查询,如 650.9。苹果在这里 https://opensource.apple.com/... 开放了 ld64 的源码,但更新不那么及时,始终落后于正式版(如 2021.8 为止开源最新是 609 版本,Xcode 12.5.1 是 650.9) 。zld 等基于 ld64 的项目都是 fork 自开源版的 ld64。.o、.a、.dylib
ld64 主要处理 Mach kernel) 上的 Mach-O 输入,包括:.o)
.a).o 的集合,让工程代码能模块化地被组织和复用。.o offset 的映射表,便于 link 时快速查询某个 symbol 的归属。lipo 等工具查看其架构信息。
.dylib、.tbd).tbd (text-based dylib stub) 是苹果在 Xcode 7 后引入的一种描述 dylib 的文件格式,包含支持的架构、导出哪些 symbol 等信息。通过解析 .tbd ld64 可以快速地知道该 dylib 提供了哪些 symbol 可被用于链接 & 有哪些其他动态库依赖,而不用去解析整个解析一遍 dylib。目前大多数系统的 dylib 都采用这种方式。--- !tapi-tbd
tbd-version:4
targets:[ i386-ios-simulator, x86_64-ios-simulator, arm64-ios-simulator ]
uuids:
- target:i386-ios-simulator
value:A4A5325F-E813-3493-BAC8-76379097756A
- target:x86_64-ios-simulator
value:C2A18288-4AA2-3189-A1C6-5963E370DE4C
- target:arm64-ios-simulator
value:81DE1BE5-83FA-310A-9FB3-CF39C14CA977
install-name:'/System/Library/Frameworks/Foundation.framework/Foundation'
current-version: 1775.118.101
compatibility-version: 300
reexported-libraries:
- targets:[ i386-ios-simulator, x86_64-ios-simulator, arm64-ios-simulator ]
libraries:[ '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation',
'/usr/lib/libobjc.A.dylib' ]
exports:
- targets:[ arm64-ios-simulator, x86_64-ios-simulator, i386-ios-simulator ]
symbols:[ '$ld$hide$os10.0$_OBJC_CLASS_$_NSURLSessionStreamTask', '$ld$hide$os10.0$_OBJC_CLASS_$_NSURLSessionTaskMetrics',
....
_NSLog, _NSLogPageSize, _NSLogv, _NSMachErrorDomain, _NSMallocZone,
....]
_someGlolbalVar 、C function _someGlobalFunction、 ObjC class __OBJC_CLASS_$_SomeClass、 ObjC method -[SomeClass foo] 等。不同的 compiler 有不同的 name mangling 策略。static 标记)int i = 1;
|static int i = 1;
|extern int i;
|LC_DYSYMTAB 来获取三组 symbol 的偏移和大小
N_PEXT 字段) ,static linker 会在输出中把该 symbol 转为 local symbol。可以理解为该 symbol definition 只在这一次 link 过程中对外可见,后续 link 的产物如果要被二次 link,就对外不可见了(体现了 private 的性质)__attribute__((visibility("xxx"))) 来标识,可选值为 default(normal)、hidden(private external)__attribute__((visibility("xxx"))) 的,默认为 default-fvisibility 可以修改默认 visibility (gcc、clang 都支持)__attribute__((visibility("xxx"))) 的,visibility 为 xxx// test.c__attribute__((visibility("default"))) int i1Default = 101;
__attribute__((visibility("hidden"))) int i1Hidden = 102;
int i1Normal = 103;
-fvisibility:
-fvisibility=hidden:
__attribute__((weak)) 、#pragma weak 标记 weak 属性,看一个例子:// main.cvoid __attribute__((weak)) foo() {
printf("weak foo called");
}int main(int argc, char * argv[]) {
foo();
}// strong_foo.c
void foo() {
printf("strong foo called");
}main.o 中该函数对应的 symbol table entry 被标记为了 N_WEAK_DEF,static linker 据此来区分 strong / weak:
strong foo calledint i;
A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static说的比较绕不要被带进去了,可以先简单理解 tentative definition 为「未初始化的全局变量定义」。结合更多的例子来理解:
int i1 = 1;
// regular definition,global symbol
static int i2 = 2;
// regular definition,local symbol
extern int i3 = 3;
// regular definition,global symbol
int i4;
// tentative definition, global symbol
static int i5;
// tentative definition, local symbolint i1;
// valid tentative definition, refers to 第 1 行
int i2;
// invalid tentative definition,visibility 和第 2 行的 static 冲突
int i3;
// valid tentative definition, refers to 第 3 行
int i4;
// valid tentative definition, refers to 第 4 行
int i5;
// invalid tentative definition,visibility 和第 5 行的 static 冲突__DATA,__common 这个 section。LC_SEGMENT_64 描述了各个 section 对应的 Relocation Entries 的数量、偏移量:
relocation_info 表示一条 Relocation Entry:r_address :从该 section 头开始偏移多少位置的内容需要 relocater_extern & r_symbolnumr_extern 为 1 表示从 symbol table 的第 r_symbolnum 个 symbol 读取信息r_extern 为 0 表示从第 r_symbolnum 个 section 读取信息r_type :relocation 的类型,如 X86_64_RELOC_BRANCH 表示 relocate 的是 CALL/JMP 指令的内容SectionBoundaryAtom。ld64 在解析时会把 input files 抽象成各种 atoms,交由 Resolver 统一处理。N_TYPE 字段得来N_ABS,ld64 不会修改它的值N_UNDF,对应上面 Symbol 的 tentative definitionkindStoreX86PCRel32printf |// Foo.h
extern const int someGlobalVar;
int someGlobalFunction(void);
// Foo.m
const int someGlobalVar = 100;
int someGlobalFunction() {
return 123;
}// main.m
#import "Foo.h"int main(int argc, char * argv[]) {
int i = someGlobalVar;
someGlobalFunction();
}main.m 调用了 Foo.h 定义的全局变量 someGlobalVar 和函数 someGlobalFunction,compiler 生成的 main.o 和 Foo.o 存在以下 symbol:
main.o 和 Foo.o 的 symbol table 提供,边信息(fixup)由 main.o 的 relocation entries 提供。-ObjC 的由来」一节会详细展开。SymbolTable 对象,里面包含了所有处理过的 symbol,并提供了各种快速查询的接口。SymbolTable 里增加 atom 时会触发合并操作,主要分为两种SymbolTable 核心的数据结构是 _indirectBindingTable,这东西其实就是个存储 atom 的数组,每个 atom 都会按解析顺序被 append 到这个数组上(如果不被合并的话)。SymbolTable 还维护了多个 mapping,辅助用于外部根据 name、content、references 查询某个 atom 的各类需求。class SymbolTable : public ld::IndirectBindingTable
{
private:// core vector
std::vector&_indirectBindingTable;
// for by-name query
NameToSlot_byNameTable;
// for by-content query
ContentToSlot_literal4Table;
ContentToSlot_literal8Table;
ContentToSlot_literal16Table;
UTF16StringToSlot_utf16Table;
CStringToSlot_cstringTable;
// fo by-reference query
ReferencesToSlot_nonLazyPointerTable;
ReferencesToSlot_threadPointerTable;
ReferencesToSlot_cfStringTable;
ReferencesToSlot_objc2ClassRefTable;
ReferencesToSlot_pointerToCStringTable;
} SymbolTable 来完成。-v 来输出 clang 调用的 ld 命令。ld files...[options] [-o outputfile]ld -filelist xxx -framework Foundation -lobjc -o yyy -o 指定 output 的路径-filelist 以文件的形式传入,该文件以换行符分隔每一个 input file-lxxx,告诉 ld64 去 lib 搜索路径找 libxxx.a 或者 libxxx.dylib/usr/lib 和 /usr/local/lib-Lpath/to/your/lib 来增加额外的 lib 搜索路径-framework xxx,告诉 ld64 去 framework 搜索路径找 xxx.framework/xxx/Library/Frameworks 和 /System/Library/Frameworks-Fpath/to/your/framework 来增加额外的 framework 搜索路径-syslibroot /path/to/search,会给 lib 和 framework 搜索路径都加上 /path/to/search 的前缀(如 iOS 模拟器一般会拼上形如 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.5.sdk 的路径)
Options 对象,便于后续逻辑的读取。-filelist、各种搜索路径等等的逻辑)都会在这一步转换解析成实际 input files 的绝对路径-dead_strip)存到 Options 对应的字段中
Options.cpp 中 Options 的构造函数:// create object to track command line arguments
Options options(argc, argv);
ld::tool::InputFiles inputFiles(options);
.o、.a、.dylib 三类,ld64 在解析不同类型的文件时,会调用该文件对应的 parser 来处理(如 .o 是 mach_o::relocatable::parse),并返回对应的 ld::File 子类(如 .o 是 ld::relocatable::File),有点工厂模式的味道。.o.o 是 ld64 获取 section 和 atom 信息的直接来源,因此需要深度地扫描。
mach_o::relocatable::parseLC_SEGMENT_64 提供各个 section 的信息(位置、大小、relocation 位置、relocation 条目数等)LC_SYMTAB 提供 symbol table 信息(位置、大小、条目数)LC_DYSYMTAB 提供 symbol table 分类统计LC_LINKER_OPTION-framework UIKit 的参数)LC_BUILD_VERSIONmakeSections:根据 LC_SEGMENT_64 创建 Section 数组,存入 _sectionsArray__compact_unwind 和 __eh_frame_atomsArray:遍历 _sectionsArray,把每个 section 的 atom 加入 _atomsArraymakeFixups:创建 fixup_sectionsArray,读取该 section 的 relocation entriesFixupInAtom_allFixups (vector).o 的逻辑参考 ld::relocatable::File* Parser::parse。.a.a 时一开始只处理 .a 的 symbol table (.a 的 symbol table 存储的是 symbol name -> .o offset,仅包含每个 .o 的 global symbols),不需要把内部所有的 .o 挨个解析一遍。Resolver 在 resolve undefined symbol 时会来查找 .a 的 symbol table 并按需懒加载对应的 .o。
archive::Parser::parse.a.a symbol table header,获取 symbol table 条目数_hashTable 中
mach_o::dylib::parse.o 类似)LC_SEGMENT_64 、LC_SYMTAB 、LC_DYSYMTAB 等和 .o 类似LC_DYLD_INFO、 LC_DYLD_INFO_ONLY 提供 dynamic loader infoLC_RPATH、LC_VERSION_MIN_IPHONEOSLC_DYLD_INFO、 LC_DYLD_INFO_ONLY、 LC_DYLD_EXPORTS_TRIE 提供的 symbol 信息,存入 _atoms_atoms 。.tbd,关键是要获取两个信息:_NSLog).tbd 文件,parse 完(其实就是调 yaml 解析库解析了一遍)可以调接口(tapi::LinkerInterfaceFile)直接得到结构化的信息。
InputFiles::makeFile 中可以看到取出目标架构的逻辑:
InputFiles::InputFiles 的构造函数Resolver 把 input files 提供的所有 atoms 汇总关联成 atom graph 并处理,是「链接」的核心模块。
SymbolTable 中。pthread_cond_wait 等待.o 的 atoms
parse 阶段 ld64 已经从 object file 的 symbol table 和 relocation entries 中抽象出了 _atoms,这一步挨个处理即可。Resolver::doAtom 处理单个 atom 的逻辑 :SymbolTable::add(仅 global symbol & undefined external symbol,local symbol 不处理)_indirectBindingTable (定义见「概念铺垫 — Symbol Table」NameToSlot、ContentToSlot、ReferencesToSlot_indirectBindingTable 中对应的 atom).a 的 atoms
buildAtomList 阶段理论上完全不需要处理静态库,因为只有在后面 resolve undefined symbol 时才有可能查询静态库里包含的 symbol。但在以下两种情况下,这一步需要对静态库内的 .o 展开处理:.a 受 -all_load 或 -force_load 影响,强制 load 所有 .o-ObjC,强制 load 所有包含 ObjC class 和 category 的 .o(symbol name 包含 _OBJC_CLASS_ 、.objc_c).o 的 load 状态.dylib 的 atoms
buildAtomList 阶段不 add 动态库的 atoms,但会做一些额外的处理和校验,包括 bitcode bundle(__LLVM, __bundle)、 Swift framework 依赖检查、Swift 版本检查等。SymbolTable 中已经收集了 input files 中的大部分 atom,下一步需要把其中归属不明的 symbol 引用关联到对应的 symbol 定义上去。SymbolTable 中 undefined symbol (被 reference 的但是没有对应 atom 实体的 symbol definition).o offset 的 mapping,因此要判断某个 symbol definition 是否属于该静态库只需要去这个 mapping 里查即可。如果查找到了,则解析对应的 .o、并把该 .o 的 atoms 加入 SymbolTable 中(.o 的加载逻辑参考前文 Parsing input files 和 buildAtomList)section$、segment$ 等 boundary atoms,并手动创建对应的 symbol definition-undefined 不是 error(命令行参数控制发现 undefined symbol 时不报错)、或者命中了 -U(参数控制某些 undefined symbol 不报错),那么 ld64 会手动创建一个 UndefinedProxyAtom 作为其 symbol definition-dead_strip 后的逻辑。此时所有的 atom 和它们之间的引用关系已经记录在了 SymbolTable 中,可以把所有的 atom 抽象成 atom graph 来移除没有被引用到的无用 atom。_main)-u(强制加载某个 symbol,即使在静态库中)、-exported_symbols_list、-exported_symbol(在 output 中作为 global symbol 输出) 命中的 atoms.o 中被标记为了 S_ATTR_NO_DEAD_STRIP)ld::passes::objcld::passes::stubsld::passes::dylibsld::passes::dedup::doPass
synthesizeDebugNotesbuildSymbolTablegenerateLinkEditInfobuildChainedFixupInfobuildSymbolTable 负责构建 output file 中的 symbol table。「概念铺垫 — Symbol」中提到每个 symbol 在 link 阶段有自己的 visibility,用来控制 link 时对其他文件的可见性。同理,在 link 结束后输出的 Mach-O 中这些 symbol 现在隶属于一个新的文件,此时它们的 visibility 要被 ld64 依据各种处理策略来重新调整:-reexport-lx、-reexport_library、-reexport_framework(指定 lib 的 global symbol 在 output 中继续为 global)、-hidden-lx(指定 lib 中的 symbol 在 output 中转为 hidden)FinalSection 数组愉快地去写 output file 了,大致逻辑如下:FinalSection 数组applyFixUps),根据 fixup 的类型修正 atom content 对应位置的内容-l、 -framework 等 lib 依赖也能让 linker 正常工作的机制。#import -framework AppKit.o 的 LC_LINKER_OPTION 中带有 -framework AppKit#import /usr/include/module.modulemap 内容module zlib [system] [extern_c] {
header "zlib.h"
export *
link "z"
}-lz.o 的 LC_LINKER_OPTION 中带有 -lz.o 时,解析 import,把依赖的 framework 写入最后 Mach-O 里的 LC_LINKER_OPTION (存储了对应的 -framework XXX 信息)
-fmodules)自动开启 auto linking 。可以用 -fno-autolink 主动关闭。-ObjC 的由来
前面提到开启了 -ObjC 后,ld64 会在解析符号 search lib 时强制加载每个静态库内包含 ObjC class 和 category 的 .o。这么做的原因是什么呢?global(自己定义、link 时外部文件可见)undefined external(外部定义、需要 link 时 fixup)local(对外部不可见)ClassA & ClassB :
// ClassA.m#import "ClassB.h"@implementation ClassA- (void)methodA
{
[[ClassB new] methodB];
}@end// ClassB.m@implementation ClassB- (void)methodB
{}@endClassA.o:_OBJC_CLASS_$_ClassBClassB.o:_OBJC_CLASS_$_ClassB-[ClassB methodB]_OBJC_CLASS_$_ClassB 这个对 ClassB 类本身的 reference,根本没有 -[ClassB methodB]。这样的话,按照 ld64 正常的解析逻辑,既不会因为 ClassA 中对 methodB 的调用去寻找 ClassB.m 的定义(压根没有生成 undefined external)、即使想找,ClassB 也没有暴露这个 method 的 symbol (local symbol 对外部文件不可见)。ClassB 的例子中,atom 之间的依赖关系如下:_OBJC_CLASS_$_ClassB -> __OBJC_CLASS_RO_$_ClassB ->__OBJC_$_INSTANCE_METHODS_ClassB -> -[ClassB methodB]ClassB 和 methodBClassB 的 methodBFromCategoryClassB 和 methodB 、methodBFromCategorymethodBFromCategory,但 A 没有解析 methodBFromCategory 这个符号的需求(没生成),因此 ld64 不需要加载 C。methodBFromCategory 定义必须被 ld64 link 进来。这里需要分两种情况:objc-cat-list -> __OBJC_$_CATEGORY_ClassB_$_SomeCategory__OBJC_$_CATEGORY_INSTANCE_METHODS_ClassB_$_SomeCategory ->-[ClassB(SomeCategory) methodBFromCategory]objc-cat-list 表示所有 ObjC 的 categories,在 dead code strip 初始阶段被标记为 live,因此 methodBFromCategory 会被 link 进 executable 而不被裁剪。methodBFromCategory 没有被 link 进 executable,导致最终运行时 ClassB 没有加载该 category、执行时错误。-ObjC 这个开关,保证静态库中单独定义的 ObjC category 被 link 进最终的 output 中。-ObjC,但这种为了兼容 category 而暴力加载静态库中所有 ObjC class 和 category 的实现并不是最完美的方案,因为可能因此在 link 阶段加载了许多本不需要加载的 ObjC class。理论上我们可以通过人为在 category 定义和引用之间建立引用关系来让 ld64 在不开启 -ObjC 的情况下也能加载 category,比如 IGListKit 就曾尝试手动注入一些 weak 的 dummy 变量(PR https://github.com/Instagram/...) ,但这种做法为了不劣化也会带来一定维护成本,因此也需要权衡。-ObjC 的处理可参考 src/ld/parsers/archive_file.cpp:bool File::forEachAtom(ld::File::AtomHandler& handler) const
{
bool didSome = false;
if ( _forceLoadAll || _forceLoadThis ) {
// call handler on all .o files in this archive
...
}
else if ( _forceLoadObjC ) {
// call handler on all .o files in this archive containing objc classes
for (const auto& entry : _hashTable) {
if ( (strncmp(entry.first, ".objc_c", 7) == 0) || (strncmp(entry.first, "_OBJC_CLASS_$_", 14) == 0) ) {
const Entry* member = (Entry*)&_archiveFileContent[entry.second];
MemberState& state = this->makeObjectFileForMember(member);
char memberName[256];
member->getName(memberName, sizeof(memberName));
didSome |= loadMember(state, handler, "-ObjC forced load of %s(%s)\n", this->path(), memberName);
}
}
// ObjC2 has no symbols in .o files with categories but not classes, look deeper for those
const Entry* const start = (Entry*)&_archiveFileContent[8];
const Entry* const end = (Entry*)&_archiveFileContent[_archiveFilelength];
...
}
...
}man ld 查看 Options for introspecting the linker 一栏-print_statisticsld total time: 2.26 seconds
option parsing time:6.9 milliseconds (0.3%)
object file processing:0.1 milliseconds (0.0%)
resolve symbols: 2.24 seconds
build atom list:0.0 milliseconds (0.0%)
passess:6.2 milliseconds (0.2%)
write output:10.4 milliseconds (0.4%)-t.o .a .dylib。-why_load xxx.a 中 .o 被加载的原因(即什么 symbol 被需要)。-ObjC forced load of bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTHomeTab/libCommon.a(ArticleTabBarStyleNewsListScreenshotsProvider_IMP.o)
-ObjC forced load of bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTHomeTab/libCommon.a(TTExploreMainViewController.o)
-ObjC forced load of bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTHomeTab/libCommon.a(TTFeedCollectionViewController.o)
-ObjC forced load of bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTHomeTab/libCommon.a(TTFeedCollectionFollowListCell.o)
....
_dec_8i40_31bits forced load of external/TTAudio/Vendor/libopencore-amrnb.a(d8_31pf.o)
_decode_2i40_11bits forced load of external/TTAudio/Vendor/libopencore-amrnb.a(d2_11pf.o)
_decode_2i40_9bits forced load of external/TTAudio/Vendor/libopencore-amrnb.a(d2_9pf.o)-why_live xxx-dead_strip 后,某个 symbol 的 reference chain(即不被 strip 的原因)-why_live _OBJC_CLASS_$_TTNewUserHelper:_OBJC_CLASS_$_TTNewUserHelper from external/TTVersionHelper/ios-arch-iphone/libTTVersionHelper_TTVersionHelper_awesome_ios.a(TTNewUserHelper.o)
objc-class-ref from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTPrivacyAlertManager/libNews.a(TTPrivacyAlertManager.swift.o)
+[TTDetailLogManager createLogItemWithGroupID:] from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailLogManager.o)
__OBJC_$_CLASS_METHODS_TTDetailLogManager from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailLogManager.o)
__OBJC_METACLASS_RO_$_TTDetailLogManager from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailLogManager.o)
_OBJC_METACLASS_$_TTDetailLogManager from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailLogManager.o)
_OBJC_CLASS_$_TTDetailLogManager from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailLogManager.o)
objc-class-ref from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/LMCoreKitTTAdapter/libNews.a(LMDetailTechnicalLoggerImpl.o)
___73-[TTDetailFetchContentManager fetchDetailForArticle:priority:completion:]_block_invoke from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailFetchContentManager.o)
-[TTDetailFetchContentManager fetchDetailForArticle:priority:completion:] from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailFetchContentManager.o)
__OBJC_$_INSTANCE_METHODS_TTDetailFetchContentManager from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailFetchContentManager.o)
__OBJC_CLASS_RO_$_TTDetailFetchContentManager from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailFetchContentManager.o)
_OBJC_CLASS_$_TTDetailFetchContentManager from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTDetail/libCommon.a(TTDetailFetchContentManager.o)
objc-class-ref from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/BDAudioBizTTAdaptor/libNews.a(TTAudioFetchableImp.o)
objc-class-ref from bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/BDAudioBizTTAdaptor/libNews.a(TTAudioFetchableImp.o)-map (linkmap)# Path: /Users/bytedance/NewsInHouse_bin
# Arch: x86_64# Object files:
...
[3203] bazel-out/ios-x86_64-min10.0-applebin_ios-ios_x86_64-dbg-ST-7bf874b56ea0/bin/Module/TTHomeTab/libCommon.a(TTFeedActivityView.o)
...# Sections:
# AddressSizeSegmentSection
0x1000040000x0D28B292__TEXT__text
0x10D28F2920x00011586__TEXT__stubs
...
0x10D70B5E80x00346BE0__DATA__cfstring
0x10DA521C80x00032170__DATA__objc_classlist
...# Symbols:
# AddressSizeFileName
0x1000045900x00000020[8] -[NSNull(Addition) boolValue]
...
0x1117EE0C60x00000027[4282] literal string: -[TTFeedGeneralListView skipTopHeight]
...
0x1104B44300x00000028[22685] _OBJC_METACLASS_$_MQPWebService
0x1104B44580x00000028[22685] _OBJC_CLASS_$_APayH5WapViewToolbar
...
0x1114A9CD40x0000005C[ 10] GCC_except_table0
0x1114A9D300x00000028[ 14] GCC_except_table12
...
<>0x00000008[3269] _kCoverAcatarMargin
<>0x00000008[3269] _kCoverTitleMargin
...