当前位置: 首页 > 知识库问答 >
问题:

在windowsx64调用约定中,R10-R15寄存器用于什么?

翟棋
2023-03-14

从Intel在https://software.intel.com/en-us/articles/introduction-to-x64-assembly上介绍x64汇编,

  • RCX、RDX、R8、R9用于整数和指针参数,从左到右顺序为
  • 寄存器RAX、RCX、RDX、R8、R9、R10和R11被认为是易失的,必须在函数调用时被销毁。
  • RBX、RBP、RDI、RSI、R12、R14、R14和R15必须保存在任何使用它们的函数中。
sub_18000BF10   proc near 
lpDirectory     = qword ptr -638h
nShowCmd        = dword ptr -630h
Parameters      = word ptr -628h

             sub     rsp, 658h
             mov     r9, rcx
             mov     r8, rdx
             lea     rdx, someCommand ; "echo "Hello""...
             lea     rcx, [rsp+658h+Parameters] ; LPWSTR
             call    cs:wsprintfW
             xor     r11d, r11d
             lea     r9, [rsp+658h+Parameters] ; lpParameters
             mov     [rsp+658h+nShowCmd], r11d ; nShowCmd
             lea     r8, aCmdExe     ; "cmd.exe"
             lea     rdx, Operation  ; "open"
             xor     ecx, ecx        ; hwnd
             mov     [rsp+658h+lpDirectory], r11 ; lpDirectory
             call    cs:ShellExecuteW
             mov     eax, 1
             add     rsp, 658h
             retn
sub_18000BF10    endp

或者如果我们可以在用户定义函数中那样做,而系统API函数却不那样做,这有什么原因吗?我想在寄存器中快速调用参数会比检查(抵消堆栈)更有效。

共有1个答案

汪才英
2023-03-14

Windowsx64调用约定的目的是通过将4个寄存器参数转储到阴影空间中,创建所有参数的连续数组,从而使实现变量函数(如printf和scanf)变得容易。大于8个字节的参数是通过引用传递的,因此每个参数总是正好取一个参数传递槽。

给定这种设计限制,更多的寄存器参数将需要更大的阴影空间,这会为没有很多参数的小函数浪费更多的堆栈空间。

是的,更多的注册参数通常会更有效率。但是如果被调用者想要立即使用不同的参数进行另一个函数调用,那么它就必须将所有的注册参数存储到堆栈中,因此对有用的注册参数的数量是有限制的。

不管有多少寄存器用于参数传递,您都需要调用保留寄存器和调用破坏寄存器的良好组合。R10和R11是被调用的scratch regs。一个用asm编写的透明包装函数可以使用它们来获取暂存空间,而不干扰RCX、RDX、R8、R9中的任何参数,也不需要在任何地方保存/恢复调用保留寄存器。

r12.r15是调用保留的寄存器,只要您在返回之前保存/恢复它们,您就可以用于任何您想要的用途。

或者我们是否可以在用户定义的函数中做到这一点

是的,当从asm调用到asm时,您可以自由地建立您自己的调用约定,这取决于操作系统施加的约束。但是,如果希望异常能够通过这样的调用(例如,如果一个子函数回调到某个可以抛出的C++),则必须遵守更多的限制,例如创建释放元数据。如果没有,你几乎可以做任何事情。

请参阅我的“选择您的呼叫约定”,以便将参数放在您想要的地方。回答CodeGolf问答“使用x86/x64机器代码打高尔夫球的技巧”。

您还可以在任何您想要的寄存器中返回,并返回多个值。(例如,asmstrcmpmemcmp函数可以返回EAX中不匹配的-/0/+差值,并返回RDI中的不匹配位置,因此调用方可以使用其中之一或两者。)

相比之下,x86-64 System V ABI通过寄存器中的前6个整数参数和XMM0.7中的前8个FP参数。(Windows x64传递堆栈上的第5个参数,即使它是FP,并且前4个参数都是整数。)

所以其他主要的x86-64调用约定确实使用了更多的arg传递寄存器。它不使用阴影空间;它在RSP下面定义了一个红色区域,可以防止被异步攻击。小的leaf函数仍然可以避免操纵RSP来保留空间。

有趣的事实:R10和R11也是x86-64 SYSV中的非arg传递调用阻塞寄存器。有趣的事实#2:syscall会破坏R11(和RCX),因此Linux使用R10而不是RCX来向系统调用传递参数,但在其他方面使用与用户空间函数调用相同的register-arg传递约定。

另请参见为什么Windows64在x86-64上使用与所有其他操作系统不同的调用约定?更多的猜测和信息,为什么微软做的设计选择他们的调用约定。

x86-64 System V使得实现变量函数(索引参数的代码更多)变得更加复杂,但它们通常很少见。大多数代码不会在sscanf吞吐量上出现瓶颈。阴影空间通常比红区更糟糕。最初的Windows x64约定不通过值传递向量参数(__m128),因此Windows上有第二个64位调用约定,称为VectorCall,它允许高效的向量参数。(通常没什么大不了的,因为大多数使用向量参数的函数都是内联的,但是SIMD数学库函数会从中受益。)

在低8(不需要REX前缀的RAX.RDI原始寄存器)中传递更多的参数,以及更多不需要REX前缀的被调用破坏的寄存器,可能对内联足够多的代码的代码大小有好处,从而不会进行大量的函数调用。您可以说,Window选择调用保留更多的非REX寄存器对于包含函数调用的循环的代码更好,但是如果您对短调用进行大量的函数调用,那么它们将从更多不需要REX前缀的被调用破坏的暂存寄存器中获益。我想知道MS在这方面花了多少心思,或者他们在选择哪一个低8的寄存器将被调用保留时,是否只是保留了类似于32位调用约定的东西。

x86-64 System V的弱点之一是没有调用保留的XMM寄存器。因此任何函数调用都需要溢出/重新加载任何FP变量。有一对组合,比如xmm6和xmm7的低128位或64位,可能会很好。

 类似资料:
  • Windows上是否有任何方法可以解决XMM寄存器保留在函数调用中的要求?(除了将其全部写入汇编中) 不幸的是,我有许多AVX2内在函数因此而臃肿。 例如,这将被编译器(MSVC)放置在函数的顶部: 00007FF9D0EBC602 vmovaps xmmword ptr[rsp 1490h]、xmm6 00007FF9 D0EBC60B vmovaps XMMWORDPTR[rsp 1480h]

  • x86 正如所料,前4个参数通过x64中的寄存器传递。 其余参数按与x86中相同的顺序放在堆栈中。 与x86相反,在x64中我们不使用指令。相反,我们在的开头保留足够的堆栈空间,并使用指令将参数添加到堆栈中。 在x64中,在之后,不会进行堆栈清理,而是在的末尾。 这就引出了我的问题:

  • 我需要只使用%RAX、%RBX、%RCX、%RDX、%RSI和%RDI(还有%RSP和%RBP)编写像素化汇编代码 GCC编写的程序集代码: 已将%dl更改为%rdx:

  • 我正在使用registerForActivityResult,因为StartActivityForResult函数已被弃用。因此,我们正朝着使用registerForActivityResult的新方式迁移。这在活动中非常有效。然而,当在片段中使用这个函数时,永远不会调用回调函数。我还必须提到,父活动和其他子片段以旧的方式处理一些结果。调试代码时,我看到调用了父级的onActivityResult

  • 2.1 通用 CPU 寄存器 CPU 的寄存器能够对少量的数据进行快速的存取访问。在 x86 指令集里,一个 CPU 有 八个通用寄存器:EAX, EDX, ECX, ESI, EDI, EBP, ESP 和 EBX。还有很多别的寄存器,遇 到的时候具体讲解。这八个通用寄存器各有不同的用途,了解它们的作用对于我们设计调试 器是至关重要的。让我们先简略的看一看每个寄存器和功能。最后我们将通过一个简单