zoukankan      html  css  js  c++  java
  • VS debug下为什么多此一举jmp函数地址?

    VS debug下为什么call 函数后,会jmp函数地址?多此一举?

    http://blog.csdn.net/viper/article/details/6332934

    在写跑在main之前的时候,碰到了很奇怪的问题。

    1. int initBreak()  
    2. {  
    3. DebugBreak();  
    4. return 0;  
    5. }  
    6.   
    7. typedef int (*pInit)();  
    8.   
    9. pInit start3 = initBreak;  

    initBreak是函数名,start3 是指针,它们的值竟然不一样。

    开始学习C语言的时候,就知道函数名代表函数地址,可以被赋值给函数指针,方便后面的调用。为什么这里的值会不一样了?

    还是使用 跑在main之前 (2) 代码例子,继续用windbg分析。

    0:000> g
    Fri Apr 15 17:03:10.492 2011 (UTC + 8:00): Breakpoint 0 hit
    eax=00000000 ebx=7ffdf000 ecx=0041956c edx=00130000 esi=00000000 edi=00000000
    eip=0f9186a6 esp=0012ff30 ebp=0012ff34 iopl=0 nv up ei pl zr na pe nc
    cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
    MSVCR100D!_initterm_e+0x6:
    0f9186a6 c745fc00000000 mov dword ptr [ebp-4],0 ss:0023:0012ff30={testC!__native_startup_lock (0041956c)}
    0:000> kPL
    ChildEBP RetAddr 
    0012ff34 0041272b MSVCR100D!_initterm_e(
    <function> ** pfbegin = 0x0041641c, 
    <function> ** pfend = 0x00416a40)+0x6

    0012ff80 0041263f testC!__tmainCRTStartup(void)+0xdb
    0012ff88 77773c45 testC!mainCRTStartup(void)+0xf
    0012ff94 77ce37f5 kernel32!BaseThreadInitThunk+0xe
    0012ffd4 77ce37c8 ntdll!__RtlUserThreadStart+0x70
    0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
    0:000> dd start l4
    00416728 00411186 00411023 0041118b 00000000
    0:000> dd start2 l4
    00416208 00411186 00411023 0041118b 00000000
    0:000> dd start3 l4
    00416624 0041118b 00000000 00000000 00000000
    0:000> dd start4 l4
    00416838 0041118b 00000000 00000000 00000000
    0:000> ln 41118b
    (0041118b) testC!ILT+390(_initBreak) | (00411190) testC!ILT+395(__controlfp_s)
    Exact matches:
    0:000> ln start3
    (00416624) testC!start3 | (00416728) testC!start
    Exact matches:
    testC!start3 = 0x0041118b

    0:000> ln start4
    (00416838) testC!start4 | (0041693c) testC!pinit
    Exact matches:
    testC!start4 = 0x0041118b
    0:000> ln initBreak
    d:projectminevs.net estc estc estc.c(47)
    (004120f0) testC!initBreak | (00412150) testC!main
    Exact matches:
    testC!initBreak (void)

    能够看到,start3和start4均为0x0041118b,而函数initBreak则是004120f0,它们的值并不一样,这是为什么呢?

    再前进一点点就有答案了,

    0:000> u 41118b -8
    testC!ILT+380(__RTC_Initialize)+0x2:
    00411183 250000e985 and eax,85E90000h
    00411188 0e push cs
    00411189 0000 add byte ptr [eax],al
    testC!ILT+390(_initBreak):
    0041118b e9600f0000 jmp testC!initBreak (004120f0)

    testC!ILT+395(__controlfp_s):
    00411190 e987300000 jmp testC!controlfp_s (0041421c)
    testC!ILT+400(__StackOverflow):
    00411195 e9d6040000 jmp testC!_StackOverflow (00411670)
    testC!ILT+405(_GetSystemTimeAsFileTime:
    0041119a e90d310000 jmp testC!GetSystemTimeAsFileTime (004142ac)
    testC!ILT+410(_f1):
    0041119f e96c0d0000 jmp testC!f1 (00411f10)

    真相如此简单,就是一条5个字节的JMP指令。

    另一个问题随之而来,编译器为何如此做呢,不是降低效率,多此一举嘛。Google了一下,答案在此:

    什么是Incremental Link Table呢?

    假如一个程序有连续两个foo和bar (所谓连续,就是他们编译连接之后函数体连续存放), foo入口位置在0x0400,长度为0x200个字节,那么bar入口就应该在0x0600 = 0x0400+0x0200。程序员在开发的时候总是频繁的修改code然后build,假如程序员在foo里面增加了一些内容,现在foo函数体占0x300个字节了,bar的入口也就只好往后移0x100变成了0x0700,这样就有一个问题,如果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。

    所以,想得到正确的结果,disable incremental linking即可,代价是链接时间的变长,没有两全其美的方法。


    Keep it simple!
    作者:N3verL4nd
    知识共享,欢迎转载。
  • 相关阅读:
    测试开发进阶——spring boot——MVC——thymeleaf模板——通过Model model的model.addAttribute返回数据给模板——示例01
    测试开发进阶——spring boot——MVC——thymeleaf模板——打开网页
    git小笔记
    web.config中sessionState节点的配置方案
    【BZOJ1053】 反素数ant
    正则表达式 (python 2)
    简政放权是合理的,大道至简
    一本通1035
    一本通1033
    Oracle中事务隔离机制
  • 原文地址:https://www.cnblogs.com/lgh1992314/p/5834867.html
Copyright © 2011-2022 走看看