2013-12-18 37 views
2

我在類似於此線程的自動工具C項目中使用自定義精靈標題:How do you get the start and end addresses of a custom ELF section in C (gcc)?。問題是聲明自定義部分的c文件被鏈接到一個靜態庫中,然後鏈接到最終的應用程序。鏈接到庫時缺少__start_section和__stop_section符號

在此配置中,不會生成符號__start_custom_section和__stop_custom_section。我定義這樣的精靈部分:

struct mystruct __attribute((__section__("custom_section"))) __attribute((__used__) = { 
... 
}; 

如果我鏈接到目標文件而不是庫,符號被創建並且一切都按預期工作。這不是一個可擴展的解決方案,但是因爲我希望新模塊通過將它們編譯到模塊庫中來工作。任何想法爲什麼鏈接器不能創建這些特殊的符號時,該部分存在於一個庫vs單個對象文件?

+0

我試過_-Wl, - no-gc-sections_和_ -Wl, - 整個歸檔沒有運氣。 – an0n

+0

我已經斷定該部分正在消失,因爲ld正在優化它,因爲沒有直接引用正在進入該部分的數據結構。仍然不知道爲什麼當我直接鏈接到目標文件時不會得到優化...是不是庫只是對象的存檔?我如何告訴ld該部分正在被使用,並且在鏈接時不會優化它?我正在考慮放入少量未使用的數據,這些數據確實會被引用,並且會一直存在,只是爲了強制創建部分,但這似乎是一種破解... – an0n

+0

使用gcc 4.8.1 btw – an0n

回答

1

我已經做了一些與此類似最近,和我的解決方案不依賴於任何特定的編譯器實現,內部無證符號等。但是,它確實需要多一點的工作:)

背景

磁盤上的ELF二進制文件可以通過知道其格式並使用提供給我們的一對結構很容易地加載和分析:http://linux.die.net/man/5/elf。您可以遍歷每個分段和分段(分段是分段的容器)。如果你這樣做,你可以計算你的部分的相對的開始/結束虛擬地址。通過這種邏輯,您會認爲您可以在運行時通過迭代ELF二進制文件的已加載的內存版本的段和段來執行相同的操作。但是,唉,您只能遍歷片段本身(通過http://linux.die.net/man/3/dl_iterate_phdr),並且所有片段元數據都已丟失。

那麼,我們該如何保留段元數據呢?存儲它自己。

解決方案

如果你有一個名爲「.mycustom」自定義欄目,然後定義元數據結構應該以最低的店兩個數字將顯示相對起始地址和你的「.mycustom的大小' 部分。創建此元數據結構的全局實例,該實例將在另一個名爲'.mycustom_meta'的自定義節中生成本身

例子:

typedef struct 
{ 
    unsigned long ulStart; 
    unsinged long ulSize; 
} CustomSectionMeta; 

__attribute((__section__(".mycustom_meta"))) CustomSectionMeta g_customSectionMeta = { 0, 0 }; 

你可以看到,我們的結構實例與零兩個初始化啓動和大小值。編譯此代碼時,您的目標文件將包含名爲'.mycustom_meta'的部分,對於32位編譯,其大小爲8個字節(對於64位爲16個字節),並且這些值全爲零。運行objdump就可以了,你會看到很多。如果需要,請將其放入靜態庫(.a)中,然後運行readelf,您將看到完全相同的結果。如果需要,將它構建到共享對象(.so)中,運行readelf就可以了,並且您將再次看到相同的內容。將它構建成一個可執行程序,在其上運行readelf,並且它仍然存在。

現在,我們需要編寫一個小的可執行文件(我們稱之爲MetaWriter),它將更新磁盤上的ELF文件以填充開始和大小值。以下是基本步驟:

  1. 以二進制模式打開您的ELF文件(.o,.so或可執行文件)並將其讀入連續數組中。或者,您可以將其映射到內存中以實現相同效果。
  2. 使用上面列出的ELF鏈接中找到的頭結構和說明來讀取二進制文件。
  3. 找到'.mycustom'部分並閱讀section.sh_addr和section.sh_size。
  4. 找到你的'.mycustom_meta'部分。使用步驟3中的開始和大小值創建CustomSectionMeta的實例。memcpy()將結構覆蓋現有'.mycustom_meta'部分數據的頂部,到目前爲止全部爲零。
  5. 將您的ELF數據保存回原始文件。除了您寫入'.mycustom_meta'部分的幾個字節之外,它現在應該完全不被修改。

我所做的是在我的Makefile中執行這個MetaWriter程序作爲構建過程的一部分。所以,你會建立你的.so或可執行文件,然後運行MetaWriter來填寫元部分。之後,它準備好了。

現在,當你的.so或可執行文件運行的代碼,它可以僅從g_customSectionMeta讀取,這將與起始地址填充抵消你的「.mycustom」部分的,以及它的大小,當然這可以用來輕鬆計算結束。必須將此開始偏移量添加到加載的ELF二進制文件的基址中。有幾種方法可以得到這個,但我發現的最簡單的方法是運行dladdr上的一個符號,我知道它存在於二進制文件中(例如g_customSectionMeta!),並使用結果值dli_fbase來了解模塊的基地址。

例子:

#include <dlfcn.h> 
Dl_info dlInfo; 
if (dladdr(&g_customSectionMeta, &dlInfo) != 0) 
{ 
    void * vpBase = dlInfo.dli_fbase; 
    void * vpMyCustomStart = vpBase + g_customSectionMeta.ulStart; 
    void * vpMyCustomEnd = vpMyCustomStart + g_customSectionMeta.ulSize; 
} 

這將是一個有點過分張貼到做這一切工作所需的完整的代碼量,尤其是在MetaWriter的ELF二進制文件的解析。但是,如果您需要一些幫助,請隨時與我聯繫。

1

在我的情況下,變量未在代碼中引用,並且該部分在發佈模式(-O2)中進行了優化。添加used屬性解決了問題。例如:

static const unsigned char unused_var[] __attribute__((used, section("foo"))) = { 
    0xCA, 0xFE, 0xBA, 0xBE 
};