《高级C/C++编译技术》01

news/2024/4/29 10:09:24/文章来源:https://www.cnblogs.com/zhh567/p/16674022.html

Linux程序内存布局:

启动程序的默认加载点是在链接阶段才添加的,通常放在程序内存映射的起始处(这是可执行文件和动态库之间的唯一区别)。启动代码有两种不同方式:

  1. crt0:“纯粹”的入口点,这是程序代码的第一部分,在内核控制下执行
  2. crt1:更现代化的启动例程,可以在main函数执行前和程序终止后完成一些任务

 

 

链接阶段

  1. 重定位
    将单独目标中不同类型的节拼接到程序内存映射中。将节中从0开始的地址转换成最终程序内存映射中更具体地地址范围。


    目标文件中每个节的起始地址都是0
    objdump -D tmp.o
    root@DESKTOP-5F95GBN:~/code# objdump -D tmp.otmp.o:     file format elf64-x86-64Disassembly of section .text:0000000000000000 <main>:0:   f3 0f 1e fa             endbr644:   55                      push   %rbp5:   48 89 e5                mov    %rsp,%rbp8:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # f <main+0xf>f:   b8 00 00 00 00          mov    $0x0,%eax14:   e8 00 00 00 00          callq  19 <main+0x19>19:   b8 00 00 00 00          mov    $0x0,%eax1e:   5d                      pop    %rbp1f:   c3                      retqDisassembly of section .rodata:0000000000000000 <.rodata>:0:   68 65 6c 6c 6f          pushq  $0x6f6c6c655:   20 77 6f                and    %dh,0x6f(%rdi)8:   72 6c                   jb     76 <main+0x76>a:   64                      fs...Disassembly of section .comment:0000000000000000 <.comment>:0:   00 47 43                add    %al,0x43(%rdi)3:   43 3a 20                rex.XB cmp (%r8),%spl6:   28 55 62                sub    %dl,0x62(%rbp)9:   75 6e                   jne    79 <main+0x79>b:   74 75                   je     82 <main+0x82>d:   20 39                   and    %bh,(%rcx)f:   2e 34 2e                cs xor $0x2e,%al12:   30 2d 31 75 62 75       xor    %ch,0x75627531(%rip)        # 75627549 <main+0x75627549>18:   6e                      outsb  %ds:(%rsi),(%dx)19:   74 75                   je     90 <main+0x90>1b:   31 7e 32                xor    %edi,0x32(%rsi)1e:   30 2e                   xor    %ch,(%rsi)20:   30 34 2e                xor    %dh,(%rsi,%rbp,1)23:   31 29                   xor    %ebp,(%rcx)25:   20 39                   and    %bh,(%rcx)27:   2e 34 2e                cs xor $0x2e,%al2a:   30 00                   xor    %al,(%rax)Disassembly of section .note.gnu.property:0000000000000000 <.note.gnu.property>:0:   04 00                   add    $0x0,%al2:   00 00                   add    %al,(%rax)4:   10 00                   adc    %al,(%rax)6:   00 00                   add    %al,(%rax)8:   05 00 00 00 47          add    $0x47000000,%eaxd:   4e 55                   rex.WRX push %rbpf:   00 02                   add    %al,(%rdx)11:   00 00                   add    %al,(%rax)13:   c0 04 00 00             rolb   $0x0,(%rax,%rax,1)17:   00 03                   add    %al,(%rbx)19:   00 00                   add    %al,(%rax)1b:   00 00                   add    %al,(%rax)1d:   00 00                   add    %al,(%rax)...Disassembly of section .eh_frame:0000000000000000 <.eh_frame>:0:   14 00                   adc    $0x0,%al2:   00 00                   add    %al,(%rax)4:   00 00                   add    %al,(%rax)6:   00 00                   add    %al,(%rax)8:   01 7a 52                add    %edi,0x52(%rdx)b:   00 01                   add    %al,(%rcx)d:   78 10                   js     1f <.eh_frame+0x1f>f:   01 1b                   add    %ebx,(%rbx)11:   0c 07                   or     $0x7,%al13:   08 90 01 00 00 1c       or     %dl,0x1c000001(%rax)19:   00 00                   add    %al,(%rax)1b:   00 1c 00                add    %bl,(%rax,%rax,1)1e:   00 00                   add    %al,(%rax)20:   00 00                   add    %al,(%rax)22:   00 00                   add    %al,(%rax)24:   20 00                   and    %al,(%rax)26:   00 00                   add    %al,(%rax)28:   00 45 0e                add    %al,0xe(%rbp)2b:   10 86 02 43 0d 06       adc    %al,0x60d4302(%rsi)31:   57                      push   %rdi32:   0c 07                   or     $0x7,%al34:   08 00                   or     %al,(%rax)...
  2. 解析引用
    将地址范围线性地转换成程序映射地址范围,并为不同部分间代码建立关联,使其成为一个整体。即不同目标文件之间相互引用了程序内存(函数入口点)或数据内存(全局数据、静态数据、外部数据)中地地址。
    当引用同一目标文件中的地址时,函数或数据被拓展成相对于代码节的起始地址的相对偏移。
    当引用其他文件的地址时,在生成完整的映射之前会被一直当作未解析引用。

    解析符号的工作:
    1. 检查拼接到程序内存映射中的节
    2. 找到哪些代码产生了外部调用
    3. 计算该引用在内存映射中的地址
    4. 将机器指令中的伪地址换为程序内存映射的实际地址

内核加载流程

  1. shell 委托任务后,内核通过调用exec函数族做出相应,它们最终调用 sys_execve 函数
  2. 函数 search_binary_handler(文件 fs/exec.c 中) 识别可执行文件
  3. 如果是ELF格式,调用 load_elf_binary 函数(fs/binfmt_elf.c 中)
  4. 定位可执行二进制文件中的 PT_INTERP 段,用于动态加载阶段

加载器

链接器是一个高度复杂的模块,它要准确的区分出各种节的属性(代码、未初始化数据、初始化数据、构造器、调试信息等)。
装载器则要简单。它将链接器创建的节复制到内存映射中,装载器不需要了解各个节的内部结构就能完全复制。它只要关心节读写属性,以及在启动前是否需要打补丁。(涉及到动态链接时还要比复制数据块更复杂一些)装载器还会根据节的相同装载需求将链接器创建的节组合成段。装载器的段一般会携带多个拥有相同访问属性的节。

可使用 readelf 检查段

暂时只讨论静态链接场景,使用 gcc 的 -static 生成静态链接代码。

  1. 加载器读取程序二进制文件段的头,确定每个段的地址和字节长度。
    此阶段装载器仍不会向程序的内存映射写入任何数据。装载器此阶段只建立并维护一组包含可执行文件段(就是每个段的页宽)和程序内存映射关联的结构(比如 vm_are_struct)
  2. 分配给进程的物理内存页和程序内存映射表之间的虚拟内存映射关系已经建立好后,当内核在运行时需要某个程序段时才动态加载对应的页。

程序执行入口点

装载完成后(即准备程序基本数据和复制程序必要的节到内存中),装载器会查询ELF头的 e_entry 字段的值。这个值包含的程序内存地址指定了该程序从何处开始。e_entry 值包含了 .text 节的首地址,通常就是  _start 函数的首地址。

_start() 函数为接下来需要调用的 __libc_start_main 函数准备入参

__libc_start_main 为程序启动准备环境的过程中扮演了重要角色。启动阶段,它不仅会为程序设置好运行环境,会执行以下操作:

  1. 启动程序的线程
  2. 调用 _init() 函数在main函数前完成必要的初始化操作。(gcc 利用 __attribute__((constructor)) 关键字对程序启动前的自定义操作提供支持)
  3. 注册 __fini() 和 rtld_fini() 函数,这些函数会在程序调用终止时调用。通常 _fini() 和 _init() 操作顺序相反
  4. gcc 利用 __attribute__((destructor)) 关键字对程序结束时的自定义操作提供支持

最后,所有准备操作完成时,__libc_start_main() 调用 main() 函数,启动程序。

函数调用过程:
ELF.e_entry字段 -> _start() -> int __linc_start_main() -> main()

 重用概念

静态库:

动态库:

动态库的两种实现方式:

  1. 装载时重定位 (load time relocation, LTR)
    这种方式自身二进制文件无需包含多余的代码,缺点是:如果多个程序在运行时需要同一系统功能,那么每个程序都要加载一份副本。
    原因是在装载时重定位技术为了实现向应用程序特定地址映射的功能,修改了动态库中 .text 节的符号。对于其他应用程序,载入动态库的地址范围可能不同,因此之前修改过的动态库代码不能适用于另一个应用程序内的内存布局。
  2. 运行时重定位
    仅需加载一次,其他需要这个库的程序都可以使用。利用位置无关代码(position independent code, PIC)实现通过修改动态代码库访问符号的方式,只要一份加载到某个进程中内存映射的副本,就能映射到任何应用程序进程中,通过内存映射实现共享。
    虚拟内存的概念为运行时共享的实现奠定了基础。如果一个实际进程的内存映射只是从0开始的进程内存映射,那么我们必须能创建出多个不同进程组成的实际进程内存映射。这就是动态库的运行时共享机制。

动态链接详解

  1. 构建动态库
    构建动态库生成的二进制文件本质上和可执行文件是相同的,唯一区别是动态库缺少了让其独立执行的启动例程。
    Windows中构建动态库时必须对所有的引用进行解析。如果正在构建的动态库调用了其他动态库中的函数,那么在构建阶段就必须找到其他依赖的库的引用符号。
    Linux中默认选项能让编译更加灵活:有些符号可不在编译阶段进行解析,而解析引用的过程可以在完成链接其他的动态库后生成最终二进制文件时再执行。也可用选项使其像windows一样严格。
    在Linux中,可以修改动态库使其可以独立运行。
  2. 构建可执行文件(只查找符号)
    与使用静态库不同,链接器会将之前已完成链接的动态库二进制文件与正在编译的项目合并。这一步链接器会把所有注意力放在动态库的符号上,这个阶段中链接器不关心任何节的细节,无论代码节(.text)还是数据节(.data 和 .bss)。链接器在这个阶段认为“所有符号都能被正确解析”。链接器只会检查二进制文件中所需的符号是否都能在动态库中找到,一旦找到所有符号,链接器就会完成任务并创建可执行程序。

    这种认为“所有符号都能被正确解析”的做法即只构建接口,细节在运行时加载。
  3. 运行时装载和符号解析
    前一个阶段已经对动态库中可执行文件所需的符号做了检验,现在运行时会检验动态库能否正常工作:
    1. 程序先找到动态库位置
    2. 进程将动态库载入内存映射中,这时必须确保构建阶段时链接中的符号在运行时能被正常引用。运行时动态库中的函数符号必须与构建阶段中的完整函数签名相同。
    3. 运行时要将程序的符号解析到正确地址上,这个地址是动态库所映射到的进程内存映射中的地址。与一般装载时链接不同,该阶段会把动态库装载到进程的内存映射中——这就是动态链接。

动态库的特点

1. 创建动态库需要完整的构建过程

静态库只需要编译,动态库则需要编译和链接。这使得动态库更加类似可执行文件,唯一区别是可执行文件包含启动代码。

2. 动态库可以链接其他库

可执行文件和动态库都可以加载和链接动态库。

应用程序二进制接口(ABI)

由软件模块提供给客户端的接口通常是应用程序编程接口(API)。当提供给客户端的是二进制文件时,接口的定义就会发生变化,成为应用程序二进制接口。可以把ABI当作编译链接过程中根据源代码接口创建的符号集合(主要是函数入口点)。

  • 在构建阶段,客户端二进制文件根据库的ABI接口进行链接。在此阶段只会检查动态库的外部符号(比如函数指针这样的ABI),并不关心任何节(函数体)。
  • 为了能完成动态链接的运行,运行时使用的动态库二进制数据必须导出与构建时一致的ABI(ABI不能改变)。

静态库与动态库对比

静态库

Linux 静态库 https://www.cnblogs.com/zhh567/p/16664821.html

Windows 静态库 https://www.cnblogs.com/zhh567/p/16536626.html

注意

  1. 丢失符号可见性和唯一性的可能性
    当静态库链接到客户端二进制文件时,静态库中符号成为客户端二进制文件符号列表中的一部分,并保留了原有的可见性,全局符号依然为全局符号,局部符号依然为局部符号。
    当静态库链接到动态库时,这个规则被打破。
    动态库的隐含假设是模块化,其能完全自主的管理其局部符号,为此内部对于库用户是透明的。静态库提供外部接口和内部细节,动态库只提供接口。这种设计原则会影响到静态库符号可见性。许多动态库被加载到相同的进程中,一个动态库会包含与其他动态库具有相同名称的局部符号,而链接器能避免命名的冲突。静态库的符号不会作为全局可见的符号保留,而是变为私有符号或被忽略。
  2. 静态库使用禁忌
    1. 不该使用一个链接了多个动态库的静态库(除libc),改用动态库更有利
    2. 如果实现的功能需要存在一个类的单例,应该封装为动态库而非静态库。原因即可能丢失符号可见性和唯一性
      如果使用动态库实现功能模块,应当将日志类放到另一个动态库中

静态库链接规则

  • 依次链接静态库
  • 链接时从传给链接器的链接库列表中最后一个静态库开始,反方向逐个链接
  • 链接器会对静态库详细的检索,所有目标文件中,只有包含客户二进制文件实际所需符号的目标文件才会进行链接

静态库转为动态库

  • Linux下 ar -x <static-lib>.a
  • Windows下 使用 lib.exe
  • 链接器将提取出的目标文件构建动态库

 

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

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

相关文章

某银行开发一个信用卡管理系统CCMS

38&#xff0e;现准备为某银行开发一个信用卡管理系统CCMS&#xff0c;该系统的基本功能为&#xff1a; (1)信用卡申请&#xff1a;非信用卡客户填写信用卡审请表&#xff0c;说明所要申请的信用卡类型及申 请者的蒸本信息&#xff0c;并提交给CCMS。如果信用卡申请者被…

Day08__异常

异常 Error和Exception捕获和抛出异常package exception;public class Demo01 {public static void main(String[] args) {int a=1;int b=0;//Ctrl+Alt+ttry {//try监控区域System.out.println(a/b);}catch (ArithmeticException e){//捕获异常System.out.println("除数不…

JAVA基础知识

JAVA基础知识目录IDEA快捷键简洁语法CtrlAltShift操作其他常用操作JAVA基础知识注释字面量变量数据类型关键字标识符类型转换自动类型转换表达式的自动类型转换强制类型转换运算符算数运算符符号做连接符自增自减运算符关系运算符和逻辑运算符三元运算符运算符优先级数组数组的…

Code For Better 谷歌开发者之声——Flutter - Google 开源的移动 UI 框架

写在前面 如今&#xff0c;人们都希望自己手机里的应用又漂亮的设计&#xff0c;顺滑的动画以及优异的功能&#xff0c;为了做到这些&#xff0c;开发者们需要在不妥协质量和性能的前提下&#xff0c;更快速地推进新功能的研发&#xff0c;这就是Flutter的核心。 文章目录写在前…

【转】VS2019 安装完成之后再安装其它功能模块组件安装

VisualStudio2019安装完成之后,由于开发需求需要安装其它模块或组件,点击工具,选择获取工具和功能,进行选择安装。 做个笔记随笔记录一下。选择需要的模块进行即可。【转自】https://www.cnblogs.com/jiayan1578/p/13665423.html

网络抖动了解

转自:https://blog.csdn.net/W_317/article/details/112801222, https://zhuanlan.zhihu.com/p/403614008 1.网络抖动 指网络中的延迟是指信息从发送到接收经过的延迟时间,一般由传输延迟及处理延迟组成; 抖动=最大延迟-最小延迟 如访问一个网站的最大延迟是20毫秒,最小延迟…

【转载】预测算法--时间序列(ARIMA)模型

ARIMA:AutoregressiveIntegratedMovingAverage model。自回归差分移动平均模型(p,d,q),可以说AR自回归模型,MA移动平均模型,ARMA自回归移动平均模型都是ARIMA的特殊形式. 时间序列模型一般性步骤包括:1. 数据平稳性检验;2. 确定模型参数;3. 构建时间序列模型;4.模型预测…

STM32二十:OLED和LCD

一.概述 1.OLED介绍 1 //OLED的显存2 //存放格式如下.3 //[0]0 1 2 3 ... 127 4 //[1]0 1 2 3 ... 127 5 //[2]0 1 2 3 ... 127 6 //[3]0 1 2 3 ... 127 7 //[4]0 1 2 3 ... 127 8 //[5]0 1 2 3 ... 127 9 //[6]0 1 2 3 ... 127 10 //[7]0 1 2 3 ... 127 …

一.xv6环境搭建

内容大致来源:1.视频教程:https://space.bilibili.com/16765968/channel/collectiondetail?sid=86878 2.文档:https://tarplkpqsm.feishu.cn/docs/doccnoBgv1TQlj4ZtVnP0hNRETd#W8iZmH一.windows升级为专业版注意:docker支持Windows 10 操作系统专业版,所以要升级windows…

【ASP.NET Core】自定义Session的存储方式

在开始今天的表演之前,老周先跟大伙伴们说一句:“中秋节快乐”。 今天咱们来聊一下如何自己动手,实现会话(Session)的存储方式。默认是存放在分布式内存中。由于HTTP消息是无状态的,所以,为了让服务器能记住用户的一些信息,就用到了会话。但会话数据毕竟是临时性的,不…

MySQL-3-多表查询和事务(结合案例学习)

我们之前在讲解SQL语句的时候&#xff0c;讲解了DQL语句&#xff0c;也就是数据查询语句&#xff0c;但是之前讲解的查询都是单表查询&#xff0c;而本章节我们要学习的则是多表查询操作&#xff0c;主要从以下几个方面进行讲解。 多表查询多表查询多表关系分类连接查询内连接隐…

【数据结构】绪论

文章目录 1. 绪论 1.1 概述 1.2 数据与数据结构 1.2.1 术语 1.2.2 逻辑结构 1.2.3 存储结构&#xff1a; 1.2.4 数据操作&#xff1a; 1.3 算法 1.3.1 算法特性 1.3.2 算法目标 1.3.3 算法分析&#xff1a;概述 1.3.4 算法分析&#xff1a;时间复杂度&#xff08;大…

Markdown笔记软件之 Obsidian

我使用过什么markdown笔记软件了解自己的需求 Markdown 语法简洁vscode内置 markdown 插件,预览等 snippet(摘要功能)自定义代码片段typero实时渲染,所见即所得 美观缺点不适合我个人 收费 不支持打标签 tag 放弃:解决不了我的痛点(全键盘),收费 不支持移动端joplin支持 v…

模拟用户登录功能的实现以及演示SQL注入现象

模拟用户登录功能的实现以及演示SQL注入现象 /* 实现功能&#xff1a;1、需求&#xff1a;模拟用户登录功能的实现。2、业务描述&#xff1a;程序运行的时候&#xff0c;提供一个输入的入口&#xff0c;可以让用户输入用户名和密码用户输入用户名和密码之后&#xff0c;提交信息…

Day07__面向对象

面向对象 什么是面向对象回顾方法的定义 package objectOriented;import java.io.IOException;//回顾方法的定义 public class Demo01 {public static void main(String[] args) {}public static String sayHello(){return "Hello,World!";}public int max(int a,int…

Deno 会取代NodeJS吗?

目标:了解Deno的学习价值和前景。 从下面几个维度进行分析 成熟度 Node已经在大量商业应用中,Deno只是还在商业试验阶段 生态 Node已经有丰富的生态,包含各种框架和库,并且都已经广泛应用Deno的框架和库基本上都是刚刚起步 学习成本 如果你已经了解Node,Deno也还是需要不…

基于蜜蜂算法求解电力系统经济调度(Matlab代码实现)

目录 1 蜜蜂优化算法 1.1 蜂群觅食机制 1.2 蜜蜂算法 1.3 流程 2 经济调度 3 运行结果 4 Matlab代码及文章 5 参考文献 6 写在最后 1 蜜蜂优化算法 蜜蜂算法( Bees Algorithm&#xff0c;BA) 由英国学者 AfshinGhanbarzadeh 和他的研究小组于 2005 年提出。该算法是一…

element table 列头和行高调整

1、行高调整<el-table :row-style="{height:0}"></el-table>2、列头高度调整<el-table :header-cell-style="{padding:0}" :row-style="{height:0}"></el-table>

都这麽大了还不了解防火墙?

目录 一、思考 二、实验 三、过程 1、实验拓扑 2、cloud-IO配置 3、防火墙配置 3.1 登录防火墙 4、区域划分 方法一 方法二 4.1 内网划分 4.2 各区域网关 4.3 区域配置 5、防火墙策略 5.1 允许-回程路由&#xff08;内网~外网&#xff09; 5.2 禁止-新建策略…

AI作画飞入平民百姓家——stable diffusion初体验

1. 前言 stable_diffusion来了&#xff0c;这个号称是最强的民用文本生成图片的模型它来了&#xff0c;相比较DAEE等大模型&#xff0c;它能够让我们消费级的显卡也能够实现文本到图像的生成。下面&#xff0c;我们也来试一下。 2. 准备过程 该服务器上必须要有的基础工具an…