当前位置: 首页 > article >正文

从elf文件动态加载的过程解释got,plt及got.plt,plt.sec


author: hjjdebug
date: 2025年 05月 23日 星期五 17:29:34 CST
desprib: 从elf文件动态加载的过程解释got,plt及got.plt,plt.sec


文章目录

  • 1. 概念定义
  • 2. 测试源码
  • 3. 外部函数调用对应着 .plt.sec 中的一小段代码,
  • 4. .got.plt 将来存储实际的外部函数地址, 开始存储.plt中对应地址
  • 5. plt 节对应一小段代码,即以槽号为参数,调用地址解析函数.把真实外部地址存入.got.plt表

1. 概念定义

1 GOT : 全局偏移表(GOT, Global Offset Table)
由于调试时没有碰到使用它,就不多介绍了

  1. got.plt , 归属于got,数据表,保存有外部函数地址.
    但第一次函数调用时,保存的是plt中的对应地址

  2. plt.sec ,其中的sec可能是section的简写, 表示归属plt
    程序段, 对于每一个外部调用项,例add@plt, printf@plt
    提供一条跳转指令 jmp (*addr)
    其中 addr 是 got.plt地址表中一项

  3. plt : 程序链接表(PLT,Procedure Link Table)
    程序段, 代码形式
    push number
    jmp _dl_runtime_resolve
    以槽位号为参数,调用地址解析函数,修改对应got.plt项

2. 测试源码

下面给出一个简单的测试代码来分析

$cat lib.cpp
int add(int i,int j){return i+j;}$cat main.cpp
#include <stdio.h>
int add(int i,int j);
int main() {int i=add(3,5);printf("i:%d\n",i);return 0;
}

编译:
把add 编译称库函数 g++ -shared -o lib.so lib.cpp
$ g++ -no-pie -o tt main.cpp -L. lib.so
执行:
hjj@hjj-7090:~/test/tt$ export LD_LIBRARY_PATH=.
hjj@hjj-7090:~/test/tt$ ./tt
i:8

代码分析:
汇编码:

int main() {401156:	f3 0f 1e fa          	endbr64 40115a:	55                   	push   %rbp40115b:	48 89 e5             	mov    %rsp,%rbp40115e:	48 83 ec 10          	sub    $0x10,%rspint i=add(3,5);401162:	be 05 00 00 00       	mov    $0x5,%esi401167:	bf 03 00 00 00       	mov    $0x3,%edi40116c:	e8 df fe ff ff       	callq  401050 <_Z3addii@plt> //调用外部add函数401171:	89 45 fc             	mov    %eax,-0x4(%rbp) //保存到iprintf("i:%d\n",i);401174:	8b 45 fc             	mov    -0x4(%rbp),%eax401177:	89 c6                	mov    %eax,%esi      //i给第2参数401179:	48 8d 3d 84 0e 00 00 	lea    0xe84(%rip),%rdi //字符串给第1参数      401180:	b8 00 00 00 00       	mov    $0x0,%eax401185:	e8 d6 fe ff ff       	callq  401060 <printf@plt> //调用外部printfreturn 0;40118a:	b8 00 00 00 00       	mov    $0x0,%eax
}

从上边汇编码分析知:

3. 外部函数调用对应着 .plt.sec 中的一小段代码,

每段代码即而跳转进.got.plt中定义的地址. 相当于 jmp (*addr), 从固定表项取地址去执行
Disassembly of section .plt.sec:
0000000000401050 _Z3addii@plt:
401050: f3 0f 1e fa endbr64
401054: f2 ff 25 bd 2f 00 00 bnd jmpq *0x2fbd(%rip) # 404018 <_Z3addii>
40105b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

0000000000401060 printf@plt:
401060: f3 0f 1e fa endbr64
401064: f2 ff 25 b5 2f 00 00 bnd jmpq *0x2fb5(%rip) # 404020 <printf@GLIBC_2.2.5>
40106b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

0x404018 处存储着 int add(int i, int j);函数的实际入口地址.
0x404020 处存储着 printf 的实际入口地址,
但是, 第一次调用时, 0x404018 存储的还不是add 的地址, 而是0x401030 .plt中的地址

4. .got.plt 将来存储实际的外部函数地址, 开始存储.plt中对应地址

Contents of section .got.plt:
404000 103e4000 00000000 00000000 00000000 .>@…
404010 00000000 00000000 30104000 00000000 …0.@…
404020 40104000 00000000 @.@…
加载如内存后, 404008,404010地址就变成有效值了. 它们是动态解析函数地址._dl_runtime_resolve

这个表从0x404018开始,地址是会被修改的,函数实际地址会由地址解析函数放到这里.
加载时的初始化地址对应着.plt中的一小段代码地址,

5. plt 节对应一小段代码,即以槽号为参数,调用地址解析函数.把真实外部地址存入.got.plt表

0x401030 在 .plt 节中, 对应一小段代码. 形式为 push number; jump address_resolve;
Disassembly of section .plt:
0000000000401020 <.plt>:
401020: ff 35 e2 2f 00 00 pushq 0x2fe2(%rip) # 404008 <GLOBAL_OFFSET_TABLE+0x8>
401026: f2 ff 25 e3 2f 00 00 bnd jmpq *0x2fe3(%rip) # 404010 <GLOBAL_OFFSET_TABLE+0x10>
40102d: 0f 1f 00 nopl (%rax)
401030: f3 0f 1e fa endbr64 #后面的代码只会走一次,当第一次函数调用时.
401034: 68 00 00 00 00 pushq $0x0
401039: f2 e9 e1 ff ff ff bnd jmpq 401020 <.plt>
40103f: 90 nop
401040: f3 0f 1e fa endbr64
401044: 68 01 00 00 00 pushq $0x1
401049: f2 e9 d1 ff ff ff bnd jmpq 401020 <.plt>
40104f: 90 nop

add会push 进0, 即槽位0 ,对应着修改.got.plt的404018. 调用地址解析函数(404010处地址)
同样,
printf会push进1, 即槽位1 ,对应着修改.got.plt的404020. 调用地址解析函数(404010处地址)
地址解析函数会根据槽位找到符号,再根据符号名找到符号地址,这就是地址解析的过程,
找到地址后根据槽位存入地址表, 这就是后绑定.
好处你就随便说了, 只绑定一次, 用的时候才绑定, 不用一下子把所用外部函数都绑定等.

我的gcc version
$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
反汇编及地址解析,绑定实现的细节也许会与编译器有关系,不过其原理应该是一致的.
通过具体解剖这个小麻雀,我们知道了got.plt, plt,plt.sec的工作的细节,知道了动态连接的过程.

http://www.lryc.cn/news/2384676.html

相关文章:

  • 鸿蒙HarmonyOS多设备流转:分布式的智能协同技术介绍
  • XXE(外部实体注入)
  • jenkins凭据管理
  • 驱动开发硬核特训 · Day 31:理解 I2C 子系统的驱动模型与实例剖析
  • 9大开源AI智能体概况
  • 【python】局域网内通过python远程重启另一台windows电脑
  • 超越感官的实相:声、光、气味的科学与哲学探微
  • Python邮件处理:POP与SMTP
  • 什么是VR场景?VR与3D漫游到底有什么区别
  • python学习day2:进制+码制+逻辑运算符
  • 【分布式文件系统】FastDFS
  • 14、自动配置【源码分析】-初始加载自动配置类
  • word为章节标题添加自动编号
  • 无人机飞行间隔安全智能评估、安全风险评估
  • C++成员对象和封闭类
  • 【VLNs篇】03:VLMnav-端到端导航与视觉语言模型:将空间推理转化为问答
  • PCB设计实践(二十五)贴片电阻与插件电阻的全面解析:差异、演进与应用场景
  • 知道不知道
  • 文章记单词 | 第106篇(六级)
  • SpringBoot项目中Redis的使用
  • Canvas设计图片编辑器全讲解(一)Canvas基础(万字图文讲解)
  • 利用Qt绘图随机生成带多种干扰信息的数字图片
  • STM32——从点灯到传感器控制
  • java day14
  • Tailwind css实战,基于Kooboo构建AI对话框页面(一)
  • 重塑数学边界:人工智能如何引领数学研究的新纪元
  • docker部署并测试翻译模型-CSANMT连续语义增强机器翻译
  • 蓝桥杯2025.5.23每日一题-儿童数
  • Spring Boot项目配置核心 - pom.xml的依赖管理与构建优化
  • 告别手抖困扰:全方位健康护理指南