linux之库的原理(1)

前提介绍:

(1).o/.obj的名字其实叫可重定位文件

(2)所有的二进制文件(动/静态库,可执行文件,.o文件)都有自己的格式叫EIF,也可以说这些文件都是有一定格式的,或者可以说EIF自身就是二进制文件。

1.对EIF的介绍

下面通过一些指令来说明这张图的详情

(1)指令size

复制代码
//size + 二进制文件,查看该文件的EIF属性
size code.o
ubuntu@VM-0-2-ubuntu:~/test$ size code.o
   text	   data	    bss	    dec	    hex	filename
     32	      0	      0	     32	     20	code.o

首先介绍:代码节(.text):保存机器指令,数据节(.data):保存变量。这些数据都是属于section区的,且一个section中存的基本是同一个属性的数据。

每一个.o文件的EIF格式是一样的,因此同一个位置的数据能进行合并形成一个更大的可执行文件

所以.o文件可以直接合并生成.exe,可以说静态库的合并就是这样的(直接与用户的.o文件合并了)

.o的同一个section之间合并后叫segment(section合并的原则是存在program header table中的)

(2)readelf -S

复制代码
//读取EIF中section header table中的数据
readdlf -S code.o

ubuntu@VM-0-2-ubuntu:~/test$ readelf -S code.o
There are 10 section headers, starting at offset 0x128:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000000  0000000000000000  AX       0     0     1
  [ 2] .data             PROGBITS         0000000000000000  00000040
       0000000000000000  0000000000000000  WA       0     0     1
  [ 3] .bss              NOBITS           0000000000000000  00000040
//...

每个区域的划分方法:可以将一个文件视为一个一维线性数组,那么每个区域就可以用其相对于起始地址的offset来划分了。

bss区:也是用来存变量的.

全局变量分为已初始化和未初始化的(默认值为0),已初始化的存进.data区中,未初始化的所有变量都只用一个数字记录起来然后存进bss中,然后bss在内存展开时才会给我们的所有未初始化全局变量开辟空间。

一个section是否发生了合并的判断方法

(1)根据可执行文件的section区间与.o文件的section区间在个数,属性上进行比对。

(2)根据program header table中的数据进行合并。

(3)readelf -l

复制代码
//只能对可执行文件使用,可以说明该文件的segment情况
ubuntu@VM-0-2-ubuntu:~/test$ readelf -l code

Elf file type is DYN (Position-Independent Executable file)
Entry point 0x22c0
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000001060 0x0000000000001060  R      0x1000
//...

加载的本质:可以发现有一段区间为load区(就是会加载到内存的区间)。在.o文件合并成.exe后会更新program header table和section header table中的数据,然后OS根据program header table 中的数据找到load区间,然后将load区间中的数据加载到内存当中。

OS也将内存看作一个线性一维数组,每一个元素的大小为4KB,也就是说OS对内存和对磁盘的操作单元都为4KB,因此内存和磁盘实际上就可以用同一个单位进行交互了。这也说明我们无论向内存申请多少数据(小于4KB),OS也会直节给我们4KB的内存资源造成资源浪费。

其实EIF中的每一部分其实都是彼此相互独立加载到内存中的,因此如果有些section很小,进行合并后就可以提高内存的使用效率。

section header table的数据意义与用途:

(1)是链接器,进行合并操作以及记录合并后每个区间的范围大小(EIF header 中也存储着大部分区间的空间大小)

(2)说明.exe的形成过程

(3)形成program header table

program header table的数据意义与用途:

(1)是加载器的一种,给编译器用的,即用于加载文件到内存,形成进程的初始化(说明能加载进内存的区间和说明不同区间的权限(readelf -l 中的flag 就是说明不同区间的权限))

(2)页表中的权限初始化就是来自于这里

(4)后面直接说明区间:symbol table

复制代码
//可以查一个.exe的符号表数据
ubuntu@VM-0-2-ubuntu:~/test$ readelf -s code

Symbol table '.dynsym' contains 31 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBCXX_3.4.21 (3)

Symbol table '.symtab' contains 88 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS Scrt1.o
     2: 000000000000038c    32 OBJECT  LOCAL  DEFAULT    4 __abi_tag

symbol table就是符号表,用于记录函数名,变量名与代码之间的关系。

其的本质就是一个一维的字符数组例:

复制代码
char arr[] = "hello\0func\0";

寻找每个字符串是要记录每一个字符串对起始下标的偏移量,然后根据偏移量在arr[](不是真名)中读取数据。

(5)EIF header

复制代码
//读取该.exe的EIF header数据
readelf -h code
ubuntu@VM-0-2-ubuntu:~/test$ readelf -h code

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
//..
  Entry point address:               0x22c0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          22512 (bytes into file)
//..

Magic:一个随机值,用于给OS说明我这个程序格式是EIF,也就是OS是通过Magic来识别一个可执行文件是什么类型的(如区分.mp3,mp4,.txt等,因此上面文章直接使用.exe是不准确的,但图省事我后面依旧有可能使用.exe)

EIF的分区本质就是用偏移量(相对编址)来划分的。

2.objdump

把二进制文件进行反汇编

bash 复制代码
ubuntu@VM-0-2-ubuntu:~/test$ objdump -d test
//-d使用说明只查看代码段的反汇编
test:     file format elf64-x86-64


Disassembly of section .init:

0000000000001000 <_init>:
    1000:	f3 0f 1e fa          	endbr64
    1004:	48 83 ec 08          	sub    $0x8,%rsp
    1008:	48 8b 05 d9 2f 00 00 	mov    0x2fd9(%rip),%rax        # 3fe8 <__gmon_start__@Base>
    100f:	48 85 c0             	test   %rax,%rax
    1012:	74 02                	je     1016 <_init+0x16>
    1014:	ff d0                	call   *%rax
    1016:	48 83 c4 08          	add    $0x8,%rsp
    101a:	c3                   	ret
//..




ubuntu@VM-0-2-ubuntu:~/test$ objdump -d test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:	f3 0f 1e fa          	endbr64
   4:	55                   	push   %rbp
   5:	48 89 e5             	mov    %rsp,%rbp
   8:	48 8d 05 00 00 00 00 	lea    0x0(%rip),%rax        # f <main+0xf>
   f:	48 89 c7             	mov    %rax,%rdi
  12:	e8 00 00 00 00       	call   17 <main+0x17>
  17:	b8 00 00 00 00       	mov    $0x0,%eax
  1c:	5d                   	pop    %rbp
  1d:	c3                   	ret

左边的数字就是汇编指令转换为机器码的情况,如果指令对应的是一个函数的话,机器码的后8位数字就是该函数的地址,如果该函数在当前文件中找不到的话,此处的数字为全0,代表该函数没有被链接(只有.o这种未被链接的文件才有这种函数.exe出现未被链接的函数直接就error不允许生成.exe了)

总结有.o是允许有不认识的函数出现的,只有在链接时才会修正这些函数地址,也就是.o文件彼此之间不认识,链接后才可能认识。

相关推荐
测试员周周1 小时前
【Appium 系列】第10节-手势操作实战 — 滑动、拖拽、缩放与轻拂
linux·服务器·开发语言·人工智能·python·appium·pytest
实心儿儿1 小时前
Linux —— 进程间通信 - system V进程间通信 - 共享内存(1)
linux·运维·服务器
CC城子1 小时前
EtherCAT研究之物理层PHY(一)
linux·运维·数据库
代码AC不AC1 小时前
【Linux】信号保存 及 信号捕获
linux·信号保存·信号捕获
wzhao1011 小时前
动态链接器(十一):线程局部存储
linux·rust·gnu
neo_Ggx231 小时前
Linux 日志检索速查:按时间、接口、Trace ID 查询完整请求链路
java·linux·服务器
蜀道山老天师1 小时前
Prometheus监控Hadoop集群(实操完整版,含避坑指南)
大数据·linux·运维·hadoop·云原生·prometheus
biubiubiu07061 小时前
Ubuntu命令练习
linux·运维·ubuntu
曦夜日长1 小时前
Linux系统篇,开发工具(二):vim的使用与配置
linux·服务器·vim·excel