Часть 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.