Тайна AVR GCC

Всем известно, что, даже если проект содержит только while (1), сгенерированный компилятором код имеет довольно существенный размер (около пары сотен байт). Собственно, меня давно интересовало назначение этих инструкций. И сегодня я наконец-то от нечего делать взял дизассемблер, проанализировал содержимое стандартного кода, генерируемого AVR-GCC для каждого проекта, и на случай, если упомянутый вопрос мучал не только меня, решил написать статью, посвященную его полному разбору.



Статья обновлена — улучшено форматирование исходников.

Читать дальше
  • +4
  • 22 января 2012, 20:10
  • _YS_

Комментарии (47)

RSS свернуть / развернуть
Не пойму, а в чем проблема с вектором прерываний, все равно эту память нельзя использовать, она так и так занята. Ну а таблица векторов прерываний заполняется для надежности, вдруг у вас контроллер будет работать в условиях высоких помех, там все что угодно сработать может и прерывания, которые в вашем коде не наблюдаются.
0
Почему это нельзя использовать? Если прерывания не включены, то исполнение начнется с нулевого смещения последовательно.
0
Ну если человек получает огромное удовольствие от поиска странных багов, то может и использовать. Вообще, можно все что угодно делать, только потом это может плохо закончится.
0
Использовать то можно, но черевато. Вектора то никуда не денутся и если что разрешить случайно, то будет те несанкционированный JMP :)
0
Так это, если у меня в коде нет ни единого sei(), что может сработать?
0
А, что I флаг можно только через SEI CLI крутить? Я штуки три способа с ходу придумаю. А при желании наверчу так, что ты хрен найдешь где у меня прерывания включаются без прогона под отладчиком по шагам.
+1
Да тут как ни крути — если маленький код, где 100% граблей не будет, эта кучка байт погоды не сделает. Если большой (без прерываний) — лучше перестраховаться и тоже оставить.

А чтобы не прописывать вектора можно тупо макрос с nop'ами засунуть, типа .dw 0xFFFF,0xFFFF…
+1
комментарий был удален
хотя при включении он и так ноль.
Хрен там. Как повезет.
0
Кагбэ даташит гарантирует, не?
0
при сбросе по питанию или помехе какой может быть такая ситуация, что проц пошел с нуля, а в регистрах каша. Я несколько раз ловил такие приколы.
0
В общем, GCC писали перестраховщики. :)
0
Не GCC, а AVR-LIBC.
0
Вот блин, ну не как народу не угодить.
0
Нет, просто нормальные люди.
+2
Мораль: AVR-GCC размещает переменные в памяти, начиная с адреса 0x0100. Интересно, подо что зарезервированы первые 255 байт?
Возможно и меньше. У AVR регистровый файл и регистры периферии занимают первые 0x60 байт адресного пространства оперативной памяти. Неясно правда, чем заняты остальные 0xA0 байт. Возможно нечто вроде выравнивания, благо ОЗУ дохрена. А может у 128-й и больше места в начале под регистры отведено, но это уже надо смотреть даташит. По моему, когда регистры не умещаются в 0x20-0x59 они помещаются за ОЗУ.
0
  • avatar
  • Vga
  • 22 января 2012, 20:43
Поправил статью, с адреса 0x0100 как раз и начинается оперативка.
0
Можно глупый вопрос? А зачем брать дизассемблер, если компилятор Сей и так ассемблерный листинг свой может выдавать? Я так один раз хотел вплотную заняться сями. Но посмотрел, как int main (void) { } на 140 байт разраслась, и разочаровался. Лучше буду и дальше писать для однокристаллок на ассемблере с кучей макросов, но чётко знать, как должна пойти программа!
(жирным выделена важная часть, остальное — просто разошёлся :)
0
  • avatar
  • Deer
  • 22 января 2012, 20:51
Листинг надо искать по директориям, а дизассемблер показывается по Wiew->Disassembler в AVR Studio. :)

Но посмотрел, как int main (void) { } на 140 байт разраслась, и разочаровался.

Ну, теперь Вы знаете, что это за байты. :)
0
*View
0
Зато листинг на порядок информативнее.
Но посмотрел, как int main (void) { } на 140 байт разраслась, и разочаровался
Ну и что с того? Тем паче это статический стартап-код, он расти не будет. Вообще, при желании можно запретить компилятору юзать стандартный стартап и написать свой. Там можно что угодно делать. Хоть код на месте ТВП писать.
+1
Ну, всё равно считаю, что для однокристаллок ассемблер намного разумнее применять!

Хотя… ещё недавно я думал, что там ни к чему динамическое распределение памяти и объектно-ориентированный подход. Ан нет! Появилась задача, наконец, где применение этих средств вполне оправданно!

Наконец настали каникулы, так что скоро надеюсь доделаю девайсы и напишу ряд статей :)
0
всё равно считаю, что для однокристаллок ассемблер намного разумнее применять!

Я тоже через это проходил. :) На самом деле истина посередине. Общую логику проще пиать на С, а критичные места на асме.
0
Ну… я всё ещё считаю, что реализую ООП на асме, и будет экономично и быстро! :)
0
В нынешних однокристалках памяти столько, что на асме под иные можно мегабайты кода накатать. Нафиг-нафиг. Да и нынешние компиляторы C иной раз матерых ассемблерщиков по оптимальности кода уделывают. Правда, преимущественно на x86.
А я еще и настолько ленив, что и на ЯВУ-то не всегда до конца проект довожу. На асме я и до main'а не доберусь.
Да и по большей части задача выглядит примерно так. Соответсвенно и инструмент можно выбирать так же.
+1
Ну, всё равно считаю, что для однокристаллок ассемблер намного разумнее применять!
Если вас интересует процесс, то да, конечно. Тут и спорить бессмысленно. А вот если интересует результат, тот тут все далеко не так однозначно.
+1
Смотря какой процесс, опять же. Меня, например, интересует процесс создания программы (логика-алгоритмы-структуры данных), а не выражения ее на неудобном языке.
Ассемблер только для оптимизации, процев вроде PIC10F200 (хотя возможно есть и компиляторы С, способные в них уложиться) и изучения самого ядра. А, ну и для реверс-инженеринга, разумеется :)
0
Понятное дело, что знать асм желательно, как минимум это полезно, что бы понимать, что нагенерил компилятор. Но писать на нем программы полностью — чистая потеря времени, как по мне. Тем более, что писать эффективный код под современные процы (даже микроконтроллеры) достаточно сложно, уж больно много всяких факторов надо держать в голове. Да и есть более интересные задачи, чем укорачивание прошивки на несколько байт, что, с учетом размера флеша у современных контроллеров, не имеет никакого практического смысла. Это чем-то напоминает мои развлечения с односторонней разводкой плат. С одной стороны мне это нравится (именно процесс) и даже определенный азарт в этом есть (впихнуть невпихуемое), а с другой — я прекрасно понимаю, что в промышленности никто не возится с платой 5х3см днями только для того, что бы обойтись одним слоем вместо двух. Правда, у моих развлечений есть хоть какой-то практический смысл — односторонние платы легко делать в домашних условиях.
+1
Собственно, я об этом и говорю.
0
Вот кстати в плюс CodeVision'у. У того код инициализации переменных подключается только при наличии инициализированных переменных. Ещё есть опция очистки глобальных переменных (по сути — всей ОЗУ) при старте (по умолчанию включено). А таблицу векторов можно сделать любого размера, я её укорачивал за счёт неиспользуемых прерываний на тини2313, когда мне не хватало нескольких байт памяти.
Но добавлен ещё код для инициализации EECR (регистр управления eeprom), MCUCR, XMCRB (внешняя память) и отключения вотчдога (если он, конечно, не включен в настройках проекта). Так что проект из топика получился на 266 байт (против 208, если я правильно понял, у GCC). Если же убрать инициализацию переменной i и очистку памяти, то остаётся 196 байт.
0
  • avatar
  • ACE
  • 22 января 2012, 21:20
GCC тоже все это умеет, только доку курить надо. Она там что БСЭ в 100500 томах. А из ключей можно стихи складывать.
0
Ну это понятно, даже упоминать не стал. Я так, про настройки по умолчанию.
0
Если же убрать инициализацию переменной i и очистку памяти, то остаётся 196 байт.

Если убрать переменную, то у GCC тоже получается 196 байт. :) А инициализировать переменные (если они есть) требует стандарт С.

Так что в целом один к одному…
0
Ну инициализировать очисткой памяти — да, это правильно. Вот интересно что gcc будет делать, если объявить много глобальных переменных, без присвоения начального значения? Также как и тут, в прошивке выделит место под все переменные? Или все-таки подумает и сделает очистку области памяти переменных нулями?
0
Хм, сейчас спробую…
0
Ух, там такое… :) Я, пожалуй, дополню статью.
0
Готово, привел и этот случай.
0
Вот интересно что gcc будет делать, если объявить много глобальных переменных, без присвоения начального значения?
Положит их в секцию .bss и инициализирует нулями в цикле.
А вот если объявлять локальные переменные, то они выделяются на стеке, и там может оказаться любой мусор.
0
Тут есть еще один момент. Локальные переменные в стеке все равно инициализируются (не важно, сразу или в процессе работы), а на это тоже идет время, причем уже во время нормальной работы, а не на старте. С другой стороны, глобальные переменные сильно ухудшают сопровождение, расширение и тестирование, могут создавать проблемы при отладке. Так что, как всегда «выбирай, но осторожно. остороно, но выбирай» :)
0
Сверху уже есть ответ. Не все так просто оказалось. Почему-то GCC упорно пихает код инициализации значений вместе с нормальным кодом обнуления.
0
Я немножко не о том. Я о разнице глобальных и локальных переменных. А тот код, который цсс «упорно пихает», как по мне, погоды не делает. К тому же, подозреваю, он просто есть в стартап коде, который лежит прекомпиленый. Думаю, если сильно приспичит, его можно выпилить руками.
0
Ничего выпиливать не нужно, просто сказать компилятору -no_start_code или что0то в этом духе (см. доки)
0
Это и есть «выпилить», на самом деле. Конкретный способ это уже дело десятое…
0
Ненене, выпилить-то можно. Я не об этом. Я о том, что, например, код обнуления глобальных переменных GCC вставляет только тогда, когда есть обявленные переменные без четкого значения. Когда нет — не вставляет. А вот упомянутый кусок кода с чтением предопределенных значений он кладет всегда, вне зависимости от того, если ли предопреленные значения, или все по умолчанию, или даже переменных вовсе нет. Вот что странно.
0
А инициализировать переменные (если они есть) требует стандарт С.
Статические только, если мне память не изменяет.
0
Вот именно. Занулять стандарт требует только статические переменные. Во всех остальных может честно располагаться мусор.
0
Свой startup код уже никто не пишет?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.