2012-11-09 69 views
2

給定一個帶有C綁定和任意簽名的函數,創建一個指向該函數的指針,傳遞它,包裝它並調用它是一件簡單的事情。在不知道簽名的情況下封裝C函數調用的方法?

int fun(int x, int y) 
{ 
    return x + y; 
} 
void* funptr() 
{ 
    return (void*)&fun; 
} 
int wrapfun(int x, int y) 
{ 
    // inject additional wrapper logic 
    return ((int (*)(int, int))funptr())(x, y); 
} 

只要調用者和被調用者遵循相同的調用約定並且簽名一致,一切都可以工作。

現在讓我們說,我想換一個圖書館與成千上萬的功能。我可以用nmreadelf搶的被包裹的所有函數的名字,但我寧願沒有在意簽名,甚至需要包括圖書館的相關頭文件。

在某些情況下,乾淨利落地包括標題可能不是一個選項,因爲版本和平臺之間發生的化妝品的變化。例如:

// from openssl/ssl.h v0.9.8 
SSL_CTX* SSL_CTX_new(SSL_METHOD* meth); 
// from openssl/ssl.h v1.0.0 
SSL_CTX* SSL_CTX_new(const SSL_METHOD* meth); 

這就是我的背景基本原理,你可以離開或採取。無論如何,我的問題是這樣的:

是否有寫

// pseudocode 
void wrapfun() 
{ 
    return ((void (*)())funptr())(); 
} 

使得wrapfun呼叫者知道的fun簽名的方式,但wrapfun本身並不一定要做嗎?

回答

5

如果看一下從編譯的C函數產生的組件,你會看到每一個功能體由

pushq %rbp 
movq %rsp, %rbp 
; body 
leave 
ret 

http://en.wikipedia.org/wiki/X86_instruction_listings列出了leave指令包裹作爲80186當量(在AT & T語法)

movq %rbp, %rsp 
popq %rpb 

所以leave只是前兩行的倒數:保存調用者的堆棧幀並創建我們自己的堆棧幀,然後放開。

結束retcall的反例,http://www.unixwiz.net/techtips/win32-callconv-asm.html顯示在這些配對指令期間發生的指令指針寄存器的隱藏推送和彈出。

虛空函數指針調用本身並不工作,因爲由編譯器功能wrapfun創造了這個集會的,究其原因。我們需要做的是創建包裝器,以便它可以將由調用者爲其設置的堆棧框架直接傳遞給fun的調用,而不會造成自己的堆棧框架妨礙。換句話說,遵守C調用約定並同時違反它。

考慮一個C原型

int wrapfun(int x, int y); 

與組件實現配對(AT &Ťx86_64的)

.file "wrapfun.s" 
    .globl wrapfun 
    .type wrapfun, @function 
wrapfun: 
    call funptr 
    jmp  *%rax 
    .size wrapfun, .-wrapfun 

基本上,我們跳過典型堆棧指針和基址指針操作,因爲我們要fun的堆棧看起來完全像我的堆棧。對funptr的調用將創建他自己的堆棧空間並將其結果保存到寄存器RAX中。因爲我們沒有自己的堆棧空間,並且因爲我們的調用者的IP很好地坐在堆棧的頂部,所以我們可以簡單地無條件跳轉到封裝函數,並讓他的ret一路跳回。以這種方式,一旦函數指針被調用,他就會看到堆棧是由調用者設置的。

如果我們需要使用局部變量,參數傳遞給funptr,等等,我們總是可以建立我們的堆棧,那麼之前撕裂下來的號召:

wrapfun: 
    pushq %rbp 
    movl %rsp, %rbp ; set up my stack 
    call funptr 
    leave    ; tear down my stack 
    jmp  *%rax 

另外,我們可以嵌入此邏輯成聯彙編,同時我們什麼樣的編譯器會之前和之後做知識的優勢:

void wrapfun() 
{ 
    void* p = funptr(); 
    __asm__(
     "movq -8(%rbp), %rax\n\t" 
     "leave\n\t" 
     "popq %rbx\n\t" 
     "call *%rax\n\t" 
     "pushq %rbx\n\t" 
     "pushq %ebp\n\t" // repeat initial function setup 
     "movq %rsp, %rbp" // so it can be torn down correctly 
    ); 
} 

這種方法之前,魔術ç局部變量的聲明更容易的優點。聲明的最後一個局部變量將在RBP-sizeof(var)處,並且在拆除堆棧之前我們將它保存在RAX中。另一個可能的好處是可以將C預處理器用於例如內聯32位或64位彙編,而無需單獨的源文件。

編輯:現在的缺點是保存IP到寄存器通過要求RBX不是由呼叫者使用限制了應用的可移植性的要求。

總之,答案是肯定的。如果你願意讓自己的手變得有點髒,那麼無需知道它的簽名就可以包裝一個功能。沒有關於可移植性的承諾;)。

2

除了Ryan's answer你也應該考慮使用libffi外國功能界面庫,這可能是你的GCC編譯器內)。它適合你的目標,並且「可移植」的抽象細節(對於它支持的許多體系結構,系統和ABI)。

+0

我對libffi進行了一些研究,同時尋找解決方案來解決我自己的問題,並在發佈此答案後。這似乎是一個很酷的想法,也是一種將各種編譯和非編譯語言的位粘合在一起的有效解決方案,只要至少有一方願意針對此接口實施...... –

相關問題