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

У меня есть процесс (который запускается сторожем каждый раз, по какой-то причине он останавливается), который обычно использует около 200 МБ памяти. Однажды я увидел, что он поглощает память - с использованием памяти около 1,5-2 ГБ, что определенно означает "утечку памяти" где-то ( "утечка памяти" в кавычках, поскольку это не настоящая утечка памяти - как выделенная память, никогда не освобождаемая и недоступен - обратите внимание, что используются только умные указатели. Итак, я думаю о каком-то огромном контейнере (я не нашел) или о чем-то вроде этого)

Позже процесс завершился сбоем из-за высокого использования памяти и создания дампа ядра - около 2 ГБ. Но проблема в том, что я не могу воспроизвести проблему, поэтому valgrind здесь не поможет (наверное). Это случается очень редко, и я не могу "поймать" это.

Итак, мой вопрос - есть ли способ, используя exe и файл core, чтобы определить, какая часть процесса использовала большую часть памяти?

Я посмотрел на основной файл с gdbНет ничего необычного. Но ядро ​​большое, поэтому должно быть что-то. Есть ли умный способ понять, что произошло, или только догадки могут помочь (но для таких больших exe.., 12 потоков, около 50-100 (может быть больше) классов и т. Д., И т. Д.)

Это C++ приложение, работающее на RHEL5U3.

4 ответа

Решение

Откройте этот coredump в шестнадцатеричном формате (как байты / слова / слова / слова). Начиная с середины файла, попробуйте заметить любой повторяющийся узор. Если что-то найдено, попробуйте определить начальный адрес и длину некоторой возможной структуры данных. Используя длину и содержание этой структуры, попытайтесь угадать, что это может быть. Используя адрес, попробуйте найти какой-нибудь указатель на эту структуру. Повторяйте, пока не дойдете до стека или какой-либо глобальной переменной. В случае переменной стека вы легко узнаете, в какой функции запускается эта цепочка. В случае глобальной переменной вы знаете, по крайней мере, ее тип.

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

Обновить

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

Слова "используютсятолько умные указатели" пугают меня. Если значительная часть этих интеллектуальных указателей является общими указателями (shared_ptr, intrusive_ptr, ...), вместо поиска огромных контейнеров стоит искать циклы общего указателя.

Обновление 2

Попробуй определить, где твоя куча заканчивается в corefile (brk значение). Запустите coredumped процесс под GDB и используйте pmap команда (с другого терминала). GDB также должен знать это значение, но я не знаю, как его спросить... Если большая часть памяти процесса выше brkВы можете ограничить свой поиск большими выделениями памяти (скорее всего, std::vector).

Чтобы повысить вероятность обнаружения утечек в области кучи существующего coredump, можно использовать некоторое кодирование (я сам этого не делал, просто теория):

  • Прочитайте файл coredump, интерпретируя каждое значение как указатель (игнорируйте сегмент кода, невыровненные значения и указатели на область без кучи). Сортировка списка, вычисление различий соседних элементов.
  • На этом этапе вся память разбита на множество возможных структур. Вычислить гистограмму размеров структуры, отбросить любые незначительные значения.
  • Рассчитайте разницу адресов указателей и структур, к которым относятся эти указатели. Для каждого размера структуры вычислите гистограмму смещения указателей, снова отбросьте все незначительные значения.
  • Теперь у вас достаточно информации, чтобы угадать типы структур или построить ориентированный граф структур. Найти исходные узлы и циклы этого графа. Вы даже можете визуализировать этот график как "список" холодных "областей памяти".

Файл Coredump находится в elf формат. От заголовка требуется только начало и размер сегмента данных. Чтобы упростить процесс, просто прочитайте его как линейный файл, игнорируя структуру.

Однажды я увидел, что он поглощает память - с использованием памяти около 1,5-2 ГБ

Довольно часто это будет конечным результатом ошибочного цикла. Что-то вроде:

size_t size = 1;
p = malloc(size);
while (!enough_space(size)) {
  size *= 2;
  p = realloc(p, size);
}
// now use p to do whatever

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

используются только умные указатели

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

только догадки могут помочь

Вот и все. Ответ Евгения имеет хорошие отправные точки, которые помогут вам угадать.

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

Valgrind вероятно найдет несколько возможных ошибок, и стоит проанализировать все из них. Вам нужно создать файл подавления и использовать его следующим образом --suppressions=/path/to/file.supp, За каждую возможную ошибку, valgrind flags, либо добавьте предложение в файл подавления, либо измените вашу программу.

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

Существует графический интерфейс для Valgrind под названием Alleyoop, но я не использовал его много.

Другие вопросы по тегам