Débuter en Assembleur ARM64 natif sous macOS Tahoe
Introduction
Ce guide vous explique comment écrire, assembler et exécuter votre premier programme en langage assembleur ARM64 natif sur un Mac Apple Silicon sous macOS Tahoe. Pas de machine virtuelle, pas d'émulation : le code s'exécute directement sur le processeur de votre Mac.
L'assembleur est le langage le plus proche du processeur. Chaque instruction que vous écrivez correspond à une opération élémentaire que le CPU exécute directement. C'est la façon la plus directe de comprendre ce que fait réellement votre machine.
Sur Apple Silicon (puce M1, M2, M3, M4…), l'architecture est ARM64 (AArch64). Les outils nécessaires (as et clang) sont fournis gratuitement par Apple via les Xcode Command Line Tools, installés automatiquement avec Homebrew.
Concepts de base à connaître
Avant de commencer, voici quelques notions indispensables pour comprendre le code que vous allez écrire :
- Registres (x0 à x30) : Ce sont de petites cases mémoire ultrarapides situées directement dans le processeur. En ARM64, les registres généraux s'appellent x0 à x30. On y place les valeurs sur lesquelles on veut travailler.
- Syscall : Un "appel système" est une demande adressée au noyau macOS pour effectuer une opération (écrire du texte, lire un fichier, quitter le programme…). On place le numéro du syscall dans le registre x16, les arguments dans x0, x1, x2…, puis on déclenche l'appel avec l'instruction svc #0x80.
- svc #0x80 : C'est l'instruction ARM64 qui déclenche un appel système sur macOS. Elle transfère le contrôle au noyau.
- Directive .global : Indique à l'assembleur que le symbole (ici _main) doit être visible de l'extérieur, comme point d'entrée du programme.
- Directive .align 2 : Aligne le code sur une frontière de 4 octets (2² = 4). ARM64 exige que les instructions soient alignées sur 4 octets.
- adr x1, msg : Calcule l'adresse mémoire du label msg et la place dans le registre x1.
- .ascii "…" : Directive qui insère une chaîne de caractères brute dans le programme (sans octet nul de fin, contrairement à .asciz).
Étape 1 — Vérifier les outils de base
Xcode Command Line Tools
Les outils as (assembleur) et clang (compilateur/lieur) font partie des Xcode Command Line Tools. Si Homebrew est déjà installé, ils sont déjà présents. Vérifiez-le avec :
xcode-select --version
La réponse attendue ressemble à : xcode-select version 2416.
Vérifier as et clang
Confirmez ensuite que l'assembleur et le compilateur sont bien disponibles, et qu'ils ciblent bien arm64-apple-darwin :
as --version && clang --version
(La mention Target: arm64-apple-darwin confirme que vous travaillez bien en natif ARM64, sans émulation Rosetta.)
Étape 2 — Consulter la liste des syscalls
Les numéros de syscalls macOS sont définis dans un header du SDK Apple, directement sur votre machine. C'est la référence officielle pour connaître le numéro à placer dans x16 :
cat /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/syscall.h
Les syscalls les plus utiles pour débuter :
- SYS_exit = 1 : Quitter le programme avec un code de retour.
- SYS_fork = 2 : Créer un processus fils.
- SYS_read = 3 : Lire depuis un descripteur de fichier (stdin = 0).
- SYS_write = 4 : Écrire vers un descripteur de fichier (stdout = 1, stderr = 2).
- SYS_open = 5 : Ouvrir un fichier.
- SYS_close = 6 : Fermer un descripteur de fichier.
(Ce fichier est généré automatiquement depuis le source du noyau XNU d'Apple. La liste complète contient 558 entrées à ce jour.)
Étape 3 — Créer un répertoire de travail
Créez un dossier dédié à vos expérimentations en assembleur et placez-vous dedans :
mkdir ~/asm-test && cd ~/asm-test
Étape 4 — Écrire le programme
Créer le fichier source
Créez le fichier hello.s (l'extension .s est la convention standard pour les fichiers source en assembleur) :
cat > hello.s << 'EOF'
.global _main
.align 2
_main:
; write(1, msg, 14)
mov x0, #1 ; fd = stdout
adr x1, msg ; pointeur vers le message
mov x2, #14 ; longueur (13 caractères + \n)
mov x16, #4 ; syscall write
svc #0x80
; exit(0)
mov x0, #0 ; code de retour
mov x16, #1 ; syscall exit
svc #0x80
msg:
.ascii "Hello, World!\n"
EOF
Comprendre le code ligne par ligne
- .global _main : Déclare _main comme point d'entrée visible. Sur macOS, le point d'entrée s'appelle _main (avec underscore), contrairement à Linux qui utilise _start.
- .align 2 : Garantit l'alignement du code sur 4 octets. Obligatoire en ARM64 : toutes les instructions font exactement 4 octets.
- mov x0, #1 : Place la valeur 1 dans le registre x0. Pour write, x0 est le descripteur de fichier : 1 = stdout (la sortie standard, votre terminal).
- adr x1, msg : Calcule l'adresse en mémoire de la chaîne msg et la met dans x1. C'est le deuxième argument de write : l'adresse du texte à afficher.
- mov x2, #14 : Place 14 dans x2. Troisième argument de write : le nombre d'octets à écrire. "Hello, World!" fait 13 caractères + 1 pour le retour à la ligne \n = 14.
- mov x16, #4 : Place le numéro du syscall write (4) dans x16. Sur macOS ARM64, le numéro de syscall va toujours dans x16.
- svc #0x80 : Déclenche l'appel système. Le noyau macOS exécute le syscall numéro 4 avec les arguments placés dans x0, x1, x2.
- mov x0, #0 : Code de retour 0 (succès) pour exit.
- mov x16, #1 : Numéro du syscall exit.
- .ascii "Hello, World!\n" : Insère la chaîne de caractères dans le segment de données du programme. Le \n est le caractère de retour à la ligne (octet 0x0A).
Convention d'appel ARM64 macOS (résumé)
Pour un syscall sur macOS ARM64, la convention est toujours la même :
- Numéro du syscall → x16
- 1er argument → x0
- 2e argument → x1
- 3e argument → x2
- Déclenchement → svc #0x80
- Valeur de retour → x0 (après le svc)
Étape 5 — Assembler et lier
On utilise clang qui appelle l'assembleur as puis le lieur en une seule commande :
clang -o hello hello.s && echo "OK"
Si la réponse est OK, le fichier exécutable hello a été créé. En cas d'erreur, le message indiquera la ligne fautive dans votre fichier .s.
(Pourquoi clang et pas directement as ? Clang gère automatiquement l'édition de liens, la signature de code macOS et les options de compilation. C'est la méthode recommandée pour les exécutables macOS.)
Étape 6 — Exécuter le programme
./hello
Résultat attendu :
Hello, World!
(Si vous observez un % collé en fin de ligne sans retour à la ligne, c'est que le compteur d'octets dans x2 est inférieur à la longueur réelle de la chaîne. Vérifiez que x2 = 14 pour inclure le \n.)
Récapitulatif des commandes
Voici l'ensemble du flux de travail en une seule séquence :
# Vérifier les outils
xcode-select --version
as --version && clang --version
# Consulter la liste des syscalls
cat /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/syscall.h
# Créer un répertoire de travail
mkdir ~/asm-test && cd ~/asm-test
# Écrire le programme
cat > hello.s << 'EOF'
.global _main
.align 2
_main:
mov x0, #1
adr x1, msg
mov x2, #14
mov x16, #4
svc #0x80
mov x0, #0
mov x16, #1
svc #0x80
msg:
.ascii "Hello, World!\n"
EOF
# Assembler et lier
clang -o hello hello.s && echo "OK"
# Exécuter
./hello
Conclusion
Vous avez écrit, assemblé et exécuté votre premier programme en assembleur ARM64 natif sur macOS, sans aucun outil tiers, uniquement avec ce qu'Apple fournit. Le programme dialogue directement avec le noyau macOS via deux syscalls : write pour afficher du texte, et exit pour terminer proprement.
La prochaine étape naturelle est d'explorer des syscalls supplémentaires (lecture au clavier avec read, gestion de fichiers avec open/close), d'apprendre à créer des boucles et des conditions en ARM64, ou d'utiliser otool -tv hello pour désassembler votre binaire et observer le code machine généré.
↑ Haut de page