coo*_*451 87 c c++ standards gcc
我需要一个函数(就像来自WinAPI的SecureZeroMemory)总是将内存归零并且不会被优化掉,即使编译器认为在此之后永远不会再访问内存.似乎是挥发性的完美候选者.但是我实际上遇到了一些与GCC合作的问题.这是一个示例函数:
void volatileZeroMemory(volatile void* ptr, unsigned long long size)
{
volatile unsigned char* bytePtr = (volatile unsigned char*)ptr;
while (size--)
{
*bytePtr++ = 0;
}
}
Run Code Online (Sandbox Code Playgroud)
很简单.但是,如果你调用它,GCC实际生成的代码会因编译器版本和你实际尝试为零的字节数而有很大不同.https://godbolt.org/g/cMaQm2
我测试过的任何其他编译器(clang,icc,vc)都会生成一个人们期望的存储,包括任何编译器版本和任何数组大小.所以在这一点上我想知道,这是一个(非常古老而严重的?)GCC编译器错误,或者是标准中volatile的定义,它不确定这实际上是符合行为的,这使得编写便携式基本上是不可能的" SecureZeroMemory"功能?
编辑:一些有趣的观察.
#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;
}
Run Code Online (Sandbox Code Playgroud)
callMeMaybe()的可能写入将使除6.1之外的所有GCC版本生成预期的存储.在内存栏中进行注释也会使GCC 6.1生成存储,尽管只能与callMeMaybe()的可能写入一起使用.
有人还建议刷新缓存.Microsoft 在"SecureZeroMemory"中根本不尝试刷新缓存.无论如何,缓存很可能会很快失效,所以这可能不是什么大问题.此外,如果另一个程序试图探测数据,或者它将被写入页面文件,它将始终是归零版本.
在独立功能中使用memset()也存在一些关于GCC 6.1的问题.Godbolt上的GCC 6.1编译器可能是一个破坏的构建,因为GCC 6.1似乎为某些人的独立函数生成了一个正常的循环(就像在godbolt上的5.3一样).(阅读zwol答案的评论.)
zwo*_*wol 81
海湾合作委员会的行为可能是一致的,即使不是这样,你也不应该依赖于volatile这样的情况下做你想做的事情.C委员会设计volatile用于内存映射硬件寄存器和异常控制流程中修改的变量(例如信号处理程序和setjmp). 这是唯一可靠的东西. 使用作为一般的"不优化这个"注释是不安全的.
特别是,关键点上的标准尚不清楚.(我已经将你的代码转换为C;这里C和C++之间不应该存在任何分歧.我还手动完成了在可疑优化之前发生的内联,以显示编译器在这一点"看到"的内容.)
extern void use_arr(void *, size_t);
void foo(void)
{
char arr[8];
use_arr(arr, sizeof arr);
for (volatile char *p = (volatile char *)arr;
p < (volatile char *)(arr + 8);
p++)
*p = 0;
}
Run Code Online (Sandbox Code Playgroud)
内存清除循环arr通过volatile限定的左值进行访问,但arr本身未声明volatile.因此,至少可以说C编译器推断循环所做的存储是"死",并完全删除循环.C原理中的文字暗示委员会意味着要求保留这些商店,但标准本身实际上并没有像我读到的那样提出要求.
有关标准执行或不需要的更多讨论,请参阅为什么volatile变量局部变量与volatile参数的优化不同,为什么优化器会从后者生成无操作循环?,通过易失性引用/指针访问声明的非易失性对象是否会在所述访问时赋予易失性规则?和GCC bug 71793.
有关委员会的想法的 更多信息volatile,请在C99理由中搜索"volatile"一词.John Regehr的论文" Volatiles is Miscompiled "详细说明volatile了生产编译器可能无法满足程序员的期望.LLVM团队的一系列文章" 每个C程序员应该知道的关于未定义行为的内容 "并未特别涉及,volatile但将帮助您了解现代C编译器不是 "便携式汇编程序"的方式和原因.
关于如何实现一个能够做你想做的功能的实际问题volatileZeroMemory:无论标准要求或意图要求什么,最好的做法是假设你不能使用volatile它.还有就是可以上依赖于工作的选择,因为它会打破太多其他的东西,如果它不工作:
extern void memory_optimization_fence(void *ptr, size_t size);
inline void
explicit_bzero(void *ptr, size_t size)
{
memset(ptr, 0, size);
memory_optimization_fence(ptr, size);
}
/* in a separate source file */
void memory_optimization_fence(void *unused1, size_t unused2) {}
Run Code Online (Sandbox Code Playgroud)
但是,您必须确保memory_optimization_fence在任何情况下都不会内联.它必须位于自己的源文件中,并且不得进行链接时优化.
还有其他选项,依赖于编译器扩展,在某些情况下可以使用,并且可以生成更严格的代码(其中一个出现在此答案的前一版本中),但没有一个是通用的.
(我建议调用该函数explicit_bzero,因为它在多个C库中以该名称可用.该名称至少有四个其他竞争者,但每个竞争者只被一个C库采用.)
你也应该知道,即使你可以让它工作,也可能还不够.特别要考虑
struct aes_expanded_key { __uint128_t rndk[16]; };
void encrypt(const char *key, const char *iv,
const char *in, char *out, size_t size)
{
aes_expanded_key ek;
expand_key(key, ek);
encrypt_with_ek(ek, iv, in, out, size);
explicit_bzero(&ek, sizeof ek);
}
Run Code Online (Sandbox Code Playgroud)
假设具有AES加速指令的硬件,如果expand_key并且encrypt_with_ek是内联的,编译器可能能够ek完全保留在向量寄存器文件中 - 直到调用explicit_bzero,这迫使它将敏感数据复制到堆栈上以便擦除它,并且,更糟糕的是,对于仍然位于向量寄存器中的键没有做一件坏事!
bam*_*s53 15
我需要一个函数(如WinAPI中的SecureZeroMemory)总是将内存归零并且不会被优化掉,
这就是标准功能memset_s的用途.
至于挥发性的这种行为是否符合,这有点难以说,而且据说挥发性长期以来一直受到错误的困扰.
一个问题是规范说"根据抽象机器的规则严格评估对易失性对象的访问".但这只是指'volatile对象',而不是通过添加了volatile的指针访问非易失性对象.显然,如果编译器可以告诉你实际上并没有访问volatile对象,那么就不需要将对象视为volatile.
| 归档时间: |
|
| 查看次数: |
4507 次 |
| 最近记录: |