c - 跳出内联汇编在 AVR32 上转到了错误的目标

标签 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 标签(即不在内联汇编中的标签),这本身就可以了。 - 经验教训。

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

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

我能做些什么吗?

最佳答案

如果您不告诉编译器执行可能不会从您的 asm 语句的另一端出来,编译器会假设是这种情况。

所以你的两个例子都是不安全的,幸运的是第二个没有破坏任何东西,因为函数太简单了。


我完全不确定您的代码是如何编译的; C 本地标签通常不会显示为具有相同名称的 asm 标签。如果编译器生成的代码完全使用了它们,gcc 使用类似 .L1 的名称,这与它为 if()for 发明的分支目标相同/while 循环。 @Kampi 使用 AtmelStudios 7.0.1931 报告您的源链接器错误。

也许您实际上正在查看一个未链接的 .o,其中分支目标只是一个由链接器填充的占位符。 (并且对 undefined symbol 的引用是等待发生的链接器错误)。 e0 8f 00 00 的编码当然符合:汇编器没有在编译器给它的 .s 中找到分支目标标签,所以它把它当作一个外部符号并使用了具有更多位移字节的分支。显然在 AVR32 上,相对分支位移是相对于分支指令的 start 的,这与许多 ISA 不同,它是相对于分支末尾的。 (即当指令被解码/执行时 PC 已经递增。)

所以这可以解释您没有链接器错误(因为您从未运行过链接器),并且看到了一个伪造的分支目标。 更新:这正在被链接,但是链接到一个库中。所以库本身仍然有一个未解析的符号。

另一个内联 asm 语句中定义的目标 出现在编译器的 asm 输出中,因此汇编程序找到它并可以使用短的 rjmp

(一些汇编程序通过要求 extern foo 声明来帮助您捕获此类错误。GAS 没有;它只是假设任何 undefined symbol 都是 extern。GAS 语法来自旨在汇编编译器输出的传统 Unix 汇编器,其中一次只编译一个 C 函数(不是整个文件优化)的古代编译器不知道函数的定义是否会出现在此 .c 文件或单独的 .c 文件。因此,此语法允许在没有足够内存的机器上一次性编译 C 以返回并为符号添加 extern 声明稍后在 asm 输出中没有定义。)


GNU C asm goto 使它安全

GNU C 内联汇编确实有跳出内联汇编语句(到 C 标签)的语法。 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels .并查看有关 SO 的示例:Labels in GCC inline assembly . (使用 x86 指令,但 asm 模板的内容与您如何使用 asm goto 语法无关。)

在没有用于条件代码/标志输出的 GCC6 语法的目标上,使用内联 asm 中的条件分支跳转到 some_label: return true; 或跳转到返回 false;。 ( Using condition flags as GNU C inline asm outputs )

但根据the commit message说明 Linux 内核放弃 AVR32 支持的原因,AVR32 gcc 停留在 gcc4.2。 asm goto 只出现在 gcc4.5 中。

除非 AtmelStudio 编译器(基于?)更新的 gcc,否则您根本无法安全地执行此操作。

关于c - 跳出内联汇编在 AVR32 上转到了错误的目标,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56579861/

相关文章:

c - 如果你 malloc 一个 struct* 它会创建局部变量吗?

c - clang 和 gcc 之间的行为差​​异?

c - ARM 程序集 : can’t find a register in class ‘GENERAL_REGS’ while reloading ‘asm’

c - 如何在文件末尾自动追加换行符

c - 不正确的寄存器 0x​​104567910 与 '%rbx' 后缀一起使用

GCC 内联汇编错误 : Error: junk `(%esp)' after expression

c++ - printf 没有打印正确的值

C删除数组元素,程序停止

无法从 arduino 发送 RS-232 命令

linux - 如何在 Linux 上使用 gcc 程序集在 x86-64 中设置控制寄存器 0 (cr0) 位