Как исполнить свой код ДО инициализации среды Arduino?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Коллеги, тут как-то обсуждался вопрос, как выполнить некий код сразу же после загрузки процессора (до инициализации среды Arduino). Кажется, вопрос был связан с отключение сработавшего WatchDog'а, который, как известно, должен быть отключён очень быстро (сколько-то микросекунд) после загрузки, а среда инициализируется дольше.

Мне тут по другому поводу понадобилось нечто подобное. Сделал и решил показать всем, как это делается.

В приведённом примере функция preInit – это САМЫЕ ПЕРВЫЕ команды процессора, которые выполняются непосредственно после Reset. До них не выполняется ничего – не инициализирован стек, не инициализирован глобальные переменные, не отработали конструкторы глобальных объектов (привет Serial'у – он ещё не «родился») - ничего!

Имейте в виду – у Вас нет ничего, даже нормального стека!

Чисто показать «первость» не могу (просто верю документации), но косвенно, смотрите:

1. переменная cat2 объявлена «как обычно» (и стало быть она будет проинициализирована нулём при общей инициализации среды), а переменная cat1 объявлена с указанием «не инициализировать».

2. В функции preInit я записываю в обе переменные ненулевые значения.

3. При печати видно, что cat1 сохранила своё значение, а cat2 стала нулём.

Вывод: функция preInit выполнялась, по крайней мере, ДО инициализации глобальных переменных.

//
//	Эксперимент на кошках!
//	Проверка функций инициализации (функций исполняемых до всего остального)
//
//	Глобальная переменная cat1 помечена как "неициализируемая", 
//	чтобы предотвратить присваивание ей нуля в секции .init4
//	cat2 описана стандартно.
//
int cat1 __attribute__ ((section (".noinit")));
int cat2;

//
//	Функция preInit будет выполняться ДО ВСЕГО
//	прямо в момент "сотворения мира" (секция .init0)
//	Однако в этот момент ещё даже не инициализирован стек!
//	Стек инициализируется в секции .init2, потому, если возможно, лучше 
//	нам использовать не .init0 как здесь, а .init3
//	тогда хоть стеком можно пользоваться.
//
void preInit (void) __attribute__ ((naked)) __attribute__ ((section (".init0")));

void preInit (void) {
	cat1 = 321;
	cat2 = 123;
}

void setup() {
  Serial.begin(115200);
  Serial.println("Test on cats");
  Serial.print("Cat #1:");
  Serial.println(cat1);
  Serial.print("Cat #2:");
  Serial.println(cat2);
}

void loop() {}

 

Sindbad
Offline
Зарегистрирован: 08.12.2015

Т.е. если записать

void preInit (void) {
	while (true);
}

Потребуется перепрошивка загрузчика?

Radjah
Offline
Зарегистрирован: 06.08.2014

ЕМНИП загрузчик как раз отработал и передал управление на код программы. Или всё это работает только без загрузчика?

Клапауций 321
Offline
Зарегистрирован: 17.12.2015

Radjah пишет:

ЕМНИП загрузчик как раз отработал и передал управление на код программы. Или всё это работает только без загрузчика?

подозреваю, что балалайка работает в загрузчике.

Logik
Offline
Зарегистрирован: 05.08.2014

Клапауций 321 пишет:

Radjah пишет:

ЕМНИП загрузчик как раз отработал и передал управление на код программы. Или всё это работает только без загрузчика?

подозреваю, что балалайка работает в загрузчике.

Не. Работает именно до загрузчика. После ресета происходит команда безусловного перехода именно на этот код. Все остальное потом. В общем прелесно,  можна вирусы писать или блокировать софт у заказчика нерасплатившегося, просто прелесно )))

Клапауций 321
Offline
Зарегистрирован: 17.12.2015

Logik пишет:

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

*откуда и когда бы это не запускалось - это записывается в секцию прикладной программы.

**проверил - на работу загрузчика пример не влияет.

Logik
Offline
Зарегистрирован: 05.08.2014

Клапауций 321 пишет:

мне это напоминает #крымнаш - кто до того запрещал писать вирусы и блокировать софт?

*откуда и когда бы это не запускалось - это записывается в секцию прикладной программы.

Блокировать после загрузчика не интересно, не мешает перезалить софт заново, залить другой софт. А до загрузчика - без программатора никак. А ведь можна так сделать запрос кода на розблокировку.

Клапауций 321 пишет:

**проверил - на работу загрузчика пример не влияет.

А впишите типа A:goto A;

 

Клапауций 321
Offline
Зарегистрирован: 17.12.2015

Sindbad пишет:

Т.е. если записать

void preInit (void) {
	while (true);
}

Потребуется перепрошивка загрузчика?

не знаю, может я что-то не так сделал

// void preInit (void) {
//  cat1 = 321;
//  cat2 = 123;
// }

void preInit (void) {
while (true);
}

почему же у меня работает загрузчик?, если всё должно повиснуть в бесконечном цикле цитирую: прямо в момент "сотворения мира" (секция .init0)

*оно виснет - дигиспарк не распознаётся системой, т.к. не грузится библиотека и не происходит инициализация в сетапе, но... прекрасно перепрошивается.

значит - волшебство происходит после загрузчика, а не до.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Sindbad пишет:

Т.е. если записать

void preInit (void) {
	while (true);
}

Потребуется перепрошивка загрузчика?

Не знаю, я ж написал, что сейчас проверить не могу, приходится просто доверять документации (сижу на вокзале с ноутбуком на коленях, нанка есть, а вот программатора - нету :( )

Атмеловская документация говорит, что что код .init0 "will be jumped into immediately after a reset".

Да и знаете, для моих нужд необходимо было кое-что сделать до запуска конструкторов глобальных объектов (мне и .init3 выше головы хватило). А делать что-то до загрузчика мне не надо. Проверьте сами и скажите нам, что получилось, ладно?

Клапауций 321
Offline
Зарегистрирован: 17.12.2015

ЕвгенийП пишет:

Атмеловская документация говорит, что что код .init0 "will be jumped into immediately after a reset".

ясно - "will be jumped into immediately after a reset" прикладной программы делает загрузчик.

*расходимся - нас, в очередной раз, наебали.

Beginner100500
Offline
Зарегистрирован: 24.07.2015

По ресету происходит прыг на адрес 0, который в зависимости от настройки фьюзов, находится или в самом начале флеша ИЛИ в начале  загрузчика. Я так перевел, с гугло-переводчиком. И где-то ещё читал про то, что загрузчик первым делом смотрит на USB и если там нет активности, то отдает управление на 0 адрес флеша.

Согласно документации на glibc (тоже поверил гуглю) в начало программы, с 0 адреса флеша, линкуются вектора прерываний и какие-то точки init.0 .. init.9, которые отвечают за начальную настройку памяти, пересылку в неё предустановленных значений глобалов, настройку классов объектов и ещё что-то, каждая за свое дело. Что именно так и не разобрался, плюнул.

Logik
Offline
Зарегистрирован: 05.08.2014

Beginner100500 пишет:

По ресету происходит прыг на адрес 0, который в зависимости от настройки фьюзов, находится или в самом начале флеша ИЛИ в начале  загрузчика. 

Да. В этом вероятно и фишка. В начале флеша стоит прыг на код ТС. Надо фюзы смотреть. 

Посмотрел. Действительно фюзы настроены на загрузчик (по крайней мере для наны и мини 328), соответственно код ТС будет после загрузчика. Жаль. Скайнет опечален )))

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Logik пишет:

Посмотрел. Действительно фюзы настроены на загрузчик (по крайней мере для наны и мини 328), соответственно код ТС будет после загрузчика. Жаль. Скайнет опечален )))

Вообще-то, мне непонятна Ваша печаль.

Никто не заявлял, про "ДО или ПОСЛЕ" загрузчика. Заявлялось, что это выпоняется "в момент сотворения мира", т.е. первым делом после Reset - когда можно, например, поуправлять созданием глобальных объектов и т.п.

Некоторые читатели вбили себе в голову (неясно с чего), что это будет до загрузчика, а когда выяснилось, что не так - посчитали. что их обманули. А обманули-то сами себя. Нехрен было вбвать себе в голову то, что никто не писал.

Logik
Offline
Зарегистрирован: 05.08.2014

Не моя печаль, скайнета ;)

Вся беда пошла от фразы:

ЕвгенийП пишет:

 функция preInit – это САМЫЕ ПЕРВЫЕ команды процессора, которые выполняются непосредственно после Reset. До них не выполняется ничего – не инициализирован стек, не инициализирован глобальные переменные, не отработали конструкторы глобальных объектов (привет Serial'у – он ещё не «родился») - ничего!

Её надо читать так:

функция preInit – это САМЫЕ ПЕРВЫЕ команды процессора, которые выполняются непосредственно после Reset и загрузчика. До них не выполняется ничего (кроме загрузчика) – не инициализирован стек, не инициализирован глобальные переменные, не отработали конструкторы глобальных объектов (привет Serial'у – он ещё не «родился») - ничего (загрузчик это не делает, а может и делает, но не так как нам надо, потому надо считать что это не сделано)!

Клапауций 321
Offline
Зарегистрирован: 17.12.2015

ЕвгенийП пишет:

Никто не заявлял, про "ДО или ПОСЛЕ" загрузчика. Заявлялось, что это выпоняется "в момент сотворения мира", т.е. первым делом после Reset - когда можно, например, поуправлять созданием глобальных объектов и т.п.

Некоторые читатели вбили себе в голову (неясно с чего), что это будет до загрузчика, а когда выяснилось, что не так - посчитали. что их обманули. А обманули-то сами себя. Нехрен было вбвать себе в голову то, что никто не писал.

какой-то противоречивый набор заявлений:
1. ТС заявляет, что у него есть чюдо-средство для платы дуино запуска кода сразу после Reset.
2. ТС-у на практике объяснили, что первым делом после Reset у платы дуино начинает работать код загрузчика.
3. ТС начинает путаться и давать взаимоисключающие показания: см. цитату выше.
*. вангую уход ТС в полный отказ.
ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Logik пишет:

Не моя печаль, скайнета ;)

Вся беда пошла от фразы:

ЕвгенийП пишет:

 функция preInit – это САМЫЕ ПЕРВЫЕ команды процессора, которые выполняются непосредственно после Reset. До них не выполняется ничего – не инициализирован стек, не инициализирован глобальные переменные, не отработали конструкторы глобальных объектов (привет Serial'у – он ещё не «родился») - ничего!

Её надо читать так:

функция preInit – это САМЫЕ ПЕРВЫЕ команды процессора, которые выполняются непосредственно после Reset и загрузчика

Нет, так её нельзя читать.Она опять будет некорректной, только с другого конца.
 
Эта фраза была переводом документации производителя, которая писалась в расчёте на то, что читатель знает матчасть и понимает о чём речь.
 
А матчасть такова: логически есть два ресета - "Application reset" и "Boot Loader Reset" (см. даташит на стр. 281). Кто из них получит управление при включении питания, зависит от состояния фьюза "Boot Reset Fuse". 
 
В данном месте документации имелся в виду именно "Application Reset" и авторы считали, что читатель это понимает. Те же кто этого не понял, стали ожидать чудес, а когда не дождались, привычно наехали на ТС.
 
Поэтому, если и нужно поменять мою фразу, то добавляя лишь одно слово, вот так: "... это САМЫЕ ПЕРВЫЕ команды процессора, которые выполняются непосредственно после Application Reset ..."
 
Вот так будет строго и абсолютно корректно для любого случая.
Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Вопрос, можно? А зачем это вообще нужно? Есть задачи, когда требуется лезть в код этих init.0 .. init9, какие? Интересно просто, особенно в применении к Wiring, поскольку у вас упор сделан "до инициализации среды Wiring" :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Arhat109-2 пишет:

Вопрос, можно? А зачем это вообще нужно? Есть задачи, когда требуется лезть в код этих init.0 .. init9, какие? 

Первый абзац первого поста читали? Или "чукча - не читатель ..."?

Клапауций 321
Offline
Зарегистрирован: 17.12.2015

Arhat109-2 пишет:

Вопрос, можно? А зачем это вообще нужно? Есть задачи, когда требуется лезть в код этих init.0 .. init9, какие? Интересно просто, особенно в применении к Wiring, поскольку у вас упор сделан "до инициализации среды Wiring" :)

не задавай глупых вопросов, а перечитай все темы ТС, которые он написал - это не для ускоренного самообразования, это для выноса мозга желтушным заголовком и умолчания деталей.

*не знаю для кого он это пишет - для себя, наверное.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

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

Хотелось бы вместо оскорблений увидеть живой пример где это реально требуется, а не очередной "шедевр эквилибристики".

Logik
Offline
Зарегистрирован: 05.08.2014

Возможно таким путем можно получить информацию была ли попытка обновления прошивки и  успешна ли она. 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Arhat109-2 пишет:

Читаю темы, просматриваю .. не попадалось.

Тем и вопросов о том, что watchdog необходимо выключить при загрузке, а в setup - уже поздно, было много. Например:

Из того, Вам они не попадались, вовсе не следует, что их не было. 

Там предлагалось лечение путём установки нестандартного загрузчика. Здесь другой рецепт.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Logik пишет:

Возможно таким путем можно получить информацию была ли попытка обновления прошивки и  успешна ли она. 

Как? Есть идеи как это сделать?

Клапауций 321
Offline
Зарегистрирован: 17.12.2015

Arhat109-2 пишет:

Да, читал .. "вот тут спрашивали" .. 

здесь спрашивали, но походу у хабражителя ничего не вышло

http://geektimes.ru/post/255800/

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Судя по статье вопрос гарантированно решается перекомпиляцией и перепрошивкой загрузчика, а вовсе не предложенным способом. Я не работал ещё с watchdog .. хотя, возможно в ближайшем будущем и понадобится. У нас идет активная подготовка к февральским соревнованиям, хотим таки туда попасть .. а там потребуется переводить контроллер в спящий режим, хотя бы на время его стояния в карантинах, дабы не жрать батарейки. Разберусь - отпишусь, если интересно. Но вряд ли потребуется лезть в init секции.

Клапауций 321
Offline
Зарегистрирован: 17.12.2015

Arhat109-2 пишет:

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

в той статье из интересного этот код

uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) __attribute__((naked)) __attribute__((section(".init3")));
void get_mcusr(void){
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

Arhat109-2 пишет:

Я не работал ещё с watchdog .. хотя, возможно в ближайшем будущем и понадобится. У нас идет активная подготовка к февральским соревнованиям, хотим таки туда попасть .. а там потребуется переводить контроллер в спящий режим, хотя бы на время его стояния в карантинах, дабы не жрать батарейки. Разберусь - отпишусь, если интересно. Но вряд ли потребуется лезть в init секции.

если будет интересно, то лучше посмотри и напиши компилляцию на тему "Application reset, который делает bootloader" или, подозреваю, не делает.

-NMi-
Offline
Зарегистрирован: 20.08.2018

Понимаю, шо пост стар, но, хотелось бы узнать поболее...
В первой строке мы определили переменную в некой области .noinit
Потом прототип функции с параметрами .init3
Потом сама функция берет mcusr и сохраняет в переменной, верно??

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

nik182
Offline
Зарегистрирован: 04.05.2015

-NMi- пишет:
... Почему спрашиваю - на стм32 и прочих нельзя просто так взять и обратиться к любой ячейки памяти, будет ошибка и зависон проца...

А Вы с какой целью интересуетесь? По крайней мере в stm32 чтение доступной в рамках МК памяти ни разу не приводило к зависаниям. Там же линейная модель. Всё адресное пространство поделено между всей периферией и памятью. Если читаешь существующую ячейку, неважно к чему относящуюся, ответ всегда будет.   

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

-NMi- пишет:
на стм32 и прочих нельзя просто так взять и обратиться к любой ячейки памяти, будет ошибка и зависон проца. Про авр -там пофигу.
Отнюдь.

Если по этим адресам памяти будет что-либо расположено, реакция последует вполне адекватная.

А вот если Вы попытаетесь обратиться к тем адресам, по которым ничего нет (а это совсем не сложно: из 4 Гигов адресного пространства ARM в МК используется от десятков Кбайт до единиц Мбайт), то контроллер вполне обоснованно решит, что программа невменяема и перекроет ей кислород.

b707
Offline
Зарегистрирован: 26.05.2017

andriano пишет:

А вот если Вы попытаетесь обратиться к тем адресам, по которым ничего нет (а это совсем не сложно: из 4 Гигов адресного пространства ARM в МК используется от десятков Кбайт до единиц Мбайт), то контроллер вполне обоснованно решит, что программа невменяема и перекроет ей кислород.

выглядит как досужие рассуждения бабок у подьезда... простите

По моему контроллер ничего не "решает".  Ему пофиг, вменяема программа или нет.

На мой взгляд, чтение любой ячейки может привести только к трем результатам - получения реального значения, получения "пустышки". определенной где-то в дефолтах и исключению. То есть вопрос упирается лишь в то - генерируется ли исключение в arm-eabi или где там еще при обращении к памяти по нераспределенному адресу. Честно, я не знаю ответа, но из общих соображений - с чего бы возникать исключению при чтении? - уж хотя бы при записи, и то понятнее

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

b707 пишет:

Честно, я не знаю ответа, но из общих соображений - с чего бы возникать исключению при чтении? - уж хотя бы при записи, и то понятнее

Зачем "общие соображения", если намного проще узнать экспериментально?

rkit
Offline
Зарегистрирован: 23.11.2016

andriano пишет:

 намного проще узнать экспериментально?

Протестировать ВСЕ ВОЗМОЖНЫЕ СОСТОЯНИЯ КОНТРОЛЛЕРА - это проще теперь?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Кто говорит обо всех возможных состояниях?

Нужно проверить лишь одно - обращение за пределы используемой части адресного пространства.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

andriano пишет:

Кто говорит обо всех возможных состояниях?

Нужно проверить лишь одно - обращение за пределы используемой части адресного пространства.

должно возвращать FF по идее

rkit
Offline
Зарегистрирован: 23.11.2016

andriano пишет:

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

всего-то пару миллиардов адресов, ага

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

rkit пишет:

andriano пишет:

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

всего-то пару миллиардов адресов, ага

Тем более!

Из пары миллиардов адресов* выбрать один ничуть не сложнее, чем из дюжины.

* На самом деле либо из 4-х миллиардов, либо 1-го миллиарда.

__Alexander
Offline
Зарегистрирован: 24.10.2012

А что мешает прочитать последнюю ячейку из 4Гб и посмотреть что выйдет?

x = flash_read(0xFFFFFFFB);
uart_put_string("Не зависло");
for(;;){}


unsigned long flash_read(unsigned long address) 
	{
  return (*(__IO unsigned long*) address);
  }

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Ну вот, уже и номер ячейки выбран. ))))

(только, мне кажется, должно быть 0xfffffffc)

__Alexander
Offline
Зарегистрирован: 24.10.2012

andriano пишет:

Ну вот, уже и номер ячейки выбран. ))))

(только, мне кажется, должно быть 0xfffffffc)

Та то не важно, в любом случае будет Hard Fault. А не, обманул. При таком адресе всё нормально. Возвращает нули, по крайней мере симулятор.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Посмотрим, как ведет себя скетч при выходе за границы оперативной памяти (BluePill):

int interval = 500;

void setup() {
  Serial.begin(115200);
  while(!Serial){};
  Serial.println("Start");
}

void loop() {
  static unsigned int previousMillis = millis();
  if (millis() - previousMillis > interval) {
    previousMillis = millis();
    static int n; // переменная в RAM
    static unsigned int * b = (unsigned int*)((unsigned int)&n & 0xffff0000u);// вычисляем начало RAM
    Serial.print(n);
    Serial.print("k\t");
    Serial.println(*b);
    b += 256; // проверяем RAM через каждый кбайт (1кбайт - это 256 int)
    n++;
  }
}

результат:

Start
0k	0
1k	4
2k	0
3k	536873972
4k	0
5k	0
6k	0
7k	1663247104
8k	3641737215
9k	509413376
10k	730507721
11k	3398111176
12k	3135832682
13k	3661211306
14k	3907066949
15k	3148395146
16k	2919408233
17k	2713856942
18k	2720611131
19k	2960827058
20k	

Т.е. программа доходит до 20-й итерации и в строке 17 прекращает работу.

А после нее продолжает работу уже совсем другая программа, которая мигает светодиодом примерно 3-4 раза в секунду.