现在的位置: 首页 > 综合 > 正文

汇编中的ILT

2014年02月04日 ⁄ 综合 ⁄ 共 1727字 ⁄ 字号 评论关闭

http://mocheng.wordpress.com/2006/07/17/what-is-ilt%EF%BC%88incremental-link-table/

这两天研究了一下DLL的import/export原理,看了一些资料,无意中发现网上有一篇文章存在错误,而这篇文章流传还甚广,恐怕也误了不少子弟,觉得有必要说一下:)

随便用哪个搜索引擎来搜索“C++ 虚函数 ILT”,排在前面的都有“C++虚函数调用的反汇编解析”一文,此文被转来转去,都不知道最初出处是哪里了。原文作者亲自动手研究了一下VC产生的汇编代码,钻研精神可嘉,不过对一些概念理解有错误。

文中提到:

004010DC call @ILT+5(Test) (0040100a)//调用Test函数;
//这里@ILT+5就是跳转到Test函数的的jmp指令的地址,一个模块中所有的
//函数调用都会是象这样@ILT+5*n,n表示这个模块中的第n个函数,而ILT的意思
//是Import Lookup Table,程序调用函数的时候就是通过这个表来跳转到相应函数而执//行代码的

作者显然只是试验了Debug版本,因为只有Debug才会出现@ILT,而ILT这里也不是Import Lookup Table的意思,Import Lookup Table是使用DLL时才用到的概念,和C++虚函数实现没有任何关系,这里的ILT是Incremental Link Table的意思,缩写冲突真烦人:)

什么是Incremental Link Table呢?

想想如果我们自己要做编译器(compiler)和连接器(linker),当然希望编译连接运行得越快越好,同时也希望产生的二进制代码也是又快又小,上帝是公平的,鱼与熊掌不可兼得,所以我们自然想到用两种build方式,一种Release,编译慢一些,但是产生的二进制代码紧凑精悍,一种Debug,编译运行快,产生的代码臃肿一点没关系,Debug版本嘛,就是指望程序员在开发的时候反复的build,为了不浪费程序员的时候,要想尽办法让编译连接速度变快。

假如一个程序有连续两个foo和bar (所谓连续,就是他们编译连接之后函数体连续存放), foo入口位置在0×0400,长度为0×200个字节,那么bar入口就应该在0×0600 = 0×0400+0×0200。程序员在开发的时候总是频繁的修改code然后build,假如程序员在foo里面增加了一些内容,现在foo函数体占0×300个字节了,bar的入口也就只好往后移0×100变成了0×0700,这样就有一个问题,如果foo在程序中被调用了n次,那么linker不得不修改这n个函数调用点,虽然linker不嫌累,但是link时间长了,程序员会觉得不爽。所以MSVC在Debug版的build,不会让各个函数体之间这么紧凑,每个函数体后都有padding(全是汇编代码int
3,作用是引发中断,这样因为古怪原因运行到不该运行的padding部分,会发生异常),有了这些padding,就可以一定程度上缓解上面提到的问题,不过当函数增加内容太多超过padding,还是有问题,怎么办呢?MSVC在Debug build中用上了Incremental Link Table, ILT其实就是一串jmp语句,每个jmp语句对应一个函数,jmp的目的地就是函数的入口点,和没有ILT的区别是,现在对函数的调用不是直接call到函数入口点了,而是call到ILT中对应的位置,而这个位置上什么也不做,直接jmp到函数中去。这样的好处是,当一个函数入口地址改变时,只要修改ILT中对应值就搞定了,用不着修改每一个调用位置,用一个冗余的ITL把时间复杂度从O(n)将为O(1),值得,当然Debug版的二进制文件会稍大稍慢,Release版不会用上ILT。

回到网上那篇文章上来,原作者作了一些探索,反先了函数调用以ILT为跳板jmp一下,觉得很有意思,可惜没有试验一下release版,所以也没有进一步探索一下ILT到底是什么东西,真是可惜。如果研究一下Release版的汇编代码,会看到非常紧凑简洁的汇编代码,那才是真正纯正的vtable实现。

抱歉!评论已关闭.