我们可以实现 c++ linux 中的 thunk 吗?
我想使用类成员函数作为回调,我不使用 libsigc,因为它很慢。 在ATL中,我们可以使用成员函数进行C风格的回调(http://www.codeproject.com/KB/cpp/SoloGenericCallBack.aspx),那么我们可以在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;
}
I want to use class member functions as callbacks, I don't use libsigc, because it's slow.
In ATL, we can use member function for C-style callback(http://www.codeproject.com/KB/cpp/SoloGenericCallBack.aspx), so can we implement c++ thunk in linux?
The code below will 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;
}
EDIT:
Thanks to user786653, I get the right answer:
#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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
是的,但我不会推荐它。它(显然)会让你的代码的可移植性大大降低,如果你不小心的话,你可能会打开一个安全漏洞。
您需要使用
mprotect(2)
使代码可执行。类似mprotect(&thunk_struct, sizeof(struct _CallBackProcThunk), PROT_READ|PROT_WRITE|PROT_EXEC)
。此外,结构打包的常规 GCC 语法是 struct S { /* ... */ } __attribute__ ((packed)),尽管较新的版本可能支持
#pragma pack
语法。您可能还想将
stdint.h
中的DWORD
替换为uint32_t
,将BYTE
替换为uint8_t< /code> (或者只是在其中粘贴
typedef
)。编辑:
从
mprotect
的手册页“[..]addr 必须与页面边界对齐”。您应该检查返回值。尝试做类似这样的事情:以下计算是错误的:
它在
int*
上进行计算。这里应该足够了。
一个非常丑陋(不可移植等),但小且独立的示例如下:
Yes, but I wouldn't recommend it. It will (obviously) make your code a lot less portable and you're potentially opening a security hole if you're not careful.
You will need to make the code executable with
mprotect(2)
. Something likemprotect(&thunk_struct, sizeof(struct _CallBackProcThunk), PROT_READ|PROT_WRITE|PROT_EXEC)
.Also the normal GCC syntax for structure packing is
struct S { /* ... */ } __attribute__ ((packed))
though newer versions might support the#pragma pack
syntax.You will probably also want to substitute
DWORD
withuint32_t
fromstdint.h
andBYTE
withuint8_t
(or just stick atypedef
in there).EDIT:
From the man page on
mprotect
"[..]addr must be aligned to a page boundary". You should check the return value. Try doing something like this instead:The following calculation is wrong:
It's doing its calculations on
int*
's.should be sufficient here.
A very ugly (non-portable etc. etc.), but small and self-contained example follows:
合理的方法是这样的:
回调的组装如下所示(x64):
A reasonable approach is something like this:
The assembly of callback looks like this here (x64):
您不能直接将指向成员的指针传递给 C 回调,但是有一些可以很好地工作的可移植技巧(即不限于一个目标操作系统)。
最简单的方法就是使用包装器非成员函数,其唯一目的是调用成员函数。
您可以将
wrapper()
作为函数指针传递。另请参阅 Cast member function for create_pthread() call 的示例,了解如何处理出现
void* 的情况
参数与回调,并希望使用它来存储(直接或不)指向要操作的对象的引用/指针。You can't pass pointer-to-member pointers to C callbacks directly, but there are portable tricks (i.e. not restricted to one target OS) that work very well.
The easiest way to do that is just to use a wrapper non-member function whose only purpose is to call your member function.
You can pass
wrapper()
as a function pointer.See also for example Cast member function for create_pthread() call for how to handle cases where you get a
void*
parameter with the callback and want to use that to store (directly or not) a reference/pointer to the object you want to operate on.你也许可以。然而,没有必要。
大多数异步 API 允许在注册异步事件时传递
void*
参数。当事件被报告时,这个void*
也会被报告,并且可以用来调用对象的成员函数。 (语言含糊,因为像epoll_wait()
这样的 API 实际上不会回调你,而pthread_create()
则会回调你。)。You probably can. However, there is no need to.
Most asynchronous APIs allow to pass a
void*
argument when registering for an asynchronous event. When the event gets reported thisvoid*
is reported as well and can be used to call a member function of an object. (Vague language because APIs likeepoll_wait()
don't actually call you back, where aspthread_create()
does.).