flash怎么做网站,域名 删除 wordpress,中国制造网外贸网官网登录入口,wordpress 显示指定分类文章静态链接库将代码和数据在编译时整合到可执行文件#xff0c;使程序独立运行。动态链接库允许在程序运行时加载#xff0c;而不是在编译时将库的代码和数据静态地合并到可执行文件中。这允许多个程序共享同一份库#xff0c;减小程序体积。由于动态链接库在编译时并未确定其…静态链接库将代码和数据在编译时整合到可执行文件使程序独立运行。动态链接库允许在程序运行时加载而不是在编译时将库的代码和数据静态地合并到可执行文件中。这允许多个程序共享同一份库减小程序体积。由于动态链接库在编译时并未确定其在内存中的具体位置而是在运行时加载因此必须进行加载时重定位。
另外一方面这个特性也用于实现U-Boot的代码重定位的功能这些都与位置无关码有关所以本篇文章就来详细地介绍一下位置无关代码的实现。 文章目录 1 位置无关的编译器选项2 加载动态链接库2.1 问题引入2.2 加载时重定位(Load-time relocation) 3 位置无关代码(Position Independent Code)3.1 代码段和数据段之间的偏移3.2 全局偏移表(Global Offset Table)3.3 实例通过GOT进行数据引用(位置无关)3.3.1 反汇编及elf头分析3.3.2 GDB分析 3.4 函数的重定位 4 总结 1 位置无关的编译器选项
这四个编译选项与位置无关代码(Position Independent CodePIC)和位置无关可执行文件(Position Independent ExecutablePIE)有关。它们的作用主要是为了提高代码的可重定位性使得代码更适用于共享库和在内存中的不同位置加载的情况。
-fPIC(Position Independent Code) 作用 生成位置无关的代码适用于共享库。用途 当编译共享库时通常需要使用-fPIC以确保库中的代码可以在内存中的不同位置加载。 -fPIE(Position Independent Executable) 作用 生成位置无关的可执行文件适用于可执行文件。用途 当编译可执行文件时使用-fPIE会生成一个可以在内存中的不同位置加载的可执行文件。 -pie(Position Independent Executable) 作用 生成位置无关的可执行文件与-fPIE类似。用途 在链接阶段使用-pie可以生成位置无关的可执行文件这也是为了提高安全性。与 -fPIE 不同的是-pie在链接时指定而不是在编译时。 -fno-pic 作用 禁用位置无关代码。用途 当不需要位置无关代码时使用-fno-pic禁用生成与地址相关的代码。
编译和链接指定
# 编译时指定 -fPIC
gcc -c -fPIC source.c -o object_file.o
# 链接时指定 -pie
gcc object_file.o -o executable -pie2 加载动态链接库
2.1 问题引入
与可执行文件不同构建动态链接库时链接器无法假定已知的代码加载地址。因为每个程序可以使用多个共享库事先无法知道任何给定共享库将在进程的虚拟内存中的哪个位置加载。对于不同操作系统来说有不同的解决方法。本文就介绍一下Linux是如何解决这个问题的。
现在我们看一个简单的代码
int test 10;int func(int a)
{test a;return test;
}将上面的代码编译成动态链接库后就涉及到使用mov指令将全局变量test的值从内存位置取到寄存器中。但mov指令需要绝对地址但由于动态链接库没有预定义的加载地址所以这个地址将在运行时确定。
在Linux中动态链接器是一段代码位于/lib/ld-linux.so.2(2为版本)。当可执行文件运行时它负责将共享库从磁盘加载到内存中。动态链接器就是用来解决前面的绝对地址的问题。
在Linux ELF共享库中主要有两种解决这个问题的方法加载时重定位和位置无关代码。
2.2 加载时重定位(Load-time relocation)
实时性 在加载时进行地址重定位即在将共享库加载到进程的地址空间时需要根据实际的加载地址对库中的数据和代码引用进行修正。这就是为什么称之为“加载时”重定位。非位置无关 共享库中的代码和数据引用是使用绝对地址的因此必须在加载时将这些地址调整为实际的加载地址。
存在的问题
(1)性能问题
当一个应用程序加载与加载时重定位条目关联的共享库时尽管只需加载重定位的条目但如果一个复杂的软件在启动时加载多个大型共享库并且每个库都需要进行加载时重定位会导致应用程序启动时间明显延迟。
(2)代码段无法共享
共享库的初衷之一是为了节省RAM使得一些常见的共享库能够被多个应用程序共享。这意味着对于每个应用程序共享库都必须完全加载到内存中导致相当大量的RAM浪费。
(3)要求代码段可写
为了允许在加载时动态地修改其中的绝对地址将其调整为实际的加载地址加载时重定位要求代码段保持可写状态这带来了潜在的代码安全风险。
对于加载时重定位这种方法实际上已经过时了甚至最新的编译器已经不支持这种方法。PIC是目前常见的解决方案接下来我们就深入讨论一下位置无关代码。
3 位置无关代码(Position Independent Code)
PIC的原理很简单在代码中对所有全局数据和函数引用添加一个额外的中间层。通过巧妙地利用链接和加载过程中的结果使共享库的代码部分实现位置无关。
3.1 代码段和数据段之间的偏移
PIC的一个关键点是利用链接时已知的代码段和数据段之间的偏移。当链接器合并多个目标文件时它会整合它们的各个部分形成一个大的代码段。因此链接器了解各个部分的大小和它们的相对位置。
举例来说代码段可能直接跟在数据部分后面这意味着从代码部分中的任意指令到数据段开头的偏移量等于代码部分的大小减去指令距离代码部分开头的偏移量。这两个量都是链接器已知的。 如上图所示代码段被加载到某个地址(在链接时未知)假设是0xXXXX0000紧随其后的是偏移为0xXXXXF000的数据段。如果代码段中偏移为0x80的某个指令需要引用数据部分的内容链接器知道相对偏移量(在上图中为0xEF80所以可以将其编码到指令中。
注意如果在代码段和数据段之间放置了别的段或者如果数据段在代码段之前都不会影响结果。因为链接器分配了它们放置的位置并且知道所有段的大小。
3.2 全局偏移表(Global Offset Table)
全局偏移表GOT可以帮我们实现位置无关数据寻址。实际上GOT就是一个地址表存储在数据段中。假设代码段中的某个指令想要引用一个变量。它会引用GOT中的一个条目而不是直接使用绝对地址引用(这将需要进行重定位)。由于GOT位于数据段的一个已知位置这个引用是相对的并且在链接器中是已知的而GOT条目本身将包含变量的绝对地址 通过将变量引用重定向到GOT我们避免了在代码段中直接使用绝对地址而是通过GOT中的条目进行引用从而减少了需要在加载时进行的具体地址修正。但是我们在数据段中引入了一个新的重定位因为全局偏移表仍然需要包含变量的绝对地址。那么这样做的优点有哪些呢
加载时重定位需要对每个变量的引用都进行重定位而在全局偏移表中只需要对每个变量进行一次重定位数据段是可写的并且在进程之间不共享
实际上这就是解决前面提到的加载时重定位的三个缺点。
3.3 实例通过GOT进行数据引用(位置无关)
3.3.1 反汇编及elf头分析
我们编写一个简单的函数其中有一个全局变量myglob在函数中取全局变量的值然后加上a和b返回。然后使用-fPIC和-shared将这个代码编译成动态链接库 然后我们输入下面的命令来看一下这个动态链接库的汇编
objdump -d -Mintel libreloc.so主要来关注一下ml_func函数 mov rbp, rsp: 将栈底指针rbp设置为当前栈指针rspmov DWORD PTR [rbp-0x4], edi: 将函数的第一个参数(edi寄存器)保存到栈中mov DWORD PTR [rbp-0x8], esi: 将函数的第二个参数(esi寄存器)保存到栈中mov rax, QWORD PTR [rip0x2ed2]: 加载当前指令地址(rip寄存器)偏移0x2ed2处的值到rax寄存器中。
后面的汇编很好理解就不继续往下分析了。这里重点关注一下mov rax, QWORD PTR [rip0x2ed2]我们可以猜测这个可能就是GOT的地址。
接下来我们使用readelf -S libreloc.so来查看一下库的section表 再回到前面的rip0x2ED2rip存储当前正在执行的指令的地址由于指令流水线和对齐的原因rip的值在这里是下一条指令的地址0x110E0x110E0x2ED20x3FE0。而前面我们用readelf看到.got表格的首地址为0x3FD8那0x3FE0肯定就是.got表格的内容了里面保存着myglob全局变量的地址。
现在我们需要思考一下全局变量myglob是如何保存到GOT表格中的
我们输入readelf -r libreloc.so查看库的重定位表 我们可以看到myglob在0x3FE0处。该重定位的类型是R_X86_64_GLOB_DAT它告诉动态加载器要将符号的实际地址放入该偏移量。
3.3.2 GDB分析
我们先来写一个driver.c在这里面来调用库中的ml_func()函数 然后我们链接libreloc.so库并编译一下
gcc -o driver driver.c -L. -lreloc在执行之前我们需要把库搜索路径添加到系统配置中否则运行./driver的时候会提示找不到库。如下图所示
echo /home/vino/Desktop/test | sudo tee -a /etc/ld.so.conf
sudo ldconfig运行结果如下图所示 现在我们就用GDB来调试一下程序
gdb driver #调试程序
break ml_func #查看断点
run #运行程序
set disassembly-flavor intel #设置汇编显示语法
disas ml_func #查看ml_func的反汇编如下图所示 rip 0x7FFFF7FB210E加上0x2ED2就等于0x7FFFF7FB4FE0和后面的注释一样。根据前面的分析我们知道这个地址里保存的应该是全局变量myglob的地址。
x/gx 0x7FFFF7FB4FE0 #查看从该内存开始64字节的值
p myglob #查看myglob的地址结果如下图所示 可以看到两个地址是匹配的。
3.4 函数的重定位
前面介绍的是全局变量的重定位对于函数也需要重定位它有着另一种机制懒绑定。
当共享库引用某个函数时函数的真实地址在加载时未知。为了加速这个过程引入了过程链接表(PLT)。PLT包含对函数进行间接调用的代码而不是直接包含函数地址。在程序执行时当函数首次调用时PLT代码负责将函数的真实地址填充到全局偏移表(GOT)中的相应条目。此后的调用直接通过GOT访问函数地址避免了每个函数调用时的绑定延迟。这种机制减少了不必要的解析工作提高了程序执行效率。
本篇文章就不分析函数的重定位了实际上原理类似。
4 总结
本文解释了什么是位置无关代码以及它如何帮助创建具有可共享只读文本段的共享库。位置无关代码(PIC)通过引入全局偏移表(GOT)和过程链接表(PLT)实现解决了共享库加载时的重定位问题。GOT提供了数据和函数的间接引用PLT实现了懒绑定推迟函数地址的解析。当然这也伴随额外的内存加载和寄存器使用成本但在权衡之下现代的编译器都更倾向于使用PIC。