通过Xcode 10链接libstdc++来深入分析tbd文件

栏目: IOS · 发布时间: 5年前

内容简介:相信玩Ok,解决方案是有了,我们需要更深入的理解下:为什么

相信玩 iOS 开发的同学对 tbd 这个格式的文件已经不再陌生了。最近 Xcode 10 升级的时候,你会发现很多原先用 libstdc++ 的库在新的 Xcode 已经没有链接通过。而临时的解决方案也比较简单,网上也很多这样的文章,简而言之就是从 Xcode 9 中拷贝对应的 libstdc++.tbd 文件给新的 Xcode 10 来使用。

Ok,解决方案是有了,我们需要更深入的理解下:为什么 拷贝tbd文件,就能够成功解决链接问题?

tbd格式解析

tbd 全称是 text-based stub libraries ,本质上就是一个 YAML 描述的文本文件。

他的作用是用于记录 动态库的一些信息 ,包括导出的符号、动态库的架构信息、动态库的依赖信息。

为什么需要包含这些信息呢?

  • 动态库的架构信息是了确保运行的程序在运行的平台加载正确的库。比如你不能在运行 ARM 指令集的 iOS 设备上加载 x86 格式的库。

后续我们会举一个手动修改 tbdinstall-name 字段的小例子来让运行在模拟器的时候加载 ARM64 架构的动态库

  • 导出的符号。写过程序的人都知道,我们肯定会依赖别人提供的一些函数方法。一般业界都会把这些函数或者方法封装成库的形势。

那库就分为静态库和动态库两种。相信网上关于这两者的讨论和阐述已经很多了,再次不再赘述。唯一需要提及的一点是,动态库是在程序运行(启动依赖或者按需加载)时候加载进程序的地址空间的,那么我们在静态期的时候,是如何得知动态库提供了哪些能力呢? 而这就是 tbd 格式提供的导出符号表的加载,它会指导链接器在链接过程中,将需要决议的符号先做个标记,标记是来自哪个动态库。

这里举个小例子吧。

在程序构建的过程中,比如我们开发一个iOS应用,毋庸置疑的会用到 UIKit 这个动态库。而为了使我们的程序能够构建成功,这里分为了两个步骤:

  • 通过引入头文件, import <UIKit/UIKit.h> ,我们知道了 UIKit 里面的函数、变量声明。有声明,就能通过编译器的检查。

  • 我们在代码里面使用了 UIKit 的函数,其本质是一种符号,因此需要链接器来决议这个符号来自哪?要是所有地方都找到,就会报类似 undefined symbol 之类的错误(想必大家已经很熟悉了)。

为什么要改造成tbd格式

tbd 格式实际上是从 Xcode 7 时代引入的。

用于取代在真机开发过程中直接使用传统的 dylib

用于取代在真机开发过程中直接使用传统的 dylib

用于取代在真机开发过程中直接使用传统的 dylib

我们都知道一个库在没有 strip 诸如调试信息、非导出符号的情况下是非常大的。但是由于在开发过程中,调试等过程是必不可少的,我们来对比下传统直接包含 dylib 的时候大小,我们以 CoreImage.framework 来举例:

  • 首先看下模拟器上的传统架构大小:

通过Xcode 10链接libstdc++来深入分析tbd文件

  • 再看下对应的真机上的伪 framework (包含 tbd )的大小

通过Xcode 10链接libstdc++来深入分析tbd文件

差距很明显了吧,对于真机来说,由于动态库都是在设备上,在 Xcode 上使用基于 tbd 格式的伪 framework 可以大大减少 Xcode 的大小。

题外话:网上有人说模拟器上还是使用 dylib ,的确没错。但是模拟器现在也桥了一层 tbd 格式,真正的 dylib 是在这个路径下: iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents

此外,虽然从 Xcode 7 时代到现在,一直都是 tbd 的说法,但是它也是经历了一些演变了,目前已经发展了 v3 格式的版本。

为什么拷贝tbd文件能解决Xcode 10上的问题

网上很多人都研究过 dyld 的代码,与之对应还有一种 ld ,就是平时我们在构建程序过程中,链接过程中出错的根因:

通过Xcode 10链接libstdc++来深入分析tbd文件

既然我们通过拷贝 tbd 的方式能解决链接不过的问题,那我们就要知道 ld 是如何运用 tbd 文件的。

既然报错事 library not found ,我们扒一下 linker 的源码即可:

Options::FileInfo Options::findLibrary(const char* rootName, bool dylibsOnly) const
{
    FileInfo result;
    const int rootNameLen = strlen(rootName);
    // if rootName ends in .o there is no .a vs .dylib choice
    if ( (rootNameLen > 3) && (strcmp(&rootName[rootNameLen-2], ".o") == 0) ) {
        for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin();
             it != fLibrarySearchPaths.end();
             it++) {
            const char* dir = *it;
            if ( checkForFile("%s/%s", dir, rootName, result) )
                return result;
        }
    }
    else {
        bool lookForDylibs = false;
        switch ( fOutputKind ) {
            case Options::kDynamicExecutable:
            case Options::kDynamicLibrary:
            case Options::kDynamicBundle:
            case Options::kObjectFile:  // <rdar://problem/15914513> 
                lookForDylibs = true;
                break;
            case Options::kStaticExecutable:
            case Options::kDyld:
            case Options::kPreload:
            case Options::kKextBundle:
                lookForDylibs = false;
                break;
        }
        switch ( fLibrarySearchMode ) {
        case kSearchAllDirsForDylibsThenAllDirsForArchives:
                // first look in all directories for just for dylibs
                if ( lookForDylibs ) {
                    for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin();
                         it != fLibrarySearchPaths.end();
                         it++) {
                        const char* dir = *it;
                        auto path = std::string(dir) + "/lib" + rootName + ".dylib";
                        if ( findFile(path, {".tbd"}, result) )
                            return result;
                    }
                    for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin();
                         it != fLibrarySearchPaths.end();
                         it++) {
                        const char* dir = *it;
                        if ( checkForFile("%s/lib%s.so", dir, rootName, result) )
                            return result;
                    }
                }
                // next look in all directories for just for archives
                if ( !dylibsOnly ) {
                    for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin();
                         it != fLibrarySearchPaths.end();
                         it++) {
                        const char* dir = *it;
                        if ( checkForFile("%s/lib%s.a", dir, rootName, result) )
                            return result;
                    }
                }
                break;

            case kSearchDylibAndArchiveInEachDir:
                // look in each directory for just for a dylib then for an archive
                for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin();
                     it != fLibrarySearchPaths.end();
                     it++) {
                    const char* dir = *it;
                    auto path = std::string(dir) + "/lib" + rootName + ".dylib";
                    if ( lookForDylibs && findFile(path, {".tbd"}, result) )
                        return result;
                    if ( lookForDylibs && checkForFile("%s/lib%s.so", dir, rootName, result) )
                        return result;
                    if ( !dylibsOnly && checkForFile("%s/lib%s.a", dir, rootName, result) )
                        return result;
                }
                break;
        }
    }
    throwf("library not found for -l%s", rootName);
}
  • throwf("library not found for -l%s", rootName); 这里我们就找到了错误发生的原因,我们再往上溯源,找到 linker 处理编译单元的入口:

  • InputFiles::addOtherLinkerOptions 里面存在如下代码:

    CStringSet newLibraries = std::move(state.unprocessedLinkerOptionLibraries);
        state.unprocessedLinkerOptionLibraries.clear();
        for (const char* libName : newLibraries) {
            if ( state.linkerOptionLibraries.count(libName) )
                continue;
            try {
                Options::FileInfo info = _options.findLibrary(libName);
                if ( ! this->libraryAlreadyLoaded(info.path) ) {
                    _linkerOptionOrdinal = _linkerOptionOrdinal.nextLinkerOptionOrdinal();
                    info.ordinal = _linkerOptionOrdinal;
                     //<rdar://problem/17787306> -force_load_swift_libs
                    info.options.fForceLoad = _options.forceLoadSwiftLibs() && (strncmp(libName, "swift", 5) == 0);
                    ld::File* reader = this->makeFile(info, true);
                    ld::dylib::File* dylibReader = dynamic_cast<ld::dylib::File*>(reader);
                    ld::archive::File* archiveReader = dynamic_cast<ld::archive::File*>(reader);
                    if ( dylibReader != NULL ) {
                        dylibReader->forEachAtom(handler);
                        dylibReader->setImplicitlyLinked();
                        dylibReader->setSpeculativelyLoaded();
                        this->addDylib(dylibReader, info);
                    }
                    else if ( archiveReader != NULL ) {
                        _searchLibraries.push_back(LibraryInfo(archiveReader));
                        _options.addDependency(Options::depArchive, archiveReader->path());
                        //<rdar://problem/17787306> -force_load_swift_libs
                        if (info.options.fForceLoad) {
                            archiveReader->forEachAtom(handler);
                        }
                    }
                    else {
                        throwf("linker option dylib at %s is not a dylib", info.path);
                     }
                 }
             }
            catch (const char* msg) {
                // <rdar://problem/40829444> only warn about missing auto-linked library if some missing symbol error happens later
                state.missingLinkerOptionLibraries.insert(libName);
            }
            state.linkerOptionLibraries.insert(libName);
        }

而上述这些需要查询的 library 是从哪里来的呢?

  • 我们以 xcconfig 举例来看:

    OTHER_LDFLAGS = $(inherited) -ObjC -l"stdc++"

在链接过程中就需要处理这样的 stdc++ Library ,而查询的方式就是在特定目录结构中搜索是否有对应的库文件或者 tbd 文件。

后记

使用 tbd 当然不止减少 Xcode 体积大小这一个好处,嘿嘿,你们自己摸索下吧~

而且,基于这种思路,能玩出许多类似文体两开花,中美合拍美猴王的玩法,加油吧。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning Python, 5th Edition

Learning Python, 5th Edition

Mark Lutz / O'Reilly Media / 2013-7-6 / USD 64.99

If you want to write efficient, high-quality code that's easily integrated with other languages and tools, this hands-on book will help you be productive with Python quickly. Learning Python, Fifth Ed......一起来看看 《Learning Python, 5th Edition》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换