位置无关码PIC详解:原理、动态链接库、代码重定位

静态链接库将代码和数据在编译时整合到可执行文件,使程序独立运行。动态链接库允许在程序运行时加载,而不是在编译时将库的代码和数据静态地合并到可执行文件中。这允许多个程序共享同一份库,减小程序体积。由于动态链接库在编译时并未确定其在内存中的具体位置,而是在运行时加载,因此必须进行加载时重定位。

另外一方面,这个特性也用于实现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)有关。它们的作用主要是为了提高代码的可重定位性,使得代码更适用于共享库和在内存中的不同位置加载的情况。

  1. -fPIC(Position Independent Code)
    • 作用: 生成位置无关的代码,适用于共享库。
    • 用途: 当编译共享库时,通常需要使用-fPIC,以确保库中的代码可以在内存中的不同位置加载。
  2. -fPIE(Position Independent Executable)
    • 作用: 生成位置无关的可执行文件,适用于可执行文件。
    • 用途: 当编译可执行文件时,使用-fPIE会生成一个可以在内存中的不同位置加载的可执行文件。
  3. -pie(Position Independent Executable)
    • 作用: 生成位置无关的可执行文件,与-fPIE类似。
    • 用途: 在链接阶段,使用-pie可以生成位置无关的可执行文件,这也是为了提高安全性。与 -fPIE 不同的是,-pie在链接时指定,而不是在编译时。
  4. -fno-pic:
    • 作用: 禁用位置无关代码。
    • 用途: 当不需要位置无关代码时,使用-fno-pic禁用,生成与地址相关的代码。

编译和链接指定?

# 编译时指定 -fPIC
gcc -c -fPIC source.c -o object_file.o
# 链接时指定 -pie
gcc object_file.o -o executable -pie

2 加载动态链接库

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,在函数中取全局变量的值然后加上ab返回。然后使用-fPIC-shared将这个代码编译成动态链接库:

在这里插入图片描述

然后我们输入下面的命令来看一下这个动态链接库的汇编:

objdump -d -Mintel libreloc.so

主要来关注一下ml_func函数:

在这里插入图片描述

  1. mov rbp, rsp: 将栈底指针rbp设置为当前栈指针rsp
  2. mov DWORD PTR [rbp-0x4], edi: 将函数的第一个参数(edi寄存器)保存到栈中
  3. mov DWORD PTR [rbp-0x8], esi: 将函数的第二个参数(esi寄存器)保存到栈中
  4. mov rax, QWORD PTR [rip+0x2ed2]: 加载当前指令地址(rip寄存器)偏移0x2ed2处的值到rax寄存器中。

后面的汇编很好理解就不继续往下分析了。这里重点关注一下mov rax, QWORD PTR [rip+0x2ed2],我们可以猜测,这个可能就是GOT的地址。

接下来我们使用readelf -S libreloc.so来查看一下库的section表:

在这里插入图片描述

再回到前面的rip+0x2ED2rip存储当前正在执行的指令的地址,由于指令流水线和对齐的原因,rip的值在这里是下一条指令的地址0x110E0x110E+0x2ED2=0x3FE0。而前面我们用readelf看到.got表格的首地址为0x3FD8,那0x3FE0肯定就是.got表格的内容了,里面保存着myglob全局变量的地址。

现在我们需要思考一下,全局变量myglob是如何保存到GOT表格中的?

我们输入readelf -r libreloc.so查看库的重定位表:

在这里插入图片描述

我们可以看到myglob0x3FE0处。该重定位的类型是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

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

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

相关文章

【Electron】Electron是什么

1. Electron是什么 Electron是使用JavaScript、HTML和CSS构建跨平台(Windows、MacOs、Linux)的桌面应用。Electron其实就是一个可以展示网页内容的壳子,相当于一个独立的浏览器,可以提供给你一些接口,去调用系统的资源…

微软 Power Apps model drven app 模型驱动应用使用Plugin插件实现业务流程跳转阶段功能

微软 Power Apps model drven app 模型驱动应用使用Plugin插件实现业务流程跳转阶段功能 模型驱动应用使用插件实现跳转业务流程阶段跳转功能 在实际操作中总会遇到使用业务流程的需求,那么如何使用plugin实现跳转阶段的功能呢 需求背景是主表上有业务流程&#x…

在Python环境中运行R语言的配环境实用教程

前情提要 在做一些生物信息与医学统计的工作,本来偷懒希望只靠python完成的,结果还是需要用R语言,倒腾了一会儿,调成功了,就记录一下这个过程。 我的环境: win10, pycharm, R-4.3.2 首先,我们…

Redis 面试题 | 05.精选Redis高频面试题

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

muduo网络库剖析——线程Thread类

muduo网络库剖析——线程Thread类 前情从muduo到my_muduo 概要框架与细节成员函数使用方法 源码结尾 前情 从muduo到my_muduo 作为一个宏大的、功能健全的muduo库,考虑的肯定是众多情况是否可以高效满足;而作为学习者,我们需要抽取其中的精…

OpenCV书签 #差值哈希算法的原理与相似图片搜索实验

1. 介绍 差值哈希算法(Difference Hash Algorithm,简称dHash) 是哈希算法的一种,主要可以用来做以图搜索/相似图片的搜索工作。 2. 原理 差值哈希算法通过计算相邻像素的差异来生成哈希,即通过缩小图像的每个像素与平…

高效构建Java应用:Maven的使用总结

一、Maven简介和快速入门 1.1 Maven介绍 Maven-Introduction Maven 是一款为 Java 项目构建管理、依赖管理的工具(软件),使用 Maven 可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。 总结:Maven…

excel(wps)之vlookup函数合并sheet数据

VLOOKUP函数是Excel中的一个纵向查找函数,它与LOOKUP函数和HLOOKUP函数属于一类函数,在工作中都有广泛应用,例如可以用来核对数据,多个表格之间快速导入数据等函数功能。功能是按列查找,最终返回该列所需查询列序所对应…

DEB方式安装elastic search7以及使用

参考:https://www.cnblogs.com/anech/p/15957607.html 1、安装elastic search7 #手动下载安装 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.1-amd64.deb wget https://artifacts.elastic.co/downloads/elasticsearch/elastics…

深度学习(4)--Keras安装

目录 Keras安装: 1.1.安装CUDA/cuDDN工具包 1.1.1.安装前准备 1.1.2.安装CUDA 1.1.3.安装cuDDN 1.2.安装Anaconda 1.3.安装tensorflow框架 1.3.1.使用cmd安装 1.3.2.使用Anaconda Prompt安装 1.4.安装Keras框架 1.5.打开jupyter notebook,执行import调用 Keras…

18.鸿蒙HarmonyOS App(JAVA)日期选择器-时间选择器

18.鸿蒙HarmonyOS App(JAVA)日期选择器-时间选择器 点击button按钮触发事件显示月份与获取的时间 Button button3 (Button) findComponentById(ResourceTable.Id_button3);button3.setClickedListener(new Component.ClickedListener() {Overridepublic void onClick(Compon…

基于springboot酒店预订系统

开发工具:IDEA 服务器:Tomcat9.0, jdk1.8 项目构建:maven 数据库:mysql5.7 前端技术:AdminLTEjQueryvue.jselementuijsp 服务端技术:springbootmybatis 本系统功能包括: 一、…

实验:MySQL 客户端SocketTimeout 抓包分析

实验准备 服务端环境准备 服务器信息 阿里云 99 大洋白嫖机 $ cat /proc/version Linux version 5.15.0-83-generic (builddlcy02-amd64-027) (gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #92-Ubuntu SMP Mon Aug 14 09:30:42 UT…

特斯拉FSD的神经网络(Tesla 2022 AI Day)

这是特斯拉的全自动驾驶(Full Self Driver)技术结构图,图中把自动驾驶模型拆分出分成了几个依赖的模块: 技术底座:自动标注技术处理大量数据,仿真技术创造图片数据,大数据引擎进不断地更新&…

UML中的实现关系

在UML(统一建模语言)中,“实现”关系是指一个类(实现类)实现一个接口或抽象类的方法的情况。这种关系通常用于指定类如何实现某个特定的接口规范。 UML中的实现关系 在UML类图中,实现关系用一条带有空心箭…

win10 任务栏设置透明

先看效果图 第一步:按下“Win R”组合键,输入“regedit”并回车,打开注册表编辑器。 第二步:在注册表中找到路径“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced”。 第三步:在…

机器人3D视觉引导半导体塑封上下料

半导体塑封上下料是封装工艺中的重要环节,直接影响到产品的质量和性能。而3D视觉引导技术的引入,使得这一过程更加高效、精准。它不仅提升了生产效率,减少了人工操作的误差,还为半导体封装技术的智能化升级奠定了坚实的基础。 传统…

RK3568笔记十一:mpp编解码

若该文为原创文章,转载请注明原文出处。 主要是想测试MPP的解码,为后续做测试。 一、环境 1、平台:rk3568 2、开发板:ATK-RK3568正点原子板子 3、环境:buildroot 二、编译 使用的是正点原子提供的虚拟机,搭建好环…

yolov5 opencv dnn部署自己的模型

yolov5 opencv dnn部署自己的模型 github开源代码地址使用github源码结合自己导出的onnx模型推理自己的视频推理条件c部署c 推理结果 github开源代码地址 yolov5官网还提供的dnn、tensorrt推理链接本人使用的opencv c github代码,代码作者非本人,也是上面作者推荐的…

mysql进阶-执行计划

目录 1. 概念 2. 使用 3. 具体相关字段含义 3.1 id 3.2 select_type 3.3 table 3.4 partition 3.5 type 3.6 possible_key 3.7 key 3.8 key_len 3.9 ref 3.10 row 3.11 filtered 3.12 extra 1. 概念 一条语句通过优化器之后,会生成具体的执行计划用…