<script type="text/javascript"> /*<![CDATA[*/ if(top.location != self.location){ top.location = self.location; } var myref = encodeURIComponent("http://hi.baidu.com/%D3%A3%C1%E8%D7%BC/blog/item/abe5f724a974f92cd5074203%2Ehtml"); /*]]>*/ </script> /*<![CDATA[*/ #usrbar{padding:4px 10px 3px 0;font-size:12px;height:19px;line-height:19px;color:#000000;font-family:Arial;text-align:right;background:#ffffff;filter:alpha(opacity=65);-moz-opacity:0.5;width:auto !important;width:100%;letter-spacing:normal} #usrbar a,#usrbar a:link,#usrbar a:visited{color:#0000CC;text-decoration:underline} #ft{clear:both;height:20px;line-height:20px;color:#666666;font-size:12px;font-family:Arial;text-align:center} #ft a,#ft a:link,#ft a:visited{color:#7777CC;text-decoration:underline} #usrbar,#usrbar a,#usrbar a:link,#usrbar a:visited,#ft,#ft a,#ft a:link,#ft a:visited{letter-spacing:normal} /*]]>*/
查看文章
|
Thunk 技术的一个改进 作者:南风 摘要:介绍了 thunk 技术中如何避免直接写机器码。 关键字:Thunk 机器码 this指针 Thunk技术,一般认为是在程序中直接构造出可执行代码的技术(在正常情况下,这是编译器的任务)。《深度探索C++对象模型》中对这个词的 来源有过考证(在中文版的162页),说thunk是knuth的倒拼字。knuth就是大名鼎鼎的计算机经典名著《The Art of Computer Programming》的作者,该书被程序员们称为“编程圣经”,与牛顿的“自然哲学的数学原理”等一起,被评为“世界历史上最伟大的十种科学著作”之 一(也不知是谁评的,我没查到,不过反正这本书很牛就是了)。 void foo(int a) { printf ("In foo, a = %d/n", a); } unsigned char code[9]; * ((DWORD *) &code[0]) = 0x042444FF; /* inc dword ptr [esp+4] */ code[4] = 0xe9; /* JMP */ * ((DWORD *) &code[5]) = (DWORD) &foo - (DWORD) &code[0] - 9; /* 跳转偏移量 */ void (*pf)(int/* a*/) = (void (*)(int)) &code[0]; pf (6); 这是一段典型的thunk代码,其执行结果是“In foo, a = 7”。 void ThunkTemplate(DWORD& addr1,DWORD& addr2)//生成机器码 { int flag = 0; DWORD x1,x2; if(flag) { //注意,这个括号中的代码无法直接执行,因为其中可能含有无意义的占位数。 __asm { thunk_begin: ;//这里写thunk代码的汇编语句. ... thunk_end: ; } } __asm { mov x1,offset thunk_begin; //取 Thunk代码段 的地址范围. mov x2,offset thunk_end; } addr1 = x1; addr2 = x2; } 上面的函数用于生成thunk的机器码模板,之所以称为模板,是因为其中包含了无意义的占位数,必须将这些占位数替换为有意义的值之后,才可以 执行这些代码。因此,在函数中thunk代码模板放在一个if(0)语句中,就是避免调用该函数的时候执行thunk代码。另外,为了能方便的得到 thunk代码模板的地址,这里采用一个函数传出thunk代码的首尾地址。 至于替换占位数的功能是很简单的,直接替换就好。 void ReplaceCodeBuf(BYTE *code,int len, DWORD old,DWORD x)//完成动态值的替换. { int i=0; for(i=0;i<len-4;++i) { if(*((DWORD *)&code[i])==old) { *((DWORD *)&code[i]) = x; return ; } } }这样使用两个函数: DWORD addr1,addr2; ThunkTemplate(addr1,addr2); memset(m_thunk,0,100);//m_thunk是一个数组: char m_thunk[100]; memcpy(m_thunk,(void*)addr1,addr2-addr1);//将代码拷贝到m_thunk中。 ReplaceCodeBuf(m_thunk,addr2-addr1,-1,(DWORD)((void*)this));//将m_thunk中的-1替换为this指针的值。 原理部分到此为止。下面举一个完整的,有实际意义的例子。在windows中,回调函数的使用是很常见的。比如窗口过程,又比如定时器回调函 数。这些函数,你写好代码,但是却从不直接调用。相反,你把函数地址传递给系统,当系统检测到某些事件发生的时候,系统来调用这些函数。这样当然很好,不 过如果你想做一个封装,将所有相关部分写成一个类,那问题就来了。 VOID CALLBACK TimerProc( HWND hwnd, // handle to window UINT uMsg, // WM_TIMER message UINT_PTR idEvent, // timer identifier DWORD dwTime // current system time ); 四个参数,个个都有用途。没有地方可以让你传递那个this指针。当然了,你实在要传也可以做到,比如将hwnd设置为一个结构体的指针,其中包含 原来的hwnd和一个this指针。在定时器回调函数中取出hwnd后强制转化为结构体指针,取出原来的hwnd,取出this指针。现在就可以通过 this指针自由的调用类成员函数了。不过这种方法不是我想要的,我要的是一个通用,统一的解决方法。通过在参数里面加塞夹带的方法,一般也是没有问题 的,不过如果碰到一个回调函数没有参数怎么办?另外,本来是封装为一个类的,结果还是要带着一个全局函数,你难道不觉得有些不爽吗? 1、准备好this指针 关键的代码如下(完整的工程在附件中): void ThunkTemplate(DWORD& addr1,DWORD& addr2,int calltype=0) { int flag = 0; DWORD x1,x2; if(flag) { __asm //__thiscall { thiscall_1: mov ecx,-1; //-1占位符,运行时将被替换为this指针. mov eax,-2; //-2占位符,运行时将被替换为CTimer::CallBcak的地址. jmp eax; thiscall_2: ; } __asm //__stdcall { stdcall_1: push dword ptr [esp] ; //保存(复制)返回地址到当前栈中 mov dword ptr [esp+4], -1 ; //将this指针送入栈中,即原来的返回地址处 mov eax, -2; jmp eax ; //跳转至目标消息处理函数(类成员函数) stdcall_2: ; } } if(calltype==0)//this_call { __asm { mov x1,offset thiscall_1; //取 Thunk代码段 的地址范围. mov x2,offset thiscall_2 ; } } else { __asm { mov x1,offset stdcall_1; mov x2,offset stdcall_2 ; } } addr1 = x1; addr2 = x2; } 上面的函数有几个地方需要说明: 1、为了能适应两种不同的成员函数调用约定,这里写了两份代码。通过参数calltype决定拷贝哪一份代码到缓冲区。 mov eax,-2; jmp eax; 这是由汇编语言的特点决定的。直接写jmp -2是通不过的(根据地址的不同,jmp汇编后可能出现好几种形式。这里必须出现一个真实的地址以便汇编器决定jmp类型)。 设置thunk代码的完整代码如下: DWORD FuncAddr; GetMemberFuncAddr_VC6(FuncAddr,&CTimer::CallBcak); DWORD addr1,addr2; ThunkTemplate(addr1,addr2,0); memset(m_thunk,0,100); memcpy(m_thunk,(void*)addr1,addr2-addr1); ReplaceCodeBuf(m_thunk,addr2-addr1,-1,(DWORD)((void*)this)); //将-1替换为this指针. ReplaceCodeBuf(m_thunk,addr2-addr1,-2,FuncAddr); //将-2替换为成员函数的指针. 如果你还想和以前一样直接在数组中赋值机器码(毕竟这样看起来很酷,我完全理解)。那也可以这样,调用ThunkTemplate生成 m_thunk后,打印出该数组的值,而后在程序中直接给m_thunk数组赋值,就象网上大部分thunk代码那样,当然在调用前要多一个步骤就是替换 掉占位数。不过无论如何,调用这两个函数生成机器码应该比手工查找方便多了,如果你也这样认为,那就算我这篇文章没白写。 |
登录后,您就出现在这里。 | |||||||
wizyco | scholesyc | 编码错误 | 路鬼甲 | 号子月茗 |