Разработка ОС: как избежать бесконечного цикла после процедуры исключения - PullRequest
2 голосов
/ 05 февраля 2012

Несколько месяцев я работал над «самодельной» операционной системой. В настоящее время он загружается и переходит в 32-битный защищенный режим. Я загрузил таблицу прерываний, но пока не настроил нумерацию страниц.

Теперь, когда я писал свои подпрограммы исключения, я заметил, что когда инструкция выдает исключение, выполняется подпрограмма исключения, но затем ЦПУ возвращается к инструкции, которая вызвала исключение! Это не относится к каждому исключению (например, исключение деления на ноль будет возвращаться к инструкции ПОСЛЕ инструкции по делению), но давайте рассмотрим следующее общее исключение защиты:

MOV EAX, 0x8
MOV CS, EAX

Моя процедура проста: она вызывает функцию, которая отображает красное сообщение об ошибке.

Результат: MOV CS, EAX завершается ошибкой -> Отображается мое сообщение об ошибке -> Процессор возвращается к MOV CS -> бесконечный цикл, спамит сообщение об ошибке.

Я говорил об этой проблеме с учителем по операционным системам и безопасности Unix. Он сказал мне, что знает, что у Linux есть способ обойти это, но он не знает, какой именно.

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

Поэтому мне было интересно, есть ли другой способ решения проблемы. Может быть, есть «магический» регистр, который содержит бит, который может изменить это поведение?

-

Большое спасибо заранее за любые предложения / информацию.

-

РЕДАКТИРОВАТЬ : Кажется, многие люди задаются вопросом, почему я хочу пропустить проблемную инструкцию и возобновить нормальное выполнение.

У меня есть две причины для этого:

  1. Прежде всего, убийство процесса было бы возможным решением, но не чистым. Это не так, как это делается в Linux, например, где (AFAIK) ядро ​​посылает сигнал (я думаю, SIGSEGV), но не сразу прерывает выполнение. Это имеет смысл, поскольку приложение может блокировать или игнорировать сигнал и возобновить собственное выполнение. Это очень элегантный способ сообщить приложению, что он что-то не так сделал IMO.

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

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

Ответы [ 4 ]

3 голосов
/ 08 февраля 2012

Разные исключения имеют разные причины. Некоторые исключения являются нормальными, и исключение только сообщает ядру, что ему нужно сделать, прежде чем разрешить программному обеспечению продолжить работу. Примеры этого включают ошибку страницы, сообщающую ядру, что ему нужно загрузить данные из пространства подкачки, неопределенное исключение инструкции, говорящее ядру, что оно должно эмулировать инструкцию, которую не поддерживает ЦП, или исключение отладки / точки останова, сообщающее ядру об этом Необходимо уведомить отладчик. Для них нормально, чтобы ядро ​​все исправило и продолжило молча.

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

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

  • войти в ванную
  • снять штаны
  • 1012 * сидячий *
  • начать генерировать вывод

Теперь представьте, что шаг 2 не выполнен, потому что вы носите шорты (исключение «не могу найти штаны»). Вы хотите остановиться на этом (с хорошим, понятным сообщением об ошибке или чем-то в этом роде) или игнорировать этот шаг и попытаться выяснить, что пошло не так, после того, как исчезла вся полезная диагностическая информация?

2 голосов
/ 05 февраля 2012

То, что вы видите, является характеристикой исключений общей защиты. В Руководстве по системному программированию Intel четко указано, что (6.15 Исключение и ссылка на прерывание / прерывание 13 - Общее исключение защиты (#GP)):

Saved Instruction Pointer
The saved contents of CS and EIP registers point to the instruction that generated the
exception.

Следовательно, вам нужно написать обработчик исключений, который пропустит эту инструкцию (что было бы странно), или просто убить вызывающий процесс с «Исключением общей защиты на $SAVED_EIP» или похожим сообщением.

2 голосов
/ 05 февраля 2012

Если я правильно понимаю, вы хотите пропустить инструкцию, вызвавшую исключение (например, mov cs, eax), и продолжить выполнение программы при следующей инструкции.

Зачем вам это нужно?Как правило, разве остальная часть программы не должна зависеть от результатов успешного выполнения этой инструкции?

В целом существует три подхода к обработке исключений:

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

  • Восстановите среду и затем снова выполните инструкцию.Например, сбои страниц иногда обрабатываются таким образом.

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

0 голосов
/ 06 февраля 2012

Я могу представить себе несколько ситуаций, в которых кто-то захочет ответить на GPF, анализируя сбойную инструкцию, эмулируя ее работу, а затем возвращаясь к инструкции после. Нормальным шаблоном было бы установить все так, чтобы инструкция, если она была повторена, была бы успешной, но можно, например, иметь некоторый код, который ожидает доступа к некоторому оборудованию по адресам 0x000A0000-0x000AFFFF и желающий запустить его на машине, на которой отсутствует такое оборудование. В такой ситуации, возможно, не захочется когда-либо хранить «реальную» память в этом пространстве, поскольку каждый отдельный доступ должен быть захвачен и обработан отдельно. Я не уверен, есть ли способ справиться с этим без необходимости декодировать какую-либо инструкцию, пытавшуюся получить доступ к этой памяти, хотя я знаю, что некоторые программы для виртуальных ПК справляются с этим довольно хорошо.

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

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

...