Прыжок со встроенной сборки идет к неверной цели на AVR32
Мы разрабатываем приложение для 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 (т. Е. Метках, которые не находятся во встроенной сборке), что само по себе было бы хорошо - урок усвоен.
Но кроме того, он не предупреждает и не выдает ошибки, когда встречает неизвестную (неопределенную) метку, а вместо этого создает бесконечные циклы, просто используя смещение 0 при переходе / переходе к таким меткам.
Я считаю последний катастрофической ошибкой. Это, вероятно, означает, что (без какого-либо предупреждения) я получу бесконечный цикл в моем программном обеспечении всякий раз, когда я использую неопределенную метку во встроенном коде сборки (например, из-за опечатки).
Что я могу с этим поделать?
1 ответ
Если вы не скажете компилятору, что выполнение может не выйти на другую сторону вашего asm
Заявление, компилятор предполагает, что это так.
Так что оба ваших примера небезопасны, и вам повезло, что второй пример ничего не сломал, потому что функция слишком проста.
Я не уверен, как ваш код компилируется вообще; Локальные метки C обычно не отображаются как ассемблеры с тем же именем. Если они вообще используются сгенерированным компилятором кодом, gcc использует такие имена, как .L1
так же, как для отраслевых целей, которые он изобретает для if()
а также for
/ while
петли. @Kampi сообщает об ошибке компоновщика для вашего источника с AtmelStudios 7.0.1931.
Возможно, вы на самом деле смотрите на не связанные .o
где цель перехода была просто заполнителем, который должен быть заполнен компоновщиком. (А ссылка на неопределенный символ - это ошибка компоновщика, ожидающая своего появления). Кодировка e0 8f 00 00
Конечно, это подходит: ассемблер не нашел метку цели ветви в .s
компилятор дал его, поэтому он рассматривал его как внешний символ и использовал ветвь с большим количеством байтов смещения. По-видимому, в AVR32 относительные смещения ветвей относительно начала инструкции ветвления, в отличие от многих ISA, где это относительно конца ветвления. (т.е. ПК во время декодирования / выполнения инструкции уже был увеличен.)
Это объясняет отсутствие ошибок компоновщика (потому что вы никогда не запускали компоновщик), а также наличие фиктивной цели ветки. Обновление: это было связано, но в библиотеке. Таким образом, у самой библиотеки все еще был неразрешенный символ.
Цель, определенная в другом встроенном операторе asm, присутствует в выводе asm компилятора, поэтому ассемблер находит его и может использовать короткий rjmp
,
(Некоторые ассемблеры помогают вам поймать подобные ошибки, требуя extern foo
деклараций. ГАЗ нет; это просто предполагает, что любой неопределенный символ extern
, Синтаксис GAS исходит от традиционных ассемблеров Unix, которые предназначены для сборки выходных данных компилятора, где древние компиляторы, которые компилировали только одну функцию C за раз (не оптимизацию целого файла), не знали бы, появится ли определение функции в этом .c
файл или отдельный .c
файл. Таким образом, этот синтаксис позволяет за один проход компилировать C на машинах без достаточного количества памяти, чтобы вернуться и добавить extern
объявления для символов, которые не определены позже в выводе asm.)
GNU C asm goto
делает это безопасным
Встроенный asm GNU C имеет синтаксис для перехода от операторов inline-asm (к меткам C). https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html. И посмотрите пример 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, вы просто не можете безопасно сделать это безопасно.