为什么参数通过 RDI、R10、R11 传递,而不是通过堆栈传递?

Why are parameters passed via RDI, R10, R11 and not via stack?

提问人:mc jazda 提问时间:11/11/2023 最后编辑:mc jazda 更新时间:11/11/2023 访问量:115

问:

因此,我正在编写一个简单的程序来生成曼德布洛特集合,它允许您决定使用用 C++ 或 assemlby 编写的函数来生成该集合。主程序是用C#编写的。我正在使用带有 VS2022、x64 和 AMD 处理器的 Windows 10。

我正在将 7 个参数(一个字节* 和六个整数)传递给汇编过程并尝试访问它们。前 4 个参数按预期通过 RCX、RDX、R8 和 R9 寄存器传递,但我无法访问其余 3 个参数。在 [RSP+40] 处有一些随机数据。然后我尝试使用堆栈帧方法(因为我声明了 2 个局部变量),但值看起来仍然是随机的。我终于注意到我缺少的第 5、6 和 7 个参数是通过 RDI、R10 和 R11 传递的。有没有这样的调用约定,或者我只是遗漏了什么?

顺便说一句,大约半年前,当我在汇编中尝试参数传递时,它们按预期传递,这意味着:RCX、RDX、R8、R9,然后到堆栈上。

这是我的汇编函数的声明:

[DllImport(masmDllPath)]
private static extern void generateMandelMASM(
    byte[] bmp, int rowCount, int rowNum, int resX, int resY, int alignment, int iterCount);

我正在使用 lambda expresion 通过委托在单独的线程中调用 C# 中提到的函数,如下所示:

for (int i = 0; i < settings.threadCount; i++)
{
    int localIter = i; // Necessary because lambda captures by reference, not value

    threads[i] = new Thread(() => generateMandel(bitmapRows[localIter], rowsPerThread[localIter],
        rowOffset[localIter], settings.resX, settings.resY, alignment, settings.iterationCount));
}

组装程序的一部分:

.code

generateMandelMASM PROC

    LOCAL alignment: DWORD
    LOCAL rowCount: QWORD
    
    ;--------- Prologue ---------
    push rbp
    mov rbp, rsp
    sub rbp, 16     ; subtracting number of locals * 8 bytes
    
    mov r11d, dword ptr [rbp+50o]
    mov alignment, r10d

在 DllImport 中显式声明调用约定不会更改参数的传递方式。

C# 程序集 x86-64 MASM 调用约定

评论

0赞 phuclv 11/11/2023
如果不需要与外部编译代码交互,则可以使用所需的任何自定义调用约定
2赞 harold 11/11/2023
40 是错误的偏移量,如果你不这样做,那将是正确的push rbp
2赞 harold 11/11/2023
不过,你写了什么代码来发现这一点?您显示的代码非常奇怪。 几乎是错误的(分配堆栈空间你需要从中减去,而不是),并且(使用八进制很奇怪,但没关系)既不考虑也不考虑sub rbp, 16rsprbp[rbp+50o]push rbpsub rbp, 16
2赞 harold 11/11/2023
此外,如果您使用 MASM,似乎会自动设置堆栈框架,或者人们这么说。这将在这里造成严重破坏。我从不使用那个功能。LOCAL
1赞 Peter Cordes 11/11/2023
调用方中将函数参数存储到堆栈的代码通常首先在寄存器中具有值。一些非参数传递寄存器在执行时碰巧持有有趣的值的情况并不少见,但这只是一个实现细节,恰好适用于此特定调用站点。使用调试器检查堆栈内存并验证参数是否位于它们应有的位置,并调试损坏的代码以访问堆栈帧中的正确位置。call

答:

-1赞 Blindy 11/11/2023 #1

x64 调用约定在使用堆栈之前首先使用寄存器,因为寄存器的速度要快得多(并且以后不必移动到寄存器中,因为它们已经在寄存器中)。您可以在此处阅读更多内容:

enter image description here

评论

0赞 Peter Cordes 11/11/2023
OP 知道前 4 个在寄存器中传递。他们错误地假设后面的参数不在堆栈上,因为他们的错误代码没有加载它们,并且他们注意到这个调用者碰巧在几个随机寄存器中留下了它们的副本。你的回答并不能纠正这一点,事实上,除非你查看表格图像的细节,否则听起来你同意他们关于传入 RDI、R10 和 R11 的观点。他们似乎认为 C# 正在使用自定义调用约定,因此链接标准调用约定也无法纠正这一点。
0赞 Blindy 11/12/2023
什么?链接 x64 的官方调用约定如何不直接和最终地回答他,这确实是它的工作方式,这里没有错误?另外,“查看表格图像的细节”到底是什么意思?如果我不看细节,我为什么要发布它?
0赞 prl 11/11/2023 #2

第五个参数位于函数调用前的 [rsp+0x20]。在调用指令推送 rip 和函数 prologue 推送 rbp 后,第五个参数位于 [rbp+0x30]。

评论

0赞 Ben Voigt 11/11/2023
为什么推送 64 位寄存器会将堆栈指针移动 128 位(0x10 字节)?
0赞 Ben Voigt 11/11/2023
没错,因为 x86_64 架构没有 LR 来保存返回地址。如果你把这个0x28中间条件包括在内,答案会更清楚。
1赞 prl 11/11/2023
通常,在将 rbp 设置为 rsp 后,您不会更改它。(我敢肯定这是一个错别字,它应该改变 rsp。然而,在 之后,第五个参数为 [rbp+0x40] 或 [rbp+100o]。sub rbp, 16sub rbp, 16