Ошибка сегментации - Segmentation fault

В вычисление, а ошибка сегментации (часто сокращается до segfault) или же нарушение доступа это вина, или состояние отказа, вызванное оборудованием с защита памяти, уведомив Операционная система (ОС) программа попыталась получить доступ к ограниченной области памяти (нарушение доступа к памяти). По стандарту x86 компьютеры, это форма общая ошибка защиты. ОС ядро в ответ обычно выполняет некоторые корректирующие действия, как правило, передавая ошибку нарушителю процесс отправив процессу сигнал. В некоторых случаях процессы могут устанавливать собственный обработчик сигналов, что позволяет им восстанавливаться самостоятельно,[1] но в противном случае используется обработчик сигналов ОС по умолчанию, что обычно вызывает ненормальное прекращение процесса (программа крушение ), а иногда и дамп ядра.

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

Многие языки программирования могут использовать механизмы, предназначенные для предотвращения ошибок сегментации и повышения безопасности памяти. Например, Язык программирования Rust использует "Собственность"[2] основанная на модели для обеспечения безопасности памяти.[3] Другие языки, например Лисп и Ява, использовать сборку мусора,[4] что позволяет избежать определенных классов ошибок памяти, которые могут привести к ошибкам сегментации.[5]

Обзор

Пример сигнала, созданного человеком

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

Термин «сегментация» используется в вычислительной технике по-разному; в контексте «ошибки сегментации», термина, используемого с 1950-х годов, он относится к адресному пространству программа.[нужна цитата ] С защитой памяти, только собственное адресное пространство программы доступно для чтения, и только куча и часть чтения / записи сегмент данных программы доступны для записи, а данные только для чтения и сегмент кода не доступны для записи. Таким образом, попытка чтения за пределами адресного пространства программы или запись в сегмент адресного пространства, доступный только для чтения, приводит к ошибке сегментации, отсюда и название.

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

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

На уровне операционной системы эта ошибка перехватывается, и сигнал передается процессу-нарушителю, активируя обработчик процесса для этого сигнала. В разных операционных системах используются разные имена сигналов, указывающие на то, что произошла ошибка сегментации. На Unix-подобный операционных систем, сигнал называется SIGSEGV (сокращенно от нарушение сегментации) отправляется в нарушивший процесс. На Майкрософт Виндоус, вызывающий нарушение процесс получает STATUS_ACCESS_VIOLATION исключение.

Причины

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

Ниже приведены некоторые типичные причины ошибки сегментации:

  • Попытка доступа к несуществующему адресу памяти (вне адресного пространства процесса)
  • Попытка доступа к памяти, на которую программа не имеет прав (например, к структурам ядра в контексте процесса)
  • Попытка записи в постоянную память (например, сегмент кода)

Это, в свою очередь, часто вызвано ошибками программирования, которые приводят к недопустимому доступу к памяти:

  • Разыменование нулевой указатель, который обычно указывает на адрес, не являющийся частью адресного пространства процесса.
  • Разыменование или присвоение неинициализированному указателю (дикий указатель, что указывает на случайный адрес памяти)
  • Разыменование или присвоение освобожденному указателю (висячий указатель, который указывает на память, которая была освобождена / освобождена / удалена)
  • А переполнение буфера
  • А переполнение стека
  • Попытка выполнить программу, которая не компилируется правильно. (Некоторые компиляторы выводят запускаемый файл несмотря на наличие ошибок времени компиляции.)

В коде C ошибки сегментации чаще всего возникают из-за ошибок при использовании указателя, особенно в Распределение динамической памяти C. Разыменование нулевого указателя всегда будет приводить к ошибке сегментации, но дикие указатели и висячие указатели указывают на память, которая может существовать, а может и не существовать, и может быть или не быть доступной для чтения или записи, и, таким образом, может привести к временным ошибкам. Например:

char *p1 = НОЛЬ;           // Нулевой указательchar *p2;                  // Дикий указатель: вообще не инициализирован.char *p3  = маллок(10 * размер(char));  // Инициализированный указатель на выделенную память                                        // (при условии, что malloc не завершился ошибкой)свободный(p3);                  // p3 теперь является висячим указателем, так как память была освобождена

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

Умение обращаться

Действие по умолчанию для ошибки сегментации или ошибки шины: ненормальное прекращение процесса, который его вызвал. А основной файл могут быть созданы для облегчения отладки, а также могут выполняться другие действия, зависящие от платформы. Например, Linux системы, использующие патч grsecurity, могут регистрировать сигналы SIGSEGV для отслеживания возможных попыток вторжения с использованием переполнение буфера.

В некоторых системах, таких как Linux и Windows, программа может сама обработать ошибку сегментации.[6]. В зависимости от архитектуры и операционной системы запущенная программа может не только обрабатывать событие, но и извлекать некоторую информацию о своем состоянии, например, получение трассировки стека, значения регистров процессора, строку исходного кода, когда она была запущена, адрес памяти, который был недействительный доступ[7] и было ли действие чтением или записью.[8]

Хотя ошибка сегментации обычно означает, что в программе есть ошибка, которую необходимо исправить, также можно намеренно вызвать такой сбой в целях тестирования, отладки, а также для эмуляции платформ, где требуется прямой доступ к памяти. В последнем случае система должна иметь возможность разрешить выполнение программы даже после возникновения ошибки. В этом случае, когда система позволяет, можно обработать событие и увеличить счетчик программы процессора, чтобы «перепрыгнуть» через сбойную инструкцию, чтобы продолжить выполнение.[9]

Примеры

Ошибка сегментации на EMV клавиатура

Запись в постоянную память

Запись в постоянную память вызывает ошибку сегментации. На уровне ошибок кода это происходит, когда программа пишет в свою часть. сегмент кода или доступная только для чтения часть сегмент данных, поскольку они загружаются ОС в постоянную память.

Вот пример ANSI C код, который обычно вызывает ошибку сегментации на платформах с защитой памяти. Он пытается изменить строковый литерал, что является неопределенным поведением в соответствии со стандартом ANSI C. Наиболее компиляторы не поймает это во время компиляции, а вместо этого скомпилирует это в исполняемый код, который выйдет из строя:

int главный(пустота){    char *s = "Привет, мир";    *s = 'ЧАС';}

При компиляции программы, содержащей этот код, строка "hello world" помещается в Родата раздел программы запускаемый файл: раздел только для чтения сегмент данных. После загрузки операционная система помещает его с другими строками и постоянный данные в сегменте памяти, доступном только для чтения. При выполнении переменная, s, устанавливается так, чтобы указывать на местоположение строки, и делается попытка написать ЧАС через переменную в память, вызывая ошибку сегментации. Компиляция такой программы с помощью компилятора, который не проверяет назначение мест только для чтения во время компиляции, и запуск ее в Unix-подобной операционной системе приводит к следующему ошибка выполнения:

$ gcc segfault.c -g -o segfault$ ./segfaultОшибка сегментации

Обратная трассировка основного файла из GDB:

Программа получила сигнал SIGSEGV, Сегментация вина.0x1c0005c2 в главный () в segfault.c:66               *s = 'ЧАС';

Этот код можно исправить, используя массив вместо указателя на символ, поскольку он выделяет память в стеке и инициализирует ее значением строкового литерала:

char s[] = "Привет, мир";s[0] = 'ЧАС';  // эквивалентно * s = 'H';

Несмотря на то, что строковые литералы не должны изменяться (это имеет неопределенное поведение в стандарте C), в C они имеют статический символ [] тип,[10][11][12] поэтому в исходном коде нет неявного преобразования (что указывает на символ * в этом массиве), а в C ++ они имеют static const char [] type, и, следовательно, существует неявное преобразование, поэтому компиляторы обычно перехватывают эту конкретную ошибку.

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

В языках C и C-подобных, нулевые указатели используются для обозначения "указателя на отсутствие объекта" и в качестве индикатора ошибки, и разыменование нулевой указатель (чтение или запись через нулевой указатель) - очень распространенная ошибка программы. Стандарт C не говорит, что нулевой указатель совпадает с указателем на адрес памяти 0, хотя на практике это может быть так. Большинство операционных систем отображают адрес нулевого указателя таким образом, что доступ к нему вызывает ошибку сегментации. Это поведение не гарантируется стандартом C. Разыменование нулевого указателя неопределенное поведение в C, и соответствующая реализация может предполагать, что любой указатель, на который разыменован, не является нулевым.

int *ptr = НОЛЬ;printf("% d", *ptr);

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

Разыменовывание нулевого указателя и последующее присвоение ему (запись значения несуществующей цели) также обычно вызывает ошибку сегментации:

int *ptr = НОЛЬ;*ptr = 1;

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

int *ptr = НОЛЬ;*ptr;

Переполнение буфера

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

Другой пример рекурсия без базового случая:

int главный(пустота){    главный();    возвращаться 0;}

что вызывает стек для переполнения что приводит к ошибке сегментации.[13] Бесконечная рекурсия не обязательно может привести к переполнению стека в зависимости от языка, оптимизаций, выполняемых компилятором, и точной структуры кода. В этом случае поведение недоступного кода (оператор return) не определено, поэтому компилятор может устранить его и использовать хвостовой зов оптимизация, которая может привести к тому, что стек не будет использоваться. Другие оптимизации могут включать перевод рекурсии в итерацию, что, учитывая структуру функции примера, приведет к тому, что программа будет работать вечно, при этом, вероятно, не переполняется ее стек.

Смотрите также

Рекомендации

  1. ^ Программирование на языке C: секреты глубокого C Питер Ван дер Линден, стр.188
  2. ^ Язык программирования Rust - Право собственности
  3. ^ Бесстрашный параллелизм с Rust - Блог о языке программирования Rust
  4. ^ Маккарти, Джон (Апрель 1960 г.). "Рекурсивные функции символьных выражений и их машинное вычисление, Часть I". Коммуникации ACM. 4 (3): 184–195. Получено 2018-09-22.
  5. ^ Дурджати, Динакар; Ковшик, Сумант; Адве, Викрам; Латтнер, Крис (1 января 2003 г.). «Безопасность памяти без проверок во время выполнения или сборки мусора» (PDF). Материалы конференции ACM SIGPLAN 2003 г. по языку, компилятору и инструментам для встроенных систем. ACM: 69–80. Дои:10.1145/780732.780743. ISBN  1581136471. Получено 2018-09-22.
  6. ^ «Чистое восстановление после Segfaults под Windows и Linux (32-бит, x86)». Получено 2020-08-23.
  7. ^ «Реализация обработчика SIGSEGV / SIGABRT, который печатает трассировку стека отладки». Получено 2020-08-23.
  8. ^ "Как определить операции чтения или записи при отказе страницы при использовании обработчика sigaction в SIGSEGV? (LINUX)". Получено 2020-08-23.
  9. ^ "LINUX - ОБРАБОТКА НЕИСПРАВНОСТЕЙ ЗАПИСИ". Получено 2020-08-23.
  10. ^ «6.1.4 Строковые литералы». ISO / IEC 9899: 1990 - Языки программирования - C.
  11. ^ «6.4.5 Строковые литералы». ISO / IEC 9899: 1999 - Языки программирования - C.
  12. ^ «6.4.5 Строковые литералы». ISO / IEC 9899: 2011 - Языки программирования - C.
  13. ^ В чем разница между ошибкой сегментации и переполнением стека? в Переполнение стека

внешняя ссылка