recursionassemblyx86x86-64setjmp

Assembly early return on a recursive function


This is more an academic exercise than anything else, but I'm looking to write a recursive function in assembly, that, if it receives and "interrupt signal" it returns to the main function, and not just the function that invoked it (which is usually the same recursive function).

For this test, I'm doing a basic countdown and printing one-character digits (8...7...6...etc.). To simulate an "interrupt", I am using the number 7, so when the function hits 7 (if it starts above that), it will return a 1 meaning it was interrupted, and if it wasn't interrupted, it'll countdown to zero. Here is what I have thus far:

.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

Does the above look like a valid approach, passing the memory address I want to go back to in rsi? The function was incredibly tricky for me to write, but I think mainly due to the fact that I'm pretty new/raw with assembly.


Solution

  • As well as returning to this alternate return address, you also need to restore the caller's (call-preserved) registers, not just the ones of your most recent parent. That includes RSP.

    You're basically trying to re-invent C's setjmp / longjmp which does exactly this, including resetting the stack pointer back to the scope where you called setjmp. I think a few of the questions in SO's tag are about about implementing your own setjmp / longjmp in asm.

    Also, to make this more efficient you might want to use a custom calling convention where the return address pointer (or a jmpbuf pointer after implementing the above) is in a call-preserved register like R15, so you don't have to save/restore it around print calls inside the body of your recursive function.