c - 跳出内联汇编到 AVR32 上的错误目标
问题描述
我们正在使用 AtmelStudio 7.0.1645 为 Atmel AVR32 / UC3C0512C 开发应用程序。在进行一些基本测试时,我注意到一些非常奇怪的事情。
请考虑以下代码(我知道它的风格不好且不常见,但这不是重点):
float GetAtan2f(float p_f_y,
float p_f_x)
{
unsigned int l_ui_x,
l_ui_y,
l_ui_Sign_x,
l_ui_Sign_y,
l_ui_Result;
float l_f_Add,
l_f_Result;
asm volatile(
"RJMP GETATAN2_EXIT \n"
:
: /* 0 */ "m" (p_f_y),
/* 1 */ "m" (p_f_x)
: "cc", "memory", "r0", "r1", "r2", "r3", "r5"
);
GETATAN2_EXIT:
return (l_f_Result);
}
在查看该代码的反汇编时(在编译/链接之后),我发现以下内容:
Disassembly of section .text.GetAtan2f:
00078696 <GetAtan2f>:
78696: eb cd 40 af pushm r0-r3,r5,r7,lr
7869a: 1a 97 mov r7,sp
7869c: 20 9d sub sp,36
7869e: ef 4c ff e0 st.w r7[-32],r12
786a2: ef 4b ff dc st.w r7[-36],r11
786a6: e0 8f 00 00 bral 786a6 <GetAtan2f+0x10>
786aa: ee f8 ff fc ld.w r8,r7[-4]
786ae: 10 9c mov r12,r8
786b0: 2f 7d sub sp,-36
786b2: e3 cd 80 af ldm sp++,r0-r3,r5,r7,pc
我们注意到这rjmp
已经成为bral
- 完全可以接受的,只是同一件事的另一个助记符。
但是当查看该行中的分支目标时,我们还注意到这将产生一个无限循环,它显然不应该这样做。它应该分支到786aa
(这是函数返回的开始)而不是786a6
.
如果我更改代码使其显示为
float GetAtan2f(float p_f_y,
float p_f_x)
{
unsigned int l_ui_x,
l_ui_y,
l_ui_Sign_x,
l_ui_Sign_y,
l_ui_Result;
float l_f_Add,
l_f_Result;
asm volatile(
"RJMP GETATAN2_EXIT \n"
:
: /* 0 */ "m" (p_f_y),
/* 1 */ "m" (p_f_x)
: "cc", "memory", "r0", "r1", "r2", "r3", "r5"
);
asm volatile(
"GETATAN2_EXIT: \n"
:
:
: "cc", "memory"
);
return (l_f_Result);
}
它按预期工作,即反汇编现在读取
Disassembly of section .text.GetAtan2f:
00078696 <GETATAN2_EXIT-0x12>:
78696: eb cd 40 af pushm r0-r3,r5,r7,lr
7869a: 1a 97 mov r7,sp
7869c: 20 9d sub sp,36
7869e: ef 4c ff e0 st.w r7[-32],r12
786a2: ef 4b ff dc st.w r7[-36],r11
786a6: c0 18 rjmp 786a8 <GETATAN2_EXIT>
000786a8 <GETATAN2_EXIT>:
786a8: ee f8 ff fc ld.w r8,r7[-4]
786ac: 10 9c mov r12,r8
786ae: 2f 7d sub sp,-36
786b0: e3 cd 80 af ldm sp++,r0-r3,r5,r7,pc
我们注意到现在的分支目标是正确的。
所以内联汇编器显然不知道 C 标签(即不在内联汇编中的标签),这本身就可以 - 经验教训。
但此外,它在遇到未知(未定义)标签时不会发出警告或抛出错误,而是在分支/跳转到此类标签时仅使用偏移量 0 来产生无限循环。
我认为后者是一个灾难性的错误。这可能意味着(没有任何警告)每当我在内联汇编代码中使用未定义的标签时(例如因为拼写错误),我的软件中都会出现无限循环。
我能做些什么吗?
解决方案
如果您不告诉编译器执行可能不会从您的asm
语句的另一端出来,编译器会假定是这种情况。
所以你的两个例子都是不安全的,幸运的是第二个没有破坏任何东西,因为函数太简单了。
我不确定您的代码是如何编译的;C 本地标签通常不会显示为具有相同名称的 asm 标签。如果它们完全被编译器生成的代码使用,gcc 使用的名称与它发明的 for和/循环.L1
的分支目标相同。@Kampi 使用 AtmelStudios 7.0.1931 报告您的源的链接器错误。if()
for
while
也许您实际上正在查看 non-linked .o
,其中分支目标只是要由链接器填充的占位符。(并且对未定义符号的引用是等待发生的链接器错误)。的编码e0 8f 00 00
当然符合这一点:汇编器在编译器给它的时候没有找到分支目标标签.s
,所以它把它当作一个外部符号,并使用了一个具有更多位移字节的分支。显然在 AVR32 上,相对分支位移是相对于分支指令的开始的,不像许多 ISA 是相对于分支的结束。(即,当指令被解码/执行时,PC 已经递增。)
所以这可以解释你没有链接器错误(因为你从未运行过链接器),并且看到了一个虚假的分支目标。 更新:这已被链接,但已链接到库中。所以图书馆本身仍然有一个未解决的符号。
另一个内联 asm 语句中定义的目标存在于编译器的 asm 输出中,因此汇编器找到它并可以使用 short rjmp
。
(一些汇编程序通过要求声明来帮助您捕获此类错误extern foo
。GAS 不会;它只是假设任何未定义的符号是extern
.GAS 语法来自传统的 Unix 汇编程序,这些汇编程序旨在汇编编译器输出,其中古老的编译器只编译一个 C 函数一次(不是整个文件优化)不知道函数的定义是否会出现在这个.c
文件或单独的.c
文件中。所以这种语法可以在没有足够内存的机器上一次性编译 C 并返回并添加extern
后面未在 asm 输出中定义的符号的声明。)
GNU Casm goto
使这个安全
GNU C inline asm 确实具有跳出 inline-asm 语句(到 C 标签)的语法。 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels。并查看 SO: Labels in GCC inline assembly的示例。(使用 x86 指令,但 asm 模板的内容与您如何使用asm goto
语法无关。)
在没有用于条件代码/标志输出的 GCC6 语法的目标上,使用内联 asm 中的条件分支跳转到 asome_label: return true;
或下降到 a可能是一种方便的方法return false;
。(使用条件标志作为 GNU C 内联 asm 输出)
但是根据提交消息说明 Linux 内核放弃 AVR32 支持的原因,AVR32 gcc 卡在 gcc4.2 上。 asm goto
只出现在 gcc4.5 中。
除非 AtmelStudio 编译器是(基于?)更新的 gcc,否则您根本无法安全地安全地执行此操作。
推荐阅读
- conv-neural-network - 用于输出的 Keras 2D 密集层
- javascript - getJSON 返回 200 但错误
- java - 从其他应用程序调用私有实例变量,其 getter setter 不可用
- wordpress - 在订单表格内输入时强制自动替换字母
- ruby-on-rails - 使用 ActiveStorage 将系统生成的文件上传到 S3?
- python-3.x - TensorFlow:如何打印 GradientDescentOptimizer 中使用的错误值?
- javascript - updatePixels() 实际上并未更新像素
- azure-language-understanding - 短语列表是否已从 LUIS 中删除?
- excel - VBA跨多个网页抓取
- windows-runtime - C++/WinRT 中 Platform::IBoxArray 的等价物