Windows 64位参数传递规则

对于汇编语言,个人觉得更多的需要的是耐心,不像高级语言那样有繁杂的语法体系。

参数传递方式strcpy为何不安全中讲解了函数调用过程以及参数传递的方式,本文从汇编层面说一下Windows下x64的参数调用相应的规则。通过汇编能让你更彻底的了解到底什么是指针,参数如何传递的。

Microsoft x64 调用约定

适用范围:主要用于 Windows 操作系统上的 64 位程序。
参数传递规则:前四个整数类型(包括指针)或浮点类型的参数会依次通过寄存器 rcx、rdx、r8 和 r9 传递,后续的参数则通过栈来传递。例如下面的 C++ 函数调用:

#include <iostream>


int test_add(int a, int b, int c, int d, int e, int f) {

    std::cout << a + b + c + d + e + f << std::endl;
    return 0;
}
int main()
{
    test_add(1, 2, 3, 4, 5, 6);
    std::cout << "Hello World!\n";
}

main函数对应的汇编代码:

int main()
{
00007FF6C5702430  push        rbp  
00007FF6C5702432  push        rdi  
00007FF6C5702433  sub         rsp,0F8h  
00007FF6C570243A  lea         rbp,[rsp+30h]  
00007FF6C570243F  lea         rcx,[__29F341FE_ConsoleApplication1@cpp (07FF6C5713068h)]  
00007FF6C5702446  call        __CheckForDebuggerJustMyCode (07FF6C5701401h)  
00007FF6C570244B  nop  
    test_add(1, 2, 3, 4, 5, 6);
00007FF6C570244C  mov         dword ptr [rsp+28h],6  
00007FF6C5702454  mov         dword ptr [rsp+20h],5  
00007FF6C570245C  mov         r9d,4  
00007FF6C5702462  mov         r8d,3  
00007FF6C5702468  mov         edx,2  
00007FF6C570246D  mov         ecx,1  
00007FF6C5702472  call        test_add (07FF6C57012BCh)  
00007FF6C5702477  nop  
    std::cout << "Hello World!\n";
00007FF6C5702478  lea         rdx,[string "Hello World!\n" (07FF6C570AC28h)]  
00007FF6C570247F  mov         rcx,qword ptr [__imp_std::cout (07FF6C5711190h)]  
00007FF6C5702486  call        std::operator<<<std::char_traits<char> > (07FF6C570108Ch)  
00007FF6C570248B  nop  
}

注意其中对于test_add的调用,是从右到左依次入栈的。从汇编代码看与上面所说的规则是一致的。相应的栈内存如下(具体可参见Visual Studio快捷键介绍和高级玩法
在这里插入图片描述
00007FF6C5702472 call test_add (07FF6C57012BCh)看看这句话做了什么:
调用之前寄存器的值:

RAX = 0000000000000001 RBX = 0000000000000000 RCX = 0000000000000001 RDX = 0000000000000002 RSI = 0000000000000000 RDI = 0000000000000000 R8  = 0000000000000003 R9  = 0000000000000004 R10 = 0000000000000012 R11 = 000000326D4FFD00 R12 = 0000000000000000 R13 = 0000000000000000 R14 = 0000000000000000 R15 = 0000000000000000 RIP = 00007FF6C5702472 RSP = 000000326D4FFC50 RBP = 000000326D4FFC80 EFL = 00000206 

进入到test_add后寄存器的值:

RAX = 0000000000000001 RBX = 0000000000000000 RCX = 0000000000000001 RDX = 0000000000000002 RSI = 0000000000000000 RDI = 0000000000000000 R8  = 0000000000000003 R9  = 0000000000000004 R10 = 0000000000000012 R11 = 000000326D4FFD00 R12 = 0000000000000000 R13 = 0000000000000000 R14 = 0000000000000000 R15 = 0000000000000000 RIP = 00007FF6C5702370 RSP = 000000326D4FFC48 RBP = 000000326D4FFC80 EFL = 00000206 

对比可知rsp和rip发生了变化,查看此时堆栈的值:
在这里插入图片描述
这个值恰恰是call指令后面的地址。从这里可以看出call做了什么:

将call后面的指令压入堆栈中;rsp = rsp -8(栈是从高地址向低地址增长的)
跳转到call 函数所在位置 jump to fun_address rip指向新的地址

栈是1字节存储的,地址8字节需要-8,这也就解释了rsp为什么要-8。(000000326D4FFC50 - 000000326D4FFC48 = 8(十六进制))

注意: 00007FF6C5702430 push rbp 会将rsp-8

windbg源代码调试

设置源代码就能进行源码级别的调试了。VS本身就能调试,搞这么费劲干嘛那?当需要跨语言开发的时候,比如通过Java或者Python和C++协同开发的时候,通过attach进程,然后就能够调试C++代码了。 还有exe出现crash的时候,windbg就可以排上用场了。经典的蓝屏也可以通过windbg来分析。
在这里插入图片描述
只要有pdb,在另一台电脑上,有源代码,指定之后也可以进行调试。

原理:在 Visual Studio 中,你可以手动指定源代码文件的新路径,让调试器能够找到正确的源代码。 操作步骤
打开调试会话:在另一台电脑上打开需要调试的程序,并启动调试会话。 指定源代码路径:当调试器提示找不到源代码文件时,你可以在 “源文件”
窗口中右键单击,选择 “浏览”,然后手动指定源代码文件的新路径。这样,调试器就会使用新的路径来查找源代码文件。

查看调用堆栈

在这里插入图片描述
这里传递的是引用, 查看Args to Child(子程序)的参数共四个,按显示的是按照参数从左到右的顺序,这点很重要。

通过dd命令查看。这说明引用的本质还是指针
在这里插入图片描述
这一次参数使用指针,我们发现两者的值是一样的。这也说明了引用的本质是指针
在这里插入图片描述

查看汇编代码

我想查看调用这个函数的函数汇编代码,使用u命令即可查查看汇编代码。通过调用堆栈能够看到RetAddr或者Call site,基于此,我们就可以查看相应的汇编代码了。
在这里插入图片描述
通过RetAddr或者Call Site的地址,在这里两者的值是一样的。
在这里插入图片描述

这个是有跳转的
在这里插入图片描述
调用堆栈如下,上图对应的是下图中的最前面两个调用堆栈:
在这里插入图片描述
在这里插入图片描述

更多内容,欢迎关注我的微信公众号:半夏之夜的无情剑客。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

helloworddm

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值