Vous avez sûrement déjà codé avec un langage de haut niveau comme C ou Pascal. Ces langages permettent d'écrire de manière quasi naturelle ce que nous voulons que la machine fasse : un programme C exécutant : « afficher "coucou" » s'écrira[1]:
Exemple coucou_c.c
void main() { puts("coucou\n"); }
La même chose en assembleur s'écrirait[2]:
Exemple coucou_asm.asm
section .text global _start msg db 'coucou',0x0A msg_len equ $ - msg _start: mov eax,4 mov ebx,1 mov ecx,msg mov edx,msg_len int 80h mov eax,1 int 80h
Ce dernier source est tout à fait fonctionnel et autonome. Nous aurions pu également nous aider de la libc et éviter la manipulation directe de l'interruption 0x80, et nous aurions alors eu le mélange de C et d'assembleur suivant[3]:
Exemple coucou_asm_libc.asm
extern puts section .text global main msg db 'coucou',0 main: push ebp mov ebp,esp push ebx push esi push edi push dword msg call puts pop edi pop esi mov esp,ebp pop ebp ret
Lier son code assembleur avec la libc peut-être effectivement très pratique si l'on ne veut pas réinventer la roue ; mais il faut à ce moment se poser la question de savoir si nous n'aurions pas plus vite fait d'intégrer de l'assembleur inline dans un code C. Dans le cadre d'applications « professionnelles » il est néanmoins conseillé de ne pas passer directement par l'interruption logicielle 0x80 pour faire appel aux routinex kernel. En effet, rien ne certifie que les services proposés par cette interruption ne changeront pas — l'équipe de développement kernel conseillant de toujours passer par la libc s'agissant des appels systèmes et se réservant le droit de modifier quoi que ce soit sans crier gare. Pour en savoir plus sur l'interruption 0x80 et les différents appels systèmes.
Malgré tout il existe quelques avantages à coder en assembleur « pur » (c'est à dire, à ne pas se lier avec la libc) :
La vitesse d'exécution.
La taille du code généré.
Le faible besoin en RAM.
Pour ce qui est de la vitesse d'exécution, il est vrai qu'on arrive rapidement à produire un code moins performant que celui généré par gcc, mais tout dépend de ce qu'on fait de ce pour quoi on utilise l'assembleur. Dans la majeure partie des cas la nécessité d'écrire un programme entier en assembleur ne se posera pas — s'il s'agit d'optimisation, on emploiera plus volontier de l'assembleur inline.
Mais si, par exemple, il est vital d'obtenir un binaire très petit, si la RAM disponible est très faible ou encore si l'on ne veut absolument pas se lier avec une librairie comme la libc, alors on pourra se fier à l'assembleur. Quelques explications supplémentaires pourront être trouvées sur Linuxego.
Voici en guise d'exemple un tableau récapitulant les différents programmes sourcés ci-dessus et la taille des binaires générés (avant et après un strip [4]) :
Tableau 1-1. Comparatif des tailles
Nom | Taille | Strip |
---|---|---|
coucou_asm | 428 | 428 |
coucou_asm_libc | 4751 | 2956 |
coucou_c | 4792 | 3000 |
On voit que c'est sans conteste le programme écrit en assembleur qui l'emporte au niveau de la taille finale[5].
[1] | Le code est volontairement dépouillé du superflu. Utilisez gcc coucou_c.c -o coucou_c pour compiler cet exemple. |
[2] | Utilisez nasm -f elf coucou.asm ; ld -s coucou_asm.o -o coucou_asm pour compiler cet exemple. |
[3] | Utilisez nasm -f elf coucou_asm_libc.asm ; gcc coucou_asm_libc.o -o coucou_libc pour compiler cet exemple. |
[4] | strip mon_prog. Stripper un programme consiste à l'alléger en supprimant les symboles qu'il contient. |
[5] | Pour vous détendre un peu et voir jusqu'ou on peut aller pour faire maigrir un code, jetez un oeil sur A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux |
Précédent | Sommaire | Suivant |
Les conventions AT&T | Niveau supérieur | Comment ça fonctionne? |