2015-09-21 66 views
16

我剛纔讀的ELF文件關於init and fini sections並給它一個嘗試:執行init和FINI

#include <stdio.h> 
int main(){ 
    puts("main"); 
    return 0; 
} 

void init(){ 
    puts("init"); 
} 
void fini(){ 
    puts("fini"); 
} 

如果我做gcc -Wl,-init,init -Wl,-fini,fini foo.c和運行結果不打印「初始化」部分:

$ ./a.out 
main 
fini 

init部分沒有運行,還是無法打印?

是否有關於init/fini的任何「官方」文檔?

man ld說:

-init=name 
    When creating an ELF executable or shared object, call 
    NAME when the executable or shared object is loaded, by 
    setting DT_INIT to the address of the function. By 
    default, the linker uses "_init" as the function to call. 

不應該意味着,這將是足以命名的初始化函數_init? (如果我做海灣合作委員會抱怨多個定義。)

+0

這是奇怪的...關於你的第一個問題 - 我的GDB顯示初始化函數不會運行在所有。 – MByD

+1

在標準庫初始化足夠讓put工作之前,單元部分很可能運行。你爲什麼不嘗試一些沒有依賴關係的東西,比如設置一個全局變量,以查看單元函數是否實際運行。 –

回答

8

不要這樣做;讓你的編譯器和鏈接器按照他們認爲合適的方式填寫這些部分。

而是用合適的function attributes標記你的函數,以便編譯器和鏈接器將它們放在正確的部分。

例如,

static void before_main(void) __attribute__((constructor)); 
static void after_main(void) __attribute__((destructor)); 

static void before_main(void) 
{ 
    /* This is run before main() */ 
} 

static void after_main(void) 
{ 
    /* This is run after main() returns (or exit() is called) */ 
} 

還可以分配一個優先級(比如說,__attribute__((constructor (300)))),101和65535(含)之間的整數,與第一具有較小優先級編號運行功能。

請注意,爲了說明,我標記了功能static。也就是說,這些函數在文件範圍外是不可見的。這些功能不需要被導出的符號自動調用。


爲了測試,我建議保存在單獨的文件下面,說tructor.c

#include <unistd.h> 
#include <string.h> 
#include <errno.h> 

static int outfd = -1; 

static void wrout(const char *const string) 
{ 
    if (string && *string && outfd != -1) { 
     const char  *p = string; 
     const char *const q = string + strlen(string); 

     while (p < q) { 
      ssize_t n = write(outfd, p, (size_t)(q - p)); 
      if (n > (ssize_t)0) 
       p += n; 
      else 
      if (n != (ssize_t)-1 || errno != EINTR) 
       break; 
     } 
    } 
} 

void before_main(void) __attribute__((constructor (101))); 
void before_main(void) 
{ 
    int saved_errno = errno; 

    /* This is run before main() */ 
    outfd = dup(STDERR_FILENO); 
    wrout("Before main()\n"); 

    errno = saved_errno; 
} 

static void after_main(void) __attribute__((destructor (65535))); 
static void after_main(void) 
{ 
    int saved_errno = errno; 

    /* This is run after main() returns (or exit() is called) */ 
    wrout("After main()\n"); 

    errno = saved_errno; 
} 

,所以你可以編譯並鏈接任何程序或庫的一部分。要將其編譯爲共享庫,請使用

gcc -Wall -Wextra -fPIC -shared tructor.c -Wl,-soname,libtructor.so -o libtructor.so 

,您可以使用

LD_PRELOAD=./libtructor.so some-command-or-binary 

功能保持errno不變干預它到任何動態鏈接的命令或二進制,但它不應該在實踐中關係,並使用低級別write()系統調用來將消息輸出到標準錯誤。最初的標準錯誤被複制到一個新的描述符中,因爲在許多情況下,標準錯誤本身在最後一個全局析構函數 - 我們的析構函數 - 運行之前被關閉。

(有些偏執的二進制文件,通常是安全敏感的,接近於他們不知道所有的描述,所以你可能看到在所有情況下After main()消息。)

+0

這些funktion屬性究竟如何工作?編譯器是否爲init/fini部分生成單獨的代碼?但據我瞭解代碼是從不同的文件(crti.o)鏈接。 – michas

+0

@michas基本上,這些屬性在C語言中暴露的機制完全相同,允許在初始化時構造C++全局對象。 C++構造函數和'__attribute __((構造函數))'註釋的C函數都是簡單的代碼,在'main'之前調用「,因此(在前一種情況下)對象可以在'main'開始時使用。 –

+0

@michas:是的,'crti.o'提供了GNU C庫在ELF'_init'和'_fini'部分中需要的代碼。它取決於所使用的具體ABI(x86,x86-64,ARM變體等),以及標記函數的調用方式。例如,在x86-64上,我相信函數地址在'.init_array'和'.fini_array'部分列出(分別是'__frame_dummy_init_array_entry'和'__do_global_dtors_aux_fini_array_entry'符號)。 –

4

它不是ld錯誤但在主要可執行文件的glibc啓動代碼中。對於共享對象,調用-init選項設置的功能。


This是提交到 ld添加選項 -init-fini
該計劃的 _init功能不是從文件 glibc-2.21/elf/dl-init.c:58DT_INIT進入由動態鏈接程序調用,而是由主可執行從 __libc_csu_init文件 glibc-2.21/csu/elf-init.c:83調用。

也就是說,該程序的DT_INIT中的函數指針被啓動忽略。

如果使用-static進行編譯,則fini也不會被調用。

DT_INITDT_FINI絕對不能使用,因爲它們是old-style, see line 255

以下工作:

#include <stdio.h> 

static void preinit(int argc, char **argv, char **envp) { 
    puts(__FUNCTION__); 
} 

static void init(int argc, char **argv, char **envp) { 
    puts(__FUNCTION__); 
} 

static void fini(void) { 
    puts(__FUNCTION__); 
} 


__attribute__((section(".preinit_array"), used)) static typeof(preinit) *preinit_p = preinit; 
__attribute__((section(".init_array"), used)) static typeof(init) *init_p = init; 
__attribute__((section(".fini_array"), used)) static typeof(fini) *fini_p = fini; 

int main(void) { 
    puts(__FUNCTION__); 
    return 0; 
} 

$ gcc -Wall a.c 
$ ./a.out 
preinit 
init 
main 
fini 
$ 
+0

在這種情況下,它將成爲手冊頁或ld中的一個錯誤。或者在這種情況下是否有理由忽略它? – michas

+0

@ 4566976 - 有關此的任何文檔? – MByD

+0

ld不會抱怨未知的fini名稱,但fini仍然有效。 – michas