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

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

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

  1. int initBreak()
  2. {
  3. DebugBreak();
  4. return 0;
  5. }
  6. typedef int (*pInit)();
  7. 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:\project\mine\vs.net\testc\testc\testc.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即可,代价是链接时间的变长,没有两全其美的方法。

VS debug下为什么多此一举jmp函数地址?的更多相关文章

  1. DEBUG模式下, 内存中的变量地址分析

    测试函数的模板实现 /// @file my_template.h /// @brief 测试数据类型用的模板实现 #ifndef MY_TEMPLATE_H_2016_0123_1226 #defi ...

  2. 直接调用类成员函数地址(用汇编取类成员函数的地址,各VS版本还有所不同)

    在C++中,成员函数的指针是个比较特殊的东西.对普通的函数指针来说,可以视为一个地址,在需要的时候可以任意转换并直接调用.但对成员函数来说,常规类型转换是通不过编译的,调用的时候也必须采用特殊的语法. ...

  3. C++ 虚函数表解析(比较清楚,还可打印虚函数地址)

    C++ 虚函数表解析 陈皓 http://blog.csdn.net/haoel 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父 ...

  4. C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址

    C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址 讲解之前,了解下什么是虚函数,什么是虚表指针,了解下语法,(也算复习了) 开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当 ...

  5. Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取

    为什么要写这篇文章 1.      因为最近在学习<软件调试>这本书,看到书中的某个调试历程中讲了Windows的系统调用的实现机制,其中讲到了从Ring3跳转到Ring0之后直接进入了K ...

  6. hash算法搜索获得api函数地址的实现,"kernel32.dll", "CreateThread"

    我们一般要获得一个函数的地址,通常采用的是明文,例如定义一个api函数字符串"MessageBoxA",然后在GetProcAddress函数中一个字节一个字节进行比较.这样弊端很 ...

  7. 旧书重温:0day2【4】动态获取函数地址

    通过以上3篇文章的学习,我们已经可以获取到kernel32.dll的地址了下一步 我们就是获取几个重要的函数 1.GetProcAddress 2.LoadLibrary 有了这两个函数很多函数都可以 ...

  8. C++反汇编第二讲,反汇编中识别虚表指针,以及指向的虚函数地址

    讲解之前,了解下什么是虚函数,什么是虚表指针,了解下语法,(也算复习了) 开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当然也可以看原博客链接: http://blog.csdn.net ...

  9. 通过PEB寻找函数地址

      通过PEB的Ldr参数(结构体定义为_PEB_LDR_DATA),遍历当前进程加载的模块信息链表,找到目标模块.   摘自PEB LDR DATA: typedef struct _PEB_LDR ...

随机推荐

  1. 10道java经典算法题,每一题都能帮你提升java水平!

    JAVA经典算法题 [程序1] 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 1.程序分析:   ...

  2. Java工作流系统-父子流程的配置讲解

    父子流程 关键字: 驰骋工作流程快速开发平台 工作流程管理系统 工作流引擎 asp.net工作流引擎 java工作流引擎. 开发者表单  拖拽式表单 工作流系统 适配数据库: oralce,mysql ...

  3. 小小知识点(十四)——Adobe photoshop cc 2018中简单抠图的一些基本操作

    一 如何抠图 1. 右键弹出选择工具,随后鼠标左键选择快速选择工具 2.通过点击鼠标,选择想要的区域: Alt+鼠标右键  左右拖动鼠标可调整画笔大小 Alt+鼠标滑轮,可放大或缩小画布大小 ctrl ...

  4. docker+mysql 更改配置后重启不了的解决方案

    docker+mysql 更改配置后重启不了的解决方案 前提:在最近的项目中,决定将项目改造成数据库读写分离的架构,于是擅自更改生产环境的数据库的配置文件my.cnf,由于我是用docker进行部署的 ...

  5. POJ 1269 Intersecting Lines(判断两直线位置关系)

    题目传送门:POJ 1269 Intersecting Lines Description We all know that a pair of distinct points on a plane ...

  6. 【转】[ppurl]从”皮皮书屋”下载电子书的姿势

    转:http://blog.csdn.net/hcbbt/article/details/42072545 写在前面的扯皮 为什么标题的”皮皮书屋”加上了引号,因为皮皮书屋(http://www.pp ...

  7. Netty 的基本简单实例【服务端-客户端通信】

    Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象. 在Netty里面,Accept连接可以使用单独的线程池去处理,读写操作又是另外的线程池来处理. Accept连接和读写 ...

  8. 多态(C++)

    #include <iostream> using namespace std; class HeroFighter { public: virtual int power() { ; } ...

  9. 怎样使用七牛云CDN加速并绑定阿里云域名

    昨天晚上在某个群里看到群友问,七牛云能不能绑定自己的域名作为静态资源文件的前缀,忽然想起来我已经有快两年时间没有登录过我的七牛云账号了,不禁老脸一红,这是有多久没有自己前后端都弄了,幸好还没有老年痴呆 ...

  10. APICloud发布低代码开发平台

    云原生的出现,致使传统IT模式正在集中向云架构.云开发转型,其中在企业业务的互联网化.数字化进程中尤为突出,并衍生出“敏捷开发”.“快速迭代”的刚性需求.面对双模IT,如何打造全新的IT团队与模式?并 ...