Intel x86 Assembly Language & Microarchitecture Tutoriel
Démarrer avec Intel x86 Assembly Language & Microarchitecture
Recherche…
Remarques
Cette section fournit une vue d'ensemble de ce qu'est x86 et pourquoi un développeur peut vouloir l'utiliser.
Il devrait également mentionner tous les grands sujets dans x86, et établir un lien avec les sujets connexes. La documentation de x86 étant nouvelle, vous devrez peut-être créer des versions initiales de ces rubriques connexes.
x86 langage d'assemblage
La famille des langages d'assemblage x86 représente des décennies d'avancées sur l'architecture Intel 8086 d'origine. En plus de l'utilisation de plusieurs dialectes basés sur l'assembleur, des instructions, des registres et d'autres fonctionnalités supplémentaires ont été ajoutés au cours des années, tout en restant compatibles avec l'assemblage 16 bits utilisé dans les années 80.
La première étape pour travailler avec un assemblage x86 consiste à déterminer l'objectif. Si vous souhaitez écrire du code dans un système d’exploitation, par exemple, vous souhaiterez également déterminer si vous choisirez d’utiliser un assembleur autonome ou des fonctions d’assemblage intégré intégrées à un langage de niveau supérieur, tel que C. souhaitez coder sur le "bare metal" sans système d'exploitation, vous devez simplement installer l'assembleur de votre choix et comprendre comment créer du code binaire qui peut être transformé en mémoire flash, image de démarrage ou être chargé en mémoire au emplacement approprié pour commencer l'exécution.
Un assembleur très populaire qui est bien pris en charge sur un certain nombre de plates-formes est NASM (Netwide Assembler), qui peut être obtenu à partir de http://nasm.us/ . Sur le site NASM, vous pouvez télécharger la dernière version de votre plate-forme.
les fenêtres
Les versions 32 et 64 bits de NASM sont disponibles pour Windows. NASM est fourni avec un programme d'installation pratique qui peut être utilisé sur votre hôte Windows pour installer l'assembleur automatiquement.
Linux
Il se peut que NASM soit déjà installé sur votre version de Linux. Pour vérifier, exécutez:
nasm -v
Si la commande est introuvable, vous devrez effectuer une installation. À moins que vous ne fassiez quelque chose qui nécessite des fonctionnalités NASM de pointe, le meilleur moyen est d'utiliser votre outil de gestion de paquets intégré pour votre distribution Linux pour installer NASM. Par exemple, sous des systèmes dérivés de Debian tels qu'Ubuntu et d'autres, exécutez ce qui suit à partir d'une invite de commande:
sudo apt-get install nasm
Pour les systèmes basés sur RPM, vous pouvez essayer:
sudo yum install nasm
Mac OS X
Les versions récentes d'OS X (y compris Yosemite et El Capitan) sont livrées avec une ancienne version de NASM préinstallée. Par exemple, El Capitan a la version 0.98.40 installée. Bien que cela fonctionnera probablement pour presque toutes les utilisations normales, il est en réalité assez ancien. Au moment de la rédaction de cet article, la version NASM 2.11 est disponible et 2.12 dispose d’un certain nombre de candidats à la sortie.
Vous pouvez obtenir le code source NASM à partir du lien ci-dessus, mais à moins que vous ayez besoin de l'installer de manière spécifique, il est beaucoup plus simple de télécharger le paquet binaire à partir du répertoire de la version OS X et de le décompresser.
Une fois décompressé, il est fortement recommandé de ne pas écraser la version de NASM installée sur le système. Au lieu de cela, vous pouvez l'installer dans / usr / local:
$ sudo su
<user's password entered to become root>
# cd /usr/local/bin
# cp <path/to/unzipped/nasm/files/nasm> ./
# exit
À ce stade, NASM est dans /usr/local/bin , mais il ne se trouve pas dans votre chemin. Vous devez maintenant ajouter la ligne suivante à la fin de votre profil:
$ echo 'export PATH=/usr/local/bin:$PATH' >> ~/.bash_profile
Cela ajoutera /usr/local/bin à votre chemin. L'exécution de nasm -v à l'invite de commande doit maintenant afficher la version la plus récente.
x86 Linux Hello World Exemple
Il s'agit d'un programme Hello World de base dans un assemblage NASM pour Linux x86 32 bits, utilisant directement les appels système (sans aucun appel de fonction libc). C'est beaucoup à prendre, mais avec le temps, cela deviendra compréhensible. Les lignes commençant par un point-virgule ( ; ) sont des commentaires.
Si vous ne connaissez pas déjà la programmation de systèmes Unix de bas niveau, vous pouvez simplement écrire des fonctions dans asm et les appeler à partir de programmes C ou C ++. Ensuite, vous pouvez vous contenter d'apprendre à gérer les registres et la mémoire sans apprendre à utiliser l'API d'appel système POSIX et l'ABI pour l'utiliser.
Cela fait deux appels système: write(2) et _exit(2) (pas le wrapper libc exit(3) qui vide les tampons stdio, etc.). (Techniquement, _exit() appelle sys_exit_group, pas sys_exit, mais cela ne compte que dans un processus multi-thread .) Voir aussi syscalls(2) pour la documentation sur les appels système en général, et la différence entre les rendre directement et utiliser la libc fonctions d'emballage
En résumé, les appels système sont effectués en plaçant les arguments dans les registres appropriés et le numéro d'appel système dans eax , puis en exécutant une instruction int 0x80 . Voir aussi Quelles sont les valeurs de retour des appels système dans Assembly? pour plus d'explications sur la façon dont l'interface asm syscall est documentée avec principalement la syntaxe C.
Les numéros d'appel syscall pour l'ABI 32 bits se trouvent dans /usr/include/i386-linux-gnu/asm/unistd_32.h (même contenu dans /usr/include/x86_64-linux-gnu/asm/unistd_32.h ).
#include <sys/syscall.h> inclura le bon fichier pour que vous puissiez exécuter echo '#include <sys/syscall.h>' | gcc -E - -dM | less pour voir la macro defs (voir cette réponse pour en savoir plus sur la recherche de constantes pour asm dans les en-têtes C )
section .text ; Executable code goes in the .text section
global _start ; The linker looks for this symbol to set the process entry point, so execution start here
;;;a name followed by a colon defines a symbol. The global _start directive modifies it so it's a global symbol, not just one that we can CALL or JMP to from inside the asm.
;;; note that _start isn't really a "function". You can't return from it, and the kernel passes argc, argv, and env differently than main() would expect.
_start:
;;; write(1, msg, len);
; Start by moving the arguments into registers, where the kernel will look for them
mov edx,len ; 3rd arg goes in edx: buffer length
mov ecx,msg ; 2nd arg goes in ecx: pointer to the buffer
;Set output to stdout (goes to your terminal, or wherever you redirect or pipe)
mov ebx,1 ; 1st arg goes in ebx: Unix file descriptor. 1 = stdout, which is normally connected to the terminal.
mov eax,4 ; system call number (from SYS_write / __NR_write from unistd_32.h).
int 0x80 ; generate an interrupt, activating the kernel's system-call handling code. 64-bit code uses a different instruction, different registers, and different call numbers.
;; eax = return value, all other registers unchanged.
;;;Second, exit the process. There's nothing to return to, so we can't use a ret instruction (like we could if this was main() or any function with a caller)
;;; If we don't exit, execution continues into whatever bytes are next in the memory page,
;;; typically leading to a segmentation fault because the padding 00 00 decodes to add [eax],al.
;;; _exit(0);
xor ebx,ebx ; first arg = exit status = 0. (will be truncated to 8 bits). Zeroing registers is a special case on x86, and mov ebx,0 would be less efficient.
;; leaving out the zeroing of ebx would mean we exit(1), i.e. with an error status, since ebx still holds 1 from earlier.
mov eax,1 ; put __NR_exit into eax
int 0x80 ;Execute the Linux function
section .rodata ; Section for read-only constants
;; msg is a label, and in this context doesn't need to be msg:. It could be on a separate line.
;; db = Data Bytes: assemble some literal bytes into the output file.
msg db 'Hello, world!',0xa ; ASCII string constant plus a newline (0x10)
;; No terminating zero byte is needed, because we're using write(), which takes a buffer + length instead of an implicit-length string.
;; To make this a C string that we could pass to puts or strlen, we'd need a terminating 0 byte. (e.g. "...", 0x10, 0)
len equ $ - msg ; Define an assemble-time constant (not stored by itself in the output file, but will appear as an immediate operand in insns that use it)
; Calculate len = string length. subtract the address of the start
; of the string from the current position ($)
;; equivalently, we could have put a str_end: label after the string and done len equ str_end - str
Sous Linux, vous pouvez enregistrer ce fichier sous le nom Hello.asm et en générer un exécutable 32 bits avec les commandes suivantes:
nasm -felf32 Hello.asm # assemble as 32-bit code. Add -Worphan-labels -g -Fdwarf for debug symbols and warnings
gcc -nostdlib -m32 Hello.o -o Hello # link without CRT startup code or libc, making a static binary
Voir cette réponse pour plus de détails sur la construction de l' assemblage en 32 ou 64 bits statiques ou liées de façon dynamique Linux executables, pour la syntaxe MSNA / yasm ou GNU AT & T avec la syntaxe GNU as directives. (Point clé: assurez-vous d'utiliser -m32 ou équivalent lorsque vous construisez du code 32 bits sur un hôte 64 bits, ou vous rencontrerez des problèmes déroutants au moment de l'exécution.)
Vous pouvez suivre son exécution avec strace pour voir les appels système qu'il effectue:
$ strace ./Hello
execve("./Hello", ["./Hello"], [/* 72 vars */]) = 0
[ Process PID=4019 runs in 32 bit mode. ]
write(1, "Hello, world!\n", 14Hello, world!
) = 14
_exit(0) = ?
+++ exited with 0 +++
La trace sur stderr et la sortie normale sur stdout vont toutes deux au terminal ici, elles interfèrent donc dans la ligne avec l'appel système en write . Rediriger ou tracer vers un fichier si vous le souhaitez. Notez que cela nous permet de voir facilement les valeurs de retour de syscall sans avoir à ajouter de code pour les imprimer, et est en fait encore plus facile que d'utiliser un débogueur standard (comme gdb) pour cela.
La version x86-64 de ce programme serait extrêmement similaire, en passant les mêmes arguments aux mêmes appels système, juste dans des registres différents. Et en utilisant l'instruction syscall au lieu de int 0x80 .