1

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Salih MSA
  • 120
  • 1
  • 7
  • 1
    Return addresses are 8 bytes (qword) in x86-64, and so is `push rbp`. Use a debugger to look at registers and memory. (Also, the standard calling convention passes args in regs, and stack args are passed in 8-byte slots. You're free to do whatever weird thing you want, like misaligning the stack with a 2-byte `push word` of ASCII values if you want, as long as you don't plan to call any libc functions. But you still have to match the caller with the callee.) – Peter Cordes Aug 13 '21 at 00:42
  • @PeterCordes you've given me alot to go ahead and research, thank you so much! the tutorials I've seen are all just in 32-bit, so I've been filling in the bridge to x86-64 myself. – Salih MSA Aug 13 '21 at 00:51
  • 1
    I'd suggest looking at C compiler output, like [How to remove "noise" from GCC/clang assembly output?](https://stackoverflow.com/q/38552116). See also [What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64](https://stackoverflow.com/q/2535989) - the user-space function-calling convention is very similar to the system-call calling convention. – Peter Cordes Aug 13 '21 at 00:53
  • this may warrant a separate question, but I tried offsetting my ebp by 8 + 8 and it's gets me the address I want - shouldn't it be 8 + 8 + 2, considering how 8 + 8 would just get me beyond the old ebp and to the start of the return address and not the start of the txt variable? @PeterCordes – Salih MSA Aug 13 '21 at 01:20
  • 1
    Saved-RBP plus return address is 16 bytes, just like how in 32-bit mode the first stack arg is at `[ebp+8]` if you set up EBP as a traditional frame pointer. Single-step with a debugger to see how that happens. If you tried to actually use `ebp` in 64-bit mode, you'd be truncating the stack address to 32 bits, making an invalid pointer. (So your write system-call would return `-EFAULT` if you used it with LEA, or a segfault if you dereferenced.) – Peter Cordes Aug 13 '21 at 01:39
  • 3
    Intel processors are little endian and the address of a multibyte value is the address of the lowest byte. So your two byte parameter is in bytes [rbp+16] and [rbp+17]. It is addressed using the address of the lowest byte which is rbp+16. – prl Aug 13 '21 at 06:24
  • 3
    [rbp] is the address of the saved rbp value. [rbp+8] is the address of the return address. [rbp+16] is the address of the parameter. – prl Aug 13 '21 at 06:28
  • 2
    You may be better off trying harder to find a 64-bit tutorial. There are so many differences between 32 and 64 bit that you will waste a lot of time writing code that you eventually have to throw out and write again, and learn incorrect habits that you will then have to unlearn. There are some things from 32-bit code that will carry over and provide useful insight into 64-bit code, but you kind of have to be an expert already to know which ones they are. – Nate Eldredge Aug 13 '21 at 16:04

1 Answers1

3

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.

Errorist
  • 224
  • 2
  • 6
  • You can [edit] answers instead of deleting and posting a new one. I just edited your previous answer since you deleted it while I was writing a comment to point out the stack adjustment mismatch bug in the question that you added comments for in this. I guess at this point there's no reason to undelete the original, but you might want to copy some of what I added. Especially the part about not using memory-source push word at all, to avoid misaligning the stack. – Peter Cordes Aug 13 '21 at 17:48