记得前段时间写的文章《Linux动态共享库与版本控制初记》留下了一大堆问题,最起码的ELF文件是什么都没有弄清楚,接下来我准备搞几篇文章弄弄这个东西,也算满足我自己的一个小心愿。
先来看一个非常简单的C程序吧(m.c)
#include <stdio.h> int main(int argc,char** argv) { int a = 5; return 0; }因为简单,所以才能最直观的弄清楚原理。
使用gcc命令
gcc -o m m.c生成可执行程序m
再使用file命令
file m观察这个文件的基本信息,得到
m: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.9, dynamically linked (uses shared libs), for GNU/Linux 2.6.9, not stripped由此我们可以看到,这个文件是ELF类型的,符合32位小端模式的,可执行文件。它还没有没strip过。
再使用readelf命令提取这个ELF的文件头信息
readelf m -h得到
ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2‘s complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8048280 Start of program headers: 52 (bytes into file) Start of section headers: 1852 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 7 Size of section headers: 40 (bytes) Number of section headers: 28 Section header string table index: 25从上面的信息可以看到进入点地址是0x8048280。这个地址是哪个函数呢?
我们必须使用更多命令来获得信息
使用命令objdump来获得更多信息
objdump -d ./m得到(部分)
08048280 <_start>: 8048280: 31 ed xor %ebp,%ebp 8048282: 5e pop %esi 8048283: 89 e1 mov %esp,%ecx 8048285: 83 e4 f0 and $0xfffffff0,%esp 8048288: 50 push %eax 8048289: 54 push %esp 804828a: 52 push %edx 804828b: 68 80 83 04 08 push $0x8048380 8048290: 68 90 83 04 08 push $0x8048390 8048295: 51 push %ecx 8048296: 56 push %esi 8048297: 68 54 83 04 08 push $0x8048354 804829c: e8 c7 ff ff ff call 8048268 <__libc_start_main@plt> 80482a1: f4 hlt 80482a2: 90 nop 80482a3: 90 nop看到call命令后的<_libc_start_main@plt>猜到,<>这个符号包裹的应该是个函数,那么,0x8048280出的函数就是<_start>。
即进入点是函数start,这个函数做了什么事呢?
看汇编代码注意到,它调用了函数_libc_start_main (@,按照C的标准是不应该出现在函数名中的,函数名应该是字母,数字,下划线的集合,所以函数名应该是
_libc_start_main),这个函数的地址应该是0x8048268,恩
在调用这个函数之前,执行了很多push,看看有哪些:0x8048380 0x8048390 0x8048354
再次浏览objdump的信息,发现
08048354 <main>: 8048354: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048358: 83 e4 f0 and $0xfffffff0,%esp 804835b: ff 71 fc pushl 0xfffffffc(%ecx) 804835e: 55 push %ebp 804835f: 89 e5 mov %esp,%ebp 8048361: 51 push %ecx 8048362: 83 ec 10 sub $0x10,%esp 8048365: c7 45 f8 05 00 00 00 movl $0x5,0xfffffff8(%ebp) 804836c: b8 00 00 00 00 mov $0x0,%eax 8048371: 83 c4 10 add $0x10,%esp 8048374: 59 pop %ecx 8048375: 5d pop %ebp 8048376: 8d 61 fc lea 0xfffffffc(%ecx),%esp 8048379: c3 ret 804837a: 90 nop 804837b: 90 nop 804837c: 90 nop 804837d: 90 nop 804837e: 90 nop 804837f: 90 nop是main()函数。
这些汇编语言看的挺熟悉的,对m.c执行命令gcc
gcc -S m.c生成m.s文件,浏览内容
main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $16, %esp movl $5, -8(%ebp) movl $0, %eax addl $16, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret一一对应啊。我知道m.s里面的汇编是AT&T汇编,那么上面的那个是什么汇编呢,亟待解决。
另外两个地址0x8048380 0x8048390处对应的函数分别是__libc_csu_fini()和__libc_csu_init(),在此不深究。
到此,发现了函数这样调用的:
_start()调用_libc_start_main(),而_libc_start_main()的入参参数中一个是main()函数。
使用ltrace命令跟踪./m程序
ltrace ./m得到
__libc_start_main(0x8048354, 1, 0xbf9478f4, 0x8048390, 0x8048380 <unfinished ...> +++ exited (status 0) +++由此再次确认,main()函数是_libc_start_main()函数的第一个参数,第4,5个参数分别是__libc_csu_init()和__libc_csu_fini(),那第2,3个参数是什么呢?
先看看_libc_start_main()函数的原型吧:
int __libc_start_main(int *(main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end) );可以看到第2个参数就是main函数的的第1个参数,即“main”
可以做个实验
ltrace ./m 1得到
__libc_start_main(0x8048354, 2, 0xbf97ec54, 0x8048390, 0x8048380 <unfinished ...> +++ exited (status 0) +++你看,数值变为2了,因为我们输入了两个参数:main , 1
_libc_start_main()的第3个参数地址很奇怪,下次再搞。第4,5个参数名也和__libc_csu_init()和__libc_csu_fini()一一对应。
任重而道远,下次继续,我就不相信搞不懂。
感谢一下文章