1.3. Qu'est-ce que c'est?

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) :

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

NomTailleStrip
coucou_asm428428
coucou_asm_libc47512956
coucou_c47923000

On voit que c'est sans conteste le programme écrit en assembleur qui l'emporte au niveau de la taille finale[5].

Notes

[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