使用objdump和readelf可以对目标文件进行详细的分析,这里只说objdump -S和readelf -h的内容。当然它们还有其他很多的选项,其他的选项可以查看它们的man文档。
因为接触的不久,这里以很简单的程序hello.c进行简单的分析。
#include <stdio.h>
int main()
{
printf("hello world!\n");
return 0;
}
一个源文件经过预编译、编译、汇编、链接后生成目标文件。目标文件可以通过加载器加载到内存中运行,这期间还会生成很多文件,这里不进行说明。
加载器要将用户程序加载到内存中运行都需要那些信息呢?
最重要的就是用户程序中各个段地址,因为要进行重定位,为什么要进行重定位这里说明,可以百度、google。比如上面hello.c,内核要将hello.c加载到内存,就需要知道hello.c存储在磁盘的那个位置,还需要知道当前内存中哪里空间是空闲的,以用来加载hello.c。hello.c要打印一个字符串“hello world!”这个字符串需要存储在内存中的一个段,这个段在在哪内核也需要知道,所以也会进行记录。
现在将hello.c使用gcc编译,生成hello目标文件。使用的环境是linux,所以生成的文件是ELF格式的,我们可以使用readelf来查看。
使用readelf -h hello
$ readelf -h hello
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: DYN (共享目标文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x530
程序头起点: 64 (bytes into file)
Start of section headers: 6448 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 56 (字节)
Number of program headers: 9
节头大小: 64 (字节)
节头数量: 29
字符串表索引节头: 28
编译器在将源文件编译成目标文件的时候,需要根据当前的内核、计算机的CPU类型等信息编译成对应的二进制文件。上面提到用户程序在加载到内存中时需要重定位,重定位需要起始地址、程序的大小。用户程序的信息存在另外的位置,这里也有指出。
下面使用objdump -S hello,将可执行程序反汇编。
$ objdump -S hello
hello: 文件格式 elf64-x86-64
Disassembly of section .init:
00000000000004e8 <_init>:
Disassembly of section .plt:
0000000000000500 <.plt>:
0000000000000510 <puts@plt>:
Disassembly of section .plt.got:
0000000000000520 <__cxa_finalize@plt>:
Disassembly of section .text:
0000000000000530 <_start>:
54d: 48 8d 3d e6 00 00 00 lea 0xe6(%rip),%rdi # 63a <main>
554: ff 15 86 0a 20 00 callq *0x200a86(%rip) # 200fe0 <__libc_start_main@GLIBC_2.2.5>
0000000000000560 <deregister_tm_clones>:
00000000000005a0 <register_tm_clones>:
00000000000005f0 <__do_global_dtors_aux>:
0000000000000630 <frame_dummy>:
000000000000063a <main>:
0000000000000660 <__libc_csu_init>:
00000000000006d0 <__libc_csu_fini>:
Disassembly of section .fini:
00000000000006d4 <_fini>:
这里将里面的汇编指令都删掉了,因为我们并不研究具体的汇编代码,而且着太多了,我也没太看懂。
内核的一个重要的功能就是加载用户程序,在加载用户程序之前需要进行一些初始化工作,比如初始化用户程序所要用到的段,和用户程序要用到的数据。可以看到前面有一个<_inti>标识。中间有很多我现在还看不懂,就不胡说了。程序的开始位置并不是所以为的main,而是从start开始可以再上面找到<_start>标识,里面的指令就是用户程序真正开始运行用到的指令。<_start>中会有一条跳转指令,跳转到,为了说明这一点,并没有将这条指令,可以再上面看到,linux下具体怎么跳转的我还不太会。这里的用户程序hello中有一个重要的功能是打印hello world!我们可以在上面找到,在这里进行打印。用户程序所用到的数据需要声明,在声明时编译器给了特定的标号,标号就是当前的地址,可以理解为一个指针。使用各这标号将字符串打印出来。怎么打印的这里不进行说明。
以上是我对readelf -h和objdump -S出来的内容进行的简单的分析。
可能会有错误的地方,希望能够指出来。