assembly x64汇编中的阶乘程序是否始终返回1?

ifsvaxew  于 2022-11-30  发布在  其他
关注(0)|答案(1)|浏览(130)

我正在尝试用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
quhf5bfb

quhf5bfb1#

下面是我看到的错误:
1.你在反向操作堆栈。x86上的堆栈向下增长,所以你应该在进入函数时从%rsp中减去,并在返回时加上。

  1. 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用于调试,您将遇到问题。
  2. fact函数会破坏ABI中指定为保留调用的%rbx。这可能会在main返回时导致崩溃或其他错误行为。

相关问题