Введение
Сегодня, наверное, уже не осталось человека, который бы не программировал под Windows с прямым использованием ее API-функций, а не функций библиотеки компиляторов. Действительно, Windows 2000 предлагает столь внушительный набор экспортирумых функций, что их с избытком хватит для решения широкого круга задач. Другое преимущество заключается в том, что вызов API-функций позволяет взаимодействовать с Windows напрямую, минуя стандартные библиотеки компилятора. Но возникают несколько вопросов, например, откуда экспортируются функции API и как они реализованы. Данная статья поможет пролить свет на данные вопросы.
Подсистемы окружения
Наверное, первое что интересует программиста, который начинает писать программы с использованием Win32 API, так это то, откуда они экспортируются. Win32 API экспортируется из четырех динамически подключаемых библиотек: Kernel32.dll, User32.dll, Gdi32.dll, Advapi32.dll (Advanced API). Сразу нужно определиться с тем, что Windows 2000 поддерживает также стандарт на вызовы UNIX - POSIX, которые также реализованы в Windows 2000. Поэтому для Windows 2000 определено такое понятие, как
подсистема окружения (subsystem environment) - это часть поддержки среды операционной системы, предоставляемой пользователям и программистам. В Windows 2000 реализовано две подсистемы окружения: Win32 и POSIX. Первая является "родной" подсистемой, без которой работа Windows 2000 не представляется возможной, вторая запускается по требованию и предназначена для поддержки UNIX-приложений. Каждый EXE-файл принадлежит только одной подсистеме окружения и может вызывать только API, реализованные этой подсистемой. Таким образом, каждая подсистема проецирует на приложение свой образ операционной системы.
Ntdll.dll
Отличительной чертой Windows является то, что интерфейсы API отделены от их реализации. Это позволяет Microsoft изменять от версии к версии внутренние функции Windows, но оставлять неизменными функции в интерфейсных библиотеках. Так как для обработки большинства API-функций необходимо переключиться в режим ядра, то должна существовать универсальная библиотека, работающая как шлюз между пользовательским режимом и режимом ядра. Такой библиотекой является Ntdll.dll. Для каждой функции в Ntdll.dll существует инструкция перехода в привилегированный режим работы процессора для обработки соответствующей API-функции.
Функции библиотек подсистемы окружения Win32 необходимо разделить на две группы. К первой относятся функции, не требующие перехода в режим ядра, т. е. полностью реализованные в соответствующей DLL (GetCurrentProcess). Вторая группа функций - функции, требующие вызова к Ntdll.dll и как следствие переключения в режим ядра (WriteFile).
В режиме ядра также работают USER и GDI (подсистемы, реализующие GUI-интерфейс). Но в отличие от библиотек Kernel32.dll и Advapi32.dll, библиотеки User32.dll и Gdi32.dll транслируют свои вызовы в драйвер режима ядра Win32k.sys, который также является частью подсистемы Win32.
Исполнительная система и ядро
Ntdll.dll является всего лищь DLL-библиотекой пользовательского режима, для обработки API в режиме ядра она обращается к компонентам операционной системы. В Windows 2000 следует различать
исполнительную систему (executive), содержащую базовые сервисы операционной системы и
ядро (kernel), содержащего низкоуровневые сервисы операционной системы. Если представлять в виде иерархии, то Executive находится выше ядра, т. к. для реализации той или иной API ей требуется обращения к ядру. Например, планирование потоков происходит в диспетчере потоков в Executive, но сама диспетчеризация (переключение контекста) в ядре. Исполнительная система Executive образует верхний уровень Ntoskrnl.exe, а ядро - нижний. Executive состоит из диспетчеров, таких как диспетчер процессов, потоков, ввода-вывода и др.
Диспетчер системных сервисов
За направление на обработку соответствующему диспетчеру соответствующего системного сервиса отвечает диспетчер системных сервисов - функция KiSystemService в Ntoskrnl.exe. Она осуществляет диспетчеризацию или обработку системного сервиса, используя таблицу диспетчеризации системных сервисов. Вызов (активация) диспетчера системных сервисов происходит в результате программного прерывания int 0x2e (на процессорах х86), эта инструкция располагается в каждом сервисе в Ntdll.dll. Следующая схема иллюстрирует обработку системного сервиса.
KiSystemService принимает номер системного сервиса от Ntdll.dll, находит соответствующий элемент в таблице диспетчеризации системных сервисов и передает системный сервис на обработку исполнительной системе. Ntdll.dll передает в регистре EAX номер системного сервиса, регистр EBX указывает на параметры, передаваемые системному сервису. Затем следует инструкция int 0x2e и по таблице диспетчеризации прерываний, управление передается диспетчеру системных сервисов.
Вызываемые интерфейсы ядра
Как уже было указано, соответствующие библиотеки транслируют API-вызовы в вызовы к Ntdll.dll для переключения в режим ядра. Вызываемые функции Ntdll.dll, называются интерфейсами диспетчера системных сервисов или вызываемыми интерфейсами ядра. Microsoft некоим образом не документирует эти функции, а если и можно встретить в Platform SDK какую-то функцию, экспортируемую из Ntdll.dll, то будет указано, что лучше использовать соответствующие функции Win32 API и не лезть в библиотеку системной поддержки.
Для того, чтобы вызвать системный сервис из Ntdll.dll, нужно получить ее адрес в виртуальном адресном пространстве процесса функцией LoadLibrary, а затем получить указатель на соответствующую функцию вызовом GetProcAddress. Например, в следующем коде показано, как вызвать сервис NtQuerySystemInformation из Ntdll.dll для получения информации о количестве процессоров, установленных в системе.
//Определяем таким образом для Windows XP
#define _WIN32_WINNT 0x0501
#include
#include
#include
#define STATUS_SUCCESS (NTSTATUS)0x00000000L
void main()
{
SYSTEM_BASIC_INFORMATION sysinfo;
ULONG ReturnLength;
NTSTATUS
(WINAPI
*pNtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS,
PVOID,ULONG,PULONG);
HMODULE hModule=LoadLibrary("Ntdll.dll");
pNtQuerySystemInformation=
(NTSTATUS (WINAPI*)(SYSTEM_INFORMATION_CLASS,PVOID,ULONG,PULONG))
GetProcAddress(hModule,"NtQuerySystemInformation");
if(pNtQuerySystemInformation==NULL) abort();
if(((pNtQuerySystemInformation)(SystemBasicInformation,&sysinfo,
sizeof(sysinfo),&ReturnLength))!=STATUS_SUCCESS) abort();
printf("Number of processors: %d\n",sysinfo.NumberOfProcessors);
}
Здесь в качестве примера вызывается внутренний сервис
NtQuerySystemInformation в Ntdll.dll. Обратите внимание, что Ntdll.dll не проецируется на виртуальное адресное пространство процесса при вызове
LoadLibrary, т. к. она проецируется на начальном этапе запуска процесса. Здесь получается лишь ее базовый адрес в виртуальном адресном пространстве процесса.
Обратите особое внимание на файл winternl.h, в котором объявлены многие недокументированные структуры и функции. Наконец-то Microsoft приоткрыла завесу тайны над внутренними сервисами, экспортируемыми Ntdll.dll.
Этот заголовочный файл стал настоящей находкой для системных программистов, т. к. он содержит объявления многих недокументированных сервисов, экспортируемых из Ntdll.dll. Остается только догадываться, почему Microsoft решилась объявить эти функции, хотя они до сих пор относятся к разряду не документированных. К их числу относят: NtCreateFile, NtOpenFile, NtWaitForSingleObject. Например, прототип всем известного внутреннего сервиса NtCreateFile выглядит следующим образом.
NTSTATUS NtCreateFile( PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess,
ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);
Мало того, в Platform SDK Windows Server 2003 можно встретить описание этих недокументированных функций. Последнюю версию можно скачать с сайта Microsoft.
Функции, экспортируемые Ntdll.dll
Просмотреть вызываемые интерфейсы ядра, можно, используя программу dumpbin с ключом /EXPORTS. Она отобразит все экспортируемые Ntdll.dll функции. Как окажется, Ntdll.dll экспортирует множество разнообразных функций, в том числе, такие функции, как
strcmp,
strcpy для оперирования ANSI-строками и
wcscmp,
wcscpy для Unicode-строк.
Большинство функций Ntdll.dll начинаются со следующих префиксов.
Сс | Диспетчер кэша |
Cm | Диспетчер конфигурации |
Ex | Функции поддержки Executive |
FsRtl | Функции библиотеки периода выполения драйвера файловой системы |
Hal | Функции HAL |
Io | Функции диспетчера ввода-вывода |
Ke | Функции ядра |
Lpc | Механизм Lpc |
Lsa | Локальная аутентификация |
Mm | Функции диспетчера памяти |
Nt | Системные сервисы |
Ob | Функии диспетчера объектов |
Po | Функции диспетчера электропитания |
Pp | Функции диспетчера Plug and Play |
Ps | Функции поддержки процессов |
Rtl | Функции библиотеки периода выполнения |
Se | Функции защиты |
Wmi | WMI |
Zw | Точка входа для системных сервисов (аналогичная префиксу Nt) |
Win32 API и Windows API
С появлением 64-разрядных версий Windows XP и Windows Server 2003, интерфейс программирования в Windows стал называться Windows API, для обозначения как 32-разрядного API, так и 64-разрядного.