stringtranslate.com

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

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

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

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

Обзор

Пример сигнала, генерируемого человеком
Ошибка сегментации, затрагивающая Krita в среде рабочего стола KDE
Разыменование нулевого указателя в Windows 8

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

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

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

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

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

Причины

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

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

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

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

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

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

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

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

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

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

Примеры

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

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

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

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

int main ( void ) { char * s = «привет, мир» ; * s = 'Ч' ; }        

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

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

Трассировка основного файла из GDB :

Программа получила сигнал SIGSEGV , ошибка сегментации . 0x1c0005c2 в main () в segfault . с : 6 6 * s = 'H' ;             

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

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

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

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

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

интервал * ptr = NULL ; printf ( "%d" , * ptr );    

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

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

интервал * ptr = NULL ; * ПТР = 1 ;     

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

интервал * ptr = NULL ; * ПТР ;   

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

Следующий код обращается к массиву символов sза его верхней границей. В зависимости от компилятора и процессора это может привести к ошибке сегментации.

char s [] = «Привет, мир» ; символ c = s [ 20 ];      

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

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

int main ( void ) { return main (); }   

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

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

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

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

Внешние ссылки