关于Thunk

最近心情越来越差了,我心情不好的时候就喜欢倒腾些小玩意和吃东西,下面就是我的小玩意之一,其实也是两个月以前的代码了,现在把它写成文档,希望能帮我回复心情,现在开始正题~~

    两年前我就听过这个技术,大概其也算是懂点,但是没实做过,前些时用MFC做UI时需要一个对SetTimer API的封装,因为这类API对C++对象的支持很不好,所以,我们总得耍点花招对付它,于是重拾起了这个Thunk。
    对于SetTimer API我就不做过多介绍了,MSDN都有,现在只看他这个回调函数
MSDN的上是这样的: VOID CALLBACK TimerProc(HWND, UINT,UINT_PTR, DWORD);
注意,调用方式是 CALLBACK 也就是stdcall,压栈方式从右至左,函数本身清理stack(因为stdcall不考虑可变参数问题,不明白的请google),例如:
void stdcall test(int n)
{

}
int main()
{
    test(33);

    return 0;
}

在main中生成的代码应该是类似
push 21h //传递参数33
call test //调用test

而test的代码应该是
void _stdcall(int n)
{
    ....
    ret 4 //因为这里需要函数本身清理堆栈(stdcall)
}


因此,根据VOID CALLBACK TimerProc(HWND, UINT,UINT_PTR, DWORD);我们大概可以知道操作系统调用我们注册的回调函数TimerProc时候的动作了,下面的问题是如果玩点花样~
既然我们想把一个C++的对象和回调函数联系起来,那么只要把这个对象的指针存到某个地方就可以了,不考虑thunk技术的话用个全局表也未尝不可,现在单说thunk
既然我们知道系统调用TimerProc生成的汇编代码是call TimerProc,那么注册进去的函数指针只要最终能修正堆栈就可以了(我这里针对这个settimer就不考虑任何其他参数问题了,不过大同小异)
看这个class的一部分
class TimerEx
{
private:
#pragma pack(push, 1)
  struct Thunk
  {
    t_byte   push;
    t_uint32  pthis;
    t_byte   call;
    t_uint32  offset;
    t_byte   ret;
    t_uint16  retn;
  };
#pragma pack(pop)
private:
  Thunk   m_thunk;
  t_uint32  m_timer_id;
public:
  static void TimerFunc(TimerEx *ptimer)
  {
    ptimer->OnTimer();
  }
public:
  virtual void OnTimer()
  {
    printf("OnTimer/n");
  }

......
};
#pragma pack(push,1)是为了防止编译器做优化对其操作

我现在希望每当设定的timer到时之后都调用一次这个类OnTimer,因此也就要调用TimerFunc(废话~),从上面我们得知,既然一定会有call操作,那么不如让系统call到一段可配置的内存段(这段瞧不懂得要看汇编去了),这个所谓的可配置的内存段就是那个Thunk结构,也负责记录这个类的this指针(基地址),看下我的实现代码:
TimerEx() : m_timer_id(0)
{
  memset(&m_thunk, 0, sizeof(Thunk));
  m_thunk.push = 0x68;//push指令的二进制码
  m_thunk.pthis = (t_uint32)this;//记录this指针
  m_thunk.call = 0xE8;  //call的二进制码
  
/*注意,以下三行都是因为call(jmp)的定址方式,在win32中,call的函数地址其实是当前指令地址的下一条指令与被call的地址(TimerEx::TimerFunc)的偏移,
因为call + 操作数为5个字节,所以才有op_offset += 5*/
  t_uint32 proc_addr = (t_uint32)(&TimerEx::TimerFunc);
  t_uint32 op_offset = (t_uint32)&(m_thunk.call);
  op_offset += 5;
  m_thunk.offset = proc_addr - op_offset;
  m_thunk.ret = 0xC2; //返回

/*注意: 因为 static void TimerFunc(TimerEx *ptimer)是_cdecl,所以调用方清理堆栈,因此算上已经压入的四个参数,一共是20
*/

  m_thunk.retn = 20;
}


以下是设置代码
bool TimerEx::StartTimer(t_uint32 interval)
{
m_timer_id = ::SetTimer(NULL, 0, interval, (::TIMERPROC)m_thunk);
return m_timer_id != 0;
}









class TimerEx
{
private:
#pragma pack(push, 1)
  struct Thunk
  {
    t_byte   push;
    t_uint32  pthis;
    t_byte   call;
    t_uint32  offset;
    t_byte   ret;
    t_uint16  retn;
  };
#pragma pack(pop)
private:
  Thunk   m_thunk;
  t_uint32  m_timer_id;
public:
  static void TimerFunc(TimerEx *ptimer)
  {
    ptimer->OnTimer();
  }
public:
  virtual void OnTimer()
  {
    printf("OnTimer/n");
  }

public:
  TimerEx() : m_timer_id(0)
  {
    memset(&m_thunk, 0, sizeof(Thunk));

    m_thunk.push = 0x68;
    m_thunk.pthis = (t_uint32)this;
    m_thunk.call = 0xE8;
    t_uint32 proc_addr = (t_uint32)(&TimerEx::TimerFunc);
    t_uint32 op_offset = (t_uint32)&(m_thunk.call);
    op_offset += 5;
    m_thunk.offset = proc_addr - op_offset;
    m_thunk.ret = 0xC2;
    m_thunk.retn = 20;
  }
  
  virtual ~TimerEx()
  {
    if(m_timer_id != 0)
    {
      StopTimer();
    }
  }

private:
  TimerEx(const TimerEx &other);
  TimerEx& operator=(const TimerEx &other);
public:

  bool StartTimer(t_uint32 interval)
  {
    m_timer_id = ::SetTimer(NULL, 0, interval, (::TIMERPROC)&m_thunk);

    return m_timer_id != 0;
  }


  void StopTimer()
  {
    if(m_timer_id != 0)
    {
      ::KillTimer(NULL,m_timer_id);
      m_timer_id = 0;
    }
  }

};
class TimerExTest : public TimerEx
{
public:
  virtual void OnTimer()
  {
    printf("TimerExTest/n");
  }
};

void Run();
int main()
{
  TimerEx   tex;
  TimerExTest  tex2;
  tex.StartTimer(1000);
  tex2.StartTimer(2000);

  Run();

  printf("done/n");
  cin.get();
  
  return 0;
}

void Run()
{

  ::MSG msg;
  BOOL bRet;
  HWND hWnd;

  while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
  {
    if (bRet == -1)
    {
      // handle the error and possibly exit
      cout << ::GetLastError() << endl;
      exit(-1);
    }
    else
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

}  
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值