Часть 1. Базовые функции
Монитор процессов и потоков.
Цель работы – практическое знакомство с методикой использования базовых функций для получения информации о процессах, потоках, модулях и кучах ОС Windows XP.
1. КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
1.1 Получение списка процессов, выполняющихся в системе
Задача получения списка выполняющихся в системе процессов является одной из основных при выполнении мониторинга ресурсов, как отдельного ПК, так и ЛВС в целом, поэтому для ее решения разработано значительное количество утилит, имеется встроенное системное средство – диспетчер задач.
Все перечисленные программные средства используют как функции Win32 API (Application Program Interface – прикладной программный интерфейс), так и функции еще одного базового интерфейса, называемого Native API (естественный API). Внешняя часть Native API пользовательского режима содержится в модуле ntdll.dll, «настоящий» интерфейс реализован в ntoskernel.exe – ядре операционной системы NT (NT operation system kernel). Функции Win32 API, как правило, обращаются к функциям Native API, отбрасывая часть полученной от них информации. Поэтому использование функций Native API позволяет получить, вообще говоря, более эффективное ПО.
К базовым функциям Win32 API для получения информации о выполняющихся в системе процессах относятся функции CreateToolHelp32Snapshot(), Process32First(), Process32Next(), Thread32First (), Thread32Next(), Module32First(), Module32Next(), Heap32ListFirst(), Heap32ListNext() и некоторые другие. Самая известная из функций Native API для доступа к содержимому многих важных внутренних структур операционной системы, таких как списки процессов, потоков, дескрипторов, драйверов и т. п. – функция NtQuerySystemInformation ().
1.1.1 Использование функций CreateToolHelp32Snapshot () и Process32xxxx() для получения списка имен процессов
Первый этап получения информации о выполняющихся в системе процессах - получение снимка (snapshot) системы, который содержит информацию о состоянии системы в момент выполнения снимка.
Снимок создается с помощью функции CreateToolHelp32Snapshot(dwFlags, th32ProcessID), первый аргумент определяет, какая информация будет записана в снимок - возможные значения dwFlags приведены в таблице.
Флаг - dwFlags | Описание |
TH32CS_SnapHEAPLIST | В снимок включается список куч, принадлежащих указанному процессу |
TH32CS_SnapPROCESS | В снимок включается список процессов, присутствующих в системе |
TH32CS_SnapTHREAD | В снимок включается список потоков |
TH32CS_SnapMODULE | В снимок включается список модулей, принадлежащих указанному процессу |
TH32CS_SnapALL | В снимок включается список куч, процессов, потоков и модулей |
Второй аргумент определяет процесс, информация о котором необходима (если требуется список куч и модулей). В остальных случаях он игнорируется.
Второй этап - извлечение из снимка списка процессов. Для выполнения этой операции служат функции:
Process32First ( hSnapshot, LPProcessEntry32)
Process32Next ( hSnapshot, LPProcessEntry32).
Первый аргумент - хэндл созданного снимка (возвращает функция CreateToolHelp32Snapshot).
Второй аргумент- структура, содержащая 10 полей:
1. Первое поле этой структуры - dwSize - должно перед вызовом функции содержать размер структуры в байтах - sizeof (ProcessEntry32).
2. Второе поле - cntUsage - содержит число ссылок на процесс, то есть число потоков, которые в настоящий момент используют какие-либо данные процесса.
3. Третье поле - th32ProcessID - является идентификатором процесса.
4. Шестое поле - cntThreads - определяет число потоков, принадлежащих процессу.
5. Седьмое поле - th32ParentProcessID - является идентификатором родительского по отношению к текущему процесса.
6. Поле pcPriClassBase cодержит базовый приоритет процесса.
7. Поле szExeFile cодержит имя файла, создавшего процесс.
Для того, чтобы получить информацию о первом процессе в снимке, необходимо вызвать функцию Process32First. В случае успешного завершения функция возвращает TRUE. Для того, чтобы просмотреть все оставшиеся процессы, нужно вызывать функцию Process32Next до тех пор, пока она не возвратит FALSE.
В список используемых модулей - uses - необходимо добавить модуль TlHelp32
Пример 1. Получить список имен выполняющихся в системе процессов, используя рассмотренные выше функции.
На форме размещены компоненты ListBox, Label и Button, обработчик события OnClick имеет вид:
var
SH : Thandle;
Num, I : Integer;
PPE : TProcessEntry32;
Pr_names : array [0..80] of string;
begin
Num := 0;
// получение снимка состояния системы
SH := CreateToolHelp32SnapShot(Th32cs_SnapAll, 0);
// выделение из снимка имени первого процессов
PPE.dwSize := sizeof (ProcessEntry32);
Process32First(SH, PPE);
Pr_Names [Num] := PPE.szExeFile;
// получение имен других процессов
while Process32Next(SH, PPE) do
begin
Num := Num + 1;
Pr_Names [Num] := PPE.szExeFile;
end;
Listbox1.Clear;
// вывод списка имен выполняющихся процессов
for I := 0 to Num do Listbox1.Items.Add (Pr_Names [I] );
// освобождение ресурса – снимка состояния системы
CloseHandle(SH)
end;
Результат выполнения примера 1 показан на рис. 1.
Рис. 1. Список выполняющихся процессов
1.1.2 Использование функций CreateToolHelp32Snapshot () и Thread32xxxx() для получения сведений о приоритетах потоков процессов
Для получения сведений о приоритетах потоков необходимо извлечь из снимка состояния системы с помощью функций Thread32First() и Thtead32Next () значения соответствующих полей.
Обращение к функциям имеет вид:
Thread32First ( hSnapshot, LPTHREADEntry32)
Thtead32Next ( hSnapshot, LPTHREADEntry32).
Первый аргумент - хэндл созданного снимка (возвращает функция CreateToolHelp32Snapshot).
Второй аргумент - структура, содержащая 7 полей:
1. Первое поле этой структуры - dwSize - должно перед вызовом функции содержать размер структуры в байтах - sizeof (THREADEntry32).
2. Поле th32OwnerProcessID содержит идентификатор родительского процесса.
3. Поле tpBasePri содержит текущий приоритет потока.
4. Поле tpDeltaPri содержит разность между текущим уровнем приоритета потока и базовым уровнем, то есть тем, который присваивается при создании потока.
Пример 2. Получить список выполняющихся в системе потоков, используя рассмотренные выше функции. Вывести содержимое полей 2 и 3 структуры THREADEntry32
На форме размещены компоненты ListView, Label и Button, обработчик события OnClick имеет вид:
var
Sh : Thandle;
Th : TTHREADENTRY32;
LstIt : TlistItem;
begin
Sh := CreateToolHelp32Snapshot (TH32CS_SNAPALL,0);
Th.dwSize := sizeof (TTHREADEntry32);
Thread32First(sh,Th);
ListView1.Items.Clear;
LstIt :=ListView1.Items.Add;
LstIt.Caption:=IntToStr(Th.th32OwnerProcessID);
LstIt.SubItems.Add(IntToStr(Th.tpBasePri));
repeat
LstIt :=ListView1.Items.Add;
LstIt.Caption:=IntToStr(Th.th32OwnerProcessID);
LstIt.SubItems.Add(IntToStr(Th.tpBasePri))
until not Thread32Next (sh,Th);
CloseHandle(Sh);
end;
Созданы 2 Columns, имеющие заголовки Идент процесса и Базовый приор потока (свойства ListView).
ВАЖНО! Свойство ViewStyle компонента ListView должно быть установлено в vsReport.
Результат выполнения примера 2 показан на рис. 2.
Рисунок 2. Список потоков
1.1.3 Использование функций CreateToolHelp32Snapshot () и Module32xxxx()
для получения списка модулей
Для получения сведений о приоритетах потоков необходимо извлечь из снимка состояния системы с помощью функций Module32First() и Module32Next () значения соответствующих полей.
Обращение к функциям имеет вид:
Module32First (hSnapshot, LPMODULEENTRY32);
Module32Next (hSnapshot, LPMODULEENTRY32).
Первый аргумент - хэндл созданного снимка (возвращает функция CreateToolHelp32Snapshot).
Второй аргумент - структура, содержащая 10 полей:
dwSize : DWORD -
th32ModuleID : DWORD – размер модуля
th32ProcessID : DWORD - идентификатор процесса, владеющего модулем
GlblcntUsage : DWORD - счетчик глобальных пользователей модуля
ProccntUsage : DWORD - счетчик процессов - пользователей
modBaseAddr : DWORD – базовый адрес модуля
modBaseSize : DWORD – базовый размер модуля
hModule : HMODULE - хэндл модуля
szModule : array [1.. MAX_MODULE_NAME32 + 1] of char – имя модуля
szExePath : array [1.. MAX_PATH] of char – путь размещения модуля.
1.1.4 Использование функций CreateToolHelp32Snapshot () и Heap32Listxxxx()
для получения списка куч
Для получения сведений о кучах необходимо извлечь из снимка состояния системы с помощью функций Heap32ListFirst() и Heap32ListNext () значения соответствующих полей.
Обращение к функциям имеет вид:
Heap32ListFirst (hSnapshot, LPHEAPLIST32);
Heap32ListNext (hSnapshot, LPHEAPLIST32).
Первый аргумент - хэндл созданного снимка (возвращает функция CreateToolHelp32Snapshot).
Второй аргумент - структура, содержащая 4 поля:
dwSize : DWORD - размер кучи
th32ProcessID : DWORD - идентификатор процесса, владеющего кучей
th32HeapID : DWORD - идентификатор кучи
dwFlags : DWORD - тип кучи
1.2 Завершение выбранного процесса
Для завершения процесса используется функция TerminateProcess ( HandleProc, ExitCode). Первый аргумент функции – описатель или хэндл процесса типа THandle – возвращается функцией, создавшей процесс, второй аргумент – код возврата типа DWord.
Значение описателя необходимо получить по идентификатору процесса с помощью функции
OpenProcess ( PROCESS_TERMINATE, // флаг доступа
false, // handle inheritance flag
procid ); // идентификатор процесса
Алгоритм завершения процесса включает следующие шаги:
1. Создать список процессов, используя описанную в п.1.1.1 методику. Кроме имен процессов, сохраняемых в массиве Pr_Names, необходимо сохранять в дополнительном массиве идентификаторы процессов.
2. Получить номер (индекс) выделенного мышью имени завершаемого процесса, используя метод ListBox1.ItemIndex и соответствующий ему идентификатор процесса.
3. По идентификатору процесса получить его описатель, используя функцию OpenProcess().
4. Если описатель получен, завершить процесс, используя полученный описатель.
Для процессов с небольшими значениями ProcID – системных процессов – функция OpenProcess () не возвращает описатель, так как обычное приложения не должно иметь возможности останавливать системные процессы (службы). В то же время иногда необходимо иметь под рукой средство для удаления из системы зависшей службы.
Уровень привилегий приложения можно повысить. В частности, отладчик для выполнения своих функций должен обладать самыми широкими полномочиями в отношении всех процессов системы. Изменение уровня привилегий процесса выполняется при помощи следующих действий:
1. Прежде всего, необходимо с помощью функции OpenProcessToken() из библиотеки advapi32.dll открыть токен доступа процесса.
2. Подготовить структуру TOKEN_PRIVILEGES, в которой разместить информацию о требуемом уровне привилегий.
3. Обратиться к функции AdjustTokenPrivilages().
Пример 3.Процедура получения привилегий отладчика. Для выполнения примера необходимо иметь права администратора.
procedure EnableDebugPriv;
var
hToken : THandle;
DebugValue: Int64;
tkp, oldtkp : TTokenPrivileges;
Return : DWORD;
begin
if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken)
then
LookupPrivilegeValue('', 'SeDebugPrivilege', DebugValue)
else
begin
ShowMessage ('Ошибка OpenProcessToken');
ExitCode := 1;
Exit;
end;
tkp.PrivilegeCount := 1;
tkp.Privileges[0].Luid := DebugValue;
tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
if AdjustTokenPrivileges (hToken, False, tkp, sizeof(TTokenPrivileges), OldTkp, Return)
then ExitCode :=0
else ExitCode :=2;
end;
1.3 Мониторинг процессe, tkp, sizeof(TTokenPrivileges), OldTkp, Return)
then ExitCode :=0
else ExitCode :=2;
end;
1.3 Мониторинг процессов
Мониторинг выполняющихся в системе процессов – основа всех приложений для наблюдения за работой информационных систем и их пользователей. Для отслеживания появления в системе новых приложений или завершения выполнявшихся можно использовать два способа:
1. периодическое выполнение снимка состояния системы и его анализ, для чего приложение, рассмотренное в п.1.1.1, подключается к обработчику прерываний таймера. Это просто, но неэффективно – приложения не запускаются и не завершаются то и дело.
2. подключение к процедуре запуска и завершения процессов с помощью функции ядра PsSetCreateProcessNotifyRoutine(),описанной в Windows DDK, путем регистрации функции обратного вызова. Это более эффективно, но требует разработки драйвера режима ядра.
2. МЕТОДИКА ВЫПОЛНЕНИЯ
2.1. Выполнить базовые задания для всех бригад:
2.1.1 Используя компонент ListBox, построить список процессов, выполняющихся в системе.
2.1.2 Для выбранного процесса вывести сведения о количестве его потоков. Процесс выбирать с помощью мыши в списке из окна Listboxа.
2.1.3 Добавить возможность завершения прикладных процессов системы с указанием процесса курсором окна ListBox. Системные процессы (службы) завершать не нужно. Проверить работу приложения.
2.1.4 Разработать простейший монитор имен процессов, используя первый способ п.1.3.