考虑以下代码:
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()
分配了一个返回值并以{ {1}}易失,可以在status
指令和str
指令之间进行更改。
请尝试忽略cmp
条件的动机,其目的是尝试检测对CPU的物理攻击,在这种情况下,可以通过物理设备在外部更改条件标志和寄存器。
工具链 ARM DS-5_v5.27.0 arm编译器: ARMCompiler5.06u5 (armcc)
目标是ARM CortexM0 + CPU
答案 0 :(得分:4)
C11 6.7.3/7中管理volatile
对象的主要规则是:
任何引用此类对象的表达式均应严格求值 根据抽象机的规则,如 5.1.2.3。此外,在每个序列点上,对象中最后存储的值应与抽象机规定的值一致, 除非经过前面提到的未知因素的修正。
接着说
什么构成对具有volatile限定的对象的访问 类型是实现定义的。
,适用于如何解释其他规则(例如5.1.2.3中的规则)。编译器的《用户指南》讨论了the details of volatile accesses,但似乎没有什么令人惊讶的地方。第5.1.2.3节本身主要讨论排序规则;评估表达式的规则在别处(但在访问易失性对象时仍必须遵循该规则)。
以下是抽象机行为的相关详细信息:
赋值操作具有将值存储在status
所标识的对象中的副作用。该语句的末尾有一个序列点,所以
status
是易失性的,因此该行表示的赋值是程序在序列点之前对status
进行的最后写操作。接下来用{p>评估if
语句中的条件表达式
(status) == (SUCCESS_CONST)
,然后再评估其他子表达式。status
的评估发生在对==
操作的评估之前,并且status
中的值进行任何操作,必须首先读取该值。 标准不需要要求将易失性对象驻留在可寻址存储中,因此,原则上,您的易失性自动变量可以专门分配给寄存器。在那种情况下,只要使用该对象的机器指令直接从其寄存器读取其值或直接对其寄存器进行更新,就不需要单独的加载或存储来实现适当的易变语义。您的特定对象似乎不属于此类,因为生成的程序集中的存储指令似乎表明它确实与内存中的位置关联。
此外,如果程序为分配给寄存器的对象正确实现了可变语义,则该寄存器将必须为r0。我不熟悉这种汇编语言的细节以及运行代码的处理器,但是r0显然不是这种存储的可行之地。
在这种情况下,我同意status
应该从内存中读回,并且如果条件表达式中第二次出现需要再次从内存中读回{em> 被评估。这是抽象机的行为,对于所有易失性访问,符合标准的实现都具有这种表现。那么,我的分析是,您的实现在这方面是不合格的,我倾向于将其报告为错误。
关于变通方法,我认为最好的办法是在汇编中编写重要的部分-如果实现支持,则直接插入汇编,或者在必要时作为在汇编中实现的完整功能。
答案 1 :(得分:1)
除非有非常规的解释,否则所描述的行为不符合C标准。如果认为编译器在这方面符合要求,则应将其报告为错误。