main.cpp代码:
#include <iostream>
int main()
{
std::cout << "Hello World!\n";
}
Ghidra 中的拆卸:
*************************************************************
* FUNCTION
*************************************************************
int __cdecl main (int _Argc , char * * _Argv , char * * _E
assume GS_OFFSET = 0xff00000000
int EAX:4 <RETURN>
int ECX:4 _Argc
char * * RDX:8 _Argv
char * * R8:8 _Env
undefined1 Stack[-0x10]:1 local_10 XREF[1]:
140012292 (*)
undefined1 Stack[-0xd8]:1 local_d8 XREF[1]: 14001226a (*)
main XREF[1]: main:1400112cb (T) ,
main:1400112cb (j)
140012260 40 55 PUSH RBP
140012262 57 PUSH RDI
140012263 48 81 ec SUB RSP ,0xe8
e8 00 00 00
14001226a 48 8d 6c LEA RBP =>local_d8 ,[RSP + 0x20 ]
24 20
14001226f 48 8d 0d LEA _Argc ,[__6AFE2A9E_TestApplication@cpp ] = 01h
f0 0d 01 00
140012276 e8 63 f1 CALL __CheckForDebuggerJustMyCode void __CheckForDebuggerJustMyCod
ff ff
14001227b 90 NOP
14001227c 48 8d 15 LEA _Argv ,[s_Hello_World!_ ] = "Hello World!\n"
a5 89 00 00
140012283 48 8b 0d MOV _Argc ,qword ptr [->MSVCP140D.DLL::std::cout ] = 00021d22
0e ef 00 00
14001228a e8 f8 ed CALL std::operator<<<> basic_ostream<char,std::char_tra
ff ff
14001228f 90 NOP
140012290 33 c0 XOR EAX ,EAX
140012292 48 8d a5 LEA RSP =>local_10 ,[RBP + 0xc8 ]
c8 00 00 00
140012299 5f POP RDI
14001229a 5d POP RBP
14001229b c3 RET
尝试理解我为在 Ghidra 中练习反汇编而编写的这个非常简单的程序。
序言对我来说很有意义。将之前的
RBP
压入堆栈,将 RSP
减去 0xE8
(232 字节)来分配当前堆栈帧。
地址
RSP+0x20
存储在堆栈上的 RBP+0xD8
中,但随后事情对我来说就不再有意义了......
一个地址被加载到
_Argc
..."Hello, World!\n"
的地址被加载到_Argv
,这是一个char**
,所以是一个指向字符数组的指针...然后是一个四字指针(8字节),因为这个是一个 64 位应用程序,被加载到 _Argc
(参数计数),它是一个整数或 4 字节变量,然后(或多或少)调用 std::cout
。
为什么局部变量
_Argc
和 _Argv
在这里被重新调整用途来保存其他局部变量,其中一个是不同的且更大的数据类型?我完全误读了吗?我无法真正理解它,我唯一的假设是它是一些编译器优化魔法。
这对我来说是有意义的,只是吐槽,
std::cout
取决于它的调用约定,需要RDX
(_Argv
)中的字符数组来保存要输出的字符串。还要用ECX
(_Argc
)来保存要输出的字符串数量?
我可能完全错了。只是想了解一下 Visual Studio 2022 生成的程序集。任何帮助或见解将不胜感激。我在谷歌上的尝试没有结果。
反汇编程序是否也可能使用
_Argc
和 _Argv
代替 ECX
和 RDX
,因为这些寄存器用于将相应的参数传递给当前函数?感谢您的帮助。
因为它可以。编译器没有为临时变量保留新的空间,而是发现 argc 和 argv 从未使用过,并重新调整了它们的槽的用途。事实上,编译器足够聪明,可以在上次使用后执行此操作。
几年前,linux 内核邮件列表上存在一些争论,他们希望使用一个方法标记来阻止编译器执行此操作,因为它会导致系统调用门出现问题。 (通过将所有寄存器压入系统调用门来解决问题......)