J'ai besoin d'une fonction qui (comme SecureZeroMemory de la WinAPI) met toujours à zéro la mémoire et n'est pas optimisée, même si le compilateur pense que la mémoire ne sera plus jamais accessible après cela. Cela semble être un candidat parfait pour le volatile. Mais j'ai quelques problèmes pour que cela fonctionne avec GCC. Voici un exemple de fonction :
void volatileZeroMemory(volatile void* ptr, unsigned long long size)
{
volatile unsigned char* bytePtr = (volatile unsigned char*)ptr;
while (size--)
{
*bytePtr++ = 0;
}
}
C'est assez simple. Mais le code que GCC génère réellement si vous l'appelez varie énormément en fonction de la version du compilateur et de la quantité d'octets que vous essayez réellement de réduire à zéro. https://godbolt.org/g/cMaQm2
- GCC 4.4.7 et 4.5.3 n'ignorent jamais le volatile.
- GCC 4.6.4 et 4.7.3 ignorent le volatile pour les tableaux de taille 1, 2 et 4.
- GCC 4.8.1 jusqu'à 4.9.2 ignore volatile pour les tableaux de taille 1 et 2.
- GCC 5.1 jusqu'à 5.3 ignore le volatile pour les tableaux de taille 1, 2, 4, 8.
- GCC 6.1 l'ignore simplement pour toute taille de tableau (points bonus pour la cohérence).
Tous les autres compilateurs que j'ai testés (clang, icc, vc) génèrent les magasins que l'on pourrait attendre, avec n'importe quelle version du compilateur et n'importe quelle taille de tableau. Donc, à ce stade, je me demande s'il s'agit d'un bug (assez ancien et grave ?) du compilateur GCC, ou si la définition de volatile dans la norme est si imprécise qu'il s'agit en fait d'un comportement conforme, rendant essentiellement impossible l'écriture d'une fonction "SecureZeroMemory" portable ?
Edit : Quelques observations intéressantes.
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <atomic>
void callMeMaybe(char* buf);
void volatileZeroMemory(volatile void* ptr, std::size_t size)
{
for (auto bytePtr = static_cast<volatile std::uint8_t*>(ptr); size-- > 0; )
{
*bytePtr++ = 0;
}
//std::atomic_thread_fence(std::memory_order_release);
}
std::size_t foo()
{
char arr[8];
callMeMaybe(arr);
volatileZeroMemory(arr, sizeof arr);
return sizeof arr;
}
L'écriture possible de callMeMaybe() fera que toutes les versions de GCC sauf 6.1 génèrent les magasins attendus. Le fait de commenter dans la barrière de mémoire fera également que GCC 6.1 génère les magasins, bien que seulement en combinaison avec l'écriture possible de callMeMaybe().
Quelqu'un a également suggéré de vider les caches. Microsoft fait no essayer de vider le cache en "SecureZeroMemory". Le cache sera probablement invalidé assez rapidement de toute façon, donc ce n'est probablement pas un gros problème. De plus, si un autre programme essayait de sonder les données, ou si elles devaient être écrites dans le fichier de page, ce serait toujours la version mise à zéro.
Il y a également quelques soucis concernant l'utilisation de memset() dans la fonction autonome de GCC 6.1. Le compilateur GCC 6.1 sur godbolt pourrait être une construction cassée, car GCC 6.1 semble générer une boucle normale (comme 5.3 le fait sur godbolt) pour la fonction autonome pour certaines personnes. (Lire les commentaires de la réponse de zwol).