Stephen Smith's Blog

Musings on Machine Learning…

Apple M1 Assembly Language Hello World

with 18 comments


Last week, we talked about using a new Apple M1 based Macintosh as a development workstation and how installing Apple’s development system XCode also installed a large number of open source development tools including LLVM and make. This week, we’ll cover how to compile and run a simple command line ARM Assembly Language Hello World program.

Thanks to Alex vonBelow

My book “Programming with 64-Bit ARM Assembly Language” contains lots of sample self contained Assembly Language programs and a number of iOS and Android samples. The command line utilities are compiled for Linux using the GNU tool set. Alex vonBelow took all of these and modified them to work with the LLVM tool chain and to work within Apple’s development environment. He dealt with all the differences between Linux and MacOS/iOS as well. His version of the source code for my book, but modified for Apple M1 is available here:

Differences Between MacOS and Linux

Both MacOS and Linux are based on Unix and are more similar than different. However there are a few differences of note:

  • MacOS uses LLVM by default whereas Linux uses GNU GCC. This really just affects the command line arguments in the makefile for the purposes of this article. You can use LLVM on Linux and GCC should be available for Apple M1 shortly.
  • The MacOS linker/loader doesn’t like doing relocations, so you need to use the ADR rather than LDR instruction to load addresses. You could use ADR in Linux and if you do this it will work in both.
  • The Unix API calls are nearly the same, the difference is that Linux redid the function numbers when they went to 64-bit, but MacOS kept the function numbers the same. In the 32-bit world they were the same, but now they are all different.
  • When calling a Linux service the function number goes in X16 rather than X8.
  • Linux installs the various libraries and includes files under /usr/lib and /usr/include, so they are easy to find and use. When you install XCode, it installs SDKs for MacOS, iOS, iPadOS, iWatchOS, etc. with the option of installing lots for versions. The paths to the libs and includes are rather complicated and you need a tool to find them.
  • In MacOS the program must start on a 64-bit boundary, hence the listing has an “.align 2” directive near top.
  • In MacOS you need to link in the System library even if you don’t make a system call from it or you get a linker error. This sample Hello World program uses software interrupts to make the system calls rather than the API in the System library and so shouldn’t need to link to it.
  • In MacOS the default entry point is _main whereas in Linux it is _start. This is changed via a command line argument to the linker.

Hello World Assembly File

Below is the simple Assembly Language program to print out “Hello World” in a terminal window. For all the gory details on these instructions and the architecture of the ARM processor, check out my book.

// Assembler program to print "Hello World!"
// to stdout.
// X0-X2 - parameters to linux function services
// X16 - linux function number
.global _start             // Provide program starting address to linker
.align 2

// Setup the parameters to print hello world
// and then call Linux to do it.

_start: mov X0, #1     // 1 = StdOut
adr X1, helloworld // string to print
mov X2, #13     // length of our string
mov X16, #4     // MacOS write system call
svc 0     // Call linux to output the string

// Setup the parameters to exit the program
// and then call Linux to do it.

mov     X0, #0      // Use 0 return code
       mov     X16, #1     // Service command code 1 terminates this program
       svc     0           // Call MacOS to terminate the program

helloworld:      .ascii  "Hello World!\n"


Here is the makefile, the command to assemble the source code is simple, then the command to link is a bit more complicated.

HelloWorld: HelloWorld.o
ld -macosx_version_min 11.0.0 -o HelloWorld HelloWorld.o -lSystem -syslibroot
`xcrun -sdk macosx --show-sdk-path` -e _start -arch arm64

HelloWorld.o: HelloWorld.s
as -o HelloWorld.o HelloWorld.s

The xcrun command is Apple’s command to run or find the various SDKs. Here is a sample of running it:

stephensmith@Stephens-MacBook-Air-2 ~ % xcrun -sdk macosx –show-sdk-path
objc[42012]: Class AMSupportURLConnectionDelegate is implemented in both ?? (0x1edb5b8f0) and ?? (0x122dd02b8). One of the two will be used. Which one is undefined.
objc[42012]: Class AMSupportURLSession is implemented in both ?? (0x1edb5b940) and ?? (0x122dd0308). One of the two will be used. Which one is undefined.
objc[42013]: Class AMSupportURLConnectionDelegate is implemented in both ?? (0x1edb5b8f0) and ?? (0x1141942b8). One of the two will be used. Which one is undefined.
objc[42013]: Class AMSupportURLSession is implemented in both ?? (0x1edb5b940) and ?? (0x114194308). One of the two will be used. Which one is undefined.
stephensmith@Stephens-MacBook-Air-2 ~ %

After the ugly warnings from Objective-C, the path to the MacOS SDK is displayed.

Now we can compile and run our program.

stephensmith@Stephens-MacBook-Air-2 Chapter 1 % make -B
as -o HelloWorld.o HelloWorld.s
objc[42104]: Class AMSupportURLConnectionDelegate is implemented in both ?? (0x1edb5b8f0) and ?? (0x1145342b8). One of the two will be used. Which one is undefined.
objc[42104]: Class AMSupportURLSession is implemented in both ?? (0x1edb5b940) and ?? (0x114534308). One of the two will be used. Which one is undefined.
ld -macosx_version_min 11.0.0 -o HelloWorld HelloWorld.o -lSystem -syslibroot `xcrun -sdk macosx –show-sdk-path` -e _start -arch arm64 
stephensmith@Stephens-MacBook-Air-2 Chapter 1 % ./HelloWorld 
Hello World!
stephensmith@Stephens-MacBook-Air-2 Chapter 1 %


The new Apple M1 Macintoshes are running ARM processors as part of all that Apple Silicon and you can run standard ARM 64-bit Assembly Language. LLVM is a standard open source development tool which contains an Assembler that is similar to the GNU Assembler. Programming MacOS is similar to Linux since both are based on Unix and if you are familiar with Linux, most of your knowledge is directly applicable.


Written by smist08

January 8, 2021 at 10:31 am

18 Responses

Subscribe to comments with RSS.

  1. Hi Stephen! Thank you for your article. There seems to be an error in your Introduction.

    It didn’t compile in my LLVbrain. Got an error in line 2.

    > development system XCode
    > ……………………………….^
    > Unknown keyword.

    Maybe AvB may help 😉

    Best wishes

    Peter H aus D an der E

    January 8, 2021 at 6:59 pm

  2. […] Apple M1 Assembly Language Hello World 14 by joubert | 7 key aspects to get started in the world of online women dating […]

  3. In the code listing, the comment states “Service command code 93 terminates this program” but the actual code uses #1. I take it the larger value is the renumbered Linux call, and the smaller is the original 32-bit / M1 call?

    Micah Dubinko

    January 14, 2021 at 7:35 pm

    • Thanks, I edited from the 64-bit linux version and fixed the call but not the comment. I updated the article. Cheers


      January 14, 2021 at 7:53 pm

  4. Xcode


    January 14, 2021 at 8:59 pm

    • Everything else may be camelCase, but not Xcode.


      January 15, 2021 at 12:50 am

  5. […] week’s article on an Assembly Language “Hello World” program hit number 1 on Hacker News and based on the […]

  6. […] Apple M1 Assembly Language Hello World via […]

  7. thank you for sharing this

    Hassaan hameed

    February 5, 2021 at 8:51 am

  8. >// linux write system call

    Do you mean the UNIX system call?


    March 2, 2021 at 12:32 pm

    • MacOS system call really.


      March 2, 2021 at 6:30 pm

      • Sure, that too, but this comes from the UNIX heritage of macOS, so it’s not unique to macOS.

        It’s part of an abstract specification of UNIX and a concrete implementation of macOS.

        The numbers for the syscalls are different from what Linux uses for the same syscalls, but they are fundamentally the same syscalls.


        March 3, 2021 at 12:12 am

  9. Hey Stephen,

    Could you explain why you replaced “ldr X1, =helloworld” with “adr X1, helloworld” in this example? I am aware that this is necessary on M1 Mac, but wondering about broader justification of this.



    April 15, 2021 at 8:10 am

    • I believe its because Apple chose to not have their program loader support relocations. As a result perhaps they can load programs a bit faster by not having to fix up a bunch of addresses. Remember LDR places the address of helloworld in memory and then loads that with a PC relative address and this memory address needs to be fixed when the program loads.


      April 15, 2021 at 3:41 pm

      • You can make the linker optimize the relocations for you with .loh markup in the assembly


        February 6, 2022 at 4:15 pm

  10. […] you’ve read Steven Smith’s article about building Hello World in assembly language for M1 Macs, you may now be wondering why my code […]

  11. I am on mini m1 with Monterey 12.4. When I execute the ld command, I get “library not found for -lSystem”. So far, I have not found a solution to this. Can you offer some help? thanks.

    H. Schdwetman

    June 17, 2022 at 2:13 pm

    • This worked for me:
      -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib


      September 12, 2022 at 11:32 am

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: