3

I saw the following rules from NASM's document:

The stack pointer %rsp must be aligned to a 16-byte boundary before making a call. Fine, but the process of making a call pushes the return address (8 bytes) on the stack, so when a function gets control, %rsp is not aligned. You have to make that extra space yourself, by pushing something or subtracting 8 from %rsp.

And I have a snippet of NASM assembly code as below:

The %rsp should be at the boundary of 8-bytes before I call the function "inc" in "_start" which violates the rules described in NASM's document. But actually, everything is going on well. So, how can I understand this?

I built this under Ubuntu 20.04 LTS (x86_64).

global _start

section .data
init:
    db 0x2

section .rodata
codes: 
    db '0123456789abcdef'

section .text
inc:
    mov rax, [rsp+8]  ; read param from the stack;
    add rax, 0x1
    ret

print:
    lea rsi, [codes + rax]
    mov rax, 1
    mov rdi, 1
    mov rdx, 1
    syscall
    ret

_start:
    ; enable AC check;
    pushf
    or dword [rsp], 1<<18
    popf

    mov rdi, [init]  ; move the first 8 bytes of init to %rdi;
    push rdi  ; %rsp -> 8 bytes;
    call inc
    pop r11  ; clean stack by the caller;
    call print

    mov rax, 60
    xor rdi, rdi
    syscall
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Jason Yu
  • 1,886
  • 16
  • 26
  • 2
    For your own functions you can use whatever convention you want. Note that standard conventions use registers to pass arguments (terms and conditions apply, read the fine print :)) Also, even with 3rd party functions, the sky does not necessarily fall down if you don't align `rsp` as required, it depends on what the called function is doing. Passing floats to vararg functions such as `printf` typically does crash with unaligned stack pointer. – Jester Jul 03 '20 at 11:53
  • 3
    BTW, `inc` is also the name of an instruction; I'd recommend not using it as a label name. Also, NASM documentation almost certainly didn't say "%rsp", unless it was quoting another doc (e.g. the x86-64 System V ABI), because `%` decorations are AT&T syntax, not NASM Intel syntax – Peter Cordes Jul 03 '20 at 11:55
  • And just for the record, this looks pretty inefficient, and is only handling 4-bit numbers, even though you load 8 *bytes* (16 nibbles). See [How to convert a binary integer number to a hex string?](https://stackoverflow.com/q/53823756) for simple and efficient loops that don't waste their time making function calls. I guess you're using a function just for the sake of learning about functions. – Peter Cordes Jul 03 '20 at 12:00
  • 1
    instead of `pop r11 ; clean stack by the caller;` you could better (not clobbering any registers) simply add the size of what you pushed to rsp – Tommylee2k Jul 03 '20 at 13:19
  • 2
    @Tommylee2k: `pop` with a dummy register is actually *more* efficient than `add rsp,8` between two other stack operations (like `ret` and `call`). That's why `clang` uses it, for example. [Why does this function push RAX to the stack as the first operation?](https://stackoverflow.com/a/45823778) / [What is the stack engine in the Sandybridge microarchitecture?](https://stackoverflow.com/q/36631576) / [NASM should I pop function argument after calling a function?](https://stackoverflow.com/q/60830765) – Peter Cordes Jul 03 '20 at 21:16

1 Answers1

8

The ABI is a set of rules for how functions should behave to be interoperable with each other. Each of the rules on one side are paired with allowed assumptions on the other. In this case, the rule about stack alignment for the caller is an allowed assumption about stack alignment for the callee. Since your inc function doesn't depend on 16-byte stack alignment, it's fine to call that particular function with a stack that's only 8-byte aligned.

If you're wondering why it didn't break when you enabled AC, that's because you're only loading 8-byte values from the stack, and the stack is still 8-byte aligned. If you did sub rsp, 4 or something to break 8-byte alignment too, then you would get a bus error.

Where the ABI becomes important is when the situation isn't one function you wrote yourself in assembly calling another function you wrote yourself in assembly. A function in someone else's library (including the C standard library), or one that you compiled from C instead of writing in assembly, is within its rights to do movaps [rsp - 24], xmm0 or something, which would break if you didn't properly align the stack before calling it.

Side note: the ABI also says how you're supposed to pass parameters (the calling convention), but you're just kind of passing them wherever. Again, fine from your own assembly, but they'll definitely break if you try to call them from C.