0

We are developing an application for the Atmel AVR32 / UC3C0512C using AtmelStudio 7.0.1645. 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.

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.

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, i.e. 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 (i.e. labels which are not in inline assembly), which per se would be O.K. - lesson learned.

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.

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 (e.g. because of a typo).

Is there anything I can do about it?

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
Binarus
  • 4,005
  • 3
  • 25
  • 41
  • 2
    (a) Looking at the assembly generated by the compiler (as with GCC’s `-S` switch) rather than the disassembly may be helpful. (b) I am not sure jumping out of inline assembly is supported. For one thing, consider the registers you listed as modified by the inline assembly. The compiler might save and restore those registers around the inline assembly, but a jump would skip the restore. – Eric Postpischil Jun 13 '19 at 11:57
  • *The compiler might save and restore those registers around the inline assembly, but a jump would skip the restore.* Thank you very much, I haven't thought about that yet (currently doing only the basic tests and working towards the more complex ones later). But wouldn't that be one more reason for the compiler to give a warning? And weirdly enough, `-S` seems to be silently ignored by the AVR32 toolchain, at least when a library is the target. Some months ago, I already have put a day in trying to make avr32-gcc produce a listing file (.lss), but without success, so I now use avr32-objdump. – Binarus Jun 13 '19 at 12:15
  • Did you do anything else? I can´t get my example with an inline assembler jump to an external label compiled. The compiler throws an error with an undefined reference to that label ```asm volatile("rjmp Label \n" :::"cc", "memory"); Label: return 0;``` This only works with two inline assembler parts. – Kampi Jun 13 '19 at 12:26
  • Do you perhaps have a `-o OutputFileName` switch along with `-S`? If so, the compiler may be writing the assembly output to that file name. E.g., it is not writing an actual object or library module to the output file; it is writing the assembly. – Eric Postpischil Jun 13 '19 at 12:26
  • Offtopic: @Binarus maybe you should update your version? I have the version 7.0.1931 and I get a .lss file (same device as you use). – Kampi Jun 13 '19 at 12:29
  • 5
    @EricPostpischil: jumping out of inline asm is supported but you have to tell the compiler about the possibility with `asm goto`. (And you can use a C local label). https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels – Peter Cordes Jun 13 '19 at 12:47
  • 1
    But according to the commit message stating reasons for Linux dropping AVR32 support (https://github.com/torvalds/linux/commit/26202873bb51fafdaa51be3e8de7aab9beb49f70) AVR32 gcc is stuck at gcc4.2, while `asm goto` only appeared in gcc4.5. Unless the AtmelStudio compiler is newer, you simply can't safe do this. – Peter Cordes Jun 13 '19 at 13:12
  • @Kampi: No, I didn't do anything special. What version of AtmelStudio do you run? These are my compiler flags: `-x c -DNDEBUG -I".." -I"C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\UC3C_DFP\1.0.49\include\AT32UC3C0512C" -O0 -ffunction-sections -masm-addr-pseudos -Wall -Wextra -Wundef -mpart=uc3c0512c -c -fvisibility=internal -fno-common -Wconversion -S -MD -MP -MF "$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -MT"$(@:%.o=%.o)"`. – Binarus Jun 13 '19 at 13:20
  • @EricPostpischil Grrrr ... you are completely right! While I don't have `-o` explicitly specified in the GCC options, it gets added automatically while building, and indeed, there is **assembly code in the .o files**. I would *never* have come to that idea, because *every* compiler I have seen so far, including the earlier ones from Atmel, generate an extra .lss (list) file. – Binarus Jun 13 '19 at 13:28
  • heh, I'd already concluded from @Kampi's evidence that it doesn't link, and from the `bral` encoding, that you were looking at a `.o`, not a linked executable. That was the only sane explanation for what you were seeing. – Peter Cordes Jun 13 '19 at 13:29
  • @Kampi O.K., I'll try to update and see if I can get an .lss. In the meantime, could you please post your compiler options or tell what you did do to get the .lss? – Binarus Jun 13 '19 at 13:35
  • @PeterCordes Thank you very much for these invaluable hints. You are right, GCC in the AVR version won't support that approach. – Binarus Jun 13 '19 at 13:37
  • @Binarus I simply create a new AVR studio project and change nothing at the compiler options. Under `AVR32/GNU Common` in the project properties, [you can set a tick for .lss file generation](https://imgur.com/a/zMrI5Qw). Maybe you forgot this? – Kampi Jun 13 '19 at 15:16
  • @Kampi Thank you very much for the hint, but I knew that checkbox and have it activated all the time. I'll upgrade to the newest version of Atmel Studio, and if this does not help, try to create a new project and set the toolchain settings step by step as they are in the current project. – Binarus Jun 13 '19 at 15:52

1 Answers1

3

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.

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. 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. @Kampi reports a linker error for your source with 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. (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. 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. (i.e. PC while an instruction is decoded/executed has already been incremented.)

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.

(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.)


GNU C asm goto makes this safe

GNU C inline asm does have syntax for jumping out of inline-asm statements (to C labels). https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels. And see an example on SO: Labels in GCC inline assembly. (Using x86 instructions, but the contents of the asm template are irrelevant to how you use the asm goto syntax.)

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; or fall through to a return false;. (Using condition flags as GNU C inline asm outputs)

But according to the commit message stating reasons for the Linux kernel dropping AVR32 support, AVR32 gcc is stuck at gcc4.2. asm goto only appeared in gcc4.5.

Unless the AtmelStudio compiler is (based on?) a more recent gcc, you simply can't safe do this safely.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • O.K., thank you very much, accepted and +1. *... so it treated it as an external symbol and used a branch with more displacement bytes ...* Finally I have understood this. The target was a library, and thus an error would have occurred not before linking the library to an executable. **What still worries me** is that I have seen some assemblers where you had to **state explicitly when a label was external** (somehow like it is in C). I was assuming that it's the same with GCC, but there might be differences between inline and standalone assembly. – Binarus Jun 13 '19 at 13:51
  • 1
    @Binarus: Not necessarily a library; it could have been defined in another C source file, like `gcc foo.c bar.c`. And yes, some assemblers require `extern foo` declarations. GAS does not: unrecognized symbols are assumed to be extern. – Peter Cordes Jun 13 '19 at 13:53
  • 1
    *it could have been defined in another C source file, like gcc foo.c bar.c* Yes, that's clear, and is what I actually meant: The error would occur not before linking **my** library to another executable (unless some other part of that executable, e.g. a source file or another library, happens to define the "missing" label". * GAS does not: unrecognized symbols are assumed to be extern.* This is the key point. Now you have made me learn within the last hour than I've learned in the last month ... thanks again! – Binarus Jun 13 '19 at 13:57
  • @Binarus: oh! I misread what you were saying. Yes that makes even more sense. Right, your library has an unresolved symbol. (I updated my answer with the explanation about GAS syntax; it's actually an interesting point why GAS syntax doesn't require that. The syntax was designed for compiler output, from back when machines were tiny and compiling one function at a time was a thing.) – Peter Cordes Jun 13 '19 at 13:58