Assembleur SPARC v8 01/12/2009 Cours d'introduction Ing1 – Promotion 2012 Matthieu Bucchianeri (2005-2007) Laurent Lec (2009) v 2.2 Assembleur & microprocesseurs RISC ● ● ● L'assembleur est le langage le plus proche du microprocesseur. Pas de variables ni de fonctions, mais des registres, des adresses mémoires... Pas de structures de contrôles avancées comme les conditions ou les boucles, mais des instructions de saut et de branchement. Types de données ● ● SPARC v8 est une spécification de microprocesseur 32 bits – mot (word) = 32 bits – demi-mot (half-word) = 16 bits – octet (byte) = 8 bits L'ordre des octets en mémoire est big endian. Registres ● ● Seules des opérandes dans des registres peuvent être utilisées pour les calculs Disposition des registres sur SPARC : – ● %g0 à %g7 ● %g0 vaut toujours 0 ● %g5 à %g7 sont réservés – %i0 à %i7 : recevoir des arguments – %l0 à %l7 : registres locaux – %o0 à %o7 : passer des arguments En principe, on ne touche pas trop aux registres globaux... Instructions de manipulation des registres ● Copier une valeur d'un registre à un autre MOV ● Mettre une valeur (< 13 bits = 8192) dans un registre MOV ● regs, regd imm, regd Mettre une valeur (> 13 bits) dans un registre @SET imm, regd ● Mettre à zéro un registre CLR regd Instructions d'accès mémoire ● Lire depuis la mémoire vers un registre LD ● Ecrire le contenu d'un registre en mémoire ST ● ● [address], regd regs, [address] Adresse : « registre + registre » ou « registre + constante » On peut préciser le type de la donnée Utiliser des variables ● ● Une variable est : – un registre ; – une zone mémoire. En assembleur, créer une variable revient à réserver un bloc de mémoire ma_variable: .reserve 4 ; int ma_variable @SET ma_variable, %l0 LD [%l0], %l1 ADD %l1, 2, %l1 ST %l1, [%l0] ; ma_variable = ma_variable + 2 Piège ! ● Sur SPARC les accès mémoires sont alignés : – mot de 32 bits = une adresse multiple de 4 octets – demi-mot de 16 bits = une adresse multiple de 2 .align boundary 4 ; exemple : 4000 var1: .reserve 2 ; 4000 var2: .reserve 4 ; 4002 @SET var2, %l0 ; (4002 % 4) != 0 LD ; Bus Error [%l0], %l1 Piège ! ● La solution .align boundary 4 var1: .reserve 2 .align boundary 4 var2: ; exemple : 4000 ; 4002 aligné à 4 = 4004 .reserve 4 @SET var2, %l0 ; (4004 % 4) == 0 LD ; ok [%l0], %l1 Utiliser des variables ● Les variables n'ont pas de type. ● On met ce qu'on veut dans les blocs mémoires table: .reserve 8 ; short[4] @SET ma_variable, %l0 LDSH [%l0], %l1 ; table[0] LDSH [%l0 + 4], %l2 ; table[2] Quelques opérations de base ● Un microprocesseur sait principalement faire de l'arithmétique et de la logique ; ● Prend 1 ou 2 registres en opérande ; ● Un registre en destination ; ● Quelques opération pour exemple : ADD SUB regs1, (regs2 | imm), regd regs1, (regs2 | imm), regd Un exemple ● Le code suivant place 2 entiers dans %i0 et %l1 et retourne leur somme dans %o0 : @SET 12345, %i0 ; car > 8192 MOV ; car < 8192 ADD ● ● 1234, %l1 %i0, %l1, %o0 On mélange sans problème des registres de catégorie différente. Un registre peut être à la fois source et destination. Encore d'autres instructions AND regs1, (regs2 | imm), regd OR NEG INC DEC regs1, (regs2 | imm), regd regs, regd (imm ,) regd (imm ,) regd Multiplications, divisions, décalages ● Multiplication et division (préciser si signés ou non) : UMUL SMUL UDIV SDIV ● (regs2 | (regs2 | (regs2 | (regs2 | imm), imm), imm), imm), regd regd regd regd Décalages : SLL SRL SRA regs1, regs1, regs1, regs1, regs1, (regs2 | imm), regd regs1, (regs2 | imm), regd regs1, (regs2 | imm), regd Astuces ● SRA effectue un décalage arithmétique – ● Il propage le bit de signe à droite Multiplication par un nombre 2n – Décalage vers la gauche de n bits UMUL %l0, 4, %l0 SLL %l0, 2, %l0 ● Une division par 2n – Décalage à droite de n bits SDIV SRA – %l0, 8, %l0 %l0, 3, %l0 Notez l'usage de SRA et pas SRL Contrôler le chemin d'exécution ● On veut des if et des boucles – ● ● Un microprocesseur sait juste faire des « GOTO » 4 codes de conditions – Z – le résultat vaut zéro – N – le résultat est négatif – C – il y a une retenue – V – il y a un débordement GOTO à une adresse si Z = 1, si Z = 0, si N = 1... Les codes de condition ● La mise à jour des codes de condition se fait par les instructions directement, en ajoutant le suffixe CC ADDCC ● Ou bien explicitement TST regs1, (regs2 | imm), regd regs Les branchements dans tout ça ? ● ● ● Une condition : – Tester qu'une valeur est égale ou non à une autre – Tester la relation entre deux valeurs (plus petit, plus grand...) Solution : soustraire les deux valeurs à tester (B - A) – Si Z, alors A == B – Si N, alors A > B – Si N ou Z, alors A ≥ B – Etc... Utiliser CMP Les instructions de branchement BA BE BNE BL(U) BLE(U) BG(U) BGE(U) BNEG BPOS BZ BNZ label label label label label label label label label label label « branche toujours » == != < ≤ > ≥ <0 >0 == 0 != 0 Les structures de contrôle ● Le If ● La boucle test condition Loop: si faux goto CAS_faux test condition [ Execution cas vrai ] si faux goto Fin goto Fin [ Execution boucle ] CAS_faux: goto Loop [ Execution cas faux ] Fin: Fin: ● Il y a d'autres formes ! Un if...then...else, naïvement y = abs(x) : if (x < 0) y = -x else y = x TST %l0 BNEG neg MOV %l0, %l1 BA end neg: NEG ; CMP %l0, 0 revient au même ;y=x Si %l0 < 0, je saute ! %l0, %l1 ; y = -x end: MAIS DELAY-SLOT ! NE MARCHE PAS Avec des delay-slot, toujours naïvement ● L'instruction qui suit un branchement est toujours exécutée TST %l0 BNEG neg NOP MOV %l0, %l1 BA end NOP neg: NEG %l0, %l1 end: Les NOP ne servent à rien ! Avec des delay-slot et mieux pensé TST %l0 BNEG end NEG MOV %l0, %l1 %l0, %l1 end: Somme des entiers d'un tableau, version (très) naïve ; %i0 = int tab[] ; %i1 = int size; CLR %l0 ; int compteur = 0; loop: TST %i1 BZ end ; if (size == 0) goto end; DEC %i1 ; size--; LD [%i0], %l2 ; int tmp = *tab; ADD %l0, %l2, %l0 ; compteur = compteur + tmp; INC 4, %i0 ; tab++; BA loop ; goto loop; NOP NOP end: MOV %l0, %i0 ; return (compteur); Version moins naïve... TST %i1 BZ end ; if (size == 0) goto end; %l0 ; int compteur = 0 SLL %i1, 2, %l1 ; size = size * 4; ADD %i0, %l1, %l1 ; int *end_tab = (char*)tab + size; LD [%i0], %l2 ; int tmp = *tab; INC 4, %i0 ; tab++; CMP %i0, %l1 BNE loop ; if (tab != end_tab) goto loop %l0, %l2, %l0 ; compteur = compteur + tmp; %l0, %i0 ; return (compteur); CLR loop: ADD end: MOV Les fonctions ● Une fonction = un label (= une adresse) ● Une fonction a une structure particulière : ma_fonction: SAVE %sp, -96, %sp ; instructions ... RET RESTORE Les fonctions (explications) ● Les instructions SAVE et RESTORE font coulisser la fenêtre de registres : – – SAVE : ● %o0-%o7 deviennent %i0-%i7 ● %l0-%l7 sont sauvés ● %i0-%i7 sont sauvés RESTORE : ● ● ● %i0-%i7 redeviennent %o0-%o7 Les valeurs dans %l0-%l7 sont restaurées comme avant l'appel de fonction Idem pour %i0-%i7 SAVE/RESTORE : un exemple MOV -32, %l0 MOV 1, %o0 MOV 64, %o1 SAVE ADD %i0, %i1, %l0 ADD %l0, 4, %i0 RESTORE ADD %o0, %l0, %l1 Que vaut %l1 ? Les fonctions (explications) ● RET = retour de la fonction. ● CALL = appel de fonction. .proc add SAVE %sp, -96, %sp ADD %i0, %i1, %i0 ; valeur de retour dans %i0 RET RESTORE ... MOV 123, %o0 MOV 456, %o1 CALL add NOP ; add(123, 456); ; %o0 vaut la valeur de retour de la fonction Piège ! Ne pas utiliser %i6, %i7, %o6 et %o7 ! Ils sont utilisés implicitement par le microprocesseur. Advanced Assembler (AASM) http://savannah.nongnu.org/projects/aasm/ ● ● ● Logiciel d'assemblage écrit par Alexandre Becoulet et Cédric Bail, qui gère le x86 et le SPARC Première étape dans la phase de construction de binaires à partir de code assembleur Pour indiquer qu'on code pour SPARC : .mod_load asm-sparc .mod_out-elf32 .include sparc/v8.def Sections... ● ● Sections – .text : le code – .data : les données initialisées – .rodata : les données initialisées et lisibles seulement – .bss : les données non initialisées Avec AASM, une section a la forme : .section code .text .section data .data ... ... .ends .ends Section de code .section code .text .section_align 4 .mod_asm opcodes v8 .extern printf ; on va utiliser printf de la libc .export main ; notre main est exporté (pas static) .proc main SAVE %sp, -96, %sp ... RET RESTORE .endp .ends Section de données .section data (.data | .rodata | .bss) .section_align 4 var1: .dump word 0x4242 str: .string "Un grand verre d'huile d'arachide\0" .align boundary 4 array: .fill 0 8 ; 8 octets mis à 0 .ends Piège ! .dump dword 0x42424242 .dump word 0x4242 .dump byte 0x42 ● Un dword est 32 bits et non pas 64 ! ● Un word est 16 bits au lieu de 32 ● AASM était conçu pour x86 à l'origine Un exemple simple .mod_load asm-sparc .mod_load elf-out32 .include sparc/v8.def .section data .rodata .section_align 4 str1: .string "Luc ?\0" str2: .string "Ouiiiiiiiiiiiiiii\0" .ends (suite) .section code .text .section_align 4 .mod_asm opcodes v8 .extern puts .extern sleep .define LUC_LATENCY 1 .export main .proc main SAVE %sp, -96, %sp @SET .rodata:str1, %o0 CALL puts NOP MOV LUC_LATENCY, %o0 CALL sleep NOP @SET .rodata:str2, %o0 CALL puts NOP RET RESTORE .endp .ends Compilation en deux étapes !!! ● Sur une machine FreeBSD x86 : $ aasm luc.aasm › génère luc.o ● Sur une machine SPARC : $ gcc -o luc luc.o $ ./luc Luc ? Ouiiiiiiiiiiiiiii Autres exemples Vous devez pour cet exercice ecrire un programme assembleur qui appelle la fonction strcmp() avec les deux premiers arguments passes en ligne de commande a votre programme. Votre programme affichera la valeur renvoyee par strcmp(). -- Sujet Exo 6 Promo 2008 .mod_load asm-sparc .mod_load out-elf32 .include sparc/v8.def .include my_aff_reg.inc .section code .text .section_align 4 .mod_asm opcodes v8 .extern strcmp .export main .proc main save %sp, -96, %sp cmp %i0, 3 bne exit nop ld [%i1 + 4], %o0 ld [%i1 + 8], %o1 call strcmp nop @my_aff_reg %o0 exit: clr %i0 ret restore .endp .ends ; if (argc != 3) goto exit; ; argv[1] ; argv[2] ; strcmp(argv[1], argv[2]); Vous devez pour cet exercice ecrire un programme assembleur qui affiche le contenu d'un fichier texte dont le nom est passe en argument sur la ligne de commande. -- Sujet Exo 7 Promo 2008 [...] .define BUFF_SIZE 512 .section data .data .section_align 4 mode: .string "rb" .ends .section code .text [...] .proc main save %sp, -96, %sp cmp %i0, 2 bne exit nop ld [%i1 + 4], %o0 @set .data:mode, %o1 call fopen nop tst %o0 bz exit nop call do_cat nop call fclose nop exit: ret restore .endp ; if (argc != 2) goto exit; ; argv[1] ; "rb" ; FILE* f = fopen(argv[1], "rb"); ; if (f == NULL) goto exit; ; do_cat(); ; fclose(f); (suite) .extern fgets .extern puts .proc do_cat ; %i0 = FILE* f; save %sp, -96, %sp loop: @set .bss:buff, %o0 mov BUFF_SIZE, %o1 mov %i0, %o2 call fgets ; fgets(buff, BUFF_SIZE, f); nop tst %o0 bz exit ; if (fgets(...) == NULL) goto exit; nop @set .bss:buff, %o0 call puts ; puts(buff); nop ba loop nop exit: ret restore .endp .ends .section data .bss .section_align 4 buff: .reserve BUFF_SIZE .ends Bibliographie ● SPARC v8 Architecture Manual http://www.sparc.com/standards/V8.pdf ● Cours de Nicolas Pouillon http://asm.ssji.net/