Possiamo implementare c ++ thunk in linux?
Domanda
Voglio usare le funzioni dei membri della classe come callback, non uso libsigc, perché è lento. In ATL, possiamo usare la funzione membro per il callback in stile C (http://www.codeproject.com/KB/cpp/SoloGenericCallBack.aspx), quindi possiamo implementare il thunk c ++ in Linux?
Il codice seguente andrà in crash:
#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;
}
MODIFICA: Grazie a user786653, ottengo la risposta giusta:
#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;
}
Soluzione
Sì, ma non lo consiglierei. (Ovviamente) renderà il tuo codice molto meno portabile e potresti potenzialmente aprire un buco di sicurezza se non stai attento.
Dovrai rendere il codice eseguibile con mprotect(2)
. Qualcosa come mprotect(&thunk_struct, sizeof(struct _CallBackProcThunk), PROT_READ|PROT_WRITE|PROT_EXEC)
.
Anche la normale sintassi di GCC per il packaging della struttura è struct S { /* ... */ } __attribute__ ((packed))
sebbene le versioni più recenti potrebbero supportare la sintassi #pragma pack
.
Probabilmente vorrai anche sostituire DWORD
con uint32_t
da stdint.h
e BYTE
con uint8_t
(o semplicemente inserire un typedef
lì dentro).
MODIFICA:
Dalla pagina man su mprotect
"[..] addr deve essere allineato al confine di una pagina". Dovresti controllare il valore restituito. Prova a fare qualcosa di simile invece:
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 */
}
Il seguente calcolo è sbagliato:
DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(MemFunToStdCallThunk)))
Sta facendo i suoi calcoli su int*
.
(DWORD)proc - ((DWORD)this+sizeof(MemFunToStdCallThunk)
dovrebbe essere sufficiente qui.
Segue un esempio molto brutto (non portatile ecc. ecc.), ma piccolo e autonomo:
#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;
}
Altri suggerimenti
Un approccio ragionevole è qualcosa del genere:
struct Foo {
void doit();
};
extern "C" {
void callback(void *handle) {
reinterpret_cast<Foo*>(handle)->doit();
}
}
L'assemblaggio di callback ha questo aspetto qui (x64):
callback:
jmpq _ZN3Foo4doitEv
Non puoi passare direttamente puntatori da puntatore a membro a callback C, ma ci sono trucchi portabili (cioè non limitati a un sistema operativo di destinazione) che funzionano molto bene.
Il modo più semplice per farlo è semplicemente utilizzare una funzione non membro wrapper il cui unico scopo è chiamare la funzione membro.
void wrapper()
{
object->callWhatever();
}
Puoi passare wrapper()
come puntatore a funzione.
Vedi anche ad esempio Funzione membro del cast per la chiamata create_pthread () per come gestire i casi in cui ottieni un parametro void*
con il callback e vuoi usarlo per memorizzare (direttamente o meno) un riferimento / puntatore all'oggetto su cui vuoi operare.
Voglio usare le funzioni dei membri della classe come callback, non uso libsigc, perché è lento.In ATL, possiamo usare la funzione membro per il callback in stile C (http://www.codeproject.com/KB/cpp/SoloGenericCallBack.aspx), quindi possiamo implementare il thunk c ++ in Linux?
Probabilmente puoi.Tuttavia, non è necessario.
La maggior parte delle API asincrone consente di passare un argomento void*
durante la registrazione per un evento asincrono.Quando l'evento viene segnalato, viene segnalato anche questo void*
e può essere utilizzato per chiamare una funzione membro di un oggetto.(Linguaggio vago perché API come epoll_wait()
in realtà non ti richiamano, mentre pthread_create()
fa.).