Home > Software design >  Can I save the return address in a register and push it back onto the stack before `ret` in NASM x86
Can I save the return address in a register and push it back onto the stack before `ret` in NASM x86

Time:11-28

I have a _write function that expects the pointer to the string and the string length as parameters.

section .data
  string_literal_at_index_0: db 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x77,0x6f,0x72,0x6c,0x64,0x21

global _start
section .text
_start:
   push string_literal_at_index_0 ; argument for the write function
   push 12 ; argument for the write function
   call _write
   mov rax, 60
   mov rdi, 0
   syscall
   
_write:
   pop r8 ; pop return address so i can acess the arguments
   mov rax, 1
   mov rdi, 1
   pop rdx
   pop rsi
   syscall
   push r8 ; push the return address back
   ret

I have to pop the return address so I can access the parameters for the function

NOTE : I know that accessing them using EBP is much better, but I have to use this way

My question is : is it okay to do it this way and will it mess up the stack?

NOTE : This might be a very stupid question but I am a beginner in assembly. So correct me if anything I said in this question is wrong!

CodePudding user response:

Yes, you can do that, but you don't have to. You can access stack args relative to RSP directly like mov rsi, [rsp 16], no need for RBP especially in simple functions that don't need to change RSP at all.

Both the standard x86-64 calling conventions pass args in registers. x86-64 System V (used on non-Windows) is almost the same for function calls as it is for syscall, so you don't need stack args at all until you have more than 6 integer/pointer args.

But if you really want to invent your own stack-args-only calling convention that has the callee pop them from the stack (like 32-bit stdcall), yes you could do this. (Normally you'd still use mov to access the stack, not pop restore the return address, and return with ret 16 at the end.)

A much better custom calling convention for this write(1, buf, size) wrapper function would be to take the pointer in RSI and the size in RDX, so you don't need to copy them anywhere, just set up RAX and RDI (__NR_write = STDOUT_FILENO = 1), then syscall / ret.


In the standard x86-64 System V convention, you need to return with RSP unmodified, so you'd need sub rsp, 16 (or two dummy pushes) before the final push/ret to put the return address back where it originally was, instead of where the highest arg was.

Your caller will be expecting to be able to access its own stack frame relative to RSP, so it needs to know where RSP will be pointing after a call returns. As long as caller and callee agree on what should happen, you're fine.

(See Agner Fog's calling conventions doc, and look at compiler output for simple functions to see how they access args and pass them in registers. How to remove "noise" from GCC/clang assembly output?)

  • Related