|
In questo documento andiamo ad affrontare l' utilizzo delle API Native di
Windows: sono api non documentate nel Platform SDK e di cui viene sconsigliato
l' utilizzo per il fatto che possono essere soggette a modifiche strutturali da
una versione all' altra del sistema operativo (del resto non essendo fornita una
documentazione ufficiale non viene nemmeno perseguito l' obiettivo della
compatibilità retroattiva). Tuttavia non è che poi ci siano tutte queste
modifiche: anzi il programma seguente funziona correttamente su un Windows 2000
sp4 ed un Windows xp sp2 entrambi aggiornati con le patch fornite alla data
22/12/2004. Utilizzeremo l' api NtQuerySystemInformation implementata nella dll
ntdll.dll per enumerare i processi: altri metodi (perfettamente
documentati nel Platform SDK) per l' enumerazione dei processi consistono
nell' utilizzo della "Tool Help Library", della "Process Status Helper Library"
(PSAPI), della "Performance Data Helper Library". Lo stesso Task Manager
utilizza NtQuerySystemInformation per enumerare i processi. Per le dichiarazioni e le definizioni delle varie strutture
utilizzeremo la unit Native.pas creata da Marcel Van Brakel e liberamente
scaricabile all' indirizzo
http://members.chello.nl/m.vanbrakel2/. Vediamo ora
di esaminare la funzione NtQuerySystemInformation
function NtQuerySystemInformation(SystemInformationClass: SYSTEM_INFORMATION_CLASS;
SystemInformation: PVOID;
SystemInformationLength: ULONG;
ReturnLength: PULONG
): NTSTATUS; stdcall;
con
type
_SYSTEM_INFORMATION_CLASS = (
SystemBasicInformation,
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemNotImplemented1,
SystemProcessesAndThreadsInformation,
SystemCallCounts,
SystemConfigurationInformation,
SystemProcessorTimes,
SystemGlobalFlag,
SystemNotImplemented2,
SystemModuleInformation,
SystemLockInformation,
SystemNotImplemented3,
SystemNotImplemented4,
SystemNotImplemented5,
SystemHandleInformation,
SystemObjectInformation,
SystemPagefileInformation,
SystemInstructionEmulationCounts,
SystemInvalidInfoClass1,
SystemCacheInformation,
SystemPoolTagInformation,
SystemProcessorStatistics,
SystemDpcInformation,
SystemNotImplemented6,
SystemLoadImage,
SystemUnloadImage,
SystemTimeAdjustment,
SystemNotImplemented7,
SystemNotImplemented8,
SystemNotImplemented9,
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemLoadAndCallImage,
SystemPrioritySeparation,
SystemNotImplemented10,
SystemNotImplemented11,
SystemInvalidInfoClass2,
SystemInvalidInfoClass3,
SystemTimeZoneInformation,
SystemLookasideInformation,
SystemSetTimeSlipEvent,
SystemCreateSession,
SystemDeleteSession,
SystemInvalidInfoClass4,
SystemRangeStartInformation,
SystemVerifierInformation,
SystemAddVerifier,
SystemSessionProcessesInformation);
SYSTEM_INFORMATION_CLASS = _SYSTEM_INFORMATION_CLASS;
Come si può vedere il tipo enumerativo _SYSTEM_INFORMATION_CLASS (tipologia
del primo parametro) definisce il tipo di informazione di sistema che la
NtQuerySystemInformation deve prelevare. Nel caso in cui si vogliano enumerare i
processi si utilizzerà il valore SystemProcessesAndThreadsInformation (intero 5
come si può vedere dal fatto che è al 6° posto nell' enumerazione (enumerazione che è in base 0)). Il parametro SystemInformation è un puntatore che definisce l' indirizzo del risultato
della query sul sistema effettuata dalla NtQuerySystemInformation: nel caso in
cui vogliamo enumerare i processi, tale risultato consisterà in una sequenza di
blocchi di byte ognuno dei quali raccoglie le informazioni di ogni singolo
processo; ognuno di questi blocchi di byte può essere rappresentato dal seguente
record
_SYSTEM_PROCESSES = record // Information Class 5
NextEntryDelta: ULONG;
//offset all' inizio del blocco successivo
ThreadCount: ULONG;
//numero di thread
Reserved1: array [0..5] of ULONG;
CreateTime: LARGE_INTEGER;
//l' istante di creazione del processo espresso come il numero di intervalli
//da 100 nanosecondi a partire dal 1 Gennaio 1601
UserTime: LARGE_INTEGER;
//tempo totale di esecuzione in modalità utente da parte dei thread del processo
//espresso in numero di intervalli da 100 nanosecondi
KernelTime: LARGE_INTEGER;
//tempo totale di esecuzione in modalità kernel da parte dei thread del processo
//espresso in numero di intervalli da 100 nanosecondi
ProcessName: UNICODE_STRING;
//nome del processo: dalla definizione di UNICODE_STRING si ha che il nome del processo
//è dato da ProcessName.Buffer (di tipo PWideChar)
BasePriority: KPRIORITY;
//Priorità di esecuzione con la quale vengono creati i thread del processo
ProcessId: ULONG;
//PID del processo
InheritedFromProcessId: ULONG;
//PID del processo padre
HandleCount: ULONG;
//numero di handle aperti dal processo
SessionId: ULONG;
//Id della sessione in cui viene eseguito il processo
Reserved2: ULONG;
VmCounters: VM_COUNTERS;
//struttura che raccoglie statistiche sull' utilizzo della memoria virtuale da
//parte del processo
IoCounters: IO_COUNTERSEX; // Windows 2000 only
//struttura che raccoglie statistiche sulle operazioni di I/O effettuate dal processo
Threads: array [0..0] of SYSTEM_THREADS;
//array in cui ogni elemento rappresenta le caratteristiche di ognuno dei thread del processo
end;
SYSTEM_PROCESSES = _SYSTEM_PROCESSES;
PSYSTEM_PROCESSES = ^SYSTEM_PROCESSES;
TSystemProcesses = SYSTEM_PROCESSES;
PSystemProcesses = PSYSTEM_PROCESSES;
Non mi sto a dilungare a questo punto sulla definizione dei tipi dei vari
campi del record in questione (ad esempio Threads, IoCounters, etc...) in quanto
sono presenti anch' essi nella unit Native.pas creata da Marcel Van Brakel. Il campo principale è NextEntryDelta: per ogni
blocco che mi rappresenta le caratteristiche di un processo, il valore di
NextEntryDelta indica a quanti byte di distanza, dall' inizio del blocco,
incomincia il blocco successivo. Tale valore ci consente di enumerare tutti i
blocchi: l' ultimo blocco avrà NextEntryDelta = 0 (questo sarà quindi il
criterio di arresto dell' enumerazione). Il parametro SystemInformationLength
indica la dimensione che diamo alla memoria puntata da SystemInformation;
ReturnLength indica invece la dimensione effettiva dell' informazione ottenuta
come risultato della query sul sistema. Come fare per determinare la dimensione della memoria da allocare e da far puntare al parametro SystemInformation (ossia il valore da dare al parametro SystemInformationLength)? Bisogna osservare che nel caso delle
tipologia di informazione SystemCallCounts (valore intero 6) e
SystemModuleInformation (valore intero 11), se assegnamo a
SystemInformationLength un valore inferiore alla dimensione effettiva del
risultato, allora ReturnLength restituisce la dimensione effettiva del
risultato. In questo modo possiamo procedere eseguendo una prima chiamata con
SystemInformationLength = 0, ReturnLength avrà come valore la dimensione effettiva del
risultato, allochiamo un blocco di memoria di dimensione pari a ReturnLength e
chiamiamo nuovamente NtQuerySystemInformation passandogli come SystemInformation
un puntatore alla memoria appena allocata. Purtroppo nel caso dell' enumerazione
dei processi e dei thread (tipologia di informazione
SystemProcessesAndThreadsInformation corrispondente al valore intero 5) questo meccanismo non funziona. Siamo quindi costretti ad andare a spanne partendo da una dimensione
iniziale ed incrementando tale dimensione fino a quando la NtQuerySystemInformation viene eseguita senza restituire il valore $C0000004 che
indica appunto fallimento per dimensione insufficiente della memoria allocata. Per dovere di cronaca la NtQuerySystemInformation può restituire i seguenti valori: $00000000: successo $C0000002: operazione non implementata $C0000003: classe di informazione non valida: è stato dato al primo parametro un valore che non rientra in nessuno di quelli specificati dall' enumerazione SYSTEM_INFORMATION_CLASS $C0000004: dimensione della memoria allocata inferiore alla dimensione effettiva del risultato Bene mi sembra tutto ed è ora di
passare ad un esempio: una form, un button ed un memo
procedure ElencoProcessi;
var
i,rl,cp : dword;
pinfo : PSystemProcesses;
buf : Pointer;
dim: dword;
begin
dim := 10000;
GetMem(buf, dim);
rl := 0;
//N.B. se il primo parametro è 6 (SystemCallCounts) o 11 (SystemModuleInformation):
//se il valore assegnato a dim non contiene tutto il risultato restituito dalla funzione,
//(e quindi viene restituito il valore STATUS_INFO_LEN_MISMATCH che equivale a $C0000004
//come è possibile vadere nel file NTSTATUS.h presente nel DDK), allora il parametro rl
//conterrà la dimensione effettiva da assegnare a buf; in tutti gli altri casi bisogna
//andare a spanne come di seguito (SystemProcessesAndThreadsInformation equivale a 5)
i := NtQuerySystemInformation(SystemProcessesAndThreadsInformation, buf, dim, @rl);
while (i = $C0000004) do
begin
dim := dim + 10000;
FreeMem(buf);
GetMem(buf, dim);
i := NtQuerySystemInformation(SystemProcessesAndThreadsInformation, buf, dim, @rl);
end;
if i = 0 then
begin
cp := 0;
repeat
pinfo := PSystemProcesses(Pointer(dword(buf) + cp));
cp := cp + pinfo.NextEntryDelta;
with pinfo^ do
begin
if (ProcessName.Buffer <> nil) then
begin
Form1.Memo1.Lines.Add(WideCharToString(ProcessName.Buffer))
end
else
begin
Form1.Memo1.Lines.Add('System Idle');
end;
Form1.Memo1.Lines.Add(' ' + 'Numero di thread: ' + IntToStr(ThreadCount));
Form1.Memo1.Lines.Add(' ' + 'PID: ' + IntToStr(ProcessId));
Form1.Memo1.Lines.Add(' ' + 'PID processo padre: ' + IntToStr(InheritedFromProcessId));
Form1.Memo1.Lines.Add(' ' + 'Numero di handle: ' + IntToStr(HandleCount));
Form1.Memo1.Lines.Add(' ' + 'Id di sessione: ' + IntToStr(SessionId));
end;
until (pinfo.NextEntryDelta = 0);
end;
FreeMem(buf);
end;
|