Кто любит RISC в жизни, заходим, не стесняемся.
Ответить

GNU LD. Пытаюсь написать свой скрипт компоновки.

Пт мар 26, 2021 21:55:34

Здравствуйте, коллеги!

Я решил поучиться работать с GNU LD. Начать решил с малого: в стандартный файл скрипта для STM32L152RE, который идет в комплекте с EmBitz, добавить секции для размещения данных в EEPROM (по умолчанию их там нет).

Посмотрите, пожалуйста, я все правильно сделал? :)



Судя по тому, что я наблюдаю в выходном бинарнике, все работает.

Проверял, объявляя массив вот так:

Код:
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));
        }



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

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Пт мар 26, 2021 23:43:14

YS писал(а):зачем при объявлении секции .data используется атрибут AT?
Потому что данные для ОЗУ, но хранятся во флеше.
https://ftp.gnu.org/old-gnu/Manuals/ld- ... /ld_3.html

YS писал(а):EEP1 (rw) : ORIGIN = 0x08080000, LENGTH = 8K
EEP2 (rw) : ORIGIN = 0x08082000, LENGTH = 8K
EEPROM в STM32 поддерживает прямую запись?

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Сб мар 27, 2021 10:18:18

Спасибо. :beer:

Кроме всего прочего, я тут нашел замечательную публикацию, которая мне многое пояснила.

Для тех, кто найдет эту тему поиском, напишу, в чем дело.

Как обычно, всему виной историческая терминология. Там есть два термина - 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 поддерживает прямую запись?


Да. Сначала надо разблокировать запись в специальном регистре, и дальше можно писать по указателю. Ну а в нашем случае, если переменная расположена в новой секции, можно будет писать как в обычную переменную.

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Сб мар 27, 2021 11:09:53

Оно конечно будет работать, но радости не принесёт.
Размечать EEPROM как область памяти - означает позволить компилятору самому назначить место хранения переменных. Ну в том смысле что он будет там всё оптимизировать и складывать в аккуратную кучку. Это удобно, но не долговечно.

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Чт апр 08, 2021 23:17:14

Более того - все время работать с разблокированным на запись EEPROM не очень удачная идея с точки зрения сохранности данных в нем. С всем что флеша касается вообще работают обычно так: разблокировали - записали что хотели - заблокировали обратно. Иначе записанного можно однажды лишиться при отклонениях от идеала.

И это, при рестарте переменные инициализируются же. Это чего, каждый ребут будет цикл еепром давать? Или оно как uninitialized идет? А как вы начальные значения пропишете только 1 раз? И вообще как оно будет non-volatile при этом, если так в лоб? Можно const'ами обвесить, но записывать как? :)

Кроме того - доступ на запись может bus stall на довольно почтенное время. Вам совсем плевать на реальное время и вы готовы с неконтролируемым (вами явно) тупняком чипа от такого доступа жить? Флеш так то довольно долго пишется (по микроконтроллерным меркам) и в это время модуль флеша не может код прошивки отдавать. В 2-банковых чипах, конечно, не занятый записью банк дееспособен пока другой пишется - но этим надо явно заморочиться, положив код в один и поюзав еепром в другом.

Ну и если хочется видеть это напрямую в памяти - например, завести typedef struct config какой-нибудь, переменную (-ые) которые заявлены - как _указатели_ на это, и по мере желания можно указатель назначать куда там охота (ram vs eeprom) например (но осторожно, указатели они такие). На еепром лучше указывать с полями const везде, во избежание. А записывать чем-то типа memcpy этого (из версии в RAM) - сняв лок, записав как надо, вернув лок - и вызывается в момент когда ОК что чип может довольно долго тупануть и за это ничего не будет. На 2-банковом в принципе можно код в 1 банк, еепром в другой, тогда это не проблема вроде, но потребует специального внимания.

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Чт апр 15, 2021 14:59:52

все время работать с разблокированным на запись EEPROM не очень удачная идея с точки зрения сохранности данных в нем.


С чего вы взяли, что я буду так делать? :)

И это, при рестарте переменные инициализируются же.


Инициализируются только переменные из сегмента .data. Для этого в startup-коде, который по умолчанию добавляется при компоновке, есть специальный цикл. Для всех остальных сегментов никакой инициалиации по умолчанию нет.

А как вы начальные значения пропишете только 1 раз?


То, что помещено в заданные сегменты, попадает в образ прошивки по соответствующим адресам и, соответственно, записывается программатором при прошивке чипа. В этом и есть прелесть задания области сегментом в скрипте компоновщика.

И вообще как оно будет non-volatile при этом, если так в лоб?


Эта область памяти non-volatile аппаратно.

Кроме того - доступ на запись может bus stall на довольно почтенное время. Вам совсем плевать на реальное время и вы готовы с неконтролируемым (вами явно) тупняком чипа от такого доступа жить?


FLASH находится в другом банке относительно EEPROM. Кроме того, запись туда имеет смысл производить только при сохранении настроек, например.

но осторожно, указатели они такие


Именно от этой проблемы избавляет введение специального сегмента. В этом случае компоновщик сам проверяет адреса.

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Пт апр 23, 2021 13:50:58

Я вот тоже хочу научиться понимать и работать с линкером. Задача такая - есть кастомный бутлоадер и есть основное приложение. Как это делать и как отредактировать линкер скрипты для обеих частей в интернете разжёвано. Моя пробелма - и загрузчик, и приложение использую ST-шную библиотеку для криптографии STM32cryptographic...GCC.a. Вернее используют они конечно не всю библиотеку целиком, а только несколько функций. Как можно сделать, чтобы эти функции при линковке загрузчика ложились в определенное место в прошивке, а в приложении эти функции подхватывались из того же места?

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Пт апр 23, 2021 18:31:11

Использовать одни и те же данные или код и в бутлоадере и в основной прошивке плохая идея. В идеале, они должны быть абсолютно независимыми и не связанными. Единственное, что бутлоадер может знать об основной программе - её начальный адрес. Не более. А вот основная программа о бутлоадере не должна знать ничего. Вообще. Для неё его не существует.

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Пт апр 23, 2021 18:49:49

Использовать одни и те же данные или код и в бутлоадере и в основной прошивке плохая идея. В идеале, они должны быть абсолютно независимыми и не связанными. Единственное, что бутлоадер может знать об основной программе - её начальный адрес. Не более. А вот основная программа о бутлоадере не должна знать ничего. Вообще. Для неё его не существует.

В иделе да, но на секундочку давайте предположим, что я знаю, что делаю (всего лишь не знаю, как :D). Мы говорим о МК, и хотя сегодня они уже тоже дают много памяти, хранить вещи два раза - можно и обойтись.

Чёрт, да это уже на уровне практикума интересно попытаться реализовать.

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Пт апр 23, 2021 19:20:01

В соседней теме я уже давал ссылку на эту книжку. Вам она тоже пригодится. Тут глава про загрузчик, а в конце её раздел про API загрузчика. Это то, что вам нужно.
Но я бы все-таки создал константную структуру указателей во флеш-памяти загрузчика по фиксированному адресу, который будет известен основной программе. Она будет обращаться к ней по этому адресу и вычитывать из неё указатели на нужные функции. Это избавит от траты ОЗУ.

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Пт апр 23, 2021 20:19:51

В соседней теме я уже давал ссылку на эту книжку. Вам она тоже пригодится. Тут глава про загрузчик, а в конце её раздел про API загрузчика. Это то, что вам нужно.
Но я бы все-таки создал константную структуру указателей во флеш-памяти загрузчика по фиксированному адресу, который будет известен основной программе. Она будет обращаться к ней по этому адресу и вычитывать из неё указатели на нужные функции. Это избавит от траты ОЗУ.

Ок, почитаю на выхах.

Пока мне удалось только положить функцию в загрузчике в отдельную секцию, её адрес - 0x080000c0. это void func(void) {}; но вызвать её из основной программы не получается, вызываю вот так ((void(*)(void))0x080000c0)(); Это было слишком наивно видимо :) буду читать

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Пт апр 23, 2021 20:59:19

И как? Работает или нет?
Обратите внимание на эту страничку. Там сказано, чему должен быть равен младший бит в адресе перехода при вызове функции. А у вас он какой? Как адрес секции - с нулем в младшем бите?

1. Объявите тип структуры с полями-указателями на функции с требуемой сигнатурой.
2. В загрузчике создайте такую константную структуру, инициализируйте её указателями на требуемые функции, и поместите её в RO-секцию по фиксированному адресу.
3. В основной программе объявите константный указатель на эту структуру и инициализируйте его её фиксированным адресом.
4. В основной программе вызывайте функции, обращаясь к полям этой структуры через указатель на неё.

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Вс апр 25, 2021 16:47:03

Как можно сделать, чтобы эти функции при линковке загрузчика ложились в определенное место в прошивке, а в приложении эти функции подхватывались из того же места?


Я не великий гуру линкер-скриптинга, но предположу.

В скрипте компоновщика пишем:

Код:
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.

Re: GNU LD. Пытаюсь написать свой скрипт компоновки.

Вс апр 25, 2021 17:16:17

YS писал(а):В той программе, которая эти функции должна только использовать, но не содержать, к секции .shared_code_section добавляем атрибут NOLOAD.

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