我正在尝试用x64汇编语言编写一个递归阶乘程序。出于某种原因,无论我做什么,我总是得到一个结果1。我意识到我不一定需要声明一个局部变量,但我这样做是因为我在编写一个编译器(为了简单起见)。
.section .data
outfmt: .asciz "%d\n"
.section .text
.extern printf
.global main
fact:
addq $16, %rsp
movq %rdi, 0(%rsp) # Convert parameter to local var.
movq 0(%rsp), %rax # Move rdi into rax.
movq $1, %rbx # Move 0 into rbx.
cmp %rax, %rbx # If rdi <= 1, return 1.
jle if
jmp else
else:
subq $1, 0(%rsp) # n = n - 1
call fact # Call fact
imulq 0(%rsp), %rax # Multiply n with fact n - 1
jmp factend
if:
movq $1, %rax # Return 1
jmp factend
factend:
subq $16, %rsp
ret
main:
movq $5, %rdi
call fact
movq %rax, %rsi
leaq outfmt, %rdi
movq $0, %rax
call printf
ret
1条答案
按热度按时间quhf5bfb1#
下面是我看到的错误:
1.你在反向操作堆栈。x86上的堆栈向下增长,所以你应该在进入函数时从
%rsp
中减去,并在返回时加上。cmp %rax, %rbx ; jle if
是向后的。当rbx <= rax
时它会跳到if
,但你想相反,所以你可能想要cmp %rbx, %rax
。Jcc
助记符采用英特尔语法设计,非常直观,您可以编写cmp rax, rbx ; jle if
,以便在rax <= rbx
时跳转。AT&T像其他指令一样反转cmp
的操作数,不幸的是,这意味着条件跳转助记符不再匹配。1.在递归调用
fact
之前,您忘记了将参数加载到%rdi
中。如果您要坚持将变量保留在堆栈上的方案,您可能需要movq 0(%rsp), %rdi
。1.差1错误。在递归调用
fact
之前,您从变量中减去1,因此它等于return (x <= 1) ? 1 :(x-1)*fact(x-1);
。这意味着您的程序将计算4的阶乘而不是5。1.您没有按照SysV ABI的要求维护16字节堆栈对齐(我假设您正在遵循该要求):Why does the x86-64 / AMD64 System V ABI mandate a 16 byte stack alignment?特别是,您调用库函数
printf
时堆栈未对齐,这可能会导致崩溃。在调用printf
之前,您需要将main
中的堆栈指针向下调整8个字节。一个简单的方法是在main
的开头和结尾压入和弹出一些寄存器。在
fact
中也不维护堆栈对齐,但在这种情况下,这是无害的,因为fact
不调用除自身之外的任何函数。然而,如果它这样做了,例如,如果您添加了一个printf
用于调试,您将遇到问题。fact
函数会破坏ABI中指定为保留调用的%rbx
。这可能会在main
返回时导致崩溃或其他错误行为。