不以main為入口的函數
先看一段程序
#include <stdio.h> void test() { printf("Hello Word!\n"); return 0; }
沒有main函數,編譯一定不會通過,在gcc下編譯會提示以下信息:
/usr/lib/gcc/i686-linux-gnu/4.7/../../../i386-linux-gnu/crt1.o:在函數‘_start’中:
(.text+0x18):對‘main’未定義的引用
collect2: 錯誤: ld 返回 1
可以看到錯誤信息提示,提到了一個“crt1.o”這個文件,其中crt是“C runtime library”的縮寫,其含義是“C運時庫”。
C運行時庫除了給我們提供必要的庫函數調用(如memcpy、printf、malloc等)之外,它提供的另一個最重要的功能是為應用程序添加啟動函數。C運行時庫啟動函數的主要功能為進行程序的初始化,對全局變量進行賦初值,加載用戶程序的入口函數。
從給出的錯誤提示信息中還可以得知,在crt1.o中,有一個名為“_start”的函數。現在網上找到一份crt1.o的偽代碼:
section .text: __start: : init stack; init heap; open stdin; open stdout; open stderr; : push argv; push argc; call _main; (調用 main) : destory heap; close stdin; close stdout; close stderr; : call __exit;
從偽代碼可以看出,在這個_start函數中,調用了main函數。那麽我們可不可以用什麽手段,去修改入口函數,把入口函數改成test函數呢?
gcc test.c -etest -nostartfiles
其中-e選項為修改函數的入口地址,可惜在網上和man手冊裏都沒有找到有關-e這個選項的解釋,只搜索到了-E這個選項。問了好多人才明白原來-e指的 就是entrance。這裏把entrance指定為test函數。
-nostartfiles選項的作用是通知編譯器不自動加入啟動函數以及別的庫級 別的初始化,這樣就不會調用到crt1.o中的_start函數。
此時,編譯通過。可是執行程序,可以成功打印出Hello Word!字樣,但是卻會提示“段錯誤”。
Hello Word!
段錯誤
原來,在編譯的時候加上了-nostartfiles這個選項的同時,使得最後的return語句不能正常執行。解決方案是,把程序最後的return語句改成exit語句,讓exit語句來做最後的“善後工作”。
#include <stdio.h> void test() { printf("Hello Word\n"); exit(0); }
沒有任何錯誤。
還有兩種方法。。。
方法一:
define預處理指令 這種方式很簡單,只是簡單地將main字符串用宏來代替,或者使用##拼接字符串。示例程序如下:#include <stdio.h> #define start main int start() { printf("Hello Word\n"); return 0; }
#include <stdio.h> #define start m##a##i##n int start() { printf("Hello Word\n"); return 0; }
方法二:
_start函數
_start函數是C程序的入口函數,會調用main函數。在調用main函數之前,會先執行_start函數分配必要的資源,然後再調用main函數。但是在用gcc編譯程序時可以使用-nostartfiles選項來重寫_start函數。示例程序如下:
#include <stdio.h> #include <stdlib.h> _start(void) { printf("Hello Word\n"); exit(0); }
編譯指令:
gcc -nostartfiles _start.c -o main.out
反匯編生成的執行程序
a.out: file format elf64-x86-64 Disassembly of section .plt: 0000000000400320 <puts@plt-0x10>: 400320: ff 35 ea 01 20 00 pushq 0x2001ea(%rip) # 600510 <_GLOBAL_OFFSET_TABLE_+0x8> 400326: ff 25 ec 01 20 00 jmpq *0x2001ec(%rip) # 600518 <_GLOBAL_OFFSET_TABLE_+0x10> 40032c: 0f 1f 40 00 nopl 0x0(%rax) 0000000000400330 <puts@plt>: 400330: ff 25 ea 01 20 00 jmpq *0x2001ea(%rip) # 600520 <_GLOBAL_OFFSET_TABLE_+0x18> 400336: 68 00 00 00 00 pushq $0x0 40033b: e9 e0 ff ff ff jmpq 400320 <puts@plt-0x10> 0000000000400340 <exit@plt>: 400340: ff 25 e2 01 20 00 jmpq *0x2001e2(%rip) # 600528 <_GLOBAL_OFFSET_TABLE_+0x20> 400346: 68 01 00 00 00 pushq $0x1 40034b: e9 d0 ff ff ff jmpq 400320 <puts@plt-0x10> Disassembly of section .text: 0000000000400350 <_start>: 400350: 55 push %rbp 400351: 48 89 e5 mov %rsp,%rbp 400354: bf 68 03 40 00 mov $0x400368,%edi 400359: e8 d2 ff ff ff callq 400330 <puts@plt> 40035e: bf 00 00 00 00 mov $0x0,%edi 400363: e8 d8 ff ff ff callq 400340 exit@plt上面的結果是完整的反匯編結果,我們可以看到_start函數中只有我們調用printf和exit函數相關的一些指令,並且.txt段中只有_start函數,沒有看到main函數。如果將源代碼中的_start替換為main,重新編譯程序,反匯編的結果中會看到_start函數會調用到main。 另外還有一點需要註意,因為這裏重寫了_start函數,所以gcc為默認的main函數準備的清理動作就沒用上,所以如果退出的時候直接使用return,會導致程序崩潰。所以這裏要使用exit()來退出程序。 原因請參看這篇文章:http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
不以main為入口的函數