通过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 体积大小这一个好处,嘿嘿,你们自己摸索下吧~

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


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

查看所有标签

猜你喜欢:

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

敏捷估计与规划

敏捷估计与规划

[美] Mike Cohn / 宋锐 / 清华大学出版社 / 2007-7 / 39.90元

《敏捷估计与规划》一书为对敏捷项目进行估计与规划提供了权威实际的指导方针。在本书中,敏捷联盟的共同创始人Mike Cohn讨论了敏捷估计与规划的思想,并使用现实的例子与案例分析向您详细地展示了如何完成工作。本书清晰地阐述了有关的概念,并引导读者逐步认识到下列一些问题的答案:我们要构建什么?它的规模有多大?需要在什么时候完成?到那个时候我们到底能完成多少?您首先会认识到优秀的计划由哪些东西组成,接着......一起来看看 《敏捷估计与规划》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

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

RGB HEX 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具