هل يمكننا تطبيق c++ thunk في نظام التشغيل Linux؟
سؤال
أرغب في استخدام وظائف أعضاء الفصل كرد اتصال، ولا أستخدم libsigc، لأنه بطيء.في ATL، يمكننا استخدام وظيفة العضو لرد الاتصال بنمط C (http://www.codeproject.com/KB/cpp/SoloGenericCallBack.aspx)، فهل يمكننا تنفيذ c++ thunk في Linux؟
سوف يتعطل الكود أدناه:
#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;
}
المحلول
نعم، لكني لا أوصي به.(من الواضح) أنه سيجعل التعليمات البرمجية الخاصة بك أقل قابلية للنقل ومن المحتمل أن تفتح ثغرة أمنية إذا لم تكن حذراً.
ستحتاج إلى جعل الكود قابلاً للتنفيذ باستخدام mprotect(2)
.شيء مثل mprotect(&thunk_struct, sizeof(struct _CallBackProcThunk), PROT_READ|PROT_WRITE|PROT_EXEC)
.
كما أن بناء جملة دول مجلس التعاون الخليجي العادي لتعبئة الهيكل هو struct S { /* ... */ } __attribute__ ((packed))
على الرغم من أن الإصدارات الأحدث قد تدعم #pragma pack
بناء الجملة.
ربما تريد أيضًا الاستبدال DWORD
مع uint32_t
من stdint.h
و BYTE
مع uint8_t
(أو مجرد التمسك ب typedef
هناك).
يحرر:
من صفحة الرجل على mprotect
"[..]يجب محاذاة عنوان addr إلى حدود الصفحة".يجب عليك التحقق من قيمة الإرجاع.حاول القيام بشيء مثل هذا بدلاً من ذلك:
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;
}
نصائح أخرى
النهج المعقول هو شيء من هذا القبيل: Genacodicetagpre
يبدو تجميع رد النداء هنا (x64):
Genacodicetagpreلا يمكنك تمرير مؤشرات من مؤشر إلى عضو إلى عمليات الاسترجاعات بلغة C مباشرةً، ولكن هناك حيلًا محمولة (مثل:لا يقتصر على نظام تشغيل مستهدف واحد) يعمل بشكل جيد جدًا.
أسهل طريقة للقيام بذلك هي فقط استخدام وظيفة مجمعة غير عضو هدفها الوحيد هو استدعاء وظيفة العضو الخاصة بك.
void wrapper()
{
object->callWhatever();
}
يمكنك تمرير wrapper()
كمؤشر وظيفة.
أنظر أيضاً على سبيل المثال وظيفة عضو الإرسال لاستدعاء create_pthread() لمعرفة كيفية التعامل مع الحالات التي تحصل فيها على void*
المعلمة مع رد الاتصال وتريد استخدامها لتخزين (مباشرة أو لا) مرجع/مؤشر للكائن الذي تريد العمل عليه.
أرغب في استخدام وظائف أعضاء الفصل كرد اتصال، ولا أستخدم libsigc، لأنه بطيء.في ATL، يمكننا استخدام وظيفة العضو لرد الاتصال بنمط C (http://www.codeproject.com/KB/cpp/SoloGenericCallBack.aspx)، فهل يمكننا تنفيذ c++ thunk في Linux؟
ربما تستطيع ذلك.ومع ذلك، ليست هناك حاجة لذلك.
تسمح معظم واجهات برمجة التطبيقات غير المتزامنة بتمرير ملف void*
الوسيطة عند التسجيل لحدث غير متزامن.عندما يتم الإبلاغ عن هذا الحدث void*
يتم الإبلاغ عنها أيضًا ويمكن استخدامها لاستدعاء وظيفة عضو لكائن ما.(لغة غامضة لأن واجهات برمجة التطبيقات تحب epoll_wait()
لا أتصل بك في الواقع مرة أخرى، حيث pthread_create()
يفعل.).