這篇頂多只能算是讀書心得而已,且不談及「底線開頭」之 library。由於這些動作部份必須上述到 linker ,故分 glibc(gcc) 與 visual c++ (msvc) 探討。要建立完整觀念請洽 「程式設計師的自我修養」第十一章 - 執行階段程式庫。 

由於筆者所閱之書籍 (也可能因為平台不同而有所不同名詞),對於 main 只有以「入口函式」、「入口點」、「entry point 」形容 (雖然它不是真正的入口點),沒提到「entry function」此名詞。為說明清晰,本文將以 entry function 作為名詞。

destructor 實作面這篇文章避開。

結論是 : main / WinMain 不是第一個 entry function。

 

動作原理概述

 

1. OS 建立該應用程式 (.exe) 之行程後,將控制權轉到該程式之入口,此入口往往為執行階段程式庫之某入口函式。

2. entry function 將對環境進行初始化,包含了 heap、I/O、thread、global variable construct ... etc。

3. main 執行完後,返回到 entry function,entry function 進行清楚工作,包含 global variable destruct、destroy heap、close I/O。

4. 進行系統呼叫結束行程。

 

主要在 2. 3 點著為筆墨。

 

glibc (gcc)

 

1. 動作原理

 

(1) linker ( ld.exe ) 預設之 entry function 為 _start  ,此 entry function 由組語實作,且與平台相關。

(2) 在正式呼叫 _start 前 (正確的說是在 _start function 之前半段),loader 將使用者之參數、環境變數推到 stack。stack 「由上而下」為, argc、 argv、 env (環境變數)。

(3) _start 最後將呼叫 __lib_start_main function,再由 __lib_start_main 間接呼叫 main。pseudo code 如下。

void _start()
{
    %ebp = 0;
    int argc = pop from stack
    char** argv = top of stack;
    __lib_start_main(main, argc, argv, ....);
}


(4) 當 main 結束後回到 __lib_start_main,而 __lib_start_main 裡將再呼叫 exit 函式。

_start -> __lib_start_main -> main -> (return to __lib_start_main) -> exit ,看一下 exit 大概長怎樣

 

void exit(int status)
{
    while(__exit_funcs!=NULL){
        ...
        __exit_funcs = __exit_funcs->next;
    }
    ...
}

 

以 linklist 方式 (其實也沒說一定要用 linklist,只是較適合 ) 紀錄有哪些 function 在 main 呼叫完時必須繼續執行,諸如上述的,heap、IO、global destructor 與 atexit 所註冊之 function。

 

2. 示範例與結果參考

 

#include <cstdio>
#include <cstdlib>

//////////////////////////////////////////////////////////////////////////
// (1) depends on compiler
// < gcc only > 

/* before main */
__attribute__((constructor)) void before_main1(){ puts("before_main1");}
__attribute__((constructor)) void before_main2(){ puts("before_main2");} 
__attribute__((destructor)) void after_main(){ puts("after main");}

//////////////////////////////////////////////////////////////////////////
// (2) global class object constructor / destructor
// < C++ only >

class myclass{
public:
    myclass() {puts("constructor from global class");}
    ~myclass() {puts("destructor from global class");}
};
myclass gobal_class;

//////////////////////////////////////////////////////////////////////////
// (3) initialize global variable
// < C++ only >

int gfunc() 
{
    puts("from gfunc");
    return 1;
}
int gvar = gfunc();

//////////////////////////////////////////////////////////////////////////
// (4) main function
int main();

//////////////////////////////////////////////////////////////////////////
// (5) exit main
void ex_func1() {puts("from ex_func1"); ;}
void ex_func2() {puts("from ex_func2"); ;}

//////////////////////////////////////////////////////////////////////////
// (4) main function
int main()
{
    puts("from main");
    atexit(ex_func1);
    atexit(ex_func2);
    return 0;
}

 

執行結果

constructor from global class
from gfunc
before_main2
before_main1
from main
from ex_func2
from ex_func1
after main
destructor from global class

 

結果觀查

(1) 全域類別物件建構 、全域變數初始化 -> (與 MSVC 不同 )

(2)  stack 順序執行 __attribute__((constructor)) 指令函式 ->

(3)  main  -> 

(4) stack 順序執行 atexit ->

(5)  stack 順序執行 __attribute__((destructor)) 指令函式

(6) 全域類別物件解構

 

[3] 隱藏 console 視窗

必須在 linker 那裡動手腳,但 gcc.exe / g++.exe 這部份有提供參數,假設原始檔為 x.cpp,欲產生不會有 console window 之 x.exe 如下。

g++ -o x.exe x.cpp -mwindows  

 

Visual C++ (MSVC)

 

1. 動作原理

 

(1) 從 mainCRTStartup() 進入 (依 Wide String 分版本)。

(2) 進入 _heap_init(),初始化 heap,完成後反回 mainCRTStartup()。

(3) 進入 _ioinit(),初始化 IO。

(4) 取得 argc, argv , env

(5) 初始化資料

(6) 呼叫 main 記錄傳回值

(7) 檢查錯誤,將 main 傳回值傳回。

 

2.  示例碼與結果參考 

 

//////////////////////////////////////////////////////////////////////////
// (1) depends on compiler
// < MSCV only > 

/* before main */
#define SECNAME ".CRT$XCG"
#pragma section(SECNAME, long, read)
static void before_main1(){ puts("before_main1");}
static void before_main2(){ puts("before_main2");}
typedef void (__cdecl * _PVFV)();
__declspec( allocate(SECNAME)) _PVFV dump[] = {before_main1, before_main2};

//////////////////////////////////////////////////////////////////////////
// (2) global class object constructor / destructor
// < C++ only >

class myclass{
public:
    myclass() {puts("constructor from global class");}
    ~myclass() {puts("destructor from global class");}
};
myclass gobal_class;

//////////////////////////////////////////////////////////////////////////
// (3) initialize global variable
// < C++ only >

int gfunc() 
{
    puts("from gfunc");
    return 1;
}
int gvar = gfunc();

//////////////////////////////////////////////////////////////////////////
// (4) main function
int main();

//////////////////////////////////////////////////////////////////////////
// (5) exit main
void ex_func1() {puts("from ex_func1"); ;}
void ex_func2() {puts("from ex_func2"); ;}

//////////////////////////////////////////////////////////////////////////
// (4) main function
int main()
{
    puts("from main");
    atexit(ex_func1);
    atexit(ex_func2);
    return 0;
}

 

執行結果

before_main1
before_main2
constructor from global clas
from gfunc
from main
from ex_func2
from ex_func1
destructor from global class

 

結果觀查

(1)  依序執行 #pragma section(".CRT$XCG", long, read) 指令函式 -> 

(2)  全域類別物件建構 、全域變數初始化 ->

(3)  main  -> 

(4) stack 順序執行 atexit ->

(5) 全域類別物件解構

 

[3] 隱藏 main 


這點在 VC 算是常見到的。console 若不想顯示出視窗本體,可由 linker 那裡下手

以我手邊 VC2010 為例,是放在 

「專案」-> 「屬性」-> 「連結器」-> 「命令列」,

在下面之「其他選擇項」輸入 "/subsystem:windows /entry:mainCRTStartup" 即可。

 

也可能程式碼開頭加入這行

#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )

是一樣的意思。

 

before_main portable in C ?

 


上述不論是 glibc, msvc,扣除掉 depends on compiler 部份,尷尬的是沒有一個方法可用於 C language (都在 C++ 下才能用),真要達成可攜作法,除了一一研究各家 linker 外,似乎沒其他作法。有種聲稱 kuso 作法是用 macro ,但實際上只是把 main 定義為 second_main ,而在其他 header 再寫個 main 而已,實際上經過 preprocessor 後還是一樣從 main 進入。

 

最後,可以確定幾件事只有,關於 entry,只能確定某些步驟比 main 早,某些比 main 晚,但詳細之 order 關係並無一定之關係,所有相對之執行點 compiler 均保留實作之權力 (甚至一個複雜一點的問題:在 global class object 裡,註冊一個 atexit 函式又會變怎樣?沒用 compiler 試過真的不知道.. )

arrow
arrow
    全站熱搜

    edisonx 發表在 痞客邦 留言(0) 人氣()