Использование функций указателя - 2 отдельных приложения на 1 устройстве

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

На моем Cortex M4 у меня есть 2 отдельных приложения - загрузчик и пользовательское приложение. Теперь у меня было несколько (много) функций, которые были одинаковыми для обоих приложений. Поэтому я скомпилировал их только для загрузчика, а затем создал массив указателей на функции по указанному адресу, который известен пользовательскому приложению. Поэтому в приложении я не собирал файлы с этими функциями снова, но использую эти указатели всякий раз, когда это необходимо.

Это пример кода, который я попытался сделать общим для обоих приложений:

static uint8_t       m_var_1;

// Sends events to the application.
static void send_event(fs_op_t const * const p_op, fs_ret_t result)
{
    uint8_t var_2;
    [...]
}

Мое приложение заканчивается в Hardfault, что происходит, например, при делении на ноль или использовании указателя на функцию со значением NULL. Я не уверен почему, но я начал задаваться вопросом, что происходит с этими переменными. var_2 наверняка будет расположен в стеке, так что это не проблема. Но что насчет m_var_1? В файле карты он имеет указанное место в оперативной памяти. Но у меня нет отдельных разделов RAM для приложения и загрузчика. Я не уверен, но у меня есть ощущение, что эта переменная может использовать то же место в оперативной памяти, что и при создании загрузчика. Это возможно? Может быть, другие проблемы?

1 ответ

Решение

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

В вашем приложении переменная, даже если она там существует, скорее всего, будет находиться по другому адресу. Вызов функций происходит, потому что они находятся в ПЗУ и не могут быть разными для приложения и загрузчика. Вызов их через константные указатели, которые также хранятся в ПЗУ, обходит проблему.

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

В противном случае вы будете ненавидеть делать следующее.

Часть 1, настройка:

  • ввести специальный раздел компоновщика со всеми переменными, доступными как системным папкам (приложение и загрузчик)
  • пусть один линкер заполнит его
  • установите его для другого компоновщика как не-tocuh
  • будьте осторожны с инициализацией
    • желательно не принимать значение инициализации
    • если вам нужна инициализация, например, "bss" (init до 0) или "data" (init до указанного значения),
      сделайте это явно в начале системной части, которая не связана с компоновщиком, который вы разрешаете устанавливать переменные
    • в целях безопасности рекомендуется выполнять инициализацию одинаково в обеих частях системы.
    • "data" init использует специальный раздел энергонезависимого компоновщика с копией инициализируемых переменных, доступ к которым возможен

Часть 2, доступ:

  • Опция 1)
    хранить константные указатели на эти переменные, как вы делали для функций
  • вариант 2)
    получить второй компоновщик (другой, который не выполнил фактическую настройку секции общих переменных), чтобы создать идентично структурированный и идентично расположенный раздел как секцию из первого компоновщика; необходимо больше изучения вашего линкера

Часть 3, восстановление значений, хранящихся в другой системной части
(например, вы хотите оставить какое-то сообщение от загрузчика, чтобы прочитать мое приложение)

  • спроектировать, какая системная часть инициализирует какую переменную, другая только читает их
  • разделите общие переменные на четыре части,
    • записывается и читается обеими частями системы, инициализируется обеими
    • написанный и прочитанный x, только прочитанный y, инициализированный x
    • написанный и прочитанный y, только прочитанный x, инициализированный y
    • записано обеими частями системы, не инициализировано, использует контрольные суммы и проверки достоверности,
      если переменная не была инициализирована, init по умолчанию
  • инициировать каждый раздел только в соответствующей части системы записи
  • настроить как "без инициализации" в другом компоновщике
  • установить как "без инициализации" в обоих компоновщиках для четвертого случая
  • использовать геттеры и сеттеры с обновлением контрольной суммы и правдоподобием для четвертого случая

Чтобы сделать все это, требуется тщательное изучение возможностей и синтаксиса компоновщика.
Поэтому я рекомендую не пытаться, если вы можете обойти это. Рассмотрите возможность использования существующего симулятора файловой системы; потому что это в основном то, что выше означает.

Другие вопросы по тегам