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

时间:2018-09-26 14:26:44

标签: c assembly arguments cpu-registers calling-convention

我试图了解汇编如何与参数和返回值一起工作。

到目前为止,我已经了解到%eax是返回值,并且要加载单个参数,我需要使用%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  $8, %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  $0, %eax
  pushq %r10
  pushq %r11
  pushq %r14
  pushq %r15
  pushq %rbx
  callq _printf
  addq  $48, %rsp
  xorl  %eax, %eax
  addq  $8, %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  $8, %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  $0, %eax
  pushq %r10
  pushq %r11
  pushq %r14
  pushq %r15
  pushq %rbx
  callq _printf
  addq  $48, %rsp
  xorl  %eax, %eax
  addq  $8, %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的参数。

我想知道的是为什么在第一个参数加载到内存中之前,寄存器r10 r11 r14r15rsi { {1}} rdx rcx和'r9'在第一个参数之后加载到内存中?为何还要使用r8r14而不是r15r12

在这种情况下,为什么还要从堆栈指针中加上和减去8,这对寄存器的推入和弹出顺序有影响吗?

如果不让我知道,我希望所有子问题都与此问题有关。也请让我警惕我可能会弄错的任何知识。这是我通过将c转换为汇编中学到的东西。

1 个答案:

答案 0 :(得分:1)

首先,您似乎正在使用未经优化的代码,因此发生了不必要的事情。

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

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

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

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

  

我想知道的是为什么在第一个参数加载之前将寄存器r10 r11 r14和r15装入内存,而将第一个参数之后将rsi rdx rcx r8和'r9'寄存器装入内存?

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

  

为什么还要使用r14和r15而不是r12和r13?

同样,这就是编译器选择的。并非仅将这些用作临时位置。如果对代码进行了优化,则可能会使用更少的寄存器。

  

在这种情况下,为什么还要从堆栈指针中加上和减去8,这对寄存器的推入和弹出顺序有影响吗?

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