在递归函数上提前组装 return

Assembly early return on a recursive function

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

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

.globl _start
_start:

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

    # return 0;
    mov %eax, %edi
    mov , %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 , %rax        # linux syscall write
    mov , %rdi        # file descriptor: stdout=1
    lea -8(%rbp), %rsi  # memory location of string goes in rsi
    mov , %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 [=11=], %rdi
    jz end

    # imaginary interrupt on num=7
    cmp , %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 [=11=], %eax
    mov %rbp, %rsp
    pop %rbp
    ret

fast_ret:
    mov , %eax
    jmp *%rsi

上面的方法是否有效,在 rsi 中传递了我想返回的内存地址?这个函数对我来说写起来非常棘手,但我认为主要是因为我非常 new/raw 汇编。

除了 return转到这个备用 return 地址外,您还需要恢复呼叫者的 (call-preserved) 寄存器,而不仅仅是最近的 parent。这包括 RSP。

你基本上是在尝试 re-invent C 的 setjmp / longjmp 正是这样做的,包括将堆栈指针重置回你调用 setjmp 的范围.我认为 SO 的 标签中的一些问题是关于在 asm.

中实现你自己的 setjmp / longjmp

此外,为了提高效率,您可能需要使用自定义调用约定,其中 return 地址指针(或实现上述内容后的 jmpbuf 指针)位于 call-preserved 寄存器中,例如R15,因此您不必 save/restore 它围绕递归函数体内的打印调用。