
PSylvie
|
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.
|