arm 编译器 5 不完全遵守 volatile 限定符
arm compiler 5 do not fully respect volatile qualifier
考虑以下代码:
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()
的 return 值并存储在内存,因为 status
是易失性的,可以在 str
指令和 cmp
指令之间更改。
请尽量忽略 if
条件的动机,其目的是尝试检测对 CPU 的物理攻击,其中条件标志和寄存器可以通过物理设备从外部更改.
工具链ARM DS-5_v5.27.0 arm编译器:ARMCompiler5.06u5 (armcc)
目标是 ARM CortexM0+ CPU
所描述的行为不符合 C 标准,除非有非常规的解释。如果编译器应该在这方面符合要求,则应将其报告为错误。
管理 volatile
个对象的主要规则是这样的,来自 C11 6.7.3/7:
any expression referring to such an object shall be evaluated strictly
according to the rules of the abstract machine, as described in
5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine,
except as modified by the unknown factors mentioned previously.
它接着说
What constitutes an access to an object that has volatile-qualified
type is implementation-defined.
,适用于如何解释其他规则(例如 5.1.2.3 中的规则)。您的编译器用户指南讨论了 the details of volatile accesses,但似乎没有什么令人惊讶的地方。 5.1.2.3节本身主要讲排序规则;评估表达式的规则在其他地方(但仍然必须遵循关于访问 volatile 对象的规定)。
这里是抽象机行为的相关细节:
赋值操作有一个副作用,即在 status
标识的对象中存储一个值。该语句末尾有一个序列点,所以
- 在执行后续语句中出现的任何评估之前应用副作用,并且
- 因为
status
是易失性的,该行表示的赋值是 last 由程序在序列点之前执行的 status
写入。
接下来计算if
语句中的条件表达式,
- 首先评估子表达式
(status) == (SUCCESS_CONST)
,在任何其他子表达式之前。
-
status
的评估发生在 ==
操作的评估之前,并且
- 采用将该标识符转换为其标识的对象中存储的值的形式(左值转换,根据 paragraph 6.3.2.1/2)。
- 为了对当时存储在
status
中的值执行任何操作,必须首先读取该值。
标准不要求易失性对象驻留在可寻址存储中,因此原则上,您的易失性自动变量可以专门分配给寄存器。在那种情况下,只要使用该对象的机器指令直接从其寄存器读取其值或直接对其寄存器进行更新,就不需要单独的加载或存储来实现适当的易失性语义。但是,您的特定对象似乎不属于此类,因为生成的程序集中的存储指令似乎表明它确实与内存中的某个位置相关联。
此外,如果程序正确地为分配给寄存器的对象实现了可变语义,则该寄存器必须是 r0。我不熟悉这种汇编语言和运行代码的处理器的细节,但它看起来肯定不像 r0 是此类存储的可行位置。
在这种情况下,我同意 status
应该从内存中读回,如果它第二次出现在 需要评估条件表达式。这是抽象机的行为,符合规范的实现针对所有易失性访问表现出这种行为。那么,我的分析是,您的实施在这方面不符合要求,我倾向于将其报告为错误。
至于解决方法,我认为最好的办法是在汇编中编写重要的部分——如果您的实现支持内联汇编,或者在必要时作为一个完整的函数在汇编中实现。
考虑以下代码:
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()
的 return 值并存储在内存,因为 status
是易失性的,可以在 str
指令和 cmp
指令之间更改。
请尽量忽略 if
条件的动机,其目的是尝试检测对 CPU 的物理攻击,其中条件标志和寄存器可以通过物理设备从外部更改.
工具链ARM DS-5_v5.27.0 arm编译器:ARMCompiler5.06u5 (armcc)
目标是 ARM CortexM0+ CPU
所描述的行为不符合 C 标准,除非有非常规的解释。如果编译器应该在这方面符合要求,则应将其报告为错误。
管理 volatile
个对象的主要规则是这样的,来自 C11 6.7.3/7:
any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously.
它接着说
What constitutes an access to an object that has volatile-qualified type is implementation-defined.
,适用于如何解释其他规则(例如 5.1.2.3 中的规则)。您的编译器用户指南讨论了 the details of volatile accesses,但似乎没有什么令人惊讶的地方。 5.1.2.3节本身主要讲排序规则;评估表达式的规则在其他地方(但仍然必须遵循关于访问 volatile 对象的规定)。
这里是抽象机行为的相关细节:
赋值操作有一个副作用,即在
status
标识的对象中存储一个值。该语句末尾有一个序列点,所以- 在执行后续语句中出现的任何评估之前应用副作用,并且
- 因为
status
是易失性的,该行表示的赋值是 last 由程序在序列点之前执行的status
写入。
接下来计算
if
语句中的条件表达式,- 首先评估子表达式
(status) == (SUCCESS_CONST)
,在任何其他子表达式之前。 -
status
的评估发生在==
操作的评估之前,并且 - 采用将该标识符转换为其标识的对象中存储的值的形式(左值转换,根据 paragraph 6.3.2.1/2)。
- 为了对当时存储在
status
中的值执行任何操作,必须首先读取该值。
- 首先评估子表达式
标准不要求易失性对象驻留在可寻址存储中,因此原则上,您的易失性自动变量可以专门分配给寄存器。在那种情况下,只要使用该对象的机器指令直接从其寄存器读取其值或直接对其寄存器进行更新,就不需要单独的加载或存储来实现适当的易失性语义。但是,您的特定对象似乎不属于此类,因为生成的程序集中的存储指令似乎表明它确实与内存中的某个位置相关联。
此外,如果程序正确地为分配给寄存器的对象实现了可变语义,则该寄存器必须是 r0。我不熟悉这种汇编语言和运行代码的处理器的细节,但它看起来肯定不像 r0 是此类存储的可行位置。
在这种情况下,我同意 status
应该从内存中读回,如果它第二次出现在 需要评估条件表达式。这是抽象机的行为,符合规范的实现针对所有易失性访问表现出这种行为。那么,我的分析是,您的实施在这方面不符合要求,我倾向于将其报告为错误。
至于解决方法,我认为最好的办法是在汇编中编写重要的部分——如果您的实现支持内联汇编,或者在必要时作为一个完整的函数在汇编中实现。