美文网首页ios进阶selector
5、iOS强化 --- 链接与符号(补充内容)

5、iOS强化 --- 链接与符号(补充内容)

作者: Jax_YD | 来源:发表于2021-03-10 10:34 被阅读0次

    上一篇文章4、iOS强化 --- 链接与符号(Symbol)中我们提到了链接
    链接的本质就是把多个目标文件组合成一个文件
    但是有一些地方说的不够详细,这里我们做一下补充。

    首先我们知道,在生成.o目标文件的过程中,链接器 (llvm-ld) 并没有被执行,这个过程就是编译的过程。
    那么llvm-ld在哪里执行呢?
    答案:在最终生成Mach-O文件的过工程中被执行。
    过程如下:

    image.png

    1、多个目标文件合并
    2、符号表(包括重定位符号表)合并成一张表
    3、生成一个Mach-O可执行文件

    注意⚠️ :上面从生成.o到生成Mach-O这整一个过程才是一个完整的链接过程

    • 链接有又分为静态链接动态链接

    静态链接

    • 静态链接:将多个目标文件合并成一个可执行文件。这个过程中会把多个目标文件相同性质的段合并到一起。比如a.ob.o合并成可执行文件ab;其过程就是将a.o里面的代码段和b.o里面的代码段合并到一起,数据段同理等等。

      image.png
    • 静态库链接
      一个静态库可以简单看做一个目标文件的合集,也就是多个目标文件经过压缩合并形成的一个文件。
      而静态库链接,就是将自己的目标文件与静态库里面的某个模块(用到的一个或多个目标文件)链接成可执行文件。

      image.png

    动态链接

    既然有了静态链接,那么动态链接又是怎么回事呢?
    通过上面我们知道,静态链接是将多个目标文件合并成一个可执行文件,如果遇到静态库那就将静态库也合并进去。这样在实际开发中就会有一个问题:

    比如:我们开发中经常会用到的Foundation框架(Apple提供的),如果采用静态链接的方式,那么市面上所有用到它的APP都要在自己的Mach-O文件中集成它。试想一下,一旦该库出现问题,那所有用到它的APP都要从新集成,从新上架。这样做不仅让APP的体积变大,而且还极不方便。
    因此,动态链接应运而生。
    只需要在手机端存放Foundation框架,当APP运行的时候,发现使用到了Foundation,这时候再去链接,加载到内存中。这样就方便很多了。即使Foundation出问题,那么直接更新Foundation就可以了,并不需要同时更新APP。

    动态链接是在运行时由dyld(动态链接器)动态加载的。

    • 静态链接 与 动态链接 的加载过程
      image.png

    可执行文件的调用过程

    • 1、调用fork函数,创建一个process
    • 2、调用execve(程序加载器) 或其衍生函数,在该进程上加载,执行我们的Mach-O文件
      当我们调用execve(程序加载器)内核实际上在执行以下操作:
      ①:将文件加载到内存
      ②:开始分析Mach-O中的mach_header,以确认它是有效的Mach-O文件

    如何查看链接器的参数

    在终端使用下面的命令可以查看链接器的参数

    man ld
    
    image.png
    使用/ + 要查找的关键字即可进行检索。
    n代表向下检索,N代表向上检索。

    Symbol Table

    下面我们来讲一下符号(Symbol) 以及符号表(Symbol Table)
    ⚠️⚠️⚠️ 请大家结合3、iOS强化 --- Mach-O 文件这篇文章来阅读下面的内容。

    • Mach-O中通过两个load commands:
      1、LC_SYMTAB:当前Mach-O
      2、LC_DYSYMTAB:描述动态链接器使用其他的Symbol Table信息。
      用来描述Symbol Table的大小和位置,以及其他元数据。
    LC_SYMTAB

    用来描述该文件的符号表。不论是静态链接器还是动态链接器在链接此文件时,都要使用该load command。调试器也可以使用该load command找到调试信息。

    • symtab_command
      定义LC_SYMTAB加载命令的具体属性。
      /usr/include/mach-o/loader.h中的定义:
    /*
     * The symtab_command contains the offsets and sizes of the link-edit 4.3BSD
     * "stab" style symbol table information as described in the header files
     * <nlist.h> and <stab.h>.
     */
    struct symtab_command {
        // 共有属性。指明当前描述的加载命令,当前被设置为LC_SYMTAB
        uint32_t    cmd;        /* LC_SYMTAB */
        // 共有属性。指明加载命令的大小,当前被设置为sizeof(struct symtab_command)
        uint32_t    cmdsize;    /* sizeof(struct symtab_command) */
        // 表示从文件开始到 symbol table 所在位置的偏移量。symbol table用[nlist]来表示
        uint32_t    symoff;        /* symbol table offset */
        // 符号表内符号的数量
        uint32_t    nsyms;        /* number of symbol table entries */
        // 表示从文件开始到 string table 所在位置的偏移量
        uint32_t    stroff;        /* string table offset */
        // 表示string table大小(以byte为单位)
        uint32_t    strsize;    /* string table size in bytes */
    };
    
    • nlist
      定义符号的具体表示含义:
    /*
     * Format of a symbol table entry of a Mach-O file for 32-bit architectures.
     * Modified from the BSD format.  The modifications from the original format
     * were changing n_other (an unused field) to n_sect and the addition of the
     * N_SECT type.  These modifications are required to support symbols in a larger
     * number of sections not just the three sections (text, data and bss) in a BSD
     * file.
     */
    struct nlist {
        // 表示该符号在 string table 的索引
        union {
    #ifndef __LP64__
            // 在 Mach-O 中不使用该字段
            char *n_name;    /* for use when in-core */
    #endif
            // 索引
            uint32_t n_strx;    /* index into the string table */
        } n_un;
        uint8_t n_type;        /* type flag, see below */
        uint8_t n_sect;        /* section number or NO_SECT */
        int16_t n_desc;        /* see <mach-o/stab.h> */
        uint32_t n_value;    /* value of this symbol (or stab offset) */
    };
    
    • n_type
      1字节,通过四位掩码保存数据:
    名字 含义
    N_STAB(0xe0) 如果当前的n_type包含这3位中的任何一位,则该符号为调试符号表(stab)。在这种情况下,整个n_type字段将被解释为stab value。请参阅 /usr/include/mach-o/stab.h ,以获取有效的stab value
    N_PEXR(0x10) 如果当前的n_type包含此位。则将此符号标记为**私有外部符号
    __private_extern__(visibility=hidden) 只在程序内可引用和访问。当文件通过静态链接器链接的时候,不要讲其转换成静态符号(可以通过ld-keep_private_externs关闭静态链接器的这种行为)。
    N_TYPE(0x0e) 如果当前的n_type包含此位。则使用预先定义的符号类型。
    N_EXT(0x01) 如果当前的n_type包含此位。则此符号为外部符号;该符号在该文件外部定义或在该文件中定义,但可以在其他文件中使用。
    • N_TYPE
      N_TYPE字段的值包含:
    名字 含义
    N_UNDF(0x0) 该符号未定义。未定义符号是在当前模块中引用,但是被定义在其他模块中的符号。n_sect字段设置为NO_SECT
    N_ABS(0x2) 该符号是绝对符号。链接器不会更改绝对符号的值。n_sect字段设置为NO_SECT
    N_SECT(0xe) 该符号在n_sect中指定的段号中定义。
    N_PBUD(0xc) 该符号未定义,镜像使用该符号的与绑定值。n_sect字段设置问NO_SECT
    N_INDR(0xa) 该符号定义为与另一个符号相同。n_value字段是string table中的索引,用于指定另一个符号的名称。链接该符号时,此符号和另一个符号都具有相投的定义类型和值。
    #define N_GSYM      0x20    /* 全局符号 global symbol: name,,NO_SECT,type,0 */
    #define N_FNAME     0x22    /* procedure name (f77 kludge): name,,NO_SECT,0,0 */
    #define N_FUN       0x24    /* 方法/函数 procedure: name,,n_sect,linenumber,address */
    #define N_STSYM     0x26    /* 静态符号 static symbol: name,,n_sect,type,address */
    #define N_LCSYM     0x28    /* .lcomm symbol: name,,n_sect,type,address */
    #define N_BNSYM     0x2e    /* begin nsect sym: 0,,n_sect,0,address */
    #define N_AST       0x32    /* AST file path: name,,NO_SECT,0,0 */
    #define N_OPT       0x3c    /* emitted with gcc2_compiled and in gcc source */
    #define N_RSYM      0x40    /* 寄存器符号 register sym: name,,NO_SECT,type,register */
    #define N_SLINE     0x44    /* 代码行数 src line: 0,,n_sect,linenumber,address */
    #define N_ENSYM     0x4e    /* nsect符号结束 end nsect sym: 0,,n_sect,0,address */
    #define N_SSYM      0x60    /* 结构体符号 structure elt: name,,NO_SECT,type,struct_offset */
    #define N_SO        0x64    /* 源码名称 source file name: name,,n_sect,0,address */
    #define N_OSO       0x66    /* 目标代码名称 object file name: name,,(see below),0,st_mtime */
    /*   historically N_OSO set n_sect to 0. The N_OSO
     *   n_sect may instead hold the low byte of the
     *   cpusubtype value from the Mach-O header. */
    #define N_LSYM      0x80    /* 本地符号 local sym: name,,NO_SECT,type,offset */
    #define N_BINCL     0x82    /* include file 开始 include file beginning: name,,NO_SECT,0,sum */
    #define N_SOL       0x84    /* include file 名称 #included file name: name,,n_sect,0,address */
    #define N_PARAMS    0x86    /* 编译器参数 compiler parameters: name,,NO_SECT,0,0 */
    #define N_VERSION   0x88    /* 编译器版本 compiler version: name,,NO_SECT,0,0 */
    #define N_OLEVEL    0x8A    /* 编译器 -O 级别 compiler -O level: name,,NO_SECT,0,0 */
    #define N_PSYM      0xa0    /* 参数 parameter: name,,NO_SECT,type,offset */
    #define N_EINCL     0xa2    /* include file 结束 include file end: name,,NO_SECT,0,0 */
    #define N_ENTRY     0xa4    /* alternate entry: name,,n_sect,linenumber,address */
    #define N_LBRAC     0xc0    /* 左括号 left bracket: 0,,NO_SECT,nesting level,address */
    #define N_EXCL      0xc2    /* deleted include file: name,,NO_SECT,0,sum */
    #define N_RBRAC     0xe0    /* 右括号 right bracket: 0,,NO_SECT,nesting level,address */
    #define N_BCOMM     0xe2    /* 通配符开始 begin common: name,,NO_SECT,0,0 */
    #define N_ECOMM     0xe4    /* 通配符结束 end common: name,,n_sect,0,0 */
    #define N_ECOML     0xe8    /* end common (local name): 0,,n_sect,0,address */
    #define N_LENG      0xfe    /* second stab entry with length information */
    
    /*
     * for the berkeley pascal compiler, pc(1):
     */
    #define N_PC        0x30    /* global pascal symbol: name,,NO_SECT,subtype,line */
    
    • n_sect
      整数,用来在指定编号的section中找到此符号;如果在该image的任何部分都找不到该符号,则为NO_SECT。根据sectionLC_SEGMENT加载命令中出现的顺序,这些section从1开始连续编号。
    • n_desc
      16_bit值,用来描述非调试符号。低三位使用REFERENCE_TYPE
    名字 含义
    REFERENCE_FLAG_UNDEFINED_NON_LAZY(0x0) 该符号是外部非延迟(数据)符号的引用。
    REFERENCE_FLAG_UNDEFINED_LAZY(0x1) 该符号是外部延迟性符号(即对函数调用)的引用。
    REFERENCE_FLAG_DEFINED(0x2) 该符号在该模块中定义。
    REFERENCE_FLAG_PRIVATE_DEFINED(0x3) 该符号在该模块中定义,但是仅对共享库中的模块可见。
    REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY(0x4) 该符号在该文件的另一个模块中定义,是非延迟加载(数据)符号,并且仅对该共享库中的模块可见。
    REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY(0x5) 该符号在该文件的另一个模块中定义,是延迟加载(函数)符号,仅对该共享库中的模块可见。
    REFERENCED_DYNAMICALLY(0x10) 定义的符号必须是使用在动态加载器中(例如dlsymNSLookupSymbolInImage)。而不是普通的未定义符号引用。strip使用该位来避免删除那些必须存在的符号(如果该符号设置了该位,则strip不会剥离它)。
    N_DESC_DISCARDED(0x20) 在完全链接的image在运行时,动态链接器有可能会使用此符号。不要在完全链接的image中设置此位
    N_NO_DEAD_STRIP(0x20) 定义在可重定位目标文件(类型为MH_OBJECT)中的符号设置时,指示静态链接器不对该符号进行dead-strip(死代码剥离)。(⚠️ 注意:与N_DESC_DISCARDED(0x20)用于两个不同的目的。)
    N_WEAK_REF(0x40) 表示此未定义符号是弱引用。如果动态链接器找不到该符号的定义,则将其符号地址设置为0。静态链接器会将此符号设置弱链接标志。
    N_WEAK_DEF(0x80) 表示此符号为若定义符号。如果静态链接器或动态链接器为此符号找到另一个(非弱)定义,则弱定义将被忽略。只能将合并部分中的符号标记为弱定义。

    如果该文件是二级命名two_level namespace image(即如果mach_header中设置了MH_TWOEVEL标志),则n_desc的高8位表示定义此未定义符号的库的编号。使用宏GET_LIBRARY_ORDINAL来获取此值,或者使用宏SET_LIBRARY_ORDINAL来设置此值。0指定当前的image
    1到253根据文件中LC_LOAD_DYLIB命令的顺序表明库号。254用于需要动态查找的未定义符号(仅在OS X v10.3及以上版本中支持)。
    255用来指定可执行image
    对于flat namespace images,高8位必须是0。

    two_levelnamespace & flat_namespace

    上面提到了一级命名空间和二级命名空间,这里我们来看一下到底是什么意思。
    链接器默认采用二级命名空间,也就是除了会记录符号名称,还会记录符号属于哪个Mach-O的。比如会记录下来_NSLog来自Foundation
    那这样做有什么作用呢?
    采用二级命名空间之后,还拿_NSLog来说,我们引入了Foundation,已经存在了_NSLog。但是我们可以在自己的工程中定义一个名字一样的符号。而且不会冲突。

    相关文章

      网友评论

        本文标题:5、iOS强化 --- 链接与符号(补充内容)

        本文链接:https://www.haomeiwen.com/subject/ruvvqltx.html