вторник, 27 августа 2013 г.

PhantomEx: Переход в пользовательский режим - лабораторная работа

В прошлой статье мы переключили нашу ОС в пользовательский режим. Рассмотрим теперь этот процесс подробнее.

Для этого выполним пошаговую отладку процесса переключения. Для этого необходимо настроить удаленную отладку ядра, например используя связку Eclipse CDT  + GDB + VMware. О том как настроить эту систему я рассказывал в одной из статей цикла.

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

 

1. Процесс переключения в режим пользователя


Запустим процесс отладки и поставим точку останова на функции init_user_mode(...). Дойдя до этой точки остановимся и посмотрим что мы имеем
Инициализация пользовательского стека

Здесь мы выделяем память под стек в куче ядра объемом в 16 Кб. В отладчике можно посмотреть какой указатель мы получили на выделенную область памяти и с учетом её размера получить адрес вершины стека  - 0x7f00c19f. Запомним это число, именно оно вместе с адресом точки входа - функции task04() - идет в качестве параметра в функцию user_mode_switch(...). Зайдем внутрь этой функции.

Подготовка сегментных регистров - загрузка селекторов сегмента данных
Как уже многократно говорилось, только регистры DS, ES, FS и GS подлежат инициализации явным образом. Здесь мы загружаем два из них селектором сегмента данных пользовательского режима - этот селектор у нас имеет индекс 0x04 в GDT, и, с учетом RPL = 0x03, его селектор: 0x04*0x08 | RPL = 0x23.

Ситуация после загрузки селекторов сегментов данных
Для полного перехода в Ring 3 необходимо переключить CS и SS, однако явно это сделать невозможно, поэтому необходимо воспользоваться способом, описанным в теоретической части.

Формируем стек специальным образом, последовательно помещая туда значение селектора сегмента стека - 0x23, указатель на вершину нового стека, в нашем случае это значение 0x7f00c9f переданное в функцию как параметр, регистр флагов EFLAGS, селектор сегмента кода, имеющий индекс в GDT 0x03 и RPL = 3, и равный, поэтому: 0x03*0x08 | 0x03 = 0x1B, и наконец адрес точки входа в task04() равный 0x10019f . Перед вызовом IRET имеет такую картину

Стековый кадр, сформированный для перехода в Ring 3
Теперь выполняем команду IRET, и... готово!

Сразу после "прыжка" в Ring 3 - результат выполнения IRET
Видим, что IRET в соответствии с теорией верно зарядил все сегментные регистры нужными селекторами, переключил процессор на созданный для пользовательского потока стек  и передал управления на точку входа в поток пользователя.

Теперь шлепнем точку останова внутрь цикла в пользовательском потоке и сделаем несколько переключений задач - просто в eclipse жмем F8 возвращаясь к нашему брейкпоинту. Получим такую картинку

Переключение задачи на пользовательский поток #4
Вспомните, что в каждом потоке мы выводим на экран вершину стека ядра, которая помещается в TSS при каждом переключении. Теперь обратите внимание, что значение хранимое в TSS отличается от того, которое реально использует пользовательский поток - для него мы создали свой стек и он работает с ним. А вот вершина стека хранимая в TSS может быть использована при переходе в Ring 0 из потока с Ring 3 при выполнении системного вызова, о чем мы будем, разумеется, говорить.

Переключимся теперь на поток ядра, скажем на поток #2

Переключение задачи в поток ядра #2
Тут можно увидеть что значение, хранимое в TSS, и ESP процессора указывают на один и тот же стек. Это верно, так как при выполнение данного потока мы находимся в Ring 0, в чем можно убедится, взглянув на значения сегментных регистров.

2. Выполнение привилегированных команд на разных уровнях привилегий


Добавим в код потоков какую-нибудь функцию, использующую привилегированную команду. Например, у нас есть функция inb(...) читающая байт из порта ввода-вывода. Добавим эту функцию в потоки 2 и 4, для примера прочтя байт состояния клавиатуры на порте 0x64.

u8int keyb = inb(0x64);

При выполнении потока #4 нам далеко не удастся уйти от этой инструкции - произойдет исключение #GP.

Исключение #GP(0) при попытке выполнения привилегированной команды INB
Если мы уберем вызов inb(...) из потока #4 система будет работать нормально.
Выполнение потоком #2 привилегированной инструкции не приводит к исключению
Разберемся с причиной исключения, как если бы мы отлаживали наше ядро. Поставим внутрь имеющегося у нас обработчика исключения #GP точку останова и перезапустим систему. Возникнет исключение, и мы остановимся вот здесь

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

Внутрь обработчика у нас передается структура regs, содержащая значения регистров в момент исключения. Нас интересует значение EIP = 0x100491 - команда по этому адресу вызвала данное исключение. Взглянув на дизассемблерный листинг ядра, видим эту команду

Команда-виновница на дизассемблерном листинге
Посмотрим ещё раз состояние процессора перед её выполнением в пользовательском потоке
Ситуация непосредственно перед возникновением #GP
Видим, что состояние процессора таково, что не выполняется условие на корректную работу команды IN, поэтому закономерно генерируется исключение #GP, сразу после её выполнения
Сразу после выполнения IN - на пути в обработчик исключение #GP
Таким образом мы действительно находимся в пользовательском режиме, и можем наблюдать работу механизмов защиты.

Заключение


Что же выходит - находясь в режиме 3-го кольца защиты мы не можем осуществлять ввод-вывод через порты, то есть работать с устройствами? Да, не можем. Это недопустимо для пользовательского кода - прямой доступ к оборудованию со стороны пользователя может привести к краху системы, поэтому такие механизмы и предусмотрены в архитектуре процессора x86.

А как же, спросите Вы, осуществляется ввод-вывод в файлы из пользовательских программ? Для этого применяется специальный механизм выхода на уровень Ring 0 и выполнения привилегированного кода, регламентируемый операционной системой и называемый системным вызовом. О них мы и поговорим в следующий раз )