1. 程式人生 > >不以main為入口的函數

不以main為入口的函數

sse std test section strong 如果 GC 就是 變量

先看一段程序

#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為入口的函數