LINUX.ORG.RU

Непонятные прыжки по коду (memory corruption)


0

1

Добрый день,

Есть некий код, написанный на С++. В виде псевдокода схематично он выглядит так:

void func1()
{
   some_func2();
   fprintf(stderr, "some_func2()\n");
   some_func3();
   fprintf(stderr, "some_func3()\n");
}

Код, можно сказать, линейный. Иногда по логам вижу, что some_func2() вызывается, а вот до some_func3() уже не доходит. При этом func1 каким-то магическим образом завершается. Segmentation Fault не происходит.

Поначалу думал, что fprintf почему-то не виден, но оказалось код до вызова some_func3 действительно не доходит.

Причем это все происходит спорадически. 1 раз на 100 вызовов срабатывает, поэтому дебагером поймайть не могу.

Важное замечание, целевая платформа ARM.

Если б это было на x86, я бы предположил, что some_func2 где-то потерла стек и в результате вызвался ret на другую инструкцию. Но тогда были бы переодические SIGSEGV. На всякий случай в some_func2() завел массив на стеке, проинициализировал его определенными данными. На выходе из some_func2() сравниваю эти значения, все ок. Значит стек не трется. Но возможно на ARM адрес возврата из функции вообще не в стеке лежит?

Пытаюсь пофиксить эту штуку, уже вторую неделю.

По поведению, где-то memory corruption. Но где и как найти? Буду рад любым дельным советам.

★★★★

valgrind пробовал? Ассемблерный код есть? Собирается с -Wall? Предупреждений компилятора при этом нет?

Relan ★★★★★
()
Ответ на: комментарий от Relan

valgrind попробую собрать. На не уверен, что хватит памяти на устройстве. С ходу первая попытка сборки была неудачной. Valgrind потребовал glibc более старой версии, чем сейчас используется.

Собирается с -Wall -Werror Предупреждений нет.

Ассемблерный код есть, но сходу разобраться в нем не удалось. Во первых нужно подучить регистры и инструкции арма, во вторых код С++ (немного посложнее ASM чем от C).

Dead ★★★★
() автор топика

А точно ассемблер нужен?

$ more bimbom.cpp
#include <stdio.h>

void some_func2()
{
  static int i = 2;
  if (i-- < 0) throw "bred";
}

void some_func3()
{
}

void func1()
{
   some_func2();
   fprintf(stderr, "some_func2()\n");
   some_func3();
   fprintf(stderr, "some_func3()\n");
}

main()
{
   try {
     for (int i = 0; i < 100; i++) {
       func1();
     }
   } catch(...) {
   }
}
$ g++ -o bred bimbom.cpp
$ ./bred
some_func2()
some_func3()
some_func2()
some_func3()
some_func2()
some_func3()
Тихо так вылетаем в some_func2.

anonymous
()

Я сам не пробовал, но в новых gcc/clang есть некоторые собственные -fsanitize, gcc 4.9 даже объявил о поддержке ARM для AddressSanitizer.

JackYF ★★★★
()
Ответ на: комментарий от Dead

-Wall — это хорошо. Ассемблер — это плохо. Я бы в первую очередь его подозревал. Адрес возврата в ARM сохраняется в регистре r14.

Relan ★★★★★
()

Иногда по логам вижу, что some_func2() вызывается, а вот до some_func3() уже не доходит

Если ты не видишь чего-то в логах, тому может быть две причины:

  • неверно пишутся логи (падение до flush-а буфера, например, если логи пишутся через fprintf)
  • неверно работает программа

И если в коде написано «2+2», а в логах «5», я бы в первую очередь смотрел на запись логов.

kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

неверно пишутся логи (падение до flush-а буфера, например, если логи пишутся через fprintf)

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

Dead ★★★★
() автор топика
Ответ на: комментарий от Relan

Адрес возврата в ARM сохраняется в регистре r14.

Спасибо. Похоже придется разбираться с ассемблером.

Dead ★★★★
() автор топика
Ответ на: комментарий от JackYF

Я сам не пробовал, но в новых gcc/clang есть некоторые собственные -fsanitize, gcc 4.9 даже объявил о поддержке ARM для AddressSanitizer.

У меня gcc-4.6 поменять его будет очень сложно, т.к. весь SDK для чипа собирается им.

Dead ★★★★
() автор топика
Ответ на: комментарий от Dead

А что мешает поставить в начале и конце some_func2 по точке останова с тривиальным commands?

commands 1
bt 
continue
end
bt будет использовать регистры/стек для раскручивания Если есть что-то одиозное, то второй не сработает. 100 запусков - это мизер

anonymous
()
Ответ на: комментарий от anonymous

Спасибо, за совет, попробую! Не знал про commands. Не так часто приходится использовать gdb.

Dead ★★★★
() автор топика
Ответ на: комментарий от anonymous

Оптимизация не заметил, чтобы влияла. Сейчас вообще выключил. -finstrument-functions еще не пробовал, почитаю что это. Я включил -fstack-protector-all, вижу, что в ASM на входе и выходе из функций какие-то инструкции добавились. Но в целом не повлияло.

Dead ★★★★
() автор топика

Как будто-бы портится стек. Еще если это связано с fprintf, то я бы также проверил аргументы форматированного вывода. Все ли передается, что ожидается и т.п. Ну, и valgrind хорошо находит обычно, хотя и не всегда.

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

dave ★★★★★
()

Если б это было на x86, я бы предположил, что some_func2 где-то потерла стек и в результате вызвался ret на другую инструкцию

Внимание вопрос, что по твоему мешает такому случится на x64?

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

Напиши тест и прогони его n-раз, лучше под valgrind'om. Научишься проявлять, стабильно, если валгринд ничего не покажет к этому времени(что врят ле) - глуши по тихоничку всё, пока бага не перестанет проявляться.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

А чет я отфильтровал важное замечание - ой :)

pon4ik ★★★★★
()

Кстати, gcc пресловут на свои баги (особенно вне майнстрима). Какой уровень оптимизации, какие флаги? Проверь с -O0 и с -O2.

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 2)
Ответ на: комментарий от Dead

Для меня gdb хорош именно возможностью написания скриптов для отладки. Возможностью определения функций, запоминанию значений, сравнению и т.п. Получилось что-нибудь?

io ★★
()
Ответ на: комментарий от io

Получилось что-нибудь?

При помощи commands gолучилось поймать в gdb тот момент, когда jump уже осуществился. Backtrace вроде бы нормальный, т.е. точки входа в функции совпадают. Хотя логически такого перехода не должно было случится. Суть в том, что там идет примерно такой псевдокод:

func_1()
{
   if(some_var == false)
      func_2();
}


funct_2()
{
   ... do_something...

   break point is here

   ... do_something...
}

в точке срабатывания брейкпоинта some_var == true. т.е. func_2 вообще не должен был вызваться.

Тут что-то ARM-спецефичное. На x86 я себе такое поведение не могу представить.

Жаль, что от breakpoint нельзя прогнать выполнение в обратном порядке. На x86 вроде бы gdb такое умеет, но не ARM.

Dead ★★★★
() автор топика
Ответ на: комментарий от Dead

some_var == true

Оно где лежит-то? Если в стеке или на регистрах, то вполне может быть, что к моменту останова значение уже затерлось, бывает такое. GDB показывает не переменную, а значение по какому-то адресу - к переменной это значение уже может отношения не иметь.

alegz ★★★★
()
Ответ на: memory ordering от anonymous

ни о чем не говорит?

а подробнее? как узнать что именно из-за этого? P.S. в чипе одно ядро, если не считать всякие акселераторы типа мультимедийных кодеков.

Dead ★★★★
() автор топика
Ответ на: комментарий от alegz

Оно где лежит-то? Если в стеке или на регистрах, то вполне может быть, что к моменту останова значение уже затерлось, бывает такое. GDB показывает не переменную, а значение по какому-то адресу - к переменной это значение уже может отношения не иметь.

Оно лежит в хипе. И там не мусор, а вполне валидный true.

Dead ★★★★
() автор топика
Ответ на: комментарий от anonymous

матчасть по ссылке

А разве ваша ссылка относится к одноядерным/однопроцессорным системам?

Dead ★★★★
() автор топика
Ответ на: комментарий от anonymous

в последний раз для лентяев...

Спасибо, у меня как раз ARMv7. Хотя не знаю, что мне пока делать с этой информацией. Как я понимаю, если это из-за memory barrier, то получается, что это какой-то баг в gcc. Попробовать поставить ассемблерные вставки с этими инструкциями?

Dead ★★★★
() автор топика
Ответ на: комментарий от Dead

Я как раз именно на x86 подобное встречал.

if (a != NULL) {
  b = *a; // И тут, как кажется, происходит ошибка чтения с нуля

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

funct_2 большой по размеру? Что мешает в commands выводить print-ом some_var? Может быть указать display some_var. Если процедура короткая, то задать breakpoint на каждую строку и сделать условными some_var!=0. Тогда остановится после проблемной строки. Можно watch на значение some_var (после того как определен) забабахать - может где-то были перепутаны аргументы и шарахнуто не в то место - наблюдал подобное.

Кстати процедуры в одном файле или нет? У меня был забавный случай, когда для процедуры было задано специфическое ABI в include-файле. В месте использования не был подключен, как следствие были иногда фокусы входа-выхода.

io ★★
()
Ответ на: комментарий от Dead

Если лежит в хипе, то точно освобождения не было? Я у индусов такую шутку наблюдал как удаление объекта перед использованием. Очень интересные последствия и далеко от ошибки.

io ★★
()
Последнее исправление: io (всего исправлений: 1)
Ответ на: комментарий от io

Ну бляяяя.... какой же я идиот.... наверное старею....

Эта func_1 вызывается из разных тредов, отсуюда такие глюки с выводом. Главное, я ж еще недели полторы назад об этом подумал и поставил на входе в func_1:

fprintf(stderr, "this=%p id=%x\n", this, (unsigned int)pthread_self() );

Распечатка возвращаемого значения pthread_self() всегда одна и таже для каждого запуска и очень похожа на некий уникальный id.
Поэтому и отмел вариант с разными тредами.

Сейчас только в gdb заметил сообщения типа:
[Switching to LWP 2155]
[Switching to LWP 2158]
для одних и тех же брейкпоинтов.

Ребята, всем спасибо, особенно io за технику отладки через commands.

Пошел лечить проблему :)

Dead ★★★★
() автор топика
Ответ на: комментарий от Dead

Да и на pthread_self() я зря прогнал. Все же разные значения он выдавал, но я почему-то не заметил отличий:

a78df460
a787f460

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

Dead ★★★★
() автор топика
Ответ на: комментарий от Dead

На breakpoint и проверку треда навесить можно.

io ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.