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

Le , 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 : Sélectionner tout
1
2
3
4
function fchar (c: byte): char;
begin
fchar := char(c);
end;
Code : Sélectionner tout
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...
Citation 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...


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de Clandestino Clandestino - Membre éclairé https://www.developpez.com
le 24/03/2007 à 13:40
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 : Sélectionner tout
 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é....
Avatar de droggo droggo - Expert confirmé https://www.developpez.com
le 24/03/2007 à 13:52
Hi,
Citation 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 : Sélectionner tout
 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.
Avatar de Clandestino Clandestino - Membre éclairé https://www.developpez.com
le 24/03/2007 à 14:14
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 : Sélectionner tout
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...
Avatar de Clandestino Clandestino - Membre éclairé https://www.developpez.com
le 24/03/2007 à 14:17
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 : Sélectionner tout
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 : Sélectionner tout
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
Avatar de Clandestino Clandestino - Membre éclairé https://www.developpez.com
le 24/03/2007 à 14:35
Citation Envoyé par droggo
Hi,

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.
c'est pour celà que je disais ma fonction fait au maximum 13 bytes.
elle peut en faire moins. J'aurai aussi pu avoir du junk code et ma demonstration n'aurait pas suffit dans l'état...

Ma démarche ici etait simplement de montrer une technique de programmation qui permet d'auto-analyser son programme, sans avoir recours à un outil externe... J'ai trouvé celà sympatique en le faisant et je me suis pris de l'envie de partager ce bout de code...

Il est certain que l'analyse du problème avec ollydbg m'a pris beaucoup moins de temps que l'ecriture de ce minitut, mais j'y ai aussi pris moins de plaisir...

PS... J'ai volontairement mis ce post dans le sous forum freepascal en précisant que c'était sous windows, car une grande partie de ce que je viens d'ecrire n'est pas applicable à des compilateurs 16 bits, et qu'il reste à voir ce que celà peut donner sous d'autres OS...
Avatar de droggo droggo - Expert confirmé https://www.developpez.com
le 24/03/2007 à 15:29
Hi,

Oui, j'avais bien compris, mais je pensais que tu avais en tête une réponse automatique par le programme, genre "les fonctions sont identiques", par comparaison faite par le programme.

Au temps pour moi, j'ai écrit "les fonctions sont identiques", malgré tout ce que j'en ai di jusqu'ici.
Donc disons "les codes générés sont identiques"
Avatar de Clandestino Clandestino - Membre éclairé https://www.developpez.com
le 24/03/2007 à 15:53
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 : Sélectionner tout
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...
Avatar de Clandestino Clandestino - Membre éclairé https://www.developpez.com
le 24/03/2007 à 16:17
La fonction fchar s'arrète plus tot que 32 bytes, de manière classique par un
Code : Sélectionner tout
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...
Avatar de droggo droggo - Expert confirmé https://www.developpez.com
le 24/03/2007 à 17:26
Hol,

Oui, alignement sur 32 bits pour l'entrée d'une fonction, ça fait partie des optimisations pour les processeurs 32 bits.

Que le code généré soit également le même n'est pas étonnant, on cherche à obtenir le même résultat, et c'est le même compilateur.

Par contre, que l'alignement soit fait dans la version Linux et pas dans celle pour Windows est un peu plus étonnant.
Je vais jeter un coup d'oeil.
Avatar de Haywire Haywire - Membre confirmé https://www.developpez.com
le 24/03/2007 à 17:32
Merci Clandestino pour ce mini tuto très intéressant .
Je me pose souvent des questions existentielles dans le même genre donc ça pourra m'être utile .

ps: Cool c'est mon 200e message et donc ma 1ere étoile !
Avatar de droggo droggo - Expert confirmé https://www.developpez.com
le 24/03/2007 à 18:18
Al,

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

(options: optimisation pour la vitesse, -O2)
Responsables bénévoles de la rubrique Pascal : Gilles Vasseur - Alcatîz -