Что может изменить указатель кадра? - PullRequest
4 голосов
/ 31 октября 2008

У меня очень странная ошибка, возникающая сейчас в довольно крупном C ++ приложении на работе (огромная с точки зрения использования процессора и оперативной памяти, а также длины кода - свыше 100 000 строк). Это работает на двухъядерной машине Sun Solaris 10. Программа подписывается на каналы цен акций и отображает их на «страницах», настроенных пользователем (страница - это оконная конструкция, настроенная пользователем - программа позволяет пользователю настраивать такие страницы). Эта программа работала без проблем, пока одна из базовых библиотек не стала многопоточной. Части программы, затронутые этим, были изменены соответствующим образом. На мою проблему.

Примерно один раз в каждые три запуска программы происходит сбой при запуске. Это не обязательно жесткое правило - иногда оно будет три раза подряд, а затем будет работать пять раз подряд. Это segfault, что интересно (читай: больно). Это может проявляться несколькими способами, но чаще всего случается, что функция A вызывает функцию B, и при входе в функцию B указатель кадра внезапно устанавливается на 0x000002. Функция A:

   result_type emit(typename type_trait<T_arg1>::take _A_a1) const
     { return emitter_type::emit(impl_, _A_a1); }

Это простая реализация сигнала. impl_ и _A_a1 четко определены в их кадре при сбое. При фактическом выполнении этой инструкции мы оказываемся в программном счетчике 0x000002.

Это не всегда происходит с этой функцией. На самом деле это происходит в нескольких местах, но это один из более простых случаев, который не оставляет места для ошибок. Иногда случается, что переменная, размещенная в стеке, внезапно окажется в нежелательной памяти (всегда в 0x000002) безо всякой причины. В других случаях тот же код будет работать нормально. Итак, мой вопрос: что может так сильно испортить стек? Что на самом деле может изменить значение указателя кадра? Я конечно никогда не слышал о такой вещи. Единственное, о чем я могу думать, это записывать данные за пределы массива, но я построил его с помощью стекового протектора, который должен подходить для любых случаев этого. Я также хорошо в пределах моего стека здесь. Я также не вижу, как другой поток может перезаписать переменную в стеке первого потока, так как каждый поток имеет свой собственный стек (это все pthreads). Я пытался собрать это на Linux-машине, и, хотя у меня там нет segfaults, примерно один из трех раз он зависнет от меня.

Ответы [ 14 ]

9 голосов
/ 31 октября 2008

Повреждение стека, определенно 99,9%.

Запахи, которые вы должны внимательно искать: -

  • Использование массивов 'C'
  • Использование функций в стиле 'C' в стиле strcpy
  • тетср
  • Маллок и бесплатно
  • потокобезопасность всего, что использует указатели
  • Неинициализированные переменные POD.
  • Арифметика указателей
  • Функции, пытающиеся вернуть локальные переменные по ссылке
4 голосов
/ 31 октября 2008

У меня была именно эта проблема сегодня, и я был по колено в gdb грязи и отладке в течение часа, прежде чем мне пришло в голову, что я просто переписал границы массива (где я не ожидал этого меньше всего) C массив.

Так что, если возможно, используйте vector s вместо этого, потому что любая реализация STL с задержкой выдаст хорошие сообщения компилятора, если вы попробуете это в режиме отладки (тогда как массивы C накажут вас за ошибки сегмента).

3 голосов
/ 31 октября 2008

Здесь есть некоторая путаница между переполнением стека и повреждением стека.

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

void foo()
{
  foo();  // endless recursion - whoops!
}

void foo2()
{
  char myBuffer[A_VERY_BIG_NUMBER];  // The stack can't hold that much.
}

class bigObj
{
  char myBuffer[A_VERY_BIG_NUMBER];  
}

void foo2( bigObj big1)  // pass by value of a big object - whoops!
{
}

Во встроенных системах размер стека потока может измеряться в байтах, и даже простая последовательность вызовов может вызвать проблемы. По умолчанию в Windows каждый поток получает 1 Мегабайт стека, поэтому переполнение стека является гораздо менее распространенной проблемой. Если у вас нет бесконечной рекурсии, переполнение стека всегда можно уменьшить, увеличив размер стека, хотя это обычно НЕ лучший ответ.

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

Проще всего: -

void foo()
{ 
  char message[10];

  message[10] = '!';  // whoops! beyond end of array
}
3 голосов
/ 31 октября 2008

Я не уверен, что вы называете "указатель кадра", как вы говорите:

о фактическом исполнении этого инструкция, мы в конечном итоге в программе счетчик 0x000002

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

Не думаю, что здесь достаточно информации, чтобы действительно дать вам хороший ответ, но некоторые вещи, которые могут быть виновниками:

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

  • Удар оперативной памяти. Все, что пишет через неверный указатель, может привести к попаданию мусора в стек. Я не знаком с Solaris, но большинство реализаций потоков имеют потоки в одном и том же адресном пространстве процесса, поэтому любой поток может получить доступ к стеку любого другого потока. Один из способов, которым поток может получить указатель в стек другого потока, - это если адрес локальной переменной передается в API, который в конечном итоге обрабатывает указатель в другом потоке. если вы не синхронизируете вещи должным образом, это приведет к тому, что указатель получит доступ к неверным данным. Учитывая, что вы имеете дело с «простой реализацией сигнала», кажется, что вполне возможно, что один поток посылает сигнал другому. Может быть, один из параметров в этом сигнале имеет локальный указатель?

1 голос
/ 31 октября 2008

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

Во-первых, я бы удостоверился, что все библиотеки и связанные объекты были восстановлены без ошибок, и все параметры компилятора согласованы - у меня раньше была похожая проблема (Solaris 2.5), которая была вызвана объектным файлом, который не имел был восстановлен.

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

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

1 голос
/ 31 октября 2008

Можно ли запустить вещь через Valgrind? Возможно, Sun предоставляет аналогичный инструмент. В Intel VTune (на самом деле я думал о Thread Checker) также есть очень хорошие инструменты для отладки потоков и тому подобное.

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

1 голос
/ 31 октября 2008

С унифицированными переменными C ++ и состояниями гонки, вероятно, возможны прерывистые сбои.

1 голос
/ 31 октября 2008

Это звучит как проблема переполнения стека - что-то записывает за пределы массива и растоптывает кадр стека (и, вероятно, адрес возврата) в стеке. Есть большая литература по этому вопросу. «Руководство программиста оболочки» (2-е издание) содержит примеры SPARC, которые могут вам помочь.

0 голосов
/ 13 апреля 2009

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

  • В pthreads вы должны выделить стек и передать его потоку. Вы выделили достаточно? Нет автоматического роста стека, как в однопоточном процессе.
  • Если вы уверены, что не повредите стек, записав прошедшую проверку стека выделенные данные для указателей на румяна (в основном неинициализированные указатели).
  • Один из потоков может перезаписывать некоторые данные, от которых зависят другие (проверьте синхронизацию данных).
  • Отладка здесь обычно не очень полезна. Я попытался бы создать много выходных данных журнала (трассировки для входа и выхода каждого вызова функции / метода), а затем проанализировать журнал.
  • Может помочь тот факт, что ошибка проявляется по-разному в Linux. Какое отображение потоков вы используете в Solaris? Убедитесь, что вы связываете каждый поток с его собственным LWP, чтобы упростить отладку.
0 голосов
/ 31 октября 2008

Также согласился, что 0x000002 является подозрительным. Это примерно единственная константа между авариями. Еще более странным является тот факт, что это произошло только с многопоточным коммутатором. Я думаю, что меньший стек в результате многопоточности - вот что делает этот рост сейчас, но это чистое предположение с моей стороны.

Если вы передадите что-либо в стек по ссылке или по адресу, это наверняка произойдет, если другой поток попытается использовать его после того, как первый поток вернется из функции.

Возможно, вы сможете воспроизвести это, перегрузив приложение на один процессор. Я не знаю, как ты это делаешь со Спарком.

...