我试图了解汇编如何与参数和返回值一起工作。
到目前为止,我已经了解到%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
r14
和r15
和rsi
{ {1}} rdx
rcx
和'r9'在第一个参数之后加载到内存中?为何还要使用r8
和r14
而不是r15
和r12
?
在这种情况下,为什么还要从堆栈指针中加上和减去8,这对寄存器的推入和弹出顺序有影响吗?
如果不让我知道,我希望所有子问题都与此问题有关。也请让我警惕我可能会弄错的任何知识。这是我通过将c转换为汇编中学到的东西。
答案 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,这对寄存器的推入和弹出顺序有影响吗?
可能只是编译器生成的一些样板函数代码。