===== Assembleur x86 ===== //Voici quelques notes sur l'assembleur x86 IA32 avec le compilateur GAS (GNU Assembler).// ====Quelques notes sur x86 IA32 avec GAS==== Il existe plusieurs langages de programmation assembleur ([[http://www.gnu.org/software/binutils/|GAS]], [[http://www.nasm.us/|NASM]], ...) pour générer du code machine x86 32-bit. Nous utilisons ici le langage GAS ou //GNU Assembler//, qui n'utilise pas la syntaxe Intel contrairement à NASM. Un programme GAS est un fichier texte d'extension '.s' (ou '.S'). La compilation s'effectue avec //as// et l'édition de lien avec le linker //ld// (format ELF). On peut aussi utiliser directement le compilateur //gcc//. L'utilisation du suffixe '.S' indique au compilateur d'effectuer une passe de //preprocessing// avant la compilation. * Les registres généralistes sur 32 bits (%eax, %ebx, %ecx, %edx) ont des variantes 16 bits (%ax, %bx, %cx, %dx) et 8 bits (%ah, %al, %bh, %bl, %ch, %cl, %dh, %dl). En pratique, %al est la partie basse de %ax. Respectivement, %ah est la partie haute de %ax. * On dispose d'opérations classiques : //mov, add, sub, and, ...// qui se déclinent en trois versions selon que l'on manipule des registres 8, 16 ou 32 bits. On ajoute respectivement le suffixe 'b', 'w' ou 'l' à l'instruction. Par exemple, //addw %ax,%dx// ajoute %ax au registre %dx (en 16 bits). * Pour faire des comparaisons, on dispose de l'instruction //cmp//. Par exemple, //cmpw %ax,%dx// met à jour le flag de condition Z à 1 si %ax est égal à %dx (%dx-%ax=0), de telle sorte que le saut conditionnel //je// soit vrai. On dispose d'autres sauts conditionnels comme //je, jne, jl, jg, ...//, ainsi que du saut //jmp//. * Les constantes sont précécés du symbole %%$%%, comme par exemple %%$1%% ou %%$0x0b8000%% ou encore %%$'A'%%. * GAS supporte plusieurs types de commentaires %%# ...%% ou %%// ...%% ou encore %%/* ... */%%. Plus de détails : * [[https://sourceware.org/binutils/docs/as/ | Doc GAS]] * [[https://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax|Syntaxe GAS]] * [[https://en.wikibooks.org/wiki/X86_Assembly|Assembleur x86]] * Compiler Explorer: https://gcc.godbolt.org ====Exemple Hello World en GAS ==== Voici le fameux exemple "Hello World!" : .text # section declaration (code) .global _start # standard entry point for ELF linker _start: /* write our string to stdout */ movl $len,%edx # third argument: message length movl $msg,%ecx # second argument: pointer to message to write movl $1,%ebx # first argument: file handle (stdout) movl $4,%eax # system call number (sys_write) int $0x80 # call kernel /* and exit */ movl $0,%ebx # first argument: exit code movl $1,%eax # system call number (sys_exit) int $0x80 # call kernel .data # section declaration (data) msg: .ascii "Hello world!\n" # our famous string len = . - msg # string length Compilons puis exécutons ce petit programme en 32 bits: # compilation avec as (en 32 bits) as --32 hello.s -o hello.o ld -melf_i386 hello.o -o hello # compilation avec gcc (en 32 bits) gcc -m32 -static hello.s -o hello -nostdlib # execution ./hello Hello world! ==== Bootloader au format multiboot ==== Considérons le code assembleur x86 suivant d'un //bootloader// minimaliste en 32 bits au format //multiboot//. Le code se compose du //bootloader// à proprement parler (fichier //boot.s//), qui appelle la fonction //main// du //kernel// (fichier //kernel.s//). /* my own bootloader */ .set MAGIC, 0x1BADB002 # magic number (lets bootloader find the header) .set ALIGN, 1<<0 # align loaded modules on page boundaries .set MEMINFO, 1<<1 # provide memory map .set FLAGS, ALIGN | MEMINFO # multiboot flag field .set CHECKSUM, -(MAGIC + FLAGS) # checksum of above, to prove we are multiboot .set STACKSIZE, 0x10000 # stack size /* section .text */ .text .globl _start .type _start,@function .align 4 /* standard multiboot header */ multiboot: .long MAGIC .long FLAGS .long CHECKSUM /* start routine */ _start: movl $(stack + STACKSIZE),%esp # set up stack call main # call main routine hlt # halt .size _start,.-_start /* stack */ .comm stack,STACKSIZE Pour aller plus loin : * https://www.gnu.org/software/grub/manual/multiboot/multiboot.html (multiboot format) * https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#OS-image-format (multiboot header: 0x1BADB002) * https://en.wikibooks.org/wiki/X86_Assembly/Bootloaders * http://wiki.osdev.org/Bare_bones * http://wiki.osdev.org/Memory_Map_%28x86%29 * http://files.osdev.org/mirrors/geezer/osd/ram/index.htm Voici maintenant le code de notre petit kernel, qui ne fait rien pour l'instant (//nop//) ;-) /* main kernel routine */ .text .globl main main: nop # this kernel does nothing! ret .size main,.-main Compilons notre petit noyau... as --32 boot.s -o boot.o as --32 kernel.s -o kernel.o ld -Ttext=0x100000 -e _start -melf_i386 boot.o kernel.o -o kernel Lors de l'édition de lien, il est important de préciser que le point d'entrée est le symbole '_start' et de placer la séquence magique de 12 octets du //bootloader// à une adresse supérieure à 0x100000, suivi du code (segment .text). En effet, les adresses < 1MiB sont réservées pour le BIOS, la RAM Video, etc. On peut aussi utiliser un //external linker script// pour compiler notre //kernel// : ENTRY(_start) SECTIONS { . = 0x100000; .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss) } } Puis on effectue l'édition de lien : ld -T link.ld -melf_i386 boot.o kernel.o -o kernel Analysons la structure de notre exécutable avec //objdump// : %%objdump -D kernel%% boot: file format elf32-i386 Disassembly of section .text: 00100000 : 100000: 02 b0 ad 1b 03 00 00 00 fb 4f 52 e4 bc 0010000c <_start>: 10000c: bc 00 00 21 00 mov $0x210000,%esp 100011: e8 01 00 00 00 call 100017
100016: f4 hlt 00100017
: 100017: 90 nop ... Disassembly of section .bss: 00200000 : ... Il ne reste plus qu'à tester notre //kernel// sur une vraie machine ou à défaut dans QEMU : qemu-system-i386 -enable-kvm -kernel kernel L'option //-kernel// permet de "booter" directement sur un noyau linux au format //bzimage// ou sur un code au format //multiboot// comme celui décrit dans cette section. Notons que c'est le format standard supporté par GRUB. ==== Kernel en C ==== Si l'on souhaite écrire son kernel en C, pas de problème ! Il faut juste écrire un fichier //kernel.c// qui définit le symbole //main// appelé par notre //bootlader//. En revanche, pas question d'utiliser les bibliothèques standard... Notre code doit être //self-content// ;-) void main() { // ... } Pour le compiler : as --32 boot.s -o boot.o gcc -m32 -c kernel.c -o kernel.o ld -Ttext=0x100000 -e _start -melf_i386 boot.o kernel.o -o kernel ==== Un petit kernel qui affiche Hello World !==== On remplace le //kernel// précédent par le code suivant, qui va afficher "Hello World!" dans un écran VGA en mode texte. Les cartes graphiques compatible VGA possédent un //mode texte// de taille 80x25 caractères. La mémoire vidéo se situe à l'adresse physique 0xB8000-0xB8FFF. Chaque caractère est codé par 2 octets : un premier octet pour le code ASCII du caractère et un second octet pour la couleur. /* main kernel routine */ .text # section declaration (code) .globl main main: call cls call clc call printstr ret .size main,.-main /* print string */ printstr: movl $0xb8000,%ebx # set video address (text mode) movl $len,%ecx movl $msg,%esi loop: movb (%esi),%al movb $2,%ah # set green color movw %ax,(%ebx) # print it addl $2,%ebx # next video address addl $1,%esi # next char in string subl $1,%ecx # len-- jne loop ret /* clear screen */ cls: movl $0xb8000,%edi movw $0x0720,%ax movl $80*25,%ecx cld rep stosw ret /* clear cursor */ clc: movw $0x3d4,%dx movb $0xa,%al outb %al,%dx movw $0x3d5,%dx inb %dx,%al andb $0xc0,%al orb $0x1f,%al outb %al,%dx ret .data # section declaration (data) msg: .ascii "Hello world!" # our famous string len = . - msg # string length On compile enuite notre //kernel// puis on le lance dans QEMU, comme précédemment, et voici le résultat : un magnifique système d'exploitation "Hello World!" ! qemu-system-i386 -enable-kvm -kernel kernel {{ :archi:qemu-sample.png |}} Un peu d'aide : * VGA text-mode (80×25 caractères) : https://en.wikipedia.org/wiki/VGA-compatible_text_mode * Color in VGA text mode : http://geezer.osdevbrasil.net/osd/cons/index.htm * Port série (COM) : https://fr.wikipedia.org/wiki/RS-232 ==== Booter son Kernel depuis un CD-ROM ==== Pour créer un fichier //mykernel.iso// (image normale d'un CD_ROM à graver) qui boot notre programme //kernel// ! Voici deux méthodes basées sur GRUB, un //multiboot loader//, qui va charger notre //bootloader+kernel// : __Méthode 1__ : mkdir -p iso/boot/grub cp stage2_eltorito iso/boot/grub/stage2_eltorito echo timeout 2 > iso/boot/grub/menu.lst echo title mykernel >> iso/boot/grub/menu.lst echo kernel '(cd)/kernel' >> iso/boot/grub/menu.lst echo boot >> iso/boot/grub/menu.lst cp kernel iso/ genisoimage -R -b boot/grub/stage2_eltorito -no-emul-boot -boot-load-size 4 -boot-info-table -o mykernel.iso iso __Méthode 2__ : mkdir -p iso/boot/grub echo 'menuentry "mykernel" { multiboot /kernel }' > iso/boot/grub/grub.cfg cp kernel iso/ grub-mkrescue -o mykernel.iso iso Il ne reste plus qu'à le tester dans QEMU : qemu-system-i386 -cdrom mykernel.iso ==== Booter son Kernel depuis un disque (sans GRUB) ==== Pour booter son Kernel depuis un disque (//floppy//, //hard disk// ou //usb disk//), il faut tout d'abord modifier la séquence de boot du BIOS pour qu'il puisse amorcer le système depuis ce périphérique au démarrage de l'ordinateur. Ces 3 types de disque utilisent la même organisation en secteur de 512 octets. Pour rendre un disque bootable, il faut ajouter la signature de boot (i.e. //0x55aa//) à la fin du secteur 0. .code16 # generate 16-bit code .text # executable code location .globl _start; _start: # code entry point #print letter 'H' onto the screen movb $'H' , %al movb $0x0e, %ah int $0x10 #print letter 'e' onto the screen movb $'e' , %al movb $0x0e, %ah int $0x10 #print letter 'l' onto the screen movb $'l' , %al movb $0x0e, %ah int $0x10 #print letter 'l' onto the screen movb $'l' , %al movb $0x0e, %ah int $0x10 #print letter 'o' onto the screen movb $'o' , %al movb $0x0e, %ah int $0x10 hlt # halt . = _start + 510 #mov to 510th byte from 0 pos .byte 0x55 #append boot signature .byte 0xaa #append boot signature Attention, dans ce cas précis, le BIOS recopie en mémoire physique le secteur 0 (512 octets) et passe la main au CPU pour qu'il commence à exécuter en //mode real// (16 bits) les instructions au début du secteur 0... Notons que dans ce cas, il est possible de faire appel aux interruptions du BIOS pour écrire en mémoire VGA (//int 0x10//). Il faut ensuite compiler ce programme //en flat binary// (et non en ELF) puis le copier sur sa disquette : as hello.s -o hello.o ld --oformat=binary hello.o -o hello.bin dd if=/dev/zero of=floppy.img bs=512 count=2880 # standard floppy disk (1.44MB) dd if=hello.bin of=floppy.img On peut facilement tester ce programme depuis son disque avec QEMU : qemu-system-i386 -fda floppy.img -boot a # boot on floppy disk qemu-system-i386 -hda floppy.img # boot on hard disk On peut également mettre son bootloader sur le premier secteur d'une clef USB : /dev/sdc sur ma machine (fdisk -l). Mais, attention de ne pas se tromper au risque de perdre toutes vos données ! cat hello.bin > /dev/sdc Puis on reboote la "vraie" machine avec la clef USB et voici le résultat. Notons que nous n'avons pas préparé de table des partitions, d'où le message affiché par le BIOS : //Invalid Partition Table// avant notre fameux //Hello World// :-) {{ :archi:helloboot-laptop.jpg?direct&300 |}} Pour aller plus loin : * http://www.codeproject.com/Articles/664165/Writing-a-boot-loader-in-Assembly-and-C-Part * http://www.codeproject.com/Articles/668422/Writing-a-boot-loader-in-Assembly-and-C-Part * http://www.codeproject.com/Articles/737545/Writing-a-bit-dummy-kernel-in-C-Cplusplus ==== Documentation ==== * Compilateur //as// : https://sourceware.org/binutils/docs/as * Assembleur x86 : https://en.wikibooks.org/wiki/X86_Assembly * External Linker Script : http://www.emprog.com/support/documentation/thunderbench-Linker-Script-guide.pdf ==== Pour aller encore plus loin ==== * http://viralpatel.net/taj/operating-system-tutorial.php * http://wiki.osdev.org/Main_Page * http://wiki.osdev.org/Bare_bones * http://wiki.osdev.org/El-Torito * https://wiki.archlinux.org/index.php/GRUB * http://www.syslinux.org/wiki/index.php?title=ISOLINUX * http://wiki.osdev.org/Mkisofs * http://wiki.osdev.org/Bootable_CD * http://stackoverflow.com/questions/34268518/creating-a-bootable-iso-image-with-custom-bootloader