记得前段时间写的文章《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()一一对应。
任重而道远,下次继续,我就不相信搞不懂。
感谢一下文章