|
|
  |
Оператор new[] и uCOS-II |
|
|
|
Jun 25 2014, 14:01
|
Участник

Группа: Участник
Сообщений: 16
Регистрация: 15-12-11
Из: Краснодар
Пользователь №: 68 865

|
Доброе время суток! Имеется контроллер ARM7-TDMI-S (LPC2214), на нем многозадачное приложение C++ с оконной графической оболочкой на основе uCOS-II и uC-GUI. Размер памяти 1 мег. Исключения не используются, STL и шаблоны используются. Компилятор используется GCC. Вообще я не большой знаток C++ и вообще работы с микрокрнтроллерами, раньше программировал на Java большие веб-порталы, базы данных и т.д. Но мне от предшественника достался уже настроенный проект и среда разработки, т.е. вноси изменения, нажимай кнопочку, и компилируй, прошивай, отлаживай и т.д. С некоторых пор, когда я сильно увеличил интенсивность использование динамической памяти, около 20-30 обращений в секунду. Программа стала виснуть примерно через 2 - 20 минут работы. На локализацию причины ушло более месяца, и то я не уверен, что локализовал верно. Начал я с алгоритма распределения памяти, от предшественника досталась папка с исходниками newlib-1.16.0. Оттуда я вытащил исходник malloc и free, интересные алгоритмы, но ничего криминального я там не нашел (спасибо китайцу, их писавшему, очень подробные комментарии оставил, а также больше количество отключаемого диагностического кода!). Как и положено, еще мой предшественник переопределил вызовы, включающие мютекс при обращении к этим функциям. Поигрался я с параметрами компиляции, походил по ним отладчиком, все вроде работает там. Но потом я заметил, что некорректные адреса иногда возвращает именно вызов new[], а не malloc, причем, new[] сам вызывает malloc, и этот malloc возвращает адреса корректные, а из new[], как правило, тоже вылетают адреса корректные, те же, что malloc вернул. Но иногда вместо них вылетают нулевые указатели, а иногда даже указатели на его собственный, этого самого new[], код (судя по таблице символов), причем, не на точку входа, а куда-то на середину кода. И вот после того, как я по этому указателю что-то пишу, становится все совсем плохо  . В общем, заменил я вызов new char[ xxx ] на malloc( xxx ), а delete[] на free(), и все вроде как заработало. Потом я вернул new[] и delete[], но стал запрещать прерывания на время их работы (макросы OS_ENTER_CRITICAL() и OS_EXIT_CRITICAL в uCOS), тоже вроде не падает (по крайней мере, еще не упало, пока я пишу это сообщение...). Вот я и не понимаю, что такого может быть в new[] и delete[], что они не работаю нормально в много задачной среде? В программе не так уж и много мест, где они вызываются. Даже оконная оболочка ими не пользуется, а обращается напрямую к malloc и free, а с ними то я проблем не нашел. STL еще, наверное, их использует, я так догадываюсь, но он тоже вызывается не так часто. Но все-таки хотелось бы иметь нормальные варианты new и delete, которые корректно конструкторы и деструкторы вызывают, ну и т.д. Правильно ли я понимаю проблему, и какие есть способы ее решить? Спасибо за ответы!
|
|
|
|
|
Jun 25 2014, 15:14
|

Ally
     
Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050

|
Цитата(aas @ Jun 25 2014, 17:01)  Правильно ли я понимаю проблему, и какие есть способы ее решить?
Спасибо за ответы! Ну что, видать грабли засели где-то в программе и кто-то портит память в связанных цепочках менеджера управления памятью. malloc и new скорее всего ни при чем. Просто они пользуются памятью из неудачного места. А может элементарно стека не хватает по задачу. Или бывает что стек соседней задачи наезжает на стек задачи которая использует new. Вообщем начать надо с контроля стеков. А лучше перейти на Cortex. Там влет с помощью JTAG можно вычислить посторонние записи в память.
|
|
|
|
|
Jun 25 2014, 15:55
|
Участник

Группа: Участник
Сообщений: 16
Регистрация: 15-12-11
Из: Краснодар
Пользователь №: 68 865

|
Цитата(AlexandrY @ Jun 25 2014, 19:14)  Ну что, видать грабли засели где-то в программе и кто-то портит память в связанных цепочках менеджера управления памятью. malloc и new скорее всего ни при чем. Просто они пользуются памятью из неудачного места.
А может элементарно стека не хватает по задачу. Или бывает что стек соседней задачи наезжает на стек задачи которая использует new. Вообщем начать надо с контроля стеков.
А лучше перейти на Cortex. Там влет с помощью JTAG можно вычислить посторонние записи в память. Напишу чуть подробнее, что происходит. Я вытащил исходники _malloc_r, _free_r и еще чего-то из этой серии (кажется, _realloc_r и еще что-то, я посмотрел по sym и map файлам, что ко мне линкуется из этих функций, их и вытащил) в файл и прикрепил к сборке. То есть я прикрепил файл, поставил в нем настройки ка для сборки в составе newlib, и открыл макросами сборку только этих функций. Т.е. вся подсистема памяти (в объеме, в котором я ее использую, включая глобальные структуры данных) теперь берется из этого файла, а не из библиотеки. Потом включил я в ней все проверки целостности структур данных, которые еще автор оставил для режима debug, добавил свои кое-какие, в общем весь файл в ассертах, если вдруг что там было бы разрушено, сразу вылетел бы ассерт (у меня он красит низкоуровневым вызовом экран красным и останавливается на точке останова). Поставил также точки останова при возврате нулевого указателя. Более того, адрес последнего выделенного блока памяти пишется в глобальную переменную, чтобы при останове посмотреть, что это было. В общем, по этой причине в корректной работе менеджера памяти у меня сомнений нет. А вот некорректность new[] я отследил уже двумя способами, во-первых, assert сработал по нулевому указателю, который оно возвращает иногда (причем, malloc, который оно вызвало, вернул указатель нормальный. Во-вторых, assert, который сработал уже во _free_r на то, что указатель не из кучи, да и вообще нечетный (хотя должен делиться на 8). Из кучи адрес или нет, отследить у меня легко: в памяти сначала идут код и статические данные, потом стеки всех задач, потом начинается куча и до конца памяти. Причем, по этому указателю лежит текст, который там и должен лежать, т.е. который я перед этим писал по указателю, который вернул мне new[]. Стеки тоже очень большие, работают от силы на треть размера P.S. А можно подробнее, какие возможности добавились в Cortex по поиску таких вот неприятных глюков?
Сообщение отредактировал aas - Jun 25 2014, 20:18
|
|
|
|
|
Jun 25 2014, 21:14
|
Участник

Группа: Участник
Сообщений: 16
Регистрация: 15-12-11
Из: Краснодар
Пользователь №: 68 865

|
Цитата(den_po @ Jun 26 2014, 00:50)  Нену, если проблема в new, почему бы не смотреть его код? Ну или просто переписать по-своему. Я пока не нашел, к сожалению, где посмотреть его код (в смысле, исходник), ну и как правильно его переписать тем более. Там какой-то старый компилятор GNU ARM, еще 2007 или 2008 года, версию сейчас не помню, завтра на работе посмотрю ее... Но меня удивляет еще другое: что могло заставить разработчиков компилятора или библиотеки сделать new[] не thread-safe, если malloc уже сделан thread-safe?
Сообщение отредактировал aas - Jun 25 2014, 21:16
|
|
|
|
|
Jun 25 2014, 23:14
|
Частый гость
 
Группа: Участник
Сообщений: 139
Регистрация: 9-11-12
Из: Санкт-Петербург
Пользователь №: 74 315

|
Цитата(aas @ Jun 26 2014, 01:14)  Я пока не нашел, к сожалению, где посмотреть его код (в смысле, исходник), libstdc++ Цитата(aas @ Jun 26 2014, 01:14)  ну и как правильно его переписать тем более Если совсем просто, то как-то так: Код void* operator new(size_t sz){ return malloc(sz); }; void* operator new[](size_t sz){ return malloc(sz); }; void operator delete(void * p){ free(p); }; void operator delete[](void * p){ free(p); }; void* operator new(size_t size, void* p){ return p; } void* operator new[](size_t size, void* p){ return p; } void operator delete (void*, void*){ } void operator delete[] (void*, void*){ } А вообще гугль перегрузка операторов new delete
|
|
|
|
|
Jun 27 2014, 05:04
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
Цитата(aas @ Jun 26 2014, 01:14)  Но меня удивляет еще другое: что могло заставить разработчиков компилятора или библиотеки сделать new[] не thread-safe, если malloc уже сделан thread-safe? new вызывает конструктор объекта, а delete - деструктор. Возможно тут порылась собака? Причем эти вызовы вставляются компилятором. Т.е. к примеру new в библиотеке опеределен просто как void* operator new[](size_t sz){ return malloc(sz); };, а когда у вас в коде написано new Test[1050], то сначала будет вызвана функция void* operator new[](size_t sz){ return malloc(sz); }, а затем - 1050 раз конструктор класса Test (а если вылетит исключение, то для уже сконструированных объектов будет вызван деструктор) Кроме того, new и delete могут быть переопределены пользователем для каждого класса индивидуально.
|
|
|
|
|
Jun 27 2014, 06:13
|

Ally
     
Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050

|
Цитата(aas @ Jun 25 2014, 18:55)  А вот некорректность new[] я отследил уже двумя способами, ...
P.S. А можно подробнее, какие возможности добавились в Cortex по поиску таких вот неприятных глюков? Не там копаете, определенно....  Я работал немало с uC-GUI. Надеюсь ваш предыдущий разработчик не пытался ее использовать из разных задач одновременно. Дальше, использование стандартных new и malloc в uCOS это малопонятный выбор. В uCOS есть свой проверенный действительно многозадачный менеджер памяти. Разнообразия ошибок у вас должно быть больше. Припомните не было ли с вашим софтом еще других странностей не объясняемых только проблемой с New. Все это по причине того что одни задачи пишут по ошибке в область памяти других задач. Потому то вас запрещение прерываний и спасало. В Cortex-ах на такие действия можно поставить брекпойнт и отследить. В ARM7 такой возможности нет. Вам надо спускаться на уровень ассемблера и там смотреть состояние и порядок доступа к разным областям памяти. Assert-ы в этой теме как мертвому припарка. Правда бывают вообще терминальные случаи связанные с особенностями функционирования внешних шин памяти. Один раз было когда функция пакетного возврата регистров из стека в SDRAM могла исказить содержимое одного из регистров в этом пакете при определенном содержимом соседних регистров. Хотя отдельное чтение этой ячейки в SDRAM показывало правильное значение. Вот это был баг! Даже JTAG был бессилен. Требовалась тончайшая настройка контроллера SDRAM. Проблема целостности сигналов однако.
|
|
|
|
|
Jun 27 2014, 13:08
|
Участник

Группа: Участник
Сообщений: 16
Регистрация: 15-12-11
Из: Краснодар
Пользователь №: 68 865

|
Доброе время суток! Цитата(AlexandrY @ Jun 27 2014, 10:13)  Не там копаете, определенно....  Я работал немало с uC-GUI. Надеюсь ваш предыдущий разработчик не пытался ее использовать из разных задач одновременно. Нет, uC-GUI используется только в двух задачах, в соответствии с тем, как в книжке написано, одна задача, собственно, интерфейса, вторая отрисовывает WM_PAINT. Цитата(AlexandrY @ Jun 27 2014, 10:13)  Дальше, использование стандартных new и malloc в uCOS это малопонятный выбор. В uCOS есть свой проверенный действительно многозадачный менеджер памяти. Я где-то читал, этот менеджер умеет только куски памяти фиксированного размера выделять. Хотя, возможно, это была устаревшая информация, документацию именно к используемой версии я столь тщательно не читал (нужную версию книжки автора системы удалось скачать буквально пару недель назад только). А тот менеджер, что я использую, я прошел его код полностью, и при условии защиты мютексом там проблем вроде не должно быть. Цитата(AlexandrY @ Jun 27 2014, 10:13)  Разнообразия ошибок у вас должно быть больше. Припомните не было ли с вашим софтом еще других странностей не объясняемых только проблемой с New. К счастью, нет, остальное работает стабильно все. Кроме одного очень редкого глюка с GUI - иногда не отрисовывается фон окна и вместо него может вылезти кусок логотипа фирмы, который на десктопе нарисован. Это случается в среднем раз в 3-5 месяцев, и я не знаю как это отследить  . При этом программа не виснет. Цитата(AlexandrY @ Jun 27 2014, 10:13)  Все это по причине того что одни задачи пишут по ошибке в область памяти других задач. Потому то вас запрещение прерываний и спасало. А что подразумевается под областью памяти других задач? В нескольких местах у меня одна задача выделяет память, и отправляет указатель на нее в параметрах сообщения другой задаче, а другая задача эту память уже освобождает. Т.е. один кусок памяти в разное время используется разными задачами. Цитата(AlexandrY @ Jun 27 2014, 10:13)  В Cortex-ах на такие действия можно поставить брекпойнт и отследить. В ARM7 такой возможности нет. Вам надо спускаться на уровень ассемблера и там смотреть состояние и порядок доступа к разным областям памяти. Assert-ы в этой теме как мертвому припарка. Я вот нашел пока только то, что new[] иногда возвращает указатель на свой код вместо кода выделенной памяти. И запрещение прерываний действительно спасает от этого. Почему оно так делает, пока не знаю. Разве этому оператору нужны статические структуры данных? Опять такти, заменил я new на malloc, и все работает и без запрещения прерываний. Не пойму, в чем разница между new[] и malloc может быть для типа char? Там нету конструктора. Попробовал код этого места глянуть, но закопался, там сплошные вызовы каких-то внутренний функций библиотеки. Судя по именам, часть из них для работы с исключениями. Но кода там явно намного больше, чем просто вызов malloc. Цитата(AlexandrY @ Jun 27 2014, 10:13)  Правда бывают вообще терминальные случаи связанные с особенностями функционирования внешних шин памяти. Один раз было когда функция пакетного возврата регистров из стека в SDRAM могла исказить содержимое одного из регистров в этом пакете при определенном содержимом соседних регистров. Хотя отдельное чтение этой ячейки в SDRAM показывало правильное значение. Вот это был баг! Даже JTAG был бессилен. Требовалась тончайшая настройка контроллера SDRAM. Проблема целостности сигналов однако.  Мдаа, если я с таким столкнусь, искать, наверное, год буду, а не месяц
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|