首页 > 解决方案 > 递归函数的装配提前返回

问题描述

这比其他任何事情都更像是一项学术练习,但我希望在汇编中编写一个递归函数,如果它接收并“中断信号”,它将返回到主函数,而不仅仅是调用它的函数(通常是相同的递归函数)。

对于这个测试,我正在做一个基本的倒计时并打印一个字符的数字(8...7...6...等)。为了模拟一个“中断”,我使用了 number 7,所以当函数达到 7 时(如果它开始于它之上),它将返回一个被中断的1意思,如果它没有被中断,它会倒计时到零. 到目前为止,这是我所拥有的:

.globl _start
_start:

    # countdown(9);
    mov $8, %rdi
    call countdown

    # return 0;
    mov %eax, %edi
    mov $60, %eax
    syscall

print:
    push %rbp
    mov %rsp, %rbp

    # write the value to a memory location
    pushq %rdi # now 16-byte aligned
    add $'0', -8(%rbp)
    movb $'\n', -7(%rbp)

    # do a write syscall
    mov $1, %rax        # linux syscall write
    mov $1, %rdi        # file descriptor: stdout=1
    lea -8(%rbp), %rsi  # memory location of string goes in rsi
    mov $2, %rdx        # length: 1 char + newline
    syscall

    # restore the stack
    pop %rdi
    pop %rbp
    ret;

countdown:
    # this is the handler to call the recursive function so it can
    # pass the address to jump back to in an interrupt as one of the
    # function parameters
    # (%rsp) currntly holds the return address, and let's pass that as the second argument
    mov %rdi, %rdi      # redundant, but for clarity
    mov (%rsp), %rsi    # return address to jump
    call countdown_recursive


countdown_recursive:

    # bool countdown(int n: n<10, return_address)

    # ...{
    push %rbp
    mov %rsp, %rbp

    # if (num<0) ... return
    cmp $0, %rdi
    jz end

    # imaginary interrupt on num=7
    cmp $7, %rdi
    jz fast_ret

    # else...printf("%d\n", num);
    push %rsi
    push %rdi
    call print
    pop %rdi
    pop %rsi

    # --num
    dec %rdi

    # countdown(num)
    call countdown_recursive

end:
    # ...}
    mov $0, %eax
    mov %rbp, %rsp
    pop %rbp
    ret

fast_ret:
    mov $1, %eax
    jmp *%rsi

以上看起来像一个有效的方法,传递我想要返回的内存地址rsi吗?这个函数对我来说写起来非常棘手,但我认为主要是因为我对汇编很陌生/原始。

标签: recursionassemblyx86x86-64setjmp

解决方案


除了返回到这个备用返回地址之外,您还需要恢复调用者的(保留调用的)寄存器,而不仅仅是您最近的父母的寄存器。这包括RSP。

您基本上是在尝试重新发明 C 的setjmp/longjmp正是这样做的,包括将堆栈指针重置回您调用的范围setjmp。我认为 SO 的标签中的一些问题是关于在 asm 中实现你自己的 setjmp / longjmp。

此外,为了提高效率,您可能希望使用自定义调用约定,其中返回地址指针(或实现上述内容后的 jmpbuf 指针)位于像 R15 这样的调用保留寄存器中,因此您不必保存/围绕递归函数体内的打印调用恢复它。


推荐阅读