静态链接与动态链接

目录

静态链接

地址空间分配

静态链接的详细过程

静态链接库

动态链接

位置无关代码

延迟绑定机制


本篇会重点介绍静态链接,动态链接,延迟绑定机制

问:两个或者多个不同的目标文件是如何组成一个可执行文件的呢?

答:这就需要进行链接( linking )。链接由链接器( linker )完成,根据发生的时间不同,可分为编译时链接( compile time)、加载时链接( load time )和运行时链接(runtime )。

静态链接

测试代码

main.c

extern int shared;
extern void func(int * a, int * b);
int main()
{int a = 100;func(&a, &shared);return 0;
}

func.c

int shared = 1;
int tmp = 0;
void func(int * a, int * b)
{tmp = *a;*a = *b;*b = tmp;
}

地址空间分配

把两个目标文件链接成一个可执行文件

gcc -static -fno-stack-protector main.c func.c -save-temps --verbose -o func.ELF

在将main.o和func.o这两个目标文件链接成一个可执行文件时,最简单的方法是按序叠加这种方案的弊端是,如果参与链接的目标文件过多,那么输出的可执行文件会非常零散。而段的装载地址和空间以页为单位对齐,不足一页的代码节或数据节也要占用一页,这样就造成了内存空间的浪费。

另一种方案是相似节合并,将不同目标文件相同属性的节合并为一个节,如将main.o与func.o的.text节合并为新的.text 节,将main.o与 func.o中的.data节合并为新的.data节,这种方案被当前的链接器所采用,首先对各个节的长度、属性和偏移进行分析,然后将输入目标文件中符号表的符号定义与符号引用统一生成全局符号表,最后读取输入文件的各类信息对符号进行解析、重定位等操作。相似节的合并就发生在重定位时。完成后,程序中的每条指令和全局变量就都有唯一的运行时内存地址了。

静态链接的详细过程

为了构造可执行文件,链接器必须完成两个重要工作:符号解析( symbol resolution )和重定位( relocation )。

  • 符号解析是将每个符号(函数、全局变量、静态变量)的引用与其定义进行关联。
  • 重定位则是将每个符号的定义与一个内存地址进行关联,然后修改这些符号的引用,使其指向这个内存地址。

对比一下静态链接可执行文件 func.ELF和中间产物main.o的区别。使用objdump可以查看文件各个节的详细信息,这里我们重点关注.text、.data和.bss节。

objdump -h main.oobjdump -h func.ELF

5 .text         0008f480  00000000004004d0  00000000004004d0  000004d0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
20 .data         00001af0  00000000006b90e0  00000000006b90e0  000b90e0  2**5
                  CONTENTS, ALLOC, LOAD, DATA
25 .bss          000016f8  00000000006bb2e0  00000000006bb2e0  000bb2d8  2**5
                  ALLOC

  • VMA( Virtual Memory Address )是虚拟地址
  • LMA( Load Memory Address )是加载地址,一般情况下两者是相同的。

尚未进行链接的目标文件 main.o的VMA都是0。而在链接完成后的 func.ELF中,相似节被合并,且完成了虚拟地址的分配。
使用objdump查看main.o的反汇编代码,参数“-mi386:intel”表示以intel格式输出。

objdump -d -M intel --section=.text main.o

main()函数的地址从0开始。其中,对func()函数的调用在偏移0x20处,0xe8是CALL指令的操作码,后四个字节是被调用函数相对于调用指令的下一条指令的偏移量。

由于还没有重定位,编译器并不知道func函数的位置以及变量shared的位置,所以其地址用0x0代替,之后的地址替换工作是交给链接器完成
查看func.ELF的符号地址

objdump -d -M intel --section=.text func.ELF | grep -A 16 "<main>"

调用func()函数的指令CALL位于0x4009c9,其下一条指令MOV位于0x4009ce,因此相对于MOV指令偏移量为0x07的地址为0x4009ce+0x07=0x4009d5,刚好就是func()函数的地址。同时,0x4009c1处也已经改成了shared 的地址0x6ca090。

查看main.o里的可重定位表

objdump -r main.o

可重定位文件中最重要的就是要包含重定位表,用于告诉链接器如何修改节的内容。每一个重定位表对应一个需要被重定位的节。

例如名为.rel.text的节用于保存.text节的重定位表。.rel.text包含两个重定位入口:

  • shared 的类型R_X86_64_32用于绝对寻址,CPU 将直接使用在指令中编码的32位值作为有效地址。
  • func的类型R_X86_64_PC32用于相对寻址,CPU将指令中编码的32位值加上PC (下一条指令地址)的值得到有效地址。需要注意的是,func-0x0000000000000004 中的-0x4是r_addend域的值,是对偏移的调整
     


静态链接库

后缀名为.a的文件是静态链接库文件,如常见的libc.a。一个静态链接库可以视为一组目标文件
经过压缩打包后形成的集合。执行各种编译任务时,需要许多个同的目标文件,比如输入输出有printf.o、scanf.o,内存管理有malloc.o等。为了方便管理,人们使用ar 工具将这些目标文件进行了压缩、编号和索引,就形成了libc.a。

动态链接

静态链接产生问题:

  • 内存空间的浪费,大部分可执行文件都需要glibc,在静态链接时需要把libc.a和编写的代码链接进去。多个可执行程序在内存中都包含这一部分,相同的库被多次链接,内存空间浪费。
  • 标准函数只要有改动,就需要重新编译整个源文件

动态链接:系统库和自己写的代码先不链接在一起,都是独立的模块,等到程序执行时在内存中完成链接。而且内存一个系统库可以被多个程序一起使用。这些被共享的库被称作共享库,或者共享对象,这个过程由动态链接器完成。

  • 当运行func1.ELF时,系统将func1.o和依赖的testLib.o装载入内存,然后进行动态链接。完成后系统将控制权交给程序人口点,程序开始执行。
  • 当运行func2.ELF想要执行时,由于内存中已经有testLib.o,因此不再重复加载,直接进行链接即可。

GCC默认使用动态链接编译,通过下面的命令我们将func.c编译为共享库,然后使用这个库编译main.c。

gcc -shared -fpic -o func.so func.cgcc -fno-stack-protector -o func.ELF2 main.c ./func.so

参数-shared表示生成共享库, -fpic 表示生成与位置无关的代码。这样可执行文件 func.ELF2就会在加载时与func.so进行动态链接。另外动态加载器ld-linux.so本身就是一个共享库,因此加载器会加载并运行动态加载器,并由动态加载器来完成其他共享库以及符号的重定位。
 

位置无关代码

可以加载而不需要重定位的代码被称为位置无关代码(PIC),这个共享库的基本属性,通过给gcc传递 -fpic 参数可以生成 PIC 。这样一个共享库就可以被所有进程使用。

一个程序(或共享库)的数据段和代码段的相对距离不变,与绝对内存地址无关。于是就由了全局偏移量表(GOT),位于数据段的开头,用于保存全局变量和库函数的引用,每个条目占8个字节,加载时会进行重定位并填入符号的绝对地址。

因为引入了RELRO保护机制,GOT被拆分为 .got 和 .got.plt节两个部分:

  1. 前者不需要延迟绑定机制用于保存全局变量引用,加载内存后标记为只读;
  2. 后者需要延迟绑定用于保存函数引用,具有读写权限。

看一下 func.so 

objdump -h func.so

readelf -r func.so | grep tmp

objdump -d -M intel --section=.text func.so | grep -A 20 "<func>"

 全局变量 tmp 位于GOT上,R_X86_64_GLOB_DAT 表示需要动态链接器找到 tmp 的值并填充到0x200fd8。在func()函数需要取出 tmp时,计算符号相对PC的偏移 rip+0x2009e5 ,也就是0x6c9+0x2009e5=0x200fd8。

延迟绑定机制

由于动态链接是由动态链接器在程序加载时进行的,如果有很多个程序需要加载,会影响到动态链接器的性能。延迟绑定,其思想是当函数第一次被调用时,动态链接器才进行符号查找,重定位等操作,没被调用就不进行绑定。

ELF文件通过过程链接表(Procedure Linkage Table,PLT )和GOT的配合来实现延迟绑定,每个被调用的库函数都有一组对应的PLT和GOT。

位于代码段.plt节的PLT是一个数组,每个条目占16个字节。其中 PLT[0]用于跳转到动态链接器,PLT[1]用于调用系统启动函数_libc_start_main(),我们熟悉的main()函数就是在这里面调用的,从PLT[2]开始就是被调用的各个函数条目。

位于数据段.got.plt节的GOT也是一个数组,每个条目占8个字节。其中 GOT[0]和 GOT[1]包含动态链接器在解析函数地址时所需要的两个地址(.dynamic和 relor条目),GOT[2]是动态链接器ld-linux.so 的人口点,从GOT[3]开始就是被调用的各个函数条目,这些条目默认指向对应PLT条目的第二条指令,完成绑定后才会被修改为函数的实际地址。

当func.ELF2调用库函数 func()为例

  1. 当main调用func时,会进入0x4005b0这个地址,也就是PLT[2];
  2. jmp指令跳会找到0x601020这个地址,也就是GOT[4],取地址中的值,跳转到0x4005b6,也就是PLT[2],把0x1压入栈(func在.rel.plt中的下标)压栈;
  3. 之后jmp跳转到0x400590,也就是PLT[0],把GOT[1]压入栈;
  4. 调用GOT[2],也就是动态链接器的_dl_runtime_resolve()函数,完成符号解析和重定位工作,并把func的真实地址填入func@got.plt,也就是GOT[4],最后程序控制权给func(),延迟绑定完成。
  5. 之后如果需要调用func(),直接跳转到func@got.plt,控制去哪交给func()

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

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

相关文章

数据结构 - 线段树的运用

数据结构 - 线段树的运用 前言一. 线段树的运用1.1 区间和 - 线段树节点的成员变量1.2 线段树的构建1.3 线段树的区间和查询1.4 线段树的区间和更新1.5 完整代码 二. 线段树的动态扩建2.1 向下递推2.2 向上递推2.3 更新操作2.4 查询操作2.5 完整代码 三. 线段树的使用案例3.1 定…

Unity之NetCode多人网络游戏联机对战教程(3)--NetworkObject组件讲解

文章目录 NetworkObjectAlways Replicate As RootSynchronization TransformActive Scene SynchronizationScene Migration SynchronizationSpawn With ObserversDont Destroy With OwnerAuto Object Parent Sync 后话 NetworkObject 为了复制任何Netcode感知属性或发送/接收R…

Python大数据之pandas快速入门(一)

文章目录 pandas快速入门学习目标1. DataFrame 和 Series 简介2. 加载数据集(csv和tsv)2.1 csv和tsv文件格式简介2.2 加载数据集(tsv和csv) pandas快速入门 学习目标 能够知道 DataFrame 和 Series 数据结构能够加载 csv 和 tsv 数据集能够区分 DataFrame 的行列标签和行列位…

FPGA project : uart232_ram_vga

重点学习&#xff1a; 本实验重点学习了双口ram解决多bit跨时钟域同步处理的问题。 其实signal port ram&#xff0c;它的输入口和输出口分别用不同的时钟&#xff0c;也可以解决这个问题。 让我意识到的比较重要的事情&#xff1a; 1&#xff0c;代码设计中&#xff0c;一…

经典题记录 字符串相加/相乘

1. LeetCode 415 字符串相加 代码一&#xff1a;代码简短&#xff0c;但需要借助额外的一个string来保存结果&#xff0c;更占用内存。 class Solution { public:string addStrings(string num1, string num2) {string ans"";int size1num1.size();int size2num2.si…

Qt_C++读写NFC标签Ntag支持windows国产linux操作系统

本示例使用的发卡器&#xff1a;Android Linux RFID读写器NFC发卡器WEB可编程NDEF文本/智能海报/-淘宝网 (taobao.com) ntag2标签存储结构说明 #include "mainwindow.h" #include "./ui_mainwindow.h" #include <QDebug> #include "QLibrary&…

Django REST Farmowork初探

1.简介 Django REST framework &#xff08;简称&#xff1a;DRF&#xff09;是一个强大而灵活的 Web API 工具。 遵循RESTFullAPI风格&#xff0c;功能完善&#xff0c;可快速开发API平台。 官网文档&#xff1a;https://www.django-rest-framework.org 2. framwork的安装 …

界面组件DevExpress WPF v23.2新功能预览 - 更轻量级的主题

本文主要描述了DevExpress WPF即将在几个月之后发布的v23.2中包含的新功能&#xff0c;持续关注我们获取更多最新资讯哦~ P.S&#xff1a;DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强…

2023蓝帽杯半决赛取证复现

1.检材数据开始提取是今年什么时候&#xff1f;&#xff08;答案格式&#xff1a;04-12 13:26&#xff09; 09-11 17:21 这题做错了 其实当时盘古石手机取证里面就有的&#xff0c;想多了去看了日志文件 是真的有点歧义&#xff0c;20分就开始提取任务了 2.嫌疑人手机SD卡存…

精通git,没用过git cherry-pick?

前言 git cherry-pick是git中非常有用的一个命令&#xff0c;cherry是樱桃的意思&#xff0c;cherry-pick就是挑樱桃&#xff0c;从一堆樱桃中挑选自己喜欢的樱桃&#xff0c;在git中就是多次commit中挑选一个或者几个commit出来&#xff0c;也可以理解为把特定的commit复制到…

【实验记录】AGW | Visible-Infrared Re-ID

【RT】Visible Thermal Re-IDDeep Learning for Person Re-identification: A Survey and Outlook中提出了一个针对单/跨模态行人重识别的baseline&#xff1a;AGW 做过两次&#xff0c;在测试阶段有问题&#xff0c;现在再重做一次&#x1f914;Code RTX3090 修改数据集路…

手机相机系统介绍

目录 一张照片是如何生成的? 相机的成像原理 相机硬件 颜色四要素 相机硬件三大块 模组结构 镜头 镜头光路 镜头常见参数 镜头-FOV&EFL 镜头-焦距 镜头-光圈 图像传感器 图像传感器-像素-底 RGB排布 图像传感器-Pattern & PDAF Sensor CMOS sensor …

计算机类软件方向适合参加的比赛

前言 博主是一名计算机专业的大三学生&#xff0c;在校时候参加了很多比赛和训练营&#xff0c;现在给大家博主参加过的几个的比赛&#xff0c;希望能给大一大二的学生提供一点建议。 正文 最近也有比赛的&#xff0c;我会从时间线上来给大家推荐一些比赛&#xff0c;并且给…

雷柏mv20鼠标使用体验

用了1年多&#xff0c;第一次用竖着的鼠标&#xff0c;现在已经很习惯了&#xff0c;感觉还不错。说说使用感受&#xff1a; 1、 仍然是长时间使用鼠标&#xff0c;但是很少出现手腕痛的情况&#xff0c;确实是有一定效果的。 2、使用场景是有限制的&#xff0c;我是配合笔记…

解决kali beef启动失败问题及实战

文章目录 一、解决方法二、靶场实战应用1.首先打开dvwa这个靶场&#xff0c;设置难度为low2.打开xss-stored3.准备payload4.提交payload5.利用 一、解决方法 首先需卸载 ruby apt remove ruby 卸载 beef apt remove beef-xss 重新安装ruby apt-get install ruby apt-get insta…

安卓修改ROM 修改固件中的一些基本常识 自己做rom注意事项

修改rom 制作rom 解包rom的一些问题解析 安卓系列机型如何内置app 如何选择so文件内置 修改设置里 添加选项 添加文字 修改图标 修改版本号等等 实例解析 最近有几个粉丝对修改rom有兴趣。今天主要给这些友友提供一些自己初学修改rom的一些建议和思路&#xff0c;可以供大家…

uni-app:多方法实现两个view在同一行展示

效果 方法一&#xff1a;flex 布局 使用 display: flex 后&#xff0c;默认的 flex-direction 值就是 row&#xff0c;即水平排列。 <template><view class"container"><view class"left-view">123</view><view class"r…

SpringBoot的excel模板导出

Word的模板导出(参考&#xff1a;https://easyexcel.opensource.alibaba.com/docs/current/quickstart/fill) 创建有两个sheet的excel文件模板 将模板文件放入resource\templates/doc下使用 public void exportUavInfoExcel(HttpServletResponse response, CaseExportRPO cas…

GB28181协议-SDP详解

SDP协议 SDP全称是Session Description Protocol&#xff0c;翻译过来就是描述会话的协议。主要用于两个会话实体之间的媒体协商。 SDP描述由许多文本行组成&#xff0c;文本行的格式为<类型><值>&#xff0c;表示为keyvalue; SIP负责建立和释放会话&#xff0c…

架构问题:技术选型

1. 几款数据库特性及如何选型 1.MySQL&#xff1a;一种常用的开源关系型数据库管理系统&#xff0c;可以快速访问大量数据&#xff0c;并支持多用户同时访问。其最大的优点在于成本低&#xff0c;易于安装和配置&#xff0c;因此被广泛应用于各种中小型企业和网站。支持读写分离…