在递归函数上提前组装 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 的 setjmp 标签中的一些问题是关于在 asm.
中实现你自己的 setjmp / longjmp
此外,为了提高效率,您可能需要使用自定义调用约定,其中 return 地址指针(或实现上述内容后的 jmpbuf 指针)位于 call-preserved 寄存器中,例如R15,因此您不必 save/restore 它围绕递归函数体内的打印调用。
这比其他任何事情都更像是一个学术练习,但我希望在汇编中编写一个递归函数,如果它接收到并“中断信号”,它 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 的 setjmp 标签中的一些问题是关于在 asm.
此外,为了提高效率,您可能需要使用自定义调用约定,其中 return 地址指针(或实现上述内容后的 jmpbuf 指针)位于 call-preserved 寄存器中,例如R15,因此您不必 save/restore 它围绕递归函数体内的打印调用。