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.
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.