avr-gcc деструктивные оптимизации
Я программирую микроконтроллер Atmel ATtiny13a с помощью avr-gcc 4.8.2.
Вот мой код на языке Си:
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 1; // PB0 is output
for (uint8_t i = 0; i < 10; i++) {
PORTB = 1;
_delay_ms(500);
PORTB = 0;
_delay_ms(500);
}
while(1);
}
void test(void) {
DDRB = 1; // PB0 is output
for (uint8_t i = 0; i < 10; i++) {
PORTB = 1;
_delay_ms(100);
PORTB = 0;
_delay_ms(100);
}
}
Тестовая функция (быстрое мигание светодиода) никогда не вызывается из основной функции, поэтому контроллер должен вводить только основную функцию (медленное мигание).
Когда я компилирую код с помощью -O1
, все работает нормально:
avr-gcc -std=gnu99 -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -mmcu=attiny13 -DF_CPU=1200000 -Wall -Wstrict-prototypes -Os -c test.c -o test.o
avr-gcc test.o -o test.elf
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature test.elf test.hex
Но если я использую -Os
(оптимизация по размеру) или -O2
, микроконтроллер запускает функцию test
вместо функции main
функция: светодиод мигает быстро и никогда не останавливается.
Является ли флаг -Os
просто слишком опасным для использования, следует ли его избегать? Или есть что-то, что я могу изменить в своем коде, чтобы избежать такого рода ошибок? ATtiny13a имеет только 1K вспышки, поэтому уменьшение размера является чем-то важным.
Edit: как и предлагалось в комментариях, вот ассемблерная разница с -O1
и -O2
: http://www.diffchecker.com/3l9cdln6
Там вы можете увидеть, что -O2
изменяет первый раздел с .text
на .text.startup
.
--- test.o1.txt 2013-12-03 19:10:43.874598682 +0100
+++ test.o2.txt 2013-12-03 19:10:50.574674155 +0100
@@ -3,7 +3,7 @@
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
- .text
+ .section .text.startup,"ax",@progbits
.global main
.type main, @function
main:
Это, вероятно, главная проблема здесь. После некоторого дальнейшего тестирования я обнаружил, что виновником является оптимизация -freorder-functions
. Есть ли способ предотвратить такое поведение?2 ответа:
Я провел дополнительную отладку и обнаружил, что "виновником" была оптимизация
-freorder-functions
. Это задокументировано в manpage следующим образом:-freorder-functions Reorder functions in the object file in order to improve code locality. This is implemented by using special subsections ".text.hot" for most frequently executed functions and ".text.unlikely" for unlikely executed functions. Reordering is done by the linker so object file format must support named sections and linker must place them in a reasonable way.
Последняя строка в документации объясняет проблему, которую я имел / вызывал. Если мы снова посмотрим на команды компиляции из исходного вопроса:
$ avr-gcc -std=gnu99 -funsigned-char -funsigned-bitfields -fpack-struct \ -fshort-enums -mmcu=attiny13 -DF_CPU=1200000 -Wall -Wstrict-prototypes \ -Os -c test.c -o test.o $ avr-gcc test.o -o test.elf
...мы видим, что я передавал флаги оптимизации компилятору, но не компоновщику. Я предположил, что
Результат: ассемблерный код был переупорядочен компилятором (включая соответствующие метки), но компоновщик не учитывал эти метки. И поскольку функцияCFLAGS
влияет только на компиляцию, а не на связывание, поэтому я не прошел их к линкеру, но в этом случае я ошибся.test
была помещена перед функциейmain
компилятором и не была переупорядочена компоновщиком, это был код, который фактически выполнялся на микроконтроллере.Таким образом, решение оказалось следующим: флаги компилятора также должны быть переданы в линкер!
Я знаю, что мой ответ приходит через ~2 года после того, как был задан этот вопрос, но я считаю, что до сих пор нет правильного, глубокого ответа.
Давайте начнем с небольшой теории:Когда вы вызываете GCC, он обычно выполняет предварительную обработку, компиляцию, сборку и связывание. "Общие параметры" позволяют остановить этот процесс на промежуточной стадии. Например, параметр-c говорит не запускать компоновщик. Тогда выход состоит из объекта файлы, выводимые ассемблером.
Другие варианты передаются на один этап обработки. Некоторые параметры управляют препроцессором, а другие-самим компилятором. Однако другие параметры управляют ассемблером и компоновщиком; большинство из них здесь не задокументированы, поскольку вам редко нужно использовать какой-либо из них.Источник: GCC Online Docs
LDFLAGS
Дополнительные флаги для компиляторов, когда они должны вызывать компоновщик, ‘ld', такие вместо этого в переменную LDLIBS следует добавить библиотеки as-L. (-lfoo).
Источник: GNU make Manual
Как вы можете видеть, это зависит от GCC (я буду называть его так, чтобы отличить от фактического компилятора; вы можете найти его называемым C compiler frontend или просто compiler), какие параметры будут переданы в какие инструменты, и оказывается, что опция
Реальная проблема заключается в том, что вы не предоставляете-On
не передается компоновщику (вы можете проверить это, предоставив опцию-v
GCC). Так вызов GCC без этой опции, когда предполагается, что он будет выполнять только связывание, в порядке.-mmcu=dev
опцию GCC при связывании. Поэтому он не может найти правильныйcrt*.o
файл (C RunTime) и сообщить компоновщику, чтобы связать его; ваше приложение заканчивается без какого-либо кода инициализации. Поэтому обратите внимание, что вы должны включить-mmcu=dev
в LDFLAGS или передать его в GCC независимо от того, что он должен делать (предварительная обработка/компиляция/сборка/связывание). Я уже видел пару файлов Makefile без этой опции в LDFLAGS в интернете, так что будьте осторожны.
Теперь пришло время для некоторой практики-предположив, что ваш источник находится в файле
test.c
, выполните следующие команды (в linux):avr-gcc -mmcu=attiny13a -DF_CPU=1200000 -Wall -O1 -c -o testO1.o test.c avr-gcc -mmcu=attiny13a -DF_CPU=1200000 -Wall -Os -c -o testOs.o test.c avr-gcc -o testO1_nodev.elf testO1.o avr-gcc -v -o testOs_nodev.elf testOs.o > testOs_nodev.log 2>&1 avr-gcc -v -mmcu=attiny13a -o testOs_correct.elf testOs.o > testOs_correct.log 2>&1
Я оставил только необходимые опции +
Давайте посмотрим на-Wall
, для ATtiny13a вам нужно-mmcu=attiny13a
вместо-mmcu=attiny13
.testOs_nodev.log
иtestOs_correct.log
. Выполните следующую команду:diff testOs_nodev.log testOs_correct.log
И ты кое-что увидишь. например:
2c2 < Reading specs from /usr/lib/gcc/avr/5.2.0/device-specs/specs-avr2 --- > Reading specs from /usr/lib/gcc/avr/5.2.0/device-specs/specs-attiny13a 10,12c10,12 < LIBRARY_PATH=/usr/lib/gcc/avr/5.2.0/:/usr/lib/gcc/avr/5.2.0/../../../../avr/lib/ < COLLECT_GCC_OPTIONS='-v' '-o' 'testOs_nodev.elf' '-specs=device-specs/specs-avr2' < /usr/lib/gcc/avr/5.2.0/collect2 -plugin /usr/lib/gcc/avr/5.2.0/liblto_plugin.so \ -plugin-opt=/usr/lib/gcc/avr/5.2.0/lto-wrapper -plugin-opt=-fresolution=/tmp/ccqBjM6T.res \ -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lm -plugin-opt=-pass-through=-lc \ -o testOs_nodev.elf -L/usr/lib/gcc/avr/5.2.0 -L/usr/lib/gcc/avr/5.2.0/../../../../avr/lib \ testOs.o --start-group -lgcc -lm -lc --end-group --- > LIBRARY_PATH=/usr/lib/gcc/avr/5.2.0/avr25/tiny-stack/:\ /usr/lib/gcc/avr/5.2.0/../../../../avr/lib/avr25/tiny-stack/:\ /usr/lib/gcc/avr/5.2.0/:/usr/lib/gcc/avr/5.2.0/../../../../avr/lib/ > COLLECT_GCC_OPTIONS='-v' '-o' 'testOs_correct.elf' '-specs=device-specs/specs-attiny13a' \ '-mmcu=avr25' '-msp8' > /usr/lib/gcc/avr/5.2.0/collect2 -plugin /usr/lib/gcc/avr/5.2.0/liblto_plugin.so \ -plugin-opt=/usr/lib/gcc/avr/5.2.0/lto-wrapper -plugin-opt=-fresolution=/tmp/ccV919rY.res \ -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lm -plugin-opt=-pass-through=-lc \ -plugin-opt=-pass-through=-lattiny13a -mavr25 -o testOs_correct.elf \ /usr/lib/gcc/avr/5.2.0/../../../../avr/lib/avr25/tiny-stack/crtattiny13a.o \ -L/usr/lib/gcc/avr/5.2.0/avr25/tiny-stack -L/usr/lib/gcc/avr/5.2.0/../../../../avr/lib/avr25/tiny-stack \ -L/usr/lib/gcc/avr/5.2.0 -L/usr/lib/gcc/avr/5.2.0/../../../../avr/lib testOs.o \ --start-group -lgcc -lm -lc -lattiny13a --end-group
(я сломал несколько строк, чтобы сделать его читаемым)
Разница в том, что без опции-mmcu=dev
GCC по умолчанию использует файл спецификаций avr2 и не связывает ни один файл CRT.Исследуйте объектные файлы (
*.o
) и выходные файлы (*.elf
) с помощью avr-objdump:avr-objdump -xd testOs_nodev.elf
Вы заметите, что файлы
*_nodev.elf
не содержат корректной информации об архитектуре (avr вместо avr:25) и никакого кода запуска (сравнитеtestOs_correct.elf
СtestOs_nodev.elf
). В разделе кода, кажется, дословно копия того, что было предоставлено в объектном файле.
Если какая-либо часть моей разработки кажется неясной или нуждается в дополнительном объяснении, не стесняйтесь спрашивать (комментировать).