-------[  RtC Mag, At the end of the universe  ]

--------------[  Optimisation du code d'un virus  ]
---------[ in RtC mag 5 ]
----[  by Androgyne <androgyne-rtc@fr.st>  ]


-------[  Introduction

    Ce texte est tir de la traduction du "Billy Belcebu Virus Writing Guide 1.04" (version DOS) que j'ai faite et qui sera disponible sur VDS. J'ai dcid de faire paratre cet extrait car il ne concerne pas seulement les virus mais peut avoir des applications dans tous les programmes srieux... L'optimisation est essentielle dans la vitesse et la fluidit des programmes. Tout bon programmeur qui se respecte doit pouvoir optimiser son programme. Ici, Billy nous donne quelques astuces bien penses.


-------[  Premire approche

    Il y a deux types d'optimisation : l'optimisation structurelle et l'optimisation locale. Premirement, vous devez comprendre quelque chose : n'optimiser jamais votre code tant qu'il ne marche pas entirement. Si vous essayez d'optimser un code qui ne marche pas, il y aura de plus en plus de choses qui feront que a ne marchera pas, vous essaierez de rparer, et vous ferez de plus en plus d'erreurs... une boucle sans fin :)


-------[  L'optimisation structurelle

    C'est la plus efficace, et la plus dure  faire. On peut comprendre ce type d'optimisation seulement en utilisant du papier, un crayon et en crivant l'algorithme du virus. On n'a pas de papier ici mais imaginez la situation... Imaginez vous, avec votre virus, ouvrir le fichier en lecture seule d'abord, fermer, ouvrir ensuite en lecture/criture, et fermer encore. C'est une perte d'octets. Pour ce type d'optimisation, vous devez vous poser uniquement les questions : "Qu'est-ce que je peux changer pour gagner des octets ? Et qu'est-ce que je ne dois pas changer ?" La solution doit tre faite en fonction de votre problme.


-------[  L'optimisation locale


    C'est le plus facile, mais a peut aussi conomiser pleins d'octets. Ca consiste  changer quelques lignes de codes individuellement en d'autres qui font la mme chose mais en utilisant moins d'octets.


 *** mettre les registres  0 ***

        mov     bx,0000h                ; 3 bytes
        xor     bx,bx                   ; 2 bytes
        sub     bx,bx                   ; 2 bytes

    Donc, n'utiliser jamais le premier, et choisissez un des deux autres. Il y a un registre qui peut tre mis  zro par un autre moyen : DX. Voyons :

        mov     dx,0000h                ; 3 bytes
        xor     dx,dx                   ; 2 bytes
        sub     dx,dx                   ; 2 bytes
        cwd                             ; Convert word to dword ( 1 byte )

    Le "cwd" marchera uniquement si le contenu de AX est  moins de 8000h. Il y a un moyen de mettre AH  0 avec un octet : si AL < 80h, vous pouvez utiliser l'instruction "cbw".


 *** les comparaisons ***

    Il y a un moyen connu de tous, qui est d'utiliser l'instruction dveloppe spcialement pour a : "cmp". Pour comparer deux registres, vous pouvez utiliser deux mthodes avec le mme rsultats, et sans sauvegarde :

        cmp     ax,bx                   ; 2 bytes
        xor     ax,bx                   ; 2 bytes

    Mais on peut utiliser le "xor" dans tous les cas si nous voulons savoir si les valeurs sont gales. Cependant, on peut sauvegarder les octets si on utilise un "xor"  la place d'un "cmp" quand on compare un registre avec une valeur immdiate :

        cmp     ax,0666h                ; 3 bytes
        xor     ax,0666h                ; 2 bytes

    mais,  cause de la nature du "xor", on ne peut pas l'utiliser pour savoir si un registre est  0. Mais alors surgit le "or" pour nous sauver...

        cmp     ax,0000h                ; 3 bytes
        or      ax,ax                   ; 2 bytes


 *** un registre optimis, AX : ***

    Vous pouvez l'utiliser pour les comparaisons :

        cmp     bx,0666h                ; 4 bytes
        cmp     ax,0666h                ; 3 bytes

    Et vous pouvez charger AX dans un autre registre avec une mthode trs optimise :

        mov     bx,ax                   ; 2 bytes
        xchg    ax,bx                   ; 1 byte

    Vous pouvez faire a si les valeurs dans AX et BX avant le changement sont sans importance. C'est bien de le faire aprs une ouverture de fichier, parce que le file handle est mieux dans bx.


 *** les oprateurs sur les chanes de caractres ***

    Chaque oprateurs sur les chanes de caractres (MOVS, STOS, SCAS...) est une manire optimise de faire d'autres oprations. Voyons dans quel but vous pouvez les utiliser :

 - MOVS : charger  partir de la position ds:[si] vers es:[di]

        les     di,ds:[si]              ; 3 bytes

        movsb                           ; If we want a byte ( 1 byte )
        movsw                           ; If we want a word ( 1 byte )
        movsd                           ; If we want a dword ( 2 bytes ) 386+

 - LODS : mettre dans l'accumulateur la valeur de la position ds:[si]

        mov     ax,ds:[si]              ; 2 bytes

        lodsb                           ; If we want a byte ( 1 byte )
        lodsw                           ; If we want a word ( 1 byte )
        lodsd                           ; If we want a dword ( 2 bytes ) 386+


 - STOS : mettre  la position es:[di] la valeur de l'accumulateur

        les     di,al                   ; Can't do this!
        les     di,ax                   ; Can't do this!

        stosb                           ; If we want a byte ( 1 byte )
        stosw                           ; If we want a word ( 1 byte )
        stosd                           ; If we want a dword ( 2 bytes ) 386+

 - CMPS : comparer la valeur de ds:[si]  la valeur de es:[di]

        cmp     ds:[si],es:[di]         ; Can't have 2 segment overrides!

        cmpsb                           ; If we want a byte ( 1 byte )
        cmpsw                           ; If we want a word ( 1 byte )
        cmpsd                           ; If we want a dword ( 2 bytes ) 386+
        
 - SCAS: comparer la valeur de l'accumulateur avec es:[di]

        cmp     ax,es:[di]              ; 3 bytes

        scasb                           ; If we want a byte ( 1 byte )
        scasw                           ; If we want a word ( 1 byte )
        scasd                           ; If we want a dword ( 2 bytes ) 386+


 *** les registres 16 bits ***

    Gnralement, l'utilisation des registres 16 bits est plus optimise que celle des registres 8 bits. Voyons un exemple avec l'instruction "mov" :

        mov     ah,06h                  ; 2 bytes
        mov     al,66h                  ; 2 bytes ( 4 bytes total )

        mov     ax,0666h                ; 3 bytes

    Il est plus optimis d'incrmenter/dcrmenter n'importe quel registre 16 bits :

        inc     al                      ; 2 bytes
        inc     ax                      ; 1 byte

        dec     al                      ; 2 bytes
        dec     ax                      ; 1 byte


 *** bases et segments ***

    Le chargement d'un segment vers un autre segment ne peut pas tre fait directement, on doit donc jouer avec :

        mov     es,ds                   ; Can't do this!
        
        mov     ax,ds                   ; 2 bytes
        mov     es,ax                   ; 2 bytes ( 4 bytes total )

        push    ds                      ; 1 byte
        pop     es                      ; 1 byte ( 2 bytes total )

    Utiliser di/si est plus optimis que d'utiliser bp.

        mov     ax,ds:[bp]              ; 4 bytes
        mov     ax,ds:[si]              ; 3 bytes


 *** les procdures ***

    Si vous utiliser une routine un grand nombre de fois, vous devez pensez  la possibilit de faire une procdure. Ceci peut optimiser votre code. Cependant, la mauvaise utilisation d'une procdure peut aller  l'encontre de nos besoins : la taille du code va augmenter. Donc, si vous voulez savoir si la conversion d'une routine en une procdure va conomiser des octets, vous pouvez utiliser cette petite formule :

 X=[taille_routine - (taille"call" + taille"ret"]*nbr_appels - taille_routine

    Le taille"call" + taille"ret" vaut 4 octets. X sera le nombres d'octets conomiss. Voyons la fonction typique qui fait gagner des octets, le mouvement du pointeur de fichier :
 
 fpend: mov     ax,4202h                ; 3 bytes
 fpmov: xor     cx,cx                   ; 2 bytes
        cwd                             ; 1 byte
        int     21h                     ; 2 bytes
        ret                             ; 1 byte

    On a 8 octets plus la taille du call... 11 octets. Voyons si a optimise notre code :

 X = [ 7 - ( 3 + 1 ) ] * 3 - 7
 X = 2 octets conomiss

    C'est un calcul invent, bien sr. Vous pouvez appeler cette routine plus de 3 fois (ou moins), rendre sa taille diffrentes, et plein d'autres choses encore.


 *** dernires astuces pour l'optimisation locale ***

 - utilisez les SFT. Dans cette structure, vous avez un grand nombre d'informations utiles, et vous pouvez les manipuler sans aucun problme.
 - faites passer votre compilateur sur votre code au moins 3 fois pour liminer tous les nop et tout ce qui n'est pas ncessaire.
 - utilisez la pile.
 - Il est plus optimis d'utiliser l'instruction "lea" plutt que d'utiliser un "mov  offset"


-------[  Conclusion

        Il y a donc pleins de manires d'optimiser son code. Certains dveloppeurs de logiciels comme M#####OFT ferait bien de suivre ces conseils... Ah non, Billy a dit qu'il fallait que le code marche avant... :-)


-------[  EOF