Delphi - сбориник статей


I. Слежение за процессами


На первый взгляд, задача кажется малорешаемой. На второй - после поиска в MSDN - понимаешь, что она не решаема в User-mode в том смысле, что нет соответствующих API. А впрочем, когда это было проблемой для настоящих программистов?

В режиме ядра задача решается тривиально - в драйвере регистрируешь callback функцией PsSetCreateProcessNotifyRoutine и он будет вызван при создании/удалении процесса. Но нам нужна реализация в user-mode...

Ограничимся тем, что будем отлавливать создание процессов. Первое, что приходит на ум, это следующий алгоритм:

  1. получить список процессов
  2. просмотреть его на предмет появления новых процессов
  3. переход на 1)
Реализуем его с помощью NATIVE API.

Отступление: Native API - это набор API, не документированный Microsoft (или документированный частично). С его помощью можно сделать все то же что можно сделать с помощью обычных API и многое другое. Мне в Native API нравится его структура, например с помощью одной функции ZwQuerySystemInformation можно получить очень большое количество информации (перечислить хэндлы, получить информацию о процессе и многое другое).

Для Delphi порт заголовочных файлов существует в нескольких вариантах, наиболее распространен вариант JEDI. Только, при использовании JEDI, придется все Zw-функции заменить на их Nt-аналоги. Впрочем, в режиме пользователя эти функции абсолютно идентичны. Разница наблюдается только в режиме ядра, подробнее читайте статью http://www.osronline.com/article.cfm?article=257 (она доступна только по подписке, или в кэше Google )

program process_seeker; {$APPTYPE CONSOLE} uses SysUtils, windows, tintlist; type NTStatus = cardinal; PVOID = pointer; USHORT = WORD; UCHAR = byte; PWSTR = PWideChar; CONST //Статус константы STATUS_SUCCESS = NTStatus($00000000); STATUS_ACCESS_DENIED = NTStatus($C0000022); STATUS_INFO_LENGTH_MISMATCH = NTStatus($C0000004); const SystemProcessesAndThreadsInformation = 5; type PClientID = ^TClientID; TClientID = packed record UniqueProcess:cardinal; UniqueThread:cardinal; end; PUnicodeString = ^TUnicodeString; TUnicodeString = packed record Length: Word; MaximumLength: Word; Buffer: PWideChar; end; PVM_COUNTERS = ^VM_COUNTERS; VM_COUNTERS = packed record PeakVirtualSize, VirtualSize, PageFaultCount, PeakWorkingSetSize, WorkingSetSize, QuotaPeakPagedPoolUsage, QuotaPagedPoolUsage, QuotaPeakNonPagedPoolUsage, QuotaNonPagedPoolUsage, PagefileUsage, PeakPagefileUsage: dword; end; PIO_COUNTERS = ^IO_COUNTERS; IO_COUNTERS = packed record ReadOperationCount, WriteOperationCount, OtherOperationCount, ReadTransferCount, WriteTransferCount, OtherTransferCount: LARGE_INTEGER; end; PSYSTEM_THREADS = ^SYSTEM_THREADS; SYSTEM_THREADS = packed record KernelTime, UserTime, CreateTime: LARGE_INTEGER; WaitTime: dword; StartAddress: pointer; ClientId: TClientId; Priority, BasePriority, ContextSwitchCount: dword; State: dword; WaitReason: dword; end; PSYSTEM_PROCESSES = ^SYSTEM_PROCESSES; SYSTEM_PROCESSES = packed record NextEntryDelta, ThreadCount: dword; Reserved1 : array [0..5] of dword; CreateTime, UserTime, KernelTime: LARGE_INTEGER; ProcessName: TUnicodeString; BasePriority: dword; ProcessId, InheritedFromProcessId, HandleCount: dword; Reserved2: array [0..1] of dword; VmCounters: VM_COUNTERS; IoCounters: IO_COUNTERS; // Windows 2000 only Threads: array [0..0] of SYSTEM_THREADS; end; Function ZwQuerySystemInformation(ASystemInformationClass: dword; ASystemInformation: Pointer; ASystemInformationLength: dword; AReturnLength:PCardinal): NTStatus; stdcall;external 'ntdll.dll'; { Получение буфера с системной информацией } Function GetInfoTable(ATableType:dword):Pointer; var mSize: dword; mPtr: pointer; St: NTStatus; begin Result := nil; mSize := $4000; //начальный размер буфера repeat mPtr := VirtualAlloc(nil, mSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE); if mPtr = nil then Exit; St := ZwQuerySystemInformation(ATableType, mPtr, mSize, nil); if St = STATUS_INFO_LENGTH_MISMATCH then begin //надо больше памяти VirtualFree(mPtr, 0, MEM_RELEASE); mSize := mSize * 2; end; until St <> STATUS_INFO_LENGTH_MISMATCH; if St = STATUS_SUCCESS then Result := mPtr else VirtualFree(mPtr, 0, MEM_RELEASE); end; var info, info2: PSystem_Processes; i, j, k: integer; t, t1: LARGE_INTEGER; process_id: tintegerlist; begin process_id := TIntegerList.Create; //СОЗДАЕМ СПИСОК ПРОЦЕССОВ НА МОМЕНТ СОЗДАНИЯ НАШЕГО ПРОЦЕССА info := GetInfoTable(SystemProcessesAndThreadsInformation); info2 := info; while (info2^.NextEntryDelta <> 0) do begin if (process_id.IndexOf(info2^.ProcessId)=-1) then process_id.Add(info2^.ProcessId); info2 := Pointer(dword(info2)+info2^.NextEntryDelta); end; VirtualFree(info, 0, MEM_RELEASE); //А теперь смотрим что добавилось while true do begin Sleep(200); info := GetInfoTable(SystemProcessesAndThreadsInformation); info2 := info; while (info2^.NextEntryDelta <> 0) do begin if (process_id.IndexOf(info2^.ProcessId)=-1) then begin writeln(info2^.ProcessId, ' - created'); process_id.Add(info2^.ProcessId); end; info2 := Pointer(dword(info2)+info2^.NextEntryDelta); end; VirtualFree(info, 0, MEM_RELEASE); end; end.

Вы можете легко переделать этот код для того, чтобы отслеживать также и терминирование процессов :) Оставим это читателю в качестве домашнего задания :)




Начало    Вперед