Search code examples
linuxassemblyx86-64nasmfunction-parameter

Can't access parameter to assembly function


I am writing a function in assembly which essentially pushes args to the stack, then creates a stack frame (ie saving previous and moving the stack base pointer to the value of the stack pointer). I then try to access my argument by offsetting the base pointer by 4 + 2 (4 bytes being length of memory address, 2 being length of arg I want).

Here's my program (between the lines is the memory stuff):

section .data
    txt dw '25'

section .text
    global _start

_exit:
    mov rax, 60
    mov rdi, 0
    syscall

_print_arg:
    ;; function_initialisation
    push rbp ;; save old stackbase value
    mov rbp, rsp ;; set new stack base from tail of last value added to stack
    
    ;; actual function
    mov rax, 1
    mov rdi, 1
    ;;________________________
    lea rsi, [rbp + 4 + 2] ;; access stackbase, skip return address (4 bytes long) and go to start of our param (which is 2 bytes long / word)
    ;;¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬
    mov rdx, 2
    syscall
    
    ;; function_finalisation
    mov rsp, rbp ;; set stack pointer as this frames stack base (ie set ebp to tail of old function)
    pop rbp ;; set stack base pointer as original base of caller
    ret ;; return to last value stored on stack, which at this point is the implicitly pushed return address

_start:
    push word [txt] ;; push only arg to stack, word
    call _print_arg ;; implicitly push return address, call function
    pop rax ;; just a way to pop old value off the stack
    jmp _exit ;; exit routine, just a goto

I've tried directly printing the variable I push to stack in the first place, which works, so I know it's not a content-that-cannot-be-printed-issue. My guess is that my understanding of the stack and manipulating the pointer registers are fundamentally flawed.


Solution

  • the return address and rbp are 8 bytes length each on 64bit
    so the code should go like this

        section .data
        txt dw '25'
    
    section .text
        global _start
    
    _exit:
        mov rax, 60
        mov rdi, 0
        syscall
    
    _print_arg:
        push rbp        ;   rbp is 8 bytes, so rsp is decremented by 8
        mov rbp, rsp
        
        mov rax, 1
        mov rdi, 1
        lea rsi, [rbp + 8 + 8]  ;   here is the issue, [rbp + 8 + 8], that is
                                ;   8 for saved rbp, another 8 bytes of return address
                                ;   and you're pointing exactly to the first arg
        mov rdx, 2
        syscall
        
        mov rsp, rbp
        pop rbp
        ret 
    
    _start:
        push word [txt]     ;   push 2 bytes
        call _print_arg     ;   push 8 bytes of return address then jump to _print_arg
        pop rax             ;   no need to pop 8 bytes, since only 2 bytes were pushed
                            ;   so 'add rsp, 2' is appropriate
        jmp _exit
    

    Also, pop rax after the call adjusts RSP by 8, not balancing the push word. If you did that in a real function that was going to return, that would be a problem. (But _start isn't a function, and exiting still works after misaligning the stack and popping an extra 6 bytes.)

    Normally you'd only ever push multiples of 8 in the first place, e.g. by doing a movzx eax, word [txt] / push rax instead of a memory-source word push. Or push '25' instead of loading those 2 bytes from memory in the first place.