ASSEMBLEURdoc - ASSEMBLEUR by maclaren1

VIEWS: 90 PAGES: 61

									ASSEMBLEUR
             Langage(s)




Assembleur            1
Objectifs :
               Comprendre que l’assembleur est
                le meilleur ami de l’architecture
               Les assembleurs peuvent être très
                différents
               Programmer en assembleur n’est
                pas simple
               Ne pas vous spécialiser sur un
                assembleur particulier

Comment :
               Présentation de deux assembleurs
                différents (68000 et 8086)
               Les idées de base en 68000 (8086)
               Les subtilités en 8086
               En compilant et cross-compilant




Assembleur                                      2
              INTRODUCTION

Le cours d’architecture a illustré la conception d’un
microprocesseur avec pour outil de modélisation les
FSM
 le mot placé dans IR (mot instruction) contient toute
l’information sur le travail a accomplir (+ opérandes)

Comment éviter l’écriture directe du mot instruction +
opérande (ex : 000E 323C 0002) pour coder un
algorithme ?
 utilisation d’un mnémonique + opérandes réduisant
l’effort de codage (ex : move.w #2,D1)
 le mnémonique + op n’est qu’une représentation
différente de la même information
 le microprocesseur n’utilise que la première
représentation, c’est l’opération d’assemblage qui
traduit (c’est un compilateur qui génère un code du
premier type) le code assembleur en code machine

L’ensemble (mnémonique + opérandes) constitue le
langage ASSEMBLEUR

Rem : pour un même microprocesseur  plusieurs
langages assembleurs possibles (ils se distinguent
surtout par les directives, macros …) (org 300), (a : ds.b
1), ( .data)



Assembleur                                               3
  Des assembleurs pour des micropro.

Chaque architecture est spécifique (8 registres
memReg[] pour PROCESSI) même dans la classe des
CISC  les assembleurs pour le 68000 et le 8086
seront vus car ils se distinguent par l’architecture sous-
jacente
L’assembleur 68000
Il sert a modéliser la série 680XX en conservant une
compatibilité ascendante.
 On trouve des différences aux niveaux du bus de
données (832 bits) (les registres sont de 32 bits) +
horloge (celle de la FSM), instructions, mem. Virtuelle,
cache, ..

L’assembleur 8086
Il modélise les microprocesseurs jusqu’au 80386. Au
delà  RISC + CISC (486)  RISC (pentium)
On passe de 16 bits à 32 bits de données et de 20 à 32
bits d’adresse

Les principales différences entre le 68000 et le 8086
sont : le nombre de registres, les types de registres, les
interruptions, les modes d’adressage, l’organisation de
la mémoire, …

En TD  utilisation des instruction + programmation
assembleur en 68000
         les appels systèmes en 8086 (clavier, …)

Assembleur                                               4
D’autres types d’assembleurs

Ex : pour les DSP, les instructions (et l’architecture)
sont optimisées pour manipuler des calculs itératifs sur
des plages de mémoire   RISC




Assembleur                                             5
             Que manipule-t-on ?

Formats
   L’élément de base est l’octet (8 bits) dont le type
    est B (Byte) ex : FE
   Deux octets consécutifs forment un mot de type W
    (Word) ex : FE51
   Quatre octets consécutifs forment un double mot de
    type L (Long)(68000) ou bien D (Double)(8086)
    ex : FE51AABB

Base de représentation

Comment indiquer la base dans laquelle un nombre est
représenté ? (10 : binaire, hexa, dec ?)

   En 8086
    Hexa : commence par un chiffre et se termine par
    un H (également 0hXXXX)
    Binaire : // et se termine par un b
    Décimal : que des chiffres
   En 68000
    Hexa : commence par 0x (parfois $)
    Binaire : commence par un 0b (parfois %)
    Décimal : que des chiffres

Rem : on ne retrouve pas ces symboles en mémoire (le
standard c’est l’hexa)

Assembleur                                             6
   STRUCTURE INTERNE DU P
         (REGISTRES)

Les registres du 8086

4 registres généraux (16 bits), 4 registres de segments
(découpage mémoire), 4 registres spécialisés (index et
pointeur), un registre d’état, IR, IP (=PC)

     AH              AL           AX accumulateur
     BH              BL           BX base
     CH              CL           CX comptage
     DH              DL           DX données

             SP                   Pointeur de pile
             BP                   Pointeur de base
             SI                   Index de source
             DI                   Index de destination

             CS                   Segment de code
             DS                   Segment de données
             SS                   segment de pile
             ES                   Segment supplémentaire

             IP
       o d i t s z   a    p   c
                                  flags

Assembleur                                                 7
o : débordement
d : direction
i : validation d’interruption
t : pas-à-pas
s : signe
z : zéro
a : retenue aux. (sur les doubles octets)
p : parité
c : retenue

Rôle des segments :
Le 8086 est constitué de 20 bits d’adresse or les
registres sont de longueur 16 bits
 comment obtenir 20 bits ?
 en combinant les registres de segments avec les
offsets définis sur 16 bits (IP, spécifié dans le code, dans
un registre).

Adresse absolue = 16*segment + offset  20 bits

On utilise le format XXXX :YYYY pour désigner le
segment XXXX et l’offset YYYY.
 1451:0120 et 1000:4630 partagent la même adresse
absolue

Dans les TDs le code sera contenu dans un seul
segment. L’offset pourra donc varier de 0 à 65536
(64Ko)



Assembleur                                                 8
Rem : le segment joue un rôle équivalent au bit
permettant de choisir la zone « donnée » ou
« instruction » dans la mémoire de PROCESSI

Ex : mov [103],AX correspond à DS:103,AX




Assembleur                                        9
Les registres du 68000

 8 registres de données (D0-D7) de 32 bits
 8 registres d’adresses (A0-A7) de 32 bits
A7 joue le rôle de pointeur de pile (appelé également
USP ou SSP)
1 PC de 32 bits
1 registre d’état de 16 bits
T      S          I2   I1   I0            X   N    Z   V   C
superviseur                      Utilisateur=CCR

T : trace
S : superviseur
In : masque d’interruption
X : bit d’extension (sert de carry pour les additions)
N : signe
Z : zero
V: overflow
C: carry

Rem: la notion de segment n’existe plus, les adresses du
codes ou des registres sont absolues.




Assembleur                                                     10
Accès mémoires

Une case mémoire contient 8 bits (un octet) , les
registres sont de longueur 16 ou 32 bits  comment lire
et ranger les données en mémoires ?

   8086
    Pour un word (H-L) en mémoire : la partie H est
    écrite à l’adresse la plus grande
    Ex : 5B7E  7E                5B

                                 Ad+

    Le Bus est de 16 bits, on peut donc lire 16 bits en
    une seule lecture uniquement si le word commence
    à une adresse paire (aligné)

   68000
    C’est l’inverse du 8086
    Ex : 5B7E  5B              7E

                                 Ad+

    Une adresse d’octet doit être paire ou impaire
    Une adresse de word doit être paire
    Une adresse de long doit être paire
    Sinon  erreur

    Rem : les instructions et opérandes sont des
    multiples de word + le compilateur place les
    sections (prog et données) sur des adresses paires

Assembleur                                               11
           L’ ENVIRONNEMENT DE
              PROGRAMMATION

    Les outils

   L’assembleur 68000 est utilisé pour illustrer
    l’assembleur de base  pas forcement de réalité
    physique, pas d’interruptions  emploi d’un
    simulateur sur machine SPARC (contenant des P
    SPARC !)
   L’utilisation de l’assembleur dans un
    environnement complexe (clavier, écran,
    intérruptions, …) est présenté à l’aide du 8086 sur
    PC (mode compatible car  8086)


Le 68000

Le simulateur graphique utilisé s’appelle simu :
/opt/net1/tcl/bin/simu
Il simule le comportement du 68000 (excepté les
interruptions faisant appel au matériel: écran, clavier, ...)
à partir d’un exécutable (68000) ex : nomfich.exe

Comment obtenir le .exe à partir du fichier source
nomfich.s ?



Assembleur                                                 12
       Nomfich.s
                 .txt
                 .globl _main
       _main :




                 .data
       var1 :    dc.b    0xF1




        Assemblage : /opt/net/GNU/cross/bin/sun3-
        as –o nomfich.o nomfich.s

       Nomfich.o

        Edition de lien :
        /opt/net/GNU/cross/bin/sun3-ld –e _main
        –Ttext 1000 –Tdata 7000

       Nomfich.exe


    Rem : si on ajoute –a à l’assemblage  détails et
    table



Assembleur                                              13
    Le 8086

    Le code exécutable .com est exécuté sous debug ou
    sous DOS. L’assemblage et l’édition de lien se font
    à laide dut fichier de commande asm.bat

     Nomprog.asm
     Zone db          80
     debut :




      asm


     Nomprog.com




Assembleur                                           14
             LES INSTRUCTIONS
               ASSEMBLEUR

    On peut les rassembler en quatre groupes
    d’instructions
       Mouvements de données
       Opérations logiques
       Opérations arithmétiques
       Instructions de contrôles

Elles manipulent les données dans les modes
d’adressages
     Immédiat
     Registre
     Mémoire direct
     Mémoire indirect

Aux formats B,W,L (68000) ou bien B,W (8086)




Assembleur                                     15
Modes d’adressages

   Mode immédiat
  L’opérande est codée avec l’instruction

  move.w     #4 ,D0

  mov        AX, 568


   Mode registre
  L’opérande est un registre de donnée ou d’adresse

  move.l     A0, D1

  mov        AX,BX

   Mode mémoire direct
  L’opérande est désignée par l’adresse donnée dans
  l’instruction

  move.w     D0,0xC040 attention pas de l’immédiat
  move.w     var1,D1

  mov        [0hC040],AL
  mov        DS :[0hC040],AL
  mov        CS:var2,AX
  mais pas
  mov        0hFE15 :var2,AX

Assembleur                                            16
   Mode mémoire indirect
  L’opérande est désignée par une adresse placée dans
  les registres d’adresses donnée dans l’instruction

  move.w D0,(A0)
  mais pas (D0) ou (var2)

  mov AX,[SI] BX,BP,SI,DI peuvent servir de
              registre pointeur

              Indirect avec déplacement
               L’adresse = contenu du registre d’adresse
               + déplacement (le registre d’adresse
               n’est pas modifié)

              move.l #4,2(A1)
              move.w -5(A1),D0

              mov      AX,6[DI]
              mov      BX,[DI+6]




Assembleur                                                 17
              Indirect avec index
               L’adresse = contenu du registre d’adresse
               + contenu du registre d’index (le registre
               d’adresse n’est pas modifié)

               move.w #6,(A0,D3) en fait (Ai,Dj)

               mov     [BP][DI],AX
               couples possibles BP-DI, BP-SI, BX-DI,
               BX-SI

              Indirect avec post-incrémentation
               L’adresse = contenu du registre d’adresse
               puis le registre d’adresse est incrémenté en
               fonction du format (1, 2 ou 4)

               move.w D0,(A0)+

              Indirect avec pre-décrémentation
               Le registre d’adresse est décrémenté en
               fonction du format (1,2 ou 4)
               puis l’adresse = contenu du registre
               d’adresse

               mov.l    –(A5),0x3000

               Rem 1 : ces deux modes d’adressage sont
               utiles pour la gestion d’une pile


Assembleur                                                  18
              Rem 2 : pas de pre-incrémenté ni de post-
              décrémenté

              Indirect indexé avec déplacement
               comme son nom l’indique
               add.w 2(A0,D1),D0

remarque:

    move.l #var1,A6 permet d’écrire l’adresse de la
    variable var1 (ou d’une étiquette)
    équivalent à
    lea var1,A6




Assembleur                                            19
exemples de modes d’adressage
Code C :                  Code 68000 :
int tab[] = {1,2,3,4} ;   .data
int a = 4 ;               .globl _tab
int i ;                   _tab:
void main( ) {                   dc.l    1
      i = 3;                     dc.l    2
      tab[2] = a;                dc.l    3
      tab[i] = tab[0];           dc.l    4
}                         .globl _a
                          _a:
                                 dc.l    4
                          .globl _i
                          _i:
                                 ds.l    1
                          .text
                          .globl _main
                          _main:
                                 movel   #3,_i
                                 movel   _a,_tab+8
                                 movel   _i,d0
                                 lea     _tab,a0
                                 asll    #2,d0
                                 movel   _tab,(a0,d0)
                          .end




Assembleur                                              20
Mouvements de données

Principalement des move

move.b D0,D1

+ des mouvements spécialisés

movem.w        D0/D1/D2,0x7000
               sauvegarde des registres D0, D1, D2 au
               format word à partir de l’adresse 0x7000
équivalent à

movem.w        D0-D2,0x7000

en 8086 quelques spécialités:

LODSB  mov AL,[SI] + inc SI
STOSB  mov ES :[DI],AL + inc DI
MOVSB  mov ES :[DI],[SI] + inc SI + inc DI

en général ces instructions sont associées à l’instruction
LOOP

PUSH AX (empile) et POP CX (dépile)




Assembleur                                                21
Opérations logiques et arithmétiques

     logiques

    OR, AND, EOR, NOT

    OR.w (A0),D1

    dest = dest op source (les indicateurs sont mis à jour)

    + des opérations au niveau du bit (BTST, BSET, ...)

    + des décalages/rotations arithmétiques (on garde le
    signe) ou logiques (ASL, ASR, LSL, LSR, ROR,
    ROR, ...)

                        X/C      ASR


                        X/C
0                                LSR




Assembleur                                                 22
   arithmétiques

  Elles réalisent des calculs en binaire signé
  Elles modifient les indicateurs

  ADD.b D0,D1
  ADDX.b var2,D4
         (la valeur de l’indicateur X est ajoutée au
         résultat)
  SUB.w #5, (A3)


  dest = dest op source (et mise à jour des indicateurs)
  donc pour le sub : dest = dest – source

  CMP.b #4, D0

  dans cette instruction les indicateurs sont mis à jour
  en fonction du résultat D0 – 4, D0 n’est pas modifié

  + des opérations complexes telles que (DIVS ou
  DIVU) et (MULS ou MULU)

  DIVU D1,D2 (D2/D1)
         la source est sur 16 bits et la destination sur 32
         le reste occupe b16-b31, le quotient b0-b15
  MULS        (A1)+, D3
              les opérandes sont sur 16 bits, le résultat
  est sur 32 bits.

Assembleur                                                 23
  Instructions de contrôle

  Elles déroutent le cours normal du programme
  Elles servent à réaliser
   les alternatives (if, case)
   les itérations (while, repeat, for)
   les procédures

  les inconditionnels
  JMP étiquette (adresse  PC)
  BRA label       (PC + d  PC) codable avec moins
                  d’octets et code translatable

           JSR   proc
                 appel à la procédure proc. L’adresse
                 de retour est sauvegardée dans la pile
  proc :


           RTS

  JSR peut être remplacé par BSR (déplacement)




Assembleur                                                24
  les conditionnels
  Bcc étiquette         (cc : code condition)
         le « cc » doit être remplacé par EQ, NE, ...En
         général, il est utilisé après un CMP.

        Une règle simple pour déterminer le cc :
        la valeur de référence utilisée pour la
        comparaison sera la source

        ex : si D0  D1
                      (on prend D1 comme référence)
                      (la relation est  )
             alors
                     DO IT




        devient :

        prog :
            cmp.b     D1,D0 (D1 est la source)
            Bge       doit



        doit :




Assembleur                                                25
    un spécial : DBcc      Dn, étiquette

    si la condition cc est vérifiée
    alors on exécute un NOP puis on passe à
    l’instruction suivante en séquence
    sinon le registre Dn est décrémenté
          si le résultat  -1
          alors BRA étiquette
          sinon l’instruction est terminée, on passe à la
          suite

    exemple : on veut comparer le contenu de deux
    tableaux de 10 éléments

        Lea 0x2000, A1
        Lea 0x3000, A2
        Moveq #9, D0
bou:    Cmpm (A1)+, (A2)+ (seul mode possible)
        DBne D0, bou


en 8086 :

JSR  Call
Bcc  Jcc
Jmp  Jump

spécial: LOOP (décrémentation CX + test + branch. )



Assembleur                                                  26
Programmation de structure de contrôle

   If ... Then
                            if D0 > D1
if :                        then
    cmp.b      D1,D0
    bgt        then
    bra        endif
then:


endif:

    If ... Then ... Else

if:                         if D0 > D1
    cmp.b      D1,D0        then
    bgt        then                A
    bra        else
then:                       else
           A                       B



     bra       endif
else:
           B



endif:



Assembleur                               27
   Case ... OF ...
  case:
quatre: cmp.b #4,D0         case        D0 of
        bne       cinq      4:
                                    A
          A


                            5:
                                    B
          bra     endcase
cinq :    cmp.b   #5,D0
          bne     other     otherwise
          B                         C




          bra     endcase
other :
          C



endcase :

   While ... Do ...
while :                     while       D0 > D1
        cmp.b D1,D0         do
        bgt      do
        bra      endwhile
do :


       bra        while
endwhile :


Assembleur                                        28
   Repeat ... Until ...             repeat
repeat:


       cmp.b       D1,D0             until    D0  D1
       ble         endrepeat
       bra         repeat
endrepeat:

   For ...                          for D0=1 to 10 do
        move.b     #1,D0
for:    cmp.b      #10,D0
        ble        do
        bra        endfor
do:


          add.b    #1,D0
          bra      for
endfor:                     le pas par défaut est 1




Assembleur                                               29
Assembleur   30
 Technique de passage de paramètres à
            une procédure
plusieurs solutions :
   par adresses (nom de variable) (pas vraiment un
    passage et pb pour le nom des variables)
   par registres (nombre et taille limitée)
   par adresses dans des registres (nombre limité)
   par adresse dans la pile (on peut également passer
    les données par la pile)

Par registres

Les registres de données contiennent les paramètres de
la proc. avant l’appel. Le programme appelant utilise les
mêmes numéros de registres que la procédure

inc :    add.b   #1,D0
         rts

_main: move.b a,D0
       jsr    inc
       move D0,ia

Par adresses dans des registres

Ce n’est plus la donnée qui est passée mais son adresse
(taille sans incidence)


Assembleur                                                31
inc :    move.b (A0),D0
         add.b #1,D0
         move.b D0,(A1)

_main: move.l #a,A0
       move.l #ia,A1
       jsr    inc

Par adresses dans la pile

Les registres d’adresse sont remplacés par la pile (de
dimension supérieure au nombre de registres)
PB : lors de l’appel à la procédure, la pile sert également
à sauvegarder l’adresse de retour (Long)

move.l #a,-(A7)               ad+


                                             @a     Long
                                              A7
move.l #ia,-(A7)
                                     @ia     @a     Long
jsr      inc                           A7

                             @ret @ia        @a     Long
                               A7
add.l    #8,A7
                                                    Long
                                                      A7
Assembleur                                               32
inc:     move.l    4(A7),A1
         move.l    8(A7),A0
         move.b    (A0),D0
         add.b     #1,D0
         move.b    D0,(A1)
         rts
                                     @ia     @a     Long
                                       A7

PB: dans la procédure, des registres peuvent être
modifiés (ce ne sont pas des variables locales !) or les
registres du programme appelant doivent être préservés
 sauvegarde des registres utilisés (effectué dans la
procédure)

proc :   movem.l      A0/A1/D0,-(SP)
         D0    A1      A0     @ret @ia       @a     Long
         A7



         movem.l      (SP)+,A0/A1/D0

inconvénient: l’offset (le déplacement) à appliquer au
pointeur de pile pour récupérer les adresses des
paramètres dépend du nombre de registres sauvegardés
 Utilisation d’un « frame pointer » (A6)


Assembleur                                               33
proc :    link          A6,#0
                       A6   @ret @ia       @a   Long
                        A7,A6

movem.l          A0/A1/D0,-(SP)
D0       A1      A0     A6      @ret @ia   @a     Long
 A7                      A6

move.l           8(A6),A1
move.l           12(A6),A0




movem.l    (SP)+,A0/A1/D0
unlk    A6
rts

Les offsets 8 et 12 de dépendent pas du nombre de
registre sauvegardés.
Link permet également de réserver de la place pour les
variables locales à la procédure :

link      A6,#-4
                       A6     @ret @ia     @a    Long
                  A7     A6

Assembleur                                               34
/*passage des donnees et pas de variable locale a la fonction*/

int a,b,c;                        #NO_APP
                                  gcc2_compiled.:
                                  ___gnu_compiled_c:
int somme(int x,int y)            .text
                                        .even
{                                 .globl _somme
                                  _somme:
return x+y;                              link a6,#0
}                                        movel a6@(8),d1
                                         addl a6@(12),d1
main()                                   movel d1,d0
{                                        jra L1
a=1;                              L1:
b=2;                                     unlk a6
c=somme(a,b);                            rts
                                         .even
}                                 .globl _main
                                  _main:
                                         link a6,#0
                                         moveq #1,d1
                                         movel d1,_a
                                         moveq #2,d1
                                         movel d1,_b
                                         movel _b,sp@-
                                         movel _a,sp@-
                                         bsr _somme
                                         addqw #8,sp
                                         movel d0,_c
                                  L2:
                                         unlk a6
                                         rts
                                  .comm _a,4
                                  .comm _b,4
                                  .comm _c,4




Assembleur                                                        35
/*passage des donnees avec variable locale a la fonction*/

                                  #NO_APP
                                  gcc2_compiled.:
int a,b,c;                        ___gnu_compiled_c:
                                  .text
                                         .even
int somme(int x,int y)            .globl _somme
{                                 _somme:
int w;                                   link a6, #-4
                                         movel a6@(8),d1
w=x+y;                                   addl a6@(12),d1
return w;
                                        movel d1,a6@(-4)
}
                                        movel a6@(-4),d0
                                        jra L1
main()                            L1:
{                                        unlk a6
a=1;                                     rts
b=2;                                     .even
c=somme(a,b);                     .globl _main
                                  _main:
}                                        link a6,#0
                                         moveq #1,d1
                                         movel d1,_a
                                         moveq #2,d1
                                         movel d1,_b
                                         movel _b,sp@-
                                         movel _a,sp@-
                                         bsr _somme
                                         addqw #8,sp
                                         movel d0,_c
                                  L2:
                                         unlk a6
                                         rts
                                  .comm _a,4
                                  .comm _b,4
                                  .comm _c,4




Assembleur                                                   36
/*passage par adresse du resultat sans variable locale a la procédure*/

                                       #NO_APP
int a,b,c;                             gcc2_compiled.:
                                       ___gnu_compiled_c:
void somme(int x,int y,int *z)         .text
                                              .even
{                                      .globl _somme
                                       _somme:
*z=x+y;                                       link a6,#0
}                                            movel a6@(16),a0
                                             movel a6@(8),d0
main()
                                             addl a6@(12),d0
{
a=1;                                         movel d0,a0@
b=2;                                   L1:
                                              unlk a6
somme(a,b,&c);                                rts
}                                             .even
                                       .globl _main
                                       _main:
                                              link a6,#0
                                              moveq #1,d0
                                              movel d0,_a
                                              moveq #2,d0
                                              movel d0,_b
                                             pea _c
                                             movel _b,sp@-
                                             movel _a,sp@-
                                             bsr _somme
                                             addqw #8,sp
                                             addqw #4,sp
                                       L2:
                                            unlk a6
                                            rts
                                       .comm _a,4
                                       .comm _b,4
                                       .comm _c,4




Assembleur                                                                37
     Les interruptions- Les exceptions

Ce sont des procédures soit prédéfinies (prévues dans le
système d’exploitation), soit définies par l’utilisateur.
Elles se différencient des procédures classiques par leur
appel

prog :                         prog :




appui                          appui
reset            proc.         touche z          proc.
                 d’int.                          d’int.
                 RESET                           AFFTIME




               pas de
               retour

                                    Dans ce cas prog
                                    n’attendait pas
                                    l’appui sur z

Assembleur                                              38
Dans les deux cas précédents l’interruption est
matérielle.
De même, la division par zéro provoque l’arrêt du
programme (c’est l’ALU qui génère un signal associé à
une interruption)



prog :                        prog :




    INT 21h      proc.            TRAP #5      proc.
                 d’int 21h                     d’exception
                                               5




Dans ce cas ce sont des appels logiciels aux procédures
d’int. On remarque que ces procédures sont appelées par
un numéro (appel logiciel # ou une fonction des fils
activés du microprocesseur)  numéro : ad. mémoire ?

Assembleur                                           39
IL existe une table de correspondance appélée « table
des vecteur d’interruptions/exceptions ».
Elles sont placées en général en début de mémoire :

00000000                         V1
00000004                         V2
00000008                         V3
0000000A                         ...

Un vecteur est composé de 4 octets représentant
l’adresse de la procédure à appeler (adresse absolue ou
couple CS:offset)

L’adresse du vecteur de l’int N  0 :4*N (8086)
ou bien TRAP #N  0x80+4*N (68000)
En 68000, N varie de 0 à 15

rem : en 8086 il existe une interruption (int 21h fonction
35h) qui permet de connaître le vecteur d’interruption
sachant son numéro. De plus l’int 21h fonction 25h
permet de modifier le vecteur d’int. sachant son numéro.




Assembleur                                                40
Peut-on interrompre une interruption ?

Les interruptions peuvent se protéger des autres int.
(masquage) en utilisant le système de priorité (68000 :
si l’int. appelante a une priorité inférieure ou égale a un
seuil défini par l’int. en cours alors elle reste inactive, si
non elle interrompt l’int. en cours) ou bien en interdisant
toutes autres int. 8086 : pas de niveaux intermédiaires
de priorité)  modification du registre d’état.
rem : certaines int.ne sont pas masquable.

Que se passe-t-il lorsqu’il y a interruption ?

   Identification du numéro d’int.  vecteur
   est-elle autorisée à interrompre ?
       o si oui
       68000 : le PC est empilé (pile SU A7’)
                  le SR est empilé (A7’)
       8086 :     le segment CS est empilé
                  l’offset IP est empilé
                  le SR est empilé
   le vecteur de l’int. est chargé dans PC (68000) ou
    CS:IP (8086)

à la fin du code de l’interruption on trouve un RTS
spécialisé (RTE (68000), IRET (8086)) qui dépile les
indicateur et PC (ou CS:IP)  retour programme
interrompu
rem : l’int. ne doit pas modifier les registres du
programme interrompu !
Assembleur                                                  41
qui se charge d’associer des programmes d’int. aux
vecteurs d’int. ?  le système d’exploitation

Les interruptions et l’environnement PC (8086)

   L’environement


                                           BIOS : matériel
                                           DOS : logiciel

     BIOS       DOS        ?




Le syst. d’exploitation charge en mémoire des
programmes qui sont des interruptions

BIOS :       INT 10h       : vidéo
en ROM       INT 16h       : clavier

DOS :            INT 21h
 opérations sur le clavier, l’écran, les fichiers, ...
fait appel aux int du BIOS
les fonctions sont déterminées par AH
Le programmeur utilise en général les fonctions DOS
                 (sauf prog. d’interruptions)

Assembleur                                                42
Comment programmer une interruption ?

   choisir un numéro d’int. libre
   sauvegarder les registres utilisés dans la pile
    (PUSH) et les restaurer avant IRET
   faire préceder les variables et adresses par CS :

  var de l’int N
  ni, va, ...
                     va chercher va dans la zone            lors de l’appel CS et IP
                     des variables de l’int. N              sont modifiés mais pas
                                                            DS



  mov        bh,va
                                     va chercher va dans la zone
  mov        bh,cs :va               des variables de prog et non
                                     pas dans celle de l’int. N



  IRET


  var de prog :
                                   programme appelant l’int. N
                                   les variables sont référenceés
  prog :                           à DS
                                   mov bl,nb = mov bl,ds :nb

  INT N




Assembleur                                                                     43
Comment détourner une interruption ?




prog1 :      intM :      prog1 :       prog2 :   intM :




                                       JMP FAR
             IRET                                IRET

INT M                    INT M




Il faut donc récupérer l’adresse (vecteur) de l’int. M
pour l’appeler à la fin de prog2.




Assembleur                                          44
ancfonc :    dw ?
anccs :      dw ?

             jmp debut

prog2 :




             jmp dword ptr CS :ancfonc
                   forme un double mot formé de anccs et
                   ancfonc
debut :
             mov      al,M
             mov      ah,35h
             int      21h
             mov      ancfonc,BX
             mov      anccs,es
             lea      dx,prog2
             mov      al,M
             mov      ah,25h
             int      21h
                             écrit le vecteur DS:DX à
                             l’emplacement du vecteur M


Assembleur                                            45
La fin normale d’un programme rend la main au
système d’exploitation. C’est la fonction 4Ch de l’int.
21h qui permet cela mais elle libère la mémoire occupée
par l’exécutable (qui est chargé en mémoire avant
l’exécution)  à la fin du programme précédent la zone
contenant prog2 est libérée, le détournement est effacé !
Comment protéger cette mémoire  en rendant le
programme résidant.

Comment rendre résidant un programme ?


zone des variables

    JMP DEBUT
prog2 :                             zone à protéger


    JMP FAR
finprog2 :

debut:
    récup vecteur
    modif vecteur


     mise en résidant




Assembleur                                             46
La mise en résidant s’effectue en rendant la main au
DOS par la fonction 31h, on passe par DX le nombre de
paragraphe (multiple de 16 octets) à protéger depuis
l’offset 0 (début du segment de code)




    lea      dx,finprog   si finprog déborde sur un
    add      dx,15        nouveau paragraphe, la
    mov      cl,4         division par 16 tronquera la
    shr      dx,cl        fin du prog  on ajoute 15
    mov      ah,31h
    int      21h




Assembleur                                           47
On obtient finalement:




                          Ancienne
                          int. M
         IRET



zone des var de la
nouvelle int. M
                             1-point d’entrée du programme mise en
                             résidant + détournement
      JMP DEBUT
PROG2 :                   nouvelle
                          int. M

         JMP CS :...

DEBUT :
recupération vecteur
mise en résidant         retour DOS




quelconque :                 2-point d’entrée du programme
                             quelconque (après détournement et
                             résidant)
         INT M



Assembleur                                                   48
L’environnement sera :
L’assemblage sera réalisé avec le fichier asm.bat
(utilisation de MASM  NASM) :
@echo off
if exist %1.asm goto suite
echo Le fichier %1.asm n'existe pas
goto fini
:suite
echo code segment >$tempo.asm
echo assume cs:code,ds:code >>$tempo.asm
echo org 100h          >>$tempo.asm
echo _dbf : Jmp debut >>$tempo.asm
echo include %1.asm    >>$tempo.asm
echo code ends         >>$tempo.asm
echo end _dbf          >>$tempo.asm
echo Erreur lors de la compilation -
>result
masm $tempo; >>result
if errorlevel 1 goto rate
echo Erreur … l'‚dition de liens: >result
link $tempo; >>result
if errorlevel 1 goto rate
del $tempo.obj
echo Erreur dans la transformation en
.COM >result
exe2bin $tempo %1.com >>result
if errorlevel 1 goto rate
del $tempo.exe
goto fini
:rate
type result
:fini



Assembleur                                          49
exemple : divise-0.asm


; programme qui genere une division par 0
; ---------------------------------------

debut:
         mov ax,55h
         mov bx,00h
         div bx                ; divise ax par bx


    mov ah,4ch
        int 21h                ; retour au DOS



qui devient $tempo.asm

code segment
assume cs:code,ds:code
org 100h
_dbf : Jmp debut
include divise-0.asm
code ends
end _dbf


Le programme qui détournera l’int 0h est correspond au
fichier detdiv0.asm :




Assembleur                                           50
; Programme de detournement de la division par
0
; ---------------------------------------------
-
; affiche "le fameux bug du pentium" et
retourne à l'interruption 0h

ex_int_ip dw ?
ex_int_cs dw ?

message db 'le fameux bug du pentium',10,13,'$'

div0:                   ; programme
d'interruption
        push si
        push ax
        lea si,cs:message
        mov ah,0eh


boucle: mov al,cs:[si]
; affichage de "message" caractere par
caractere
        inc si
        cmp al,'$'
        je fin
        int 10h
        jmp boucle

fin:       pop ax
           pop si

           jmp dword ptr cs:ex_int_ip
findiv0:




Assembleur                                    51
debut:

        mov al,0h
; recupere l'adresse de l'interruption 0h
    mov ah,35h
    int 21h
        mov cs:ex_int_ip,bx
        mov cs:ex_int_cs,es


        lea dx,div0
; associe div0 a l'interruption 0h
        mov al,0h
    mov ah,25h
    int 21h

        mov ax,5555h
; appel a l'int 0h pour essai
        mov bx,1111h
        int 0h

        mov ah,4ch        ; retour sous dos
    int 21h

si ensuite on exécute :

; programme pour tester l'int 0h
; ------------------------------

debut:
    mov ax,5555h ; ne sert pas a grand chose
    mov bx,5555h ; .........................
        int 0h
       mov ah,4ch
        int 21h




Assembleur                                     52
 il ne reste plus qu’a rebooter !

Il faut en fait ajouter :

       lea dx,findiv0+15
; laisse en résidant
       mov cl,4
       shr dx,cl
       mov ah,31h
       int 21h




Assembleur                           53
Plus compliqué : l’affichage des
LOCKS

; affichage des lock: scroll, num, caps, ins
; -------------------

; l'état des lock est en 40h:17h

anc_ligne db ?
anc_colonne db ?

blanc:
    push ax
    inc dl
    mov al,' '
    mov ah,0eh
    int 10h
    pop ax
    ret

debut:

    mov bh,0        ; recuperation ancienne
position du curseur
    mov ah,3
    int 10h
    mov cs:anc_ligne,dh
    mov cs:anc_colonne,dl

          mov dh,2     ; positionnement du
curseur
        mov dl,40
    mov bh,0
    mov ah,2
    int 10h




Assembleur                                     54
    mov ax,40h      ; recuperation etat des
lock
    mov es,ax
    mov bl,es:[17h]

; tests sur l'etat des lock

    test bl,10h       ; scroll bit 4 à 1
    jnz affs
    call blanc
    jmp su1
affs:
    mov al,'S'
    mov ah,0eh
    int 10h

su1:    test bl,20h       ; num bit 5 à 1
    jnz affn
    call blanc
    jmp su2
affn:
    mov al,'N'
    mov ah,0eh
    int 10h

su2:
    test bl,40h       ; caps bit 6 à 1
    jnz affc
    call blanc
    jmp su3
affc:
    mov al,'C'
    mov ah,0eh
    int 10h

su3:
    and bl,80h        ; ins bit 7 à 1
    jnz affi

Assembleur                                    55
    call blanc
    jmp   fin

affi:
    mov al,'I'
    mov ah,0eh
    int 10h

fin:
    mov bh,0            ; remet le curseur a sa
position initiale
    mov dh,cs:anc_ligne
    mov dl,cs:anc_colonne
    mov ah,2
    int 10h

        mov ah,4ch         ; retour sous DOS
        int 21h




Assembleur                                     56
2. Avez-vous besoin de l'assembleur?

Je ne veux en aucun cas jouer les empêcheurs-de-tourner-en-rond, mais voici quelques
conseils issus d'une expérience gagnée à la dure.

2.1 Le Pour et le Contre
Les avantages de l'assembleur
L'assembleur peut vous permettre de réaliser des opérations très bas niveau:

      vous pouvez accéder aux registres et aux ports d'entrées/sorties spécifiques à votre
       machine;
      vous pouvez parfaitement contrôler le comportemant du code dans des sections
       critiques où pourraient sinon advenir un blocage du processeur ou des périphériques;
      vous pouvez sortir des conventions de production de code de votre compilateur
       habituel; ce qui peut vous permettre d'effectuer certaines optimisations (par exemple
       contourner les règles d'allocation mémoire, gérer manuellement le cours de
       l'éxécution, etc.);
      accéder à des modes de programmation non courants de votre processeur (par exemple
       du code 16 bits pour l'amorçage ou l'interfaçage avec le BIOS, sur les pécés Intel);
      vous pouvez construire des interfaces entre des fragments de codes utilisant des
       conventions incompatibles (c'est-à-dire produit par des compilateurs différents ou
       séparés par une interface bas-niveau);
      vous pouvez générer un code assez rapide pour les boucles importantes pour pallier
       aux défauts d'un compilateur qui ne sait les optimiser (mais bon, il existe des
       compilateurs optimisateurs librement disponibles!);
      vous pouvez générer du code optimisé "à la main" qui est plus parfaitement règlé pour
       votre configuration matérielle précise, même s'il ne l'est pour aucune autre
       configuration;
      vous pouvez écrire du code pour le compilateur optimisateur de votre nouveau
       langage. (c'est là une activité à laquelle peu se livrent, et encore, rarement.)

Les inconvénients de l'assembleur
L'assembleur est un langage très bas niveau (le langage du plus bas niveau qui soit au dessus
du codage à la main de motifs d'instructions en binaire). En conséquence:

      l'écriture de code en est longue et ennuyeuse;
      les bogues apparaissent aisément;
      les bogues sont difficiles à repérer et supprimer;
      il est difficile de comprendre et de modifier du code (la maintenance est très
       compliquée);
      le résultat est extrêmement peu portable vers une autre architecture, existante ou
       future;



Assembleur                                                                                  57
      votre code ne sera optimisé que une certaine implémentation d'une même architecture:
       ainsi, parmi les plates-formes compatibles Intel, chaque réalisation d'un processeur et
       de ses variantes (largeur du bus, vitesse et taille relatives des
       CPU/caches/RAM/Bus/disques, présence ou non d'un coprocesseur arithmétique, et
       d'extensions MMX ou autres) implique des techniques d'optimisations parfois
       radicalement différentes. Ainsi diffèrent grandement les processeurs déjà existant et
       leurs variations: Intel 386, 486, Pentium, PPro, Pentium II; Cyrix 5x86, 6x86; AMD
       K5, K6. Et ce n'est sûrement pas terminé: de nouveaux modèles apparaissent
       continuellement, et cette liste même sera rapidement dépassée, sans parler du code
       ``optimisé'' qui aura été écrit pour l'un quelconque des processeurs ci-dessus.
      le code peut également ne pas être portable entre différents systèmes d'exploitation sur
       la même architecture, par manque d'outils adaptés (GAS semble fonctionner sur toutes
       les plates-formes; NASM semble fonctionner ou être facilement adaptable sur toutes
       les plates-formes compatibles Intel);
      un temps incroyable de programmation sera perdu sur de menus détails, plutôt que
       d'être efficacement utilisé pour la conception et le choix des algorithmes utilisés, alors
       que ces derniers sont connus pour être la source de la majeure partie des gains en
       vitesse d'un programme. Par exemple, un grand temps peut être passé à grapiller
       quelques cycles en écrivant des routines rapides de manipulation de chaînes ou de
       listes, alors qu'un remplacement de la structure de données à un haut niveau, par des
       arbres équilibrés et/ou des tables de hachage permettraient immédiatement un grand
       gain en vitesse, et une parallélisation aisée, de façon portable permettant un entretien
       facile.
      une petite modification dans la conception algorithmique d'un programme anéantit la
       validité du code assembleur si patiemment élaboré, réduisant les développeurs au
       dilemne de sacrifier le fruit de leur labeur, ou de s'enchaîner à une conception
       algorithmique obsolète.
      pour des programmes qui fait des choses non point trop éloignées de ce que font les
       benchmarks standards, les compilateurs/optimiseurs commerciaux produisent du code
       plus rapide que le code assembleur écrit à la main (c'est moins vrai sur les
       architectures x86 que sur les architectures RISC, et sans doute moins vrai encore pour
       les compilateurs librement disponible. Toujours est-il que pour du code C typique,
       GCC est plus qu'honorable).
      Quoi qu'il en soit, ains le dit le saige John Levine, modérateur de comp.compilers, "les
       compilateurs rendent aisée l'utilisation de structures de données complexes; ils ne
       s'arrêtent pas, morts d'ennui, à mi-chemin du travail, et produisent du code de qualité
       tout à fait satisfaisante". Ils permettent également de propager correctement les
       transformations du code à travers l'ensemble du programme, aussi hénaurme soit-il, et
       peuvent optimiser le code par-delà les frontières entre procédures ou entre modules.

Affirmation
En pesant le pour et le contre, on peut conclure que si l'assembleur est parfois nécessaire, et
peut même être utile dans certains cas où il ne l'est pas, il vaut mieux:

      minimiser l'utilisation de code écrit en assembleur;
      encapsuler ce code dans des interfaces bien définies;
      engendrer automatiquement le code assembleur à partir de motifs écrits dans un
       langage plus de haut niveau que l'assembleur (par exemple, des macros contenant de
       l'assembleur en-ligne, avec GCC);

Assembleur                                                                                        58
      utiliser des outils automatiques pour transformer ces programmes en code assembleur;
      faire en sorte que le code soit optimisé, si possible;
      utiliser toutes les techniques précédentes à la fois, c'est-à-dire écrire ou étendre la
       passe d'optimisation d'un compilateur.

Même dans les cas où l'assembleur est nécessaire (par exemple lors de développement d'un
système d'exploitation), ce n'est qu'à petite dose, et sans infirmer les principes ci-dessus.

Consultez à ce sujet les sources du noyau de Linux: vous verrez qu'il s'y trouve juste le peu
qu'il faut d'assembleur, ce qui permet d'avoir un système d'exploitation rapide, fiable, portable
et d'entretien facile. Même un jeu très célèbre comme DOOM a été en sa plus grande partie
écrit en C, avec une toute petite routine d'affichage en assembleur pour accélérer un peu.

2.2 Comment ne pas utiliser l'assembleur
Méthode générale pour obtenir du code efficace
Comme le dit Charles Fiterman dans comp.compilers à propos de la différence entre code
écrit par l'homme ou la machine,

``L'homme devrait toujours gagner, et voici pourquoi:

      Premièrement, l'homme écrit tout dans un langage de haut nivrau.
      Deuxièmement, il mesure les temps d'éxécution (profiling) pour déterminer les
       endroits où le programme passe la majeure partie du temps.
      Troisièmement, il demande au compilateur d'engendrer le code assembleur produit
       pour ces petites sections de code.
      Enfin, il effectue à la main modifications et réglages, à la recherche des petites
       améliorations possibles par rapport au code engendré par la machine.

L'homme gagne parce qu'il peut utiliser la machine.''
Langages avec des compilateurs optimisateurs
Des langages comme ObjectiveCAML, SML, CommonLISP, Scheme, ADA, Pascal, C, C++,
parmi tant d'autres, ont tous des compilateurs optimiseurs librement disponibles, qui
optimiseront le gros de vos programmes, et produiront souvent du code meilleur que de
l'assembleur fait-main, même pour des boucles serrées, tout en vous permettant de vous
concentrer sur des détails haut niveau, et sans vous interdire de gagner par la méthode
précédente quelques pourcents de performance supplémentaire, une fois la phase de
conception générale terminée. Bien sûr, il existe également des compilateurs optimiseurs
commerciaux pour la plupart de ces langages.

Certains langages ont des compilateurs qui produisent du code C qui peut ensuite être
optimisé par un compilateur C. C'est le cas des langages LISP, Scheme, Perl, ainsi que de
nombreux autres. La vitesse des programmes obtenus est toute à fait satisfaisante.




Assembleur                                                                                      59
Procédure générale à suivre pour accélerer votre code
Pour accélérer votre code, vous ne devriez traiter que les portions d'un programme qu'un outil
de mesure de temps d'éxécution (profiler) aura identifié comme étant un goulot d'étranglement
pour la performance de votre programme.

Ainsi, si vous identifiez une partie du code comme étant trop lente, vous devriez

      d'abord essayer d'utiliser un meilleur algorithme;
      essayer de la compiler au lieu de l'interpréter;
      essayer d'activer les bonnes options d'optimisation de votre compilateur;
      donner au compilateur des indices d'optimisation (déclarations de typage en LISP;
       utilisation des extensions GNU avec GCC; la plupart des compilos fourmillent
       d'options);
      enfin de compte seulement, se mettre à l'assembleur si nécessaire.

Enfin, avant d'en venir à cette dernière option, vous devriez inspecter le code généré pour
vérifier que le problème vient effectivement d'une mauvaise génération de code, car il se peut
fort bien que ce ne soit pas le cas: le code produit par le compilateur pourrait être meilleur que
celui que vous auriez écrit, en particulier sur les architectures modernes à pipelines multiples!
Il se peut que les portions les plus lentes de votre programme le soit pour des raisons
intrinsèques. Les plus gros problèmes sur les architectures modernes à processeur rapide sont
dues aux délais introduits par les accès mémoires, manqués des caches et TLB, fautes de
page; l'optimisation des registres devient vaine, et il vaut mieux repenser les structures de
données et l'enchaînement des routines pour obtenir une meilleur localité des accès mémoire.
Il est possible qu'une approche complètement différente du problème soit alors utile.

Inspection du code produit par le compilateur
Il existe de nombreuses raisons pour vouloir regarder le code assembleur produit par le
compilateur. Voici ce que vous pourrez faire avec ce code:

      vérifier si le code produit peut ou non être améliorer avec du code assembleur écrit à
       la main (ou par un réglage différent des options du compilateur);
      quand c'est le cas, commencer à partir de code automatiquement engendré et le
       modifier plutôt que de repartir de zéro;
      plus généralement, utilisez le code produit comme des scions à greffer, ce qui à tout le
       moins vous laisse permet d'avoir gratuitement tout le code d'interfaçage avec le monde
       extérieur.
      repérer des bogues éventuels dus au compilateur lui-même (espérons-le très rare,
       quitte à se restreindre à des versions ``stables'' du compilo).

La manière standard d'obtenir le code assembleur généré est d'appeller le compilateur avec
l'option -S. Cela fonctionne avec la plupart des compilateur Unix y compris le compilateur
GNU C (GCC); mais à vous de voir dans votre cas. Pour ce qui est de GCC, il produira un
code un peu plus compréhensible avec l'option -fverbose-asm. Bien sur, si vous souhaitez
obtenir du code assembleur optimisé, n'oubliez pas d'ajouter les options et indices
d'optimisation appropriées!



Assembleur                                                                                     60
Assembleur   61

								
To top