计算机系统-链接

news2025/1/16 2:59:54

例行前言:

本篇不是学习课程时的笔记,是重看这本书时的简记。对于学习本课程的同学,未涉及的内容不代表考试不涉及,部分省略的部分是在该课程的讨论课中学习的(PIC,放出了我在讨论课中的PPT作为参考),核心内容是链接的过程(符号解析与重定位)。本章与其他章关联的内容有但不限于:程序的加载运行与虚拟存储器、程序控制流章节的相关知识。


计算机系统-链接

在这里插入图片描述

本章主要介绍链接工作,链接的过程,并介绍了相关的一些概念(可重定位目标文件,程序的加载执行等)。介绍了静态库,动态库的使用。

1.概述

链接是将各种代码和数据部分组合成一个文件的过程,这个文件可被加载到存储器并执行。链接工作由链接器来完成。链接使一个大型应用可以分解为多个模块,独立的进行修改和编译,当一个模块改变时,只需要重新编译该模块,然后重新链接,而不必重编译其他文件。

假设一个程序由swap.c和main.c两个文件构成,其编译为一个可执行的文件可以使用以下指令:

gcc -g -o p main.c swap.c

实际的过程包含预处理,编译,汇编,链接:

cpp main.c -o main.i
cc1 main.i -o main.s
as main.s -o main.o
//..swap.c也经历以上过程,得到swap.o
ld main.o swap.o p

在这里插入图片描述

swap.o和main.o是两个可重定位目标文件,接下来先说明目标文件和可重定位目标文件,在本章的最后,还会介绍可执行目标文件,以及可执行目标文件加载到存储器的过程。

目标文件

目标文件一共有三种格式:

  • 可重定位目标文件:包含二进制代码和数据,可以与其他可重定位目标文件合并成一个可执行目标文件
  • 可执行目标文件:可直接被载入存储器执行的文件
  • 共享目标文件:特殊的可重定位目标文件,可以在加载或者运行时被动态链接到存储器并链接

各个系统之间的目标文件格式都不同,但基本概念是相同的。接下来重点介绍Unix的可重定位目标文件ELF,这是本章所介绍的链接工作需要处理的文件。

可重定位目标文件ELF

一个典型的ELF可重定位目标文件的格式如下,并说明了其中的各个部分的作用,在这一节还没有说明链接需要哪些信息,可以在学习后面的部分之后,再回到这一节对照ELF文件中的各个部分。

在这里插入图片描述

  • ELF头:机器类型,节头部表的位置及条目数量和大小,目标文件类型等信息
  • .text:程序的机器代码
  • .rodata:只读数据
  • .data:初始化的全局变量。局部变量在栈中管理,不出现在该节或.bss中
  • .bss:未初始化的全局变量,只是一个占位符,不占用实际空间
  • .symtab:符号表。存放程序中定义和引用的函数和全局变量的信息。不包含局部变量
  • .rel.text:一个.text节中位置的列表,代码中会有一些需要重定位的符号(全局变量,外部函数等),这个节记录了这些需要重定位的符号的位置和一些其他信息
  • .rel.data:被模块引用或定义的全局变量的重定位信息。任何已初始化的全局变量,如果初始值是全局变量的地址或外部函数的地址,都需要修改
  • .debug、.line:行号和调试符号表,编译时有-g才会存在
  • .strtab:一个字符串表,包含节头部中的节名字等

2.静态链接

链接器需要完成的任务是将一组可重定位目标文件合并为一个可执行目标文件。为了构造可执行目标文件,链接器完成两个任务:

  • 符号解析:将每个符号引用和一个符号定义联系起来
  • 重定位:将每个符号定义与一个存储器位置联系起来,并修改所有对符号的引用,使它们指向存储器中的正确位置

符号解析

符号解析的工作是将每个符号引用和一个符号定义联系起来。符号引用和定义的信息存储在符号表中。

符号和符号表

一共有三种类型的符号:

  • 当前模块定义的被其他模块引用的全局符号(非静态函数和非静态全局变量)
  • 当前模块引用的由其他模块定义的全局符号,这些符号称为外部符号
  • 只被当前模块定义和引用的本地符号

符号表中不包含局部变量,这些变量在栈中,不需要在链接中处理和管理。不过static属性的本地过程变量不在栈中,而是本地符号。

符号表由一个个条目构成,以main.o的符号表中的几个条目来说明符号表条目的构成:

在这里插入图片描述

  • Num:条目序号
  • Value:距离定义符号的节的起始位置的偏移
  • Size:符号目标的大小
  • Type:符号是数据/函数
  • Bind:表示符号是本地的还是全局的
  • Ndx:符号所在的节的索引,1表示.text,3表示.data
  • Name:符号名

前两条是符号定义,最后一条是符号引用,其Ndx为UND,类型为NOTYPE,是在swap.c中的定义的,是一个外部符号。

符号解析

对于每个符号引用,链接器只需要扫描所有的可重定位目标文件的符号表,就应该能找到其定义,建立引用与定义之间的联系,如果没有找到,则会产生链接错误。更复杂的情况是在多个文件中有对一个符号的多个定义,链接器如何解析多重定义的全局符号,是符号解析中要解决的重要问题,接下来介绍这个问题的处理方式。

每个全局符号是的。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号

Unix链接器使用以下规则来处理多重定义的符号:

  • 不允许多个强符号
  • 如果有一个强符号和多个弱符号,选择强符号
  • 如果有多个弱符号,选择任意一个

在编写程序中,必须遵守这三条规则,否则可能会有很难察觉的错误,因为编译器不会表明其检测到了多个定义。可以用GCC-fno-common选项调用链接器,让编译器输出对多重定义的警告。以下是一个多重符号定义带来的问题:

//foo3.c
void f(void);
int x = 15213;
int main(){
    f();
    printf("x=%d\n",x);
    return 0;
}
//bar3.c
double x;
void f(){ x = -0.0; }

x会采取强定义,成为int型数据,但在f()中,作为double赋值了8个字节,这导致x之后的四个字节被写入了错误的数据。

重定位

完成符号解析后,链接器可以开始重定位了。重定位有两步:

  • 重定位节和符号定义:链接器将相同类型的节合并为同一类型的新的聚合节。此时,每一个节中的指令和全局变量都有唯一的运行时存储器地址。
  • 重定位节中的符号引用:修改代码节和数据节中对每个符号的引用,使它们指向正确的运行时地址。这是根据重定位条目完成的。

重定位节和符号定义比较简单,可以直接看下图:

在这里插入图片描述

现在在可重定位目标文件中使用相对地址的符号都有一个确定的运行时地址了。先说明重定位符号引用依据:重定位条目。

重定位条目在.rel.text和.rel.data中,一个重定位条目的格式如下:

在这里插入图片描述

  • offset:需要重定位的引用的节偏移
  • symbol:需要重定位的符号
  • type:如何修改引用,即重定位的类型

ELF定义了许多种不同的重定位类型,只关注两种最基本的重定位:

  • R_386_PC32:重定位一个使用32位PC地址的相对引用。PC相对地址是距程序计数器PC的当前运行值的偏移量。CPU执行PC相对寻址的指令时,给指令中的32位值加上当前PC,得到有效地址
  • R_386_32:重定位一个使用32位绝对地址的引用。通过绝对寻址,CPU直接用指令中编码的32位值作为有效地址

接下来给出重定位的伪代码,然后分别说明这两种重定位是怎么实现的。此时已经完成了重定位的第一步,每个节和符号已经有运行时地址了。假设s是节的地址,r是一个重定位条目,ADDR(s)是s的运行时存储器地址:

foreach section s{
    foreach relocation entry r{
        refptr = s + r.offset;				//需要重定位的引用位置
        if(r.type==R_386_PC32){
            refaddr = ADDR(s)+r.offset;		//运行时的引用位置
            *refptr = (unsigned)(ADDR(r.symbol)+*refptr-refaddr);
        }
        if(r.type==R_386_32){
            *refptr = (unsigned)(ADDR(r.symbol)+*refptr);
        }
    }
}

重定位PC相对引用

假设main.o中调用了swap程序中的swap():

/*relocation*/
6: e8 fc ff ff ff	call 7<main+0x7>	//一开始的时候,refptr即call后面的内容为0xfffffffc = -4
7:					R_386_PC32 swap

在0x7处有一个重定位,他的实际运行时地址为refaddr = ADDR(s) + r.offset。

修改引用,一开始*refptr是-4,这个初值设定的原因是:当执行CALL指令时,PC的值已经是PC+4了,而PC相对引用是当前指令的PC+相对偏移,所以要-4修正,此时就可以计算相对偏移了:

*refptr = (unsigned) (ADDR(r.symbol) + *refptr - refaddr)	//refptr指向需要修改的引用
    	= (unsigned) (符号地址 - 4 - 引用地址)

假设call指令的位置在重定位后的80483ba,swap的位置(ADDR(r.symbol))在80483c8,重定位计算后的*refptr = 0x9。当执行到CALL时,PC = 80483ba + 4 =0x80483bf,用此时的PC+9就可以得到0X80483c8。

|总结

重定位相对引用是将原来的引用替换为一个相对偏移量,计算引用的实际地址(例如上例的swap()地址),应该是PC+偏移量-4,因为执行call指令时,PC已经加4了,所以要修正,但是执行call指令时,希望只执行一个简单的加法,因此在重定位时将偏移量-4,也就是*refptr的初始值设置为-4,然后计算时加上这个值。

重定位绝对引用

重定位绝对引用只需要直接将引用替换为ADDR(r.symbol)就可以了,*refptr一开始为0。

*refptr = (unsigned)(ADDR(r.symbol)+*refptr);

静态库

编译系统支持将相关的模块的一组可重定位目标文件打包为一个文件,用作链接器的输入,这个文件称为静态库。使用静态库使一些常用的函数可以打包在一起,既方便修改,又不需要在编译链接时添加太多的文件名。

Unix系统中,静态库的文件格式是存档(archive),是一组可重定位目标文件的集合,可以使用以下命令创建库和使用库。

# 得到静态库
gcc -c addvec.c multvec.c
ar rcs libvector.a addvec.o multvec.o		#产生libvector.a静态库
# 编译与链接
gcc -c main.c
gcc -static main.o /libvector.a 

-static告诉编译器,链接器构建一个完全链接可加载到存储器中运行的可执行目标文件,不需要进一步链接。

3.动态链接与共享库

加载与链接共享库

静态库的缺点是需要定期维护更新,并显式将程序与更新的库重新链接,并且对于一些基本的静态库,每个程序都要将其代码复制到自己的代码段中,造成了空间的极大浪费。

共享库是解决静态库缺陷的产物。共享库是一个目标模块,可以在运行时,加载到任意的存储器地址,并和一个在存储器中的程序链接起来,这个过程也称为动态链接,由动态链接器完成。

Unix系统中,共享库用.so后缀表示,在windows中为DLL文件。

共享库不需要拷贝到使用它的可执行文件中,在存储器中可以被不同的运行的进程共享(映射到不同的虚拟地址,这一点可以在学习虚拟存储器章节后理解)。构建并使用一个共享库可以使用以下的命令:

gcc -shared -fPIC -o libvector.so addvec.c multvec.c
gcc main.c ./libvector.so

共享库的链接不拷贝数据和代码,只拷贝一些重定位和符号表信息,在运行时用于解析引用。

当加载器加载和运行可执行文件时,加载部分链接的文件,加载器会注意到可执行文件有一个.interp节,包含动态链接器的路径,加载器会加载和运行动态链接器,然后动态链接器会完成重定位任务:

  • 重定位共享库的数据段和代码段到一个存储器段
  • 重定位所有对共享库定义的符号的引用

完成后,动态链接器将控制传递给应用程序,然后就可以正常的执行了。

在这里插入图片描述

PIC-与位置无关的代码

这一部分内容在讨论课中探讨。PIC.pptx https://www.aliyundrive.com/s/ZX5JrBxdJtE 。

4.程序的加载执行

链接器将多个目标模块合并为一个可执行目标文件,接下来看看这个文件是什么样的格式,又是怎样加载到存储器中运行的。

可执行目标文件

可执行目标文件和可重定位目标文件的格式很相似,只是不需要.rel节,因为重定位已经完成了。.init节定义了一个_init函数,进行程序的初始化。可执行目标文件易于加载,连续的片被映射到连续的存储器段。

在这里插入图片描述

加载可执行目标文件

运行可执行目标文件时,shell会调用存储器来运行这个文件(execve函数调用加载器),加载器将代码和数据拷贝到存储器,跳转到程序的入口开始运行。

Unix程序有固定的运行时存储器映像,各个部分的排布如下:(注意:这些地址都是虚拟地址)

在这里插入图片描述

加载器运行时,按照这个映像以及段头部表的信息,将相关内容拷贝到存储器,然后加载器跳转到符号_start地址处的启动代码,所有c程序都有这个启动代码,调用一系列函数进行初始化等工作:

在这里插入图片描述

atexit注册了一系列程序终止时应该调用的函数。执行结束后_exit运行atexit注册的函数,最后将控制交给操作系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/435622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Sentinel使用

文章目录 一. 初识Sentinel1. 雪崩问题及解决方案2. 服务保护技术对比3. Sentinel介绍和安装4. 微服务整合Sentinel 二. 限流规则1. 快速入门2. 流控模式3. 流控效果4. 热点参数限流 三. 隔离与降级—调用方保护方案1. FeignClinet整合sentinel2. 线程隔离&#xff08;舱壁模式…

vue3中的单文件组件<script setup>和setup函数区别 详解

文章目录 简介基本语法变量和方法多的使用注册组件动态组件使用外部文件方法组件通信props与defineProps、emitdefineEmitsdefineExpose 获取 attrs、slots 和useAttrs、useSlots 方法与普通的 < script > 一起使用v-bind() CSS变量注入style的新特性之global对await异步…

行业认可,知道创宇入选安全牛第十版全景图30个细分领域

近日&#xff0c;国内网络安全领域专业媒体安全牛正式发布了第十版《中国网络安全行业全景图》&#xff08;以下简称“全景图”&#xff09;&#xff0c;知道创宇凭借过硬的技术实力及成熟的市场应用获得行业认可&#xff0c;入围10项一级安全分类共计30项二级细分领域&#xf…

ASEMI代理ADI亚德诺AD8130ARZ-REEL7车规级芯片

编辑-Z AD8130ARZ-REEL7芯片参数&#xff1a; 型号&#xff1a;AD8130ARZ-REEL7 −3dB带宽&#xff1a;250MHz 0.1 dB平坦度的带宽&#xff1a;25MHz 斜率&#xff1a;930V/μs 建立时间&#xff1a;20ns 上升和下降时间&#xff1a;1.5ns 输出超速恢复&#xff1a;30n…

访学案例分享|经济学老师获英国两高校邀请函

D老师所在国内高校鼓励教职员工通过各种渠道公派或者自筹经费出国访学进修。在考虑了学校要求及个人条件后&#xff0c;其决定用自费方式赴英国访学。我们分别获得了英国利兹贝克特大学和邓迪大学的邀请函&#xff0c;最终D老师顺利过签&#xff0c;如期出国。 D老师背景&#…

网络编程【UDP数据报套接字编程】

目录 1.网络编程基础 1.1 为什么需要网络编程&#xff1f; 1.2 什么是网络编程 1.3 网络编程中的基本概念 2.Socket套接字 2.1 分类 3.UDP数据报套接字编程 3.1 DatagramSocket API 3.2 DatagramPacket API 3.3 基于 UDP socket 写一个简单的回显客户端服务器程序&am…

设计模式 -- 桥梁模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

自适应安全、主权云、超级自动化顶级政府技术趋势

根据分析公司 Gartner 的数据&#xff0c;自适应安全、主权云和超级自动化是 2023 年十大政府技术趋势之一。 确定了 2023 年的 10 大政府技术趋势&#xff0c;这些趋势可以指导公共部门领导者在为后数字化政府做准备和不懈地关注任务目标时加速转型。 当前的全球动荡和技术中…

9-2分布迭代次数的15个梯度

( A, B )---1*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有1个节点&#xff0c;AB训练各由11张二值化的图片组成&#xff0c;让A中有3个0&#xff0c;B中全是0&#xff0c;排列组合A的所有可能&#xff0c;统计迭代次数的顺序。 A-B 迭代次数 标准差 势能 标准差势能 11 …

30天学会《Streamlit》(6)

30学会《Streamlit》是一项编码挑战&#xff0c;旨在帮助您开始构建Streamlit应用程序。特别是&#xff0c;您将能够&#xff1a; 为构建Streamlit应用程序设置编码环境 构建您的第一个Streamlit应用程序 了解用于Streamlit应用程序的所有很棒的输入/输出小部件 第6天 - 使…

【案例教程】基于RWEQ模型的土壤风蚀模数估算及其变化归因分析实践技术

土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一&#xff0c;土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的首要过程。中国风蚀荒漠化面积达160.74104km2&#xff0c;占国土总面积的16.7%&#xff0c;严重影响这些地区的资源开发和社会经…

什么是5G?关于5G你需要知道的知识

问&#xff1a;什么是5G&#xff1f; Answer&#xff1a; 5G是第五代移动网络。它是继1G、2G、3G、4G网络之后的新的全球无线标准。5G 支持一种新型网络&#xff0c;旨在将几乎所有人和所有事物连接在一起&#xff0c;包括机器、物体和设备。 5G 无线技术旨在为更多用户…

【MAVEN_Configuration】maven下载配置IDEA配置以及阿里云镜像配置

&#xff03;    下载 MAVNEDownLoad &#xff03; 配置 解压 指定文件夹 系统变量配置 在这里插入图片描述 check cmd 配置settings 本地仓库 阿里云镜像配置 mvn全局jdk <!-- java版本 --> <profile> <id>jdk-1.8</id> <activat…

JavaSE/内部类

前言 在学习Java的过程当中&#xff0c;不难发现Java全是由类与对象构成的&#xff0c;所以在一个类当中定义和使用一个另外一个类是我们学习过程中必须要学会使用的&#xff0c;而这种类&#xff0c;被称之为内部类。 一、实例内部类 class A{void a(){}class B{void a(){}} …

计算机图形学(5):OpenGL光照

参考 介绍 现实世界中的光照是极其复杂&#xff0c;难以计算的&#xff0c;因此OpenGL的光照使用的是简化的模型&#xff0c;其中一个模型被称为冯氏光照模型(Phong Lighting Model)。 冯氏光照模型的主要结构由三个分量组成&#xff1a; 环境(Ambient)光照 漫反射(Diffuse)…

【Docker学习三部曲】—— 入门篇

Docker 入门 Docker 概述 1️⃣ 什么是 docker &#xff1f; Docker 是一种运行应用程序的平台&#xff0c;它可以使应用程序在容器中不受环境差异的影响进行部署和运行。Docker 的流行度越来越高&#xff0c;是因为它可以帮助在不同的开发者和开发团队之间实现代码的共享和…

Linux centos搭建web服务器

文章目录 前言1. 本地搭建web站点2. 测试局域网访问3. 公开本地web网站3.1 安装cpolar内网穿透3.2 创建http隧道&#xff0c;指向本地80端口3.3 配置后台服务 4. 配置固定二级子域名5. 测试使用固定二级子域名访问本地web站点 前言 在web项目中,部署的web站点需要被外部访问,则…

动力节点Vue3课程笔记——四、Vue与AJAX

四、Vue与AJAX 4.1 回顾发送AJAX异步请求的方式 发送AJAX异步请求的常见方式包括&#xff1a; 原生方式&#xff0c;使用浏览器内置的JS对象XMLHttpRequest const xhr new XMLHttpRequest()xhr.onreadystatechange function(){}xhr.open()xhr.send() 原生方式&#xff0…

30天学会《Streamlit》(7)

30学会《Streamlit》是一项编码挑战&#xff0c;旨在帮助您开始构建Streamlit应用程序。特别是&#xff0c;您将能够&#xff1a; 为构建Streamlit应用程序设置编码环境 构建您的第一个Streamlit应用程序 了解用于Streamlit应用程序的所有很棒的输入/输出小部件 第7天 - st…

聚焦商会 | 活动彰显一个商会的战斗力与影响力

4月13日&#xff0c;北京厦航嘉年华酒店。 一个普通的日子&#xff0c;对北京茶业企业商会来说却是值得纪念的一天&#xff0c;可以浓笔重墨书写的一天。 一天成功举办了三场活动&#xff0c;得到社会各界的支持与肯定。 北京茶业企业商会是如何做到的&#xff1f;经过与商会领…