学C语言之初,老师都会告诉我们,main函数式入口点。函数执行从main 开始。对初学C编程的人这样说其实无可厚非,毕竟开始学C编程的时候讲一堆编译链接,反而增加了入门的难度。
看下面这个函数:
- [root@localhost debug]# cat before_after.c
- #include <stdio.h>
- int main()
- {
- printf("%s called \n",__FUNCTION__);
- }
- __attribute__((constructor))
- void function_before_main()
- {
- printf("%s called \n",__FUNCTION__);
- }
- __attribute__((destructor))
- void function_after_main()
- {
- printf("%s called \n",__FUNCTION__);
- }
- [root@localhost debug]# ./before_after
- function_before_main called
- main called
- function_after_main called
- [root@localhost debug]#
另外程序员的自我修养第十一章的开篇也提出了一些有趣的例子,其中atexit这个退出注册函数比较有意思,感兴趣的可以去看下。main函数不是首先执行的函数,这一点几乎是肯定的了。去年,我复习汇编编程的时候,提到了用gcc编译汇编程序,程序中需要有main,但是用ld链接的话,需要有_start 这个label。想了解详情的可以去阅读 。
- as -o cpuid.o cpuid.s
- ld -o cpuid cpuid.o
- int main()
- {
- while(1)
- {
- ;
- }
- return 0;
- }
- [root@localhost debug]# gcc -c nothing.c
- [root@localhost debug]# gcc -o nothing nothing.o
- [root@localhost debug]# ldd nothing
- linux-gate.so.1 => (0x007c9000)
- libc.so.6 => /lib/libc.so.6 (0x00c10000)
- /lib/ld-linux.so.2 (0x00240000)
跟踪一下,看了调用了那些库函数,ltrace 就派上了大用场(ltrace和strace用法几乎一样,一个是跟踪系统调用,一个是跟踪lib函数):
- [root@localhost debug]# ltrace ./nothing
- __libc_start_main(0x8048354, 1, 0xbf802074, 0x8048380, 0x8048370
[root@localhost debug]# nm /lib/libc.so.6 |grep __libc_start_main
00c25dc0 T __libc_start_main
[root@localhost debug]# objdump -d /lib/libc.so.6 |grep -A15 __libc_start_main
00c25dc0 <__libc_start_main>:
c25dc0: 55 push %ebp
c25dc1: 31 d2 xor %edx,%edx
c25dc3: 89 e5 mov %esp,%ebp
c25dc5: 57 push %edi
c25dc6: 56 push %esi
c25dc7: 53 push %ebx
c25dc8: e8 13 ff ff ff call c25ce0 <__i686.get_pc_thunk.bx>
c25dcd: 81 c3 27 b2 12 00 add $0x12b227,%ebx
c25dd3: 83 ec 4c sub $0x4c,%esp
OK,我们继续调查__libc_start_main函数之前,我们退一步做另外一个实验,不用gcc链接,改用ld直接链接,看下能否生成nothing可执行程序
- [root@localhost debug]# rm nothing.o nothing
- [root@localhost debug]# gcc -c nothing.c
- [root@localhost debug]# ld -o nothing nothing.o
- ld: warning: cannot find entry symbol _start; defaulting to 0000000008048074
上面一段得出结论是从link的角度看,_start是函数的入口点,这个定义在crt1.so。
- [root@localhost debug]# ld /usr/lib/crt1.o -o nothing nothing.o
- /usr/lib/crt1.o: In function `_start':
- (.text+0xc): undefined reference to `__libc_csu_fini'
- /usr/lib/crt1.o: In function `_start':
- (.text+0x11): undefined reference to `__libc_csu_init'
- /usr/lib/crt1.o: In function `_start':
- (.text+0x1d): undefined reference to `__libc_start_main'
- [root@localhost debug]# gcc -o nothing nothing.c
- [root@localhost debug]# readelf -h nothing
- ELF Header:
- Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
- Class: ELF32
- ...
- Machine: Intel 80386
- Version: 0x1
- Entry point address: 0x8048280
- Start of program headers: 52 (bytes into file)
- ....
- [root@localhost debug]# objdump -d nothing |grep -A15 "<_start"
- 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 70 83 04 08 push $0x8048370
- 8048290: 68 80 83 04 08 push $0x8048380
- 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
下面可以看出main函数的地址是__libc_start_main的第一个参数。
点击(此处)折叠或打开
- [root@localhost debug]# ltrace ./nothing
- __libc_start_main(0x8048354, 1, 0xbf874264, 0x8048380, 0x8048370
- --- SIGINT (Interrupt) ---
- +++ killed by SIGINT +++
- [root@localhost debug]# objdump -d nothing |grep main
- 08048268 <__libc_start_main@plt>:
- 804829c: e8 c7 ff ff ff call 8048268 <__libc_start_main@plt>
- 08048354
: - 8048362: eb fe jmp 8048362
- [root@localhost debug]#
- STATIC int LIBC_START_MAIN (int (*main) (int, char **, char **
- MAIN_AUXVEC_DECL),
- int argc,
- char *__unbounded *__unbounded ubp_av,
- #ifdef LIBC_START_MAIN_AUXVEC_ARG
- ElfW(auxv_t) *__unbounded auxvec,
- #endif
- __typeof (main) init,
- void (*fini) (void),
- void (*rtld_fini) (void),
- void *__unbounded stack_end)
- __attribute__ ((noreturn));
__libc_start_main这个函数干了那些事情呢,这个,我目前还没看懂代码。我下面郑重推荐的三篇博文中有一篇提到了,也是语焉不详,没有深入到code这一层。好在glibc 的代码,你看,与不看,它就在那里,不怒不喜。
最后,我对这篇文章其实并不满意,因为有太多的未解决的问题。成长是一个过程,我只能用这句话来安慰自己。本文要郑重鸣谢参考文献的三篇博文强烈建议读者去读这三篇。
参考文献
1
2 Hello from a libc-free world! (Part 1)
3 Hello from a libc-free world! (Part 2)
4 程序员的自我修养