草庐IT

c++ - "volatile"的定义是不稳定的,还是 GCC 有一些标准合规性问题?

coder 2023-04-26 原文

我需要一个函数(如 WinAPI 中的 SecureZeroMemory)始终将内存归零并且不会被优化掉,即使编译器认为此后再也不会访问内存。似乎是 volatile 的完美候选者。但我实际上在让这个与 GCC 一起工作时遇到了一些问题。这是一个示例函数:

void volatileZeroMemory(volatile void* ptr, unsigned long long size)
{
    volatile unsigned char* bytePtr = (volatile unsigned char*)ptr;

    while (size--)
    {
        *bytePtr++ = 0;
    }
}

足够简单。但是如果你调用它,GCC 实际生成的代码会随着编译器版本和你实际尝试归零的字节数而变化很大。 https://godbolt.org/g/cMaQm2

  • GCC 4.4.7 和 4.5.3 永远不会忽略 volatile。
  • GCC 4.6.4 和 4.7.3 忽略数组大小为 1、2 和 4 的 volatile。
  • GCC 4.8.1 到 4.9.2 忽略数组大小 1 和 2 的 volatile。
  • GCC 5.1 到 5.3 忽略数组大小为 1、2、4、8 的 volatile。
  • GCC 6.1 对任何数组大小都会忽略它(为一致性加分)。

我测试过的任何其他编译器(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;
}

The possible write from callMeMaybe() will make all GCC versions except 6.1 generate the expected stores.在内存围栏中进行注释也会使 GCC 6.1 生成存储,尽管仅结合可能来自 callMeMaybe() 的写入。

有人还建议刷新缓存。 Microsoft does not try to flush the cache at all in "SecureZeroMemory".无论如何,缓存很可能很快就会失效,所以这可能没什么大不了的。此外,如果另一个程序正在尝试探测数据,或者将其写入页面文件,则它始终是归零版本。

还有一些关于 GCC 6.1 在独立函数中使用 memset() 的问题。 Godbolt 上的 GCC 6.1 编译器可能会损坏构建,因为 GCC 6.1 似乎为某些人的独立功能生成了一个正常的循环(就像 Godbolt 上的 5.3 一样)。 (阅读zwol答案的评论。)

最佳答案

GCC 的行为可能 符合要求,即使不符合要求,您也不应该依赖 volatile 在此类情况下做您想做的事情。 C 委员会为内存映射的硬件寄存器和在异常控制流期间修改的变量(例如信号处理程序和 setjmp)设计了 volatile这些是它唯一可靠的东西。用作一般的“不要优化这个”注释是不安全的。

特别是标准在一个关键点上不清楚。 (我已将您的代码转换为 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;
}

内存清除循环通过一个 volatile 限定的左值访问 arr,但是 arr 本身是 not 声明为 volatile。因此,至少可以说允许 C 编译器推断循环所做的存储是“死的”,并完全删除循环。 C Rationale 中有文字暗示委员会打算要求保留这些商店,但正如我所读到的那样,标准本身实际上并没有提出这个要求。

有关标准要求或不要求的更多讨论,请参阅 Why is a volatile local variable optimised differently from a volatile argument, and why does the optimiser generate a no-op loop from the latter? , Does accessing a declared non-volatile object through a volatile reference/pointer confer volatile rules upon said accesses? , 和 GCC bug 71793 .

有关委员会想法 volatile 的更多信息,请搜索C99 Rationale对于“ volatile ”这个词。 John Regehr 的论文“Volatiles are Miscompiled”详细说明了生产编译器可能无法满足程序员对 volatile 的期望。 LLVM 团队的系列文章“What Every C Programmer Should Know About Undefined Behavior”并未具体涉及 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) {}

但是,您必须绝对确保 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);
}

假设硬件带有 AES 加速指令,如果 expand_keyencrypt_with_ek 是内联的,编译器可能能够将 ek 完全保留在 vector 中注册文件——直到调用 explicit_bzero,这迫使它将敏感数据复制到堆栈上只是为了删除它,更糟糕的是,它不会做任何事关于仍然位于 vector 寄存器中的键的事情!

关于c++ - "volatile"的定义是不稳定的,还是 GCC 有一些标准合规性问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38230856/

有关c++ - "volatile"的定义是不稳定的,还是 GCC 有一些标准合规性问题?的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  3. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  4. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  5. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  6. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  7. ruby-on-rails - 无法在centos上安装therubyracer(V8和GCC出错) - 2

    我正在尝试在我的centos服务器上安装therubyracer,但遇到了麻烦。$geminstalltherubyracerBuildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingtherubyracer:ERROR:Failedtobuildgemnativeextension./usr/local/rvm/rubies/ruby-1.9.3-p125/bin/rubyextconf.rbcheckingformain()in-lpthread...yescheckingforv8.h...no***e

  8. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  9. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  10. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

随机推荐