Liste de vérification des problèmes de portage

Général

  • Utilisez les nouveaux types de données Windows sécurisés 64 bits.

    Les nouveaux types de données sécurisés 64 bits, décrits plus haut dans ce document, sont définis dans Basetsd.h. Ce fichier d’en-tête est inclus dans Ntdef.h, qui est inclus dans Ntddk.h, Wdm.h et Ntifs.h.

  • Utilisez soigneusement les macros du compilateur de plateforme.

    L’hypothèse suivante n’est plus valide :

    #ifdef _WIN32  // 32-bit Windows code
    ...
    #else          // 16-bit Windows code
    ...
    #endif
    

    Toutefois, le compilateur 64 bits définit _WIN32 pour la compatibilité descendante.

    En outre, l’hypothèse suivante n’est plus valide :

    #ifdef _WIN16  // 16-bit Windows code
    ...
    #else          // 32-bit Windows code
    ...
    #endif
    

    Dans ce cas, la clause else peut représenter _WIN32 ou _WIN64.

  • Utilisez les spécificateurs de format appropriés avec printf et wsprintf.

    Utilisez %p pour imprimer des pointeurs en hexadécimal. Il s’agit du meilleur choix pour les pointeurs d’impression.

    Note Une version ultérieure de Visual C++ prendra en charge %I pour imprimer des données polymorphes. Il traitera les valeurs comme 64 bits dans Windows 64 bits et 32 bits dans Windows 32 bits. Visual C++ prend également en charge %I64 pour imprimer des valeurs de 64 bits.

  • Connaissez votre espace d’adressage.

    Ne supposez pas aveuglément, par exemple, que si une adresse est une adresse de noyau, son bit d’ordre élevé doit être défini. Pour obtenir l’adresse système la plus basse, utilisez la macro MM_LOWEST_SYSTEM_ADDRESS .

Arithmétique sur les pointeurs

  • Soyez prudent lorsque vous effectuez des opérations non signées et signées.

    Tenez compte des éléments suivants :

    ULONG x;
    LONG y;
    LONG *pVar1;
    LONG *pVar2;
    
    pVar2 = pVar1 + y * (x - 1);
    

    Le problème se produit parce que x n’est pas signé, ce qui rend l’expression entière non signée. Cela fonctionne bien, sauf si y est négatif. Dans ce cas, y est converti en valeur non signée, l’expression est évaluée à l’aide de la précision 32 bits, mise à l’échelle et ajoutée à pVar1. Sur Windows 64 bits, ce nombre négatif non signé 32 bits devient un grand nombre positif 64 bits, ce qui donne un résultat incorrect. Pour résoudre ce problème, déclarez x en tant que valeur signée ou saisissez-la explicitement en LONG dans l’expression.

  • Soyez prudent lorsque vous utilisez des constantes hexadécimales et des valeurs non signées.

    L’assertion suivante n’est pas vraie sur les systèmes 64 bits :

    ~((UINT64)(PAGE_SIZE-1)) == (UINT64)~(PAGE_SIZE-1)
    PAGE_SIZE = 0x1000UL  // Unsigned long - 32 bits
    PAGE_SIZE - 1 = 0x00000fff
    

    Expression LHS :

    // Unsigned expansion(UINT64)(PAGE_SIZE -1 ) = 0x0000000000000fff
    ~((UINT64)(PAGE_SIZE -1 )) = 0xfffffffffffff000
    

    Expression RHS :

    ~(PAGE_SIZE-1) = 0xfffff000
    (UINT64)(~(PAGE_SIZE - 1)) = 0x00000000fffff000
    

    Par conséquent :

    ~((UINT64)(PAGE_SIZE-1)) != (UINT64)(~(PAGE_SIZE-1))
    
  • Soyez prudent avec les opérations NOT.

    Tenez compte des éléments suivants :

    UINT_PTR a; ULONG b;
    a = a & ~(b - 1); 
    

    Le problème est que ~(b−1) produit 0x0000 0000 xxxx xxxx et non 0xFFFF FFFF xxxx xxxx. Le compilateur ne détecte pas cela. Pour résoudre ce problème, modifiez le code comme suit :

    a = a & ~((UINT_PTR)b - 1);
    
  • Soyez prudent lorsque vous calculez les tailles de mémoire tampon.

    Tenez compte des éléments suivants :

    len = ptr2 - ptr1 
    /* len could be greater than 2**32 */
    

    Caster les pointeurs en PCHAR pour l’arithmétique du pointeur.

    Note Si len est déclaré INT ou ULONG, un avertissement du compilateur est généré. Les tailles de mémoire tampon, même lorsqu’elles sont calculées correctement, peuvent toujours dépasser la capacité d’ULONG.

  • Évitez d’utiliser des décalages de pointeur calculés ou codés en dur.

    Lorsque vous travaillez avec des structures, utilisez la macro FIELD_OFFSET chaque fois que possible pour déterminer le décalage des membres de la structure.

  • Évitez d’utiliser des valeurs de pointeur ou de handle codées en dur.

    Ne passez pas de pointeurs ou de handles codés en dur, tels que (HANDLE)0xFFFFFFFF à des routines telles que ZwCreateSection. Utilisez plutôt des constantes, telles que INVALID_HANDLE_VALUE, qui peuvent être définies pour avoir la valeur appropriée pour chaque plateforme.

  • N’oubliez pas que dans Windows 64 bits, 0xFFFFFFFF n’est pas identique à -1.

    Par exemple :

    DWORD index = 0;
    CHAR *p;
    
    // if (p[index-1] == '0') causes access violation on 64-bit Windows!
    

    Sur les ordinateurs 32 bits :

    p[index-1] == p[0xffffffff] == p[-1] 
    

    Sur les ordinateurs 64 bits :

    p[index-1] == p[0x00000000ffffffff] != p[-1]
    

    Ce problème peut être évité en changeant le type d’index de DWORD en DWORD_PTR.

Polymorphisme

  • Soyez prudent avec les interfaces polymorphes.

    Ne créez pas de fonctions qui acceptent des paramètres de type DWORD (ou d’autres types de précision fixe) pour les données polymorphes. Si les données peuvent être un pointeur ou une valeur intégrale, le type de paramètre doit être UINT_PTR ou PVOID, et non DWORD.

    Par exemple, ne créez pas de fonction qui accepte un tableau de paramètres d’exception tapés en tant que valeurs DWORD . Le tableau doit être un tableau de valeurs DWORD_PTR . Par conséquent, les éléments de tableau peuvent contenir des adresses ou des valeurs intégrales 32 bits. La règle générale est que si le type d’origine est DWORD et qu’il doit être une largeur de pointeur, convertissez-le en valeur DWORD_PTR . C’est pourquoi il existe des types de précision de pointeur correspondants pour les types Win32 natifs. Si vous avez du code qui utilise DWORD, ULONG ou d’autres types 32 bits de manière polymorphe (autrement dit, vous souhaitez vraiment que le paramètre ou le membre de structure contienne une adresse), utilisez UINT_PTR à la place du type actuel.

  • Soyez prudent lorsque vous appelez des fonctions qui ont des paramètres de pointeur OUT.

    Ne procédez pas comme suit :

    void GetBufferAddress(OUT PULONG *ptr);
    {
      *ptr=0x1000100010001000;
    }
    void foo()
    {
      ULONG bufAddress;
      //
      // This call causes memory corruption.
      //
      GetBufferAddress((PULONG *)&bufAddress);
    }
    

    La diffusion de type bufAddress sur (PULONG *) empêche une erreur du compilateur. Toutefois, GetBufferAddress écrit une valeur 64 bits dans l’emplacement de mémoire à&bufAddress. Étant donné que bufAddress n’est qu’une valeur de 32 bits, les 32 bits qui suivent immédiatement bufAddress seront remplacés. Il s’agit d’un bogue très subtil et difficile à trouver.

  • Ne castez pas les pointeurs en INT, LONG, ULONG ou DWORD.

    Si vous devez convertir un pointeur pour tester certains bits, définir ou effacer des bits, ou manipuler son contenu, utilisez le type UINT_PTR ou INT_PTR . Ces types sont des types intégraux qui sont mis à l’échelle à la taille d’un pointeur pour Windows 32 bits et 64 bits (par exemple, ULONG pour Windows 32 bits et _int64 pour Windows 64 bits). Par exemple, supposons que vous portiez le code suivant :

    ImageBase = (PVOID)((ULONG)ImageBase | 1);
    

    Dans le cadre du processus de portage, vous devez modifier le code comme suit :

    ImageBase = (PVOID)((ULONG_PTR)ImageBase | 1);
    

    Utilisez UINT_PTR et INT_PTR le cas échéant (et si vous n’êtes pas certain qu’ils soient nécessaires, il n’y a aucun mal à les utiliser au cas où). Ne convertissez pas vos pointeurs en types ULONG, LONG, INT, UINT ou DWORD.

    Remarque HANDLE étant défini comme un void \, la conversion d’une valeur *HANDLE sur une valeur ULONG pour tester, définir ou effacer les deux bits faibles est une erreur de programmation.

  • Utilisez PtrToLong et PtrToUlong pour tronquer les pointeurs.

    Si vous devez tronquer un pointeur vers une valeur 32 bits, utilisez la fonction PtrToLong ou PtrToUlong (définie dans Basetsd.h). Cette fonction désactive l’avertissement de troncation du pointeur pendant la durée de l’appel.

    Utilisez ces fonctions avec soin. Après avoir tronqué une variable de pointeur à l’aide de l’une de ces fonctions, ne retransmettent jamais la variable LONG ou ULONG résultante en pointeur. Ces fonctions tronquent les 32 bits supérieurs d’une adresse, qui sont généralement nécessaires pour accéder à la mémoire référencée à l’origine par le pointeur. L’utilisation de ces fonctions sans examen minutieux entraîne la fragilité du code.

Structures de données et alignement de la structure

  • Examinez attentivement toutes les utilisations des pointeurs de structure de données.

    Voici les zones de problèmes courantes :

    • Structures de données stockées sur disque ou échangées avec des processus 32 bits.
    • Unions explicites et implicites avec des pointeurs.
    • Descripteurs de sécurité.
  • Utilisez la macro FIELD_OFFSET .

    Par exemple :

    struct xx {
       DWORD NumberOfPointers;
       PVOID Pointers[1];
    };
    
    

    L’allocation suivante est incorrecte dans Windows 64 bits, car le compilateur rembourse la structure avec 4 octets supplémentaires pour rendre l’exigence d’alignement de 8 octets :

    malloc(sizeof(DWORD)+100*sizeof(PVOID)); 
    
    

    Voici comment procéder correctement :

    malloc(FIELD_OFFSET(struct xx, Pointers) +100*sizeof(PVOID));
    
  • Utilisez la macro TYPE_ALIGNMENT .

    La macro TYPE_ALIGNMENT retourne l’exigence d’alignement pour un type de données donné sur la plateforme actuelle. Par exemple :

    TYPE_ALIGNMENT(KFLOATING_SAVE) == 4 on x86, 8 on Itanium
    TYPE_ALIGNMENT(UCHAR) == 1 everywhere
    

    Par exemple, un code tel que celui-ci :

    ProbeForRead(UserBuffer, UserBufferLength, sizeof(ULONG));
    

    devient plus portable en cas de modification de :

    ProbeForRead(UserBuffer, UserBufferLength, TYPE_ALIGNMENT(ULONG));
    
  • Surveillez les modifications de type de données dans les structures de noyau public.

    Par exemple, le champ Informations dans la structure IO_STATUS_BLOCK est désormais de type ULONG_PTR.

  • Soyez prudent lorsque vous utilisez des directives d’emballage de structure.

    Sur Windows 64 bits, si une structure de données est mal alignée, les routines qui manipulent la structure, telles que RtlCopyMemory et memcpy, ne sont pas défaillantes. Au lieu de cela, ils déclenchent une exception. Par exemple :

    #pragma pack (1)  /* also set by /Zp switch */
    struct Buffer {
        ULONG size;
        void *ptr;
    };
    
    void SetPointer(void *p) {
        struct Buffer s;
        s.ptr = p;  /* will cause alignment fault */
        ...
    }
    

    Vous pouvez utiliser la macro UNALIGNED pour résoudre les problèmes suivants :

    void SetPointer(void *p) {
        struct Buffer s;
        *(UNALIGNED void *)&s.ptr = p;
    }
    

    Malheureusement, l’utilisation de la macro UNALIGNED est très coûteuse sur les processeurs Itanium. Une meilleure solution consiste à placer des valeurs et des pointeurs 64 bits au début de la structure.

    Note Si possible, évitez d’utiliser différents niveaux d’empaquetage dans le même fichier d’en-tête.

Informations supplémentaires