因此对于这样的基本C代码:
int main(int argc, char** argv){
if(argc < 2){
printf("1 argument needed!");
} else {
printf("\n -- %s Entered", argv[1]);
printf("\n%s -- is the first argument\n", argv[0]);
}
return 0;
}
转换如下:
评论的部分是我度过难关的地方:
var_10= qword ptr -10h
var_4= dword ptr -4
push rbp ; understandable due to convention
mov rbp, rsp ; same as above
sub rsp, 10h ; why the space allocation?
mov [rbp+var_4], edi ; ?
mov [rbp+var_10], rsi ; ?
#1 楼
首先,您必须了解所有这些东西都有规范。这些规范从一种汇编语言到另一种汇编语言,以及从一种操作系统到另一种汇编语言都不同。这些全局规范被称为应用程序二进制接口(ABI),除其他外,我们定义了“调用”功能约定。 IDAPro似乎发现您的程序遵循
cdecl
约定,但我怀疑它是正确的,我认为此处使用的调用约定是fastcall
SystemV ABI中的amd64
(请参阅有关调用约定的Wikipedia页面)。我的猜测是您正在使用Linux ... 因此,对
rpb
和rsp
的操作仅是在此处保存堆栈帧的先前状态,以便在退出该功能时将其恢复(您将rbp
的内容堆栈在堆栈上,希望在最后保留该功能时能够将其还原)。但是,您已经了解了。空间分配(
sub rsp, 10h
)来自以下事实:您必须存储一个4字节的int
(argc
)和一个指向8字节的char*
(argv
)的指针。 。我知道,添加时只有12个字节,而不是16个字节(10h
)。但是,当它们对齐时,CPU的内存访问已得到优化(这意味着它们从2的幂开始的地址开始,或者,如果考虑十六进制表示,则该地址必须以0
结尾)。因此,编译器决定舍入对齐数据所需的内存,并更有效地获取数据。然后,您拥有堆栈上可用的内存空间,现在放手获取数据。因此,为此,您必须在启动我们当前正在查看的功能之前知道调用者的功能。
实际上,在调用我们所在的函数之前,先前的函数已将我们函数的参数存储在某些寄存器中。在这里,重要的是要同意所有功能(调用方和被调用方)将使用同一组寄存器将参数从调用方传递给被调用方。
通常,第一个整数参数存储在
rdi
中,第二个存储在rsi
中(这里列出了寄存器的完整列表。在这里,由于第一个参数是argc
和int
,一个32位寄存器就足够了(4个字节),所以我们使用edi
代替rdi
并且,由于第二个参数是指针(argv
),因此我们需要完整的64位寄存器rsi
(8个字节)。我们该怎么做呢?好吧,我们宝贵地存储了
argc
和我们刚刚分配的内存中的堆栈帧中的argv
。请注意,存储在
rbp
中的地址在当前堆栈帧的整个生命周期内都不会改变(因为我们最终需要它执行leave
并恢复先前堆栈帧的rbp
的地址。因此,大多数编译器将使用q4 312079q作为参考点,以调用当前堆栈框架本地的变量。因此,从现在开始,rbp
指的是rbp+var_4
,argc
指的是rbp+var_10
。这就是您真正需要了解的Linux中
argv
约定的全部内容。现在,该程序应该对您更易懂了。希望对您有所帮助!
评论
很棒的解释!
–托马斯
18年7月22日在15:57
* nix世界中使用了fastcall吗?
– Biswapriyo
18年7月22日在19:08
我在应有的更广泛的范围内使用术语fastcall。我的意思是:一种调用约定比寄存器更好地使用寄存器来传递参数。我之所以说fastcall是因为它是最早使用注册人的应用程序之一,也是使用最广泛的应用程序之一。但是,用于传递参数的寄存器在Microsoft ABI和SysemV ABI之间发生了变化,因此我猜想Unix不会使用fastcall,即使使用了fastcall的基本原理(通过寄存器传递)。
–恐怖
18年7月22日在19:20