函数地址并非实际代码地址

24

在Visual Studio 2008(C++)中调试一些代码时,我注意到函数指针变量中的地址并不是函数本身的实际地址。这是一个extern "C"函数。

int main() {
   void (*printaddr)(const char *) = &print; // debug shows printaddr == 0x013C1429

}

Address: 0x013C4F10
void print() {
  ...
}
获取函数地址的反汇编如下:
   void (*printaddr)(const char *) = &print;
013C7465 C7 45 BC 29 14 3C 01 mov         dword ptr [printaddr],offset print (13C1429h) 

编辑:我查看了地址013C4F10处的代码,编译器显然在该地址插入了一条“jmp”指令。

013C4F10 E9 C7 3F 00 00   jmp         print (013C1429h) 

实际上,.exe文件中有一个包含每个方法的完整跳转表。

有人可以详细说明一下它为什么要这样做吗?这是一种调试“特性”吗?


1
嗯,是什么告诉你print的地址是0x013C4F10?从反汇编来看,print的地址似乎确实是0x013C1429,这就是存储在printaddr中的值。 - Martin
Visual Studio中的反汇编显示在地址0x013C4F10处打印。 - codenheim
@Martin:当我切换到发布模式时,跳转表消失了,地址确实是实际函数地址。 - codenheim
有一本书叫做《链接器和装载器》(http://www.amazon.com/Linkers-Kaufmann-Software-Engineering-Programming/dp/1558604960),其中讲解了这个问题。我已经很久没有读过它了,而且 MS 环境也不是主要关注的对象 - 但确实有一个跳转表,允许动态加载共享库。 - Jonathan Leffler
@mrjoltcola:我点赞了@Martin的原始评论,因为他问了一个有关你的问题缺乏信息的明智问题。 - Troubadour
显示剩余3条评论
3个回答

21

7
我猜测这是为了启用编辑和继续功能。
假设你需要重新编译该函数,你只需要更改间接表而不是所有调用者。这将大大减少在使用编辑和继续功能时需要完成的工作量。

听起来很合理,但是函数指针 printaddr 仍然会指向旧的(未编辑的)print 函数…也许调试器中有个 bug? :) - Martin
@Martin:不,函数指针始终指向跳转表,因此当编辑并继续重新编译函数时,只有跳转表会被修改。函数指针值永远不会改变。这真的很美妙。想象一下,你的代码在内存中存储了一个函数指针。编辑并继续无法更新它。但是通过将其指向跳转表,修改跳转表可以一次捕获所有潜在的函数用法。 - Bahbar
上面的反汇编看起来并不像这样,因为指针没有设置为指向跳转表,而是直接指向函数。 - Martin
@Martin:唉,我想我太想相信了。感谢你指出来。 - Bahbar

2
编译器在该地址处插入了一条“jmp”指令,以调用真正的方法。
013C4F10 E9 C7 3F 00 00   jmp         print (013C1429h)

实际上,.exe文件中有一个完整的跳转表,其中包含每个方法。

这是一个调试功能。当我切换到发布模式时,跳转表会消失,地址确实是实际函数地址。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接