7、iOS强化 --- 静态链接(详解)

之前我们在讲链接与符号的时候提到了静态链接动态链接,本章我们来详细的梳理一下静态链接
接下来我们用实例来讲解一下:代码如下:

// a.c 文件
extern int global_var;

void func(int a);

int main(int argc, const char * argv[]) {
    int a = 99;
    func(a + global_var);
    return 0;
}
// b.c 文件
int global_var = 1;

void func(int a) {
    global_var = a;
}

首先我们生成a.o & b.o

xcrun -sdk iphoneos clang -c a.c b.c -target arm64-apple-ios12.2

接着将a.o & b.o合并生成可执行文件:

xcrun -sdk iphoneos clang a.o b.o -o ab -target arm64-apple-ios12.2

⚠️ 注意:这里生成的两个目标文件都是基于arm64架构。a.o & b.o通过静态链接后生成Mach-O文件ab。(其实这里基于arm64架构,链接的过程也是有动态库(系统库)参与的,这里我们只讨论静态链接。)

image.png

这里我们先认识两个概念:模块 & 符号
对于符号在前面的文章中已经做了介绍,这里我们就再简单的讲一下:

名字 解释
模块 可以理解为一个源代码文件就是一个模块,比如上面的a.c & b.c。我们在实际的开发中,一般来讲一个类在一个源文件上,就形成了一个模块。模块化的好处就是易于复用和维护,再一点就是在编译的时候,未改动的模块不用从新编译,直接用之前编译好的缓存就行。
符号 简单理解就是函数名和变量名,比如上面的mainglobal_varfunc

空间和地址分配

相似段合并
  • 静态链接:将多个目标文件合并成一个可执行文件。
    在这个过程中,把多个目标文件里面相同性质的段合并到一起。
    比如我们来合并a.o & b.o,生成ab(Mach-O);在合并的过程中,a.o & b.o里面的数据段一起合并成ab里面的数据段;同理,数据段也是一样的。

两步链接
  • 第一步:空间与地址分配
    扫描所有的输入目标文件,并且获得他们各个段的长度属性位置,将输入目标文件中的符号表中所有的符号定义符号引用收集起来,统一放到一个全局符号表中。这一步中,链接器能够获得所有的输入目标文件的段的长度,将它们合并,计算出输出文件中各个段合并后的长度位置,并建立映射关系。

  • 第二步:符号解析和重定位
    使用上面第一步收集到的信息,读取输入文件中段的数据、重定位信息,并且进行符号解析重定位,调整代码中的地址等。

    重定位
    a模块使用了global_varfunc两个符号,那么怎么知道这两个符号的地址呢?
    我们来看一下a.o文件:

    image.png

    global_var(地址0x28) 和 func(地址0x3C)的地址都是假地址。编译器暂时用假地址替代,把真正的地址计算工作留给链接器。通过前面的空间与地址分配可以得知,链接器在完成地址与空间分配之后,就可以确定所有符号的虚拟地址了。也就是说,链接器可以根据符号的地址对每个需要重定位的指令进行地址修正。
    我们再来看一下ab可执行文件:
    image.png

    可以看到global_var (地址:0x100007000,指向data段,值为1) 和 func(地址: 0x100007F90,指向func函数地址)都是真实的地址。

    重定位表
    链接器是如何知道a模块里面有哪些指令被调整,这些指令该如何调整。
    这是因为:在a.o里面,有一个重定位表,专门保存这些与重定位相关的信息。而且每个sectionsection_64headerreloff(重定位表里的偏移) 和 nreloc(需要重定位的符号的数量),让链接器知道a模块里面的哪个section里的指令需要调整。

struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};

重定位表可以可以认为是一个数组,数组里的元素为结构体relocation_info

struct relocation_info {
   int32_t  r_address;  /* offset in the section to what is being
                   relocated */
   uint32_t     r_symbolnum:24, /* symbol index if r_extern == 1 or section
                   ordinal if r_extern == 0 */
        r_pcrel:1,  /* was relocated pc relative already */
        r_length:2, /* 0=byte, 1=word, 2=long, 3=quad */
        r_extern:1, /* does not include value of sym referenced */
        r_type:4;   /* if not 0, machine specific relocation type */
};

r_addressr_length可以让我们知道要重定位的字节;r_symbolnum(当为外部符号时)是符号的index。
我们看一Relocation里面的内容:

image.png

下面我们去Symbol Table里面找对应的符号:
image.png

通过上面可以看到,a.o中的重定位表(Relocation)记录了符号_func_global_var,两个符号需要重定位。并且给出了两个符号在代码段中的位置,和指向符号表(Symbol Table)index,链接的时候(a.o里面有这两个符号的引用,b.o里面有这两个符号的定义,一起合并到全局符号表里面),在全局符号表中可以找到这两个符号的虚拟内存地址和其它信息,就可以完成重定向工作了。

上面说道r_symbolnum(当为外部符号) 是符号表的index,我们再来看一下加载命令:符号表

/*
 * 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 */
};

加载命令的前两个参数是cmd&cmdsize;符号表加载命令的symoff&nsyms告诉链接器符号表的位置(偏移)和个数;stroffstrsize告诉字符串表的位置和大小。这个我们在5、iOS强化 --- 链接与符号(补充内容)有提到过。

符号表也是一个数组,里面的元素是结构体nlist_64(<mach-o/nlist.h>):

/*
 * This is the symbol table entry structure for 64-bit architectures.
 */
struct nlist_64 {
    union {
        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 */
    uint16_t n_desc;       /* see <mach-o/stab.h> */
    uint64_t n_value;      /* value of this symbol (or stab offset) */
};

n_strx代表字符串标的index,可以找到符号对应的字符串;
n_sect代表第几个section
n_value代表符号的地址值。

image.png

符号解析
为什么要链接呢?
因为一个模块(a模块)可能引用了其它模块(b模块)的符号,所以需要把多有的模块(.o目标文件)链接在一起。
重定位就是:链接器会去查找由所有输入的.o目标文件的符号组成的全局符号表,找到相应的符号后进行重定位。

⚠️ 其中两个常见的错误:
1、ld: dumplicate symbols:多个目标文件里面有相同的符号,导致全局符号表出现多个一样的符号。
2、Undefined symbols:需要重定位的符号,在全局符号表里面没有找到(一个符号:有引用,未定义)。

静态库链接

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

静态库一般包含多个目标文件,可是链接器在链接静态库的时候是以目标文件为单位的。假设我们把所有的函数放在一个目标文件里面,而我们只用到了一个函数,此时却把很多没有用到的函数一起链接到了可执行文件里面。

静态库链接示意图:


image.png

参考文档:https://juejin.cn/post/6844903912198127623

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 简介 C/C++ 代码在变成可执行文件之前需要经历预处理、编译、汇编以及链接这几个步骤,最终生成的可执行文件包含了...
    然则阅读 1,242评论 1 9
  • 静态链接 当有两个目标文件时,如何将它们连接起来形成一个可执行文件?其中发生了什么? 使用两个源代码文件作为研究例...
    Cool_Pomelo阅读 248评论 0 0
  • 原文链接 通过前面对ELF文件结构的详细介绍,我们对ELF目标文件从整体轮廓到局部细节都有了一定的了解。那么接下来...
    baochuquan阅读 1,777评论 2 7
  • # 链接概述 链接通常是一个让人比较费解的过程,为什么汇编器不直接输出可执行文件而是输出一个目标文件呢?链接过程到...
    Tenloy阅读 785评论 0 5
  • 夜莺2517阅读 127,712评论 1 9