Из Википедии, бесплатной энциклопедии
  (Перенаправлено с SIGSEGV )
Перейти к навигации Перейти к поиску

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

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

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

Обзор [ править ]

Пример сигнала, генерируемого человеком
Указатель NULL разыменования на Windows 8

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

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

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

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

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

Причины [ править ]

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

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

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

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

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

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

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

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

Обработка [ править ]

Действие по умолчанию для ошибки сегментации или ошибки шины - ненормальное завершение процесса, который ее вызвал. Файл ядра может быть создан , чтобы помочь отладки, а также другие действия зависит от платформы также могут быть выполнены. Например, системы 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 ;

Переполнение буфера [ править ]

Переполнение стека [ править ]

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

int  main ( void ) {  main ();  возврат  0 ; }

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

См. Также [ править ]

  • Дамп ядра
  • Общая неисправность защиты
  • Ошибка страницы
  • Нарушение хранилища
  • Гуру Медитация

Ссылки [ править ]

  1. ^ Экспертное программирование C: секреты глубокого C Питера Ван дер Линдена, стр.188
  2. ^ Язык программирования Rust - Право собственности
  3. ^ Бесстрашный параллелизм с Rust - Блог о языке программирования Rust
  4. ^ Маккарти, Джон (апрель 1960). «Рекурсивные функции символьных выражений и их машинное вычисление, Часть I» . Коммуникации ACM . 4 (3): 184–195. DOI : 10.1145 / 367177.367199 . S2CID  1489409 . Проверено 22 сентября 2018 .
  5. ^ Дурджати, Динакар; Ковшик, Сумант; Адве, Викрам; Латтнер, Крис (1 января 2003 г.). «Безопасность памяти без проверок во время выполнения или сборки мусора» (PDF) . Труды конференции ACM SIGPLAN 2003 г. по языку, компилятору и инструментам для встроенных систем . ACM: 69–80. DOI : 10.1145 / 780732.780743 . ISBN  1581136471. S2CID  1459540 . Проверено 22 сентября 2018 .
  6. ^ «Отладка ошибок сегментации и проблем указателя - Cprogramming.com» . www.cprogramming.com . Источник 2021-02-03 .
  7. ^ «Чистое восстановление после Segfaults под Windows и Linux (32-бит, x86)» . Проверено 23 августа 2020 .
  8. ^ «Реализация обработчика SIGSEGV / SIGABRT, который печатает трассировку стека отладки» . Проверено 23 августа 2020 .
  9. ^ «Как определить операции чтения или записи ошибки страницы при использовании обработчика sigaction на SIGSEGV? (LINUX)» . Проверено 23 августа 2020 .
  10. ^ "LINUX - ОБРАБОТКА НЕИСПРАВНОСТЕЙ ЗАПИСИ" . Проверено 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. ^ В чем разница между ошибкой сегментации и переполнением стека? при переполнении стека

Внешние ссылки [ править ]

  • Процесс: граница фокуса и ошибка сегментации
  • Часто задаваемые вопросы: ответы пользователей относительно определения ошибки сегментации
  • Объяснение "нулевого указателя"
  • Ответ на вопрос: NULL гарантированно равен 0, а нулевой указатель - нет?
  • Базовые спецификации открытой группы, выпуск 6 signal.h