跳出内联汇编会在AVR32上到达错误的目标

时间:2019-06-13 11:50:54

标签: c gcc inline-assembly goto 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标签(即未进行内联汇编的标签),它们本身就是O.K。 -经验教训。

但是此外,当遇到未知(未定义)标签时,它不会发出警告或引发错误,而是在分支/跳转到此类标签​​时仅使用偏移量0来产生无穷循环。

我认为后者是一个灾难性的错误。这可能意味着(无任何警告)每当我在内联汇编代码中使用未定义的标签时(例如,由于输入错误),我的软件就会无休止地循环。

我能做些什么吗?

1 个答案:

答案 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输出中未定义的符号。)


GNU C 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,否则您就无法安全地做到这一点。