csapp链接

版权声明:本文为博主转载文章,遵循 CC 4.0 BY-SA 版权协议,转载附上原文出处链接和本声明。
原文链接: https://blog.csdn.net/weixin_44813883/article/details/102856987

链接是什么呢?

链接(Linking) 是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译、加载、运行时,在早期,链接是被手动执行的,在现代系统中,链接是由一个叫做链接器的程序自动执行的。

在这里插入图片描述

  • 预处理时:处理的过程

    • 以#开头的预编译指令;删除以“#define”并展开所定义的宏;处理所有的条件预编译指令,如#if,#define;插入头文件到#include处,可使用递归的方式进行处理;删除所有的注释,添加行号和文件名标识符;保留所有的#pragma编译指令。
    • 经过预处理后,得到的是与处理文件(hello.i),它还是一个可读的文本文件,但是不包含任何宏定义。
  • 编译(compile time)时:将源代码翻译成机器代码。编译的过程就是将得到的预处理文件进行词法、语法、语义分析、生成汇编代码文件,经过编译后的代码文件还是可读的文件,但是CPU无法理解执行它

  • 汇编时

    • 汇编文件代码由汇编指令构成;汇编器(as)用来将汇编语言程序转换成机器指令序列
    • 汇编指令和机器指令都属于机器指令,所构成的程序称为机器及代码。
    • 汇编的结果是一个可重定位目标文件,包含的结果是不可读的二进制代码
  • 链接:将多个可重定位的目标文件合并以生成可执行目标文件。

    • 汇编的语言:用助记符表示操作码,用符号表示位置,用助记符表示寄存器。
    • 链接的操作步骤:确定符号引用关系(符号解析);合并相关.o文件;确定每个符号的地址(优点:模块化,分成多个源程序文件;效率高,可分开编译);在指令中填入新地址。

在gcc 中的执行方法:
1.预处理:hello.c变成helloc.i(命令gcc -E或cpp)
2.编译:hello.i变成hello.s(命令gcc -0g -S)
3.汇编:hello.s变成hello.o(gcc或者as)
4.链接:hello.o+所需静态库——hello(gcc或ld)

链接的本质是什么呢?

在这里插入图片描述
从图中可以看出连接的本质就是合并相同的节,在合并之前还要进行符号解析

符号解析
  • 符号分为强符号和弱符号
    • 强符号:函数名和已初始化的全局变量名是强符号
    • 弱符号:未初始化的全局变量名是弱符号,外部符号引用函数声明
  • 在Linux链接器中使用下面的规则来处理多重定义的符号名:
    • 规则1:不允许有多个同名的强符号;
    • 规则2:如果有一个强符号和多个弱符号同名,那么选择强符号;
    • 规则3:如果有多个弱符号同名,那么从这些弱符号中任意选择一个。
  • 多重定义符号的处理规则:强符号只能被定义一次,否则链接错误。

知道了这些知识后,那我们来具体看看在Linux中是怎么样的吧

//mismatch-main.c
long int x;  /* Weak symbol */
#include <stdio.h>
	int main(int argc, char *argv[]) {
    
    
	printf("%ld\n", x);
return 0;
}
 
  
  
 
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
//文件mismatch-variable
/* Global strong symbol */
double x = 3.14;
 
  
  
 
  
  
  • 1
  • 2

第一段代码中定义了一个长整型的弱符号x,并且没有赋予初值,在第二段代码mismatch-variable中定义了一个长整型的强符号x,并赋值为3.14。
在Linux平台下输入gcc -Wall -Og -o mismatch mismatch-main.c mismatch-variable.c
可得到一个重定位目标文件mismatch
这时我们再输入./mismatch可得到一个结果为4614253070214989087
在这里插入图片描述那为什么是这个结果呢?
是因为强符号x=3.14,而浮点数在计算机中的存储、运算、表示等是按照IEEE754标准。
在命令gcc -Wall -Og -o mismatch mismatch-main.c mismatch-variable.c中

  • -Wall是表示允许发出gcc提供的所有有用的报警信息
  • -Og是表示启用全局优化
  • -o是表示设定输出文件名,不加此选项会默认可执行文件名为a.out
  • 此外,这条命令也可以将.o文件链接到可执行目标文件。
//global.h
extern int g;
int f();
 
  
  
 
  
  
  • 1
  • 2
//global-c1.c
#include "global.h"
int f() {
    
    
    return g+1;
}
 
  
  
 
  
  
  • 1
  • 2
  • 3
  • 4
//global-c2.c
#include <stdio.h>
#include <stdlib.h>
#include "global.h"
int g = 0;
	int main(int argc, char *argv[]) {
    
    
 	if (argc >= 2) {
    
    
 	g = atoi(argv[1]);
    	}
    	printf("g = %d.  f() = %d\n", g, f());
    return 0;
}
 
  
  
 
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在Linux下输入gcc -Wall -Og -o global global.h global-c1.c global-c2.c后再输入./global可得到如下结果

在这里插入图片描述
在这里是定义了一个int型的全局变量g,并且赋予了初值0,所以执行出来的结果是没有问题的。

与静态库连接

所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库(static library),它可以用来做链接器的输入。
库函数模块是许多的函数如printf,scanf,sqrt等函数都无需自己再去写,只要从共享的库函数中调用。
在Linux系统中,静态库以一种存档(archive)的特殊文件格式存放在磁盘中。存档文件时一组连接起来的可重定位目标文件的集合,有一个头不用来描述每个成员目标文件的大小和位置。存档文件名由后缀 .a标识,这样可以增强链接器功能,时期工呢过通过查找一个或者多个库文件中的定义符号来解析符号。

要创建这些函数,我们需要使用AR工具
ar (归档程序)能将制定的.o文件打包生成静态库文件。
接下来看一个例子

/* addvec.c */
/* $begin addvec */
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];
}
/* $end addvec */
 
  
  
 
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
/* multvec.c */
/* $begin multvec */
int multcnt = 0;
void multvec(int *x, int *y, 
      int *z, int n) 
{
    
    
    int i;
     multcnt++;
     for (i = 0; i < n; i++)
 z[i] = x[i] * y[i];
}
/* $end multvec */
 
  
  
 
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

将这两段代码打包到libvector库函数中
通过使用

  • gcc -c addvec.c multvec.c
  • ar rcs libvector.a addvec.o multvec.o
/* main2.c */
/* $begin main2 */
#include <stdio.h>
#include "vector.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]);
    return 0;
}
/* $end main2 */
 
  
  
 
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在main2.c 代码段中包含了头文件vector.h,定义了libvextor.a中例程的函数原型
接下来输入

  • gcc -c main2.c
  • gcc -static -o prog2c main2.o ./libvector.a
  • ./prog2c
    可得到以下结果:
    在这里插入图片描述
    链接的过程如下:
    在这里插入图片描述
重定位

链接器完成符号解析后就可以进行重定位了。重定位由两部分组成:

  • 重定位节和符号定义,链接器将所有相同类型的节合并为同一个类型的新的聚合类。
  • 重定位节中的符号引用,在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行时地址。执行这一步,链接器主要依赖于可重定位目标模块中称为重定位条目(relocation entry)的数据结构。
    可重定位目标文件

在这里插入图片描述

  • .text:已编译程序的机器代码
  • .rodata:只读数据,比如printf语句中的格式串和开关语句的跳转表
  • .data:已初始化的全局和静态C变量,局部C变量保存在栈中
  • .bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量
  • .symtab:符号表,存放在程序中定义和引用的函数和全局变量的信息

接下来我们看一下代码中的符号

#include <stdio.h>
int time;
int foo(int a) {
    
    
    int b = a + 1;
    return b;
}
int main(int argc, char *argv[])
{
    
    
    printf("%d\n", foo(5));
    return 0;
}
 
  
  
 
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在Linux环境中敲readelf -s symbols.o
可以得到
在这里插入图片描述

  • 可以知道的是foo,main为全局变量,并且他们都为函数,所以为强符号。
    foo是占4个字节,因为它是int型
  • printf是没有类型的,它是全局变量,UND应该表示的是未定义的
  • time 也是占4个字节,是int型的变量,它也是全局变量,然后它是COM,这个是表示它是未初始化的全局变量。
  • 同时代码中还有很多的局部变量。

objdump -dx main.o

  • -d表示将代码段反编译
  • -x显示所有的可用头信息,包括符号表、重定位入口,使用此选项可以将文件内容做标注,方便我们查看理解
    在这里插入图片描述
    这个是可重定位文件而不是可执行目标文件,所以它的起始地址为0。在节部分,大部分节名都有对应,其中File off指明了每个节在ELF中的偏移地址,不是实际地址。Size指明了每个节所占大小,例如.data节的地址为0x00000054加上长度0x00000008就为0x0000005c,即.bss节的地址。
    objdump -dx -j .data main.o
  • -j name显示指定名称为nane的section的信息,
    这里在main.o的反汇编里仅显示。.data节信息在这里插入图片描述
    运用该指令我们只会看到.data节中的信息,最后反汇编.data节里存放的全局变量array,由于它已被初始化,且机器为小端模式,因此,00000000 <array>: 0: 01 00 00 00 02 00 00 00

我真的太垃圾了,这个作业拖了一个月才完成。之前刚学的时候是真的不会,也不知道老师给的代码要怎么样在Linux下去运行,这两天看了很多的别的同学写的日志,自己经过琢磨,实践,终于完成了。写完这些东西发现对知识又有了进一步的了解,毕竟这两天都在和它打交道。总之,自己还是太差劲了,还需要很努力很努力!


该文章中的许多信息来源于《深入理解计算机系统》,前两张图片来源于南京大学袁春风老师课件,有一张图片来源于老师所给的PPT,部分内容参考于https://blog.csdn.net/ziyonghong/article/details/101560077
https://blog.csdn.net/angranxueer/article/details/102236976

加一点东西
在hello.c文件中

#include <stdio.h>
int main()
{
    
    
 printf("Hello World!");
                return 0;
}
 
  
  
 
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 用readelf -S hello.o查看
    在这里插入图片描述
    在这里插入图片描述
  • 用readelf -h hello.o查看
    在这里插入图片描述
  • 用readelf -s hello.o查看
    在这里插入图片描述
                                </div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet">
                </div>
版权声明:本文为博主转载文章,遵循 CC 4.0 BY-SA 版权协议,转载附上原文出处链接和本声明。
原文链接: https://blog.csdn.net/weixin_44813883/article/details/102856987

猜你喜欢

转载自blog.csdn.net/xiongdan626/article/details/103424797
今日推荐