请考虑以下代码:
volatile int status;
status = process_package_header(&pack_header, PACK_INFO_CONST);
if ((((status) == (SUCCESS_CONST)) ? ((random_delay() && ((SUCCESS_CONST) == (status))) ? 0 : side_channel_sttack_detected()) : 1))
{
...
}
生成此机器代码(使用工具链的objdump生成):
60: f7ff fffe bl 0 <process_package_header>
64: 9000 str r0, [sp, #0] /* <- storing to memory as status is volatile */
66: 42a0 cmp r0, r4 /* <- where is the load before compare? status is volatile, it could have change between the last store instruction (above line) and now */
68: d164 bne.n 134 <func+0x134>
6a: f7ff fffe bl 0 <random_delay>
现在,由于status
是易失性的,所以当达到if
语句时应该从内存中读取它。我希望在将它(cmp
)与SUCCESS_CONST
进行比较之前会看到一些加载命令,无论它是从函数process_package_header()
分配的返回值并存储在内存中的事实,因为status
是易变的并且可以在str
指令和cmp
之间进行更改指令。
请尝试忽略if
条件的动机,其目的是尝试检测CPU上的物理攻击,其中条件标志和寄存器可以通过物理设备在外部进行更改。
工具链ARM DS-5_v5.27.0 arm编译器:ARMCompiler5.06u5(armcc)
目标是ARM Cortex M0 + CPU
管理volatile
对象的主要规则来自C11 6.7.3/7:
任何涉及这种对象的表达都应严格按照抽象机的规则进行评估,如5.1.2.3所述。此外,在每个序列点,最后存储在对象中的值应与抽象机器规定的值一致,除非由前面提到的未知因素修改。
它继续说
什么构成对具有volatile限定类型的对象的访问是实现定义的。
,适用于如何解释其他规则(例如在5.1.2.3中)。您的编译器的用户指南讨论了the details of volatile accesses,但似乎没有什么令人惊讶的。第5.1.2.3节本身主要讨论排序规则;用于计算表达式的规则在其他地方(但必须遵循关于访问易失性对象的规则)。
以下是抽象机器行为的相关细节:
status
标识的对象中存储值的副作用。在该语句的末尾有一个序列点,所以
在执行后续陈述中出现的任何评估之前应用副作用,并且
因为status
是易失性的,所以该行表示的赋值是程序在序列点之前对status
执行的最后一次写入。if
语句中的条件表达式
在任何其他子表达式之前,首先评估子表达式(status) == (SUCCESS_CONST)
。
status
的评估发生在评估==
手术之前,并且
采取将该标识符转换为其标识的对象中存储的值的形式(左值转换,每paragraph 6.3.2.1/2)。
为了对当时存储在status
中的值执行任何操作,必须首先读取该值。该标准不要求volatile对象驻留在可寻址存储中,因此原则上,您的volatile自动变量可以专门分配给寄存器。在那种情况下,只要使用该对象的机器指令直接从其寄存器读取其值或直接对其寄存器进行更新,就不需要单独的加载或存储来实现适当的易失性语义。但是,您的特定对象似乎不属于此类别,因为生成的程序集中的存储指令似乎表明它确实与内存中的位置相关联。
此外,如果程序正确地为分配给寄存器的对象实现了易失性语义,则该寄存器必须是r0。我不熟悉这种汇编语言的细节和代码运行的处理器,但它看起来并不像r0是这种存储的可行位置。
在这种情况下,我同意status
应该已经从内存中读回来,如果需要对条件表达式中的第二次出现进行评估,它应该再次从内存中读回。这是抽象机器的行为,它符合实现对所有易失性访问的展示。那么,我的分析是你的实现在这方面是不符合的,我倾向于报告这是一个错误。
至于解决方法,我认为最好的方法是在汇编中编写重要的位 - 如果您的实现支持,则在内联汇编,或者如果需要,可以在汇编中实现完整的函数。
除非传统解释,否则所描述的行为不符合C标准。如果编译器在这方面应该符合,那么应该将其报告为错误。