在反汇编旧的 Delphi 3 可执行文件时,我发现一些例程在寄存器 EAX、EDX 和堆栈中传递参数——但在 ECX 中没有!
对于这些例程,ECX 永远不会设置为“合理”值。这可以的小功能,这里面的代码可以看出做使用EAX,EDX,和堆栈,并且当这样的程序被称为“紧”内部块内,这应该是含自尽可能函数参数去. (这个版本的 Delphi 明显早于调用堆栈优化。)
这非常令人惊讶,因为根据Delphi 目前的所有者(以及到目前为止,根据我自己的经验),Delphi 一直使用register:
寄存器约定
在寄存器约定下,CPU 寄存器中最多传递三个参数,其余(如果有)则在堆栈上传递。参数按声明的顺序传递(与 pascal 约定一样),并且符合条件的前三个参数按该顺序在 EAX、EDX 和 ECX 寄存器中传递。
最初,我vcl30.dpl在标准库中的一些例程中发现了这一点,因此我认为这是该特定构建的一个特性(也许该库是使用不使用 ECX 的更旧版本的 Delphi 创建的)。但现在我也发现缺少 ECX 的用户例程!(在被调用函数和调用它时,该函数都有许多堆栈参数。)在被调用函数中,一个参数可能未被使用,但编译器不会知道这一点,它仍然会提供该参数。
这搞砸了我的拆卸;不仅我必须在原始函数的原型中提供一个虚拟参数,而且回溯也会失败,因为我的代码找不到对 ECX 的赋值,因此它假定被调用的函数只使用前 2 个参数。
这似乎违反了严格的register调用约定。是否有使用其他 2 个寄存器但不使用ECX的调用约定?
示例 – 在调用库函数之前使用和破坏 ECX 的片段:
8D4DFC lea ecx, [ebp+local_4]
33D2 xor edx, edx
8BC6 mov eax, esi
8B18 mov ebx, [eax]
FF5350 call [ebx+50h] <- GetSaveFileName; this uses ECX as a proper argument
A144831041 mov eax, [lpEnginePtr]
FF702C push [eax+2Ch] <- probably a local path
6870277355 push (address)"/Saved Games/"
FF75FC push [ebp+local_4]
8D45F8 lea eax, [ebp+local_8]
BA03000000 mov edx, 3
E869EAFCFF call System.@LStrCatN <- wot no ECX?
8B55F8 mov edx, [ebp+local_8]
A144831041 mov eax, [lpEnginePtr]
E860630600 call Engine.SaveFile
...
我反编译成
call GetSaveFileName (esi, 0, addressof (local_4))
eax = lpEnginePtr
push (eax.field_2C)
push ("/Saved Games/")
push (local_4)
call System.@LStrCatN (addressof (local_8), 3)
call Engine.SaveFile (lpEnginePtr, local_8)
例程GetSaveFileName使用 ECX 并破坏它,但不保存它:
GetSaveFileName:
53 | push ebx
8BD9 mov ebx, ecx
A140A08F55 mov eax, lpGameSettings
8B90E4000000 mov edx, [eax+0E4h]
8BC3 mov eax, ebx
B944267355 mov ecx, (address)".sav"
E856EBFCFF call System.@LStrCat3
5573263Ah:
5B | pop ebx
C3 | retn
库函数System.@LStrCatN确实根本不读取 ECX:
System.@LStrCatN:
push ebx
push esi
push edx
push eax <-- not in the Save List
mov ebx, edx
xor eax, eax
mov ecx, [esp+4*edx+10h] <-- overwrite ECX!
test ecx, ecx
jz 41304AA7h
41304AA4h:
add eax, [ecx-4]
41304AA7h:
dec edx
jnz 41304A9Ch
41304AAAh:
call System.@NewAnsiString
...
其他覆盖 ECX(写而不读)的例程确实将 ECX 保存在序言中。
这在前面在 IDA 中用于 EAX/EDX 的调用约定中已经提到过,但根据评论,这是一种误解,毕竟使用了 ECX。