我们正在使用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标签(即未进行内联汇编的标签),它们本身就是O.K。 -经验教训。
但是此外,当遇到未知(未定义)标签时,它不会发出警告或引发错误,而是在分支/跳转到此类标签时仅使用偏移量0来产生无穷循环。
我认为后者是一个灾难性的错误。这可能意味着(无任何警告)每当我在内联汇编代码中使用未定义的标签时(例如,由于输入错误),我的软件就会无休止地循环。
我能做些什么吗?
答案 0 :(得分:3)
如果您不告诉编译器执行可能不会在asm
语句的另一端进行,则编译器会认为是这种情况。
因此,您的两个示例都是不安全的,并且很幸运,第二个示例不会破坏任何内容,因为该函数太简单了。
我不确定您的代码是如何编译的; C本地标签通常不会以相同名称显示为asm标签。如果编译器生成的代码完全使用了它们,则gcc使用.L1
之类的名称,就像它为if()
和for
/ while
循环发明的分支目标一样。 @Kampi使用AtmelStudios 7.0.1931报告源的链接器错误。
也许您实际上是在查看一个未链接的.o
,其中分支目标只是要由链接器填充的占位符。 (并且对未定义符号的引用是等待发生的链接器错误)。 e0 8f 00 00
的编码确实适合:汇编器在编译器提供的.s
中找不到分支目标标签,因此它将其视为外部符号,并使用了具有更多位移字节的分支。显然,在AVR32上,相对的分支位移是相对于分支指令的 start 而言的,这与许多ISA相对于分支的末尾不同。 (即,在解码/执行指令时的PC已递增。)
因此,这可以解释您缺少链接器错误的原因(因为您从未运行过链接器),并看到了虚假的分支目标。 更新:已链接此 ,但将其链接到库中。因此库本身仍然有一个未解析的符号。
在编译器的asm输出中存在另一个内联asm语句中定义的目标,因此汇编程序可以找到它并可以使用简短的rjmp
。
(某些汇编程序通过要求extern foo
声明来帮助您捕获此类错误。GAS不会;它只是假定任何未定义的符号为extern
。GAS语法来自于传统的Unix汇编程序,旨在汇编编译器输出,在这种情况下,仅一次编译一个C函数(而不是整个文件优化)的古老编译器将不知道该函数的定义是出现在此.c
文件中还是出现在单独的{{1}中}文件。因此,这种语法可以在没有足够内存的情况下在机器上进行C的单次编译,以返回并添加.c
声明,以用于稍后在asm输出中未定义的符号。)
extern
使之安全 GNU C内联汇编确实具有用于从内联汇编语句(跳转到C标签)中跳出的语法。 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels。并参见示例:Labels in GCC inline assembly。 (使用x86指令,但是asm模板的内容与您使用asm goto
语法的方式无关。)
在没有用于条件代码/标志输出的GCC6语法的目标上,使用内联汇编中的条件分支跳转到asm goto
或陷入some_label: return true;
可能是一种便捷的方法。 (Using condition flags as GNU C inline asm outputs)
但是根据the commit message指出Linux内核放弃AVR32支持的原因,AVR32 gcc停留在gcc4.2。 return false;
仅出现在gcc4.5中。
除非AtmelStudio编译器(基于?)是最新的gcc,否则您就无法安全地做到这一点。