函数开始处的MOV EDI, EDI的作用

调试程序调试到系统库函数的代码时,总会发现系统函数都是从一条MOV EDI, EDI指令开始的,紧接着这条指令下面才是标准的建立函数局部栈的代码。对系统DLL比如ntdll.dll进行反汇编,可以发现它的每个导出函数都是如此,并且每个导出函数开始处的MOV EDI, EDI上面紧接着5条NOP指令。比如在WinDbg中查看TextOutA周围的代码: 
0:000> u TextOutA-0x0a L 10 
GDI32!NtGdiTransparentBlt+0xa: 
77efc43f ff12            call    dword ptr [edx] 
77efc441 c22c00          ret     2Ch 
77efc444 90              nop 
77efc445 90              nop 
77efc446 90              nop 
77efc447 90              nop 
77efc448 90              nop 
GDI32!TextOutA: 
77efc449 8bff            mov     edi,edi 
77efc44b 55              push    ebp 
77efc44c 8bec            mov     ebp,esp 
很明显,两个字节的MOV EDI,EDI指令什么事情也不做,那么,就有两个问题:第一,为什么不直接从函数体开始而要从这条什么都不做的指令开始呢?第二,即使需要在函数一开始空出两个字节,为什么不直接使用两条NOP指令,而要使用这条MOV指令呢?在网上查阅一些资料后,得到了答案: 
对于第一个问题,答案是为了实现hot-patching技术,即运行时修改一个函数的行为。修改过程如下:把MOV EDI, EDI修改为一条短跳转指令(一条短跳转指令恰好两个字节),把MOV EDI, EDI上面的五个NOP修改为一条长跳转指令(一条长跳转指令恰好五个字节),短跳转指令跳到长跳转指令上,长跳转指令跳到修改后的函数体上。 
对于第二个问题,答案是为了提高效率。执行一条MOV指令比执行两条NOP指令花费更少的时间。 
  
下面是在网上搜索到的相关资料: 
http://blogs.msdn.com/ishai/archive/2004/06/24/165143.aspx 
在这篇日志中作者指出这是一种实现hot-patching和hot-fix的技术,而且解释了为什么不使用detours技术来实现hot-patching。此外,作者提到了具体是如何使用这种技术来实现hot-patching的,但是只是一句话带过。 
  
http://msmvps.com/blogs/kernelmustard/archive/2005/04/25/44413.aspx 
这篇文章中作者从效率和其它方面详细解释了为什么选择用这种技术来实现hot-patching以及为什么要这样实现(短跳转加长跳转而不是一次性长跳转)。 
  
http://xelf.info/knowledge/MemoryCopy.cpp 
这里给出了具体的memcpy的C语言源代码。如果在安装VC6的时候选择了安装CRT源代码,则在VC安装目录的SRC/INTEL/目录中有memcmp.asm等文件,它们就是对应的CRT函数的源代码,这些源代码中也都有详细的注释。 
  
另外,在自己的日志http://blog.csdn.net/jcwKyl/archive/2008/03/25/2217428.aspx里面,曾经对strcmp函数中的MOV EDI, EDI指令感到困惑,其实只要看看strcmp.asm中的源代码就可以明白,那条MOV EDI, EDI完全是为了内存四字节对齐的。源代码中写的是align 4,在运行时,如果需要一个填充字节,则会填充一条NOP指令,如果需要两个字节来填充,则会填充一条MOV EDI, EDI指令,之所以不用两条NOP,是出于效率的考虑。
posted @ 2013-12-25 10:59  vcerror  阅读(238)  评论(0编辑  收藏  举报