3
votes

I just started my assembly journey like recently, so obviously I'm a newbie, I've been writing fairly simple and basic programs and I just noticed something weird (to me).

a program giving the count of numbers in a table ending with 111 in binary

entry point:

#include <iostream>
#include <cstdlib>

extern "C" auto _start(void *, void *)->void;

auto print_msg(char *msg) {
    std::cout << msg;
}

auto print_int(uint64_t val) {
    std::cout << val;
}

auto main()->int {
    _start(print_int, print_msg);
    std::cout << std::endl;
    system("pause");
}

assembly:

.const
_tab    dw 65535, 61951, 61949, 61925, 61927, 61734, 61735, 61728
_LENGTH = ($ - _tab) / 2
_msg_1  db 'There are ', 0
_msg_2  db ' numbers ending with 111 in binary!', 0

.code
_start proc
         push     r15
         push     r14
         sub      rsp, 32 + 16
         mov      r14, rcx
         mov      r15, rdx
         xor      rcx, rcx
         xor      r9,  r9
         lea      r8,  _tab
_LOOP:   movzx    rax, word ptr [r8]
         and      rax, 111b
         cmp      rax, 111b
         jz       _INC
         jmp      _END_IF
_INC:    inc      rcx
_END_IF: inc      r9
         add      r8,  2
         cmp      r9,  _LENGTH
         jne      _LOOP
         mov      [rsp + 32], rcx
         lea      rcx, _msg_1
         call     r15
         mov      rcx, [rsp + 32]

         sub      rsp, 8
         call     r14
         add      rsp, 8

         lea      rcx, _msg_2
         call     r15
         add      rsp, 32 + 16
         pop      r14
         pop      r15
         ret
_start endp

end

if I comment "sub rsp, 8" and "add rsp, 8" around "call r14" out, the program will crash immediately, that doesn't make sense to me, I want to know why it happens, and also, if I replace "mov [rsp + 32], rcx" and "mov rcx, [rsp + 32]" with "push rcx" and "pop rcx", the output will be garbage, I'm also curious about that

1
Don't call your function _start; that's the default name for the entry point (at least on Linux). Programs that define their own _start don't link the standard CRT startup code, and _start is not even a function that can return; it's just an entry point. TL:DR: _start is a confusing function name for many readers.Peter Cordes

1 Answers

5
votes

The Windows x64 calling convention requires 16B alignment of RSP before a CALL instruction (but consequently guarantees rsp%16 == 8 on function entry, after call pushes a return address). This explains the sub rsp,8 around the function call.

It also requires 32B of shadow space (aka home space) reserved for the use of the called function, and that's what the sub rsp, 32 + 16 is doing.


It would be smart to just combine those together, and sub rsp, 32 + 16 + 8 on function entry, and then don't mess with RSP until the epilogue. (In a function that did an odd number of pushes, that take care of the +8 to realign the stack.)

[rsp+32] and higher bytes are safe from being stepped on by a call, lower bytes aren't.

The called function can freely make use of those 32 bytes above its return address. That explains why you get garbled output if you just push/pop around the CALL, because then your data will be in the shadow space.


See the tag wiki for ABI / calling convention links.