stringtranslate.com

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

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

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

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

Обзор

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

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

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

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

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

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

Причины

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

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

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

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

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

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

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

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

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

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

Примеры

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

Запись в память, доступную только для чтения

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

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

int main ( void ) { char * s = "привет мир" ; * s = 'H' ; }        

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

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

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

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

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

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

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

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

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

int * ptr = NULL ; printf ( "%d" , * ptr );    

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

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

int * ptr = NULL ; * ptr = 1 ;     

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

int * ptr = NULL ; * ptr ;   

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

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

char s [] = "привет, мир" ; char c = s [ 20 ];      

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

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

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

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

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

Ссылки

  1. ^ Экспертное программирование на C: глубокие секреты C. Питер Ван дер Линден, стр. 188.
  2. ^ «Язык программирования Rust — Право собственности».
  3. ^ «Бесстрашный параллелизм с Rust — блог о языке программирования Rust».
  4. ^ Маккарти, Джон (апрель 1960 г.). «Рекурсивные функции символических выражений и их вычисление машиной, часть I». Communications of the ACM . 4 (3): 184–195. doi : 10.1145/367177.367199 . S2CID  1489409. Получено 22.09.2018 .
  5. ^ Dhurjati, Dinakar; Kowshik, Sumant; Adve, Vikram; Lattner, Chris (1 января 2003 г.). "Безопасность памяти без проверок во время выполнения или сборки мусора" (PDF) . Труды конференции ACM SIGPLAN 2003 г. по языку, компилятору и инструменту для встраиваемых систем . Том 38. ACM. стр. 69–80. doi :10.1145/780732.780743. ISBN 1581136471. S2CID  1459540 . Получено 2018-09-22 .
  6. ^ "Отладка ошибок сегментации и проблем с указателями - Cprogramming.com". www.cprogramming.com . Получено 2021-02-03 .
  7. ^ "Чистое восстановление после Segfaults в Windows и Linux (32-бит, x86)" . Получено 2020-08-23 .
  8. ^ "Реализация обработчика SIGSEGV/SIGABRT, который печатает трассировку стека отладки". GitHub . Получено 2020-08-23 .
  9. ^ "Как определить операции чтения или записи ошибки страницы при использовании обработчика sigaction на SIGSEGV? (LINUX)" . Получено 2020-08-23 .
  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. ^ "В чем разница между ошибкой сегментации и переполнением стека?". Stack Overflow . Получено 2023-11-11 .

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