Ho bisogno di una funzione che (come SecureZeroMemory da WinAPI) azzeri sempre la memoria e non venga ottimizzata, anche se il compilatore pensa che la memoria non accederà mai più dopo. Sembra un candidato perfetto per volatile. Ma sto avendo alcuni problemi a far funzionare questo con GCC. Ecco una funzione di esempio:
void volatileZeroMemory(volatile void* ptr, unsigned long long size)
{
volatile unsigned char* bytePtr = (volatile unsigned char*)ptr;
while (size--)
{
*bytePtr++ = 0;
}
}
Abbastanza semplice. Ma il codice che GCC genera effettivamente se lo chiami varia notevolmente con la versione del compilatore e la quantità di byte che stai effettivamente cercando di azzerare. https://godbolt.org/g/cMaQm2
- GCC 4.4.7 e 4.5.3 non ignorano mai il volatile.
- GCC 4.6.4 e 4.7.3 ignorano il volatile per le dimensioni di array 1, 2 e 4.
- GCC 4.8.1 fino a 4.9.2 ignora volatile per le dimensioni di array 1 e 2.
- GCC da 5.1 a 5.3 ignora il volatile per le dimensioni di array 1, 2, 4, 8.
- GCC 6.1 lo ignora semplicemente per qualsiasi dimensione di array (punti bonus per la coerenza).
Qualsiasi altro compilatore che ho testato (clang, icc, vc) genera gli archivi che ci si aspetterebbe, con qualsiasi versione del compilatore e qualsiasi dimensione di array. Quindi a questo punto mi chiedo, si tratta di un bug del compilatore GCC (piuttosto vecchio e grave?), O la definizione di volatile nello standard è che imprecisa sul fatto che questo sia effettivamente un comportamento conforme, rendendo essenzialmente impossibile scrivere un portatile " SecureZeroMemory "funzione?
Modifica: alcune osservazioni interessanti.
#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'eventuale scrittura da callMeMaybe () farà in modo che tutte le versioni di GCC tranne la 6.1 generino gli archivi previsti. Commentando nella barriera della memoria, GCC 6.1 genererà anche gli archivi, sebbene solo in combinazione con la possibile scrittura da callMeMaybe ().
Qualcuno ha anche suggerito di svuotare le cache. Microsoft non tenta affatto di svuotare la cache in "SecureZeroMemory". È probabile che la cache venga comunque invalidata abbastanza velocemente, quindi questo probabilmente non sarà un grosso problema. Inoltre, se un altro programma stava tentando di sondare i dati, o se stava per essere scritto nel file di paging, sarebbe sempre la versione azzerata.
Ci sono anche alcune preoccupazioni riguardo GCC 6.1 usando memset () nella funzione standalone. Il compilatore GCC 6.1 su godbolt potrebbe essere una build non funzionante, poiché GCC 6.1 sembra generare un ciclo normale (come fa 5.3 su godbolt) per la funzione standalone per alcune persone. (Leggi i commenti della risposta di zwol.)
volatileè un bug a meno che non sia dimostrato diversamente. Ma molto probabilmente un bug.volatileè così sottospecificato da essere pericoloso: non usarlo.