Skip to content

Instantly share code, notes, and snippets.

@FiloSottile
Last active March 23, 2024 12:28
Show Gist options
  • Save FiloSottile/7125822 to your computer and use it in GitHub Desktop.
Save FiloSottile/7125822 to your computer and use it in GitHub Desktop.
NASM Hello World for x86 and x86_64 Intel Mac OS X (get yourself an updated nasm with brew)
; /usr/local/bin/nasm -f macho 32.asm && ld -macosx_version_min 10.7.0 -o 32 32.o && ./32
global start
section .text
start:
push dword msg.len
push dword msg
push dword 1
mov eax, 4
sub esp, 4
int 0x80
add esp, 16
push dword 0
mov eax, 1
sub esp, 12
int 0x80
section .data
msg: db "Hello, world!", 10
.len: equ $ - msg
; /usr/local/bin/nasm -f macho64 64.asm && ld -macosx_version_min 10.7.0 -lSystem -o 64 64.o && ./64
global start
section .text
start:
mov rax, 0x2000004 ; write
mov rdi, 1 ; stdout
mov rsi, msg
mov rdx, msg.len
syscall
mov rax, 0x2000001 ; exit
mov rdi, 0
syscall
section .data
msg: db "Hello, world!", 10
.len: equ $ - msg
@deadalnix
Copy link

@dpwell , if you have a solution, I'm taking.

@jmatth11
Copy link

jmatth11 commented Sep 3, 2015

@dpwell nasm should be downloadable through brew. because when i do 'brew search nasm' it comes back with nasm. but if you can't find it through brew just download the newest version from their website because I had the same problem you had and the way I fixed it was just updating nasm to the newest version. But I did mine through brew.

@eulerworks
Copy link

(change your program name to suit, but this worked on 10.10.4 with nasm from brew)

nasm -fmacho simple.s && ld -macosx_version_min 10.7.0 -o simple simple.o && ./simple

uname -a
14.4.0 Darwin Kernel Version 14.4.0: Thu May 28 11:35:04 PDT 2015; root:xnu-2782.30.5~1/RELEASE_X86_64 x86_64

gcc --version
gcc (GCC) 4.9.2 20141029 (prerelease)
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

@adriansr
Copy link

adriansr commented Oct 9, 2015

If someone is having the "fatal: No section for index 2 offset 0 found" error even after replacing the mov with a lea instruction, this is a problem of recent (2.11.x) nasm available from brew. Works fine with nasm 2.10 from nasm's website.

@MareoRaft
Copy link

I'm on Mavericks 10.9.5. I'm using nasm 2.11.08_1 from brew. I have gcc:

Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin13.4.0
Thread model: posix

I got

% nasm -f macho world.asm
world.asm:9: error: symbol `rax' undefined
world.asm:10: error: symbol `rdi' undefined
world.asm:11: error: symbol `rsi' undefined
world.asm:12: error: symbol `rdx' undefined
world.asm:15: error: symbol `rax' undefined
world.asm:16: error: symbol `rdi' undefined

but fixed it by changing the "r"s to "e"s in the file. Then it compiled with

% ld -macosx_version_min 10.7.0 -lSystem -o world world.o

successfully. But when I execute it, I get

% ./world
zsh: illegal hardware instruction  ./world

@D3m0t3p
Copy link

D3m0t3p commented Mar 9, 2016

i have the same problem as you @MareoRaft

@Leandros
Copy link

@MareoRaft @D3m0t3p You're using the 64-Bit assembly, but compile it as 32-Bit. It's obvious that this doesn't work.

@ourui
Copy link

ourui commented May 26, 2016

@dpwell I install nasm from brew, and the same problem with you. When I type 'nasm -v' , the result is 'NASM version 0.98.40 (Apple Computer, Inc. build 11) compiled on Feb 10 2016' . I doubt that the terminal has use system nasm. So, I close the terminal and restart, and it works. Now nasm can recognised 'macho64'. Type 'nasm -v' again, the result is 'NASM version 2.12.01 compiled on Mar 23 2016'.

@tilarids
Copy link

I've done a 32-bit version that works on 10.11.5: https://gist.github.com/tilarids/3fe6d8499ad25b722c307cfcfb817dc2
It seems padding and symbol table are now necessary.

@lwickstr
Copy link

for those having trouble with the "install nasm from brew" directions above, here is a guide to install brew:
http://www.howtogeek.com/211541/homebrew-for-os-x-easily-installs-desktop-apps-and-terminal-utilities/
(i only needed the one line command:
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
)
then running:
nasm install brew
will give you nasm version 2.12+

@shoaibmohi
Copy link

How to run NASM code in Xcode on MacOS Sierra.
Please Help.

@sguillia
Copy link

When I run the program, the return value is 1, but if I change sub esp, 12 to sub esp, 4 as it was in old revisions, then it works, return value 0. Why does it happen like that ? On OS X Sierra 10.12.6

@kwccoin
Copy link

kwccoin commented Oct 15, 2017

I also need to do :

push   dword 0
mov    eax,  1  ; 0 ; should not be 1
sub    esp,  4  ; 12 ; not sure about this and note there is a push dword 0 here
int    0x80

to work under OS X Sierra 10.12.6.


Also, the first few line is mysterious

section .text
start:
push    dword msg.len ;4 bytes
push    dword msg     ;4 bytes 
push    dword 1       ;4 bytes. and there is push dword 1 here
mov     eax, 4
sub     esp, 4        ;reserves 4 bytes more 
int     0x80
add     esp, 16       ;restore esp to original value

; usually should be 
; push ebp
; mov  ebp, esp
; push ??? the 4 bytes not sure what that for
; ...
; pop  ??? the 4 bytes
; pop  ebp

May be c calling standard, Win32 calling standard or ...

@HGarron
Copy link

HGarron commented Dec 8, 2017

FiloSottile:

Thank you very much. I am trying to learn assembly language on a Mac.

@2019osbjoh
Copy link

When attempting to link I got this:

Undefined symbols for architecture x86_64:
"_main", referenced from:
implicit entry/start for main executable
ld: symbol(s) not found for inferred architecture x86_64

anybody able to shed a little light?

@r-novel
Copy link

r-novel commented Mar 4, 2019

Hello, @2019osbjoh !
I have replaced start to _main and it works for me. (osx version: 10.12; nasm version: 2.14.02)
src:

global _main
section .text
_main:
    mov     rax, 0x2000004
    mov     rdi, 1
    mov     rsi, msg
    mov     rdx, msg.len
    syscall
    mov     rax, 0x2000001
    mov     rdi, 0
    syscall
section .data
msg:    db      "Hello, world!", 10
.len:   equ     $ - msg

build:
$ nasm -f macho64 hello-world.asm && ld -macosx_version_min 10.12 -lSystem -o hello-world hello-world.o

@lancejpollard
Copy link

lancejpollard commented Mar 21, 2019

You can make a custom global start/_main to name it however you want with this:

ld -e start -macosx_version_min 10.13.0 -static -o hello-world hello-world.o

Using "static" linking instead of dynamic, in which case you can't control the name.

@ylluminate
Copy link

ylluminate commented Jun 13, 2020

Anyone interested in assembly or native x64 generation for macOS in a really simple / clean language should consider offering some input on macOS x64 generation for V: vlang/v#5374 (updated)

Seeing the native x64 generation in action on Linux is quite impressive (https://twitter.com/v_language/status/1271163362739707910) and it sure would be useful for folks to have it on macOS as well.

@dplyukhin
Copy link

dplyukhin commented Dec 31, 2020

On 11.1 I've been getting:

ld: warning: PIE disabled. Absolute addressing (perhaps -mdynamic-no-pic) not allowed in code signed PIE, but used in _main from hello.o. To fix this warning, don't compile with -mdynamic-no-pic or link with -Wl,-no_pie

To get rid of this warning, you have two options:

  1. Pass the -no-pie option to the linker, like so: nasm -f macho64 hello-world.asm && ld -macosx_version_min 10.12 -lSystem -o hello-world hello-world.o -no-pie.
  2. (Seems to be the preferred option) Use RIP-relative addresses, as described here. Updating r-novel's code:
default rel           ; add this line
global _main
section .text
_main:
    mov     rax, 0x2000004
    mov     rdi, 1
    lea     rsi, [msg]        ; replace `mov rsi, msg` with this
    mov     rdx, msg.len
    syscall
    mov     rax, 0x2000001
    mov     rdi, 0
    syscall
section .data
msg:    db      "Hello, world!", 10
.len:   equ     $ - msg

@alblue
Copy link

alblue commented Jan 8, 2021

For macOS 11 it's necessary to specify the location of the System library if you get an error indicating it's not found, such as -L/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib when running the linker

@quackduck
Copy link

Thanks @alblue!

@simohauml
Copy link

I got error:
ld: library not found for -lSystem

@francoisdillinger
Copy link

@simohauml
Make sure you have xcode-cli installed. Go to Library/Developer/CommandLineTools/ and make sure SDKs is there. I had the same problem and it turns out I had Xcode but not the CLI installed, so my CommandLineTools didn't have SDK and I was getting the same error even using Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib with my linker.

@geekgogie
Copy link

Any idea,

$ ld -macosx_version_min 10.15 -lSystem -o hello hello.o -no_pie
Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64

fails

But,

$ ld -macosx_version_min 10.7.0 -lSystem -o hello hello.o -no_pie
ld: warning: building for macOS 10.7.0 is deprecated

works but warning.

@geekgogie
Copy link

Ahh, I got it working now. I replace the start with _main as it simply skip the main function. I think there's an option to avoid this but I have no idea yet what option in ld

@kc2kth
Copy link

kc2kth commented Jun 24, 2021

Ahh, I got it working now. I replace the start with _main as it simply skip the main function. I think there's an option to avoid this but I have no idea yet what option in ld

Would love to see how you got this going. Not sure where specifically you replaced "start" with "main". I tried changing both, I got as far as:

asm/hello % ld -macosx_version_min 10.12.0 -L/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem -o hello hello.o
Undefined symbols for architecture x86_64:
"_main", referenced from:
implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64

My code now looks like this:

global main
section .text
main:
mov rax, 0x2000004 ;write
mov rdi, 1 ;stdout
mov rsi, msg
mov rdx, msg.len
syscall
mov rax, 0x2000001 ;exit
mov rdi, 0
syscall
section .data
msg: db "Hello world!", 10
.len equ $ - msg

@geekgogie
Copy link

Ahh, I got it working now. I replace the start with _main as it simply skip the main function. I think there's an option to avoid this but I have no idea yet what option in ld

Would love to see how you got this going. Not sure where specifically you replaced "start" with "main". I tried changing both, I got as far as:

asm/hello % ld -macosx_version_min 10.12.0 -L/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem -o hello hello.o
Undefined symbols for architecture x86_64:
"_main", referenced from:
implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64

My code now looks like this:

global main
section .text
main:
mov rax, 0x2000004 ;write
mov rdi, 1 ;stdout
mov rsi, msg
mov rdx, msg.len
syscall
mov rax, 0x2000001 ;exit
mov rdi, 0
syscall
section .data
msg: db "Hello world!", 10
.len equ $ - msg

Hi @kc2kth, literally use "_main" not "main". Then try to build again.

@kc2kth
Copy link

kc2kth commented Jun 24, 2021

Hi @kc2kth, literally use "_main" not "main". Then try to build again.

Great, that did work. Happy to have a working "jumping off" point now.

Thanks!

@nullhook
Copy link

nullhook commented Jan 16, 2022

for macOS users you have two options. You can statically link or dynamically link which will require libSystem.*
ld links dynamically to your system libraries by default.

Static linking:
ld -e "start" -static -o 64 64.o

Dynamic linking:
ld -macosx_version_min 10.7.0 -e "start" -no_pie -lSystem -L/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -o 64 64.o

If the linker cannot find specific symbols you can inspect your object file to see all symbols
(note: your start symbol doesn't need to be renamed, just needs to be referenced as-is from the symbol table)

objdump -t ./64.o

SYMBOL TABLE:
0000000000000027 l     O __DATA,__data msg
000000000000000e l       *ABS* msg.len
0000000000000000 g     F __TEXT,__text start

@DiaperGlu
Copy link

DiaperGlu commented Apr 13, 2022

A couple of things about the hello world above.

OS X deprecated syscall. It still works but if you look at the compiled code, the gas compiler is replacing the syscall with imported function calls. Since they are function calls, you are supposed to align stack. It seems to work without it, but if you want to align the stack to a 16 byte boundary without worrying about where it is when the program starts, you can AND the stack pointer with FFFFFFFFFFFFFFF0 at entry.

I think exported functions have to have _ in front of them in gas. Something to do with avoiding name collisions with imports maybe..

Mac also changed where the system libraries are for security reasons for Big Sur. If you want your program to run on a system that doesn't have the xtools command line tools installed, you have to link in a certain way. I posted a question about this on the Mac developer forums and Eskimo explained how to figure it out using -v with a c program. The link to the discussion is here: https://developer.apple.com/forums/thread/669094
The short answer is, this link command works: ld -e _myhelloworldstart -no_uuid -no_eh_labels -demangle -dynamic -arch x86_64 -platform_version macos 11.0.0 11.1 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -o helloworld64 -L/usr/local/lib helloworld64.o -lSystem
The key is the -syslibroot command.
I think if you use OTOOL to look what the library is linking against, you can see what's going on.

This hello world works for gas with the filename helloworld64gas.s:

.text

_omyhelloworldmessage:
.asciz "\nHello World!\n\n"

.globl _myhelloworldstart
_myhelloworldstart:

/* align the stack regardless of where it is when this function is called */
andq $0xfffffffffffffff0, %rsp

movq $1, %rdi /* stdout handle /
leaq _omyhelloworldmessage(%rip), %rsi /
pmessageaddress /
movq $15, %rdx /
messagelength */
call _write

call _exit

with this assembly command:

as -mmacosx-version-min=11.0 ./helloworld64gas.s -o helloworld64gas.o

and this link command:

ld -e _myhelloworldstart -no_uuid -no_eh_labels -demangle -dynamic -arch x86_64 -platform_version macos 11.0.0 11.1 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -o helloworld64gas -L/usr/local/lib helloworld64gas.o -lSystem

If you are interested, and up for something different,
I wrote my own assembler which is available on here on github under the DiaperGlu repository.
This hello world code works with that assembler:

NEW-FLAT-OSYMBOL-BUF

X86-WORDLIST >SEARCH-ORDER

DECIMAL

OSYMBOL omyhelloworldmessage
13 CODE-U8, 10 CODE-U8, // pushes crlf to the buffer
$" Hello World!" PCURRENTCOMPILEBUFFER @ $>BUF // pushes the message to the buffer
13 CODE-U8, 10 CODE-U8, // appends another crlf
13 CODE-U8, 10 CODE-U8, // and another crlf

OSYMBOL myhelloworldstart
// align the stack regardless of where it is when this function is called
HEX -10 N RSP AND,

1 N RDI MOV, // stdout handle
EH. omyhelloworldmessage [O] RSI LEA, // pmessageaddress
EH. myhelloworldstart EH. omyhelloworldmessage - N RDX MOV, // messagelength

IMP CALL, OSYMBOL-IMPORT write

IMP CALL, OSYMBOL-IMPORT exit

PCURRENTCOMPILEBUFFER @
EH
1 EH[ND]
BUF>NEWEXPORTIMPORT.OBUF

DUP $" helloworld64.o" SAVEFILE$
FREEBUFFER

SEARCH-ORDER> DROP

FREE-FLAT-OSYMBOL-BUF

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment