在使用Win32 API(如CreateThread
等)时我们经常需要传入回调函数,这个回调函数不能是类的成员函数,只能是友元函数或静态函数。为了在回调函数中访问某个类的成员变量或函数,我们通常不得不通过某种方式将该类的This
指针传入到回调函数中,从而实现在回调函数中访问类的公有
的成员变量或函数。
如果有一种方式可以实现将类的成员函数作为回调函数来使用,那么就可以很完美的解决上面的问题。本篇介绍的C++ Thunk技术就是来解决这个问题的。
在了解Thunk实现原理之前,需要从汇编角度知道函数的调用过程,可以参考:
从汇编的角度分析函数调用过程(1)
从汇编的角度分析函数调用过程(2)
Thunk技术的大致步骤:
<1>. 在指定的内存区域
(可以使用VirtualAlloc
函数来分配)写入模拟函数调用的汇编指令:
// 该行用于存储函数的返回地址,因为在进入调用函数后,栈顶存储的就是函数的返回地址,所以可以直接使用[esp]。
push dword ptr [esp]
// 将pThis指针放入到函数的第一个参数位置(注意参数入栈是从右往左入栈的,而上面一行代码又压入了函数返回地址,所以是[esp+4])
mov dword ptr [esp+4], pThis
// 直接使用jmp跳转到指定的相对地址(留意相对地址的计算方式)
jmp XXXXXXX
<2>. 通过Windows提供的FlushInstructionCache
函数来更新指令缓存
<3>. 最后再将指定的内存区域
的起始地址作为函数指针来使用。
下面的代码有如下限制(不足):
1. 只支持x86平台,如果需要支持x64平台,可以在该代码的基础上进行修改。可以参考http://www.cnblogs.com/fangkm/archive/2009/05/25/1488727.html
2. 只适用于__stdcall
调用约定。
将Thunk功能的实现放入到Thunk类中,代码如下:
Thunk.h
#ifndef _THUNK_H_
#define _THUNK_H_
#pragma once
// TODO: support x64 platform.
//
#define __u8 unsigned char
#define __u16 unsigned short
#define __u32 unsigned long
#define __u64 unsigned long long
class Thunk
{
public:
Thunk();
~Thunk();
void* Stdcall(void* pThis, __u32 MemberFxnAddr);
template
static __u32 GetMemberFxnAddr(T MemberFxnName) {
union
{
T From;
__u32 To;
}union_cast;
union_cast.From = MemberFxnName;
return union_cast.To;
}
private:
#pragma pack (push, 1)
typedef struct _BYTECODE_STDCALL {
__u8 Push[3]; // push dword ptr[esp] ;
__u32 Move; // mov dword ptr [esp + 4], ?? ?? ?? ?? ;
__u32 This; // this pointer
__u8 Jmp; // 0xE9
__u32 Adrr; // relative jmp
}BYTECODE_STDCALL, *PBYTECODE_STDCALL;
#pragma pack (pop)
BYTECODE_STDCALL m_BytecodeStdcall;
Thunk* pthis_;
};
#endif // ! _THUNK_H_
Thunk.cpp
#include "Thunk.h"
#include
Thunk::Thunk()
{
pthis_ = (Thunk*)VirtualAlloc(NULL, sizeof(Thunk), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
}
Thunk::~Thunk()
{
if (NULL == pthis_)
return;
VirtualFree(pthis_, 0, MEM_RELEASE);
}
void* Thunk::Stdcall(void* pThis, __u32 MemberFxnAddr)
{
// FF 34 24 push dword ptr [esp]
pthis_->m_BytecodeStdcall.Push[0] = 0xFF;
pthis_->m_BytecodeStdcall.Push[1] = 0x34;
pthis_->m_BytecodeStdcall.Push[2] = 0x24;
// C7 44 24 04 ?? ?? ?? ?? mov dword ptr [esp+4], pThis
pthis_->m_BytecodeStdcall.Move = 0x042444C7;
pthis_->m_BytecodeStdcall.This = (__u32)pThis; // 4个字节
// E9 ?? ?? ?? ?? jmp to target addr
pthis_->m_BytecodeStdcall.Jmp = 0xE9;
pthis_->m_BytecodeStdcall.Adrr = MemberFxnAddr - (__u32)(&(pthis_->m_BytecodeStdcall)) - sizeof(BYTECODE_STDCALL);
FlushInstructionCache(GetCurrentProcess(), &(pthis_->m_BytecodeStdcall), sizeof(BYTECODE_STDCALL));
return &(pthis_->m_BytecodeStdcall);
}
以_beginthreadex
函数为例,来介绍如何使用Thunk类。
#include
#include
#include
#include "thunk.h"
using namespace std;
class Test {
public:
Test() {
}
bool CreateNewThread() {
// 重点
void* func = thunk_.Stdcall(this, Thunk::GetMemberFxnAddr(&Test::ThreadProc));
HANDLE h = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)func, NULL, 0, NULL);
return h != NULL;
}
private:
unsigned int __stdcall ThreadProc(void *arg) {
printf("ThreadProc\n");
return 0;
}
private:
Thunk thunk_; // 重点
};
int main() {
Test t;
t.CreateNewThread();
getchar();
return 0;
}