【问题标题】:Jump out of inline assembly goes to the wrong target on AVR32跳出内联汇编到 AVR32 上的错误目标
【发布时间】:2019-10-28 00:14:59
【问题描述】:

我们正在使用 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 来产生无限循环

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

我能做些什么吗?

【问题讨论】:

  • (a) 查看编译器生成的程序集(与 GCC 的 -S 开关一样)而不是反汇编可能会有所帮助。 (b) 我不确定是否支持跳出内联汇编。一方面,考虑您列出的被内联汇编修改的寄存器。编译器可能会在内联程序集周围保存和恢复这些寄存器,但跳转会跳过恢复。
  • 编译器可能会保存和恢复内联程序集周围的那些寄存器,但跳转会跳过恢复。非常感谢,我还没有考虑过(目前只做基本测试,稍后再做更复杂的测试)。但这不是编译器发出警告的另一个原因吗?奇怪的是,-S 似乎被 AVR32 工具链默默地忽略了,至少当一个库是目标时。几个月前,我已经花了一天时间尝试让 avr32-gcc 生成一个列表文件(.lss),但没有成功,所以我现在使用 avr32-objdump。
  • 你还做了什么吗?我无法通过内联汇编程序跳转到已编译的外部标签来获取我的示例。编译器会抛出对该标签 asm volatile("rjmp Label \n" :::"cc", "memory"); Label: return 0; 的未定义引用的错误。这只适用于两个内联汇编程序部分。
  • @EricPostpischil:支持跳出内联汇编,但您必须告诉编译器asm goto 的可能性。 (并且您可以使用 C 本地标签)。 gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels
  • 但是根据提交消息说明 Linux 放弃 AVR32 支持的原因 (github.com/torvalds/linux/commit/…) AVR32 gcc 卡在 gcc4.2,而 asm goto 只出现在 gcc4.5。除非 AtmelStudio 编译器更新,否则您根本无法安全地执行此操作。

标签: c gcc inline-assembly goto avr32


【解决方案1】:

如果您不告诉编译器执行可能不会出现在您的 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 文件或单独的 .c 文件中。所以这种语法使一个- 在没有足够内存的机器上通过编译 C 以返回并为稍后在 asm 输出中未定义的符号添加 extern 声明。)


GNU C asm 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 中的条件分支跳转到 some_label: return true; 或下降到 return 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,否则您根本无法安全地执行此操作。

【讨论】:

  • 好的,非常感谢,已接受并 +1。 ...所以它将其视为外部符号并使用具有更多位移字节的分支... 最后我明白了这一点。目标是一个库,因此在将库链接到可执行文件之前不会发生错误。 我仍然担心的是,我见过一些汇编程序,当标签是外部标签时,您必须明确声明(某种程度上就像在 C 中一样)。我假设它与 GCC 相同,但内联程序集和独立程序集之间可能存在差异。
  • @Binarus:不一定是库;它可以在另一个 C 源文件中定义,例如 gcc foo.c bar.c。是的,一些汇编程序需要extern foo 声明。 GAS 没有:无法识别的符号被假定为外部符号。
  • 它可以在另一个 C 源文件中定义,例如 gcc foo.c bar.c 是的,这很清楚,这就是我的真正意思:会发生错误不是在将 my 库链接到另一个可执行文件之前(除非该可执行文件的某些其他部分,例如源文件或另一个库,恰好定义了“缺失”标签”。* GAS 不会:无法识别的符号是假定为 extern。* 这是关键点。现在你让我在最后一个小时内学到的东西比我在上个月学到的东西...再次感谢!
  • @Binarus:哦!我误读了你在说什么。是的,这更有意义。对,你的图书馆有一个未解决的符号。 (我用关于 GAS 语法的解释更新了我的答案;这实际上是一个有趣的点,为什么 GAS 语法不需要这样做。该语法是为编译器输出而设计的,从机器很小的时候开始,一次编译一个函数是一回事.)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-19
  • 2010-10-23
  • 1970-01-01
  • 2017-05-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多