簡體   English   中英

交叉編譯器/平台裸包裝函數,無條件跳轉到函數指針

[英]Cross Compiler/Platform Naked Wrapper Function, Unconditional Jump to Function Pointer

我正在開發一個復雜的程序,該程序將具有調用函數的插件,但是這些函數的方法將在啟動時選擇,並使用函數指針進行分配。

與其傳遞函數指針,我不希望在主可執行文件中具有一些有效的包裝器函數來調用適當的函數。

由於這是針對插件接口的,因此調用約定將根據構建目標(使用宏)定義為__cdecl__stdcall ,並且函數將聲明為extern "C"

基本上,我希望能夠在可執行文件中聲明一個SYMBOL,以便可以根據需要加載插件。 對於解決復雜的科學問題所需的不同任務,但是有多種解決方案或方法如何獲得這些任務的結果,這些將被存儲在它們自己的插件中,因此很容易添加新方法(無需重新編譯整個應用程序),這也使共享新方法變得更加容易,因為任何具有基本代碼的人都可以添加不需要自身經驗的任何插件。

無論如何,我都可以使用此概念,或者在加載插件時必須將功能映射傳遞給插件,但是該功能映射的具體信息取決於所加載的配置和插件,因此我實際上並沒有在完成加載插件之前,先知道這是什么問題。 因此,“我的解決方案”將地圖作為一組全局變量存儲在主可執行文件中,可通過包裝函數進行訪問。

然而這不是直線前進的功能已調用它涉及調用后返回前操縱堆棧約定,應在包裝被忽略,也應該執行uncontitional跳轉jmp英特爾X386 ASM,而不是一個函數調用call了intel x386 ASM和控件應從跳轉到函數而不是包裝返回到調用代碼。 但是我需要C / C ++代碼獨立於編譯器/平台/處理器來執行此操作。

以下是我拋出的一個基本概念示例,以測試我的想法並演示我想做的事情:

C ++代碼(Microsoft Visual C ++ 2010(特定於))

#include <iostream>
void * pFunc;
int doit(int,int);
int wrapper(int, int);
int main() {
    pFunc = (void*)doit;
    std::cout << "Wrapper(2,3): " << wrapper(2,3) << std::endl;
    std::cout << "doit(2,3):    " << doit(2,3) << std::endl;
    return 0; }
int doit(int a,int b) { return a*b; }
__declspec(naked) int wrapper(int, int) { __asm jmp pFunc }

代碼已經過測試,可以正常工作,兩個調用均輸出6

包裝器和doit的ASM輸出

PUBLIC  ?wrapper@@YAHHH@Z               ; wrapper
; Function compile flags: /Odtp
;   COMDAT ?wrapper@@YAHHH@Z
_TEXT   SEGMENT
___formal$ = 8                      ; size = 4
___formal$ = 12                     ; size = 4
?wrapper@@YAHHH@Z PROC                  ; wrapper, COMDAT
; File c:\users\glen fletcher\documents\visual studio 2010\projects\test_wrapper\test_wrapper.cpp
; Line 15
    jmp DWORD PTR ?pFunc@@3PAXA         ; pFunc
?wrapper@@YAHHH@Z ENDP                  ; wrapper
_TEXT   ENDS
PUBLIC  ?doit@@YAHHH@Z                  ; doit
; Function compile flags: /Ogtp
;   COMDAT ?doit@@YAHHH@Z
_TEXT   SEGMENT
_a$ = 8                         ; size = 4
_b$ = 12                        ; size = 4
?doit@@YAHHH@Z PROC                 ; doit, COMDAT
; Line 14
    push    ebp
    mov ebp, esp
    mov eax, DWORD PTR _a$[ebp]
    imul    eax, DWORD PTR _b$[ebp]
    pop ebp
    ret 0
?doit@@YAHHH@Z ENDP                 ; doit
; Function compile flags: /Ogtp
_TEXT   ENDS

包裝的非包裝ASM

PUBLIC wrapper
_1$ = 8
_2$ = 12
_TEXT SEGMENT
wrapper PROC
   push ebp
   mov ebp, esp
   mov eax, DWORD PTR _2$[ebp]
   push eax
   mov ecx, DWORD PTR _1$[ebp]
   push ecx
   call DWORD PTR pFunc
   add esp, 8
   pop ebp
   ret 0
wrapper ENDP
_TEXT ENDS

如何獲得以跨平台和交叉編譯方式生成的原始代碼? 與編譯器生成的Epilog和Prolog代碼的C / C ++函數標准相反,NOTE也不想對處理器做任何假設,因此也無法做一個單獨的ASM文件,而是希望編譯器生成代碼僅使用無條件的跳轉語句。

goto不起作用,因為pFunc是變量而不是標簽,甚至不確定它goto是否仍可在函數之間使用。

就你的問題

如何獲得以跨平台和交叉編譯方式生成的原始代碼?

答案是“一點也不”。

函數調用約定深入到平台和編譯器/語言的特定方面 您正在觸摸所謂的ABI (應用程序二進制接口); 像這樣的問題:

  • 對於所有數字/類型/參數順序,如何/在哪里將參數從調用方傳遞到被調用函數?
  • 如何實現語言的“隱藏”功能(如C ++ this )?
  • 寄存器使用的規則是什么(通過對“目標上下文”進行函數調用而破壞了哪些reg)?
  • 對於所有類型的“返回值”,返回值如何/在哪里?
  • 源(調用者)和目標(被調用者)上下文是否使用相同的數據結構布局規則?
  • 如何處理處理器的運行狀態更改(例如,如果您嘗試在64位模式下執行時調用32位代碼,並且/或者反之亦然)?

在這個SO線程中給出了類似的答案,然后特別針對一個問題,即進行“下調” 64位Windows –> 32位Windows stdcall las,除了“它很復雜,通常不可能,而且始終非常依賴於代碼/編譯器和依賴於操作系統”之外,沒有什么可添加的。

這可以在特定情況下完成(技術術語為“ thunking”。每個“ thunk”都非常具體:例如,如果您知道被調用函數使用32位Windows / x86樣式的fastcall並具有單個參數,則可以編寫一個“ thunk”執行允許您從例如64位Linux代碼進行調用的接口(可能還有處理器狀態切換)。該thunk與第一個參數是XMM0傳遞的浮點值不同,雖然...等等。

對於一般情況...請再次參考無限的編程知識堆 ,對不起, 沒有通用函數指針 :(

編輯:如果關注的是代碼生成,則嘗試以下操作:

/* sourcefile 1 */
extern void (*p)(char *, ...);

static __inline__ void wrapper(char *arg, char *s) {
    return p(arg, s);
}

int main(int argc, char **argv)
{
    wrapper("Hello, I am %s\n", argv[0]);
    return 0;
}

/* sourcefile 2 */
extern void printf(char*, ...);
void (*p)(char *, ...) = printf;

如果我使用gcc 進行優化來編譯這兩個代碼,則編譯器將為main創建以下代碼:

0000000000400500 <main>:
  400500:       48 83 ec 08             sub    $0x8,%rsp
  400504:       48 8b 36                mov    (%rsi),%rsi
  400507:       bf 0c 06 40 00          mov    $0x40060c,%edi
  40050c:       ff 15 d6 03 10 00       callq  *1049558(%rip)        # 5008e8 <p>
  400512:       31 c0                   xor    %eax,%eax
  400514:       48 83 c4 08             add    $0x8,%rsp
  400518:       c3                      retq

這幾乎就是您想要的-除了它消除了 wrapper() ,而是通過函數指針直接內聯調用。

我想出了解決問題的方法,而不是使用裸函數或傳遞函數指針列表。

我可以將指針傳遞給函數指針的結構,即

struct Functions {
   bool (AppAPI *logInfo(std::string,...)),
   bool (AppAPI *logWarn(std::string,...)),
   bool (AppAPI *logError(std::string,...)),
   bool (AppAPI *registerFunction(std::string,void *))
   ...
} PluginFunctions;

for (int i = 0;i<plugins;i++) {
   plugin[i].initialize(&PluginFunctions)
}

PluginFunctions.logInfo = LogInfo;
...

當插件初始化函數傳遞給結構的指針時,它可以存儲該結構,然后從內存中加載函數指針的當前值,該結構只是內存中的指針表,可以在結構后設置函數指針已傳遞給插件,並且仍會更新插件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM