|
LPC2888, Data Abort exception, при выполнении кода из Flash |
|
|
|
Jan 10 2011, 07:27
|

Участник

Группа: Участник
Сообщений: 66
Регистрация: 6-11-09
Из: г. Омск
Пользователь №: 53 464

|
Прежде всего позлорадствую. Я не удивлен подобного рода проблемой, потому как мне и моим коллегам эта модель сразу дико не понравилась. Она особенная в семействе LPC2000, эдакий уродец. Все его отличия только отрицательные (они в полной мере описаны в этом аппноуте), единственный плюс это энергопотребление, по которому он и был выбран (не нами, естественно). Проблема возникла, когда размер проекта достиг порядка 2000 строк кода. Причем "охват" использования ресурсов МК небольшой: прерывания не используются, код линейный, из периферии используется только GPIO, LCD контроллер, ну и CGU для тактирования последнего. Компилятор GCC (Yagarto). Оптимизация кода отключена. Отлаживаю J-Link'ом (через GDB Server), драйвера и прошивка последние. Cache и memory remap не используются, в j-linke также отключено всяческое кеширование. Из RAM код выполняется замечательно, но из Flash вдруг где-то посередине (на дисплее половина интерфейса уже отрисовалась) процессор вываливается в Data Abort. Причем происходит это даже не в какой то ответсвенный момент работы с регистрами периферии или еще чего-то там, а в в процессе выполнения безобидной функции. Вот она: Код 10401878 <_Z14get_menu_childP18menu_item_header_th>: menu_item_header_t* get_menu_child(menu_item_header_t* menu, uint8_t i) { 10401878: e52db004 push {fp}; (str fp, [sp, #-4]!) 1040187c: e28db000 add fp, sp, #0 10401880: e24dd00c sub sp, sp, #12 10401884: e50b0008 str r0, [fp, #-8] 10401888: e1a03001 mov r3, r1 1040188c: e54b3009 strb r3, [fp, #-9] return (menu_item_header_t *)((uint8_t *)(menu) + *((uint16_t *)((uint8_t *)(menu) + sizeof(menu_item_header_t) + 2*(i)))); 10401890: e51b2008 ldr r2, [fp, #-8] 10401894: e55b3009 ldrb r3, [fp, #-9] 10401898: e2833001 add r3, r3, #1 1040189c: e1a03083 lsl r3, r3, #1 104018a0: e0823003 add r3, r2, r3 104018a4: e1d330b0 ldrh r3, [r3] <--- выполнение этой инструкции приводит к возникновению data abort exception 104018a8: e51b2008 ldr r2, [fp, #-8] 104018ac: e0823003 add r3, r2, r3 } 104018b0: e1a00003 mov r0, r3 104018b4: e28bd000 add sp, fp, #0 104018b8: e8bd0800 pop {fp} 104018bc: e12fff1e bx lr Я поставил брикпойнт на эту инструкцию. В регистре R3 на этот момент находится вполне валидный адрес из области flash. Просматриваю память по этому адресу, все шикарно читается, значение показывает то самое, которое и должно быть там (это значение я узнал, выполнив до этого код в RAM'е). Ну что делать ? Наверно, надо как-то вмешаться, возможно я сброшу какой-нить там кеш или конвейр комманд своим действием. Беру и меняю адрес в R3 на другой произвольно взятый из области flash. Делаю шаг, все равно вываливается, ну и ладно, чуда не произошло. Начинаю все сначала, и на этот раз меняю его на адрес из RAM. Вуаля, инструкция выполняется успешно ! Но мне то надо, чтобы код выполнялся так, как он написан. И пришлось мне оказать помощь процу, взяв выполнение инструкции "ldrh r3, [r3]" на себя, раз уж он не справляется, благо это меня не сильно обременило, потому как эта функция всего два раза вызывается. Итак, что я сделал. Проц останавливается про брикпоинту на этой инструкции. Я ручками беру адрес из R3, по этому адресу из памяти запрашиваю значение, запихиваю это значение в R3, в регистр PC запихиваю значение 0x104018a8 (перепрыгиваю через злосчастную инструкцию), говорю отладчику продолжить выполнять код дальше. Остановка происходит на втором вызове, я повторяю те же действия. В результате код выполняется до конца, на дисплее тот же результат, какой был при выполнении из RAM. Какие есть соображения по этому поводу ? P.S. За startup-код и линкер-скрипты ручаюсь. P.S.2. Я портировал код на IAR, и то же самое повторилось в этой же функции, но уже при выполнении из RAM.
Сообщение отредактировал artymen - Jan 10 2011, 07:32
--------------------
"Сознание своего несовершенства приближает к совершенству" Гёте
|
|
|
|
|
Jan 10 2011, 09:52
|
Участник

Группа: Участник
Сообщений: 50
Регистрация: 5-07-08
Пользователь №: 38 757

|
Выравнивание? Чему равен младший бит регистра R3 в команде, которая вызывает Data Abort?
|
|
|
|
|
Jan 11 2011, 01:27
|

Участник

Группа: Участник
Сообщений: 66
Регистрация: 6-11-09
Из: г. Омск
Пользователь №: 53 464

|
Ну точно ! Сейчас прочитал, что архитектура требует: "Halfwords must be aligned to 2-byte boundaries". В обоих случаях у меня невыровненный адрес ! Я так понимаю, поведение процессора тут зависит от реализации. Видимо, в других МК самое худшее к чему это приводит, это к усложеннной процедуре доступа к памяти и уменьшению производительности, однако здесь это обрубили абортом. Выходит, я выявил весьма скрытый баг компилятора. Он должен был оценить, что не может узнать заранее, какое выравнивание получится в этом сложном выражении, а потому использовать побайтовый доступ (LDRB), даже несмотря на политику оптимизации (которая, вобщем-то, отключена, но я склонен полагать, что он все равно на каком то минимальном уровне оптимизирует). И что можно сделать в такой ситуации ?  P.S. Об этом, оказывается, уже написали здесь: http://gcc.gnu.org/ml/gcc/2000-05/msg00683.html Еще в 2000 году. Видимо, вопрос либо остался без внимания разработчиков, либо они его победили, но спустя много лет баг вернулся
Сообщение отредактировал artymen - Jan 11 2011, 01:30
--------------------
"Сознание своего несовершенства приближает к совершенству" Гёте
|
|
|
|
|
Jan 11 2011, 02:45
|

Участник

Группа: Участник
Сообщений: 66
Регистрация: 6-11-09
Из: г. Омск
Пользователь №: 53 464

|
А чей же ? Неужто это я должен заботиться об архитектурно-специфичном выравнивании, создавая программу на языке высокого уровня ?! Я считаю, что компилятор должен брать эту ответственность на себя. Я же, как программист, не отказываюсь, например, от ответственности за выход за пределы памяти, которым грозит использование указателей.
--------------------
"Сознание своего несовершенства приближает к совершенству" Гёте
|
|
|
|
|
Jan 11 2011, 05:07
|

Участник

Группа: Участник
Сообщений: 66
Регистрация: 6-11-09
Из: г. Омск
Пользователь №: 53 464

|
Нет, не должен ! Вот хотя бы одна объективная причина (а их немало в эмбеддед кодинге, я думаю). Памяти в МК мало, поэтому я напрямую работаю с файлом шрифта, загруженным во флеш при программировании. А все внутренние структуры шрифта упакованы, а таблицы 8битные нечетного размера и т.д., то есть выравнивания никакого и в помине нету. Я пыхтел, разрабатывая свой собственный ультракомпактный формат шрифта, чтобы запихать его в мизерную память. И что в итоге получилось ? Капризный компилятор, видите ли, забил на проверку выравнивания. Ему, видите ли, вломы было разбить 16-битный доступ на два 8-битных. И теперь я должен переделывать формат файла, выравнивая данные в нем, то есть переделывать и усложнять код генератора шрифта, да еще и размер файла увеличивать ! И все для того, чтобы программа заработала конкретно на LPC2888. А, к примеру, на LPC2106 она уже будет не так работать. Согласитесь, это бред. Разработчики gcc, я думаю, согласились, и предусмотрели это, введя опции -m(no-)short-load-bytes, однако позже концепция была переделана в -m(no-)alignment-traps. Но мне несказанно повезло с текущей сборкой Yagarto. Версия gcc как раз пришлась ровно на этот перелом. В ней нету ни первой (уже устаревшей) опции, и новой (еще не появившейся) опции  UPDATE: Похоже ни той, ни другой опции больше вообще нету и не будет. Замечательно, что тут еще сказать.
Сообщение отредактировал artymen - Jan 11 2011, 05:24
--------------------
"Сознание своего несовершенства приближает к совершенству" Гёте
|
|
|
|
|
Jan 11 2011, 05:40
|

Шаман
     
Группа: Модераторы
Сообщений: 3 064
Регистрация: 30-06-04
Из: Киев, Украина
Пользователь №: 221

|
artymen, не кипятитесь. Послушайте ещё раз и внимательно. Компилятор поступает абсолютно правильно в плане выравнивания, когда сам располагает переменные в памяти и обращается к ним сообразно их типу. В Вашем же случае Вы его насильно просите рассматривать байтовые переменные (которые он вполне по праву разместил в памяти без выравнивания) как словные (полусловные). Обращение к словным переменным компилятор осуществляет как к выровненным, ибо на момент компиляции их выравнивание неизвестно. Раз так, то именно на Ваши плечи ложится задача обеспечить выравнивание в рантайме. Кроме выравнивания в Вашем фрагменте есть ещё один нюанс непереносимости. Вы полагаетесь на вполне определённое расположение байтов в полуслове. Поэтому Ваше выражение (uint16_t *)((uint8_t *)(menu) может вызвать вполне справедливые глюки при переносе на платформу с другой эндианностью. Как правило, следование хорошему стилю программирования не вызывает нареканий на компилятор.
PS Простейшее, что можно сделать в Вашем случае, это сделать длину объекта menu_item_header_t кратной двум. Если же Вы озабочены компактностью данных, то лучше работать через промежуточные переменные нужной длины.
|
|
|
|
|
Jan 11 2011, 06:20
|

Участник

Группа: Участник
Сообщений: 66
Регистрация: 6-11-09
Из: г. Омск
Пользователь №: 53 464

|
Я прекрасно это понимаю. И, как я сейчас выяснил, компилятор тоже прекрасно это понимает и выдает предупреждение, включаемое опцией -Wcast-align (у меня почему то не выдавал несмотря на -Wall, пока я не указал опцию явно). Просто я всего-навсего говорю о том, что компилятору следует не только предупреждать, но и проявлять инициативу, а именно разбить 16-битный доступ на два 8-битных. Да, это будет немного медленнее, но зато это будет 100% РАБОТАТЬ. Или указание из ARM Architecture Reference Manual на UNPREDICTABLE memory access (прям так заглавными буквами в мануале и написано) звучит недостаточно убедительно для разработчиков gcc ? Для них приоритетнее производительность в угоду 50% вероятной неработоспособности кода ? А эндианность я предусмотрел еще в самом начале проектирования, и в общем загловочном файле даже поместил Код #ifndef __ARMEL__ #error This file should only be compiled in little endian mode #endif Тут, я должен признать, справедливый упрек, но мы не расчитываем перенести когда-либо этот проект с арма на что-нить другое. Пожалуй, мне и остается только сделать длину menu_item_header_t кратной двум, тобишь выровнять элементы структуры. К счастью, формат шрифта эти изменения не затронут, и в целом не так уж много придется вещей выравнивать, но все же очень и очень неприятно...
Сообщение отредактировал artymen - Jan 11 2011, 06:21
--------------------
"Сознание своего несовершенства приближает к совершенству" Гёте
|
|
|
|
|
Jan 11 2011, 06:34
|

Шаман
     
Группа: Модераторы
Сообщений: 3 064
Регистрация: 30-06-04
Из: Киев, Украина
Пользователь №: 221

|
QUOTE (artymen @ Jan 11 2011, 11:20)  ... что компилятору следует не только предупреждать, но и проявлять инициативу, а именно разбить 16-битный доступ на два 8-битных. Да, это будет немного медленнее ... А другой программист может возразить: "Нафига мне такая инициатива компилятора? Чего это он умничает? Мне не нужно никакого разбиения на побайтовый доступ, а нужна максимальная производительность!" И тоже будет прав. На всех не угодишь.  QUOTE (artymen @ Jan 11 2011, 11:20)  ...но мы не расчитываем перенести когда-либо этот проект с арма на что-нить другое. ... но все же очень и очень неприятно... Неприятно, согласен, но раз уж Вы подсели на армы, то придётся считаться с особенностями архитектуры. Тем не менее, кортексы - в первом приближении те же армы и с выравниванием данных у них проблем нет. И перенести не сложно.
|
|
|
|
|
Jan 11 2011, 06:44
|

Местный
  
Группа: Свой
Сообщений: 370
Регистрация: 7-11-06
Пользователь №: 22 035

|
Цитата(artymen @ Jan 11 2011, 11:07)  Нет, не должен ! Вот хотя бы одна объективная причина (а их немало в эмбеддед кодинге, я думаю). Памяти в МК мало, поэтому я напрямую работаю с файлом шрифта, загруженным во флеш при программировании. А все внутренние структуры шрифта упакованы, а таблицы 8битные нечетного размера и т.д., то есть выравнивания никакого и в помине нету. Я пыхтел, разрабатывая свой собственный ультракомпактный формат шрифта, чтобы запихать его в мизерную память. И что в итоге получилось ? ... А вот с пакованными структурами вообще надо быть очень осторожным. Компилятор генерит специальный код для доступа к полям таких структур, обращааясь к ним побайтно (к стати тут интересный момент, пакуешь структуры, чтобы сэкономить память, но из-за специфической кодогенерации объём всё равно растёт). И стоит только где-нибудь в сишном коде свалиться к обращениям по нетипизированным указателям к этим полям дата аборты полезут, как клопы из-зо всех щелей. Поэтому программист на С ОБЯЗАН отслеживать все подобные моменты в своём коде. НЕ ВЫКРУЧИВАТЬ РУКИ компилятору преобразованием типов и адресной арифметикой при работе с пакованными типами и уж точно потом не жаловаться на кривизну компилятора, если не соблюдает этих элементарных правил. Всё вышеописанное справедливо для GCC, IAR а также ARM7, CortexM3, AVR32. То что на собственной шкуре испытал. З.Ы. К стати когда то над моим постом, о том что внутри memcpy происходит дата аборт, достопочтимые гуру просто надругались. Мы докопали тему и нашли когда именно в gсс могут пойти дата аборты из memcpy. Без опции no-builtin-memcpy компимлятор не вставлял свою функцию memcpy, а каждый раз генерировал новый код, где если нет явных указаний на пакованность типов, может возникать обращение по невыровненным адресам. Цитата(artymen @ Jan 11 2011, 12:20)  Просто я всего-навсего говорю о том, что компилятору следует не только предупреждать, но и проявлять инициативу, а именно разбить 16-битный доступ на два 8-битных. Собственно говоря компилятор так и делает когда необходимо, например при пакованных структурах. А если структура обыкновенная, то компилятор СПРАВЕДЛИВО считает, что всё находится по выровненным адресам и делает чтение\запись с максимальной возможной разрядностью. И вообще баги надо начинать искать со своего кода, а не огульно обвинять компилятор в том что программа не работает.  То что код работал из RAM, судя по всему простая случайность.
|
|
|
|
|
Jan 11 2011, 07:40
|

Участник

Группа: Участник
Сообщений: 66
Регистрация: 6-11-09
Из: г. Омск
Пользователь №: 53 464

|
Эх... Значит проблема у меня на уровне системного проектирвоания. Хорошо, а как бы вы разрулили эту ситуацию, когда надо сразу с данными работать, не перекидывая из памяти в память, чтобы подвести выравнивание (а также, опционально, эндианность и т.п.) под общий знаменатель ? Цитата А другой программист может возразить: "Нафига мне такая инициатива компилятора? Чего это он умничает? Мне не нужно никакого разбиения на побайтовый доступ, а нужна максимальная производительность!" И тоже будет прав. На всех не угодишь. Я предусмотрел такой ответ  Поясню свою позицию. Эта инициатива очень и очень скромная, как невинное деяние монашки, по сравнению с многочисленными другими инициативами, которые компилятор творит в процессе оптимизации: режет переменные, рубит функции, раскидывает операции и прочий ужас, против которого даже грозный volatile не всегда спасает. Так что если "другие программисты" предпочитают производительность неработоспособности, то я склонен рассматривать это как извращение, а извращение это меньшинство, следовательно следует предусмотреть явную опцию, которая отключает инициативу, а по умолчанию (тобишь для большинства) инициатива должна быть. P.S. Структуры я запаковал затем, чтобы "унифицировать" тем самым выравнивание (большинство компиляторов, я думаю, умеет упаковывывать до 1 байта, а до произвольного числа - не факт), ну и размер сократить заодно. Цитата Собственно говоря компилятор так и делает когда необходимо, например при пакованных структурах. А если структура обыкновенная, то компилятор СПРАВЕДЛИВО считает, что всё находится по выровненным адресам и делает чтение\запись с максимальной возможной разрядностью. Я понимаю, но у меня компилятор все знает. Я не подсовываю ему непакованную структуру вместо пакованной, или наоборот. Ошибка возникла в том месте, где арифметика указателей. Цитата То что код работал из RAM, судя по всему простая случайность. Там тоже был невыровненный доступ  Но мне кажется, это недоработка со стороны аппаратной поддержки невыравниваемого доступа. Полагаю, она есть несмотря на директиву в архитектуре. По всей видимости, контроллер памяти успел провернуть трюк с двойным доступом и ротацией в RAM, а вот с флешем этот трюк уже не уложился во флешевые wait-таймы, и потому провалился по таймауту, вызвав аборт. Это всего лишь предположение, точнее очередное мое злорадство в сторону LPC2888
Сообщение отредактировал artymen - Jan 11 2011, 08:25
--------------------
"Сознание своего несовершенства приближает к совершенству" Гёте
|
|
|
|
|
Jan 11 2011, 08:04
|
.
     
Группа: Участник
Сообщений: 4 005
Регистрация: 3-05-06
Из: Россия
Пользователь №: 16 753

|
Цитата(artymen @ Jan 11 2011, 15:40)  Хорошо, а как бы вы разрулили эту ситуацию, когда надо сразу с данными работать, не перекидывая из памяти в память, чтобы подвести выравнивание (а также, опционально, эндианность и т.п.) под общий знаменатель ? В таких случаях работают с побайтовым доступом. В этом ничего плохого нет, кроме пониженной скорости доступа. Но тут выбор за программистом, либо копирование, либо без с медленным доступом. Либо ещё промежуточные варианты, но строго индивидуально в каждой ситуации. По поводу эндианности точно не могу сказать. Но мое имхо, т.к. все процы LPC имеют только little, то универсальную эндианность тоже можно сделать из побайтового доступа к переменным через дефайны. Побайтовый доступ в IAR например делается очень красиво. Это просто пакованная (по желанию побайтно или по 16 бит если достаточно по-half доступ) структура с любыми типами, даже с одним LONG. И в коде обычное обращение к полю структуры. Чистый Си, без извратов. Вместо структуры можно использовать пакованный юнион со всеми его свойствами и побайтовым доступом.
Сообщение отредактировал GetSmart - Jan 11 2011, 08:12
--------------------
Заблуждаться - Ваше законное право :-)
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|