Форум РадиоКот https://radiokot.ru/forum/ |
|
GNU LD. Пытаюсь написать свой скрипт компоновки. https://radiokot.ru/forum/viewtopic.php?f=59&t=176250 |
Страница 1 из 1 |
Автор: | YS [ Пт мар 26, 2021 21:55:34 ] |
Заголовок сообщения: | GNU LD. Пытаюсь написать свой скрипт компоновки. |
Здравствуйте, коллеги! Я решил поучиться работать с GNU LD. Начать решил с малого: в стандартный файл скрипта для STM32L152RE, который идет в комплекте с EmBitz, добавить секции для размещения данных в EEPROM (по умолчанию их там нет). Посмотрите, пожалуйста, я все правильно сделал? ![]() СпойлерКод: OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) SEARCH_DIR(.) /* Memory Spaces Definitions */ MEMORY { ROM (rx) : ORIGIN = 0x08000000, LENGTH = 512K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 80K /* ДОБАВИЛ определения для встроенной в контроллер EEPROM. Значения адресов и размеров взяты из даташита. */ EEP1 (rw) : ORIGIN = 0x08080000, LENGTH = 8K EEP2 (rw) : ORIGIN = 0x08082000, LENGTH = 8K } SECTIONS { .text : { KEEP(*(.isr_vector)) *(.text*) KEEP(*(.init)) KEEP(*(.fini)) /* .ctors */ *crtbegin.o(.ctors) *crtbegin?.o(.ctors) *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) *(SORT(.ctors.*)) *(.ctors) /* .dtors */ *crtbegin.o(.dtors) *crtbegin?.o(.dtors) *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) *(SORT(.dtors.*)) *(.dtors) *(.rodata*) KEEP(*(.eh_frame*)) } > ROM .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } > ROM __exidx_start = .; .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } > ROM __exidx_end = .; __etext = .; .data : AT (__etext) { __data_start__ = .; *(vtable) *(.data*) . = ALIGN(4); /* preinit data */ PROVIDE_HIDDEN (__preinit_array_start = .); KEEP(*(.preinit_array)) PROVIDE_HIDDEN (__preinit_array_end = .); . = ALIGN(4); /* init data */ PROVIDE_HIDDEN (__init_array_start = .); KEEP(*(SORT(.init_array.*))) KEEP(*(.init_array)) PROVIDE_HIDDEN (__init_array_end = .); . = ALIGN(4); /* finit data */ PROVIDE_HIDDEN (__fini_array_start = .); KEEP(*(SORT(.fini_array.*))) KEEP(*(.fini_array)) PROVIDE_HIDDEN (__fini_array_end = .); . = ALIGN(4); /* All data end */ __data_end__ = .; } > RAM .bss (NOLOAD): { __bss_start__ = .; *(.bss*) *(COMMON) __bss_end__ = .; } > RAM .heap (NOLOAD): { __end__ = .; end = __end__; *(.heap*) __HeapLimit = .; } > RAM /* .stack_dummy section doesn't contains any symbols. It is only * used for linker to calculate size of stack sections, and assign * values to stack symbols later */ .stack_dummy (NOLOAD): { *(.stack) } > RAM /* ДОБАВИЛ две секции, соответствующие двум банкам EEPROM. Использовал KEEP, чтобы компоновщик не удалял данные, которые по той или иной причине должны быть записаны в память, но не используются в программе непосредственно. */ .eeprom_bank_1 : { KEEP (*(.eep_bank_1)); } > EEP1 .eeprom_bank_2 : { KEEP (*(.eep_bank_2)); } > EEP2 /* Set stack top to end of RAM, and stack limit move down by * size of stack_dummy section */ __StackTop = ORIGIN(RAM) + LENGTH(RAM); __StackLimit = __StackTop - SIZEOF(.stack_dummy); PROVIDE(__stack = __StackTop); /* Check if data + heap + stack exceeds RAM limit */ ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack") } Судя по тому, что я наблюдаю в выходном бинарнике, все работает. Проверял, объявляя массив вот так: Код: volatile uint32_t a[10] __attribute__ ((section(".eep_bank_2"))) = {1,2,3,4,5,6,7,8,9,10}; И да, его правда видно в бинарнике начиная с адреса 0x08082000. Но, тем не менее, у меня есть вопросы. Вопрос номер один. Я просто добавил новые секции в конец перечисления секций. Этого достаточно, чтобы они встали, как надо? Если да, то зачем при объявлении секции .data используется атрибут AT? Вопрос номер два. Я правильно понимаю, что, по сути, две записи, которые я привожу ниже, эквивалентны? Код: MEMORY { ... EEP1 (rw) : ORIGIN = 0x08080000, LENGTH = 8K ... } ... .eeprom_bank_1 : { KEEP (*(.eep_bank_1)); } > EEP1 Код: .eeprom_bank_1 0x08080000 : { KEEP(*(.eep_bank_1)); } Я так понимаю, что в первом варианте компоновщик кроме всего прочего будет проверять размер, но вроде бы во втором он должен положить данные этой секции по тому же адресу. |
Автор: | Мурик [ Пт мар 26, 2021 23:43:14 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
YS писал(а): зачем при объявлении секции .data используется атрибут AT? Потому что данные для ОЗУ, но хранятся во флеше.https://ftp.gnu.org/old-gnu/Manuals/ld- ... /ld_3.html YS писал(а): EEP1 (rw) : ORIGIN = 0x08080000, LENGTH = 8K EEPROM в STM32 поддерживает прямую запись?
EEP2 (rw) : ORIGIN = 0x08082000, LENGTH = 8K |
Автор: | YS [ Сб мар 27, 2021 10:18:18 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
Спасибо. ![]() Кроме всего прочего, я тут нашел замечательную публикацию, которая мне многое пояснила. Для тех, кто найдет эту тему поиском, напишу, в чем дело. Как обычно, всему виной историческая терминология. Там есть два термина - LMA (load memory address) и VMA (virtual memory address). Правда, в наши дни уже давно ничего никуда не загружается. По сути, VMA - это тот стартовый адрес, который компоновщик будет учитывать при обращении к переменным, расположенным в этой секции. LMA - это адрес, по которому данные этой секции будут записаны в файл образа прошивки, а в нашем случае - и во FLASH контроллера, потому что прошивка записывается туда. Адреса в случае выше указываются как <имя секции> : AT [LMA] { ... } [ > <указанная память определяет VMA>] В файле выше смысл в том, что RAM начинается с 0x20000000, и в тех местах, где производится обращение к переменным, расположенным в этой секции, компоновщик будет подставлять смещения от этого адреса. А вот константы, предназначенные для инициализации переменных, он положит сразу после кода (секция .text), то есть, во FLASH. Если этого не сделать, то выходной .hex-файл будет инструктировать программатор записывать значения прямо в RAM. Он, конечно, запишет, это технически возможно, но смысл, заключающийся в резервировании памяти в RAM и хранении инициализационных констант во FLASH, будет потерян. К слову, атрибут NOLOAD говорит компоновщику, что секцию надо всего лишь учитывать при расстановке смещений, но в выходной файл ее содержимое писать не надо. Цитата: EEPROM в STM32 поддерживает прямую запись? Да. Сначала надо разблокировать запись в специальном регистре, и дальше можно писать по указателю. Ну а в нашем случае, если переменная расположена в новой секции, можно будет писать как в обычную переменную. |
Автор: | AVI-crak [ Сб мар 27, 2021 11:09:53 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
Оно конечно будет работать, но радости не принесёт. Размечать EEPROM как область памяти - означает позволить компилятору самому назначить место хранения переменных. Ну в том смысле что он будет там всё оптимизировать и складывать в аккуратную кучку. Это удобно, но не долговечно. |
Автор: | iddqd [ Чт апр 08, 2021 23:17:14 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
Более того - все время работать с разблокированным на запись EEPROM не очень удачная идея с точки зрения сохранности данных в нем. С всем что флеша касается вообще работают обычно так: разблокировали - записали что хотели - заблокировали обратно. Иначе записанного можно однажды лишиться при отклонениях от идеала. И это, при рестарте переменные инициализируются же. Это чего, каждый ребут будет цикл еепром давать? Или оно как uninitialized идет? А как вы начальные значения пропишете только 1 раз? И вообще как оно будет non-volatile при этом, если так в лоб? Можно const'ами обвесить, но записывать как? ![]() Кроме того - доступ на запись может bus stall на довольно почтенное время. Вам совсем плевать на реальное время и вы готовы с неконтролируемым (вами явно) тупняком чипа от такого доступа жить? Флеш так то довольно долго пишется (по микроконтроллерным меркам) и в это время модуль флеша не может код прошивки отдавать. В 2-банковых чипах, конечно, не занятый записью банк дееспособен пока другой пишется - но этим надо явно заморочиться, положив код в один и поюзав еепром в другом. Ну и если хочется видеть это напрямую в памяти - например, завести typedef struct config какой-нибудь, переменную (-ые) которые заявлены - как _указатели_ на это, и по мере желания можно указатель назначать куда там охота (ram vs eeprom) например (но осторожно, указатели они такие). На еепром лучше указывать с полями const везде, во избежание. А записывать чем-то типа memcpy этого (из версии в RAM) - сняв лок, записав как надо, вернув лок - и вызывается в момент когда ОК что чип может довольно долго тупануть и за это ничего не будет. На 2-банковом в принципе можно код в 1 банк, еепром в другой, тогда это не проблема вроде, но потребует специального внимания. |
Автор: | YS [ Чт апр 15, 2021 14:59:52 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
Цитата: все время работать с разблокированным на запись EEPROM не очень удачная идея с точки зрения сохранности данных в нем. С чего вы взяли, что я буду так делать? ![]() Цитата: И это, при рестарте переменные инициализируются же. Инициализируются только переменные из сегмента .data. Для этого в startup-коде, который по умолчанию добавляется при компоновке, есть специальный цикл. Для всех остальных сегментов никакой инициалиации по умолчанию нет. Цитата: А как вы начальные значения пропишете только 1 раз? То, что помещено в заданные сегменты, попадает в образ прошивки по соответствующим адресам и, соответственно, записывается программатором при прошивке чипа. В этом и есть прелесть задания области сегментом в скрипте компоновщика. Цитата: И вообще как оно будет non-volatile при этом, если так в лоб? Эта область памяти non-volatile аппаратно. Цитата: Кроме того - доступ на запись может bus stall на довольно почтенное время. Вам совсем плевать на реальное время и вы готовы с неконтролируемым (вами явно) тупняком чипа от такого доступа жить? FLASH находится в другом банке относительно EEPROM. Кроме того, запись туда имеет смысл производить только при сохранении настроек, например. Цитата: но осторожно, указатели они такие Именно от этой проблемы избавляет введение специального сегмента. В этом случае компоновщик сам проверяет адреса. |
Автор: | SgtPepper_91 [ Пт апр 23, 2021 13:50:58 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
Я вот тоже хочу научиться понимать и работать с линкером. Задача такая - есть кастомный бутлоадер и есть основное приложение. Как это делать и как отредактировать линкер скрипты для обеих частей в интернете разжёвано. Моя пробелма - и загрузчик, и приложение использую ST-шную библиотеку для криптографии STM32cryptographic...GCC.a. Вернее используют они конечно не всю библиотеку целиком, а только несколько функций. Как можно сделать, чтобы эти функции при линковке загрузчика ложились в определенное место в прошивке, а в приложении эти функции подхватывались из того же места? |
Автор: | Professor Chaos [ Пт апр 23, 2021 18:31:11 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
Использовать одни и те же данные или код и в бутлоадере и в основной прошивке плохая идея. В идеале, они должны быть абсолютно независимыми и не связанными. Единственное, что бутлоадер может знать об основной программе - её начальный адрес. Не более. А вот основная программа о бутлоадере не должна знать ничего. Вообще. Для неё его не существует. |
Автор: | SgtPepper_91 [ Пт апр 23, 2021 18:49:49 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
Использовать одни и те же данные или код и в бутлоадере и в основной прошивке плохая идея. В идеале, они должны быть абсолютно независимыми и не связанными. Единственное, что бутлоадер может знать об основной программе - её начальный адрес. Не более. А вот основная программа о бутлоадере не должна знать ничего. Вообще. Для неё его не существует. В иделе да, но на секундочку давайте предположим, что я знаю, что делаю (всего лишь не знаю, как ![]() Чёрт, да это уже на уровне практикума интересно попытаться реализовать. |
Автор: | Professor Chaos [ Пт апр 23, 2021 19:20:01 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
В соседней теме я уже давал ссылку на эту книжку. Вам она тоже пригодится. Тут глава про загрузчик, а в конце её раздел про API загрузчика. Это то, что вам нужно. Но я бы все-таки создал константную структуру указателей во флеш-памяти загрузчика по фиксированному адресу, который будет известен основной программе. Она будет обращаться к ней по этому адресу и вычитывать из неё указатели на нужные функции. Это избавит от траты ОЗУ. |
Автор: | SgtPepper_91 [ Пт апр 23, 2021 20:19:51 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
В соседней теме я уже давал ссылку на эту книжку. Вам она тоже пригодится. Тут глава про загрузчик, а в конце её раздел про API загрузчика. Это то, что вам нужно. Но я бы все-таки создал константную структуру указателей во флеш-памяти загрузчика по фиксированному адресу, который будет известен основной программе. Она будет обращаться к ней по этому адресу и вычитывать из неё указатели на нужные функции. Это избавит от траты ОЗУ. Ок, почитаю на выхах. Пока мне удалось только положить функцию в загрузчике в отдельную секцию, её адрес - 0x080000c0. это void func(void) {}; но вызвать её из основной программы не получается, вызываю вот так ((void(*)(void))0x080000c0)(); Это было слишком наивно видимо ![]() |
Автор: | Professor Chaos [ Пт апр 23, 2021 20:59:19 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
И как? Работает или нет? Обратите внимание на эту страничку. Там сказано, чему должен быть равен младший бит в адресе перехода при вызове функции. А у вас он какой? Как адрес секции - с нулем в младшем бите? 1. Объявите тип структуры с полями-указателями на функции с требуемой сигнатурой. 2. В загрузчике создайте такую константную структуру, инициализируйте её указателями на требуемые функции, и поместите её в RO-секцию по фиксированному адресу. 3. В основной программе объявите константный указатель на эту структуру и инициализируйте его её фиксированным адресом. 4. В основной программе вызывайте функции, обращаясь к полям этой структуры через указатель на неё. |
Автор: | YS [ Вс апр 25, 2021 16:47:03 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
Цитата: Как можно сделать, чтобы эти функции при линковке загрузчика ложились в определенное место в прошивке, а в приложении эти функции подхватывались из того же места? Я не великий гуру линкер-скриптинга, но предположу. В скрипте компоновщика пишем: Код: MEMORY { ... SHARED_CODE_REGION (rx) : ORIGIN <где должен начинаться разделяемый код>, LENGTH = <сколько выделим под разделяемый код> } ... .shared_code_section: { __shared_code_start__ = .; KEEP(*(.shared_code)); } > SHARED_CODE_REGION; Функции объявляем так: Код: <тип> function(...) __attribute__ ((section(".shared_code"))) { ... } В той программе, которая эти функции должна только использовать, но не содержать, к секции .shared_code_section добавляем атрибут NOLOAD. |
Автор: | Professor Chaos [ Вс апр 25, 2021 17:16:17 ] |
Заголовок сообщения: | Re: GNU LD. Пытаюсь написать свой скрипт компоновки. |
YS писал(а): В той программе, которая эти функции должна только использовать, но не содержать, к секции .shared_code_section добавляем атрибут NOLOAD. При таком подходе существует отличная от нуля вероятность, что в двух разных проектах линкер положит функции в секцию в разном порядке. Имеет полное право тасовать их внутри секции как хочет. И тогда в программе, которая их только использует, при вызове этих функций будет неопределенное поведение. Скорее всего хардфолт или еще какой-нибудь фолт. |
Страница 1 из 1 | Часовой пояс: UTC + 3 часа |
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |