简体   繁体   English

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

[英]Jump out of inline assembly goes to the wrong target on AVR32

We are developing an application for the Atmel AVR32 / UC3C0512C using AtmelStudio 7.0.1645. 我们正在使用AtmelStudio 7.0.1645为Atmel AVR32 / UC3C0512C开发应用程序。 While doing some basic tests, I noticed something very weird. 在进行一些基本测试时,我注意到一些非常奇怪的东西。

Please consider the following code (I know that it is bad style and uncommon, but that's not the point here): 请考虑以下代码(我知道这是不好的样式,不常见,但这不是重点):

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);
}

When looking into the disassembly of that code (after it has been compiled / linked), I find the following: 当查看该代码的反汇编时(在编译/链接之后),我发现以下内容:

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

We notice that rjmp has become bral - perfectly acceptable, just another mnemonic for the same thing. 我们注意到rjmp已经变得bral -完全可以接受,只是同一件事的另一个助记符。

But when looking at the branch target in that line, we also notice that this will produce an endless loop , which it clearly shouldn't. 但是,当查看该行中的分支目标时,我们还注意到,这将产生一个无穷循环 ,这显然是不应该的。 It should branch to 786aa (which is the begin of the function return) instead of 786a6 . 它应该分支到786aa (这是函数返回的开始),而不是786a6

If I change the code so that it reads 如果我更改代码以使其读取

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);
}

it works as expected, ie the disassembly now reads 它按预期工作,即现在反汇编显示为

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

We notice that the branch target now is correct. 我们注意到分支目标现在是正确的。

So the inline assembler obviously does not know about C labels (ie labels which are not in inline assembly), which per se would be OK - lesson learned. 因此,内联汇编程序显然不了解C标签(即,不在内联汇编中的标签),这本身就可以-经验教训。

But in addition it does not warn or throw errors when it encounters an unknown (undefined) label, but instead produces endless loops by just using an offset of 0 when branching / jumping to such labels . 但是此外,当遇到未知(未定义)的标签时,它不会发出警告或引发错误,而是在分支/跳转到此类标签​​时,仅使用偏移量0会产生无限循环

I am considering the latter a catastrophic bug. 我认为后者是一个灾难性的错误。 It probably means that (without any warning) I'll get an endless loop in my software whenever I use an undefined label in inline assembly code (eg because of a typo). 这可能意味着(无任何警告)每当我在内联汇编代码中使用未定义的标签时(例如由于输入错误),我的软件中都会出现无限循环。

Is there anything I can do about it? 有什么我可以做的吗?

If you don't tell the compiler that execution might not come out the other side of your asm statement, the compiler assumes that to be the case. 如果您不告诉编译器执行可能不会出现在asm语句的另一端,则编译器会认为是这种情况。

So both your examples are unsafe, and it's just luck that the 2nd one doesn't break anything because the function is too simple. 因此,您的两个示例都不安全,很幸运第二个示例不会破坏任何内容,因为该函数太简单了。


I'm not sure how your code compiled at all; 我不确定您的代码是如何编译的。 C local labels don't normally appear as asm labels with the same name. C本地标签通常不会以相同名称显示为asm标签。 If they're used at all by compiler-generated code, gcc uses names like .L1 the same as for branch targets it invents for if() and for / while loops. 如果他们使用的都是由编译器生成的代码,GCC使用的名称,如.L1作为分支目标同它发明了if()for / while循环。 @Kampi reports a linker error for your source with AtmelStudios 7.0.1931. @Kampi使用AtmelStudios 7.0.1931报告源的链接器错误。

Perhaps you're actually looking at a non-linked .o , where the branch target was just a placeholder to be filled in by the linker . 也许您实际上是在查看一个未链接的.o ,分支目标只是一个由链接器填充的占位符 (And the reference to the undefined symbol is a linker error waiting to happen). (并且对未定义符号的引用是等待发生的链接器错误)。 The encoding of e0 8f 00 00 certainly fits that: the assembler didn't find the branch target label in the .s the compiler gave it, so it treated it as an external symbol and used a branch with more displacement bytes. e0 8f 00 00的编码肯定适合:汇编器在编译器提供的.s找不到分支目标标签,因此它将其视为外部符号,并使用了具有更多位移字节的分支。 Apparently on AVR32, relative branch displacements are relative to the start of the branch instruction, unlike many ISAs where it's relative to the end of the branch. 显然,在AVR32上,相对分支位移是相对于分支指令的开始而言的,这与许多ISA相对于分支结束而言的情况不同。 (ie PC while an instruction is decoded/executed has already been incremented.) (即,在对指令进行解码/执行时,PC已递增。)

So that would explain your lack of linker errors (because you never ran the linker), and seeing a bogus branch target. 这样就可以说明您缺少链接器错误(因为您从未运行过链接器),并且看到了虚假的分支目标。 Update: this was being linked, but into a library. 更新:这被链接的,但到库中。 So the library itself still had an unresolved symbol. 因此,库本身仍具有未解析的符号。

The target defined in another inline asm statement is present in the compiler's asm output, so the assembler finds it and can use a short rjmp . 在另一个内联汇编语句定义的目标存在于编译器的输出汇编,所以汇编程序找到它,并可以使用短rjmp

(Some assemblers help you catch mistakes like this by requiring extern foo declarations. GAS does not; it simply assumes that any undefined symbol is extern . GAS syntax comes from traditional Unix assemblers that are designed to assemble compiler output, where ancient compilers that only compiled one C function at a time (not whole-file optimization) wouldn't know whether a definition for a function would appear in this .c file or a separate .c file. So this syntax enables one-pass compiling of C on machines without enough memory to go back and add extern declarations for symbols that aren't defined later in the asm output.) (某些汇编程序通过要求extern foo声明来帮助您捕获此类错误。GAS不会;它只是假设任何未定义的符号都是extern语法来自传统的Unix汇编程序,该汇编程序旨在汇编编译器输出,而古老的编译器仅编译一次只有一个C函数(不是整个文件的优化)不会知道该函数的定义是出现在此.c文件中还是单独的.c文件中,因此该语法可以在不使用计算机的情况下对C进行一次编译足够的内存以返回并为稍后在asm输出中未定义的符号添加extern声明。)


GNU C asm goto makes this safe GNU C asm goto使此安全

GNU C inline asm does have syntax for jumping out of inline-asm statements (to C labels). GNU C内联汇编确实具有用于从内联汇编语句(跳转到C标签)中跳出的语法。 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels . https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels And see an example on SO: Labels in GCC inline assembly . 并查看SO: GCC内联汇编中的标签的示例。 (Using x86 instructions, but the contents of the asm template are irrelevant to how you use the asm goto syntax.) (使用x86指令,但是asm模板的内容与您使用asm goto语法的方式无关。)

On targets without GCC6 syntax for condition-code / flag outputs, it can be a handy way to use a conditional branch in inline asm to jump to a some_label: return true; 在没有针对条件代码/标志输出的GCC6语法的目标上,使用内联asm中的条件分支跳转到some_label: return true;可能是一种便捷的方法some_label: return true; or fall through to a return false; return false; . ( Using condition flags as GNU C inline asm outputs ) 使用条件标志作为GNU C内联asm输出

But according to the commit message stating reasons for the Linux kernel dropping AVR32 support, AVR32 gcc is stuck at gcc4.2. 但是,根据提交消息(说明Linux内核放弃AVR32支持的原因),AVR32 gcc停留在gcc4.2。 asm goto only appeared in gcc4.5. asm goto仅出现在gcc4.5中。

Unless the AtmelStudio compiler is (based on?) a more recent gcc, you simply can't safe do this safely. 除非AtmelStudio编译器(基于吗?)是最新的gcc,否则您就无法安全地做到这一点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM