Дамп памяти ошибки сегментации

Обновлено: 21.11.2024

Частая ошибка новичков во время выполнения программ на языке C – это "нарушение сегментации" или "ошибка сегментации". Когда вы запускаете свою программу и система сообщает о «нарушении сегментации», это означает, что ваша программа пыталась получить доступ к области памяти, доступ к которой ей запрещен. Другими словами, он пытался использовать память, выходящую за пределы, выделенные операционной системой (например, Unix) для вашей программы.

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

Частые причины этой проблемы:

  • Неверная строка управления форматом в операторах printf или scanf.
    Убедитесь, что строка управления форматом имеет то же количество описателей преобразования (%), что и printf или scanf имеют аргументы для печати или чтения соответственно. и что спецификаторы соответствуют типу переменной, которая должна быть напечатана или прочитана. Это также относится к fprintf и fscanf.

Правильная инициализация указателя:

Одним из распространенных способов является присвоение указателю адреса ранее определенной переменной. Например:
Или, что то же самое,
Другие распространенные способы включают присвоение указателю адреса памяти, выделенного с помощью матрицы, вектора, calloc или malloc или других эквивалентных функций распределения. Помните, что указатель должен быть инициализирован значением (т. е. ему должно быть присвоено значение, появившееся в левой части оператора присваивания) ДО того, как вы попытаетесь получить к нему доступ!

Сведение к минимуму использования переменных-указателей.

Кроме того, во многих случаях функция требует, чтобы адрес (соответствующий параметру типа указателя) был отправлен ей в качестве аргумента (как это верно для многих числовых рецептов в функциях C). Стандартная функция scanf является примером такой функции.
В таких случаях обычно лучше просто объявить переменную правильного типа перед вызовом функции и просто отправить адрес переменной в функцию. На самом деле, это то, что предназначено в подавляющем большинстве этих случаев. И именно так вы обычно сканируете: Например, посмотрите, как используется 'idum' ниже: Функция normal ожидает адрес переменной типа long. Это то, что мы отправляем без явного использования переменной-указателя в вызывающей подпрограмме.

Устранение проблемы:

Проверьте КАЖДОЕ место в вашей программе, которое использует указатели, индексирует массив или использует оператор адреса (&) и оператор разыменования (*). Каждый из них является кандидатом на роль причины нарушения сегментации. Убедитесь, что вы понимаете использование указателей и связанных с ними операторов.

Если программа использует много указателей и имеет много вхождений & и *, добавьте несколько операторов printf, чтобы точно определить место, в котором программа вызывает ошибку, и исследовать указатели и переменные, участвующие в этом операторе.

Помните, что операторы printf в целях отладки должны иметь символ новой строки (\n) в конце своих строк управления форматом, чтобы вызвать очистку буфера печати. последнее изменение:

Ошибка сегментации (segfault) – это распространенное состояние, которое приводит к сбою программ. они часто связаны с файлом с именем core. Segfaults вызваны попыткой программы прочитать или записать недопустимую ячейку памяти.

Память программы разделена на разные сегменты: текстовый сегмент для программных инструкций, сегмент данных для переменных и массивов, определенных во время компиляции, сегмент стека для временных (или автоматических) переменных, определенных в подпрограммах и функциях, и сегмент кучи. для переменных, выделяемых во время выполнения функциями, такими как malloc (в C) и allocate (в Fortran). Подробнее см. в разделе О сегментах программы.

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

Примеры распространенных ошибок сегментации

  • Например, вызов memset(), как показано ниже, приведет к segfault программы:
  • Следующие три случая иллюстрируют наиболее распространенные типы сбоев сегментации, связанных с массивами:
  • В случае A массив foo определен для index = 0, 1, 2, . 999 . Однако на последней итерации цикла for программа пытается получить доступ к foo[1000] . Это приведет к segfault, если эта ячейка памяти находится за пределами сегмента памяти, где находится foo. Даже если это не вызывает segfault, это все равно ошибка.
  • В случае B целое число n может быть любым случайным значением. Как и в случае A, если он не находится в диапазоне 0, 1, . 999 , это может привести к segfault. Так это или нет, но это определенно ошибка.
  • В случае C выделение памяти для переменной foo2 было пропущено, поэтому foo2 будет указывать на случайное место в памяти. Доступ к foo2[0], скорее всего, приведет к segfault.

Переменная foo может быть определена в ячейке памяти 1000 , но приведенный выше вызов функции попытается считать целочисленные данные в ячейку памяти 0 в соответствии с определением foo .

Найти ссылки на массив за пределами границ

Большинство компиляторов Fortran имеют опцию, которая вставляет код для проверки границ всех ссылок на массивы во время выполнения. Если доступ выходит за пределы диапазона индексов, определенного для массива, программа остановится и сообщит вам, где это произошло. Для большинства компиляторов Fortran используется параметр -C или -check, за которым следует ключевое слово. См. руководство пользователя вашего компилятора, чтобы получить точный вариант. Используйте проверку границ только при отладке, так как это замедлит вашу программу. Некоторые компиляторы C также имеют возможность проверки границ.

Проверить ограничения оболочки

Как отмечено в последнем примере выше, некоторые проблемы с segfault возникают не из-за ошибок в вашей программе, а из-за слишком низких ограничений системной памяти. Обычно такие проблемы возникают из-за ограничения размера стека. Чтобы проверить пределы памяти, используйте команду ulimit в bash или ksh или команду limit в csh или tcsh. Попробуйте увеличить размер стека, а затем перезапустите программу, чтобы проверить, исчезнет ли ошибка сегментации.

Используйте отладчики для диагностики ошибок сегментации

Если вы не можете найти проблему другим способом, попробуйте отладчик. Например, вы можете использовать хорошо известный отладчик GNU GDB для просмотра обратной трассировки файла ядра, созданного вашей программой; всякий раз, когда программы segfault, они обычно сбрасывают содержимое (своего раздела) памяти во время сбоя в основной файл. Запустите отладчик с помощью команды gdb core, а затем используйте команду backtrace, чтобы увидеть, где находилась программа в момент сбоя. Этот простой трюк позволит вам сосредоточиться на этой части кода.

Если использование обратной трассировки для основного g-файла не помогло найти проблему, возможно, вам придется запустить программу под управлением отладчика, а затем пройти код по одной функции или по одной строке исходного кода за раз. Для этого вам нужно будет скомпилировать свой код без оптимизации и с флагом -g, чтобы информация о строках исходного кода была встроена в исполняемый файл. Подробнее см. в разделе Пошаговый пример использования GDB в Emacs для отладки программы на C или C++.


Алекс Аллен

Для начинающих программистов ошибки отладки, связанные с указателями, могут стать кошмаром. «Ошибка сегментации (дамп ядра)» — это довольно расплывчатое сообщение об ошибке, и еще хуже, когда начинают появляться странные ошибки, которые не вызывают ошибок сегментации, но приводят к тому, что такие вещи, как память, неожиданно перезаписываются. К счастью, такие инструменты, как Cee Studio от нашего спонсора, предоставляют новые, более простые способы отладки ошибок памяти и переполнения буфера. Cee Studio — это веб-компилятор, разработанный для облегчения поиска ошибок сегментации за счет мгновенной и информативной обратной связи о неправильном использовании памяти. Но даже без специализированных инструментов найти проблемы с указателями проще, чем вы думаете.

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

Что такое ошибка сегментации?

Когда ваша программа работает, она имеет доступ к определенным участкам памяти. Во-первых, у вас есть локальные переменные в каждой из ваших функций; они хранятся в стеке. Во-вторых, у вас может быть некоторая память, выделенная во время выполнения (с помощью malloc в C или new в C++), хранящаяся в куче (вы также можете услышать, что это называется «свободным хранилищем»). Вашей программе разрешено обращаться только к той памяти, которая ей принадлежит, — памяти, упомянутой ранее. Любой доступ за пределы этой области вызовет ошибку сегментации. Ошибки сегментации обычно называют ошибками сегментации.

Есть четыре распространенные ошибки, которые приводят к ошибкам сегментации: разыменование NULL, разыменование неинициализированного указателя, разыменование указателя, который был освобожден (или удален в C++) или вышел за пределы области действия (в случае массивов, объявленных в функциях) и списание конца массива.

Пятый способ вызвать segfault – это рекурсивная функция, использующая все пространство стека. В некоторых системах это вызовет сообщение о "переполнении стека", а в других просто появится как другой тип ошибки сегментации.

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

Например, работающий в системе Linux, вот пример сеанса: он просто загружает программу с именем example, используя основной файл с именем «core». Основной файл содержит всю информацию, необходимую GDB для восстановления состояния выполнения, когда недопустимая операция вызвала ошибку сегментации.

После того, как мы загрузили gdb, мы получаем следующее: Итак, выполнение остановилось внутри функции с именем foo() в строке 4, которая оказалась присвоением числа 3 ячейке, на которую указывает x. Это кладезь информации: мы уже точно знаем, где возникла проблема и какой указатель был задействован.

Поскольку это несколько надуманный пример, мы можем сразу найти ошибку. Указатель x инициализируется равным 0, что эквивалентно NULL (фактически, NULL заменяет 0), и мы знаем, что пытаться получить доступ к этому указателю нельзя.

Но что, если бы это было не так очевидно? Простая печать значения указателя часто может привести к решению. В этом случае: Распечатка x показывает, что он указывает на адрес памяти 0x0 (0x указывает, что значение, следующее за ним, находится в шестнадцатеричном формате, традиционном для печати адресов памяти). Адрес 0x0 недействителен — на самом деле это NULL. Если вы разыменуете указатель, который хранит местоположение 0x0, вы обязательно получите ошибку сегментации, как и мы.

Если бы мы столкнулись с чем-то более сложным, например сбоем выполнения внутри системного вызова или библиотечной функции (возможно, из-за того, что мы передали неинициализированный указатель в fgets), нам нужно было бы выяснить, где мы вызвали библиотечную функцию и что могло случиться, чтобы вызвать segfault в нем. Вот пример из другого сеанса отладки: На этот раз segfault произошел из-за чего-то внутри strcat. Означает ли это, что библиотечная функция сделала что-то не так? Неа! Это означает, что мы, вероятно, передали в функцию неверное значение. Чтобы отладить это, нам нужно увидеть, что мы передали в strcat.

Чтобы перейти от просмотра состояния внутри каждой функции (инкапсулированного в идею стекового фрейма), мы можем использовать команды вверх и вниз. Прямо сейчас мы знаем, что находимся в кадре стека strcat, который содержит все локальные переменные strcat, потому что это верхняя функция в стеке. Мы хотим двигаться «вверх» (к более высоким числам); это противоположно тому, как печатается стек. Это немного помогает — мы знаем, что у нас есть переменная с именем x и постоянная строка. Нам, вероятно, следует поискать функцию strcat в этот момент, чтобы убедиться, что мы получили правильный порядок аргументов. Поскольку мы это сделали, проблема должна быть связана с x. Вот он снова: указатель NULL. Функция strcat должна разыгрывать указатель NULL, который мы ей дали, и хотя это библиотечная функция, она не делает ничего волшебного.

С указателями NULL, как правило, довольно легко работать — как только мы их нашли, мы знаем, что где-то в строке мы не выделили часть памяти, которую должны были иметь. Только вопрос где. Распространенной ошибкой является непроверка возврата от malloc, чтобы убедиться, что в системе не закончилась память. Другой распространенной ошибкой является предположение, что функция, вызывающая malloc, не возвращает NULL, даже если она возвращает результат malloc. Обратите внимание, что в C++ при вызове new выдается исключение bad_alloc, если не может быть выделено достаточно памяти. Ваш код должен быть готов к корректной обработке этой ситуации, и если вы решите перехватить исключение и вернуть NULL внутри функции, которая обычно возвращает новый указатель, этот совет останется в силе. Мы поступили правильно, проверив успешность выполнения malloc перед использованием памяти в create_memory, но мы не проверяем, возвращает ли create_memory действительный указатель! Позор нам. Это ошибка, которая не заметит вас, пока вы не запустите свой код в реальной системе, если вы явно не протестируете свой код в ситуациях с нехваткой памяти.

Разыменование неинициализированного указателя

Выяснить, был ли указатель инициализирован, немного сложнее, чем выяснить, является ли указатель NULL. Лучший способ избежать использования неинициализированного указателя — установить для ваших указателей значение NULL при их объявлении (или сразу же их инициализировать). Таким образом, если вы используете указатель, для которого не выделена память, вы сразу сможете это определить.

Если вы не устанавливаете указатели в NULL при их объявлении, вам будет гораздо труднее (помните, что нестатические переменные не инициализируются автоматически чем-либо в C или C++). Возможно, вам придется выяснить, является ли 0x4025e800 действительной памятью. Один из способов получить представление об этом в GDB — распечатать адреса, хранящиеся в других выделенных вами указателях. Если они достаточно близко друг к другу, вы, вероятно, правильно распределили память. Конечно, нет никакой гарантии, что это эмпирическое правило будет работать во всех системах.

В некоторых случаях ваш отладчик может сообщить вам, что адрес недействителен на основе значения, сохраненного в указателе.Например, в следующем примере GDB указывает, что char* x, который я установил так, чтобы он указывал на адрес памяти «30», недоступен. Однако, как правило, лучший способ справиться с такой ситуацией — просто не полагаться на то, что память находится близко друг к другу или явно недействительна. Установите ваши переменные в NULL с самого начала.

Разыменование освобожденной памяти

Это еще одна сложная ошибка, которую нужно найти, потому что вы работаете с адресами памяти, которые выглядят допустимыми. Лучший способ справиться с такой ситуацией снова превентивный: установите указатель так, чтобы он указывал на NULL, как только вы его освободили. Таким образом, если вы попытаетесь использовать его позже, у вас будет еще одна ошибка «разыменования NULL», которую будет намного легче отследить.

Другой формой этой ошибки является проблема с памятью, которая вышла за рамки. Если вы объявите локальный массив, такой как тогда, массив x больше не будет действительным после возврата функции. Это действительно сложная ошибка, потому что снова адрес памяти будет выглядеть корректно, когда вы распечатаете его в GDB. На самом деле, ваш код может иногда даже работать (или просто демонстрировать странное поведение, печатая все, что находится в стеке, в месте, которое раньше было памятью массива x). Как правило, способ, которым вы узнаете, есть ли у вас такая ошибка, заключается в том, что вы получите мусор, когда будете распечатывать переменную, даже если вы знаете, что она инициализирована. Следите за указателями, возвращаемыми из функций. Если этот указатель вызывает у вас проблемы, проверьте функцию и посмотрите, указывает ли указатель на локальную переменную в функции. Обратите внимание, что вполне нормально возвращать указатель на память, выделенную в функции, с помощью new или malloc, но не возвращать указатель на статически объявленный массив (например, char x[10]).

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

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

Списание конца массива

Как правило, если вы списываете границы массива, то строка, вызвавшая segfault, в первую очередь должна быть доступом к массиву. (Есть несколько случаев, когда на самом деле это не так — в частности, если тот факт, что вы списали массив, приводит к разрушению стека — в основном, перезаписывая указатель, который хранит, куда вернуться после завершения функции. .)

Конечно, иногда вы не вызовете ошибку сегментации, записывающую конец массива. Вместо этого вы можете просто заметить, что некоторые из ваших значений переменных периодически и неожиданно меняются. Эту ошибку сложно взломать; Один из вариантов — настроить отладчик на отслеживание изменений переменной и запускать вашу программу до тех пор, пока значение переменной не изменится. Ваш отладчик сломается на этой инструкции, и вы сможете проверить, не является ли такое поведение неожиданным. Этот подход может оказаться сложным, когда вы имеете дело с большим количеством динамически выделяемой памяти, и не совсем понятно, что вам следует смотреть. Чтобы упростить задачу, используйте простые тестовые примеры, продолжайте работать с теми же входными данными и отключите рандомизированные начальные значения, если вы используете случайные числа!

Переполнение стека

Переполнение стека — это не та проблема, связанная с указателем, что и другие. В этом случае вам не нужно иметь в программе ни одного явного указателя; вам просто нужна рекурсивная функция без базового случая. Тем не менее, это руководство по ошибкам сегментации, и в некоторых системах о переполнении стека будет сообщено как об ошибке сегментации. (Это имеет смысл, поскольку нехватка памяти в стеке нарушит сегментацию памяти.)

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

Как правило, вам необходимо проанализировать рекурсивную функцию, чтобы убедиться, что все базовые случаи (случаи, в которых функция не должна вызывать себя) корректно охватываются. Например, при вычислении функции факториала. В этом случае рассматривается базовый случай, когда n равно нулю, но как насчет n? Связанные статьи

Эта ошибка может появиться в Ubuntu в любой момент.Несколько дней назад, когда я выполнял свою рутинную работу на своем ноутбуке с Ubuntu, я внезапно столкнулся с ошибкой «Ошибка сегментации (дамп ядра)», а затем узнал, что эта ошибка может поразить вас в Ubuntu или любой другой операционной системе в любое время. момент, так как сбой двоичных файлов не зависит от нас.

Ошибка сегментации возникает, когда ваша система пытается получить доступ к несуществующей странице памяти. Сброс ядра означает, что часть кода пытается выполнить операцию чтения и записи в доступном только для чтения или свободном месте. Сегментные сбои обычно связаны с файлом с именем core и обычно происходят во время обновления.

При выполнении некоторых команд во время создания дампа ядра вы можете столкнуться с сообщением «Невозможно открыть файл блокировки». Это связано с тем, что система пытается захватить несуществующий битовый блок. Это происходит из-за сбоя двоичных файлов некоторые специальные программы.

Вы можете выполнить возврат или отладку, чтобы решить эту проблему, но решение состоит в том, чтобы восстановить поврежденные пакеты, и мы можем сделать это, выполнив следующие шаги:

Командная строка:

Шаг 1. Удалите файлы блокировки, находящиеся в разных местах.

Шаг 2. Удалите кэш репозитория.

Шаг 3. Обновите и обновите кэш репозитория.

Шаг 4. Теперь обновите свой дистрибутив, он обновит ваши пакеты.

Шаг 5. Найдите поврежденные пакеты и принудительно удалите их.

Помимо командной строки, лучший способ, который всегда будет работать, это:

Шаг 1. Запустите Ubuntu в режиме запуска, нажав клавишу Esc после перезагрузки.

Шаг 2. Выберите Дополнительные параметры для Ubuntu

Шаг 3. Запустите Ubuntu в режиме восстановления, и вы увидите множество вариантов.

Шаг 4. Сначала выберите "Восстановить поврежденные пакеты"

Шаг 5. Затем выберите «Возобновить обычную загрузку»

Итак, у нас есть два метода устранения ошибки сегментации: интерфейс командной строки и графический интерфейс. Иногда также может случиться так, что команда «apt» не работает из-за segfault, поэтому наш метод CLI не будет работать, в этом случае также не беспокойтесь, так как метод GUI будет работать для нас всегда.

Читайте также: