Linking assembly with C on OS X

This is easy if you know how to do it, but it can be a pain to search for if you don’t, so here’s a short how-to.

First of all, we’ll want to write a bit of C code which will invoke our assembly routine:

#include <stdio.h>

extern int my_pow(int base, int exp);

int main(int argc, char const *argv[]) {
    int base, exp, result;
    base = 2;
    exp = 8;
    result = my_pow(2,8);
    printf("Result: %d\n", result);
    return 0;

Using clang on Mac OS X (you should’ve the latest Xcode installed), you can compile (but not link) this piece of code using the command

clang -o test.o -c test.c

This assumes that your C source file is called test.c. Clang’s -c compiles the given source file to an object file for later linking, and defaults to 64-bit mode. If you’d rather create a 32-bit object file, you can supply the -arch i386 parameter.

So now we’ve compiled our C source, so it’s time to write some assembly. But how does the C function pass the parameters, and how do we return the result? To answer such questions, it is often useful to look at the assembly generated from our C code, which can be obtained with clang -S test.c. The interesting part is the following (this is AT&T syntax):

movl    $2, %eax
movl    $8, %ecx
movl    %eax, %edi
movl    %ecx, %esi
callq   _my_pow
movl    %eax, -28(%rbp)
movl    -28(%rbp), %esi
movb    $0, %al
callq   _printf

So our operands, 2 and 8, are moved into eax and ecx respectively, which are free-for-use registers. In 32-bit C functions, operands are passed on the stack, but in the System V AMD64 ABI the first integer or pointer operands are passed in registers instead. As a result, our operands are moved to edi and esi, and our _my_pow procedure is called. The return value of the procedure is always stored in the eax (or rax) register.

The Intel architecture reference can be found here. A short overview of the x64 instructions and ABI is available here. Additionally, the x86-64 Wikipedia article gives a nice overview of available registers. {: .alert .alert-info }

So now it’s time to write some assembly (this is NASM Syntax now, which uses the Intel assembly style):

global _my_pow
section .text

    push    rbp             ; create stack frame
    mov     rbp, rsp

    cmp     edi, 0          ; Check if base is negative
    mov     eax, 0          ; and return 0 if so
    jl      end

    mov     eax, edi        ; grab the "base" argument
    mov     edx, esi        ; grab the "exponent" argument

    imul    eax, edi        ; eax * base
    sub     esi, 1          ; exponent - 1

    cmp     esi, 1          ; Loop if exponent > 1
    jg      multiply

    pop     rbp             ; restore the base pointer
    ret                     ; return from procedure

First of all, we have to define the _my_pow label to be global, because otherwise C won’t be able to call it. Note the underscore, since the C convention is to prepend one to all function labels. The “create stack frame” and “restore base pointer” parts are actually not necessary for our use case, since we do not use the stack, but I’ve added them for completeness.

As we’ve previously seen in the assembly generated from our C code, the “base” argument is passed in the edi register, and the “exponent” argument is passed in the esi register, so we start by moving the base to the result register eax, and then multiply eax and the base exponent - 1 times.

Assuming that you saved the assembly under the name asm.s, you can now generate an object file using the following NASM command, which will generate an asm.o object file:

nasm -f macho64 asm.s

After having generated both object files, you can now link them by invoking

clang -o pow asm.o test.o

This should generate an executable called pow, which prints “Result: 256”. For reference, here are my resulting files, including a simple Makefile.

Feedback is always welcome and I try to incorporate it into the post where possible. For reasons of Datensparsamkeit I do not provide a commenting solution here, however. You can reach me via email (see home page) or Twitter.