Post by Olivier Miakinen$ cat rien.c
int main()
{
return 0;
}
$ wc -c rien.c
27 rien.c
$ gcc rien.c -o rien
Toutefois, dans cet exemple, il y a eu édition de liens avec la libc. Pour des
trucs qui ici ne sont pas utilisés. Et il reste les symboles de debugging.
Ne lions rien:
$ cat > rien.c
int main() { return 0; }
$ gcc -Wall -c rien.c
$ ls -la rien.o
-rw-r--r-- 1 schaefer schaefer 1216 Mar 11 09:14 rien.o
Enlevons les symboles de debugging qui ne servent pas au processeur:
$ strip rien.o
$ ls -la rien.o
-rw-r--r-- 1 schaefer schaefer 744 Mar 11 09:14 rien.o
Argh, toujours un facteur 27!
Version assembleur si ça intéresse:
$ gcc -Wall -S rien.c
$ cat rien.s
.file "rien.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.9.2-10+deb8u1) 4.9.2"
.section .note.GNU-stack,"",@progbits
La vraie taille de ça une fois compilé, c'est:
$ objdump -d rien.o
rien.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 00 00 00 00 mov $0x0,%eax
9: 5d pop %rbp
a: c3 retq
(intéressant de voir que ce ne sont pas exactement les même qualifieurs
d'instruction qui sont utilisés dans ce que -S montre et ce que objdump
montre, mais je ne suis pas un connaisseur de ce jeu d'instruction
et je n'ai pas envie de me taper les 4'000 pages de manuel -- je
sais juste qu'il y a même deux syntaxes différentes entre gcc et Intel
mais ce n'est pas ce qui se passe ici)
A voir 11 octets. Donc, zut, dans ce cas, le code source n'est pas plus long
que le code compilé.
Mais bon, ce code est un peu compilé à-la-con, on voit bien que les push
et pop du framing de la fonction ne servent à rien. Et en compilant avec
l'optimiseur?
$ gcc -O42 -Wall -c rien.c
$ strip rien.o
$ objdump -d rien.o
0000000000000000 <.text.startup>:
0: 31 c0 xor %eax,%eax
2: c3 retq
Donc 3 octets! (oui, xor x,x est souvent utilisé par les pirates pour produire
un octet à zéro dans les attaques de buffer overflow(*) -- à voir c'est aussi
une instruction optimisée, cool!) Ou deux octets si l'on s'épargne le retour
de la fonction qui ne sert à rien ici.
Ce résultat n'est toutefois pas généralisable, C est quand même un langage très
bas niveau: on doit faire beaucoup de blabla dans le code source pour
finalement produire peu d'instructions, fonctions de runtime exceptées.
D'autres langages ont des constructions bien plus complexes à compiler. Et
parfois des constructions qui semblent complexes sont utilisées par le
compilateur (y compris pour optimiser) mais ne se retrouvent plus du tout
dans l'assembleur (exemple: appel d'une méthode d'un objet dont le type
exact est disponible à la compilation, méthode qui incrémente une valeur:
on aurait l'impression qu'il faut une table de saut associée à l'objet,
appeler une méthode, etc: or, avec un bon compilateur c'est implémentable
en code inline directement par une opération d'incrémentation,
même en registre dans certains cas).
Aussi, que ce qui est réellement exécuté sur un processeur à jeu d'instruction
amd64 (Intel appelle ce qu'ils ont pompé à AMD: EMT64) ce sont des micro-
instructions RISC générées à la volée à l'exécution; dommage que cette
couche-là ne soit pas directement accessible aux compilateurs -- Intel serait
déjà mort.
Voir aussi https://godbolt.org/ pour compiler pour différentes
architectures depuis un client WWW.
NB: la zététique, à mon sens, c'est aussi vérifier une opinion de quelqu'un
qu'on apprécie généralement, même pour donner raison à un interlocuteur, lui,
généralement stupide et paresseux.
(*) https://stackoverflow.com/questions/20431174/simple-buffer-overflow-and-shellcode-example désassemblable avec http://shell-storm.org/online/Online-Assembler-and-Disassembler/
(dans ce cas avec plusieurs NULLs)