Ядерная архитектура ОС Windows презентация

Содержание

Слайд 2

Применение знаний ядра ОС Средства защиты информации – системы шифрования

Применение знаний ядра ОС

Средства защиты информации – системы шифрования сетевого трафика,

защита данных на диске (виртуальные диски, фильтр-драйверы, защищенные файловые системы)
Вредоносное ПО
Средства анализа в самом широком смысле (антивирусы, отладчики, системные утилиты)
Знание ядерной архитектуры важно для аналитика программного обеспечения
Слайд 3

Применение знаний при разработке инфраструктуры анализа бинарного кода Драйвер получения

Применение знаний при разработке инфраструктуры анализа бинарного кода

Драйвер получения информации: список

процессов, модулей и копии адресных пространств процессов
Алгоритмы в составе инфраструктуры анализа, позволяющие выявлять: переключение процессов/потоков/адресных пространств, обмен данными между адресными пространствами, механизмы межпоточной синхронизации и др.
Слайд 4

Структура курса Лекции + лабораторные задания: простейший драйвер ядра создание

Структура курса

Лекции + лабораторные задания:
простейший драйвер ядра
создание устройств и символических ссылок

драйвером
консольная прикладная программа для обращения к драйверу
диалоговая прикладная программа управления драйвером
взаимодействие драйвера с прикладной программой: Read, Write, DeviceIoControl
драйвер-фильтр клавиатуры
перехват системных сервисов
Получение копии адресного пространства чужого процесса – на уровне VAD и на уровне каталога страниц
Слайд 5

Программное обеспечение и литература Среда разработки для выполнения лабораторных работ

Программное обеспечение и литература

Среда разработки для выполнения лабораторных работ развернута на

виртуальной машине VirtualBox
Там же:
Слайды к лекциям
Задания на лабораторные работы
Дополнительная литература
Внутреннее устройство Windows, 7-е издание. Руссинович, Соломон, Ионеску, Йосифович, 2018
Programming the Microsoft Windows Driver Model, 2-е издание. Walter Oney, 2003
Слайд 6

VisualStudio 2008/2010 (WS 2010 не рекомендуется при отсутствии подключения к

VisualStudio 2008/2010 (WS 2010 не рекомендуется при отсутствии подключения к интернету)
Пакет

WDK 7.1.0, поддерживает все ОС начиная с WinXP
Для интеграции WDK с VisualStudio – сторонний продукт VisualDDK (http://visualddk.sysprog.org), без него – утилита build, работающая в режиме командной строки
либо
VisualStudio2019 + пакет WDK for Windows 10 version 2004 (по состоянию на 01.09.2020)
Утилиты SysInternals (www.sysinternals.com – сейчас часть проекта MS TechNet) – как минимум DbgView, WinObj, ProcExp, ProcMon, AutoRuns. Лучше брать SysinternalsSuite.
Примечание: В пакете WDK для Windows 8 впервые официально заявлена возможность использования IDE (Integrated Development Environment) VisualStudio, но с рядом ограничений (в частности не поддерживается разработка драйверов для XP, поддерживает только WDF/KMDF-драйверы. Но фактически создание Legacy-драйверов все равно возможно через мастер создания WDM-драйвера, также оставлена поддержка консольной сборки

Среда разработки

Слайд 7

Основные понятия Процессы, потоки и адресные пространства в ОС Windows:

Основные понятия

Процессы, потоки и адресные пространства в ОС Windows: взаимосвязь с

процессорной архитектурой
Архитектура процессора в защищенном режиме предоставляет для использования разработчикам ОС некоторые механизмы:
Защита памяти («кольца защиты»)
Сегментация
Cтраничная организация памяти
Многозадачность (task switching)
особенности использования этих процессорных механизмов во многом определяют архитектуру ОС, в особенности рассматриваемые понятия: термины процесс, поток и адресное пространство имеют смысл только в рамках конкретной ОС
Слайд 8

Защита памяти по привилегиям («кольца защиты») 0 1 2 3

Защита памяти по привилегиям («кольца защиты»)

0

1

2

3

User mode

Kernelmode

Передача управления

Доступ к данным

Важной особенностью

реализации механизма колец защиты является то, что для каждого кольца защиты хранится своя копия стековых регистров (ESP), и при переключении кольца защиты аппаратура процессора осуществляет замену текущего значения ESP на сохраненную копию, соответствующую новому кольцу защиты.
Слайд 9

Особенности организации памяти в Windows на примере 32-разрядной адресации Вирт. адрес

Особенности организации памяти в Windows на примере 32-разрядной адресации

Вирт. адрес

Слайд 10

VMCS Трансляция памяти при использовании гипервизора с включенным EPT Вирт.

VMCS

Трансляция памяти при использовании гипервизора с включенным EPT

Вирт. адрес

селектор:смещение

TLB
(буфер ассоциативной трансляции)

Линейный

адрес

Физич. адрес

Host- адрес

CR3

GDT/LDT

EPT Base Register

Сегментное преобразование

Страничное преобразование

EPT
Extended Page Tables

Виртуальная машина

гипервизор

SMM

Intel:
двойной гипервизор

Вариант без EPT: виртуальный TLB в гипервизоре

Слайд 11

ОС NT, хотя и использует селекторы, но использует их в

ОС NT, хотя и использует селекторы, но использует их в минимальной

степени.
NT реализует плоскую 32-разрядную модель памяти с размером линейного адресного пространства 4 Гб (232 байт).
В NT определено 11 селекторов, из которых нас будут интересовать всего 4:

Эти 4 селектора позволяют адресовать все 4Гб линейного адресного пространства, разница только в режиме доступа.
Первые 2 селектора имеют DPL=0 и используются драйверами и системными компонентами для доступа к системному коду, данным и стеку.
Вторые 2 селектора используются кодом пользовательского режима для доступа к коду, данным и стеку пользовательского режима. Эти селекторы являются константами для ОС NT.
Сегментное преобразование пары селектор:смещение дает 32-битный линейный адрес, для всех рассматриваемых селекторов совпадающее со значением смещения виртуального адреса.

Слайд 12

Реальная защита памяти по привилегиям, как и реализация понятия адресное

Реальная защита памяти по привилегиям, как и реализация понятия адресное пространство,

осуществляется посредством механизма страничной организации памяти:
Каждый контекст памяти (адресное пространство процесса) описывается своим каталогом страниц. Физический адрес текущего каталога страниц содержится в регистре CR3
элемент таблицы страниц содержит бит, указывающий на возможность доступа к странице из пользовательского режима (0 - доступ только ядру, 1 - доступ пользовательскому режиму). При этом все страницы доступны из режима ядра. Таким образом, механизм защиты страниц использует всего 2 уровня привилегий - 0 (ядро) и 3 (пользователь). С помощью этого механизма адресное пространство делится на 2 части: системную и пользовательскую.
элемент таблицы страниц содержит бит, указывающий на возможность записи в соответствующую страницу памяти (0 - доступ только на чтение, 1 - чтение/запись). Чтение возможно всегда, когда разрешен доступ по привилегиям.
В некоторых режимах элемент таблицы страниц содержит бит, определяющий соответствующую страницу как данные (запрет на выполнение кода с данной страницы) – см. DEP, NX bit, XD bit
Слайд 13

Виртуальное адресное пространство процесса 0 2Гб 4Гб user kernel 0

Виртуальное адресное пространство процесса

0

2Гб

4Гб

user

kernel

0

3Гб

4Гб

user

kernel

Код и данные прикладных программ

Код и данные ядра,

память устройств

user

kernel

Модули и объекты ядра

Модули и объекты прикладной программы

Адресное пространство в 32 разрядном режиме

Границу между пользовательским и системным диапазоном можно сдвинуть

Взаимодействие между кодом пользовательского режима и ядром часто изображают так:

Слайд 14

Каталоги страниц всех процессов организованы так, что преобразование линейного в

Каталоги страниц всех процессов организованы так, что преобразование линейного в физический

адрес для системного диапазона любого адресного пространства совпадают – диапазон ядра «общий» для всех адресных пространств

0

4Гб

user

kernel

Код и данные процесса 1

ядро

Код и данные процесса N


Адресное пространство 1

Адресное пространство N

Слайд 15

Слайд 16

Каталоги страниц позволяют создавать «совместно используемые» как внутри одного, так

Каталоги страниц позволяют создавать «совместно используемые» как внутри одного, так и

между несколькими адресными пространствами области памяти путем трансляции разных виртуальных адресов в одинаковые физические.

Физическая память

Адресное пространство 1

Адресное пространство 2

Слайд 17

Разновидность такого механизма – copy-on-write – используется для реализации механизма

Разновидность такого механизма – copy-on-write – используется для реализации механизма отображения

файлов в память, через который в свою очередь подгружаются в адресное пространство все исполняемые модули - .exe, .dll, .sys и т.п.

Физическая память

Адресное пространство 1

Адресное пространство 2

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

При попытке записи в какую-либо из таких страниц в одном из адресных пространств это адресное пространство вначале получает свою уникальную копию этой страницы в физической памяти, и только затем в эту копию осуществляется запись

Физическая память

Адресное пространство 1

Адресное пространство 2

модификация

Исходная копия страницы

Модиф. копия страницы

Слайд 18

Загрузка модулей в адресное пространство Исполняемые файлы в ОС Windows

Загрузка модулей в адресное пространство

Исполняемые файлы в ОС Windows имеют формат

PE (Portable Executable).
При запуске программы создается адресное пространство процесса, причем системный диапазон адр. простр. запускаемого процесса создается простым копированием части каталога страниц запускающего процесса.
Затем в пользовательский диапазон памяти отображается исполняемый модуль (обычно .exe) в соответствии с предпочтительным адресом загрузки, прописанном в PE-заголовке (задается компилятором, для MS VisualStudio обычно 0x400000).
Далее в соответствии с таблицей импорта модуля начинается подгрузка (также через механизм отображения в память) всех динамических библиотек, с которыми связан текущий загружаемый модуль. Каждая такая библиотека (обычно .dll) также имеет формат PE, и для нее весь процесс повторяется заново.
Если предпочтительный адрес загрузки уже занят, загрузчик перебазирует модуль на свободный участок виртуальной памяти. Кроме того, компилятор может создавать неперебазируемые модули, которые обязаны грузиться строго по указанному в заголовке адресу (обычно используется для некоторых модулей ядра). Сложность перебазирования определяется необходимостью загрузчику модифицировать инструкции, осуществляющие обращения по глобальным адресам (например при доступе к глобальным переменным) – компилятор формирует адреса таких переменных исходя из назначенного им же базового адреса загрузки, при перебазировании все такие адреса должны быть модифицированы. Список адресов кода, в которые требуется внести поправки при перебазировании, хранится в секции поправок (relocation table).
Слайд 19

При завершении загрузки модуля со всеми его зависимостями управление передается

При завершении загрузки модуля со всеми его зависимостями управление передается на

функцию – точку входа в модуль, прописанную в его PE-заголовке (это касается всех загружаемых модулей).
После отображения в память содержимое файла все еще не прочитано с диска. Такой файл начинает выступать в роли файла подкачки, а реальное чтение страниц произойдет при первом обращении к соответствующему адресу виртуальной памяти.
Даже после выгрузки модуля ОС по возможности сохраняет информацию о выполненном ранее отображении файла и его физические страницы, для ускорения возможной повторной загрузки модуля.
Перебазирование модуля – почти катастрофическая ситуация для ОС, поскольку вносимые поправки приводят к увеличению страниц физической памяти за счет срабатывания механизма copy-on-write. Кроме того, для перебазированного модуля становится затруднительным использовать оставшиеся от предыдущей загрузки страницы памяти.
В ОС начиная с Vista появился механизм Address Space Layout Randomization (ASLR), предназначенный для усложнения атак со стороны вредоносного кода, направленных на прямой доступ к коду и данным (в т.ч. стеку) системных библиотек. В соответствии с этим механизмом, модули размещаются по «случайно» выбираемым адресам.
В силу указанных выше проблем с перебазированием ОС не может допустить случайное размещение модуля в разных адресных пространствах. Вместо этого «случайные» адреса загрузки всех модулей вычисляются разово для всех модулей всех адресных пространств, и в ходе текущего сеанса работы ОС не пересчитываются.
Слайд 20

Слайд 21

Поддержка многозадачности Механизм аппаратной поддержки многозадачности, предоставляемый архитектурой x86 (task

Поддержка многозадачности

Механизм аппаратной поддержки многозадачности, предоставляемый архитектурой x86 (task switching) –

в Windows не используется при штатном функционировании ОС. Единственный случай его применения – при обработке BSOD (“синий экран”), т.е. при фатальном сбое в работе ядра ОС, для обработки которого на время вывода диагностических сообщений и формирования crashdump требуется работоспособная программно-аппаратная среда. В момент штатной работы ОС все происходит в рамках одного task’а, для обработки BSOD происходит переключение в другую задачу, выхода из нее уже нет.
Собственно многозадачность в терминах ОС Windows реализуется программно и будет рассмотрена позже.
Слайд 22

Основные понятия: подведение итогов Процесс – соответствует некоторому запущенному в

Основные понятия: подведение итогов

Процесс – соответствует некоторому запущенному в ОС приложению.


Процесс можно рассматривать как контейнер ресурсов, выделяемых приложению для работы.
Одним из важнейших ресурсов является адресное пространство. Каждому процессу выделяется свое собственное, единственное, адресное пространство.
Код выполняется в рамках потоков
Слайд 23

Адресное пространство – диапазон виртуальных адресов, доступный процессу. Для некоторой

Адресное пространство – диапазон виртуальных адресов, доступный процессу.
Для некоторой части

этих адресов средствами ОС при поддержке аппаратуры осуществляется трансляция в физические адреса, т.е. обращение к таким адресам будет либо немедленно перенаправлено в ОЗУ или память устройств, или приведет к задействованию механизма подкачки страниц с диска в ОЗУ с последующим доступом к ним в ОЗУ.
Если аппаратные механизмы контроля доступа к памяти не позволяют осуществить доступ, либо трансляция памяти для адресуемой виртуальной ячейке отсутствует, процессор генерирует исключение.
Адресное пространство разбито на 2 диапазона: по младшим адресам – диапазон памяти, доступный и коду пользовательского режима (user mode = 3 кольцо защиты), и коду ядра (kernel mode = 4 к.з.; по старшим адресам – диапазон памяти ядра (доступен только коду ядре).
Диапазон памяти ядра с точки трансляции адресов – «общий» для всех адресных пространств
Вся память устройств, отображаемая на адресуемую физическую память, в виртуальном адресном пространстве отображается в диапазон памяти ядра. Доступ к портам в/в коду user mode также закрыт, в результате прикладной уровень не имеет возможности прямой работы с оборудованием.
Слайд 24

TSS – Task State Segment Процессор определяет 5 структур для

TSS – Task State Segment

Процессор определяет 5 структур для организации переключения

задач (multitasking):
TSS - хранит состояние процессора
Дескриптор TSS (только в GDT, в LDT быть не может) – задает расположение TSS в памяти. Используется для вызова задачи с помощью специальных call/jmp
Task-gate descriptor/шлюз задачи (может находиться в GDT, LDT или IDT). Содержит значение селектора TSS. Используется для вызова задачи с помощью специальных call/jmp или int.
Task register TR
Флаг NT в регистре флагов EFLAGS
В защищенном режиме TSS и Дескриптор TSS должны быть созданы хотя бы для одной задачи; селектор, определяющий Дескриптор TSS , д.б. загружен в регистр задач TR (инструкцией LTR)
Слайд 25

Слайд 26

Переключение на другую задачу осуществляется в одном из 4 случаев:

Переключение на другую задачу осуществляется в одном из 4 случаев:
выполняется инструкция

JMP или CALL на дескриптор TSS в GDT.
выполняется инструкция JMP или CALL на шлюз задачи в GDT или LDT.
происходит прерывание через шлюз задачи в IDT.
выполняется инструкция IRET при установленном флаге NT (Nested Task).
Слайд 27

Обработка прерываний и исключений Два механизма прерывания работы программы interrupt

Обработка прерываний и исключений

Два механизма прерывания работы программы
interrupt – асинхронное событие,

как правило генерируемое устройством в/в.
exception – синхронное событие, генерируемое при попытке выполнения инструкции, в случае если состояние процессора соответствует одному или нескольким предопределенным условиям.
Три класса исключений:
Faults –сохраняемый cs:eip указывает на адрес вызвавшей исключение инструкции. Позволяет рестартовать инструкцию, вызвавшую исключение. Пример – деление на 0
Traps – исключение генерируется после выполнения вызвавшей его инструкции. сохраняемый cs:eip указывает на адрес следующей инструкции. Пример – отладочные прерывания.
Aborts – инструкция, вызвавшая исключение, не всегда определена и не м.б. рестартована.
Три вида дескрипторов прерываний (хранятся в IDT):
Task Gate – переключает задачу
Interrupt Gate
Trap Gate
Разница между Interrupt Gate и Trap Gate – при использовании Interrupt Gate очищается флаг IF (запрет маскируемых прерываний). При использовании Trap Gate флаг IF остается неизменным.
Слайд 28

DPL задает ограничение на уровень привилегий кода, который пытается вызвать

DPL задает ограничение на уровень привилегий кода, который пытается вызвать прерывание

(код 3-го кольца защиты не сможет вызвать прерывание с DPL=0, т.е. 0 кольца защиты).
селектор в Interrupt и Trap Gate определяет, на каком кольце защиты будет работать обработчик прерывания. В ОС Windows все обработчики прерываний работают в 0 кольце защиты.
Если при входе/выходе в обработчик прерывания меняется кольцо защиты работающего кода – меняется и стек (значение вершины стека берется из текущего TSS из поля, соответствующего новому номеру кольца защиты).
В противном случае стек не меняется.
Пример: любым системным отладчиком в пошаговом режиме пройти код, осуществляющий обнуление ESP, в двух случаях: в пользовательском режиме и в режиме ядра.
Слайд 29

EFLAGS EIP CS Error code Свободная часть стека Занятая часть

EFLAGS

EIP

CS

Error code

Свободная часть стека

Занятая часть стека

ESP перед вызовом прерывания

ESP после вызова

прерывания

Рост адресов

EFLAGS

EIP

CS

Error code

Свободная часть стека 0

Занятая часть стека 0

SS:ESP в процессе вызова прерывания после переключения кольца защиты – взяты из TSS

ESP после вызова прерывания

Рост адресов

Свободная часть стека 3

Занятая часть стека 3

SS:ESP перед вызовом прерывания

Рост адресов

Вызов прерывания: переключение 3->0 кольцо защиты (смена стека)

Вызов прерывания: без переключения колец защиты (стек не меняется)

ESP

SS

Слайд 30

Поток – единица исполнения в ОС Windows. Механизм вытесняющей многозадачности,

Поток – единица исполнения в ОС Windows. Механизм вытесняющей многозадачности, реализуемый

ОС, применяется именно к потокам.
Программный код исполняется в потоке, изначально для процесса создается один поток, который затем может создать дополнительные.
Все потоки одного процесса работают в рамках общего адресного пространства, но различаются контекстом – копией регистров, которая сохраняется перед приостановкой потока при исчерпании потоком кванта времени и подгружается при возобновлении потока при получении потоком очередного кванта времени.

Контекст потока (thread context) – термин, часто используемый в ядерной документации. Состояние работы потока определяется принадлежащей ему копией процессорных регистров, в том числе отдельной копией регистра вершины стека для каждого кольца защиты. Для ядерных функций различают два вида контекстов:
Arbitrary thread context – контекст случайного потока – как правило для функций, вызов которых произошел из-за возникновения асинхронного аппаратного события, прервав работу произвольного работающего потока (на рис. стрелка вверх)
Non-arbitrary thread context – контекст определенного потока – как правило – контекст потока, в котором произошла передача управления в код ядра (на рис. стрелка вниз)

user

kernel

Модули и объекты ядра

Модули и объекты прикладной программы

аппаратура

Слайд 31

Система приоритетов Windows NT имеет двухуровневую модель приоритетов: Приоритеты высшего

Система приоритетов

Windows NT имеет двухуровневую модель приоритетов:
Приоритеты высшего уровня (уровни запросов

прерываний - Interrupt ReQuest Level - IRQL) управляют аппаратными и программными прерываниями
приоритеты низшего уровня (приоритеты планирования) управляются планировщиком и управляют исполнением потоков
Слайд 32

Классы приоритетов в Win32 API 0 31 для 32-разрядных ОС

Классы приоритетов в Win32 API

0

31 для 32-разрядных ОС
15 для 64-разрядных ОС

Приоритет

из этого диапазона можно назначить, только обладая полномочиями администратора

Ядро возвращает управление коду user mode только при уровне IRQL PASSIVE_LEVEL

Слайд 33

Приоритеты планирования Схема приоритетов, реализуемая ядром ОС: При запуске потоку

Приоритеты планирования

Схема приоритетов, реализуемая ядром ОС:
При запуске потоку назначается базовый приоритет

(б.п.).
В процессе работы – у потока текущий приоритет (т.п.)
Если базовый приоритет в динамическом диапазоне, исходно т.п. = б.п., затем в процессе работы ОС может менять т.п. по правилу: б.п. ≤ т.п. ≤ 15
Если базовый приоритет диапазоне realtime, то всегда т.п. = б.п.
Win32 API скрывает эту схему: процессу назначается класс приоритета, а его потокам – относительный приоритет – значение ±2 относительно класса приоритета потока.
Вся эта схема все равно сводится к системе приоритетов, реализуемой ядром ОС.
Слайд 34

Планировщик Windows реализует «карусельную» (round-robbin) схему переключения потоков по правилу:

Планировщик Windows реализует «карусельную» (round-robbin) схему переключения потоков по правилу:
Если есть

потоки из диапазона realtime – переключение по карусельной схеме будет только для потоков с максимальным значением realtime-приоритета. Любые потоки с более низким приоритетом никогда не исполнятся.
Если потоков с realtime-приоритетом нет – карусельная схема для всех потоков с динамическим диапазоном приоритетов. Для исполнения выбираются потоки с максимальным т.п., но у потоков с более низким т.п. он иногда инкрементируется, и в конце концов поток даже с самым низким базовым приоритетом в конце концов получит квант времени, после чего его т.п. сбрасывается.
Слайд 35

Планировщик Windows реализует «карусельную» (round-robbin) схему переключения потоков по правилу:

Планировщик Windows реализует «карусельную» (round-robbin) схему переключения потоков по правилу:
Если есть

потоки из диапазона realtime – переключение по карусельной схеме будет только для потоков с максимальным значением realtime-приоритета.

Любые потоки с более низким приоритетом никогда не исполнятся.

24

24

24

24

24

20

13

8

0

Слайд 36

Если потоков с realtime-приоритетом нет – карусельная схема для всех

Если потоков с realtime-приоритетом нет – карусельная схема для всех потоков

с динамическим диапазоном приоритетов.
Для исполнения выбираются потоки с максимальным т.п., но у потоков с более низким т.п. он иногда инкрементируется, так что поток даже с самым низким базовым приоритетом в конце концов получит квант времени, после чего его т.п. сбрасывается.

13

11

0

б.п.

т.п.

13

⬆12

⬆ 1

13

⬆13

⬆ 2

13

⬇11

⬆ 3

13

11

⬆13


Слайд 37

Диаграмма состояния потоков

Диаграмма состояния потоков

Слайд 38

Готов (Ready). Поток готов к выполнению/рестарту после завершения ожидания. При

Готов (Ready). Поток готов к выполнению/рестарту после завершения ожидания. При поиске

потока для выполнения диспетчер рассматривает только их.
Готовность с отложенным выполнением (Deferred ready). Используется для потоков, выбранных для выполнения на конкретном процессоре, но еще не запущенных на нем.
В повышенной готовности (Standby). Поток в состоянии повышенной готовности был выбран для запуска следующим на конкретном процессоре. Как только сложатся соответствующие условия, диспетчер выполнит контекстное переключение на этот поток. Для каждого процессора в системе в состоянии повышенной готовности может быть только один поток.
Выполнение (Running). Как только диспетчер выполняет переключение контекста на поток, тот входит в состояние выполнения. Выполнение прекращается когда: - истек квант времени (и готов другой поток), - вытеснение потоком с более высоким приоритетом, - завершение работы потока, - поток уступил выполнение - поток самостоятельно перешел в состояние ожидания.
Ожидание (Waiting). Поток может войти в состояние ожидания в нескольких случаях: - поток может самостоятельно ожидать объекта для синхронизации своего выполнения; - операционная система может ждать от имени потока (например, для разрешения страничного в/в) ; - подсистема среды может приказать потоку приостановиться. Когда ожидание потока заканчивается, то в зависимости от приоритета поток либо немедленно начинает выполняться, либо возвращается в состояние готовности.
Переходное состояние (Transition). Поток входит в переходное состояние, если он готов к выполнению, но его стек ядра выгружен из памяти. Как только его стек ядра вернется в память, поток войдет в состояние готовности.
Завершение (Terminated). Когда поток заканчивает выполнение, он входит в состояние завершения. Как только поток будет завершен, объект потока исполняющей системы (структура данных с описанием потока в невыгружаемом пуле) может быть освобожден или не освобожден (политику, определяющую время удаления объекта, устанавливает диспетчер объектов). Например, объект остается, если у потока остались открытые дескрипторы. Поток также может перейти в состояние завершения из других состояний, если он будет явно уничтожен другим потоком — например вызовом TerminateThread.
Инициализация (Initialized). Это состояние используется внутри системы при создании потока
Слайд 39

Уровни запросов прерываний (IRQL) IRQL – способ управления маскированием прерываний

Уровни запросов прерываний (IRQL)

IRQL – способ управления маскированием прерываний на конкретном

процессоре. По сути – это некоторое число, назначаемое конкретному процессору и обозначающее факт маскирования на нем некоторых прерываний. Конкретное значение IRQL из диапазона DIRQL (Device IRQL) также связывается с каждым обработчиком аппаратных прерываний. Если такое прерывание в данный момент не маскировано и оно доставлено процессору, ОС производит смену текущего IRQL процессора на IRQL прерывания, маскируя при этом все прерывания с IRQL ≤ нового IRQL процессора. При завершении обработчика прерывания значение IRQL процессора восстанавливается на старое.
На уровне IRQL_PASSIVE_LEVEL разрешены все прерывания и работает механизм многозадачности. Код пользовательского режима работает только на PASSIVE_LEVEL (возможно за исключением функций, вызываемых механизмом APC на уровне APC_LEVEL – надо проверить)
На уровне APC_LEVEL реализуется механизм асинхронного вызова процедур Asynchronous Procedure Call (APC), через который в частности ядро ОС реализует механизм обратного вызова функций (callback functions) прикладного уровня для уведомления прикладной программы ядром о возникновении некоторой ожидаемой ситуации. На этом уровне маскированы прерывания с IRQL= APC_LEVEL. Доступ к выгружаемой (paged) памяти разрешен.
Слайд 40

На уровне IRQL DISPATCH_LEVEL работают так называемые «отложенные процедуры» (Deferred

На уровне IRQL DISPATCH_LEVEL работают так называемые «отложенные процедуры» (Deferred Procedure

Calls - DPC), поэтому этот уровень иногда называют DPC_LEVEL. DPC создаются обработчиками прерываний, и туда выносится весь «долгоиграющий» код обработки прерывания. Задачей обработчика прерывания является как можно быстрее зафиксировать факт возникновения прерывания и данные, необходимые для дальнейшей обработки в DPC. Вызовы DPC помещаются в очередь (своя для каждого процессора), обработка которых ведется в соответствии с собственной системой приоритетов (low-medium-high). Возврата к IRQLНа этом же уровне работают прерывания, отвечающие за подгрузку страниц в ОЗУ из файлов подкачки, а также за диспетчеризацию потоков (проверить). Отсюда самое главное ограничение на код ядра:
Слайд 41

при работе процессора на уровне IRQL≥DISPATCH_LEVEL запрещено обращение к выгружаемой

при работе процессора на уровне IRQL≥DISPATCH_LEVEL запрещено обращение к выгружаемой памяти

– оно немедленно приведет к BSOD с ошибкой IRQL_NOT_LESS_OR_EQUAL.
На уровне IRQL>PASSIVE_LEVEL нельзя предпринимать никакие действия, которые могут привести к необходимости переключения потока (например использовать механизмы синхронизации на основе диспетчерских объектов (Dispatcher Objects) с помощью функции WaitFor(Single/Multiple)Objects с ненулевым временем ожидания) – замаскированы обслуживающие такое переключение прерывания уровня APC и DISPATCH_LEVEL
Уровни выше DISPATCH_LEVEL в основном соответствуют обработчикам аппаратных прерываний (DIRQLs). Маскирована часть прерываний, к возникновению которых могут приводить в том числе вызовы многих служебных ядерных функций, поэтому множество разрешенных к вызову служебных функций сильно ограничено. Серьезная обработка должна быть вынесена в DPC, а иногда – и в рабочие потоки с IRQL PASSIVE_LEVEL.
Слайд 42

Код потока ISR 1 начало t1 PASSIVE_LEVEL Обработка очереди DPC

Код потока

ISR 1 начало

t1

PASSIVE_LEVEL

Обработка очереди DPC

ISR 2

ISR 1 продолжение

Доставка APC

Код потока

APC_LEVEL

DISPATCH_LEVEL

DIRQL

1

DIRQL 2

t2

t3

t4

t5

t6

IRQL

t1: Получено прерывание. Задача обработчика – быстро поставить задание в очередь DPC и завершиться

t2: Получено более приоритетное прерывание

t3: Выход из обработчика более приоритетного прерывания в ранее прерванный обработчик

t4: Выход из обработчика прерывания, начатого в момент времени t1 , запуск обработки очереди DPC

t5: Завершение всех заданий очереди DPC, запуск доставки APC

t6: Завершение доставки всех APC, возврат к исполнению ранее прерванного кода потока

Слайд 43

!!!В Win10 – появился Rank

!!!В Win10 – появился Rank

Слайд 44

Основные структуры управления памятью ОС Windows Cr3 -> Page Directory,

Основные структуры управления памятью ОС Windows

Cr3 -> Page Directory, Page Tables

(Каталог и таблицы страниц)
Задает отображение страниц виртуальной памяти в физическую (для каждого адресного пространства описывает преобразование виртуального адреса в физический)
Используется аппаратно каждый раз при доступе к виртуальной ячейке памяти
PFN Database (база данных PFN)
Описывает состояние страниц физической памяти
Используется ОС когда нужно заменить одну страницу памяти другой
Доступна через экспортируемую ядерную переменную
VAD Tree (дерево дескрипторов виртуальных адресов)
Сбалансированное дерево.
Для каждого адресного пространства в диапазоне виртуальных адресов пользовательского режима описывает зарезервированные блоки виртуальной памяти
Используется ОС когда нужно выделить новый блок памяти
Корень дерева доступен через структуру EPROCESS
Слайд 45

FS:[0] Основные структуры управления процессами и потоками PEB *Peb TEB

FS:[0]

Основные структуры управления процессами и потоками

PEB *Peb

TEB
(Thread Environment Block)

TIB

FS:[0]

FS:[0]

CPU

PEB
(ProcessEnvironment

Block)

FS:[0]

FS:[0]

FS:[0]

CPU

KPRCB *Prcb

KPCR
(Processor Control Region)

KTHREAD *CurrentThread
KTHREAD *NextThread

KPRCB
(Processor Control Block)

KTHREAD Tcb
EPROCESS *ThreadsProcess

ETHREAD

KPROCESS Pcb
PEB *Peb
Таблица_описателей
Корень_дерева_VAD

EPROCESS

[ffdff000]

[7ffde000]

user

kernel

TEB*Teb

Слайд 46

Пространство имен диспетчера объектов

Пространство имен диспетчера объектов

Слайд 47

Имена ядерных объектов размещаются в едином пространстве имен. Подсистема Win32

Имена ядерных объектов размещаются в едином пространстве имен. Подсистема Win32 скрывает

его наличие, транслируя обращения к именам объектов различных типов в обращения к конкретным директориям единого пространства имен, иногда полностью видоизменяя имя объекта. Например, обращение к файлу с именем c:\foo.txt будет последовательно трансформировано следующим образом:
\??\c:\foo.txt
В директории \?? будет найден элемент с именем С:, имеющий тип SymbolicLink и значение \Device\HarddiskVolume1, в результате подстановки имя примет вид
\Device\HarddiskVolume1\foo.txt
В директории \Device будет найден элемент с типом Device, на этом разбор имени будет закончен, а соответствующему устройству будет отправлен запрос, частью которого будет оставшаяся неразобранной часть имени \foo.txt – дальше с ней будет разбираться драйвер устройства.
Слайд 48

«Забегая вперед» Неразобранная часть имени, если она есть, хранится в

«Забегая вперед»
Неразобранная часть имени, если она есть, хранится в стеке размещения

в/в в поле FileName файлового объекта
PIO_STACK_LOCATION pStack;
pStack = IoGetCurrentIrpStackLocation(pIrp);
if( pStack->FileObject && pStack->FileObject->FileName.Buffer)
DbgPrint(“Name is <%ws>\n”, pStack->FileObject->FileName.Buffer);
Например для имени \??\c:\foo.txt будет передано \foo.txt
Если имя разобрано полностью, неразобранной части нет, и в поле pStack->FileObject->FileName.Buffer будет помещен нулевой указатель
Файловый объект описывает установленный канал взаимодействия с драйвером (запрос IRP_MJ_CREATE) и хранящееся в нем имя можно использовать в любом запросе в/в до уничтожения файлового объекта (запрос IRP_MJ_CLEANUP)
Слайд 49

Структура драйвера Работа драйвера начинается с вызова функции DriverEntry, соответствующей

Структура драйвера

Работа драйвера начинается с вызова функции DriverEntry, соответствующей начальной точке

входа в исполняемый модуль драйвера, которая прописана в его PE-заголовке.
Задача DriverEntry – инициализация работы драйвера. Если она завершается успешно – функция завершается с кодом STATUS_SUCCESS (=0), после чего драйвер остается в памяти ядра. При любом другом коде завершения драйвер выгружается из памяти.
Работа драйвера после загрузки по сути определяется тем, какие callback-функции зарегистрированы драйвером при инициализации:
Диспетчерские функции: массив точек входа DriverObject->MajorFunction[], вызываемых для обработки запросов, направляемых устройствам драйвера.
Обработчики прерываний (ISR), отложенных вызовов (DPC).
Подмена адресов функций обработки вызовов системных сервисов.
Функции, определяемые типом программного интерфейса задействованного при инициализации (например NDIS).
В ОС начиная с Vista – драйверы должны быть подписаны. Механизм проверки подписи может быть отключен при запуске ОС.
Слайд 50

ограничения Проблема при попытке написания кода драйвера на C++: функция

ограничения

Проблема при попытке написания кода драйвера на C++: функция main() –

не первая функция с которой начинает работать код (инициализация памяти, вызов конструкторов глобальных экземпляров классов).
В коде драйвера – жесткие ограничения на библиотечные функции. Разрешенные к вызову – можно вызывать только функции ядра, некоторые типы драйверов имеют ограниченный набор функций, разрешенных к вызову, чтобы соответствовать некоторой спецификации – например NDIS.
Для всех доступных для вызова ядерных функций документация фиксирует жесткие условия, в которых они могут быть вызваны (контекст потока – случайный или нет, уровень IRQL, с которого может быть осуществлен вызов). Кроме того – ограничения на доступ к памяти (не всегда можно работать с paged-памятью) в зависимости от условий работы.
В ранних версиях ОС было ограничение (запрет) на работу с инструкциями FPU, возникающее вследствие несохранения контекста FPU при переключении user->kernel mode
Слайд 51

Многоуровневая модель драйверов Legacy Drivers – WDM – KMDF –

Многоуровневая модель драйверов

Legacy Drivers – WDM – KMDF – WDF
Legacy Drivers

– по сути способ написания ядерного драйвера «с нуля», опираясь только на знания архитектуры ОС. Архитектура таких драйверов не претерпела изменений с момента появления ОС Windows NT 3.51. Такой драйвер будет работать на всех современных Windows
WDM – Windows Driver Model – модель драйверов, изначально разработанная как способ реализации универсальных драйверов для кардинально различающихся линеек ОС: Win9x и WinNT, путем предоставления драйверу для работы некоторого программного окружения, соответствующего поведению ядра ОС линейки Windows NT . Данная архитектура стала стандартом для драйверов всех последующих ОС данной линейки
WDF/KMDF – фреймворк, программная оболочка, скрывающая реальное устройство ядра ОС для облегчения разработки драйверов, но может стать «тормозом» при разработке «продвинутых» драйверов
Слайд 52

VisualStudio2019 + WDK for Windows 10 version 2004 (по состоянию

VisualStudio2019 + WDK for Windows 10 version 2004 (по состоянию на

01.09.2020)

https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk
Выбрать Desktop development with C++
Отметить для установки
Windows 10 SDK 10.0.19041.0
MSVC v142 – VS 2019 C++ x64/x86 build tools

Слайд 53

Слайд 54

Слайд 55

Выбор целевой ОС (сборка для Win7 будет работать и на WinXP)

Выбор целевой ОС (сборка для Win7 будет работать и на WinXP)

Слайд 56

Изменить свойства проекта

Изменить свойства проекта

Слайд 57

Интеграции WDK с VisualStudio –VisualDDK

Интеграции WDK с VisualStudio –VisualDDK

Слайд 58

Слайд 59

#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif #include NTSTATUS DriverEntry(IN PDRIVER_OBJECT

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING

RegistryPath)
{
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);
return STATUS_SUCCESS;
}

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

Регистрация драйвера в реестре. Обратить внимание на формат пути в ImagePath

Зарегистрированный драйвер может быть запущен и остановлен с помощью консольных команд
net start имя_сервиса / net stop имя_сервиса
Программно драйвер управляется с помощью WinAPI-функций диспетчера сервисов (SCM - ServiceControlManager): CreateService/DeleteService/StartService/ControlService

Слайд 60

#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif #include void OnUnload(IN PDRIVER_OBJECT

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include
void OnUnload(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("simple: OnUnload\n");
}
NTSTATUS DriverEntry(IN

PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);
DriverObject->DriverUnload = OnUnload;
return STATUS_SUCCESS;
}

Для обеспечения возможности выгрузки драйвера он должен реализовывать обработчик функции выгрузки DriverObject->DriverUnload
Задача этой функции – очистка всех ресурсов, выделенных при работе драйвера

Как DriverEntry, так и DriverObject->DriverUnload всегда вызываются в контексте одного из рабочих потоков процесса System. Поскольку адресное пространство одно и то же, используется общая таблица описателей, т.е. описатель, полученный в DriverEntry, можно закрывать в DriverUnload

Слайд 61

Регистрация и запуск/остановка драйвера Внести изменения в reg-файл (в директории

Регистрация и запуск/остановка драйвера

Внести изменения в reg-файл (в директории с проектом

драйвера):
"Start"=dword:00000003
"ImagePath"="\\??\\c:\\work\\drv\\l1\\l1\\x64\\Debug\\l1.sys“
(путь и имя файла должны быть ваши)
Выполнить reg-файл
Проконтролировать изменения реестра через редактор рестра regedit – ищем имя_сервиса и значения ключей в HKLM\System\CurrentControlSet\Services\
После первого изменения реестра перезагрузить виртуальную машину для внесения изменений из реестра в память
Запуск и остановка сервиса:
net start имя_сервиса
net stop имя_сервиса
Использовать DbgView для контроля отладочных сообщений
Слайд 62

#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif #include void OnUnload(IN PDRIVER_OBJECT

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include
void OnUnload(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("simple: OnUnload\n");
IoDeleteDevice(DriverObject->DeviceObject);
}
NTSTATUS OnCreate(IN PDEVICE_OBJECT

DeviceObject, IN PIRP Irp)
{
DbgPrint("simple: OnCreate\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS OnClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
DbgPrint("simple: OnClose\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNICODE_STRING DeviceName;
PDEVICE_OBJECT DeviceObject = NULL;
NTSTATUS status;
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);
RtlInitUnicodeString(&DeviceName,L"\\Device\\simple0");
DriverObject->MajorFunction[IRP_MJ_CREATE] = OnCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = OnClose;
DriverObject->DriverUnload = OnUnload;
status = IoCreateDevice(DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&DeviceObject);
if (!NT_SUCCESS(status))
return status;
if (!DeviceObject)
return STATUS_UNEXPECTED_IO_ERROR;
DeviceObject->Flags |= DO_DIRECT_IO;
DeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;
return STATUS_SUCCESS;
}

Взаимодействие с драйвером обычно осуществляется через создаваемые им устройства, которым направляются запросы в/в в формате IRP-пакетов

Слайд 63

#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif #include void OnUnload(IN PDRIVER_OBJECT

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include
void OnUnload(IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING Win32Device;
DbgPrint("simple: OnUnload\n");
RtlInitUnicodeString(&Win32Device,L"\\DosDevices\\simple0");
IoDeleteSymbolicLink(&Win32Device);
IoDeleteDevice(DriverObject->DeviceObject);
}
NTSTATUS OnCreate(IN

PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
DbgPrint("simple: OnCreate\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS OnClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
DbgPrint("simple: OnClose\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNICODE_STRING DeviceName,Win32Device;
PDEVICE_OBJECT DeviceObject = NULL;
NTSTATUS status;
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);
RtlInitUnicodeString(&DeviceName,L"\\Device\\simple0");
RtlInitUnicodeString(&Win32Device,L"\\DosDevices\\simple0");
DriverObject->MajorFunction[IRP_MJ_CREATE] = OnCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = OnClose;
DriverObject->DriverUnload = OnUnload;
status = IoCreateDevice(DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&DeviceObject);
if (!NT_SUCCESS(status))
return status;
if (!DeviceObject)
return STATUS_UNEXPECTED_IO_ERROR;
DeviceObject->Flags |= DO_DIRECT_IO;
DeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;
IoCreateSymbolicLink(&Win32Device, &DeviceName);
return STATUS_SUCCESS;
}

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

Слайд 64

Взаимосвязь основных ядерных объектов при прохождении запроса в/в

Взаимосвязь основных ядерных объектов при прохождении запроса в/в

Слайд 65

DRIVER_OBJECT Найти устройство по имени int 2E SYSCALL SYSENTER DeviceObject

DRIVER_OBJECT

Найти устройство по имени

int 2E
SYSCALL
SYSENTER

DeviceObject
MajorFunction[]
DriverUnload()

CreateFile(filename, …)

NtCreateFile(filename, …)

Диспетчер в/в

Найти адрес функции в

таблице системных сервисов

NtCreateFile(filename, …)

DEVICE_OBJECT

DriverObject
NextDevice
AttachedDevice
DeviceExtension

Найти драйвер этого устройства

Сформировать IRP-запрос на основе параметров CreateFile()

Передать IRP-запрос для устройства через IoCallDriver

DrvObj->MajorFunction[IRP_MJ_CREATE](pDevObj, pIrp )

FILE_OBJECT

DeviceObject

Создать файловый объект, описывающий сеанс связи с устройством

IRP

FileObject
DeviceObject
MajorFunction

= IRP_MJ_CREATE

pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, 0);
return STATUS_SUCCESS;

Если завершение с ошибкой – уничтожить файловый объект
Если успешно – поместить его адрес в таблицу описателей и вернуть индекс как HANDLE

FileObject

Таблица описателей

HANDLE – индекс в таблице описателей

Вернуть управление в CreateFile

Native API: ntdll.dll

user

kernel

Слайд 66

DRIVER_OBJECT Найти файловый объект по описателю int 2E SYSCALL SYSENTER

DRIVER_OBJECT

Найти файловый объект по описателю

int 2E
SYSCALL
SYSENTER

DeviceObject
MajorFunction[]
DriverUnload()

ReadFile(hfile, …)

NtReadFile(hfile, …)

Диспетчер в/в

Найти адрес функции

в таблице системных сервисов

NtReadFile(hfile, …)

DEVICE_OBJECT

DriverObject
NextDevice
AttachedDevice
DeviceExtension

Найти устройство

Сформировать IRP-запрос на основе параметров ReadFile()

Передать IRP-запрос для устройства через IoCallDriver

DrvObj->MajorFunction[IRP_MJ_READ](pDevObj, pIrp )

FILE_OBJECT

DeviceObject

Найти драйвер этого устройства

IRP

FileObject
DeviceObject
MajorFunction

= IRP_MJ_READ

pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, 0);
return STATUS_SUCCESS;

//Отложить обработку: сохранить pIrp
IoMarkIrpPending(pIrp);
return STATUS_PENDING;

//где-то в случайном контексте
IoCompleteRequest(pIrp, 0);

FileObject

Таблица описателей

HANDLE – индекс в таблице описателей

Если завершено или вызов асинхронный – вернуть управление в ReadFile

Если не завершено и вызов синхронный – не возвращать управление до завершения запроса

Native API: ntdll.dll

user

kernel

Используется APC для доставки уведомления о завершении запроса в/в

Слайд 67

Структура управляющей программы Функции WinAPI OpenSCManager() CreateService() DeleteService() StartService() ControlService()

Структура управляющей программы

Функции WinAPI
OpenSCManager()
CreateService()
DeleteService()
StartService()
ControlService()
CloseServiceHandle()
CreateFile()
CloseHandle()

Решаемая задача
1.

Добавление сервиса
2. Удаление сервиса
3. Запуск сервиса
4. Остановка сервиса
5. Открытие устройства
6. Закрытие устройства

Класс управления сервисами
Конструктор()
Add()
Del ()
Start ()
Stop()
Open()
Close ()
Деструктор()

Функции диспетчера сервисов

Слайд 68

Как выяснилось большинство из вас нифига не помнят/не знают базовые

Как выяснилось большинство из вас нифига не помнят/не знают базовые вещи

по языку С/С++, а потому Вспоминаем C/C++: Объявления и определения подробнее: https://learnc.info/c/beginners_guide_to_linkers.html

Иногда в русскоязычных статьях по языку c/cpp, например здесь: https://studfile.net/preview/7165254/page:7/ , вводится ТРИ термина: объявление, определение и описание. Упрощенно:
Объявление: вводит что-то (имя переменной) определенного типа (имя типа).
Описание – это объявление, которое :
память для переменной не резервирует
для одной переменной может быть >=0 описаний.
Определение – это объявление, которое:
резервирует память для переменной
для одной переменной может быть ТОЛЬКО ОДНО определение
Забудьте эту гадость если где увидите/услышите. Читайте первоисточник на английском, ссылку в заголовке или базовые статьи в MSDN, например https://docs.microsoft.com/ru-ru/cpp/cpp/declarations-and-definitions-cpp?view=msvc-160&viewFallbackFrom=vs-2019 .
Или следующие слайды.

Слайд 69

В английском языке для c/cpp используется ДВА термина: declaration (объявление),

В английском языке для c/cpp используется ДВА термина:
declaration (объявление), описывает

переменную некоторого типа НО НЕ ВЫДЕЛЯЕТ для нее память. Или задает прототип функции без ее реализации: void f1(int, void*); . Или задает новый тип, например структуру или класс, без создания переменной – экземпляра типа.
definition (определение), описывает переменную некоторого типа И ВЫДЕЛЯЕТ для нее память. Или задает прототип и реализацию функции: void f1(int a, void *b){ }. Для одной переменной всегда должно быть единственное определение и >=0 объявлений. При этом определение всегда выступает в роли объявления.
Переменная/функция в конкретном c/cpp файле должна быть объявлена до использования, и должна быть единожды определена неважно где, возможно даже в другом c/cpp-файле. По стандарту CPP определение – единственное, иначе – ошибка. По стандарту С определений может быть много – например по одному в каждом c-файле, и тогда сколько определений – столько и переменных.
Слайд 70

Сборка проекта (build): препроцессинг, компиляция, компоновка(линковка) Проект программы (project в

Сборка проекта (build): препроцессинг, компиляция, компоновка(линковка)

Проект программы (project в терминологии

visualstudio) состоит из множества c/cpp файлов, статических библиотек (.lib-файл, состоящий из множества .obj-файлов) и библиотек импорта (бинарный .lib или текстовый def-файл, описывающие имена динамических библиотек .dll и реализованных в них функций и переменных, которые мы хотим использовать из нашего кода). Все библиотечные переменные и функции должны быть объявлены – для этого мы включаем в c/cpp через #include соответствующий стандартный заголовочный h-файл .
При желании в проект можно включать файлы на других языках программирования - .asm, смешивать .cpp и .c, паскаль, фортран и т.п., с указанием компилятора и его параметров для преобразования (компиляции/трансляции) файла с исходным текстом в объектный файл (.obj)
Заголовочные файлы, которые вы видите в составе проекта, на процесс сборки проекта не влияют в том смысле что в состав проекта их включать не обязательно. Например, если в ваш проект включен некоторый файл myfile.cpp, в котором есть директива #include “header.h”, файл header.h будет обработан на этапе препроцессинга файла myfile.cpp вне зависимости от того, включили вы header.h в проект или нет. Заголовочные файлы включаются в состав проекта для вашего удобства контроля проекта.
Слайд 71

Общее правило для проекта программы, состоящего из нескольких c/cpp –

Общее правило для проекта программы, состоящего из нескольких c/cpp – файлов:
В

заголовочных файлах – только объявления. Например extern int a; Если напишете в заголовке определение, например int a; а затем включите через #include такой заголовок в разные c/cpp-файлы – получите РАЗНЫЕ переменные а в разных c/cpp-файлах и потенциальную ошибку – притом что на С проект успешно соберется а на CPP выдаст ошибку.
В c/cpp-файлах – определения. Здесь можно а иногда нужно делать объявления, но желательно вынести их в заголовочный файл, подключенный через #include
Слайд 72

Каждый c/cpp-файл является единицей компиляции и НЕЗАВИСИМО от остальных файлов

Каждый c/cpp-файл является единицей компиляции и НЕЗАВИСИМО от остальных файлов проекта

проходит через этапы:
Препроцессинг: все директивы препроцессора в исходном тексте (начинаются на #, например #include, #define заменяются на сишный код. В частности в точке #include подставляется целиком содержимое заголовочного файла, для которого также выполняется препроцессинг). Результат препроцессинга - c/cpp файл без директив препроцессора Настройки препроцессора в VisualStudio – в свойствах проекта пункт C/C++ в основном в подпункте Preprocessor
Слайд 73

Компиляция: генерация псевдокода в терминах некоторого промежуточного языка (трехадресный код,

Компиляция: генерация псевдокода в терминах некоторого промежуточного языка (трехадресный код, llvm-представление)

или сразу в виде команд процессора целевой процессорной архитектуры. Результат компиляции – объектный файл (.obj). Отличие от обычного исполняемого кода – в нем не определены внешние связи с функциями/переменными других объектных файлов. Т.е. если в текущем компилируемом сишнике были описания, но не определения используемых в коде переменных и функций – для таких участков си-кода работающий фрагмент машинного кода сгенерировать невозможно без учета других c/cpp файлов. Генерация таких фрагментов кода откладывается до этапа линковки. Не все программные ошибки могут быть выявлены на этапе компиляции. Например – если ни в одном c/cpp файле не было определения используемой переменной, но были ее описания – не будет ошибки этапа компиляции но будет ошибка этапа линковки. Настройки компилятора в VisualStudio – в свойствах проекта пункт C/C++
Слайд 74

После того как все файлы проекта без ошибок прошли препроцессинг

После того как все файлы проекта без ошибок прошли препроцессинг и

компиляцию, происходит этап компоновки он же линковка (потому что в VS компоновку делает утилита link). Компоновке подвергается одновременно все множество объектных файлов, полученных на предыдущих этапах. Результат – исполняемый файл (.exe, .dll, .sys и т.п.) или статическая библиотека (.lib) как объединение множества объектных файлов. Настройки компоновщика в VisualStudio – в свойствах проекта пункт Linker
Слайд 75

.h .h file1.cpp file2.cpp file4.asm Компилятор языка c++ Транслятор языка

.h

.h

file1.cpp

file2.cpp

file4.asm

Компилятор языка c++

Транслятор языка ассемблер

Препроцессор языка c++

Препроцессор языка c++

.h

Вообще говоря любые

файлы, указанные в директиве препроцессора #include

file3.c

Препроцессор языка c

.h

.h

.h

.h

.h

.h

Компилятор языка c++

Компилятор языка c

file1.obj

file2.obj

file3.obj

file4.obj

linker

Исполняемый файл в формате PE, elf или любом другом, поддерживаемым линковщиком.
Расширение может быть любым - .exe, .dll, .sys – оно настраивается в свойствах компоновщика

Пример: проект состоит из четырех файлов – file1.cpp, file2.cpp, file3.c, file4.asm. Количество заголовочных файлов в проекте роли не играет. Как происходит сборка (build) проекта?

file1.cpp

file2.cpp

file3.c

Слайд 76

Как запустить сборку (build) проекта Правый клик мышкой на имени

Как запустить сборку (build) проекта

Правый клик мышкой на имени проекта –

всплывающее меню – Build
Меню Build – Build имя_проекта (если проектов в солюшене несколько)
Кнопка «Start debugging» или соответствующий пункт меню «Debug». При нескольких проектах запускается активный (помечен жирным шрифтом). Перед запуском - если были изменения в исходном коде, VS вначале «спросит» вас – хотите ли «пересобрать» проект. Лучше не надо так «пересобирать»
Слайд 77

Как запустить компиляцию конкретного c/cpp-файла или единицы компиляции Правый клик

Как запустить компиляцию конкретного c/cpp-файла или единицы компиляции

Правый клик мышкой на

имени файла – всплывающее меню – Compile Напоминаю, что при этом сгенерируется .obj-файл для этого конкретного сишника. Исполняемый файл сгенерирован не будет – это уже линковка.

Любители gcc всё то же самое проделывают прямо из командной строки со всеми параметрами препроцессора – компилятора – компоновщика и им ничего этого разжёвывать не надо. Святые люди :)

Слайд 78

Слайд 79

Слайд 80

Слайд 81

Слайд 82

Слайд 83

Слайд 84

Слайд 85

Оптимизация как часть процесса компиляции Сам процесс компиляции довольно сложен

Оптимизация как часть процесса компиляции Сам процесс компиляции довольно сложен и

является объектом научных исследований в области компиляторных технологий

IR – Intermediate Representation – промежуточное представление кода
HIR – High-level IR
LIR - Low-level IR
В следующем семестре:
Control Flow Analysis – Анализ потока управления
Data Flow Analysis – Анализ потока данных

Слайд 86

Слайд 87

Слайд 88

Слайд 89

Слайд 90

Слайд 91

Слайд 92

Слайд 93

Слайд 94

Слайд 95

Слайд 96

Слайд 97

Слайд 98

Слайд 99

Слайд 100

Дополнительные ссылки «Для чайников» про то же, но в gcc:

Дополнительные ссылки

«Для чайников» про то же, но в gcc: https://habr.com/ru/post/478124/
А вот

«продвинутая» статья по структуре проекта, состоящего из нескольких единиц компиляции (c/cpp-файлов): https://code-live.ru/post/separate-compilation-cpp/
«Для чайников» про оптимизацию кода https://habr.com/ru/post/124131/
А зачем это Вам? «В уме» держим что «обфускация» – запутывание кода в целях защиты от анализа – строится на тех же принципах теории оптимизации и по сути может рассматриваться как одно из «оптимизирующих» преобразований – только цель у него другая ))
А некоторые из вас, как будущие специалисты в области ИБ, должны уметь как защищать код от анализа (обфускация), так и исследовать защищенный код (деобфускация)
На будущее - «Для совсем (про)двинутых»: серия статей того же автора https://habr.com/ru/post/119850/ Зачем? Вот на уровне llvm и делают обфускацию.
Слайд 101

Precompiled headers - предварительно откомпилированные заголовки Количество строк кода в

Precompiled headers - предварительно откомпилированные заголовки

Количество строк кода в стандартных заголовочных

файлах операционной системы как правило на 2-3 порядка превосходит объем написанного вами кода.
Вы реально пишете 200-300 строк на с/с++ и считаете это подвигом. На момент выполнения вами дистанционной работы в объеме первого видеозанятия, размер файла scmgr.cpp – 89 строк, размер файла guiappDlg.cpp – 189 строк.
В scmgr.cpp вы используете некоторые WINAPI-функции и типы данных, другие WINAPI-функции и типы вы используете в guiappDlg.cpp.
Перед использованием WINAPI-функции и типы данных должны были быть объявлены в КАЖДОМ из этих cpp-файлов, для этого как правило через директиву препроцессора #include в cpp-файл подключается стандартный заголовочный файл, такой как windows.h
Размер стандартных заголовочных файлов - таких как windows.h, ntddk.h или wdm.h - сотни тысяч строк кода
К чему это приведет – мы видели на предыдущем слайде: препроцессор к вашим 89 строкам файла scmgr.cpp добавляет сотни тысяч строк стандартных заголовков и результат отдает компилятору, затем к 189 строкам файла guiappDlg.cpp добавляет ТЕ ЖЕ САМЫЕ сотни тысяч строк стандартных заголовков и результат опять отдает компилятору и т.д. для всех cpp-файлов. Т.е. суммарное время компиляции = O(размер_стандартных_заголовков*число_cpp_файлов), объем написанного вами кода пренебрежимо мал и не учитывается.
Чтобы время компиляции зависело только от объема написанного вами кода, придумали Precompiled headers. Общая идея: неизменяемые вами стандартные заголовочные файлы, многократно включаемые в разные модифицируемые вами c/cpp-файлы, должны быть вынесены в отдельный проход компиляции, выполняемый один раз на все сборки проекта.
Слайд 102

Для этого надо сделать три вещи: в свойствах проекта в

Для этого надо сделать три вещи:
в свойствах проекта в настройках

компилятора С/С++ в подпункте Precompiled Headers включаем использование Precompiled headers и указываем имя предварительно компилируемого заголовочного h-файла
Слайд 103

В свойствах каждого c/cpp-файла, для которого хотим задействовать процесс предварительной

В свойствах каждого c/cpp-файла, для которого хотим задействовать процесс предварительной компиляции

заголовков, делаем то же самое, по умолчанию настройки проекта будут применяться ко всем создаваемым c/cpp файлам
Слайд 104

В текст c/cpp-файла, для которого хотим задействовать процесс предварительной компиляции

В текст c/cpp-файла, для которого хотим задействовать процесс предварительной компиляции заголовков,

через #include включить заголовочный файл, указанный в настройках предварительной компиляции
Слайд 105

В текст предварительно компилируемого h-файла, указанного в настройках предварительной компиляции

В текст предварительно компилируемого h-файла, указанного в настройках предварительной компиляции (у

нас это stdafx.h), через #include включаем набор стандартных заголовочных файлов, общий для всех c/cpp файлов проекта. Как правило это #include но в принципе это могут быть и ваши заголовки, в которых вы уверены и знаете что редактировать их уже не будете. Все остальные заголовочные файлы, которые вы редактируете в процессе написания кода и потому не должны включать в состав предкомпилируемых, включаете через #include как обычно в c/cpp-файлы проекта В примере на предыдущем слайде: первая #include включает огромный предкомпилируемый один раз заголовок, две следующих директивы #include включают маленькие но постоянно редактируемые вами заголовочные файлы.
Слайд 106

Теперь возвращаемся к структуре текущего проекта на следующем слайде -

Теперь возвращаемся к структуре текущего проекта

на следующем слайде - упрощенная схема

проекта с пояснениями
попытайтесь ее осознать с использованием ранее изложенных терминов и учетом последовательности сборки проекта
для понимания крайне рекомендуется полазить по файлам проекта, сравнить их исходный код со схемой и найти все указанные на слайде части
Сверьтесь со своими знаниями С/С++ в части терминов объявление/определение, кто все понял – молодец. Я у вас на две группы пока только одного такого студента видел )
Слайд 107

Программа с графическим интерфейсом (GUIApp) scm.h scm.cpp class CguiappApp :

Программа с графическим интерфейсом (GUIApp)

scm.h

scm.cpp
class CguiappApp : public CWinAppEx
{
virtual BOOL

InitInstance();
CWnd *m_pMainWnd;

};
extern CguiappApp theApp;

#include "guiapp.h"
#include "guiappDlg.h"
CguiappApp theApp;
BOOL CguiappApp::InitInstance()
{

CguiappDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();

}
class CguiappDlg : public Cdialog
{
CEdit m_edt_fname;
CEdit m_edt_sname;
CEdit m_edt_lname;
void OnBnClickedBtnFname();
void OnBnClickedBtnAdd();
void OnBnClickedBtnDel();
void OnBnClickedBtnStart();
void OnBnClickedBtnStop();
void OnBnClickedBtnOpen();
void OnBnClickedBtnClose();

};

guiapp.h

guiapp.cpp

guiappDlg.h

#include "guiapp.h"
#include "guiappDlg.h"
#include "scm.h"
void CguiappDlg::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_EDT_SNAME, m_edt_sname);
}
BEGIN_MESSAGE_MAP(CguiappDlg, CDialog)
ON_BN_CLICKED(IDC_BTN_DEL, &CguiappDlg::OnBnClickedBtnDel)
END_MESSAGE_MAP()
void CguiappDlg::OnBnClickedBtnDel()
{
CSCM scm;
CString sname;
m_edt_sname.GetWindowText(sname);
scm.Del(sname);
}

guiappDlg.cpp

Объявление переменной theApp – экземпляра класса приложения

Определение theApp

Создание экземпляра класса диалогового окна сохранение его адреса в m_pMainWnd
и отрисовка окна через DoModal()

Определение класса приложения CguiappApp

Определение класса диалогового окна CguiappDlg

Определение функции InitInstance – члена класса приложения

Определение функции DoDataExchange – члена класса диалогового окна

Определение функции OnBnClickedBtnDel – члена класса диалогового окна

Объявления переменных и функций – членов класса диалогового окна. В частности – объявление функции OnBnClickedBtnDel

Связывание переменной m_edt_sname с ресурсом с идентификатором IDC_EDT_SNAME

Связывание функции OnBnClickedBtnDel с событием ON_BN_CLICKED – нажатие на элемент управления с идентификатором ресурса IDC_BTN_DEL

Слайд 108

Программа с графическим интерфейсом (GUIApp) Консольная программа(TestCons) class CguiappApp :

Программа с графическим интерфейсом (GUIApp)

Консольная программа(TestCons)

class CguiappApp :
public CWinAppEx
{

virtual BOOL InitInstance();
CWnd *m_pMainWnd;

};
extern CguiappApp theApp;

#include "guiapp.h"
#include "guiappDlg.h"
CguiappApp theApp;
BOOL CguiappApp::InitInstance()
{

CguiappDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();

}

class CguiappDlg :
public Cdialog
{
CEdit m_edt_fname;
CEdit m_edt_sname;
CEdit m_edt_lname;
void OnBnClickedBtnFname();
void OnBnClickedBtnAdd();
void OnBnClickedBtnDel();
void OnBnClickedBtnStart();
void OnBnClickedBtnStop();
void OnBnClickedBtnOpen();
void OnBnClickedBtnClose();

};

guiapp.h

guiapp.cpp

guiappDlg.h

#include "guiapp.h"
#include "guiappDlg.h"
#include "scm.h"
void CguiappDlg::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_EDT_SNAME, m_edt_sname);
}
BEGIN_MESSAGE_MAP(CguiappDlg, CDialog)
ON_BN_CLICKED(IDC_BTN_DEL,
&CguiappDlg::OnBnClickedBtnDel)
END_MESSAGE_MAP()
void CguiappDlg::OnBnClickedBtnDel()
{
CSCM scm;
CString sname;
m_edt_sname.GetWindowText(sname);
scm.Del(sname);
}

guiappDlg.cpp

#include "scm.h"
CSCM::CSCM(void)
{
hscm = OpenSCManager(…);
}
CSCM::~CSCM(void)
{
CloseServiceHandle(hscm);
}
void CSCM::Del(LPCTSTR sname)
{
SC_HANDLE hsrv = OpenService(hscm,
sname, SERVICE_ALL_ACCESS);
DeleteService(hsrv);
CloseServiceHandle(hsrv);
}

class CSCM
{
SC_HANDLE hscm;
CSCM(void);
~CSCM(void);
void Del(LPCTSTR sname);

};

scm.h

scm.cpp

Слайд 109

В классе CSCM хотим сделать аналог функции printf, которая печатает

В классе CSCM хотим сделать аналог функции printf, которая печатает диагностические

сообщения
Первый параметр – строка форматирования fmt (почитайте про строки форматирования printf), дальше идет переменное число аргументов, которое в языке C обозначается троеточием:
int printf(const char *fmt, …)
По аналогии делаем функцию void pf(char *fmt, …)
Внутри нее мы хотим сформировать строку, выводимую куда-то на печать – для этого используем функцию vsprintf():
int vsprintf(char *str, const char *fmt, va_list argptr );
Слайд 110

Программа с графическим интерфейсом (GUIApp) Консольная программа(TestCons) class CguiappApp :

Программа с графическим интерфейсом (GUIApp)

Консольная программа(TestCons)

class CguiappApp :
public CWinAppEx
{

virtual BOOL InitInstance();
CWnd *m_pMainWnd;

};
extern CguiappApp theApp;

#include "guiapp.h"
#include "guiappDlg.h"
CguiappApp theApp;
BOOL CguiappApp::InitInstance()
{

CguiappDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();

}

class CguiappDlg :
public Cdialog
{
CEdit m_edt_fname;
CEdit m_edt_sname;
CEdit m_edt_lname;
void OnBnClickedBtnFname();
void OnBnClickedBtnAdd();
void OnBnClickedBtnDel();
void OnBnClickedBtnStart();
void OnBnClickedBtnStop();
void OnBnClickedBtnOpen();
void OnBnClickedBtnClose();

};

guiapp.h

guiapp.cpp

guiappDlg.h

#include "guiapp.h"
#include "guiappDlg.h"
#include "scm.h"
void CguiappDlg::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_EDT_SNAME, m_edt_sname);
}
BEGIN_MESSAGE_MAP(CguiappDlg, CDialog)
ON_BN_CLICKED(IDC_BTN_DEL,
&CguiappDlg::OnBnClickedBtnDel)
END_MESSAGE_MAP()
void CguiappDlg::OnBnClickedBtnDel()
{
CSCM scm;
CString sname;
m_edt_sname.GetWindowText(sname);
scm.Del(sname);
}

guiappDlg.cpp

#include "scm.h"
void CSCM::pf(LPCTSTR fmt, …)
{
//печать сообщения
}
CSCM::CSCM(void)
{
hscm = OpenSCManager(…);
}
CSCM::~CSCM(void)
{
CloseServiceHandle(hscm);
}
void CSCM::Del(LPCTSTR sname)
{
SC_HANDLE hsrv = OpenService(hscm,
sname, SERVICE_ALL_ACCESS);
if(!hsrv)
{ pf(“ERR: OpenService”); return; }
pf(“OK: OpenService”);
if(!DeleteService(hsrv))
pf(“ERR: DeleteService”);
else
pf(“OK: DeleteService”);
CloseServiceHandle(hsrv);
}

class CSCM
{
SC_HANDLE hscm;
CSCM(void);
~CSCM(void);
void Del(LPCTSTR sname);
void pf(LPCTSTR fmt, …)

};

scm.h

scm.cpp

Будем делать аналог printf()

Слайд 111

Кроме обычной форматированной печати строки, нужно уметь получать код и

Кроме обычной форматированной печати строки, нужно уметь получать код и выводить

текстовое описание ошибок, возникающих при работе WinAPI-функций.
Для такого вывода делаем функцию void pferr(char *fmt, …)
Внутри нее мы делаем такую же форматированную печать как в случае pf, но дополнительно добавим получение кода ошибки и строку с её текстовым описанием
Слайд 112

Программа с графическим интерфейсом (GUIApp) Консольная программа(TestCons) class CguiappApp :

Программа с графическим интерфейсом (GUIApp)

Консольная программа(TestCons)

class CguiappApp :
public CWinAppEx
{

virtual BOOL InitInstance();
CWnd *m_pMainWnd;

};
extern CguiappApp theApp;

#include "guiapp.h"
#include "guiappDlg.h"
CguiappApp theApp;
BOOL CguiappApp::InitInstance()
{

CguiappDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();

}

class CguiappDlg :
public Cdialog
{
CEdit m_edt_fname;
CEdit m_edt_sname;
CEdit m_edt_lname;
void OnBnClickedBtnFname();
void OnBnClickedBtnAdd();
void OnBnClickedBtnDel();
void OnBnClickedBtnStart();
void OnBnClickedBtnStop();
void OnBnClickedBtnOpen();
void OnBnClickedBtnClose();

};

guiapp.h

guiapp.cpp

guiappDlg.h

#include "guiapp.h"
#include "guiappDlg.h"
#include "scm.h"
void CguiappDlg::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_EDT_SNAME, m_edt_sname);
}
BEGIN_MESSAGE_MAP(CguiappDlg, CDialog)
ON_BN_CLICKED(IDC_BTN_DEL,
&CguiappDlg::OnBnClickedBtnDel)
END_MESSAGE_MAP()
void CguiappDlg::OnBnClickedBtnDel()
{
CSCM scm;
CString sname;
m_edt_sname.GetWindowText(sname);
scm.Del(sname);
}

guiappDlg.cpp

#include "scm.h"
void CSCM::pf(LPCTSTR fmt, …)
{ /*печать сообщения*/}
void CSCM::pferr(LPCTSTR fmt, …)
{ /*печать сообщения об ошибке*/}
CSCM::CSCM(void)
{
hscm = OpenSCManager(…);
}
CSCM::~CSCM(void)
{
CloseServiceHandle(hscm);
}
void CSCM::Del(LPCTSTR sname)
{
SC_HANDLE hsrv = OpenService(hscm,
sname, SERVICE_ALL_ACCESS);
if(!hsrv)
{ pferr(“ERR: OpenService”); return; }
pf(“OK: OpenService”);
if(!DeleteService(hsrv))
pferr(“ERR: DeleteService”);
else
pf(“OK: DeleteService”);
CloseServiceHandle(hsrv);
}

class CSCM
{
SC_HANDLE hscm;
CSCM(void);
~CSCM(void);
void Del(LPCTSTR sname);
void pf(LPCTSTR fmt, …);
void pferr(LPCTSTR fmt, …);

};

scm.h

scm.cpp

Слайд 113

Два вопроса: как превратить переменное число аргументов (…) в единственный

Два вопроса:
как превратить переменное число аргументов (…) в единственный параметр

argptr типа va_list?
Как получить код ошибок и текстовое описание ошибок, возникающих при работе системных WinAPI-функций?
Слайд 114

Как превратить переменное число аргументов (…) в единственный параметр argptr

Как превратить переменное число аргументов (…) в единственный параметр argptr типа

va_list?
Для этого в стандартной библиотеке языка C предусмотрены макросы va_start(), va_end()
Макрос va_start()
void va_start( va_list arg_ptr, prev_param );
формирует значение параметра arg_ptr как начало списка параметров текущей функции, начиная с параметра, следующего за prev_param. Т.е. если у нас функция например из четырех параметров, а мы хотим описать список параметров, начиная с третьего, то в качестве prev_param мы должны указать второй параметр.
Макрос va_end(va_list arg_ptr) завершает работу со списком параметров arg_ptr
В случае с нашей функцией void pf(char *fmt, …) мы хотим описать список параметров начиная со второго (следующий за fmt), а потому в качестве prev_param мы должны указать первый параметр - fmt
В ИТОГЕ получим:
void pf(char *fmt, …)
{
va_list arg_ptr;
char str[1024];
va_start(arg_ptr, fmt);
vsprintf(str, fmt, arg_ptr);
va_end(arg_ptr);
/*Дальше с str что-то делаем для ее вывода на экран. В нашем случае хотим добавить эту строку в многострочный список ListBox в нашем диалоговом окне */
}
ВАЖНО: это небезопасная реализация работы со строками – возможно переполнение буфера строки. Для безопасной реализации вместо vsprintf необходимо использовать vsnprintf_s
Слайд 115

Мы хотим сделать наш класс управления сервисами CSCM быстро переносимым

Мы хотим сделать наш класс управления сервисами CSCM быстро переносимым в

другие проекты, в частности в консольные программы.
Для этого CSCM должен быть минимально привязан к структуре диалогового MFC-приложения. Потому всё, что мы будем делать в функции-члене класса CSCM для печати сообщения – это вызывать функцию печати сообщения из класса диалогового окна.
При переносе CSCM в консольное приложение мы заменим эту реализацию на вызов функции печати в консоль, и больше никаких изменений не потребуется
Слайд 116

Как из функции pf() - члена класса CSCM вызывать функцию

Как из функции pf() - члена класса CSCM вызывать функцию vpf()

класса диалогового окна CguiappDlg для печати сообщения в многострочный список:
Вспоминаем про theApp – экземпляр класса приложения (смотрим схему)
Чтобы в scm.cpp можно было использовать theApp, надо обязательно включить заголовочный файл “guiappDlg.h” – там объявление theApp через extern.
“guiappDlg.h” нельзя включать без предварительного включения “guiapp.h”, так MFC устроен.
theApp указывает на class CguiappApp, в котором есть m_pMainWnd – экземпляр класса нашего диалогового окна CguiappDlg, из которого хотим вызвать vpf
НО: m_pMainWnd объявлен как указатель на базовый для CguiappDlg класс CWnd (смотри схему). Функции CguiappDlg через этот указатель не видны. Требуется приведение типа:
theApp.m_pMainWnd - это указатель на CWnd
(CguiappDlg*)theApp.m_pMainWnd - это указатель на CguiappDlg
((CguiappDlg*)theApp.m_pMainWnd)->vpf() - а это функция – член класса нашего диалогового окна, которую хотим вызвать
Слайд 117

Программа с графическим интерфейсом (GUIApp) Консольная программа(TestCons) class CguiappApp :

Программа с графическим интерфейсом (GUIApp)

Консольная программа(TestCons)

class CguiappApp :
public CWinAppEx
{

virtual BOOL InitInstance();
CWnd *m_pMainWnd;

};
extern CguiappApp theApp;

#include "guiapp.h"
#include "guiappDlg.h"
CguiappApp theApp;
BOOL CguiappApp::InitInstance()
{

CguiappDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();

}

class CguiappDlg :
public Cdialog
{
CListBox m_lst_log;
void vpf(const char *fmt,
va_list arg_ptr);
void vpferr(const char *fmt,
va_list arg_ptr);

};

guiapp.h

guiapp.cpp

guiappDlg.h

#include "guiapp.h"
#include "guiappDlg.h"
#include "scm.h"
void CguiappDlg:: vpf(const char *fmt, va_list arg_ptr)
{
char str[1024];
vsprintf(str, fmt, arg_ptr);
m_lst_log.InsertString(-1, str);
}
void CguiappDlg:: vpferr(const char *fmt, va_list arg_ptr)
{
//все как в vpf, но дополнительно к сформированной
//строке str добавляем строку описания ошибки,
//полученную через FormatMessage
}

guiappDlg.cpp

#include "guiapp.h"
#include "guiappDlg.h"
#include "scm.h"
void CSCM::pf(LPCTSTR fmt, …)
{
va_list arg_ptr;
va_start(arg_ptr, fmt);
((CguiappDlg*)(theApp.m_pMainWnd))->
vpf(fmt, arg_ptr);
va_end(arg_ptr);
}
void CSCM::pferr(LPCTSTR fmt, …)
{
va_list arg_ptr;
va_start(arg_ptr, fmt);
((CguiappDlg*)(theApp.m_pMainWnd))->
vpferr(fmt, arg_ptr);
va_end(arg_ptr);}

class CSCM
{
void pf(LPCTSTR fmt, …);
void pferr(LPCTSTR fmt, …);

};

scm.h

scm.cpp

Слайд 118

Пример переноса CSCM в консольную программу (TestCons) – когда надо

Пример переноса CSCM в консольную программу (TestCons) – когда надо управлять

драйвером без использования GUI

#include
#include
#include "scm.h"
void vpf(const char *fmt, va_list arg_ptr);
{
vprintf(fmt, arg_ptr);
puts(“\n”);
}
void vpferr(const char *fmt, va_list arg_ptr);
{
vprintf(fmt, arg_ptr);
puts(“\n”);
//добавляем FormatMessage
//и вывод полученной строки
//через puts
}
void main()
{
CSCM scm;
scm.Add(“lab_x”,
“\\??\\C:\\drv\\lab_x.sys”);
scm.Start(“lab_x”);
scm.Open(“\\\\.\\labx_link”);
scm.Close();
scm.Del(“lab_x”);
}

testcons.cpp

#include "testcons.h"
#include "scm.h"
void CSCM::pf(LPCTSTR fmt, …)
{
va_list arg_ptr;
va_start(arg_ptr, fmt);
vpf(fmt, argptr);
va_end(arg_ptr);
}
void CSCM::pferr(LPCTSTR fmt, …)
{
va_list arg_ptr;
va_start(arg_ptr, fmt);
vpferr(fmt, argptr);
va_end(arg_ptr);}

class CSCM
{
void pf(LPCTSTR fmt, …);
void pferr(LPCTSTR fmt, …);

};

scm.h

scm.cpp
void vpf(const char *fmt, va_list arg_ptr);
void vpferr(const char *fmt, va_list arg_ptr);

testcons.h

В состав проекта консольной программы TestCons включаем две единицы компиляции: testcons.cpp и scm.cpp.
В testcons.cpp реализуем функции консольной печати и объявляем их в testcons.h
В scm.cpp меняем только вызовы функций печати из диалогового окна – подменяем их функциями печати консольного приложения

Слайд 119

Как получить код ошибок и текстовое описание ошибок, возникающих при

Как получить код ошибок и текстовое описание ошибок, возникающих при работе

системных WinAPI-функций? Использование функций FormatMessage() и GetLastError()

LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL);
Флаг FORMAT_MESSAGE_ALLOCATE_BUFFER заставляет ОС саму выделить память под формируемую строку, адрес выделенной памяти возвращается в lpMsgBuf, поэтому передаем его через адрес (&). Если бы не использовали FORMAT_MESSAGE_ALLOCATE_BUFFER – память под строку должны были бы выделять сами, адрес этой памяти в lpMsgBuf передавали бы без &.
Флаг FORMAT_MESSAGE_FROM_SYSTEM говорит ОС, что в третьем параметре передается код системной ошибки, получаемой через GetLastError().
Для системных кодов ошибки есть текстовые описания, которые и возвращаются в lpMsgBuf с учетом настроек языка (четвертый параметр – в данном случае используются системные языковые настройки)
В принципе можно составлять и свои таблицы строк – например при использовании флага FORMAT_MESSAGE_FROM_HMODULE во втором параметре надо указывать HMODULE загруженного файла с таблицей строк.
//используем lpMsgBuf для печати сообщения, например:
m_lst_log.InsertString(-1, (char*)lpMsgBuf);
// Освобождаем выделенную память (только в случае FORMAT_MESSAGE_ALLOCATE_BUFFER)
LocalFree( lpMsgBuf );

Слайд 120

Пример использования функции FormatMessage совместно с GetLastError LPVOID lpMsgBuf; char

Пример использования функции FormatMessage совместно с GetLastError

LPVOID lpMsgBuf;
char str[2048];
str[2047] = ‘\0’;
FormatMessage(

FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL);
vsprintf(str, fmt, arg_ptr);
strcat(str, (char*)lpMsgBuf);
// Free the buffer.
LocalFree( lpMsgBuf );
m_lst_log.InsertString(-1, str);
Слайд 121

Примеры анализа драйвера Использование дизассемблера IDA Pro Использование среды анализа TREX

Примеры анализа драйвера

Использование дизассемблера IDA Pro
Использование среды анализа TREX

Слайд 122

Отладочные возможности процессора, особенности в Windows, средства отладки Пошаговая отладка:

Отладочные возможности процессора, особенности в Windows, средства отладки

Пошаговая отладка: TraceFlag в

регистре флагов
Точки прерывания:
Модификация отлаживаемого кода – запись инструкции int 3 (код 0xCC)
Отладочные регистры DR0-DR3 (не более 4 точек прерывания на доступ к памяти r/w/e)
И при пошаговой отладке, и при использовании отладочных регистров генерируется прерывание int 1
Слайд 123

Проблемы программных отладчиков Конкуренция с отлаживаемой программой за общие ресурсы:

Проблемы программных отладчиков

Конкуренция с отлаживаемой программой за общие ресурсы: программные отладчики

могут быть выявлены путем контроля флагового регистра, отладочных регистров, контроля целостности кода (выявление программных точек останова)
При отладке ядерным отладчиком ядерного кода – использование общего стека (можно было бы запускать обработку отладочных прерываний в отдельной задаче, но ни один доступный отладчик этого не делает) – отлаживаемый код может нарушить работу отладчика. Пример.
Доставлять или нет отлаживаемой программе контролируемые отладчиком прерывания? Все существующие отладчики могут быть выявлены при специальной организации в отлаживаемой программе обработки исключений (механизм SEH - Structured Exception Handling или VEH – Vectored Exception Handling). Пример.
Катастрофическое замедление программы при пошаговой отладке - все существующие отладчики могут быть выявлены путем контроля времени исполнения фрагментов программного кода

//выполните любым системным отладчиком (не эмулятором) пошаговую отладку данного кода драйвера:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
static ULONG tmpesp;
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);
__asm
{
mov tmpesp, esp
mov esp, 0
mov eax, 0
mov esp, tmpesp
}
return STATUS_SUCCESS;
}

Задача: найти ошибку в этом коде (исправить одну инструкцию)

Слайд 124

«Классические» отладчики уровня ОС фактически вытеснены отладочными возможностями симуляторов: backend:

«Классические» отладчики уровня ОС фактически вытеснены отладочными возможностями симуляторов:
backend: VmWare
frontend: интерфейс

GDB (IDA, VisualStudio, …)
QEMU, VirtualPC, BOCHS, AMD SimNow

Сам по себе дизассемблер или отладчик не автоматизирует решение задачи анализа потока данных, если под анализом понимать отслеживание зависимостей между инструкциями (по управлению и данным). Автоматизацией занимаются средства-«надстройки», например:
автоматизация поверх IDA:
Фирма zynamics (www.zynamics.com), средство BinNavi
Автоматизация поверх отладчиков - трассировка:
BitBlaze/TEMU
Valgrind + Avalanche
TREX

Слайд 125

Взаимодействие с устройствами ReadFile – ZwReadFile – IRP_MJ_READ WriteFile –

Взаимодействие с устройствами

ReadFile – ZwReadFile – IRP_MJ_READ
WriteFile – ZwWriteFile – IRP_MJ_WRITE
DeviceIoControl

– ZwDeviceIoControl – IRP_MJ_DEVICE_CONTROL
Слайд 126

Flags StackSize = 3 AttachedDevice Flags StackSize = 1 AttachedDevice

Flags
StackSize = 3
AttachedDevice

Flags
StackSize = 1
AttachedDevice


Пересылка запросов между устройствами - IoCallDriver()
Получение адреса

устройства по имени – IoGetDeviceObjectPointer()
Подключение фильтра – IoAttachDeviceToDeviceStack()

DeviceObjectN

DeviceObject1

Flags
StackSize = 2
AttachedDevice

FltDeviceObject

IoCallDriver()

Слайд 127

Драйвера подразделяются на 3 класса по их положению в стеке

Драйвера подразделяются на 3 класса по их положению в стеке драйверов:

драйвера высшего уровня, драйвера промежуточного уровня и драйвера низшего уровня.
Драйвер высшего уровня –верхний в стеке драйверов, получает запросы через Диспетчер в/в от компонентов прикладного уровня.
Драйвер высшего уровня (или, что более правильно, устройство высшего уровня) имеет один или несколько стеков размещения в/в.
Слайд 128

Число стеков размещения в/в устанавливается Диспетчером в/в в поле StackSize

Число стеков размещения в/в устанавливается Диспетчером в/в в поле StackSize объекта-устройство.

По умолчанию это значение равно 1. Присваивание происходит при создании устройства функцией IoCreateDevice(). Если создаётся многоуровневый драйвер, необходимо установить StackSize на 1 больше, чем StackSize нижележащего объекта-устройство. В случае, если устройство будет использовать больше одного устройства уровнем ниже, его поле StackSize должно быть на 1 больше максимального значения StackSize всех устройств уровнем ниже.
Слайд 129

Код запроса в/в сохранен в поле MajorFunction (есть еще второстепенный

Код запроса в/в сохранен в поле MajorFunction (есть еще второстепенный код

MinorFunction) текущего стека размещения ввода - вывода в IRP
PIO_STACK_LOCATION IoStack;
IoStack = IoGetCurrentIrpStackLocation(Irp);
switch (IoStack->MajorFunction)
{
case IRP_MJ_READ:

break;

}
Доступ к стеку нижележащего устройства:
IoStack = IoGetNextIrpStackLocation(Irp);
Слайд 130

BOOL WINAPI ReadFile( __in HANDLE hFile, __out LPVOID lpBuffer, __in

BOOL WINAPI ReadFile(
__in HANDLE hFile,
__out LPVOID lpBuffer,


__in DWORD nNumberOfBytesToRead,
__out_opt LPDWORD lpNumberOfBytesRead,
__inout_opt LPOVERLAPPED lpOverlapped );
Слайд 131

BOOL WINAPI WriteFile( __in HANDLE hFile, __in LPCVOID lpBuffer, __in

BOOL WINAPI WriteFile(
__in HANDLE hFile,
__in LPCVOID lpBuffer,


__in DWORD nNumberOfBytesToWrite,
__out_opt LPDWORD lpNumberOfBytesWritten,
__inout_opt LPOVERLAPPED lpOverlapped );
Слайд 132

По адресу lpNumberOfBytesRead/ lpNumberOfBytesWritten в результате успешного завершения функции помещается

По адресу lpNumberOfBytesRead/ lpNumberOfBytesWritten в результате успешного завершения функции помещается содержимое

поля Irp->IoStatus.Information диспетчерской функции драйвера, обработавшего запрос
Слайд 133

Метод передачи буфера, используемый в запросах чтения и записи, контролируется

Метод передачи буфера, используемый в запросах чтения и записи, контролируется полем

Flags объекта-устройство (DeviceObject->Flags).
После создания объекта-устройство с помощью функции IoCreateDevice() необходимо выставить в нем нужные флаги.
Можно устанавливать несколько флагов, при этом применяются следующие правила:
Если установлены флаги DO_BUFFERED_IO или DO_DIRECT_IO, метод передачи буфера будет соответственно буферизованным или прямым
Если поле флагов не инициализировано (никакие флаги не установлены), используется метод передачи буфера Neither.
Одновременная установка флагов DO_BUFFERED_IO и DO_DIRECT_IO запрещена и будет являться ошибкой.
Слайд 134

Слайд 135

Размер буфера для операций чтения/записи расположен в стеке размещения в/в:

Размер буфера для операций чтения/записи расположен в стеке размещения в/в:
Stack->Parameters.Read.Length
Stack->Parameters.Write.Length
С точки

зрения языка СИ поле Parameters – это объединение (union) структур для всех видов запросов
т.е. одна и та же область памяти (поле Parameters) может интерпретироваться различно в зависимости от вида запроса (код главной функции Stack->MajorFunction)
Слайд 136

Neither I/O buf_1 user kernel virtual address Адр. простр.1 Адр. простр.2 buf_2 физ. память чтение/запись

Neither I/O

buf_1

user

kernel

virtual address

Адр. простр.1

Адр. простр.2

buf_2

физ. память

чтение/запись

Слайд 137

Direct I/O buf_1 user kernel virtual address Адр. простр.1 Адр.

Direct I/O

buf_1

user

kernel

virtual address

Адр. простр.1

Адр. простр.2

buf_2

физ. память

buf_3

MDL

MmGetSystemAddressForMdl()

чтение/запись

MDL – структура, описывающая буфер с

сохранением информации об адресном пространстве, по сути описываются страницы физической памяти
Буфер в пользовательском диапазоне адресов, описанный через MDL, можно использовать вне зависимости от текущего адресного пространства
Преобразование MDL в буфер в виртуальной памяти в системном диапазоне адресов:
OutBuffer = MmGetSystemAddressForMdl( Irp->MdlAddress );
Слайд 138

Buffered I/O buf_1 user kernel virtual address Адр. простр.1 Адр.

Buffered I/O

buf_1

user

kernel

virtual address

Адр. простр.1

Адр. простр.2

buf_2

физ. память

buf_3

Буфер в невыгружаемой памяти

чтение/запись

Слайд 139

BOOL WINAPI DeviceIoControl( __in HANDLE hDevice, __in DWORD dwIoControlCode, __in_opt

BOOL WINAPI DeviceIoControl(
__in HANDLE hDevice,
__in DWORD dwIoControlCode,


__in_opt LPVOID lpInBuffer,
__in DWORD nInBufferSize,
__out_opt LPVOID lpOutBuffer,
__in DWORD nOutBufferSize,
__out_opt LPDWORD lpBytesReturned,
__inout_opt LPOVERLAPPED lpOverlapped );
Слайд 140

По адресу lpBytesReturned в результате успешного завершения функции помещается содержимое

По адресу lpBytesReturned в результате успешного завершения функции помещается содержимое поля

Irp->IoStatus.Information диспетчерской функции драйвера, обработавшего запрос
Способ передачи буфера в отличие от запросов чтения/записи управляется полем Method в значении контрольного кода dwIoControlCode
Слайд 141

CTL_CODE( DeviceType, Function, Method, Access ) DeviceType определяет тип объекта-устройство,

CTL_CODE( DeviceType, Function, Method, Access )

DeviceType определяет тип объекта-устройство, которому предназначен

запрос. Это тот самый тип устройства, который передается функции IoCreateDevice() при создании устройства.
Существует два диапазона значений типов устройств:
0-32767 – зарезервированные значения для стандартных типов устройств,
32768-65535 – диапазон значений типов устройств для выбора разработчиком.

Function идентифицирует конкретные действия, которые должно предпринять устройство при получении запроса. Значение должны быть уникальным внутри устройства. Два диапазона значений:
0-2047 – зарезервированный диапазон значений,
2048-4095 – диапазон значений, доступный разработчикам устройств.

FILE_ANY_ACCESS 0
FILE_READ_ACCESS 0x01
FILE_WRITE_ACCESS 0x02

METHOD_BUFFERED 0
METHOD_IN_DIRECT 1
METHOD_OUT_DIRECT 2
METHOD_NEITHER 3

Слайд 142

Слайд 143

METHOD_BUFFERED InBuf[InBufferSize ] OutBuf[OutBufferSize ] Промежуточный буфер [max(InBufferSize, OutBufferSize)] Irp->AssociatedIrp.SystemBuffer

METHOD_BUFFERED

InBuf[InBufferSize ]

OutBuf[OutBufferSize ]

Промежуточный буфер
[max(InBufferSize, OutBufferSize)]

Irp->AssociatedIrp.SystemBuffer

1. Перед вызовом IRP_MJ_DEVICE_CONTROL копируется InBufferSize байт

2.

После успешного завершения IRP_MJ_DEVICE_CONTROL копируется Irp->IoStatus.Information байт
Слайд 144

Механизмы синхронизации Спин-блокировки – для межпроцессорной синхронизации, синхронизации на уровне

Механизмы синхронизации

Спин-блокировки – для межпроцессорной синхронизации, синхронизации на уровне IRQL=DISPATCH_LEVEL (обычные

блокировки) или >DISPATCH_LEVEL (DIRQL)(блокировки обработчиков прерываний)
Для синхронизации на уровне либо Ресурсы Исполнительной системы (позволяют управлять синхронизацией вида «писатель - читатели»)
Слайд 145

GUI_App HANDLE hFile=CreateFile(“\\.\SymLinkName”, …) FILE_OBJECT DeviceObject DEVICE_OBJECT GuiDev DriverObject NextDevice

GUI_App

HANDLE hFile=CreateFile(“\\.\SymLinkName”, …)

FILE_OBJECT

DeviceObject

DEVICE_OBJECT
GuiDev

DriverObject
NextDevice
DeviceExtension

DeviceObject
MajorFunction[]

DRIVER_OBJECT
(FilterDriver)

DEVICE_OBJECT
TargetDev

DriverObject
NextDevice
AttachedDevice
DeviceExtension

DeviceObject
MajorFunction[]

DRIVER_OBJECT
(TargetDriver)

DEVICE_OBJECT
AttachedDev1

DriverObject
NextDevice
AttachedDevice
DeviceExtension


DEVICE_OBJECT
FilterDev

DriverObject
NextDevice
AttachedDevice
DeviceExtension

Слайд 146

IoGetDeviceObjectPointer() – по имени устройства получаем его DEVICE_OBJECT (TargetDevice) IoCreateDevice()

IoGetDeviceObjectPointer() – по имени устройства получаем его DEVICE_OBJECT (TargetDevice)
IoCreateDevice() создаем FilterDevice
IoAttachDeviceToDeviceStack()

подключаем FilterDevicve к TargetDevice, в результате получаем адрес последнего устройства в цепочке AttachedDevice
Слайд 147

Спин-блокировки (spin-lock) Спинлоки служат для обеспечения монопольного доступа потока к

Спин-блокировки (spin-lock)

Спинлоки служат для обеспечения монопольного доступа потока к защищаемой структуре

данных.
Физически спинлок представляет собой переменную в памяти и реализуется на атомарных операциях, которые должны присутствовать в системе команд процессора. Каждый процессор, желающий получить доступ к разделяемому ресурсу, атомарно записывает условное значение «занято» в эту переменную, используя аналог операции swap (в архитектуре x86 — xchg). Если предыдущее значение переменной (возвращаемое командой) было «свободно» то считается, что данный процессор получил доступ к ресурсу, в противном случае, процессор возвращается к операции swap и крутится в цикле ожидая, пока спинлок будет освобождён. После работы с разделяемым ресурсом процессор-владелец спинлока должен записать в него условное значение «свободно».
Слайд 148

Пример реализации спин-блокировки (для ядра Windows неверен) mov eax, spinlock_address

Пример реализации спин-блокировки (для ядра Windows неверен)

mov eax, spinlock_address
mov ebx,

SPINLOCK_BUSY
wait_cycle:
lock xchg [eax], ebx
cmp ebx, SPINLOCK_FREE
jnz wait_cycle

Поток 1

Поток 2

SP

1.

2.

3,4...

Слайд 149

Поток 1 Поток 2 SP 1. 2. 4,5... CPU 3. IRQL=PASSIVE_LEVEL IRQL=PASSIVE_LEVEL 6. 7. 9. 8.

Поток 1

Поток 2

SP

1.

2.

4,5...

CPU

3.

IRQL=PASSIVE_LEVEL

IRQL=PASSIVE_LEVEL

6.

7.

9.

8.

Слайд 150

Поток 1 Поток 2 SP Квантование времени выключено, переключение на

Поток 1

Поток 2

SP

Квантование времени выключено,
переключение на поток1 невозможно
deadlock

2.

4,5...

CPU

3.

IRQL=PASSIVE_LEVEL

IRQL=PASSIVE_LEVEL

6.

IRQL=DISPATCH_LEVEL

1.

Слайд 151

Поток 1 Поток 2 SP 1. 2. 3,4... CPU1 IRQL=PASSIVE_LEVEL

Поток 1

Поток 2

SP

1.

2.

3,4...

CPU1

IRQL=PASSIVE_LEVEL

IRQL=DISPATCH_LEVEL

5.

6.

IRQL=DISPATCH_LEVEL

Поток 1

Поток 2

SP

1.

2.

5

CPU

4.

IRQL=PASSIVE_LEVEL

IRQL=DISPATCH_LEVEL

3.

6.

IRQL=DISPATCH_LEVEL

CPU2

Правильная реализация спин-блокировок в ядре Windows:
в

момент захвата спин-блокировки уровень IRQL повышается до некоторого уровня IRQL>=DISPATCH, ассоциированного со спин-блокировкой;
в момент освобождения - восстановление старого уровня IRQL.

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

Многопроцессорная система: попытка захвата уже занятой блокировки может последовать только со стороны другого процессора, его работа блокируется до освобождения блокировки первым процессором

Слайд 152

С каждой спин-блокировкой связан конкретный уровень IRQL, на который перейдет

С каждой спин-блокировкой связан конкретный уровень IRQL, на который перейдет процессор

после захвата блокировки.
В соответствии с правилом, нельзя использовать блокировку из кода, работающего на IRQL>IRQL блокировки.
В Windows – 2 вида спин-блоктровок:
Обычные – с ними связан IRQL=DISPATCH_LEVEL
Спин-блокировки синхронизации прерываний – с ними связан один из DIRL

Правило использования спин-блокировок – ограничение на IRQL до и после захвата:
IRQLдо <= IRQLпосле

Слайд 153

Функции для работы с обычными спин-блокировками VOID KeInitializeSpinLock(IN PKSPIN_LOCK SpinLock);

Функции для работы с обычными спин-блокировками

VOID KeInitializeSpinLock(IN PKSPIN_LOCK SpinLock);
VOID KeAcquireSpinLock(IN PKSPIN_LOCK

SpinLock, OUT PKIRQL OldIrql);
VOID KeReleaseSpinLock(IN PKSPIN_LOCK SpinLock, IN KIRQL NewIrql);
VOID KeAcquireLockAtDpcLevel(IN PKSPIN_LOCK SpinLock);
VOID KeReleaseLockFromDpcLevel(IN PKSPIN_LOCK SpinLock);

typedef struct _DEVICE_EXTENSION
{
...
KSPIN_LOCK spinlock
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;
NTSTATUS DriverEntry(....)
{
KeInitializeSpinLock(&extension->spinlock);
}
NTSTATUS DispatchReadWrite( .... )
{
KIRQL OldIrql;
...
KeAcquireSpinLock(&extension->spinlock, &0ldIrql);
// произвести обработку данных, защищенных спин-блокировкой
KeReleaseSpinLock(&extension->spinlock, OldIrql);
}

Слайд 154

Взаимоблокировки (deadlocks) Решение: блокировки должны захватываться всеми потоками в одном

Взаимоблокировки (deadlocks)

Решение: блокировки должны захватываться всеми потоками в одном порядке

Поток 1

Поток

2

SP1

1

2

4,...

SP2

3,...

Проблема:

Поток 1

Поток 2

SP1

1.

2.

5,...

SP2

4

3.

6.

Слайд 155

Диспетчерские объекты Dispatcher Objects набор механизмов синхронизации, рассчитанных на применение

Диспетчерские объекты Dispatcher Objects

набор механизмов синхронизации, рассчитанных на применение в основном для

уровня IRQL PASSIVE_LEVEL.
В начале каждого объекта – структура DISPATCHER_HEADER
Два состояния: сигнальное и несигнальное
Типы диспетчерских объектов различаются правилом изменения состояния (правило перехода в сигнальное или несигнальное состояние
поток, ожидающий захвата диспетчерского объекта, блокирован и помещен в список ожидания в структуре DISPATCHER_HEADER.
Слайд 156

Блокирование потока – состояние потока, при котором он не занимает

Блокирование потока – состояние потока, при котором он не занимает время

процессора.
Блокированный поток не будет поставлен планировщиком в очередь на исполнение до тех пор, пока не будет выведен из состояние блокирования.
Слайд 157

Слайд 158

Диспетчерские объекты могут иметь имена в пространстве имен диспетчера объектов

Диспетчерские объекты могут иметь имена в пространстве имен диспетчера объектов (обычно

директория \BaseNamedObjects)
Через эти имена существующий объект можно открывать из приложений пользовательского режима или ядра ОС.
Кроме того, код ядра может получить доступ к объекту по его описателю (HANDLE): ObReferenceObjectByHandle()
Для окончания использования объекта, полученного через ObReferenceObjectByHandle() – функция ObDereferenceObject()
Слайд 159

Ожидание (захват) диспетчерских объектов Для ожидания момента перехода объекта из

Ожидание (захват) диспетчерских объектов

Для ожидания момента перехода объекта из несигнального в

сигнальное состояние служат специальные функции ожидания: KeWaitForSingleObject() и KeWaitForMultipleObjects()
в качестве одного из их параметров указывается интервал времени ожидания.
Функции вернут управление либо при захвате объекта, либо при истечении времени ожидания
!!! либо если были вызваны с ненулевым временем ожидания на IRQL=DISPATCH_LEVEL (вытесняющая многозадачность отключена, заменить текущий поток нечем) – так делать нельзя!!!
С ненулевым временем ожидания можно вызывать при IRQLС нулевым временем ожидания можно вызывать на IRQL<=DISPATCH_LEVEL
Нулевое время ожидания: параметр Timeout == NULL или *Timeout == 0
Нулевое время ожидания используется для проверки состояния объекта без блокирования потока
На DISPATCH_LEVEL можно использовать потому, что поток не блокируется, т.е. нет необходимости переключаться на другой поток
Слайд 160

Мьютексы ядра Мьютекс (mutex = Mutually EXclusive) означает взаимоисключение, т.е.

Мьютексы ядра

Мьютекс (mutex = Mutually EXclusive) означает взаимоисключение, т.е. мьютекс обеспечивает

нескольким потокам взаимоисключающий доступ к совместно используемому ресурсу. В отличие от спин-блокировки, ожидающий поток не блокирует процессор.
захват мьютекса является уникальным в рамках конкретного контекста потока. Поток, в контексте которого произошел захват мьютекса, является его владельцем, и может впоследствии рекурсивно захватывать его. Драйвер, захвативший мьютекс в конкретном контексте потока, обязан освободить его в том же контексте потока, нарушение этого правила приведет к появлению “синего экрана”.
Для мьютексов предусмотрен механизм исключения взаимоблокировок
Слайд 161

VOID KeInitializeMutex(IN PKMUTEX Mutex, IN ULONG Level); LONG KeReleaseMutex(IN PKMUTEX

VOID KeInitializeMutex(IN PKMUTEX Mutex, IN ULONG Level);
LONG KeReleaseMutex(IN PKMUTEX Mutex, IN

BOOLEAN Wait);
Если параметр Wait равен TRUE, сразу за вызовом KeReleaseMutex() должен следовать вызов одной из функций ожидания KeWaitXxx(). В этом случае гарантируется, что пара функций – освобождение мьютекса и ожидание – будет выполнена как одна операция, без возможного в противном случае переключения контекста потока.
LONG KeReadStateMutex(IN PKMUTEX Mutex);
Слайд 162

семафор более гибкая форма мьютексов. В отличие от мьютексов, программа

семафор

более гибкая форма мьютексов. В отличие от мьютексов, программа имеет контроль

над тем, сколько потоков одновременно могут разблокироваться семафором.
VOID KeInitializeSemaphore(
IN PKSEMAPHORE Semaphore,
IN LONG Count,
IN LONG Limit);
Count – начальное значение, присвоенное семафору, определяющее число свободных в данный момент ресурсов. Если Count=0, семафор находится в несигнальном состоянии (свободных ресурсов нет), если >0 – в сигнальном.
Limit – максимальное значение, которое может достигать Count (максимальное число свободных ресурсов).
Слайд 163

LONG KeReleaseSemaphore( _Inout_ PRKSEMAPHORE Semaphore, _In_ KPRIORITY Increment, _In_ LONG

LONG KeReleaseSemaphore(
_Inout_ PRKSEMAPHORE Semaphore,
_In_ KPRIORITY Increment,
_In_ LONG Adjustment,

_In_ BOOLEAN Wait
);
Adjustment:
на сколько должно увеличиться поле Count семафора (Count = Count + Adjustment ).
Не м.б. отрицательным.
Если Count + Adjustment > Limit, изменение Count не происходит, генерируется исключение STATUS_SEMAPHORE_LIMIT_EXCEEDED
Слайд 164

Использование семафоров: задача «потребители – производители» WaitFor… Потоки - производители

Использование семафоров: задача «потребители – производители»

WaitFor…

Потоки - производители

S2

S1

WaitFor…

ReleaseSemaphore

ReleaseSemaphore

Потоки - потребители

Заполнение задания

на обработку потребителем

Обработка и возврат обработанного задания производителю

Список пустых заданий

Очередь заданий

Начальное состояние:
очередь заданий пуста, все задания в списке пустых заданий, число заданий = MaxJobs

S1(Count=0, Limit=MaxJobs)

S2(Count=MaxJobs, Limit=MaxJobs)

Слайд 165

События (events) Позволяют проводить синхронизацию исполнения различных потоков, т.е. один

События (events)

Позволяют проводить синхронизацию исполнения различных потоков, т.е. один или несколько

потоков могут ожидать перевода события в сигнальное состояние другим потоком.
два вида событий:
события синхронизации (synchronization events). при переводе в сигнальное состояние будет разблокирован один поток, после чего событие автоматически переходит в несигнальное состояние.
Оповещающие события (notification event): при переводе в сигнальное состояние будут разблокированы все ожидающие событие потоки. Перевод события в несигнальное состояние - вручную.
Имя файла: Ядерная-архитектура-ОС-Windows.pptx
Количество просмотров: 83
Количество скачиваний: 0