在前面的C/C++学习的时候,我们经常接触各种各样的库,除了标准库外还有第三方的库(著名的如boost等)。那么什么是库呢?本期我们就来学习一下。
相关代码已经上传至作者的个人gitee:library/Archive/Makefile · 楼田莉子/Linux学习 - 码云 - 开源中国喜欢请点个赞谢谢
目录
[全局偏移量GOT(global offset table)](#全局偏移量GOT(global offset table))
[查看ELF Header](#查看ELF Header)
[ELF Program Header Table](#ELF Program Header Table)
[ELF Section Header Table](#ELF Section Header Table)
库的介绍
库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:
静态库 .a[Linux]、.lib[windows]
动态库 .so[Linux]、.dll[windows]
比如C++标准库

前面我们也试着制作过类似的库:https://blog.csdn.net/2401_89119815/article/details/155616901?fromshare=blogdetail&sharetype=blogdetail&sharerId=155616901&sharerefer=PC&sharesource=2401_89119815&sharefrom=from_link这里就不做过多的赘述了
运行项目的时候分为以下步骤:

以前的过程中我们都是直接将.c文件编译为可执行文件,这样在小项目是可以的,但是一旦面对多文件的大项目,这种方式就非常乏力了。我们更推荐如图所示的这样将源文件编译问.o文件,然后和库一起链接成可执行程序。
只有链接多个.o文件才会产生关联。
库的本质就是.o文件通过打包变成.a或者.so文件
静态库
静态库(.a) :程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库。
一个可执行程序可能用到许多的库,这些库运行有的是静态库,有的是动态库,而我们的编译默认为动态链接库,只有在该库下找不到动态.so的时候才会采用同名静态库。我们也可以使用 gcc 的 -static 强制设置链接静态库。
bash
# Makefile静态库构建
libmystdio.a:Mystdio.o Mystring.o
@ar -rc $@ $^
@echo "build $^ to $@ ... done"
%.o: %.c
@gcc -c $<
@echo "compling $< to $@ ... done"
.PHONY: clean
clean:
@rm -rf *.a *.o stdc*
@echo "clean ... done"
.PHONY: output
output:
@mkdir -p stdc/include
@mkdir -p stdc/lib
@cp -f *.h stdc/include
@cp -f *.a stdc/lib
@tar -czf stdc.tgz stdc
@echo "output stdc ... done"
下图中,ar -tv libmystdio.a 是一个用于查看静态库内容的命令
ar 是 gnu 归档工具。
rc 表示 (replace and create)。
t: 列出静态库中的文件。
v: verbose 详细信息。

有了静态库我们只需要.h文件看具体的声明就可以知道大概的方法和作用

随后我们直接编译main.c文件形成.o文件

如果我们直接用main.c文件编译有以下几种方式:
bash
# 1、直接指定库文件名称
gcc main.c libmystdio.a -o program
# 2、使用-l 或者-L(推荐)
gcc main.c -L. -lmystdio -o program
# 3、显示指定头文件路径
gcc main.c -I. -L. -lmystdio -o program
# 4、使用绝对路径
# 假设当前目录是 /home/user/Archive
gcc main.c /home/user/Archive/libmystdio.a -o program
#5、有独立路径的情况下
gcc main.c -I/usr/include -L/usr/lib -lmystdio -o program
如果我们使用main.o文件编译除了上面的先编译main.c外
bash
#1、直接指定库文件
gcc main.o libmystdio.a -o program
#2、使用 -l 和 -L 选项(推荐)
gcc main.o -L. -lmystdio -o program
#3、库文件有自己的独立路径
gcc main.o -L/opt/lib -lmystdio -o program
#4、混合编译------库在不同路径,同时链接多个库
gcc main.o -L. -lmystdio -L/usr/lib -lm -o program
成功编译链接并运行

"-L": 指定库路径
"-I": 指定头文件搜索路径
"-l": 指定库名
测试目标文件生成后,静态库删掉,程序照样可以运行
关于 `-static` 选项,是强制要求所有库必须有静态库,且全部静态链接到我的可执行程序中
库文件名称和引入库的名称:去掉前缀 `lib`,去掉后缀 `.so`、`.a`,如:`libc.so` -> `c`
可以使用相对路径编译

动态库
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)。
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
bash
# Makefile
libmystdio.so: Mystdio.o Mystring.o
gcc -o $@ $^ -shared
%.o: %.c
gcc -fPIC -c $<
.PHONY: clean
clean:
@rm -rf *.so *.o stdc*
@echo "clean ... done"
.PHONY: output
output:
@mkdir -p stdc/include
@mkdir -p stdc/lib
@cp -f *.h stdc/include
@cp -f *.so stdc/lib
@tar -czf stdc.tgz stdc
@echo "output stdc ... done"
`-shared`:表示生成共享库格式
`-fPIC`:产生位置无关码 (position independent code)
库名规则:`libxxx.so`
成功编译

但是我们运行main可执行文件就会发现如下报错:

我们发现是因为运行的时候加载器和OS没有找到动态库。

拷贝 .so 文件到系统共享库路径下,一般指 /usr/lib、/usr/local/lib、/lib64 或者开篇指明的库路径等 (不推荐随便改)

向系统共享库路径下建立同名软连接

不要忘了解除软链接

更改环境变量:LD_LIBRARY_PATH (开发建议)

ldconfig 方案:配置 /etc/ld.so.conf.d/,然后执行 ldconfig 更新(因为作者不是root所以这里就不演示了)
东静态库均存在的时候优先使用动态库
如果要么要用-static,必须存在静态库。如果只提供静态库,即使用动态链接也只能静态链接。
只有动态库才需要运行时搜索库
ELF文件
在前面的练习中,我们都知道一种叫.o文件的东西。它们被称为目标文件。如果我们修改了一个源文件,那么只需要单独编译这一个文件,而不需要重新编译整个工程。目标文件是一个二进制文件,文件的格式是 ELF,是对二进制代码的一种封装,但是请注意,它们并不是随意混在一起的二进制文件。

以下四种文件都是 ELF 文件:
可重定位文件(Relocatable File) :即 xxx.o 文件。包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
可执行文件(Executable File) :即可执行程序。
共享目标文件(Shared Object File) :即 xxx.so 文件。
内核转储(core dumps):存放当前进程的执行上下文,用于 dump 信号触发。
一个 ELF 文件由以下四部分组成:
-
ELF 头(ELF header):描述文件的主要特性。其位于文件的开始位置,它的主要目的是定位文件的其他部分。
-
程序头表(Program header table):列举了所有有效的段(segments)和他们的属性。表里记录着每个段的开始位置和偏移(offset)、长度,毕竟这些段都是紧密地放在二进制文件中,需要段表的描述信息才能把它们每个段分割开。
-
节头表(Section header table):包含对节(sections)的描述。
-
节(Section):ELF 文件中的基本组成单位,包含了特定类型的数据。ELF 文件的各种信息都存储在不同的节中,如代码节存储了可执行代码,数据节存储了全局变量和静态数据等。
最常见的节:
代码节(.text) :用于保存机器指令,是程序的主要执行部分。
数据节(.data) :保存已初始化的全局变量和局部静态变量。
bss 节(.bss) :保存未初始化的全局变量和局部静态变量(在程序加载时初始化为零)。
只读数据节(.rodata):保存只读数据,如字符串常量等。

ELF从形成到加载轮廓
ELF形成可执行
step-1:将多份 C/C++ 源代码,翻译成为目标 .o 文件
step-2:将多份 .o 文件 section 进行合并

ELF可执行文件加载
一个ELF会有多种不同的Section,在加载到内存的时候,也会进行Section合并,形成segment
合并原则:相同属性,比如:可读,可写,可执行,需要加载时申请空间等。
这样,即便是不同的Section,在加载到内存中,可能会以segment的形式,加载到一起
很显然,这个合并工作也已经在形成ELF的时候,合并方式已经确定了,具体合并原则被记录在了ELF的程序头表(Program header table) 中
为什么要将section合并成为segment?
Section合并的主要原因是为了减少页面碎片,提高内存使用效率。如果不进行合并,假设页面大小为4096字节(内存块基本大小,加载,管理的基本单位),如果.text部分为4097字节,.init部分为512字节,那么它们将占用3个页面,而合并后,它们只需2个页面。
此外,操作系统在加载程序时,会将具有相同属性的section合并成一个大的segment,这样就可以实现不同的访问权限,从而优化内存管理和权限访问控制。
对于程序头表和节头表又有什么用呢?实际上,ELF 文件提供了两个不同的视图/视角来帮助我们理解这两个部分:
"链接视图 (Linking view)"--- 对应节头表 (Section header table)
文件结构的粒度更细,将文件按功能模块的差异进行划分。静态链接分析时一般关注的是链接视图,能够理解 ELF 文件中包含的各个部分的信息。
为了空间布局上的效率,在链接目标文件时,链接器会把很多节(section)合并、规整成可执行的段(segment)、可读写的段、只读段等。合并后,空间利用率就提高了,否则每一段很小的话,未来物理内存页浪费会很大(物理内存页分配一般按整块给,比如 4KB),所以链接器趁链接时就把小块们合并了。
"执行视图 (Execution view)" --- 对应程序头表 (Program header table)
告诉操作系统如何加载可执行文件,完成进程内存的初始化。一个可执行程序的格式中,必须有 program header table。
总结而言就是一个在链接时作用,一个在运行加载时作用。

从链接视图来看:
命令 readelf -S hello.o 可以帮助查看ELF文件的节头表。
text节 :保存了程序代码指令的代码节。
data节 :保存了初始化的全局变量和局部静态变量等数据。
rodata节 :保存了只读的数据,如C语言代码中的字符串。由于.rodata节是只读的,所以只能存在于一个可执行文件的只读段中。因此,只能在text段(而不是data段)中找到.rodata节。
BSS节 :为未初始化的全局变量和局部静态变量预留位置。
symtab节 :符号表,记录源码中函数名、变量名与代码的对应关系。
got.plt节 (全局偏移表-过程链接表):.got节保存全局偏移表。.got节和.plt节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。对于GOT的理解,我们后面会说明。
使用 readelf 命令查看.so文件可以看到该节。
从执行视图来看 :
告诉操作系统哪些模块可以被加载进内存。
加载进内存后,哪些分段是可读可写,哪些分段是只读,哪些分段是可执行的。
链接与加载
静态链接
无论是自己的.o文件,还是静态库中的.o文件,本质上都是将.o文件进行链接的过程。
因此,研究静态链接,本质上就是研究.o文件是如何链接的。
cpp
//hello.c
#include<stdio.h>
void run();
int main()
{
printf("hello world!\n");
run();
return 0;
}
//code.c
#include<stdio.h>
void run()
{
printf("running...\n");
}

汇编层面的过程:
objdump -d code.o 用于反汇编目标文件(object file)中的可执行代码段,显示机器指令对应的汇编代码。


静态链接就是将不同的.o文件合并
静态库的发生过程在链接之前就全部完成。
所以链接其实就是将编译之后的所有目标文件连同用到的一些静态库、运行时库组合,拼装成一个独立的可执行文件。其中就包括我们之前提到的地址修正,当所有模块组合在一起之后,链接器会根据我们的.o文件或者静态库中的重定位表找到那些需要被重定位的函数和全局变量,从而修正它们的地址。这就是静态链接的过程。
链接过程中会涉及到对.o中外部符号进行地址重定位。

ELF加载与进程地址空间
到这里的时候我们思考以下问题:
- 创建一个进程,先创建内核进程相关的数据结构?还是先加载ELF格式的二进制文件?
先创建内核进程相关的数据结构(PCB),再加载ELF格式的二进制文件。
- 程序没有被加载到内存,程序自己有没有地址??什么地址??
在加载之前程序有自己的"地址"。在这个情况下是平坦模式,平坦模式对我们编写的"每一行"代码都进行编址。原则上从0号开始,结束位置为系统位数的地址全部为1的时候(如32位系统结束位置为32位上全为1的时候)
这里说的是虚拟地址空间,不仅对OS有效,对编译器也有效。

段地址+偏移量=逻辑地址


动态链接与动态库加载
进程如何看到动态库

进程如何共享动态库
动态库的是共享库,本质上也是代码和数据,因此如果多个进程需要这个资源只需要在内存中加载一份就可以了。
但是我们的OS怎么确定动态库被加载了呢?

动态链接
在项目实践中动态库的使用远远高于静态库
动态链接的程序在加载的时候先找对应的动态库
静态链接最大的问题在于生成的文件体积大,并且相当耗费内存资源 。随着软件复杂度的提升,我们的操作系统也越来越臃肿,不同的软件就有可能都包含了相同的功能和代码,显然会浪费大量的硬盘空间。
动态链接的优势在此就体现出来了,我们可以将需要共享的代码单独提取出来,保存成一个独立的动态链接库,等到程序运行的时候再将它们加载到内存,这样不但可以节省空间,因为同一个模块在内存中只需要保留一份副本,可以被不同的进程所共享。
那么动态链接到底是如何工作的?
动态链接实际上将链接的整个过程推迟到了程序加载的时候。如果我们去运行一个程序,操作系统会首先将程序的数据代码连同它用到的一系列动态库先加载到内存,其中每个动态库的加载地址都是不固定的,操作系统会根据当前地址空间的使用情况为它们动态分配一段内存。
动态库内部是没有main函数的,有的是一群方法的集合,每个方法都有自己的地址。
当动态库被加载到内存以后,一旦它的内存地址被确定,我们就可以去修正动态库中的那些函数跳转地址了。
动态链接的程序执行流程
在C/C++程序中,当程序开始执行时,它首先并不会直接跳转到main函数。实际上,程序的入口点是_start,这是一个由C运行时库(通常是glibc)或链接器(如ld)提供的特殊函数。
在_start函数中,会执行一系列初始化操作,这些操作包括:
-
设置堆栈:为程序创建一个初始的堆栈环境。
-
初始化数据段:将程序的数据段(如全局变量和静态变量)从初始化数据段复制到相应的内存位置,并清零未初始化的数据段。
-
动态链接:这是关键的一步,_start函数会调用动态链接器的代码来解析和加载程序所依赖的动态库(shared libraries)。动态链接器会处理所有的符号解析和重定位,确保程序中的函数调用和变量访问能够正确地映射到动态库中的实际地址。
-
调用__libc_start_main:一旦动态链接完成,_start函数会调用__libc_start_main(这是glibc提供的一个函数)。__libc_start_main函数负责执行一些额外的初始化工作,比如设置信号处理函数、初始化线程库(如果使用了线程)等。
-
调用main函数:最后,__libc_start_main函数会调用程序的main函数,此时程序的执行控制权才正式交给用户编写的代码。
-
处理main函数的返回值:当main函数返回时,__libc_start_main会负责处理这个返回值,并最终调用_exit函数来终止程序。
以上流程通常情况下是不需要在乎的,但是了解以上流程有利于我们理解并调试程序
动态库的相对地址
动态库为了随时进行加载,为了支持并映射到任意进程的任意位置,对动态库中的方法,统一采用相对编址的方案进行编址(其实可执行程序也一样,都要遵守平坦模式,只不过exe是直接加载的)。
bash
# ubuntu下查看任意⼀个库的反汇编
objdump -S /lib/x86_64-linux-gnu/libc-2.31.so | less
注意:
动态库也是一个文件,要访问也是要被先加载,要加载也是要被打开的
让我们的进程找到动态库的本质:也是文件操作,不过我们访问库函数,通过虚拟地址进行跳转访问的,所以需要把动态库映射到进程的地址空间中
那么我们的程序是怎么跟库映射起来的呢?

我们的程序如何进行库函数的调用呢?
注意:
库已经被我们映射到了当前进程的地址空间中
库的虚拟起始地址我们也已经知道了
库中每一个方法的偏移量我们也知道
所以:访问库中任意方法,只需要知道库的起始虚拟地址 + 方法偏移量即可定位库中的方法
而且:整个调用过程,是从代码区跳转到共享区,调用完毕再返回到代码区,整个过程完全在进程地址空间中进行的。

动态链接的过程就是在加载的时候,修改代码中call后面的0000 ... 0000地址。而这个地址就是动态库中的起始虚拟地址+方法在库中的偏移量。
地址重定位,就是修改call后面的内容,函数地址。但是call不是代码区吗?代码区是只读的啊,那实际上是怎么读的呢?
全局偏移量GOT(global offset table)
动态链接采用的做法是在 .data (可执行程序或者库自己)中专门预留一片区域用来存放函数的跳转地址,它也被叫做全局偏移表GOT,表中每一项都是本运行模块要引用的一个全局变量或函数的地址。
因为.data区域是可读写的,所以可以支持动态进行修改

-
由于代码段只读,我们不能直接修改代码段。但有了GOT表,代码便可以被所有进程共享。但在不同进程的地址空间中,各动态库的绝对地址、相对位置都不同。反映到GOT表上,就是每个进程的每个动态库都有独立的GOT表,所以进程间不能共享GOT表。
-
在单个.so下,由于GOT表与 .text 的相对位置是固定的,我们完全可以利用CPU的相对寻址来找到GOT表。
-
在调用函数的时候会首先查表,然后根据表中的地址来进行跳转,这些地址在动态库加载的时候会被修改为真正的地址。
-
这种方式实现的动态链接就被叫做 PIC 地址无关代码 。换句话说,我们的动态库不需要做任何修改,被加载到任意内存地址都能够正常运行,并且能够被所有进程共享,这也是为什么之前我们给编译器指定 -fPIC 参数的原因,PIC = 相对编址 + GOT。
库的依赖
注意:
不仅仅有可执行程序调用库
库也会调用其他库!!库之间是有依赖的,如何做到库和库之间互相调用也是与地址无关的呢??
库中也有.GOT,和可执行程序一样!这也就是为什么大家都是ELF的格式!
由于GOT表中的映射地址会在运行时去修改,我们可以通过gdb调试去观察GOT表的地址变化。在这里我们只用知道原理即可,有兴趣的同学可以参考:使用gdb调试GOT
由于动态链接在程序加载的时候需要对大量函数进行重定位,这一步显然是非常耗时的。为了进一步降低开销,OS还做了一些其他的优化,比如延迟绑定,或者也叫PLT(过程连接表(Procedure Linkage Table))。与其在程序一开始就对所有函数进行重定位,不如将这个过程推迟到函数第一次被调用的时候,因为绝大多数动态库中的函数可能在程序运行期间一次都不会被使用到。
思路是:GOT中的跳转地址默认会指向一段辅助代码,它也被叫做桩代码/stub。在我们第一次调用函数的时候,这段代码会负责查询真正函数的跳转地址,并且去更新GOT表。当我们再次调用函数的时候,就会直接跳转到动态库中真正的函数实现。
总结
静态链接的出现,提高了程序的模块化水平。对于一个大的项目,不同的人可以独立地测试和开发自己的模块。通过静态链接,生成最终的可执行文件。
我们知道静态链接会将编译产生的所有目标文件,和用到的各种库合并成一个独立的可执行文件,其中我们会去修正模块间函数的跳转地址,也被叫做编译重定位(也叫做静态重定位)。
而动态链接实际上将链接的整个过程推迟到了程序加载的时候。比如我们去运行一个程序,操作系统会首先将程序的数据代码连同它用到的一系列动态库先加载到内存,其中每个动态库的加载地址都是不固定的,但是无论加载到什么地方,都要映射到进程对应的地址空间,然后通过.GOT方式进行调用(运行重定位,也叫做动态地址重定位)。
动态链接器的介绍
动态链接器(如ld-linux.so)负责在程序运行时加载动态库。
当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。
环境变量和配置文件:
Linux系统通过环境变量(如LD_LIBRARY_PATH)和配置文件(如/etc/ld.so.conf及其子配置文件)来指定动态库的搜索路径。
这些路径会被动态链接器在加载动态库时搜索。
缓存文件:
为了提高动态库的加载效率,Linux系统会维护一个名为/etc/ld.so.cache的缓存文件。
该文件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会首先搜索这个缓存文件。
ELF信息查询
查看ELF Header
-h或--file-header:显示ELF文件的文件头信息。文件头包含了ELF文件的基本信息,比如文件类型、机器类型、版本、入口点地址、程序头表和节头表的位置和大小等

cpp
// 内核中关于ELF Header相关的数据结构
// 没错,操作系统自己必须能够识别特定格式的可执行程序:/linux/include/elf.h
typedef struct elf32_hdr {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry; /* Entry point */
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
typedef struct elf64_hdr {
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
ELF Program Header Table
-l或--program-headers:显示ELF文件的程序头部(也称为段头)信息。可执行文件在内存中的布局和加载过程非常重要。

cpp
/*
* PhysAddr: 现代OS,可以不考虑了
* LOAD: 不是所有的section都要被加载到内存,有的是与加载链接有关的字段,但是有LOAD的就是将来要加载的部分
*/
// 内核中关于ELF Program Header相关的数据结构
typedef struct elf32_phdr {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
typedef struct elf64_phdr {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment, file & memory */
} Elf64_Phdr;
ELF Section Header Table
-S 或 --section-headers:显示 ELF 文件的节头信息。节头描述了 ELF 文件中各个节的起始地址、大小、标志等信息。

cpp
/*
* .got 通常与全局偏移表(Global Offset Table,简称 GOT),这块需要简化讲解。
*/
// 内核中关于ELF Section Header相关的数据结构
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
typedef struct elf64_shdr {
Elf64_Word sh_name; /* Section name, index in string tbl */
Elf64_Word sh_type; /* Type of section */
Elf64_Xword sh_flags; /* Miscellaneous section attributes */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Size of section in bytes */
Elf64_Word sh_link; /* Index of another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
查看具体的sections信息
bash
#命令在下面,内容稍多所以以这种形式展示
[loukou-ruizi@iZ7xv2ky2xqqjicthpjcwzZ prochar]$ objdump -S process
process: file format elf64-x86-64
Disassembly of section .init:
0000000000400600 <_init>:
400600: 48 83 ec 08 sub $0x8,%rsp
400604: 48 8b 05 ed 09 20 00 mov 0x2009ed(%rip),%rax # 600ff8 <__gmon_start__>
40060b: 48 85 c0 test %rax,%rax
40060e: 74 05 je 400615 <_init+0x15>
400610: e8 cb 00 00 00 callq 4006e0 <.plt.got>
400615: 48 83 c4 08 add $0x8,%rsp
400619: c3 retq
Disassembly of section .plt:
0000000000400620 <.plt>:
400620: ff 35 e2 09 20 00 pushq 0x2009e2(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
400626: ff 25 e4 09 20 00 jmpq *0x2009e4(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
40062c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000400630 <putchar@plt>:
400630: ff 25 e2 09 20 00 jmpq *0x2009e2(%rip) # 601018 <putchar@GLIBC_2.2.5>
400636: 68 00 00 00 00 pushq $0x0
40063b: e9 e0 ff ff ff jmpq 400620 <.plt>
0000000000400640 <puts@plt>:
400640: ff 25 da 09 20 00 jmpq *0x2009da(%rip) # 601020 <puts@GLIBC_2.2.5>
400646: 68 01 00 00 00 pushq $0x1
40064b: e9 d0 ff ff ff jmpq 400620 <.plt>
0000000000400650 <strlen@plt>:
400650: ff 25 d2 09 20 00 jmpq *0x2009d2(%rip) # 601028 <strlen@GLIBC_2.2.5>
400656: 68 02 00 00 00 pushq $0x2
40065b: e9 c0 ff ff ff jmpq 400620 <.plt>
0000000000400660 <printf@plt>:
400660: ff 25 ca 09 20 00 jmpq *0x2009ca(%rip) # 601030 <printf@GLIBC_2.2.5>
400666: 68 03 00 00 00 pushq $0x3
40066b: e9 b0 ff ff ff jmpq 400620 <.plt>
0000000000400670 <memset@plt>:
400670: ff 25 c2 09 20 00 jmpq *0x2009c2(%rip) # 601038 <memset@GLIBC_2.2.5>
400676: 68 04 00 00 00 pushq $0x4
40067b: e9 a0 ff ff ff jmpq 400620 <.plt>
0000000000400680 <__libc_start_main@plt>:
400680: ff 25 ba 09 20 00 jmpq *0x2009ba(%rip) # 601040 <__libc_start_main@GLIBC_2.2.5>
400686: 68 05 00 00 00 pushq $0x5
40068b: e9 90 ff ff ff jmpq 400620 <.plt>
0000000000400690 <srand@plt>:
400690: ff 25 b2 09 20 00 jmpq *0x2009b2(%rip) # 601048 <srand@GLIBC_2.2.5>
400696: 68 06 00 00 00 pushq $0x6
40069b: e9 80 ff ff ff jmpq 400620 <.plt>
00000000004006a0 <time@plt>:
4006a0: ff 25 aa 09 20 00 jmpq *0x2009aa(%rip) # 601050 <time@GLIBC_2.2.5>
4006a6: 68 07 00 00 00 pushq $0x7
4006ab: e9 70 ff ff ff jmpq 400620 <.plt>
00000000004006b0 <fflush@plt>:
4006b0: ff 25 a2 09 20 00 jmpq *0x2009a2(%rip) # 601058 <fflush@GLIBC_2.2.5>
4006b6: 68 08 00 00 00 pushq $0x8
4006bb: e9 60 ff ff ff jmpq 400620 <.plt>
00000000004006c0 <rand@plt>:
4006c0: ff 25 9a 09 20 00 jmpq *0x20099a(%rip) # 601060 <rand@GLIBC_2.2.5>
4006c6: 68 09 00 00 00 pushq $0x9
4006cb: e9 50 ff ff ff jmpq 400620 <.plt>
00000000004006d0 <usleep@plt>:
4006d0: ff 25 92 09 20 00 jmpq *0x200992(%rip) # 601068 <usleep@GLIBC_2.2.5>
4006d6: 68 0a 00 00 00 pushq $0xa
4006db: e9 40 ff ff ff jmpq 400620 <.plt>
Disassembly of section .plt.got:
00000000004006e0 <.plt.got>:
4006e0: ff 25 12 09 20 00 jmpq *0x200912(%rip) # 600ff8 <__gmon_start__>
4006e6: 66 90 xchg %ax,%ax
Disassembly of section .text:
00000000004006f0 <_start>:
4006f0: 31 ed xor %ebp,%ebp
4006f2: 49 89 d1 mov %rdx,%r9
4006f5: 5e pop %rsi
4006f6: 48 89 e2 mov %rsp,%rdx
4006f9: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
4006fd: 50 push %rax
4006fe: 54 push %rsp
4006ff: 49 c7 c0 30 0b 40 00 mov $0x400b30,%r8
400706: 48 c7 c1 c0 0a 40 00 mov $0x400ac0,%rcx
40070d: 48 c7 c7 f6 08 40 00 mov $0x4008f6,%rdi
400714: e8 67 ff ff ff callq 400680 <__libc_start_main@plt>
400719: f4 hlt
40071a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
0000000000400720 <deregister_tm_clones>:
400720: b8 97 10 60 00 mov $0x601097,%eax
400725: 55 push %rbp
400726: 48 2d 90 10 60 00 sub $0x601090,%rax
40072c: 48 83 f8 0e cmp $0xe,%rax
400730: 48 89 e5 mov %rsp,%rbp
400733: 77 02 ja 400737 <deregister_tm_clones+0x17>
400735: 5d pop %rbp
400736: c3 retq
400737: b8 00 00 00 00 mov $0x0,%eax
40073c: 48 85 c0 test %rax,%rax
40073f: 74 f4 je 400735 <deregister_tm_clones+0x15>
400741: 5d pop %rbp
400742: bf 90 10 60 00 mov $0x601090,%edi
400747: ff e0 jmpq *%rax
400749: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000400750 <register_tm_clones>:
400750: b8 90 10 60 00 mov $0x601090,%eax
400755: 55 push %rbp
400756: 48 2d 90 10 60 00 sub $0x601090,%rax
40075c: 48 c1 f8 03 sar $0x3,%rax
400760: 48 89 e5 mov %rsp,%rbp
400763: 48 89 c2 mov %rax,%rdx
400766: 48 c1 ea 3f shr $0x3f,%rdx
40076a: 48 01 d0 add %rdx,%rax
40076d: 48 d1 f8 sar %rax
400770: 75 02 jne 400774 <register_tm_clones+0x24>
400772: 5d pop %rbp
400773: c3 retq
400774: ba 00 00 00 00 mov $0x0,%edx
400779: 48 85 d2 test %rdx,%rdx
40077c: 74 f4 je 400772 <register_tm_clones+0x22>
40077e: 5d pop %rbp
40077f: 48 89 c6 mov %rax,%rsi
400782: bf 90 10 60 00 mov $0x601090,%edi
400787: ff e2 jmpq *%rdx
400789: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000400790 <__do_global_dtors_aux>:
400790: 80 3d 01 09 20 00 00 cmpb $0x0,0x200901(%rip) # 601098 <completed.6355>
400797: 75 11 jne 4007aa <__do_global_dtors_aux+0x1a>
400799: 55 push %rbp
40079a: 48 89 e5 mov %rsp,%rbp
40079d: e8 7e ff ff ff callq 400720 <deregister_tm_clones>
4007a2: 5d pop %rbp
4007a3: c6 05 ee 08 20 00 01 movb $0x1,0x2008ee(%rip) # 601098 <completed.6355>
4007aa: f3 c3 repz retq
4007ac: 0f 1f 40 00 nopl 0x0(%rax)
00000000004007b0 <frame_dummy>:
4007b0: 48 83 3d 68 06 20 00 cmpq $0x0,0x200668(%rip) # 600e20 <__JCR_END__>
4007b7: 00
4007b8: 74 1e je 4007d8 <frame_dummy+0x28>
4007ba: b8 00 00 00 00 mov $0x0,%eax
4007bf: 48 85 c0 test %rax,%rax
4007c2: 74 14 je 4007d8 <frame_dummy+0x28>
4007c4: 55 push %rbp
4007c5: bf 20 0e 60 00 mov $0x600e20,%edi
4007ca: 48 89 e5 mov %rsp,%rbp
4007cd: ff d0 callq *%rax
4007cf: 5d pop %rbp
4007d0: e9 7b ff ff ff jmpq 400750 <register_tm_clones>
4007d5: 0f 1f 00 nopl (%rax)
4007d8: e9 73 ff ff ff jmpq 400750 <register_tm_clones>
00000000004007dd <speedfloat>:
4007dd: 55 push %rbp
4007de: 48 89 e5 mov %rsp,%rbp
4007e1: 48 83 ec 30 sub $0x30,%rsp
4007e5: f2 0f 11 45 e8 movsd %xmm0,-0x18(%rbp)
4007ea: f2 0f 11 4d e0 movsd %xmm1,-0x20(%rbp)
4007ef: f2 0f 10 45 e0 movsd -0x20(%rbp),%xmm0
4007f4: f2 0f 2c c0 cvttsd2si %xmm0,%eax
4007f8: 89 45 fc mov %eax,-0x4(%rbp)
4007fb: e8 c0 fe ff ff callq 4006c0 <rand@plt>
400800: 99 cltd
400801: f7 7d fc idivl -0x4(%rbp)
400804: 89 d0 mov %edx,%eax
400806: f2 0f 2a c0 cvtsi2sd %eax,%xmm0
40080a: 66 0f 28 c8 movapd %xmm0,%xmm1
40080e: f2 0f 58 4d e8 addsd -0x18(%rbp),%xmm1
400813: f2 0f 2a 45 fc cvtsi2sdl -0x4(%rbp),%xmm0
400818: f2 0f 58 45 e0 addsd -0x20(%rbp),%xmm0
40081d: f2 0f 58 c1 addsd %xmm1,%xmm0
400821: f2 0f 11 45 d8 movsd %xmm0,-0x28(%rbp)
400826: 48 8b 45 d8 mov -0x28(%rbp),%rax
40082a: 48 89 45 d8 mov %rax,-0x28(%rbp)
40082e: f2 0f 10 45 d8 movsd -0x28(%rbp),%xmm0
400833: c9 leaveq
400834: c3 retq
0000000000400835 <download>:
400835: 55 push %rbp
400836: 48 89 e5 mov %rsp,%rbp
400839: 48 83 ec 30 sub $0x30,%rsp
40083d: 89 7d ec mov %edi,-0x14(%rbp)
400840: 48 89 75 e0 mov %rsi,-0x20(%rbp)
400844: bf 00 00 00 00 mov $0x0,%edi
400849: e8 52 fe ff ff callq 4006a0 <time@plt>
40084e: 89 c7 mov %eax,%edi
400850: e8 3b fe ff ff callq 400690 <srand@plt>
400855: b8 00 00 00 00 mov $0x0,%eax
40085a: 48 89 45 f8 mov %rax,-0x8(%rbp)
40085e: f2 0f 2a 45 ec cvtsi2sdl -0x14(%rbp),%xmm0
400863: f2 0f 10 4d f8 movsd -0x8(%rbp),%xmm1
400868: 66 0f 2e c8 ucomisd %xmm0,%xmm1
40086c: 76 24 jbe 400892 <download+0x5d>
40086e: f2 0f 2a 45 ec cvtsi2sdl -0x14(%rbp),%xmm0
400873: f2 0f 11 45 f8 movsd %xmm0,-0x8(%rbp)
400878: f2 0f 2a 45 ec cvtsi2sdl -0x14(%rbp),%xmm0
40087d: 48 8b 45 f8 mov -0x8(%rbp),%rax
400881: 48 8b 55 e0 mov -0x20(%rbp),%rdx
400885: 48 89 45 d8 mov %rax,-0x28(%rbp)
400889: f2 0f 10 4d d8 movsd -0x28(%rbp),%xmm1
40088e: ff d2 callq *%rdx
400890: eb 62 jmp 4008f4 <download+0xbf>
400892: f2 0f 2a 45 ec cvtsi2sdl -0x14(%rbp),%xmm0
400897: 48 8b 45 f8 mov -0x8(%rbp),%rax
40089b: 48 8b 55 e0 mov -0x20(%rbp),%rdx
40089f: 48 89 45 d8 mov %rax,-0x28(%rbp)
4008a3: f2 0f 10 4d d8 movsd -0x28(%rbp),%xmm1
4008a8: ff d2 callq *%rdx
4008aa: 48 8b 05 cf 07 20 00 mov 0x2007cf(%rip),%rax # 601080 <speed>
4008b1: 48 ba cd cc cc cc cc movabs $0x403e4ccccccccccd,%rdx
4008b8: 4c 3e 40
4008bb: 48 89 55 d8 mov %rdx,-0x28(%rbp)
4008bf: f2 0f 10 4d d8 movsd -0x28(%rbp),%xmm1
4008c4: 48 89 45 d8 mov %rax,-0x28(%rbp)
4008c8: f2 0f 10 45 d8 movsd -0x28(%rbp),%xmm0
4008cd: e8 0b ff ff ff callq 4007dd <speedfloat>
4008d2: f2 0f 10 4d f8 movsd -0x8(%rbp),%xmm1
4008d7: f2 0f 58 c1 addsd %xmm1,%xmm0
4008db: f2 0f 11 45 f8 movsd %xmm0,-0x8(%rbp)
4008e0: bf 30 75 00 00 mov $0x7530,%edi
4008e5: b8 00 00 00 00 mov $0x0,%eax
4008ea: e8 e1 fd ff ff callq 4006d0 <usleep@plt>
4008ef: e9 6a ff ff ff jmpq 40085e <download+0x29>
4008f4: c9 leaveq
4008f5: c3 retq
00000000004008f6 <main>:
4008f6: 55 push %rbp
4008f7: 48 89 e5 mov %rsp,%rbp
4008fa: bf 50 0b 40 00 mov $0x400b50,%edi
4008ff: e8 3c fd ff ff callq 400640 <puts@plt>
400904: be 65 09 40 00 mov $0x400965,%esi
400909: bf e8 03 00 00 mov $0x3e8,%edi
40090e: e8 22 ff ff ff callq 400835 <download>
400913: bf 61 0b 40 00 mov $0x400b61,%edi
400918: e8 23 fd ff ff callq 400640 <puts@plt>
40091d: be 65 09 40 00 mov $0x400965,%esi
400922: bf 14 00 00 00 mov $0x14,%edi
400927: e8 09 ff ff ff callq 400835 <download>
40092c: bf 70 0b 40 00 mov $0x400b70,%edi
400931: e8 0a fd ff ff callq 400640 <puts@plt>
400936: be 65 09 40 00 mov $0x400965,%esi
40093b: bf 00 04 00 00 mov $0x400,%edi
400940: e8 f0 fe ff ff callq 400835 <download>
400945: bf 7e 0b 40 00 mov $0x400b7e,%edi
40094a: e8 f1 fc ff ff callq 400640 <puts@plt>
40094f: be 65 09 40 00 mov $0x400965,%esi
400954: bf 38 00 00 00 mov $0x38,%edi
400959: e8 d7 fe ff ff callq 400835 <download>
40095e: b8 00 00 00 00 mov $0x0,%eax
400963: 5d pop %rbp
400964: c3 retq
0000000000400965 <flashprocess>:
400965: 55 push %rbp
400966: 48 89 e5 mov %rsp,%rbp
400969: 53 push %rbx
40096a: 48 81 ec a8 00 00 00 sub $0xa8,%rsp
400971: f2 0f 11 85 68 ff ff movsd %xmm0,-0x98(%rbp)
400978: ff
400979: f2 0f 11 8d 60 ff ff movsd %xmm1,-0xa0(%rbp)
400980: ff
400981: f2 0f 10 85 60 ff ff movsd -0xa0(%rbp),%xmm0
400988: ff
400989: 66 0f 2e 85 68 ff ff ucomisd -0x98(%rbp),%xmm0
400990: ff
400991: 76 0e jbe 4009a1 <flashprocess+0x3c>
400993: 48 8b 85 68 ff ff ff mov -0x98(%rbp),%rax
40099a: 48 89 85 60 ff ff ff mov %rax,-0xa0(%rbp)
4009a1: f2 0f 10 85 60 ff ff movsd -0xa0(%rbp),%xmm0
4009a8: ff
4009a9: f2 0f 5e 85 68 ff ff divsd -0x98(%rbp),%xmm0
4009b0: ff
4009b1: f2 0f 10 0d f7 01 00 movsd 0x1f7(%rip),%xmm1 # 400bb0 <__dso_handle+0x68>
4009b8: 00
4009b9: f2 0f 59 c1 mulsd %xmm1,%xmm0
4009bd: f2 0f 11 45 e0 movsd %xmm0,-0x20(%rbp)
4009c2: f2 0f 10 45 e0 movsd -0x20(%rbp),%xmm0
4009c7: f2 0f 2c c0 cvttsd2si %xmm0,%eax
4009cb: 89 45 dc mov %eax,-0x24(%rbp)
4009ce: 48 8d 85 70 ff ff ff lea -0x90(%rbp),%rax
4009d5: ba 65 00 00 00 mov $0x65,%edx
4009da: be 00 00 00 00 mov $0x0,%esi
4009df: 48 89 c7 mov %rax,%rdi
4009e2: e8 89 fc ff ff callq 400670 <memset@plt>
4009e7: c7 45 ec 00 00 00 00 movl $0x0,-0x14(%rbp)
4009ee: eb 11 jmp 400a01 <flashprocess+0x9c>
4009f0: 8b 45 ec mov -0x14(%rbp),%eax
4009f3: 48 98 cltq
4009f5: c6 84 05 70 ff ff ff movb $0x3d,-0x90(%rbp,%rax,1)
4009fc: 3d
4009fd: 83 45 ec 01 addl $0x1,-0x14(%rbp)
400a01: 8b 45 ec mov -0x14(%rbp),%eax
400a04: 3b 45 dc cmp -0x24(%rbp),%eax
400a07: 7c e7 jl 4009f0 <flashprocess+0x8b>
400a09: 48 8b 0d 78 06 20 00 mov 0x200678(%rip),%rcx # 601088 <lable.2458>
400a10: 8b 05 86 06 20 00 mov 0x200686(%rip),%eax # 60109c <index.2459>
400a16: 8d 50 01 lea 0x1(%rax),%edx
400a19: 89 15 7d 06 20 00 mov %edx,0x20067d(%rip) # 60109c <index.2459>
400a1f: 48 98 cltq
400a21: 48 01 c8 add %rcx,%rax
400a24: 0f b6 00 movzbl (%rax),%eax
400a27: 0f be d0 movsbl %al,%edx
400a2a: 48 8b 45 e0 mov -0x20(%rbp),%rax
400a2e: 48 8d 8d 70 ff ff ff lea -0x90(%rbp),%rcx
400a35: 48 89 85 58 ff ff ff mov %rax,-0xa8(%rbp)
400a3c: f2 0f 10 85 58 ff ff movsd -0xa8(%rbp),%xmm0
400a43: ff
400a44: 48 89 ce mov %rcx,%rsi
400a47: bf 90 0b 40 00 mov $0x400b90,%edi
400a4c: b8 01 00 00 00 mov $0x1,%eax
400a51: e8 0a fc ff ff callq 400660 <printf@plt>
400a56: 8b 05 40 06 20 00 mov 0x200640(%rip),%eax # 60109c <index.2459>
400a5c: 48 63 d8 movslq %eax,%rbx
400a5f: 48 8b 05 22 06 20 00 mov 0x200622(%rip),%rax # 601088 <lable.2458>
400a66: 48 89 c7 mov %rax,%rdi
400a69: e8 e2 fb ff ff callq 400650 <strlen@plt>
400a6e: 48 89 c6 mov %rax,%rsi
400a71: 48 89 d8 mov %rbx,%rax
400a74: ba 00 00 00 00 mov $0x0,%edx
400a79: 48 f7 f6 div %rsi
400a7c: 48 89 d1 mov %rdx,%rcx
400a7f: 48 89 c8 mov %rcx,%rax
400a82: 89 05 14 06 20 00 mov %eax,0x200614(%rip) # 60109c <index.2459>
400a88: 48 8b 05 01 06 20 00 mov 0x200601(%rip),%rax # 601090 <stdout@@GLIBC_2.2.5>
400a8f: 48 89 c7 mov %rax,%rdi
400a92: e8 19 fc ff ff callq 4006b0 <fflush@plt>
400a97: f2 0f 10 85 60 ff ff movsd -0xa0(%rbp),%xmm0
400a9e: ff
400a9f: 66 0f 2e 85 68 ff ff ucomisd -0x98(%rbp),%xmm0
400aa6: ff
400aa7: 72 0a jb 400ab3 <flashprocess+0x14e>
400aa9: bf 0a 00 00 00 mov $0xa,%edi
400aae: e8 7d fb ff ff callq 400630 <putchar@plt>
400ab3: 48 81 c4 a8 00 00 00 add $0xa8,%rsp
400aba: 5b pop %rbx
400abb: 5d pop %rbp
400abc: c3 retq
400abd: 0f 1f 00 nopl (%rax)
0000000000400ac0 <__libc_csu_init>:
400ac0: 41 57 push %r15
400ac2: 41 89 ff mov %edi,%r15d
400ac5: 41 56 push %r14
400ac7: 49 89 f6 mov %rsi,%r14
400aca: 41 55 push %r13
400acc: 49 89 d5 mov %rdx,%r13
400acf: 41 54 push %r12
400ad1: 4c 8d 25 38 03 20 00 lea 0x200338(%rip),%r12 # 600e10 <__frame_dummy_init_array_entry>
400ad8: 55 push %rbp
400ad9: 48 8d 2d 38 03 20 00 lea 0x200338(%rip),%rbp # 600e18 <__init_array_end>
400ae0: 53 push %rbx
400ae1: 4c 29 e5 sub %r12,%rbp
400ae4: 31 db xor %ebx,%ebx
400ae6: 48 c1 fd 03 sar $0x3,%rbp
400aea: 48 83 ec 08 sub $0x8,%rsp
400aee: e8 0d fb ff ff callq 400600 <_init>
400af3: 48 85 ed test %rbp,%rbp
400af6: 74 1e je 400b16 <__libc_csu_init+0x56>
400af8: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
400aff: 00
400b00: 4c 89 ea mov %r13,%rdx
400b03: 4c 89 f6 mov %r14,%rsi
400b06: 44 89 ff mov %r15d,%edi
400b09: 41 ff 14 dc callq *(%r12,%rbx,8)
400b0d: 48 83 c3 01 add $0x1,%rbx
400b11: 48 39 eb cmp %rbp,%rbx
400b14: 75 ea jne 400b00 <__libc_csu_init+0x40>
400b16: 48 83 c4 08 add $0x8,%rsp
400b1a: 5b pop %rbx
400b1b: 5d pop %rbp
400b1c: 41 5c pop %r12
400b1e: 41 5d pop %r13
400b20: 41 5e pop %r14
400b22: 41 5f pop %r15
400b24: c3 retq
400b25: 90 nop
400b26: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
400b2d: 00 00 00
0000000000400b30 <__libc_csu_fini>:
400b30: f3 c3 repz retq
Disassembly of section .fini:
0000000000400b34 <_fini>:
400b34: 48 83 ec 08 sub $0x8,%rsp
400b38: 48 83 c4 08 add $0x8,%rsp
400b3c: c3 retq
本期的内容到这里就结束了,喜欢的话请点个赞谢谢
封面图自取:
