Forketyfork
Forketyfork

Reputation: 7810

Trivial macOS assembly 64-bit program has incorrect stack alignment

I'm trying to create a trivial console assembly program with arguments. Here's the code:

.section __TEXT,__text

.globl _main

_main:

    movl    $0, %edi
    callq   _exit

Here's the compile and link script:

as test.s -o test.o
ld test.o -e _main -o test -lc 

Now the program either fails with a segmentation fault, or executes without error, depending on the argument count:

$ ./test
Segmentation fault: 11
$ ./test 1
$ ./test 1 2
$ ./test 1 2 3
Segmentation fault: 11
$ ./test 1 2 3 4
$ ./test 1 2 3 4 5
Segmentation fault: 11

And so on.

Under the LLDB I see a more informative error:

Process 16318 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x00007fffad14b2fa libdyld.dylib`stack_not_16_byte_aligned_error
libdyld.dylib`stack_not_16_byte_aligned_error:
->  0x7fffad14b2fa <+0>: movdqa %xmm0, (%rsp)
    0x7fffad14b2ff <+5>: int3   

libdyld.dylib`_dyld_func_lookup:
    0x7fffad14b300 <+0>: pushq  %rbp
    0x7fffad14b301 <+1>: movq   %rsp, %rbp

Indeed, if I stop the execution at the first line of the program, I see that the stack is not 16-byte aligned for some argument count, while it is aligned for another. Although the System V ABI for AMD64 states that:

The stack pointer holds the address of the byte with lowest address which is part of the stack. It is guaranteed to be 16-byte aligned at process entry.

What am I missing?

Upvotes: 2

Views: 641

Answers (1)

fuz
fuz

Reputation: 93172

I guess on OS X the kernel doesn't guarantee a stack alignment on entry to main. You have to manually align the stack. Fortunately, this is rather easy, just zero-out the least four bits of the stack pointer. In case you need to fetch the argument vector or other data, make sure to store the original stack pointer somewhere:

_main:
    mov %rsp,%rax    # copy original stack pointer
    and $-16,%rsp    # align stack to 16 bytes
    push %rax        # save original stack pointer
    push %rbp        # establish...
    mov %rsp,%rbp    # ...stack frame
    ...              # business logic here
    leave            # tear down stack frame
    pop %rsp         # restore original stack pointer
    ...              # exit process

You also need to mentally keep track of your stack alignment. It might be easier to have main do nothing but the stack alignment and then calling your actual main function so you can use a normal stack frame in it:

_main:
    mov %rsp,%rbx    # save original stack pointer
    and $-16,%rsp    # align stack to 16 bytes
    call _my_main    # call actual main function
    mov %rbx,%rsp    # restore original stack pointer
    ...              # exit process

For your particular example program, you can just use this minimal code:

_main:
    and $-16,%rsp    # align stack to 16 bytes
    xor %edi,%edi    # set exit status to zero
    call _exit       # exit program

Upvotes: 2

Related Questions