WEB开发网
开发学院软件开发VC Thunk技术的一个改进 阅读

Thunk技术的一个改进

 2010-08-15 20:46:25 来源:WEB开发网   
核心提示:Thunk技术,一般认为是在程序中直接构造出可执行代码的技术(在正常情况 下,Thunk技术的一个改进,这是编译器的任务),《深度探索C++对象模型》中对这个词的来源有过考证 (在中文版的162页),将其替换为计算出来的动态值,经过这样的处理,说thunk是knuth的倒拼字,knuth就是大名鼎鼎的计算机经 典名著《

Thunk技术,一般认为是在程序中直接构造出可执行代码的技术(在正常情况 下,这是编译器的任务)。《深度探索C++对象模型》中对这个词的来源有过考证 (在中文版的162页),说thunk是knuth的倒拼字。knuth就是大名鼎鼎的计算机经 典名著《The Art of Computer Programming》的作者,该书被程序员们称为 “编程圣经”,与牛顿的“自然哲学的数学原理”等一起 ,被评为“世界历史上最伟大的十种科学著作”之一(也不知是谁评的 ,我没查到,不过反正这本书很牛就是了)。

一般情况下,使用thunk技术 都是事先查好指令的机器码,然后将数组或结构体赋值为这些机器码的二进制值 ,最后再跳转到数组或结构体的首地址。比如在参考文献[1]中的代码:

void foo(int a)
{ printf ("In foo, a = %d
", 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”。

可以看到,它定义了一个数组code[9],然后将事先查好的各汇编指令的 机器码直接赋值给数组。然后定义一个函数指针等于数组的首地址,最后通过该 函数指针调用thunk代码。这里使用了函数指针完成调用,好处是代码比较清晰易 读。也可以使用汇编代码jmp或call来完成,这样就不必额外定义一个函数指针。

网络上的thunk代码,基本上都是这个思路。如果你实际写一段这样的代 码,一定会发现很麻烦。对着教科书查找每一个汇编指令的机器码,相信不会是 一件愉快的事情。其实我们回过头来想想,这件事计算机来做不是最合适吗,编 译器不就是做这个事情的吗?

以上面的代码为例,让我们重新考虑一下整 个过程。我们的目的是在调用函数foo之前将参数增加1。一般而言,这样做肯定 是没有foo函数的源代码或者不允许修改源代码,否则直接改foo函数的代码就好 了,何必这么麻烦。为了调用时候的简单化,定义一个函数指针是比较合适的, 否则每次调用都写汇编代码jmp或call太麻烦。这样一来,函数指针必须指向一个 代码段的地址。但是这个代码段必须用机器码来构造吗,直接写汇编代码也同样 可以做到。

当然,这里有一个问题。我们写汇编指令的时候,必须是一条 指令一条指令的写,不能说指令写一半,然后让汇编程序去处理。上面的代码中 ,第一条指令inc直接写汇编语句当然没问题。但下面的jmp语句,就不能直接写 。因为我们写汇编语句的时候,jmp跳转偏移量是未知的,必须编译后才知道。并 且我们不能只写jmp而不写偏移量,那是通不过编译的。

这个问题可以这 样解决,写jmp语句的时候,我们写一个占位的DWORD,其值设为一个特殊的值, 比如0xffff(原理是这样,实际处理还要迂回一下,后面有说明)。只要在这段 thunk代码中不出现这个值就好。然后执行的时候,在第一次调用之前,在thunk 代码中查找该值,将其替换为计算出来的动态值。经过这样的处理,就可以彻底 在thunk代码中消除机器码的直接操作。

1 2 3 4  下一页

Tags:Thunk 技术 一个

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接