2011-09-11 52 views
2

我想使用類成員函數作爲回調,我不使用libsigc,因爲它很慢。 在ATL中,我們可以使用成員函數進行C風格的回調(http://www.codeproject.com/KB/cpp/SoloGenericCallBack.aspx),那麼我們可以在linux中實現C++ thunk嗎?我們可以在linux中實現C++ thunk嗎?

下面的代碼會崩潰:

#include <assert.h> 
#include <stdio.h> 
#include <sys/mman.h> 

typedef char BYTE; 
typedef int DWORD; 
typedef int* DWORD_PTR; 
typedef int* INT_PTR; 
typedef bool BOOL; 
typedef unsigned long ULONG; 
typedef unsigned long* ULONG_PTR; 
#define PtrToUlong(p) ((ULONG)(ULONG_PTR) (p)) 
#define __stdcall __attribute__((__stdcall__)) 

//#pragma pack(push, 1) 
struct MemFunToStdCallThunk 
{ 
    BYTE   m_mov; 
    DWORD  m_this; 
    BYTE   m_pushEax; 
    BYTE   m_jmp; 
    DWORD  m_relproc; 

    void Init(DWORD_PTR proc, void* pThis) 
    { 
     printf("proc=%x\n", proc); 
     m_mov = 0xB8; // mov eax 
     m_this = PtrToUlong(pThis); 
     m_pushEax = 0xc3;// push eax 
     m_jmp = 0xe9;   //jmp 
     m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(MemFunToStdCallThunk))); 
     printf("m_relproc = %x\n", m_relproc); 
     mprotect(this, sizeof(MemFunToStdCallThunk), PROT_READ|PROT_WRITE|PROT_EXEC); 
    } 

    void* GetCodeAddress() 
    { 
     return this; 
    } 
}__attribute__ ((packed)); 
//#pragma pack(pop) 

template< typename TDst, typename TSrc > 
TDst UnionCastType(TSrc src) 
{ 
    union 
    { 
     struct 
     { 
      int* pfn; //function,index 
      long delta; // offset, 
     }funcPtr; 
     TSrc uSrc; 
    }uMedia; 
    uMedia.uSrc = src; 
    return uMedia.funcPtr.pfn; 
} 




typedef int (__stdcall *StdCallFun)(int, int); 
class CTestClass 
{ 
public: 
    int m_nBase; 
    MemFunToStdCallThunk m_thunk; 

    int memFun(int m, int n) 
    { 
     int nSun = m_nBase + m + n; 
     printf("m=%d,n=%d,nSun=%d\n", m, n, nSun); 
     return 1234; 
    } 

public: 
    CTestClass() 
    { 
     m_nBase = 10; 
    } 

    void Test() 
    { 
     printf("%x\n", &CTestClass::memFun); 
     m_thunk.Init(UnionCastType<DWORD_PTR>(&CTestClass::memFun), this); 
     StdCallFun fun = (StdCallFun)m_thunk.GetCodeAddress(); 
     assert(fun != NULL); 

     int ret = fun(9, 3); 
     printf("ret = %x\n", ret); 


    } 
}; 



int main() 
{ 
    CTestClass test; 
    test.Test(); 
    return 0; 
} 

編輯: 感謝user786653,我得到正確的答案:

#include <assert.h> 
#include <stdio.h> 
#include <sys/mman.h> 
#include <unistd.h> 

typedef char BYTE; 
typedef int DWORD; 
typedef int* DWORD_PTR; 
typedef int* INT_PTR; 
typedef bool BOOL; 
typedef unsigned long ULONG; 
typedef unsigned long* ULONG_PTR; 
#define PtrToUlong(p) ((ULONG)(ULONG_PTR) (p)) 
#define __stdcall __attribute__((__stdcall__)) 

struct MemFunToStdCallThunk 
{ 
    BYTE   m_repairStack[10]; 
    DWORD  m_mov; 
    DWORD  m_this; 
    BYTE   m_jmp; 
    DWORD  m_relproc; 

    void Init(DWORD_PTR proc, void* pThis) 
    { 
     printf("proc=%p\n", proc); 
     m_repairStack[0] = 0x83; //sub esp, 0x4 
     m_repairStack[1] = 0xec; 
     m_repairStack[2] = 0x04; 
     m_repairStack[3] = 0x8b; //mov eax,[esp + 0x4] 
     m_repairStack[4] = 0x44; 
     m_repairStack[5] = 0x24; 
     m_repairStack[6] = 0x04; 
     m_repairStack[7] = 0x89;//mov [esp], eax 
     m_repairStack[8] = 0x04; 
     m_repairStack[9] = 0x24; 
     m_mov = 0x042444C7; // mov dword ptr [esp+0x4], 
     m_this = PtrToUlong(pThis); 
     m_jmp = 0xe9;   //jmp 
     m_relproc = (DWORD)proc - ((DWORD)this+sizeof(MemFunToStdCallThunk)); 
     printf("m_relproc = %d\n", m_relproc); 
     //long page_size = sysconf(_SC_PAGE_SIZE); 
     //mprotect((void*)(PtrToUlong(this) & -page_size), 2*page_size, PROT_READ|PROT_WRITE|PROT_EXEC); 
    } 

    void* GetCodeAddress() 
    { 
     return this; 
    } 
}__attribute__ ((packed)); 


template< typename TDst, typename TSrc > 
TDst UnionCastType(TSrc src) 
{ 
    union 
    { 
     struct 
     { 
      int* pfn; //function or index 
      long delta; // offset 
     }funcPtr; 
     TSrc uSrc; 
    }uMedia; 
    uMedia.uSrc = src; 
    return uMedia.funcPtr.pfn; 
} 




typedef int (__stdcall *StdCallFun)(int, int); 
class CTestClass 
{ 
public: 
    int m_nBase; 
    MemFunToStdCallThunk m_thunk; 

    int memFun(int m, int n) 
    { 
    printf("this=%p\n", this); 
     int nSun = m_nBase + m + n; 
     printf("m=%d,n=%d,nSun=%d\n", m, n, nSun); 
     return nSun; 
    } 

public: 
    CTestClass() 
    { 
     m_nBase = 10; 
    } 

    void Test() 
    { 
     int (CTestClass::*abc)(int, int); 
     printf("sizeof(MemFunToStdCallThunk)=%d,sizeof(abc)=%d\n", sizeof(MemFunToStdCallThunk), sizeof(abc)); 
     printf("memFun=%p\n", &CTestClass::memFun); 
     m_thunk.Init(UnionCastType<DWORD_PTR>(&CTestClass::memFun), this); 
     StdCallFun fun = (StdCallFun)m_thunk.GetCodeAddress(); 
     assert(fun != NULL); 

     int ret = memFun(2, 3); 
     printf("ret 1= %d\n", ret); 
     ret = fun(9, 3); 
     printf("ret 2= %d\n", ret); 
    } 
}; 


int main() 
{ 
    CTestClass test; 
    test.Test(); 
    return 0; 
} 

回答

2

是的,但我不會推薦它。它會(顯然)使你的代碼更加便攜,如果你不小心的話,你可能會打開一個安全漏洞。您需要使代碼可執行mprotect(2)。像mprotect(&thunk_struct, sizeof(struct _CallBackProcThunk), PROT_READ|PROT_WRITE|PROT_EXEC)

另外結構打包的正常GCC語法是struct S { /* ... */ } __attribute__ ((packed)),雖然新版本可能支持#pragma pack語法。

你可能也想從stdint.h和​​與uint32_t替代DWORDuint8_t(或只是堅持一個typedef在那裏)。

編輯:

從上mprotect手冊頁「[..]地址必須對齊到一個頁面邊界」。你應該檢查返回值。試着做這樣的事情,而不是:

long page_size = sysconf(_SC_PAGE_SIZE); 
uintptr_t addr = ((uintptr_t)this) & -page_size; 
if (mprotect((void*)addr, 2*page_size, PROT_READ|PROT_WRITE|PROT_EXEC)) { 
    perror("mprotect"); 
    /* handle error */ 
} 

下面的計算是錯誤的:

DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(MemFunToStdCallThunk))) 

它做對int*的其計算。

(DWORD)proc - ((DWORD)this+sizeof(MemFunToStdCallThunk) 

在這裏應該足夠了。

一個非常難看(非便攜式等等,等等),但小且自包含的實例如下:

#include <stdio.h> 
#include <stdlib.h> 
#include <stdint.h> 
#include <unistd.h> 
#include <sys/mman.h> 

struct thunk { 
    uint32_t mov; 
    uint32_t this_ptr; 
    uint8_t jmp; 
    uint32_t rel; 
} __attribute__((packed)); 

class Test { 
public: 
    virtual int foo(void) { 
     printf("foo! %p\n", (void*)this); 
     return 42; 
    } 
}; 

int main() 
{ 
    Test test; 
    printf("%d\n", test.foo()); 

    thunk t; 
    t.mov = 0x042444C7; 
    t.this_ptr = (uint32_t)&test; 
    t.jmp = 0xe9; 
    t.rel = ((uint32_t)(void*)&Test::foo) - ((uint32_t)&t + sizeof(thunk)); 

    uint32_t addr = (uint32_t)&t; 
    long page_size = sysconf(_SC_PAGE_SIZE); 
    if (mprotect((void*)(addr & -page_size), 2*page_size, PROT_READ|PROT_WRITE|PROT_EXEC)) { 
     perror("mprotect"); 
     return 1; 
    } 

    union { 
     void* p; 
     int (*foo)(int); 
    } u; 
    u.p = &t; 
    printf("%d\n", u.foo(0)); 
    return 0; 
} 
+0

我寫了一些測試代碼(請參閱上文),但它會在調用jmp指令時崩潰(分段錯誤)。 – xufan

+0

謝謝,我得到了正確的答案。無論我是否稱爲mprotect,都沒關係。 – xufan

+0

+1我會補充說,ATL不會使堆棧可執行(這有點冒險),但是分配內存頁面並嘗試爲許多thunk使用單頁面內存(所以不要浪費內存) – xanatos

0

不能指針到構件的指針傳遞到C回調直接,但有便攜式的技巧(即不限於一個目標操作系統),工作得很好。

最簡單的方法就是使用一個包裝非成員函數,其唯一目的是調用你的成員函數。

void wrapper() 
{ 
    object->callWhatever(); 
} 

您可以通過wrapper()作爲函數指針。

另請參閱Cast member function for create_pthread() call以瞭解如何處理帶回調的void*參數,並希望使用該參數存儲(直接或不是)存儲(直接或不是)要運行的對象的引用/指針。

1

合理的做法是這樣的:

struct Foo { 
    void doit(); 
}; 

extern "C" { 
    void callback(void *handle) { 
     reinterpret_cast<Foo*>(handle)->doit(); 
    } 
} 

回調的組件看起來像這樣在這裏(64):

callback: 
    jmpq _ZN3Foo4doitEv 
+0

如果定義可用,它通常內聯'doit()'。 –

0

我想使用類成員函數作爲回調,我不使用libsigc,因爲它很慢。在ATL中,我們可以使用成員函數進行C風格的回調(http://www.codeproject.com/KB/cpp/SoloGenericCallBack.aspx),那麼我們可以在linux中實現C++ thunk嗎?

你可能可以。但是,沒有必要。

大多數異步API允許在註冊異步事件時通過void*參數。當事件被報告時,這個void*也被報告,並且可以用來調用一個對象的成員函數。 (模糊語言,因爲像epoll_wait()這樣的API實際上並沒有給你回電,pthread_create()就是這樣)。

相關問題