Свързването със статична библиотека не е еквивалентно на свързването с нейните обекти

Проблем:

Изображението на фърмуера, генерирано при свързване със статична библиотека, е различно от изображението на фърмуера, генерирано при свързване с обектите, директно извлечени от статичната библиотека.

И двете изображения на фърмуера се свързват без грешка и се зареждат успешно в микроконтролера.

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

Единствените предупреждения по време на компилация са 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. Това се оказа червена херинга... което се е случвало достатъчно често, за да ме доведе до отговора чрез "STM32 WWDG прекъсва задействане, когато не е конфигуриран" публикация на stackoverflow.

След като прочетох тази публикация и научих, че отчитането на името на gdb функцията често е неточно за функции, които споделят един и същ адрес на паметта, проверих генерирания .map файл за дефектно изображение на фърмуера и потвърдих, че WWDG_IRQHandler се намира на същия адрес в паметта като повечето на IRQHandlers включително IRQHandlers за прекъсвания, които са дефинирани и използвани от системата (напр. някои прекъсвания на таймера).

Освен това, всички прекъсвания, дефинирани в обекта stm32f4xx_it.o (който дефинира IRQHandlers за прекъсвания, използвани от системата, и който е включен в статичната библиотека), сочат към адреса на паметта на 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 дава: когато изпълнението е спряно, дебъгерът има само адреса на текущата инструкция (PC регистър). Така че трябва да търси кой символ съответства на този адрес (или е най-близкият преди този адрес в случай на функция). Ако няколко символа съвпадат, може да покаже всички, но това би било безполезно в повечето случаи, така че gdb просто спира при първото съвпадение. Кой символ зависи от алгоритъма за търсене (линеен, хеш-таблица, ..). Това е доста често срещан проблем за отстраняване на грешки. - person too honest for this site; 21.05.2015

Ще получите по-добър отговор, ако можете да обясните какво всъщност означава „двоичният файл не работи“.

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

Ако е така, погледнете внимателно изхода на линкера в командния ред.

Произвеждате ли нещо, което можете да заредите в чипа и не виждате очакваното поведение?

Ако е така, използвайте хардуерен дебъгер. Преминавайте през кода, докато нещо се счупи, или го оставете да работи, след което го спрете и вижте къде сте попаднали.

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

Друга мисъл: Уверете се, че LDSCRIPT има правилните размери на флаш и RAM за действителния номер на частта (т.е. не е за различна част от семейството).

person Brian McFarland    schedule 20.05.2015
comment
Благодаря ти за предложенията, Браян; Актуализирах въпроса с повече информация. Също така проверих два пъти целта на скрипта за свързване, което не е проблемът тук, за съжаление. - person Mike Hamer; 20.05.2015

В момента също работя с този MCU. Въпреки това избягвам ST "стандартната" библиотека по основателни причини.

Изглежда, че пазачът е активиран по време на стартиране и изтича скоро (прекъсването е ранно предупреждение. Това може да се дължи на вариации в поведението по време на изпълнение. Това може много да варира в зависимост от връзката поради батути, създадени от линкера и/или Tink-Time оптимизация (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