0

AT&T syntax, x86-64:

.text
.globl _start

_start:
        push %rbp       /* push base pointer on stack */
        mov %rsp, %rbp  /* base pointer points to top of the stack */
        sub $12, %rsp   /* allocate space for variable */
        push $3
        push $2
        push $1
        mov -4(%rbp), %rax
        mov %rbp, %rsp
        pop %rbp

I am trying to understand more about the stack. I want to access some variable from the stack (1, 2, 3) using an offset from rbp. My goal is to push the value into rax and then examine the register in a debugger. However, when I do so:

(gdb) x/d $rax
0x0:    Cannot access memory at address 0x0
Peter Cordes
  • 286,368
  • 41
  • 520
  • 731
chigger
  • 25
  • 4
  • This code doesn't make much sense. First of all, note that push instructions in 64-bit mode are 64-bit pushes. Stack adjustments or offsets that are not multiples of 8 are not very meaningful. What were you expecting to get? – Nate Eldredge May 09 '20 at 20:42
  • As long as you have your debugger going, you'll probably find it more informative to single-step the program, keeping an eye on the values of the `ebp` and `esp` registers and the contents of the stack. – Nate Eldredge May 09 '20 at 20:47
  • @NateEldredge I was expecting to get 3 because I'm subtracting 4 bytes from the base pointer, giving me the 1st local variable? Here is what I was referencing https://en.m.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames I probably read it wrong. – chigger May 09 '20 at 20:50
  • Also, your "allocate space for variable" comment makes it look like you think the values 1,2,3 are going to be pushed into the 12 bytes below ebp that you just "allocated" on the stack. But in fact they're pushed below that; pushes are always relative to the *current* value of the stack pointer. Your code ends up reading a qword of which 4 bytes came from the pushed value of rbp, which on entry to the program may perhaps be 0, and 4 bytes from whatever happened to be on the stack below that, which you never initialized and may also happen to be 0. – Nate Eldredge May 09 '20 at 20:50
  • 1
    Your link is about 32-bit assembly but you're writing 64-bit. So all the sizes and offsets are wrong. – Nate Eldredge May 09 '20 at 20:51
  • If you want to put local data on the stack, you can **either** put it there with `push`, **or** manually adjust the stack pointer with `sub` and then move your data into place with `mov`. But using push **and** adjusting the stack pointer is wrong. – Nate Eldredge May 09 '20 at 20:54

1 Answers1

2

push $3 is a 64-bit push. How many bytes does the push instruction push onto the stack when I don't specify the operand size?

But that doesn't even matter because you moved RSP to point 12 bytes below the saved RBP value (which RBP points at) before doing the first push.

At process startup in a static executable run by Linux, i.e. at _start the way you apparently built your program, all the registers (except RSP) are zeroed, and stack memory below the initial RSP starts out zeroed. This is not officially guaranteed by the ABI but is in practice how Linux works. That's why you loaded zeros.

mov -4(%rbp), %rax loads 8 bytes. The low 4 bytes of that load comes from space you skipped with sub $12, %rsp. The high 4 bytes are from the bottom of the saved RBP value. Both of those things are 0 because Linux zeroed them while initializing a fresh process.

The value you load from memory into RAX was never going to be a pointer so it makes no sense as an arg to GDB's x command. x eXamines memory at a given address. What would make sense is x /16gx $rsp to dump 16 qwords above RSP.

Also note that sub $12, %rsp looks like it was naively ported from 32-bit code. That misaligns the stack. At _start, it was already aligned by 16. _start is not a function; nothing called it and you can't ret from it. You don't need to save the old RBP, or even do anything with RBP at all; one pointer to the stack (RSP) is generally enough.

At the top of a function that does get called, RSP-8 would be 16-byte aligned, so one push would re-align the stack.

Peter Cordes
  • 286,368
  • 41
  • 520
  • 731
  • You were correct I naively ported from 32-bit because `push` wasn't being accepted by the assembler, now I know why. I was just trying to use the stack to store and fetch some variables and then load them into `rax`. Are you saying that with the `sub` I wasn't allocating space, I was rather just pushing `rsp` 12 bytes away from the bottom of the stack? Why do I then see that in other code as "allocating space" for variables? – chigger May 09 '20 at 22:37
  • @chigger: You *are* allocating space, and then you're doing more pushes below that, allocating more with each push. If you did `movl $1, -4(%rbp)` (or `8(%rsp)` before any pushes) you'd store a dword into the highest dword of the space you allocated below RBP. The values in RBP and RSP are pointers, and you're doing byte offsets from them. – Peter Cordes May 10 '20 at 00:35
  • Ah, I see, `push` moves the stack pointer, so what I was doing was redundant. – chigger May 10 '20 at 17:45
  • @chigger: yeah, you can see that while single-stepping in a debugger. Or read Intel's manual: https://www.felixcloutier.com/x86/push – Peter Cordes May 10 '20 at 18:04