今天碰到一个很好玩的程序,我纠结了很久才弄明白。。。
下面解释都是在Debug模式下的:
#include <STDIO.H>
#include <windows.h>
void Fun();
int main()
{
BYTE *p;
p = (BYTE *)Fun;
printf("p = %X\n",p);
printf("*p = %X\n",*p);
printf("Fun = %X\n",Fun);
Fun();
return 0;
}
void Fun()
{
printf("Hello World\n");
}
在VC6.0下输出的结果是
这下我就奇怪了,*p为什么是E9呢??毫无根据可言额.....(没学过汇编的人就是傻额)
我就开始调试了,*p是233 = 0XE9
疑问1.为什么*p的值是E9呢?
疑问2.Fun的值怎么是0X00401060呢?打印出来不是0X401005吗?
这是为什么呢??p的值不就是该函数的入口地址吗??函数名就是函数的入口地址啊?为什么就对了呢??
我继续调试,跑到里头去看看了····
首先可以知道一点就是,p = (BYTE *)Fun; 和 printf("Fun = %X\n",Fun);是通过一个偏移获取的一个地址,目前不知道这个地址是不是真正的函数入口地址。
再看Fun();这里call 0X00401005地址,我于是跟到了Fun()函数里面。下面是Fun函数的入口地址:
这下可就差不多可以解释了,这才是真正的函数入口地址啊!!那前面那个 0X00401005是什么呢???
我现在可以推断的就是编译器通过0X00401005来找到函数真正的函数入口地址(0X00401060) 。也就是说p = 00401005,这里存在一个跳转指令(E9)跳到了真正的函数入口地址。
再引入下函数输入表(不知道是不是这么叫的)的一些知识,程序一跑起来,会有一个函数的输入表,这里记录了部分调用函数的地址。看看那么多的DLL是怎样运作的呢?不需要把函数代码都嵌入到程序里面,到了运行的时候直接去找到对应的函数入口地址就行了,那么函数入口地址怎么找呢?就是通过函数输入表(专业的好像叫符号列表,也叫导出段)。在debug模式下面访问一个函数的时候往往都是先通过一个相对地址跳转找到函数输入表,然后找到函数输入表指定条目-------这里面就是函数入口地址,再通过跳转到真正的函数入口地址,执行函数。
上面的两个疑问就解决了
1. *p的值 E9 就是跳转指令,用于跳转到真正的函数入口地址。
2. 0X00401060才是Fun函数真正的入口地址,0X00401005则是Fun函数这一条目对应于函数输入表中的地址。
之后我用OD调试了下debug模式,下面是部分截图:
这里是相对偏移偏移地址,也就是p = (BYTE *)Fun;这句代码,从这里可以看出debug模式下确实是通过相对偏移来找到输入表中函数的地址。并且p = (BYTE *)Fun;和printf("Fun = %X\n",Fun);都是通过下面这种形式----即通过相对地址找到函数输入表,然后访问到真正函数来实现的。
这里是函数调用处,也就是执行Fun();这句代码的地方,指令就是E9 后面的56000000是双字节的偏移量,再来看 p = 0X00401005,*p就是第一个字节(byte类型),也就是jmp指令(E9)。这调指令执行后就到了Fun()过程。
这里是Fun函数的入口点,地址是0X00401060,在反汇编里面叫做过程····
刚刚在请教醒哥的时候,听了他讲了很多,还是只是了解了一小部分,全都是似有似无的概念,不过醒哥确实厉害,讲了些基本的理念之后,提醒我叫我自己去调试,并且告诉了我Debug模式下和Release模式下是不一样的。回到寝室我重新调了下,自己好好分析了才发现原来真是很不一样,不过这时候才真正理解了醒哥说的那些。。。
首先程序的输出就不一样了
下面是Release模式下的OD调试结果:
上面的反汇编我都做了些注释,可能有不对,因为自己不是很会,所以···(菜鸟只能这样,勤能补拙)。这里要注意的一点是这里代码中的0X00401060可不是上面那个Fun()
函数的地址额,这里应该只是巧合吧····(不是很懂,有点困了)。。。
从上面可以看出了Release模式下和Debug下的很大区别。在Release模式下就没有了函数输入表这个概念,访问函数的时候直接到了函数的真正的内存中的地址(不是物理地址(*^__^*) ),p = (BYTE *)Fun;和printf("Fun = %X\n",Fun);都是采用的都是真正地址,这样就比上面Debug模式少了一次访问,这个更加高效,这样就是为什么成型产品是一个Debug模式的一个原因吧。
额····有点累了,睡觉了···(*^__^*)