-梅头像
关注
linux(9) 库封面图

linux(9) 库

前言:

库这个东西,我们在以前就经常用,只是以前在使用库的时候,有一些问题是无法在学习语言的时候解决的,所以我们今天站在系统的层面来学习一下库;

首先,我们先聊聊什么是库;

然后,我们就要尝试自己编写一个库;

之后,我们来讲一讲如何使用我们的库;

在此之后,我们会引出ELF文件,从而对虚拟地址有一个全新的理解;

最后,我们来讲讲动态链接和静态链接;

那么,做好咯,我们现在发车~

正文:

静态库是什么:

我们先来一个例子:

比如今天有两个人,一个叫梅(我),一个叫张三,我们的老师布置了一个作业,让我们实现一个.......的功能,要自己来写函数;

我比张三厉害一些,所以我很快就写完了这个作业:

[tsx@VM-0-10-centos mei]$ tree
.
|-- func.c
|-- func.h
`-- main.c

内部如下:

[tsx@VM-0-10-centos mei]$ cat main.c
#include "func.h"
int main()
{
  print();
  return 0;
}
[tsx@VM-0-10-centos mei]$ cat func.c
#include "func.h"
void print()
{
  printf("今天是九月十七日\n");
}
[tsx@VM-0-10-centos mei]$ cat func.h
#include<stdio.h>
void print();

运行结果如下:

[tsx@VM-0-10-centos mei]$ gcc -o main main.c func.c
[tsx@VM-0-10-centos mei]$ ./main
今天是九月十七日

所以呀,我很轻松的就完成了这个作业,这时候呢;

张三有点急了,这小子跑过来对我说,“那个,小梅啊,我这作业不会写,你发一份给我呗”

我想了想,直接发过去的话......这老师检查作业非常严格,要是发现我俩写的一模一样,那最后说是谁抄谁的?容易得罪人.......因此我根本不想把源文件给他,这时候,我就想着:

咦?我们不是还有一种编译链接的方式吗?就是把所有.c文件都编译成.o文件,再进行链接

[tsx@VM-0-10-centos mei]$ gcc -c *.c
[tsx@VM-0-10-centos mei]$ tree
.
|-- func.c
|-- func.h
|-- func.o
|-- main
|-- main.c
|-- main.o
`-- makefile

就像这样,我们就得到了两个.o文件,那么我们是不是可以只把.o文件发给张三,然后让他去链接啊,而且.o里面存的是二进制机械码(ELF文件,我们稍后讲),这老师应该也看不懂,所以啊,我对张三说:

“张三啊,我把这个.o文件发给你,你自己链接吧!”

张三挠了挠头:

“也行吧!”

于是呢,我做了如下操作:

[tsx@VM-0-10-centos 917]$ tree
.
|-- mei
|   |-- func.c
|   |-- func.h
|   |-- func.o
|   |-- main
|   |-- main.c
|   |-- main.o
|   `-- makefile
`-- zhangsan

2 directories, 7 files
[tsx@VM-0-10-centos 917]$ mv mei/func.o zhangsan
[tsx@VM-0-10-centos 917]$ tree
.
|-- mei
|   |-- func.c
|   |-- func.h
|   |-- main
|   |-- main.c
|   |-- main.o
|   `-- makefile
`-- zhangsan
    `-- func.o

这时候啊,张三就得到了我的func.o,但是张三这时候挠了挠头,这二进制机械码......张三看不懂啊?!所以张三就来问我:

“你这写的啥啊,我都看不懂你写了什么函数”

这时候我拍了拍脑袋:

“哦~~~我该把头文件发给你的,你等等,我把头文件发给你”

[tsx@VM-0-10-centos 917]$ mv mei/func.h zhangsan
[tsx@VM-0-10-centos 917]$ tree
.
|-- mei
|   |-- func.c
|   |-- main
|   |-- main.c
|   |-- main.o
|   `-- makefile
`-- zhangsan
    |-- func.h
    `-- func.o

这时候呢,张三有了我的头文件和函数,那他是不是就可以自己编main.c函数,然后链接一下就好了呀?

[tsx@VM-0-10-centos zhangsan]$ tree
.
|-- func.h
`-- func.o

0 directories, 2 files
[tsx@VM-0-10-centos zhangsan]$ touch zhang_main.c
[tsx@VM-0-10-centos zhangsan]$ vim zhang_main.c
[tsx@VM-0-10-centos zhangsan]$ gcc -c zhang_main.c
[tsx@VM-0-10-centos zhangsan]$ gcc -o zhang_main *.o
[tsx@VM-0-10-centos zhangsan]$ tree
.
|-- func.h
|-- func.o
|-- zhang_main
|-- zhang_main.c
`-- zhang_main.o

[tsx@VM-0-10-centos zhangsan]$ ./zhang_main
今天是九月十七日

我们发现,事实果然如此啊,只要我把.o文件和头文件给了张三,那我就可以让张三在不看到我源代码的同时,可以进行编译!

那么,后来我反思了一下,我后来发现,这样一个个的把.o文件和头文件给过去,好臃肿,好挫啊,所以我就想了一个办法,把它们捆绑成库:

[tsx@VM-0-10-centos 917]$ tree
.
|-- mei
|   |-- func2.c
|   |-- func2.h
|   |-- func2.o
|   |-- func.c
|   |-- func.h
|   |-- func.o
|   |-- main
|   |-- main.c
|   |-- main.o
|   `-- makefile
`-- zhangsan

我先让一切都回到没有给张三.o文件和头文件的时候,为了方便我们演示,我又多加了一个func2.c,内容如下:

[tsx@VM-0-10-centos mei]$ cat func2.c
#include "func2.h"
void print2()
{
  printf("今天是农历八月初七\n");
}

接下来,我们来进行静态库的打包

我们要用到的命令是       ar   rc   库名称   库用到的.o文件

[tsx@VM-0-10-centos mei]$  ar rc libmei.a func.o func2.o
[tsx@VM-0-10-centos mei]$ tree
.
|-- func2.c
|-- func2.h
|-- func2.o
|-- func.c
|-- func.h
|-- func.o
|-- libmei.a
|-- main
|-- main.c
|-- main.o
`-- makefile

这样,我们就形成了我的静态库了;

同时,我们也可以通过   ar   -t   来看我的库中包含了哪些.o文件

[tsx@VM-0-10-centos mei]$ ar -t libmei.a
func.o
func2.o

好,现在我们有库了,我们把库和头文件打包一下:

[tsx@VM-0-10-centos mei]$ tree
.
|-- func2.c
|-- func2.h
|-- func2.o
|-- func.c
|-- func.h
|-- func.o
|-- libmei.a
|-- main
|-- main.c
|-- main.o
|-- makefile
`-- yanmei
    |-- ku
    `-- tou

3 directories, 11 files
[tsx@VM-0-10-centos mei]$ mv libmei.a yanmei/ku
[tsx@VM-0-10-centos mei]$ mv func.h func2.h yanmei/tou
[tsx@VM-0-10-centos mei]$ tree
.
|-- func2.c
|-- func2.o
|-- func.c
|-- func.o
|-- main
|-- main.c
|-- main.o
|-- makefile
`-- yanmei
    |-- ku
    |   `-- libmei.a
    `-- tou
        |-- func2.h
        `-- func.h

那yanmei这个目录,就是我们最终要给张三的咯:

[tsx@VM-0-10-centos 917]$ tree
.
|-- mei
|   |-- func2.c
|   |-- func2.o
|   |-- func.c
|   |-- func.o
|   |-- main
|   |-- main.c
|   |-- main.o
|   `-- makefile
`-- zhangsan
    `-- yanmei
        |-- ku
        |   `-- libmei.a
        `-- tou
            |-- func2.h
            `-- func.h

那现在,张三就获得了我精心为他准备的静态库了,那么怎么用呢?

静态库的使用:

静态库的使用有四种,不过嘛,我们今天只讲最常用的两种:

①:用命令指明路径与库名称

[tsx@VM-0-10-centos 917]$ cd zhangsan
[tsx@VM-0-10-centos zhangsan]$ touch main.c
[tsx@VM-0-10-centos zhangsan]$ vim main.c
[tsx@VM-0-10-centos zhangsan]$ gcc -o main main.c -L yanmei/ku -I yanmei/tou -l mei
[tsx@VM-0-10-centos zhangsan]$ ./main
今天是九月十七日
今天是农历八月初七
[tsx@VM-0-10-centos zhangsan]$ tree
.
|-- main
|-- main.c
`-- yanmei
    |-- ku
    |   `-- libmei.a
    `-- tou
        |-- func2.h
        `-- func.h

这种方法写起来比较长,不过我觉得比较简单,毕竟相对于把文件安装到系统目录下,实在是方便太多了

②.把头文件,静态库安装到系统目录下:

[tsx@VM-0-10-centos zhangsan]$ sudo cp yanmei/tou/func.h /usr/include
[tsx@VM-0-10-centos zhangsan]$ sudo cp yanmei/tou/func2.h /usr/include
[tsx@VM-0-10-centos zhangsan]$ sudo cp yanmei/ku/libmei.a /usr/lib
[tsx@VM-0-10-centos zhangsan]$ rm main
[tsx@VM-0-10-centos zhangsan]$ tree
.
|-- main.c
`-- yanmei
    |-- ku
    |   `-- libmei.a
    `-- tou
        |-- func2.h
        `-- func.h

3 directories, 4 files
[tsx@VM-0-10-centos zhangsan]$ gcc -o main main.c -l mei
[tsx@VM-0-10-centos zhangsan]$ ./main
今天是九月十七日
今天是农历八月初七

核心是:

把头文件放到  /usr/include  目录下

把库放到   /usr/lib   目录下

对库的理解:

那我链接的本质不就是,把一堆.o文件给连接到一起吗?而我的库在经过我们上面的探索之后,我们知道了,库本质不也就是一堆.o文件的集合体吗!!!!

此外,注意一下,库必须以lib开头,以.a或.so 结尾,掐头去尾,即为库的真名

动态库:

如何打包动态库:

结论:

gcc   -shared   (.o文件)   -o    (动态库名字)

实际演示:

[tsx@VM-0-10-centos mei]$ tree
.
|-- func2.c
|-- func.c
|-- main
|-- main.c
|-- main.o
`-- makefile

0 directories, 6 files
[tsx@VM-0-10-centos mei]$ gcc -c -fPIC func.c
[tsx@VM-0-10-centos mei]$ gcc -c -fPIC func2.c
[tsx@VM-0-10-centos mei]$ tree
.
|-- func2.c
|-- func2.o
|-- func.c
|-- func.o
|-- main
|-- main.c
|-- main.o
`-- makefile
fPIC:产⽣位置⽆关码(position independent code)

这个是必须加上的;

[tsx@VM-0-10-centos mei]$ gcc -shared func.o func2.o -o libqingzhu.so
[tsx@VM-0-10-centos mei]$ tree
.
|-- func2.c
|-- func2.h
|-- func2.o
|-- func.c
|-- func.h
|-- func.o
|-- libqingzhu.so
|-- main
|-- main.c
|-- main.o
`-- makefile

0 directories, 11 files
[tsx@VM-0-10-centos mei]$ mkdir -p qingzhu/tou
[tsx@VM-0-10-centos mei]$ cd qingzhu
[tsx@VM-0-10-centos qingzhu]$ mkdir ku
[tsx@VM-0-10-centos qingzhu]$ cd ..
[tsx@VM-0-10-centos mei]$ mv func.h func2.h qingzhu/tou
[tsx@VM-0-10-centos mei]$ mv libqingzhu.so qingzhu/ku
[tsx@VM-0-10-centos mei]$ tree
.
|-- func2.c
|-- func2.o
|-- func.c
|-- func.o
|-- main
|-- main.c
|-- main.o
|-- makefile
`-- qingzhu
    |-- ku
    |   `-- libqingzhu.so
    `-- tou
        |-- func2.h
        `-- func.h

这样,我们在小梅的目录中,就建好了qingzhu这个目录啦,里面就包含我们的动态库以及头文件;

之后,我们把它移到张三目录中

[tsx@VM-0-10-centos mei]$ mv qingzhu ../zhangsan
[tsx@VM-0-10-centos mei]$ cd ../zhangsan
[tsx@VM-0-10-centos zhangsan]$ tree
.
|-- main.c
|-- qingzhu
|   |-- ku
|   |   `-- libqingzhu.so
|   `-- tou
|       |-- func2.h
|       `-- func.h
`-- yanmei
    |-- ku
    |   `-- libmei.a
    `-- tou
        |-- func2.h
        `-- func.h

6 directories, 7 files

我们在演示的时候,不是把静态库和头文件都安装到系统目录了嘛,我们现在删掉,防止对我们产生影响;

[tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/include/func.h
[sudo] password for tsx: 
[tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/include/func2.h
[tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/lib/libmei.a

动态库的使用:

给张三安装好后,我们来使用这个动态库啦,

编译时:

①:把库文件和头文件安装到系统指定路径下:

1. 把头文件复制到系统头文件目录
sudo cp qingzhu/tou/*.h /usr/include

2. 把动态库复制到系统库目录
sudo cp qingzhu/ku/libqingzhu.so /usr/lib

②:本地路径指定编译

就和我们在静态库使用做的一样:

[tsx@VM-0-10-centos zhangsan]$ gcc main.c -o main -Iqingzhu/tou -Lqingzhu/ku -lqingzhu
[tsx@VM-0-10-centos zhangsan]$ tree
.
|-- main
|-- main.c
|-- qingzhu
|   |-- ku
|   |   `-- libqingzhu.so
|   `-- tou
|       |-- func2.h
|       `-- func.h
`-- yanmei
    |-- ku
    |   `-- libmei.a
    `-- tou
        |-- func2.h
        `-- func.h

问题:

咦?那我们是不是就好啦?那动态库真的太简单啦~~当然不是!!!!!

我们这时候如果运行的话:

[tsx@VM-0-10-centos zhangsan]$ ./main
./main: error while loading shared libraries: libqingzhu.so: cannot open shared object file: No such file or directory

这什么意思呀?

找不到动态库......

怎么会这样呢?

因为!在编译的时候,我们指明的路径,是给编译器指明了路径,而我们在运行的时候,是谁来干活啊?加载器!现在我又没有指明路径,当然找不到咯

那我们这里也给出两种解决方案:

运行时:

①:把库和头文件安装到系统目录

[tsx@VM-0-10-centos zhangsan]$ sudo cp qingzhu/ku/libqingzhu.so /usr/lib
[sudo] password for tsx: 
[tsx@VM-0-10-centos zhangsan]$ sudo cp qingzhu/tou/*.h /usr/include
[tsx@VM-0-10-centos zhangsan]$ ./main
./main: error while loading shared libraries: libqingzhu.so: cannot open shared object file: No such file or directory
[tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/lib/libqingzhu.so
[tsx@VM-0-10-centos zhangsan]$ sudo cp qingzhu/ku/libqingzhu.so /usr/lib64
[tsx@VM-0-10-centos zhangsan]$ ./main
今天是九月十七日
今天是农历八月初七

注意哦,这里把头文件安装到   /usr/lib   是不可以的;

正如我上面写的,就报错了,原因是:

64 位 CentOS,系统库目录不是 /usr/lib,64 位动态库要放到 /usr/lib64 /usr/lib 是给 32 位库用的,你把 64 位 so 放进去,动态链接器不会去这里找,所以依然报错。

那就有人要问了,那我静态库怎么就可以安装到 /usr/lib  目录下呢?

静态库只在编译链接阶段使用,不区分 /usr/lib 和 /usr/lib64 的运行时查找规则;动态库是程序运行时由动态链接器加载,会严格区分 64 位库目录。

②:设置临时变量:

[tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/lib64/libqingzhu.so
[tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/include/func.h /usr/include/func2.h
[tsx@VM-0-10-centos zhangsan]$ export LD_LIBRARY_PATH=qingzhu/ku
[tsx@VM-0-10-centos zhangsan]$ ./main
今天是九月十七日
今天是农历八月初七

和我们在环境变量改PATH这个环境变量的时候差不多;

就是:PATH=$PATH:(路径)

总结一下:

这里依旧给一头表格来总结:

对比项静态库(.a)动态库(.so)
文件后缀.a.so
链接时机编译链接阶段,把库代码拷贝进可执行程序程序运行阶段,程序启动后才加载库
生成方式ar打包.o目标文件,编译目标文件不需要-fPICgcc 加-fPIC生成位置无关代码,-shared生成 so
程序体积可执行文件大,库代码被复制到程序内部可执行文件更小,库单独存放,多个程序可共享同一份 so
运行依赖编译完成后,不再依赖原静态库文件,删掉.a 照样运行运行时必须存在对应的.so,找不到直接报错
内存占用多程序同时运行,各自保存一份库代码,内存冗余内存中只加载一份库,多个进程共享,节省内存
系统目录差异64 位 CentOS,.a放/usr/lib链接器也能找到;仅链接阶段生效64 位 CentOS,64 位 so 必须放/usr/lib64;动态链接器运行时严格区分目录
更新方式库更新,必须重新编译整个程序才能生效替换新的 so 文件,程序不用重新编译,重启程序即可生效
链接参数-lxxx-lxxx
优点运行不依赖外部库,移植简单,运行速度略快程序体积小、节省内存,库升级方便
缺点可执行文件大,库更新需要重编译,内存浪费运行依赖 so 文件,部署时必须附带动态库,容易出现找不到库的问题

ELF文件:

是啥:

ELF(Executable and Linkable Format),Linux 下目标文件、可执行程序、静态库、动态库统一使用的文件格式。 四种文件都是 ELF:

①.o 目标文件(编译后,链接前)

②.a 静态库:多个.o打包而成的 ELF 集合

③.so 动态库:共享对象 ELF

④. 可执行文件(./a.out,我们直接运行的程序)

结构:

一个ELF文件的组成部分:

①.  ELF头(ELF header) :描述⽂件的主要特性。其位于⽂件的开始位置,它的主要⽬的是定位⽂
件的其他部分
②.  程序头表(Program header table) :列举了所有有效的段(segments)和他们的属性。表⾥
记着每个段的开始的位置和位移(offset)、⻓度,毕竟这些段,都是紧密的放在⼆进制⽂件中,
需要段表的描述信息,才能把他们每个段分割开。
③.  节头表(Section header table) :包含对节(sections)的描述。
④.  节(Section ):ELF⽂件中的基本组成单位,包含了特定类型的数据。ELF⽂件的各种信息和
数据都存储在不同的节中,如代码节存储了可执⾏代码,数据节存储了全局变量和静态数据等。

长啥样:

我这里给大家画了一下;

如何读:
 

# 查看ELF头部信息
readelf -h main

# 查看程序头表PHT
readelf -l main

# 查看节头表SHT
readelf -S main

使用演示:

[tsx@VM-0-10-centos zhangsan]$ readelf -h main
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400560
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6464 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 29
[tsx@VM-0-10-centos zhangsan]$ readelf -l main

Elf file type is EXEC (Executable file)
Entry point 0x400560
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000082c 0x000000000000082c  R E    200000
  LOAD           0x0000000000000e00 0x0000000000600e00 0x0000000000600e00
                 0x000000000000023c 0x0000000000000240  RW     200000
  DYNAMIC        0x0000000000000e18 0x0000000000600e18 0x0000000000600e18
                 0x00000000000001e0 0x00000000000001e0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x0000000000000700 0x0000000000400700 0x0000000000400700
                 0x0000000000000034 0x0000000000000034  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000e00 0x0000000000600e00 0x0000000000600e00
                 0x0000000000000200 0x0000000000000200  R      1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic .got 
[tsx@VM-0-10-centos zhangsan]$ readelf -S main
There are 30 section headers, starting at offset 0x1940:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       0000000000000038  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002d0  000002d0
       00000000000000f0  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           00000000004003c0  000003c0
       0000000000000077  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400438  00000438
       0000000000000014  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400450  00000450
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400470  00000470
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400488  00000488
       0000000000000060  0000000000000018  AI       5    23     8
  [11] .init             PROGBITS         00000000004004e8  000004e8
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000400510  00000510
       0000000000000050  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         0000000000400560  00000560
       0000000000000182  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         00000000004006e4  000006e4
       0000000000000009  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         00000000004006f0  000006f0
       0000000000000010  0000000000000000   A       0     0     8
  [16] .eh_frame_hdr     PROGBITS         0000000000400700  00000700
       0000000000000034  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         0000000000400738  00000738
       00000000000000f4  0000000000000000   A       0     0     8
  [18] .init_array       INIT_ARRAY       0000000000600e00  00000e00
       0000000000000008  0000000000000008  WA       0     0     8
  [19] .fini_array       FINI_ARRAY       0000000000600e08  00000e08
       0000000000000008  0000000000000008  WA       0     0     8
  [20] .jcr              PROGBITS         0000000000600e10  00000e10
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000600e18  00000e18
       00000000000001e0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000038  0000000000000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000601038  00001038
       0000000000000004  0000000000000000  WA       0     0     1
  [25] .bss              NOBITS           000000000060103c  0000103c
       0000000000000004  0000000000000000  WA       0     0     1
  [26] .comment          PROGBITS         0000000000000000  0000103c
       000000000000002d  0000000000000001  MS       0     0     1
  [27] .symtab           SYMTAB           0000000000000000  00001070
       0000000000000600  0000000000000018          28    46     8
  [28] .strtab           STRTAB           0000000000000000  00001670
       00000000000001c4  0000000000000000           0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  00001834
       0000000000000108  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

我们截一张图看看,哇,好多节,有三十个吧

我们再看这个,叫段,有九个

那么段和节什么关系?

我如果啊,要把一个文件加载到内存里,是按照内存页加载的,如果一个节呢,没有占满一个内存页,也占一整个内存页,就会造成很大的内存浪费;所以呀,我们把相同权限的节,放到一个段里,然后进行加载,这样的话,就可以给我们省下来内存;故,段是在加载过程中的概念,实际上在文件中是不存在的

内存页是啥?内存中的内存页,就相当于磁盘里的块,都是读取最小单位

ELF与虚拟地址:

首先呢,先问大家一个问题,我们的ELF文件在编好以后,,有没有“地址”呀?

答案是有的,只不过不是物理地址,而是逻辑地址(可以理解为虚拟地址),我们会记录下每一行代码的偏移量,来作为我们的逻辑地址。

我们的计算机在进行工作的时候,要求采用“平坦模式”进行工作,要求ELF文件对自己的代码和数据进行统一编址

也就是说,其实虚拟地址在我们的程序还没有加载到内存的时候,就已经把可执行程序进⾏统⼀编址了

有什么用呢?那我再问大家一个问题:

进程mm_struct、vm_area_struct在进程刚刚创建的时候,初始化数据从哪⾥来的?
从ELF各个 segment来,每个segment有⾃⼰的起始地址和⾃⼰的⻓度,⽤来初始化内核结构中的[start, end]等范围数据,另外在⽤详细地址,填充⻚表.

所以:虚拟地址机制,不仅os支持,编译器也支持!!!

所以,我们的流程是,我的可执行程序中,本来就有编好的虚拟地址,我要执行程序时,我用我ELF文件中的虚拟地址来给mm_struct赋值,将虚拟地址写入页表,然后在我分配到物理内存的时候,将物理地址填页表。

这一块与动态链接关系比较深,那我们就先讲讲动态链接;

动态链接:

那么我们的动态库啊,是出了名的省空间,不需要把动态库复制到每一个可执行文件中,那么我们来看看我们是如何调用动态库的;

首先我们要知道,动态库是ELF文件形式,而这个形式由于“平坦模式”,我们的ELF文件内部,是存着偏移量的,我们只要根据偏移量,加上虚拟起始地址,我们就可以得到它的虚拟地址,再通过页表,我们可以得知它的物理地址。

而动态链接的关键是,让我们在可执行文件中用到的函数,与库中对应的函数建立关系,然后对我们加载到内存中的程序的库函数调⽤进⾏地址修改,在内存中⼆次完成地址设置;

可是,这样我们修改的不就是代码区了嘛......代码区是只读的啊!!!不可修改

所以,我们就引入了GOT表(全局偏移量表)

动态链接采⽤的做法是在 .data (可执⾏程序或者库⾃⼰)中专⻔预留⼀⽚区域⽤来存放函数
的跳转地址,它也被叫做全局偏移表GOT,表中每⼀项都是本运⾏模块要引⽤的⼀个全局变量或
函数的地址。
因为.data区域是可读写的,所以可以⽀持动态进⾏修改

注意:
①.  由于代码段只读,我们不能直接修改代码段。但有了GOT表,代码便可以被所有进程共享。但在不同进程的地址空间中,各动态库的绝对地址、相对位置都不同。反映到GOT表上,就是每个进程的每个动态库都有独⽴的GOT表,所以进程间不能共享GOT表。

②.  在单个.so下,由于GOT表与 .text 的相对位置是固定的,我们完全可以利⽤CPU的相对寻址来找到GOT表。
③.  在调⽤函数的时候会⾸先查表,然后根据表中的地址来进⾏跳转,这些地址在动态库加载的时候会被修改为真正的地址。
④.  这种⽅式实现的动态链接就被叫做 PIC 地址⽆关代码 。换句话说,我们的动态库不需要做任何修改,被加载到任意内存地址都能够正常运⾏,并且能够被所有进程共享,这也是为什么之前我们给编译器指定-fPIC参数的原因,PIC=相对编址+GOT。

静态链接:

静态链接,就是链接器把程序用到的所有 .o 目标文件、静态库代码,直接复制合并到最终可执行文件里。

过程:

  1. 多个.o文件,把各自的代码段、数据段合并拼接;
  2. 做符号重定位:把所有函数调用、全局变量的引用,填成真实的内存地址;
  3. 最终生成的可执行文件,包含了全部需要的代码,运行时不再依赖外部库。

优点:

运行时不需要依赖外部库,移植方便。

缺点:

多个程序如果都用同一个库,内存里会加载多份库代码,浪费内存;程序体积更大。

总结:

编译链接阶段,库代码直接打包进 exe;运行时不再找库。

尾:

怎么从快八点多写到现在了,好累.......好累.....

中午吃啥?

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2601_96394870/article/details/165700646

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--