Связывание со статической библиотекой не эквивалентно связыванию с ее объектами

Проблема:

Образ микропрограммы, созданный при связывании со статической библиотекой, отличается от образа микропрограммы, созданного при связывании с объектами, непосредственно извлеченными из статической библиотеки.

Оба образа встроенного ПО линкуются без ошибок и успешно загружаются в микроконтроллер.

Последний двоичный файл (связанный с объектами) выполняется успешно и ожидаемо, а первый (связанный со статической библиотекой) — нет.

Единственными предупреждениями во время компиляции являются unused-but-set-variable в предоставленном производителем HAL, которые из-за различных определений макросов не являются необходимыми для скомпилированной реализации; и unused-parameter в различных слабых функциях, в том числе в предоставленном производителем HAL.

Описание:

Я разрабатываю встроенное приложение для STM32F407. До сих пор я работал с одной кодовой базой, включая HAL и код установки микропроцессора, драйвер для определенного периферийного устройства и приложение, использующее первые два.

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

Проблема в том, что при линковке приложения и статической библиотеки образ прошивки некорректно исполняется на микропроцессоре. При связывании приложения и объектных файлов, извлеченных непосредственно из статической библиотеки, образ встроенного ПО выполняется должным образом.

В частности:

Созданный бинарник не работает при линковке со статической библиотекой с помощью:

$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(APPOBJECTS) Library/libtest.a

Созданный бинарник работает при линковке с объектами, извлеченными из статической библиотеки, используя:

@cd Library && $(AR) x libtest.a && cd ..
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(APPOBJECTS) Library/*.o

В обоих случаях:

CFLAGS = $(INCLUDES) $(DEFS) -ggdb3 -O0 -std=c99 -Wall -specs=nano.specs -nodefaultlibs
CFLAGS+= -fdata-sections -ffunction-sections -mcpu=cortex-m4 -march=armv7e-m -mthumb
CFLAGS+= -mfloat-abi=hard -mfpu=fpv4-sp-d16 -MD -MP -MF [email protected]

LDFLAGS = -T$(LDSCRIPT) -Wl,-static -Wl,-Map=$(@:.elf=.map),--cref -Wl,--gc-sections

Я сравнил выходные данные файла -Wl,--print-gc-sections, а также файла app.map, но между двумя сборками достаточно различий, чтобы ни одна из них не выглядела неправильной. Я также пробовал без -Wl,--gc-sections, безрезультатно.

Вывод arm-none-eabi-size из двух образов прошивки:

 text      data     bss     dec     hex filename
43464        76    8568   52108    cb8c workingapp.elf

 text      data     bss     dec     hex filename
17716        44    8568   26328    66d8 brokenapp.elf

Аналогичное несоответствие размера можно увидеть при компиляции без -Wl,--gc-sections

Используя arm-none-eabi-gdb для отладки работы микроконтроллера, неисправный образ прошивки входит в бесконечный цикл, когда происходит прерывание WWDG. Это прерывание не разрешено прошивкой, поэтому обработчик прерывания по умолчанию использует Default_Handler (бесконечный цикл). Это прерывание не происходит при запуске рабочего образа прошивки.

Происходящее прерывание WWDG на самом деле является отвлекающим маневром, как описано в принятом ответе

--Майк


person Mike Hamer    schedule 20.05.2015    source источник


Ответы (3)


Обзор:

Проблема заключалась в том, что не все объекты из статической библиотеки включались в образ прошивки. Это решается путем окружения статической библиотеки флагами компоновщика --whole-archive и --no-whole-archive:

 $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(APPOBJECTS) -Wl,--whole-archive Library/libtest.a -Wl,--no-whole-archive

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

Путь решения:

Используя arm-none-eabi-gdb для отладки, выяснилось, что отключено прерывание WWDG, вызывающее Default_Handler. Это оказалось отвлекающим маневром... которое случалось достаточно часто, что привело меня к ответу через when-not-configured">"STM32 WWDG прерывает работу, когда не настроен" postoverflow.

Прочитав этот пост и узнав, что сообщения об именах функций gdb часто неточны для функций, которые совместно используют один и тот же адрес памяти, я проверил сгенерированный файл .map на наличие неисправного образа прошивки и подтвердил, что WWDG_IRQHandler находится по тому же адресу памяти, что и большинство обработчиков IRQ, включая обработчики IRQ для прерываний, которые определены и используются системой (например, некоторые прерывания таймера).

Кроме того, все прерывания, определенные в объекте stm32f4xx_it.o (который определяет обработчики IRQHandler для прерываний, используемых системой и который включен в статическую библиотеку), указывали на адрес памяти Default_Handler, а соответствующий IRQHandler символы были указаны как предоставленные startup_stm32f407xx.o.

Затем я проверил, какие объектные файлы были на самом деле связаны с образом прошивки (perl -n -e '/libtest\.a\((.*?)\)/ && print "$1\n"' app.map | sort -u), и обнаружил, что была связана только часть объектов.

Дальнейшая проверка startup_stm32f407xx.s показала, что он определяет много слабых символов, например:

.weak TIM2_IRQHandler

В процессе компоновки статической библиотеки компоновщик ищет в библиотеке неопределенные символы и включает первый найденный объект для определения этих символов. Затем он удаляет символ из списка неопределенных, а также любые другие неопределенные символы, определенные включенным объектом.

Я предполагаю, что произошло то, что компоновщик нашел неопределенный символ в startup_stm32f407xx.o и включил объект. Он считал, что все символы IRQHandler определяются их слабыми определениями. Объект stm32f4xx_it.o никогда не включался, так как он не определял неопределенные символы. Это случалось несколько раз с разными объектными файлами; иногда включались сильные символы, иногда включались слабые символы, в зависимости от того, какой объект искался первым. Интересно (но неудивительно) то, что если слабое определение удаляется, объект, содержащий строгое определение, включается, и все сильные определения из этого файла (правильно) переопределяют уже включенные слабые определения.

Решив проблему, я не уверен, куда идти дальше. Это ошибка компоновщика?

person Mike Hamer    schedule 21.05.2015
comment
Интересный. Я никогда не попадал в эту дыру, но у меня были похожие проблемы со слабыми символами при использовании LTO (поэтому я его сейчас не использую). Тем не менее, это укрепляет мое отношение к тому, чтобы не использовать эти библиотеки. По крайней мере, я сам знаю весь задействованный код. Однако для обработчиков прерываний я делаю то же самое: слабые псевдонимы для бесконечного цикла при запуске и фактические обработчики являются сильными символами. Может ли эта проблема быть связана с использованием архива (который включает в себя библиотеки) вместо обычного объектного файла? На самом деле, я все равно сомневаюсь в плюсах архивов. - person too honest for this site; 21.05.2015
comment
Что касается неправильного имени, которое дает gdb: когда выполнение остановлено, отладчик имеет только текущий адрес инструкции (регистр ПК). Поэтому он должен искать, какой символ соответствует этому адресу (или находится ближе всего к этому адресу в случае функции). Если совпадают несколько символов, он может показать их все, но в большинстве случаев это бесполезно, поэтому gdb просто останавливается при первом совпадении. Какой это символ, зависит от алгоритма поиска (линейный, хэш-таблица, ..). Это довольно распространенная проблема для отладки. - person too honest for this site; 21.05.2015

Вы получите лучший ответ, если сможете объяснить, что на самом деле означает «двоичный файл не работает».

Вы получаете двоичный файл, который ваши инструменты программирования вообще не загружают в чип?

Если это так, внимательно посмотрите на вывод компоновщика в командной строке.

Вы производите что-то, что можно загрузить в чип, но не видите ожидаемого поведения?

Если это так, используйте аппаратный отладчик. Шагайте по коду, пока что-то не сломается, или дайте ему работать, затем остановите его и посмотрите, где вы оказались.

Скорее всего, вы просто обнаруживаете ошибку, которая всегда была в коде, изменяя расположение всего в памяти. Переполнение массива, неправильное разыменование указателя и неинициализированные переменные являются типичными причинами. Включение -Wextra и -Wall может помочь раскрыть этот материал.

Еще одна мысль: убедитесь, что ваш LDSCRIPT имеет правильные размеры флэш-памяти и ОЗУ для фактического номера детали (т.е. не для другой части в семействе).

person Brian McFarland    schedule 20.05.2015
comment
Спасибо за ваши предложения, Брайан; Я обновил вопрос с дополнительной информацией. Я также дважды проверил цель скрипта компоновщика, но, к сожалению, это не проблема. - person Mike Hamer; 20.05.2015

Я также работаю в настоящее время с этим MCU. Однако я избегаю «стандартной» библиотеки ST по уважительным причинам.

Похоже, что сторожевой таймер был включен во время запуска и действительно скоро истекает (прерывание является ранним предупреждением. Это может быть связано с изменениями в поведении во время выполнения. Это может очень сильно различаться в зависимости от связи из-за трамплинов, созданных компоновщиком и/или оптимизация времени обработки (LTO) и встраивание компилятором и другие оптимизации.

Приведенные размеры кажутся выходящими за рамки нормального варианта с идентичными параметрами компиляции/связывания. Но они очень возможны для -Os по сравнению с -O3 и LTO/без LTO (тогда как для последнего результирующий размер кода может быть очень большим или меньшим, в зависимости от -O). Кроме того, я заметил, что некоторые версии gcc/ld имеют проблемы с LTO, и весь код должен быть скомпилирован и связан (!) с теми же параметрами. Также проверьте используемый ABI и убедитесь, что он соответствует (используемым C- и gcc-libs.

Хорошим началом было бы грубое пошаговое выполнение запуска после сброса с точкой наблюдения WWDG->CR. Также проверьте бит EWI; это фактически позволило бы прерывание.

person too honest for this site    schedule 21.05.2015
comment
Спасибо за ответ, Олаф. Это помогло мне найти решение. Могу я спросить, какой HAL вы используете для разработки STM32? - person Mike Hamer; 21.05.2015
comment
@MikeHamer: вообще никаких. Я сам пишу свои драйверы и использую свой собственный фреймворк. Не намного сложнее, чем настроить и использовать материал HAL. Тем более, что это не более уровень аппаратной абстракции, чем обычная система драйверов. Это быстрее и лучше соответствует потребностям проекта. - person too honest for this site; 21.05.2015