Что означают сообщения ядра Linux с ошибкой сегментации на 64-битной платформе x86

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

Пример

Значение этого следующее:

  • 'testp[1234]' - это неисправная программа и ее PID.
  • 'segfault at 0' - говорит нам адрес памяти (в шестнадцатеричном формате), который вызвал ошибку, когда программа пыталась получить к нему доступ. В данном случае адрес равен 0, так что мы имеем какое-то нулевое разыменование.
  • 'ip 0000000000401272' - это значение указателя инструкции во время ошибки. Это должна быть инструкция, которая пыталась выполнить некорректный доступ к памяти. В 64-битном x86 это будет регистр %rip (полезно для проверки в GDB и других местах).
  • 'sp 00007fff2ce4d230' - значение указателя стека. В 64-битном x86 это будет %rsp.
  • 'error 4' - это биты кода ошибки сбойной страницы из traps.h в шестнадцатеричном формате, как обычно, и почти всегда будет не менее 4 (что означает "доступ в пользовательском режиме"). Значение 4 означает, что это было чтение неразмеченной области, например, адреса 0, а значение 6 (4+2) означает, что это была запись неразмеченной области.
  • 'in testp[300000+98000]' говорит нам о конкретной области виртуальной памяти, в которой находится указатель инструкции, указывая, какой это файл (здесь это исполняемый файл), начальный адрес, по которому отображается VMA (0x300000), и размер отображения (0x98000).

С адресом ошибки 0 и кодом ошибки 4 мы знаем, что эта конкретная ошибка является чтением нулевого указателя.

Вот еще два сообщения об ошибках:

'Error 6' означает запись на не отображенный пользовательский адрес, здесь 0x1054807.

Ошибка 4 и адрес 0 - это чтение нулевого указателя, но на этот раз в какой-то функции libc, а не в собственном коде bash, поскольку об этом сообщается как 'in libc-2.23.so'.

В 64-битном x86 Linux вы получите несколько иное сообщение, если проблема на самом деле в выполняемой инструкции, а не в адресе, на который она ссылается. Например:

В файле traps.c есть целый ряд подобных типов ловушек. Два заметных дополнительных - это 'ошибка деления', которую вы получите, если выполните целочисленное деление на ноль, и 'общая защита', которую вы можете получить для некоторых чрезвычайно диких указателей (один известный мне случай - когда ваш 64-битный x86-адрес не имеет "канонической формы"). Хотя эти поля имеют несколько иной формат, большинство из них означают то же самое, что и в segfaults. Исключением является 'error:0', который не является кодом ошибки страничного сбоя.

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

Если скомпилировать (лучше всего без оптимизации) и запустить, это сгенерирует сообщение ядра:

Бит '(null)' оказался ожидаемым; это то, что генерирует функция общего ядра printf(), когда ее просят вывести что-то в виде указателя, а он равен нулю (как показано здесь). В нашем случае указатель инструкции равен 0 (null), потому что мы сделали вызов подпрограммы через нулевой указатель и таким образом пытаемся выполнить код по адресу 0. Я не знаю, почему часть 'in ...' говорит, что мы находимся в исполняемом файле (хотя в данном случае вызов действительно был там).

Код ошибки 14 в шестнадцатеричном формате, что означает, что в битах это 010100. Это чтение немаркированной области в режиме пользователя (наш обычный случай '4'), но это выборка инструкций, а не обычное чтение или запись данных. Любая ошибка 14 является признаком какого-либо искаженного вызова функции или возврата по искаженному адресу, поскольку стек был поврежден.

Для 64-битных x86 ядер Linux (и, возможно, для 32-битных x86 тоже), код, на который вы хотите посмотреть, это show_signal_msg в fault.c, который печатает общее сообщение 'segfault at ...', do_trap и do_general_protection в traps.c, которые печатают сообщения 'trap ...', и print_vma_addr в memory.c, который печатает часть 'in ...' для всех этих сообщений.

Различные биты кода ошибки в виде чисел

  • +1 ошибка защиты в сопоставленной области (например, запись в сопоставление, доступное только для чтения)
  • +2 запись (вместо чтения)
  • +4 доступ в режиме пользователя (вместо доступа в режиме ядра)
  • +8 обнаружено использование зарезервированных битов в записи таблицы страниц (ядро будет паниковать, если это произойдет)
  • +16 (+0x10) ошибка была выборкой инструкции, а не чтением или записью данных
  • +32 (+0x20) 'доступ к блоку ключей защиты'.

Hex 0x14 - это 0x10 + 4; (hex) 6 - это 4 + 2. Код ошибки 7 (0x7) - это 4 + 2 + 1, запись в режиме пользователя в отображение, доступное только для чтения, и это то, что вы получите, если попытаетесь записать в строковую константу в C:

Скомпилируйте и запустите это, и вы получите:

Похоже, что программный код всегда загружается по адресу 0x400000 для обычных программ, хотя я полагаю, что у разделяемых библиотек их расположение может быть рандомизировано.

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

Понравилась статья? Поделиться с друзьями:
Добавить комментарий