Transport d'un dll de eVC++ 3.0 en eVC++ 4.0 [Résolu]

Signaler
Messages postés
16
Date d'inscription
lundi 12 juillet 2004
Statut
Membre
Dernière intervention
13 août 2004
-
 Max -
Bonjour,

Je possède une dll sous eVC++ 3.0 qui fonctionne parfaitement. Cependant, j'ai écrit une application sous eVC++ 4.0. qui a besoin de cette dll. Si je transporte la dll en eVC++ 4.0, elle plante (violation d'accès). Par contre, mon application eVC++4.0 fonctionne très bien avec la dll sous eVC++3.0!!! Quelles sont les différences entre eVC++ 3.0 et 4.0 qui font que ma dll ne fonctionne pas sur 4.0? Quelqu'un peut-il m'aider? Merci d'avance.
Sylvie

3 réponses

Messages postés
16
Date d'inscription
lundi 12 juillet 2004
Statut
Membre
Dernière intervention
13 août 2004
2
Bonjour,

Mon problème est résolu. J'explique ici pourquoi cela ne fonctionnait pas au cas où quelqu'un rencontrerait un problème similaire.

C'est en fait une fonction « memset » qui faisait planter l'application. Il semblerait que cette fonction memset ne fonctionne pas correctement sur des adresses qui ne sont pas alignées sur 4 bytes. Cependant, le compilateur C génère ce genre d'adresses, ce qui cause le plantage de la fonction memset. Cela fonctionne très bien si le code est compilé en C++. Pour contourner ce problème, il a fallu insérer un champ supplémentaire dans la structure sur laquelle travaillait la fonction memset pour « forcer » à aligner les adresses sur 4 bytes (une autre solution consisterait à créer sa propre fonction memset).

J'espère que cette explication pourra aider d'autres personnes?
Sylvie
Messages postés
16
Date d'inscription
lundi 12 juillet 2004
Statut
Membre
Dernière intervention
13 août 2004
2
Bonjour

Je poste ici une constatation faite pour voir un peu ce que vous en pensez et éventuellement dépanner quelqu'un. J'avais posté une question il y a une semaine ou deux concernant un problème de transport d'une dll de eVC++ 3.0 en eVC++ 4.0. Le problème s'avérait en fait venir d'une fonction « memset » qui était mal exécutée et qui faisait planter l'application. Le vrai coupable est enfin démasqué : le compilateur !! Preuves à l'appui?

Voici le bout de code qui plante :

------------------------------------------------------------------------------------------------------------
#define MAX_INFO_LENGTH 40

typedef struct
{
char *pszProcessedText;
unsigned short u16Duration;
char szLastWord[MAX_INFO_LENGTH];
} IVX_TTS_Sentence_Data;

typedef struct
{
long s32Id;
void *pvTtsData;
IVX_TTS_Sentence_Data SentenceData;
long a;
} IVX_TTS_Data;

/* Small test function */
void test()
{
IVX_TTS_Data* data;
ErrorMessage(TEXT("Creates structures"), NULL,0,0);

data=(IVX_TTS_Data*)malloc(sizeof(IVX_TTS_Data));

ErrorMessage(TEXT("adress of data->SentenceData.szLastWord"),NULL,(long)data->SentenceData.szLastWord,2);
memset(data->SentenceData.szLastWord, 0, MAX_INFO_LENGTH*sizeof (char)); //Plante ici
ErrorMessage(TEXT("It crashed ??? or not ???"), 0,0,0);
/* Never goes here:
When compiling in C, for ARMV4 -> it generates wrong ARM mnemonics!!!! */
free (data);
}
------------------------------------------------------------------------------------------------------------

Voici le bout de code assembleur généré en ce qui concerne le « memset » et la fonction « ErrorMessage » qui suit (et qui n'est jamais exécutée vu que le programme plante au memset) :

------------------------------------------------------------------------------------------------------------
; 57 : memset(data->SentenceData.szLastWord, 0, MAX_INFO_LENGTH*sizeof (char)/*data->SentenceData.szLastWord)*/);
; 58 : ErrorMessage(TEXT("It crashed ??? or not ???"), 0,0,0);

ldr r0, [pc, #0x44] //ErrorMessage
mov r3, #0 //Memset
str r3, [r4, #0xE] //Memset
mov r2, #0 //ErrorMessage
str r3, [r4, #0x12] //Memset
mov r1, #0 //ErrorMessage
str r3, [r4, #0x16] //Memset
str r3, [r4, #0x1A] //Memset
str r3, [r4, #0x1E] //Memset
str r3, [r4, #0x22] //Memset
str r3, [r4, #0x26] //Memset
str r3, [r4, #0x2A] //Memset
str r3, [r4, #0x2E] //Memset
str r3, [r4, #0x32] //Memset
mov r3, #0 //ErrorMessage
bl ErrorMessage //ErrorMessage
------------------------------------------------------------------------------------------------------------

C'est ce qu'on obtient quand l'optimisation est haute (/O2) : la fonction « memset » est remplacée par une suite d'instructions « str » visant à mettre à 0 le champ « szLastWord » de la structure (ce qui est le but de la fonction « memset » !). Plantage dans ce cas?
Par contre, si l'optimisation est basse (/O1), le code assembleur correspondant est le suivant :

------------------------------------------------------------------------------------------------------------
; 57 : memset(data->SentenceData.szLastWord, 0, MAX_INFO_LENGTH*sizeof (char)/*data->SentenceData.szLastWord)*/);

mov r2, #0x28
mov r1, #0
mov r0, r4
bl memset

; 58 : ErrorMessage(TEXT("It crashed ??? or not ???"), 0,0,0);

ldr r0, [pc, #0x18]
mov r3, #0
mov r2, #0
mov r1, #0
bl ErrorMessage
------------------------------------------------------------------------------------------------------------

Là, pas d'optimisation de la fonction « memset », le compilateur décide de l'appeler telle quelle. Plus de plantage !!

Explications :

La fonction « str » a besoin, pour fonctionner correctement, d'adresses alignées sur 4 bytes. Si ce n'est pas le cas, l'adresse est arrondie inférieurement à un multiple de 4 !! (voir « Assembler Guide » de www.arm.com : http://www.arm.com/pdfs/DUI0204D_rvct_ct_ag.pdf)

Si l'optimisation est haute (/O2) et si le troisième paramètre de la fonction « memset » est un multiple de 4 (ce qui est le cas dans le programme ci-dessus : MAX_INFO_LENGTH=40), le compilateur décide d'optimiser le code en remplaçant la fonction « memset » par des instructions « str ». Par contre, si le troisième paramètre n'est pas un multiple de 4, le compilateur décide de faire directement appel à memset (comme si l'optimisation était basse /O1). C'est ce que l'on peut constater si on remplace le troisième paramètre de « memset » « MAX_INFO_LENGTH*sizeof (char) » par « (MAX_INFO_LENGTH-2)*sizeof (char) ».

Cependant, il se fait que la structure contient un « short » (« unsigned short u16Duration ») juste avant le champ utilisé dans « memset » (« char szLastWord[MAX_INFO_LENGTH] »). Dès lors, ce champ commence en une adresse non multiple de 4 (en r4 + #0xE, comme on peut le voir dans le code assembleur), l'adresse de ce champ n'est donc pas alignée sur 4 bytes. Le « paramètre » [r4, #0xE] n'est pas apprécié par l'instruction « str » qui l'arrondi inférieurement à un nombre multiple de 4. La mise à 0 faite par la première instruction « str » commence donc trop tôt (de 2 bytes, le paramètre [r4, #0xE] est remplacé par [r4, #0x10] qui ne correspond plus au début du champ à mettre à zéro) et cause le plantage de l'application.

En bref, lorsque le compilateur décide d'optimiser la fonction « memset » par des instructions « str », il ne vérifie que le fait que le troisième paramètre soit un multiple de 4 mais ne vérifie pas que le champ à mettre à 0 est aligné sur 4 bytes. Or, ce sont ces deux conditions qui sont nécessaires afin de pouvoir se servir proprement de l'instruction « str ».

Deux solutions :
1. Baisser le niveau d'optimisation du compilateur
2. Introduire dans la structure un autre « short » avant le champ szLastWord[MAX_INFO_LENGTH] pour que ce dernier soit bien aligné sur 4 bytes.
Alors là, désolé de déterrer le truc presque 10 ans après (oui, je bosse sur evc++ 4, et alors?), mais je suis sur le c*l que personne n'ai apprécié la performance de l'investigation et la précision de l'explication!
Ce genre de bug, je vous mets au défie de les trouver et de les comprendre!

Chapeau bas! 8)