Once I was wondering, how different the ‘hello world’ in assembler on Linux would be from one on MacOS.

It appears that not by much. Just some extra tricks required to make things work as expected: taking into account different write and exit syscall numbers, as well as adding extra parameters (-macosx_version_min and -lSystem) to the linker.

The code is pretty straightforward, as rediscovering ‘hello world’ is hard, thus just taking it from here:

global _main

mov rax, 0x2000004      ; syscall 4: write (
mov rdi, 1              ;    fd,
mov rsi, Msg            ;    buffer,
mov rdx, Len            ;    size
syscall                 ; )

mov rax, 0x2000001      ; syscall 1: exit (
mov rdi, 0              ;    retcode
syscall                 ; )

Msg db `Hello, world!\n`
Len: equ $-Msg

Next it needs to be compiled (by nasm in this case, can be installed with brew install nasm) and linked:

$ nasm -fmacho64 -o hello.o hello.asm
$ ld -o hello hello.o -macosx_version_min 10.13 -lSystem
$ ./hello
Hello, world

More details on NASM in MacOS here.

Setting up gdb requires some more tricks with code-signing. Those tricks are described here and here. Short dump of commands for reference:

# Keychain Access -> Certificate Assistant -> Create Certificate
# gdb-cert, Self Signed Root, Code Signing, Let me override defaults
# Specify a Location for The Certificate: Keychain = System
# On the certificate: Trust section, Code Signing = Always Trust

$ sudo killall taskgated
$ codesign -fs gdb-cert "$(which gdb)"
$ echo "set startup-with-shell off" > ~/.gdbinit

$ codesign --entitlements gdb.xml -fs gdb-cert /usr/local/bin/gdb
$ cat gdb.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<plist version="1.0">

After that gdb can be run without sudo:

$ gdb hello
GNU gdb (GDB) 8.3
Reading symbols from hello...
(No debugging symbols found in hello)
(gdb) b main
Breakpoint 1 at 0x1fd9
(gdb) r
Starting program: /path/to/hello
Thread 2 hit Breakpoint 1, 0x0000000000001fd9 in main ()
(gdb) s
Single stepping until exit from function main,
which has no line number information.
Hello, world!
[Inferior 1 (process 7485) exited normally]