Прыжок со встроенной сборки идет к неверной цели на AVR32 - PullRequest
0 голосов
/ 13 июня 2019

Мы разрабатываем приложение для Atmel AVR32 / UC3C0512C с использованием AtmelStudio 7.0.1645. Выполняя некоторые базовые тесты, я заметил кое-что очень странное.

Пожалуйста, рассмотрите следующий код (я знаю, что это плохой стиль и необычный, но здесь дело не в этом):

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 Ответ

3 голосов
/ 13 июня 2019

Если вы не скажете компилятору, что выполнение может не выйти на другую сторону вашего оператора asm, компилятор предполагает, что это так.

Так что оба ваших примера небезопасны, и вам повезло, что второй пример ничего не сломал, потому что функция слишком проста.


Я не уверен, как ваш код компилируется вообще; Локальные метки C обычно не отображаются как ассемблеры с тем же именем. Если они вообще используются сгенерированным компилятором кодом, gcc использует имена, такие как .L1, такие же, как и для целей ветвления, которые он изобретает для циклов if() и for / while. @Kampi сообщает об ошибке компоновщика для вашего источника с AtmelStudios 7.0.1931.

Возможно, вы на самом деле смотрите на несвязанный .o, где цель ветвления была просто заполнителем, который должен заполнять компоновщик . (А ссылка на неопределенный символ - это ошибка компоновщика, ожидающая своего появления). Кодировка e0 8f 00 00 определенно подходит для этого: ассемблер не нашел метку цели перехода в .s, который дал ему компилятор, поэтому он рассматривал его как внешний символ и использовал ветвь с большим количеством байтов смещения. По-видимому, в AVR32 относительные смещения ветвлений относятся к start инструкции ветвления, в отличие от многих ISA, где это относительно конца ветвления. (то есть ПК во время декодирования / выполнения инструкции уже увеличен).

Так что это объясняет отсутствие у вас ошибок компоновщика (потому что вы никогда не запускали компоновщик) и обнаружение фиктивной цели ветки. Обновление: это было связанным, но в библиотеке. Таким образом, сама библиотека все еще имела неразрешенный символ.

Цель, определенная в другом встроенном операторе asm , присутствует в выводе asm компилятора, поэтому ассемблер находит его и может использовать короткий rjmp.

(Некоторые ассемблеры помогают вам поймать подобные ошибки, требуя деклараций extern foo. GAS этого не делает; просто предполагается, что любой неопределенный символ равен extern. Синтаксис GAS исходит от традиционных ассемблеров Unix, которые предназначены для сборки выходных данных компилятора, где древние компиляторы, которые компилировали только одну функцию C за раз (не для оптимизации всего файла), не знали, появится ли определение для функции в этом файле .c или в отдельном файле .c. Таким образом, этот синтаксис позволяет -проход компиляции C на машинах без достаточного количества памяти, чтобы вернуться назад и добавить объявления extern для символов, которые не определены позже в выводе asm.)


GNU C asm goto делает это безопасным

Встроенный ассемблер GNU C имеет синтаксис для перехода от операторов inline-asm (к меткам C). https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#GotoLabels. И посмотрите пример по SO: Метки в сборочной сборке GCC . (Используя инструкции x86, но содержимое шаблона asm не имеет отношения к тому, как вы используете синтаксис asm goto.)

Для целей без синтаксиса GCC6 для выходов кода состояния / флага это может быть удобным способом использовать условную ветвь во встроенном asm для перехода к some_label: return true; или к return false;. ( Использование флагов условий в качестве встроенных выводов GNU C )

Но согласно сообщению о фиксации с указанием причин отказа ядра Linux от поддержки AVR32, AVR32 gcc застрял на gcc4.2. asm goto появился только в gcc4.5.

Если компилятор AtmelStudio (не основан на?) Является более поздним gcc, вы просто не можете безопасно сделать это безопасно.

...