深入Os--静态链接

1.一个实例

#include <iostream>

int main()
{
    printf("main\n");
    return 0;
}

执行:g++ -std=c++11 main.cpp,我们得到a.out
执行:objdump -dx a.out > 1.txt,查看1.txt。
截取以下信息:
在这里插入图片描述
在这里插入图片描述
我们上述简单的实例可执行程序引用了来自C库的符号printf
这里的C库是动态库,程序执行时如何通过callq找到printf符号定义位置属于动态库引用重定位范畴。

2.一个扩展的实例

// main.cpp
#include <iostream>
extern "C" int fun(int i, int j);
int main()
{
    printf("main\n");
    printf("1+2=%d\n", fun(1,2));
    return 0;
}

// func.cpp
extern "C" int fun(int i, int j)
{
    return i+j;
}

执行:g++ -std=c++11 func.cpp -c得到func.o。
执行:g++ -std=c++11 main.cpp -c得到main.o
执行:objdump -dx main.o > 1.txt,查看1.txt。
截取以下信息:
在这里插入图片描述
上述main.o中引用了符号fun。但1.txt中并不包含符号fun的定义。

我们继续执行:g++ -std=c++11 main.cpp fun.cpp
执行:objdum -dx a.out > 11.txt,查漏11.txt。
截取以下信息:
在这里插入图片描述
在这里插入图片描述
可以看到此时有两处改变:
(1).引用符号fun处,由0xe8 00 00 00 00变为0xe8 6a 00 00 00。
(2).fun符号的定义被拷贝到可执行文件a.out内代码段内了。

上述两处改变正是静态链接所作的工作。
(1).将符号引用对应到符号定义。
(2).将符号定义拷贝到可执行文件内。

3.静态库
3.1.实例解析
3.1.1.构建静态库
在这里插入图片描述

// t1.cpp
int addcnt = 0;
void addvec(int *x, int *y, int *z, int n)
{
    int i;
    addcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] + y[i];
}

// t2.cpp
int mulcnt = 0;
void multvec(int *x, int *y, int *z, int n)
{
    int i;
    mulcnt++;
    for (i = 0; i < n; i++)
        z[i] = x[i] * y[i];
}

// t.h
#ifndef _T_H
#define _T_H
extern int addcnt;
void multvec(int *x, int *y, int *z, int n);
void addvec(int *x, int *y, int *z, int n);
#endif

上述结构中,我们基于t1.cpp,t2.cpp构建一个静态库。
构建静态库的makefile为:

main: t1 t2 static

t1:
	g++ -std=c++11 t1.cpp -c
t2:
	g++ -std=c++11 t2.cpp -c

static:
	ar rcs libt.a t1.o t2.o

clean:
	rm *.o libt.a

3.1.2.使用静态库
我们只需借助静态库文件libt.a,静态库提供的头文件t.h。即可引用静态库中导出的符号。
静态库头文件用于为库的使用者提供导出符号的声明。静态库自身则包含导出符号的定义。
以上述实例为例,我们在main.cpp使用了此静态库中定义的全局符号。

#include <stdio.h>
#include "t.h"

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];

int main()
{
    addvec(x, y, z, 2);
    printf("z=[%d %d]\n", z[0], z[1]);
    printf("addcnt_%d\n", addcnt);
    return 0;
}

利用静态库,源文件构建可执行程序的makefile为:

main:
	g++ main.cpp -std=c++11 -I./include -L./static -lt
clean:
	rm a.out

3.2.静态链接解析
3.2.1.何时发生静态链接
上述实例中,我们通过makefile执行:g++ main.cpp -std=c++11 -I./include -L./static -lt将在完成main.cpp的预处理,编译,汇编后,进入链接过程。链接过程对main.cpp引用的来自动态库的符号,来自静态库的符号的处理策略是不同的。对引用的来自静态库的符号的处理,称为静态链接处理。对引用的来自动态库的符号的处理,称为动态链接处理。
我们这里以链接过程main.cpp如何处理引用的来自静态库libt.a的符号来说明静态链接。

(1).通过符号解析将符号引用关联到符号定义
a.链接器维护一个可重定位目标文件的集合E;一个未解析的符号集合U;一个在前面输入文件中已定义的符号集合D。初始时,E、U和D均为空。
b.对编译命令中出现的可重定位目标文件和存档文件进行迭代处理。
b.1.若当前迭代到的是可重定位目标文件,称其为 f 。
把 f 添加到 E,修改U和D来反映 f 中 的符号定义和引用,并继续下一个输入文件。
b.2.若当前迭代到的是存档文件,称其为 F 。
对存档文件中所有的成员执行迭代,设当前迭代到的成员为 m 。
若 m ,定义了一个符号来解析 U 中的一个引用,那么就将 m 加到 E 中,修改 U 和 D 来反映 m 中的符号定义和引用。

为了保证,存档内部迭代时,前面一个成员定义了符号A,但没定义 U 中符号。后面一个成员定义了 U 中符号且引用了A场景下正确性。
上述迭代过程将持续进行,知道对存档中未加入到 E 的成员执行一轮迭代后,U 和 D 无任何变化,再结束。
结束迭代时,存档文件中未加入到 E 的成员,不会出现在最终汇集成的可执行程序文件中。

c. b阶段迭代结束后。
若 U 是非空的,就输出一 个错误并终止。
若 U 是空的,则合并和重定位 E 中的目标文件,构建输出的可执行文件。E 中每个文件的全部内容均将出现在可执行文件中。

上述处理过程要求,编译时,引用静态库符号的模块必须出现在静态库模块的前面。

(2). 将过程(1)中 E 中所有文件汇集得到单独的可执行程序文件。
(3). 对可执行程序文件中每个符号引用执行重定位。
我们针对上述实例,分析main中针对addvec引用通过静态链接执行重定位的过程。
a.查看main.cpp的反汇编,重定位信息
a.1.执行:g++ -std=c++11 main.cpp -I./include -c
a.2.执行:objdump -dx main.o,截取输出中引用addvec部分的汇编
在这里插入图片描述
a.3.执行:objdum -sx main.o,截取输出中重定位节中addvec引用的重定位项
在这里插入图片描述
顺便查看输出中符号表中关于addvec符号的信息:
在这里插入图片描述
因为addvec并非main.cpp中定义的符号。所以,并不清楚此符号的位置。
b.针对执行make得到的a.out查看其数据信息
b.1.针对a.out执行:objdump -sx a.out,截取输出中符号表中addvec符号的信息
在这里插入图片描述
此时我们已经知道了addvec的具体位置信息。这正是链接器执行静态链接中做的工作之一。
有了引用符号的位置信息,再结合引用符号的重定位信息,我们就可以找到符号引用的位置,并将对应位置的内容进行重新设置。以便通过设置后的内容可以找到符号定义位置。
针对a.out执行:objdump -dx a.out,截取引用addvec的汇编部分
在这里插入图片描述
为何说:将引用位置从0x 00 00 00 00修改为0x 3b 00 00 00就是对的呢?
因为0xe8表示的机器指令用于重新设置PC。将PC设置为:新PC = PC + 0xe8后四字节。
这样新PC = 0x400575(在执行到e8指令时,PC位于下一指令起始处)+ 0x3b = 0x4005b5。
而0x4005b5正是a.out中符号addvec定义所在位置。
在这里插入图片描述
3.2.2.静态链接中符号名多次定义问题
(1). 符号的强弱
我们正常定义个函数或变量,其对应的符号均是强符号。
若我们希望定义一个弱符号,需要通过编译器修饰符__attribute__((weak)) 来达到目的。
(2). 链接阶段若收集到多个同名强符号的定义会报错

#include <stdio.h>
#include "t.h"

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];

void addvec(int *x, int *y, int *z, int n)
{
    printf("addvec_in_main\n");
}

int main()
{
    addvec(x, y, z, 2);
    printf("z=[%d %d]\n", z[0], z[1]);
    printf("addcnt_%d\n", addcnt);
    return 0;
}

若我们在上述实例的main.cpp中新增一个addvec的定义,则执行make时会报错。
在这里插入图片描述
(3).链接阶段收集到多个同名符号,但只有一个是强符号,其余为弱符号时,可以正常收集。对符号的引用会执行符号的强定义。

#include <stdio.h>
#include "t.h"

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];

__attribute__((weak)) void addvec(int *x, int *y, int *z, int n)
{
    printf("addvec_in_main\n");
}

int main()
{
    addvec(x, y, z, 2);
    printf("z=[%d %d]\n", z[0], z[1]);
    printf("addcnt_%d\n", addcnt);
    return 0;
}

按上述修改main.cpp执行make正常编译。运行a.out将执行静态库中addvec的强定义。
在这里插入图片描述
(4).链接阶段收集到多个同名符号,全部为弱符号时,可以正常收集。对符号的引用会关联到其中一个弱符号定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值