寄存器如何用作汇编中的参数?

How do registers work as arguments in assembly?

我想了解程序集如何处理参数和 return 值。

到目前为止,我了解到 %eax 是 return 值并且要加载单个参数,我需要将 %rip + offset 的有效地址加载到 %rid 中使用 leaq var(%rip), %rdi .

为了了解更多关于参数的信息,我创建了一个 c 程序,它接受 10 个(包括格式字符串在内的 11 个参数)来尝试找出寄存器的顺序。然后,我在 Mac.

上使用 gcc 将 C 代码转换为汇编代码

这是我使用的 C 代码:

#include <stdio.h>

int main(){
  printf("%s %s %s %s %s %s %s %s %s %s", "1 ", "2", "3", "4", "5", "6", "7", "8", "9", "10");
  return 0;
}

听到的是汇编输出:

.section  __TEXT,__text,regular,pure_instructions
  .macosx_version_min 10, 13
  .globl  _main                   ## -- Begin function main
  .p2align  4, 0x90
_main:                                  ## @main
  .cfi_startproc
## %bb.0:
  pushq %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset %rbp, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register %rbp
  pushq %r15
  pushq %r14
  pushq %rbx
  pushq %rax
  .cfi_offset %rbx, -40
  .cfi_offset %r14, -32
  .cfi_offset %r15, -24
  subq  , %rsp
  leaq  L_.str.10(%rip), %r10
  leaq  L_.str.9(%rip), %r11
  leaq  L_.str.8(%rip), %r14
  leaq  L_.str.7(%rip), %r15
  leaq  L_.str.6(%rip), %rbx
  leaq  L_.str(%rip), %rdi
  leaq  L_.str.1(%rip), %rsi
  leaq  L_.str.2(%rip), %rdx
  leaq  L_.str.3(%rip), %rcx
  leaq  L_.str.4(%rip), %r8
  leaq  L_.str.5(%rip), %r9
  movl  [=11=], %eax
  pushq %r10
  pushq %r11
  pushq %r14
  pushq %r15
  pushq %rbx
  callq _printf
  addq  , %rsp
  xorl  %eax, %eax
  addq  , %rsp
  popq  %rbx
  popq  %r14
  popq  %r15
  popq  %rbp
  retq
  .cfi_endproc
                                        ## -- End function
  .section  __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
  .asciz  "%s %s %s %s %s %s %s %s %s %s"

L_.str.1:                               ## @.str.1
  .asciz  "1 "

L_.str.2:                               ## @.str.2
  .asciz  "2"

L_.str.3:                               ## @.str.3
  .asciz  "3"

L_.str.4:                               ## @.str.4
  .asciz  "4"

L_.str.5:                               ## @.str.5
  .asciz  "5"

L_.str.6:                               ## @.str.6
  .asciz  "6"

L_.str.7:                               ## @.str.7
  .asciz  "7"


L_.str.8:                               ## @.str.8
  .asciz  "8"

L_.str.9:                               ## @.str.9
  .asciz  "9"

L_.str.10:                              ## @.str.10
  .asciz  "10"


.subsections_via_symbols

在那之后,我清除了删除一些 macOS 专用设置的代码?代码仍然有效。

.text
  .globl  _main                   ## -- Begin function main
_main:                                  ## @main
  pushq %rbp
  movq  %rsp, %rbp
  pushq %r15
  pushq %r14
  pushq %rbx
  pushq %rax
  subq  , %rsp
  leaq  L_.str.10(%rip), %r10
  leaq  L_.str.9(%rip), %r11
  leaq  L_.str.8(%rip), %r14
  leaq  L_.str.7(%rip), %r15
  leaq  L_.str.6(%rip), %rbx
  leaq  L_.str(%rip), %rdi
  leaq  L_.str.1(%rip), %rsi
  leaq  L_.str.2(%rip), %rdx
  leaq  L_.str.3(%rip), %rcx
  leaq  L_.str.4(%rip), %r8
  leaq  L_.str.5(%rip), %r9
  movl  [=12=], %eax
  pushq %r10
  pushq %r11
  pushq %r14
  pushq %r15
  pushq %rbx
  callq _printf
  addq  , %rsp
  xorl  %eax, %eax
  addq  , %rsp
  popq  %rbx
  popq  %r14
  popq  %r15
  popq  %rbp
  retq

.data
L_.str:                                 ## @.str
  .asciz  "%s %s %s %s %s %s %s %s %s %s"

L_.str.1:                               ## @.str.1
  .asciz  "1 "

L_.str.2:                               ## @.str.2
  .asciz  "2"

L_.str.3:                               ## @.str.3
  .asciz  "3"

L_.str.4:                               ## @.str.4
  .asciz  "4"

L_.str.5:                               ## @.str.5
  .asciz  "5"

L_.str.6:                               ## @.str.6
  .asciz  "6"

L_.str.7:                               ## @.str.7
  .asciz  "7"

L_.str.8:                               ## @.str.8
  .asciz  "8"

L_.str.9:                               ## @.str.9
  .asciz  "9"

L_.str.10:                              ## @.str.10
  .asciz  "10"

我了解到在代码的开头,基指针被压入堆栈,然后将其复制到堆栈指针中供以后使用。

leaq 然后将每个字符串加载到每个寄存器中,这些寄存器将用作 printf 的参数。

我想知道的是为什么寄存器r10r11r14r15在第一个参数加载到内存之前寄存器rsi rdx rcx r8 和 'r9' 在第一个参数之后加载到内存中?另外,为什么使用 r14r15 而不是 r12r13

此外,为什么在这种情况下从堆栈指针中添加和减去 8,寄存器的压入和弹出顺序是否重要?

我希望所有的子问题都与这个问题有关,如果没有请告诉我。也让我知道我可能会弄错的任何知识。这是我通过将c转换为汇编所学到的。

首先,看起来您正在使用未优化的代码,因此发生了不需要的事情。

查看未压入堆栈的 printf 调用之前的寄存器状态:

rdi = format string
rsi = 1
rdx = 2
rcx = 3
r8 = 4
r9 = 5

然后 6 .. 10 以相反的顺序压入堆栈。

这应该让您了解调用约定。前六个参数通过寄存器。其余参数在堆栈上传递。

What I want to know is why are registers r10 r11 r14 and r15 before the first argument is loaded into memory and that registers rsi rdx rcx r8 and 'r9' loaded into memory after the first argument?

这只是编译器选择的顺序。

Also why are r14 and r15 used instead of r12 and r13?

同样,这是编译器选择的。不是这些只是被用于临时位置。如果代码经过优化,可能会使用更少的寄存器。

Also why is 8 added and subtracted from the stack pointer in this case and does it matter which order the registers are pushed and popped?

它可能只是编译器生成的一些样板函数代码。