Developpez.com - Rubrique Pascal

Le Club des Développeurs et IT Pro

Différence de traduction entre char() et chr() par le compilateur Free Pascal

Par Clandestino

Le 2007-03-24 12:56:28, par Clandestino, Membre éclairé
Introduction

Suite à la lecture d'un post sur le sujet ( http://www.developpez.net/forums/sho...d.php?t=296299 ) , j'ai vérifié dans mon debugger s'il y avait une différence entre les deux fonctions suivantes :
Code :
1
2
3
4
function fchar (c: byte): char;
begin
fchar := char(c);
end;
Code :
1
2
3
4
function fchr (c: byte): char;
begin
fchr := chr(c);
end;
En quelques secondes je me suis rendu compte que le compilateur avait traduit ces deux fonctions de la meme manière, les instructions en assembleur étant exactement les memes pour les deux fonctions. Ceci confirmait l'analyse de Droggo...
Envoyé par Droggo
Pris d'une brutale poussée de courage, je suis allé voir le code généré :

c'est exactement le même code, ce qui confirme l'impression que j'avais eue en voyant les temps d'exécution très proches, avec l'un ou l'autre plus ou moins rapide selon le test
J'ai soudain été étonné de voir transparaitre dans les réponses suivantes, que l'analyse d'un binaire ou le debuggage de process n'est pas forcément évident pour beaucoup...

Prenant à mon tour mon courage à deux mains , je vai vous montrer comment vous pouvez démontrer sans aucun outil (ormis votre cerveau et un compilateur pascal ), que ces deux fonctions sont équivalentes...
J'espère que ce post vous sera utile ou vous interessera...
  Discussion forum
14 commentaires
  • Clandestino
    Membre éclairé
    Premiers tests

    La première chose évidente est de tester que les deux fonctions renvoient les memes valeurs ...

    me voici donc en train d'ecrire puis de compiler et lancer ce petit test qui s'amuse à calculer les resultats pour tous les bytes de 1 à 255:
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    program test;
    
    // mes fonctions à tester
    function fchar (c: byte): char;
    begin
    fchar := char(c);
    end;
    
    function fchr (c: byte): char;
    begin
    fchr := chr(c);
    end;
    
    // variables du programme
    Var
     i : byte;
    
    // programme principal
    begin
      for i := 1 to 255 do
      if fchar(i)<>fchr(i) then
       writeln('test sur ',i,' valeurs differentes')
      else
       writeln('test sur ',i,' valeurs egales');
      readln;
    end.
    Au niveau des resultats, les fonctions sont donc équivalentes...

    Je pousse le vice jusqu'à changer la variable i en integer et tenter une boucle de 1 à 500...
    Freepascal compile correctement et le test se passe sans erreur...

    Après ces tests de résultats passons à une analyse des entrailles....
  • Clandestino
    Membre éclairé
    Auto Analyse 1 : adresse des fonctions

    Je décide donc d'analyser le programme par instructions...

    Je modifie le corps principal de mon programme et ecris ce petit code qui me permet de récupérer les adresses en memoire des fonctions fchar et fchr

    Code :
    1
    2
    3
    4
    5
    6
    // programme principal
    begin
     writeln('adresse de fchar : ', hexstr(integer(@fchar),8));
     writeln('adresse de fchr : ', hexstr(integer(@fchr),8));
     readln;
    end.
    Explications sommaires:
    @fchar représente un pointeur vers fchar, cette expression contient donc l'adresse memoire ou la fonction commence.
    Le transtypage par integer() est là pour permettre l'utilisation de la fonction hexstr de freepascal, qui attend en premier parametre un integer, et qui permet d'ecrire l'adresse trouvée dans un format hexadecimal classique sur 8 chiffres...

    Avec freepascal, et sur mon systeme d'exploitation j'obtiens:
    Code :
    1
    2
    adresse de fchar : 00401018
    adresse de fchr : 00401025
  • Clandestino
    Membre éclairé
    Petit moment de reflexion et strategie de demonstration

    Me voici avec les adresses des deux fonctions...
    je calcule combien de byte il y a entre les deux debuts de fonction :attention les chiffres donnés étaient en hexadécimal, je laisse mon programme me donner le résultat
    Code :
     writeln((integer(@fchr))-(integer(@fchar)));
    Il y a donc 13 bytes entre le debut de ma fonction fchar et le debut de ma fonction fchr...

    Ma fonction fchar fait donc au maximum 13 bytes...

    Pour prouver que mes deux fonctions sont égales, il me suffit donc de dumper 13 bytes de memoire à partir de l'adresse de fchar, et 13 bytes de memoire à partir de fchr, puis de comparer les resultats.... Si je trouve la meme chose, c'est démontré....
  • Clandestino
    Membre éclairé
    Dump memoire des deux fonctions

    j'ai effectué ce test avec le compilateur freepascal sous windows vista... je vous encourage à tester la meme chose dans d'autres situations...
    Ma demonstration ne s'applique, jusqu'à preuve du contraire qu'à cette configuration...

    J'ai intégré un peu d'assembleur dans mon code, ceci rendant ce mini tuto un peu plus interessant

    Voici une fonction pour recupérer un byte à une adresse memoire donnée :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // ma fonction de dump
    Function ReadByteAtAddress( adresse: dword): byte;
    var
    d: dword;
    begin
      asm
        push eax        // sauvegarde de eax sur la pile
        push edi        // sauvegarde d'edi sur la pile
        mov edi, dword ptr[adresse]  // edi reçoit l'adresse de la fonction
        mov eax, dword ptr[edi]      // eax reçoit le dword à cette adresse
        mov d, eax                   // je stocke le resultat dans d
        pop edi         // restauration de edi depuis la pile
        pop eax         // restauration de eax depuis la pile
      end;
      // ici je mets un dword dans un byte, je perds donc les informations de 3 bytes
      // suivants que je lirai ensuite.
      // c'est un choix délibéré de lire byte par byte juste parceque la fonction
      // hexstr me renverrai sinon un resultat à lire de droite à gauche...
      ReadByteAtAddress := d;
    end;
    J'ai pris la précution dans ce bout de code asm de sauvegarder les registres utilisés puis de les retribuer, habitué que je suis à faire celà quand je patche depuis un debuggueur... je ne suis pas certain que celà soit nécessaire depuis un code source à compiler... d'autres sauront sans doute me préciser ce sujet...
  • Clandestino
    Membre éclairé
    Pogramme final

    Il ne nous reste donc plus qu'à lire deux fois treize bytes et afficher le résultat pour voir ou nous en sommes...

    Ceci peut être fait ainsi :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    program test; 
     
    // mes fonctions à tester 
    function fchar (c: byte): char; 
    begin 
      fchar := char(c); 
    end; 
     
    function fchr (c: byte): char; 
    begin 
      fchr := chr(c); 
    end; 
     
    // ma fonction de dump 
    Function ReadByteAtAddress( adresse: dword): byte; 
    var 
    d: dword; 
    begin 
      asm 
        push eax        // sauvegarde de eax sur la pile 
        push edi        // sauvegarde d'edi sur la pile 
        mov edi, dword ptr[adresse]  // edi reçoit l'adresse de la fonction 
        mov eax, dword ptr[edi]      // eax reçoit le dword à cette adresse 
        mov d, eax                   // je stocke le resultat dans d 
        pop edi         // restauration de edi depuis la pile 
        pop eax         // restauration de eax depuis la pile 
      end; 
      // ici je met un dword dans un byte, je perds donc les informations de 3 bytes 
      // suivants que je lirai ensuite. 
      // j'ai choisi de lire byte par byte juste parceque la fonction 
      // hexstr me renverrai sinon un resultat à lire de droite à gauche... 
      ReadByteAtAddress := d; 
    end; 
     
    // variables du programme 
    Var 
     i : integer; 
     buffchar : array[1..13] of byte; 
     buffchr  : array[1..13] of byte; 
     
    // programme principal 
    begin 
     i:=0; 
     
     
      
     writeln('adresse de fchar : ', hexstr(integer(@fchar),8)); 
     writeln('adresse de fchr : ', hexstr(integer(@fchr),8)); 
     
     
     writeln; 
     
     writeln('  Dump fchar                  Dump fchr'); 
     writeln; 
     writeln('adresse   byte              adresse   byte'); 
     writeln; 
     repeat 
      inc(i); 
      buffchar[I]:= ReadByteAtAddress(integer(@fchar)+i-1); 
      buffchr[I]:= ReadByteAtAddress(integer(@fchr)+i-1); 
      writeln(hexstr(integer(@fchar)+i-1,8),'  ', hexstr(buffchar[i],2), 
      '                ', 
      hexstr(integer(@fchr)+i-1,8),'  ', hexstr(buffchr[i],2)); 
      until i = 13; 
     readln; 
    end.
    voici le résultat chez moi:
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    adresse de fchar : 00401018 
    adresse de fchr : 00401025 
     
      Dump fchar                  Dump fchr 
     
    adresse   byte              adresse   byte 
     
    00401018  55                00401025  55 
    00401019  89                00401026  89 
    0040101A  E5                00401027  E5 
    0040101B  83                00401028  83 
    0040101C  EC                00401029  EC 
    0040101D  04                0040102A  04 
    0040101E  8A                0040102B  8A 
    0040101F  45                0040102C  45 
    00401020  08                0040102D  08 
    00401021  C9                0040102E  C9 
    00401022  C2                0040102F  C2 
    00401023  04                00401030  04 
    00401024  00                00401031  00
  • Clandestino
    Membre éclairé
    Je viens de faire le test à l'instant sous mon pc sous linux mandriva 2007, compilation avec lazarus...

    J'ai du ajouter simplement la directive spécifiant l'assembleur à utiliser :
    {$ASMMODE intel}

    Voici le résultat :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    adresse de fchar : 080480B8
    adresse de fchr : 080480D8
    32 bytes
    
      Dump fchar                  Dump fchr
    
    adresse   byte              adresse   byte
    
    080480B8  55                080480D8  55
    080480B9  89                080480D9  89
    080480BA  E5                080480DA  E5
    080480BB  83                080480DB  83
    080480BC  EC                080480DC  EC
    080480BD  08                080480DD  08
    080480BE  88                080480DE  88
    080480BF  45                080480DF  45
    080480C0  FC                080480E0  FC
    080480C1  8A                080480E1  8A
    080480C2  45                080480E2  45
    080480C3  FC                080480E3  FC
    080480C4  88                080480E4  88
    080480C5  45                080480E5  45
    080480C6  F8                080480E6  F8
    080480C7  8A                080480E7  8A
    080480C8  45                080480E8  45
    080480C9  F8                080480E9  F8
    080480CA  C9                080480EA  C9
    080480CB  C3                080480EB  C3
    080480CC  8D                080480EC  8D
    080480CD  B4                080480ED  B4
    080480CE  26                080480EE  26
    080480CF  00                080480EF  00
    080480D0  00                080480F0  00
    080480D1  00                080480F1  00
    080480D2  00                080480F2  00
    080480D3  8D                080480F3  8D
    080480D4  74                080480F4  74
    080480D5  26                080480F5  26
    080480D6  00                080480F6  00
    080480D7  90                080480F7  90
    Pour Linux aussi donc , freepascal compile le meme code ... Par contre il lui faut 32 bytes pour la meme chose...
  • Clandestino
    Membre éclairé
    La fonction fchar s'arrète plus tot que 32 bytes, de manière classique par un
    Code :
    1
    2
    008048CA  C9   LEAVE
    008048CB  C3   RET


    (voir cliché de la fenetre de désassemblage joint)

    Du code rempli l'intervale entre les deux fonctions
    Le meme code rempli l'intervalle entre la deuxième fonction... et le reste du programme...

    La comparaison aurait pu echouer.... J'ai eu de la chance sur ce coup-ci...
  • droggo
    Expert confirmé
    Al,

    Vérifié sur Windows : freePascal aligne sur 16 bits !! Hou la honte

    (options: optimisation pour la vitesse, -O2)
  • droggo
    Expert confirmé
    Hi,

    Dans ce que tu appelles "ces deux fonctions", une seule notation en est une (chr), l'autre est un transtypage.

    Que le compilateur les traite de la même manière et génère le même code ne rentre pas en compte pour parler de fonction au lieu de transtypage, d'autant qu'il est bien possible que d'autres compilateurs ne feront pas de même, bien que ce soit fort probable.
  • droggo
    Expert confirmé
    Hi,
    Envoyé par Clandestino
    Petit moment de reflexion et strategie de demonstration

    Me voici avec les adresses des deux fonctions...
    je calcule combien de byte il y a entre les deux debuts de fonction :attention les chiffres donnés étaient en hexadécimal, je laisse mon programme me donner le résultat
    Code :
     writeln((integer(@fchr))-(integer(@fchar)));
    Il y a donc 13 bytes entre le debut de ma fonction fchar et le debut de ma fonction fchr...

    Ma fonction fchar fait donc au maximum 13 bytes...

    Pour prouver que mes deux fonctions sont égales, il me suffit donc de dumper 13 bytes de memoire à partir de l'adresse de fchar, et 13 bytes de memoire à partir de fchr, puis de comparer les resultats.... Si je trouve la meme chose, c'est démontré....
    Sauf si le compilateur a ajouté des instructions "bidons" pour aligner le code, cas fréquent avec les compilateurs sachant vraiment optimiser à fond (presque à fond, les processeurs modernes sont très difficiles à optimiser totalement).

    Autant prendre un désassembleur et aller voir le code.