هل يمكننا تطبيق c++ thunk في نظام التشغيل Linux؟

StackOverflow https://stackoverflow.com/questions/7378772

  •  28-10-2019
  •  | 
  •  

سؤال

أرغب في استخدام وظائف أعضاء الفصل كرد اتصال، ولا أستخدم 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() يفعل.).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top