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

Linux系统学习之---库的理解和加载(毛坯初版...)

重点:

  • 大致了解静态库形成程序的过程
  • ELF文件如何加载到内存 , 以及如何转化为进程(逻辑/物理/虚拟地址,虚拟地址空间).
  • 动态库和可执行程序关联的方式.
  • 详细了解动态库的加载方式

一.ELF文件组成:

1.一个可执行程序的生成

我们使用gcc 不带任何特殊选项生成一个 a.out可执行文件时 , 中间其实隐藏了一些过程.

源文件(.c) -> 预处理文件(.i) -> 汇编文件(.s) -> 目标文件(.o) -> 可执行文件(.bin)

其中,对源文件进行头文件展开/处理预处指令/处理宏替换生成预处理文件 , 预处理文件解析成汇编语言成为目标文件 , 至此为止都是一个文件的独角戏 .

目标文件转换为可执行文件的过程叫做链接 , 往往涉及到其他文件 , 所以肯定要有一套标准来提升链接效率—ELF文件格式就是这样一套标准 , 目标文件/可执行文件/库文件都是ELF格式的.

2.ELF格式文件

一个ELF文件由四个主要部分构成 : ELF header / program header table / section / section header table , 其中section不止一个

在这里插入图片描述

3.查看ELF文件相关内容的指令

readelf -S PATH 用于查看Section Header Table(大致理解为数组)内容

readelf -s PATH 用于查看符号表内容

readelf -l PATH 读取 program headers (未来合并加载的规则)

[!折叠的代码]-

an@mycloud:~/code-in-linux/c++_linux/Library_dyna_static$ readelf -l codeElf file type is DYN (Position-Independent Executable file)
Entry point 0x1060
There are 13 program headers, starting at offset 64Program Headers:Type           Offset             VirtAddr           PhysAddrFileSiz            MemSiz              Flags  AlignPHDR           0x0000000000000040 0x0000000000000040 0x00000000000000400x00000000000002d8 0x00000000000002d8  R      0x8INTERP         0x0000000000000318 0x0000000000000318 0x00000000000003180x000000000000001c 0x000000000000001c  R      0x1[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]LOAD           0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000628 0x0000000000000628  R      0x1000LOAD           0x0000000000001000 0x0000000000001000 0x00000000000010000x0000000000000175 0x0000000000000175  R E    0x1000LOAD           0x0000000000002000 0x0000000000002000 0x00000000000020000x00000000000000f4 0x00000000000000f4  R      0x1000LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db80x0000000000000258 0x0000000000000260  RW     0x1000DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc80x00000000000001f0 0x00000000000001f0  RW     0x8NOTE           0x0000000000000338 0x0000000000000338 0x00000000000003380x0000000000000030 0x0000000000000030  R      0x8NOTE           0x0000000000000368 0x0000000000000368 0x00000000000003680x0000000000000044 0x0000000000000044  R      0x4GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x00000000000003380x0000000000000030 0x0000000000000030  R      0x8GNU_EH_FRAME   0x0000000000002010 0x0000000000002010 0x00000000000020100x0000000000000034 0x0000000000000034  R      0x4GNU_STACK      0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000  RW     0x10GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db80x0000000000000248 0x0000000000000248  R      0x1Section to Segment mapping:Segment Sections...00     01     .interp 02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03     .init .plt .plt.got .plt.sec .text .fini 04     .rodata .eh_frame_hdr .eh_frame 05     .init_array .fini_array .dynamic .got .data .bss 06     .dynamic 07     .note.gnu.property 08     .note.gnu.build-id .note.ABI-tag 09     .note.gnu.property 10     .eh_frame_hdr 11     12     .init_array .fini_array .dynamic .got 

readelf -h PATH 用于读取 ELF header

[!折叠的代码]-

an@mycloud:...$ readelf -h code
ELF Header:# Magic : 用于标识elf格式Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class:                             ELF64Data:                              2's complement, little endianVersion:                           1 (current)OS/ABI:                            UNIX - System VABI Version:                       0Type:                              DYN (Position-Independent Executable file)Machine:                           Advanced Micro Devices X86-64Version:                           0x1# Entry point address为程序入口地址,很关键.Entry point address:               0x1060Start of program headers:          64 (bytes into file)Start of section headers:          13976 (bytes into file)Flags:                             0x0Size of this header:               64 (bytes)Size of program headers:           56 (bytes)Number of program headers:         13Size of section headers:           64 (bytes)Number of section headers:         31Section header string table index: 30

objdump -d FIlename_OF_ELF 用于反汇编ELF格式文件

[!折叠的代码]-
source.o: file format elf64-x86-64

二.ELF文件的合并

  • ELF多个性质相同的的节合并成一个个段 ,
  • 所有段统一平坦编址 ,
  • 建立内和数据结构时根据虚拟地址和对应的物理地址建立页表映射 ,
  • 让段的Entry point address保存到cpu寄存器 ,
  • 之后就可以正常调度
  1. 各个ELF文件在合并时主要依靠section header table里的内容。
  2. 各个ELF文件将性质相同的节合并成段,比如可读可执行的.text .rodata合并到一起
  3. 合并之后删除重复数据以及使命已达sectionheader table

三.静态链接 :

1. 孤立的目标文件

库文件和目标文件以及可执行文件一样,都是ELF文件格式 . 毕竟我们的程序至少包含了标准输入输出的库文件.

1.使用readelf工具的-s选项来看一个目标文件的符号表.
2.可以看到最后Name是Puts的一行 ,Type 是NOTYPE, Ndx是 UND , 说明内容还不完全.
3.这是因为puts代表的是程序里调用的printf , 而printf函数的声明和定义在其他文件里

an@mycloud:...$  readelf -s source.oSymbol table '.symtab' contains 6 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS source.c2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text3: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 .rodata4: 0000000000000000    30 FUNC    GLOBAL DEFAULT    1 main5: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts #UND

2.链接时重定向:

1.现在直接看最终的可执行文件.
2.使用objdump工具的-d选项来查看可执行文件中有关puts相关的内容
3.可以看见puts函数有了地址 , 这就是从 .o文生成.bin文件的链接过程中 .o文件和库文件产生链接,找到了puts函数后就有了地址.

an@mycloud:~...$  objdump -d code
......................................
0000000000001050 <puts@plt>:1050:       f3 0f 1e fa             endbr641054:       ff 25 76 2f 00 00       jmp    *0x2f76(%rip)        # 3fd0 <puts@GLIBC_2.2.5>105a:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
.....................................

四.程序的加载和虚拟地址

1. 程序加载之前

物理层面

  • 对于c/c++这种编译型语言 , 可执行程序文件在运行之前就乖乖躺在磁盘里了 .
  • 想要真正运行,必然要经历从磁盘到内存的过程,也就是拷贝 , 于是如何高效的让程序运行,很大程度上由拷贝的效率觉得
  • 文件系统和磁盘交互的单元是多个扇区组成的块 .
    此时,至少在物理层面从磁盘到内存的拷贝效率较高,因为成块成块的读取IO次数少.

软件层面

  • 在加载到内存之前 , 磁盘上的可执行文件就已经存在了地址 .
  • 每一个ELF文件都有一个地址 , 不过与其说是地址 , 倒不如说是一个偏移量 , 且所有ELF在分配偏移量时都是以同一个起始地址为基准(可以认为是0)

2.程序加载时

  • 当可执行程序往内存中加载时 , 上面提到的地址(偏移量)就排上大用场了 .
  • 操作系统里维护着虚拟地址 , 于是永操作系统里虚拟地址加上可执行文件的偏移量,就得到了可执行文件的实际虚拟地址值.
  • 可执行文件自带物理地址 , 于是操作系统用刚刚得到的虚拟地址值和物理地址作一个映射,就得到了页表 .
  • 可执行文件ELF header部分记录的程序入口地址交给CPU的EIP寄存器,这样就能运行啦.

[!物理地址&逻辑地址&虚拟地址???]- #物理地址_逻辑地址_虚拟地址

  • 物理地址不用多说 , 文件中每个字节都自带物理地址 , 只不过操作系统层面给屏蔽了.
  • 逻辑地址就是上面说的可执行程序里各个部分的起始地址,也就是偏移量 , 一旦进入内存,有了首虚拟地址 , 马上就能通过偏移量得到实际的虚拟地址.
  • 虚拟地址就是为了方便上层使用而抽象出来的地址 , 各个区泾渭分明 , 看似连续 , 实则通过内核里维护的页表做了虚拟地址和物理地址的映射.
  • 其实可以理解逻辑地址和虚拟地址的区别很暧昧 , 甚至可以认为是一回事 , 只不过逻辑地址是磁盘层面的概念 , 虚拟地址为内存里的概念 , 二者之间的转换几乎没有成本

五.动态库的加载和动态链接:

静态库需要在生成可执行程序时将代码一同合并进去

,动态库不同 , 生成可执行文件时他没有什么动静 , 而在程序加载时 , 他会加载到物理内存虚拟地址空间的共享区 , 让程序在页表中建立映射关系.

一个程序可以通过页表映射到这个动态库 , 多个文件也可以在页表中建立和库的映射关系 .

自此就达到了多个程序共享一个库的效果 , 极大地节省了内存空间.


不同的程序虚拟地址不同 , 于是程序代码中库函数的地址必然会不同 .

一个动态库里的函数 , 本身就会有各自的地址偏移量 , 于是在运行前程序代码中库函数调用处的地址其实是这个偏移量.

当程序真的要加载了 , 就会由链接器查找共享区的这个库 , 获取相关库整体的虚拟地址 , 将其和代码里库函数的偏移量相加 , 就得到了实际的库函数的虚拟地址空间 .

但是!!! 代码段是不可修改的只读部分,此路不通…

操作系统的做法是另外维护一张表 , 叫做GOT(global offset talbel) , 即全局偏移量表 . 因此在程序加载到一个库函数时 , 会自动跳转的GOT里查找对应的偏移量,结合动态库的首地址计算出实际的虚拟地址,从而完成调用.

在计算机的体系中缓存无处不在 , 此处以PLT(procedure linkage table),即过程链接表. 他就类似于一种缓存机制 , 当一个库函数第一次调用时 , 用过其偏移量和动态库的首地址计算出的实际虚拟地址 , 然后存放到这个表里 . 下一次涉及动态库函数的调用时,优先到这个表里查找库函数对应的虚拟地址 , 免去了重复计算.

[!动过手脚的源文件]- 动过手脚的源文件

  1. 但从语法层面来讲 , c/c++的程序从main函数开始 , 可实际并非如此 .
  2. 在源代码编译后 , main函数调用之前 , 还会有一个 _start函数.
  3. 他会调用动态连接器 , 进行打开动态库->将其加载到内存->进行符号解析和重定位(检测包含的动态库函数调用,并计算处实际虚拟地址)->更新GOT表.
  4. 此时程序中动态库函数都有了调用地址后才开始执行main函数里的代码.
http://www.dtcms.com/a/419299.html

相关文章:

  • 南山模板网站建设公司怎么看网站的外链
  • 企业网站策划大纲模板文山住房和城乡建设局网站
  • Linux 基础IO与系统IO
  • 【IEDA】已解决:IDEA中jdk的版本切换
  • idea推荐springboot+mybatis+分页查询插件之PageHelper
  • 南非网站域名做网站微信支付多少钱
  • 网站开发 图形验证码网站建设衤金手指下拉10
  • OPenssh6代码移植的依赖库 OpenSSL双库连接问题的解决方案
  • 商务网站建设组成包括网站优化wordpress 换行
  • tiktok scheme
  • Xrdp 远程桌面配置【笔记】
  • 【Linux】倒计时和进度条实现
  • 网站建设需要用到哪些软件有哪些系统安装wordpress
  • 梯度下降(Gradient Descent)
  • 东莞市建设规划局网站游戏类企业网站模板
  • C++---bind(绑定函数或函数对象的参数)
  • 网站和域名网站开发技术是什么
  • 个人如何开网站西安网络推广外包公司
  • 国产处理器飞腾CPU各系列综合性能对比
  • 南宁网站设计推广wordpress+授权登录
  • 网站建设深圳哪里学品牌建设提升
  • 《LangChain入门指南》学习笔记1:第1章 LangChain:开启大语言模型时代的钥匙
  • 国清汇携手社保基金会推出《国脉相承·传世养老基金》
  • 3、内存系统详解 - 从DDR演进到GPU内存架构的认知基石
  • 芯片和半导体:Intel开始布局14A工艺
  • JavaWeb 课堂笔记 —— 26 SpringBoot 原理
  • 网上国网app下载安装哈尔滨seo优化排名
  • HTTP首部字段(速查-全47种)
  • 嘉兴高端网站定制100网站建设
  • TypeScript的新类型:unknown