Home | Chi sono | Contattami
 

Progr. lineare

Delphi
 
Componenti
  Database
 
Miei articoli

Windows

Miei articoli 

 

Native API per la gestione di DLL


In questo articolo analizzeremo le API Native inerenti la gestione delle DLL. Verranno esaminate una ad una tutte le funzioni interessate, verr analizzata l'API win32 corrispondente ad ognuna, etc... Bene, come al solito passiamo dalle parole ai fatti.

1. Gestione degli errori per le API Native

Le API Native sono le funzioni esportate dalla dll ntdll.dll; questa dll caricata nello spazio di memoria di tutti i processi, sempre la prima dll ad essere caricata e viene caricata sempre allo stesso indirizzo. Prima di passare all'esame delle singole API opportuno esaminare quelli che sono i concetti fondamentali associati alle API Native. Come prima cosa analizziamo la situazione in cui una API Native restituisce un errore.  

1.1 Win32

Le api Win32 sono le funzioni classiche che vengono utilizzate nella realizzazione di un programma: sono implementate nelle varie dll del sistema operativo tipo kernel32.dll, user32.dll, advapi.dll, etc... La prassi vuole che, nel caso una di queste funzioni incontri un problema nella propria esecuzione, prima di interrompere l'esecuzione viene salvato il codice relativo all'errore (valore di 4 byte) in una zona di memoria prefissata ed associata al thread chiamante. Tale valore pu poi essere letto tramite l'API win32 GetLastError. Nella maggior parte dei casi (non sempre per) se una API win32 ha incontrato un errore nel corso della sua esecuzione, dopo aver salvato il codice di errore, prima di uscire definitivamente, restituisce 0 come output (ripeto, quasi sempre ma non sempre, consultare al riguardo il Platform SDK per vedere il comportamento dell'api che ci interessa di volta in volta). Quindi in pratica tutte le volte che si chiama una api win32 occorre memorizzarsi l'output e, nel caso che corrisponda ad un fallimento di esecuzione (0 nella maggiorparte dei casi, ma non sempre come gi detto), si esegue una chiamata all'api win32 GetLastError per ottenere il codice di errore. Come gi detto, il codice di errore un valore a 32 bit, di seguito la definizione

come si pu notare, ogni gruppo di bit ha un significato particolare; senza entrare troppo nei dettagli, si pu vedere come i bit 30 e 31 rappresentino la tipologia di risultato (successo, informazione, avvertimento o errore). Se il bit 31 vale 1 allora si tratta di un errore o di un avvertimento; se il codice di errore viene interpretato come integer (range di definizione [-2147483648..2147483647]), il bit 31 rappresenta il segno: se impostato ad 1, il valore negativo (quindi se il valore interpretato come integer negativo allora si tratta di un errore). Il bit 29 riservato per la definizione di nuovi codici di errore: ad esempio posso crearmi delle procedure il cui comportamento (a livello di gestione errori di esecuzione) sia equivalente a quello delle api win32; in caso di errore, prima di uscire (e far restituire alla mia funzione un valore identificativo del fallimento avvenuto), vado a salvare il codice di errore corrispondente che ho definito: il salvataggio del mio codice di errore pu essere effettuato con l'api win32 SetLastError che prende appunto in input il valore a 32 bit che rappresenta il mio codice di errore. Anche in questo caso, si potr leggere il codice di errore grazie alla GetLastError. La SetLastError appunto la funzione usata dalle api win32 per salvare il codice di errore. Come si gi capito, non necessariamente vengono salvati codici relativi ad errore, ma alcune api win32 possono anche ad esempio chiamare SetLastError con parametro settato a 0 per indicare successo. Come ultima cosa utile dire che le api GetLastError e SetLastError sono esportate dalla kernel32.dll ma non sono altro che un forward alle funzioni RtlGetLastError e RtlSetLastError implementate in ntdll.dll. Per ottenere una stringa descrittiva del codice in questione si pu ricorrere all'api win32 FormatMessage; Delphi fornisce una procedura che fa da wrapper alla FormatMessage: SysErrorMessage. Per finire definiamo la seguente procedura che ci consente di loggare gli errori

function ErrStr(nomeFunc: string): Boolean; var error_code: Cardinal; error_description: string; error_string: string; begin result := False; error_code := GetLastError; error_description := SysErrorMessage(error_code); error_string := nomeFunc + ': ' + 'CODErr=' + IntToStr(error_code) + ' Descr=' + error_description; ScriviLog(error_string); result := True; end;

1.2 Native

Le api Native sono le funzioni esportate dalla ntdll.dll. A differenza delle api win32, le api Native non salvano eventuali errori (tramite la SetLastError), ma restituiscono l'errore stesso come output. Il codice di errore ha la stessa struttura di quello delle api win32: 32 bit con i significati precedentemente descritti. Nell'ambito Native si fa spesso uso del tipo NTSTATUS: non altro che un alias del tipo Integer; la prassi sempre quella di convertire in Integer (NTSTATUS) l'output della funzione Native: se il valore negativo (bit 31 settato a 1 come visto al paragrafo precedente) allora si tratta di un errore. A questo punto ci viene in aiuto una funzione esportata sempre da ntdll.dll: si tratta dell'api Nativa RtlNtStatusToDosError che converte il valore restituito da una API Native nel corrispondente valore nell'ambito win32 (ossia i valori che vengono salvati dalle api win32 e poi restituiti dalla GetLastError); del resto tantissime api win32 sono dei wrapper alle corrispondenti api Native e l'esempio pi lampante sono appunto le api per la gestione delle dll che andremo ad analizzare in questo articolo. Il seguente pseudocodice definisce in linea di massima il funzionamento di un'api win32 che chiama la corrispondente api Native

var Status: Integer; ... function Win32API( //parametri ): boolean; begin ... ... Status := NativeAPI( //parametri ); if status < 0 then begin SetLastError(RtlNtStatusToDosError(Status)); Result := False; Exit; end; end;

Bene, come ultima cosa anche in questo caso definiamo la seguente procedura che ci consente di loggare gli errori

function IntToHex(dwValue, dwDigits: Cardinal): String; stdcall; const hex: array[0..$F] of char = ('0','1','2','3','4','5','6','7', '8','9','A','B','C','D','E','F'); begin if (dwDigits > 8) then dwDigits := 8; Result := Copy( hex[(dwValue and $F0000000) shr 28]+ hex[(dwValue and $0F000000) shr 24]+ hex[(dwValue and $00F00000) shr 20]+ hex[(dwValue and $000F0000) shr 16]+ hex[(dwValue and $0000F000) shr 12]+ hex[(dwValue and $00000F00) shr 8]+ hex[(dwValue and $000000F0) shr 4]+ hex[(dwValue and $0000000F) shr 0],9-dwDigits,dwDigits); end; function ErrStrNative(nomeFunc: string; status: Integer): Boolean; var error_code: Cardinal; error_description: string; error_string: string; begin result := False; error_string := nomeFunc + ': ' + 'NTSTATUSCODErr=' + IntToHex(status,8); error_code := RtlNtStatusToDosError(status); error_description := SysErrorMessage(error_code); error_string := error_string + '; CODErr=' + IntToStr(error_code) + '; Descr=' + error_description; ScriviLog(error_string); result := True; end;

2. Strutture e funzioni che ricorrono nell'ambito delle api Native

Bene. Dopo un breve preambolo inerente gli errori di sistema e la loro gestione in ambito sia Win32 sia Native, arrivato il momento di analizzare alcune strutture e funzioni che useremo nella gestione delle api che dobbiamo trattare:

2.1 ANSI_STRING e UNICODE_STRING 

Sono le 2 strutture fondamentali: di seguito le definizioni

type PANSI_STRING = ^ANSI_STRING; ANSI_STRING = record Length: Word; MaximumLength: Word; Buffer: PAnsiChar; end; PUNICODE_STRING = ^UNICODE_STRING; UNICODE_STRING = record Length: Word; MaximumLength: Word; Buffer: PWideChar; end;

Di seguito andiamo ad esporre alcune delle funzioni che coinvolgono queste 2 strutture

2.2 Funzioni di conversione

ANSI_STRING --> UNICODE_STRING

function RtlAnsiStringToUnicodeString( DestinationString : PUNICODE_STRING; SourceString : PANSI_STRING; AllocateDestinationString : Boolean ): Integer; stdcall; external 'ntdll.dll';

UNICODE_STRING --> ANSI_STRING

function RtlUnicodeStringToAnsiString( DestinationString : PANSI_STRING; SourceString : PUNICODE_STRING; AllocateDestinationString : Boolean ): Integer; stdcall; external 'ntdll.dll';

Vediamo i singoli parametri:

  • DestinationString: puntatore al buffer che conterr il risultato della conversione
  • SourceString: puntatore al buffer che contiene il valore da convertire
  • AllocateDestinationString: specifica se la funzione deve allocare la memoria puntata da DestinationString

2.3 Deallocazione

ANSI_STRING

procedure RtlFreeAnsiString( AnsiString : PANSI_STRING ); stdcall; external 'ntdll.dll';

UNICODE_STRING

procedure RtlFreeUnicodeString( UnicodeString : PUNICODE_STRING ); stdcall; external 'ntdll.dll';

2.4 Conversione dell'output di un'api Native nel corrispondente codice Win32

function RtlNtStatusToDosError(const Status : Integer ): Cardinal; stdcall; external 'ntdll.dll';

3. API Native per la gestione delle DLL

Bene, dopo i dovuti preamboli, buttiamoci ora a capofitto sulle singole api. Parleremo di api per la gestione di dll quindi la prima cosa da fare prepararsi una dll di esempio: eccola

library TestDll; uses Windows, SysUtils, Classes; procedure Somma(i1: Cardinal; i2: Cardinal; var i3: Cardinal); stdcall; begin i3 := i1 + i2; end; procedure ScriviSuFile(nomefile: string; val: string); stdcall; var error_log_File : TextFile; FileHandle: integer; begin if not FileExists(nomefile) then begin FileHandle := FileCreate(nomefile); FileClose(FileHandle); end; AssignFile(error_log_File, nomefile); Append(error_log_File); WriteLn(error_log_File, val); CloseFile(error_log_File); end; procedure EntryPointProc(reason: integer); begin case reason of DLL_PROCESS_ATTACH: //1 begin DisableThreadLibraryCalls(HInstance); end; DLL_THREAD_ATTACH: //2 begin end; DLL_PROCESS_DETACH: //3 begin end; DLL_THREAD_DETACH: //0 begin end; end; end; exports ScriviSuFile name 'LogData', Somma name 'Addizione'; begin DllProc := @EntryPointProc; DllProc(DLL_PROCESS_ATTACH); end.

3.1 LdrLoadDll

Carica un modulo (dll) nello spazio di memoria del processo chiamante.

API Win32 corrispondenti: LoadLibrary, LoadLibraryEx

function LdrLoadDll( PathToFile : PWideChar; Flags : Cardinal; ModulFileName : PUNICODE_STRING; ModuleHandle : pCardinal ): integer; stdcall; external 'ntdll.dll';

  • PathToFile:[input] opzionale: non lo usiamo (almeno in questa trattazione)
  • Flags:[input] opzionale: non lo usiamo (almeno in questa trattazione). N.B. ha lo stesso significato del parametro Flags dell'api win32 LoadLibraryEx (rimando al Platform SDK per approfondimenti)
  • ModuleFileName:[input] nome del modulo da caricare con percorso completo
  • ModuleHandle:[output] indirizzo di base del modulo caricato, nello spazio di memoria del processo chiamante.

L'API Native LdrLoadDll viene chiamata dalle API Win32 LoadLibrary e LoadLibraryEx di cui di seguito vediamo le dichiarazioni

function LoadLibraryA(lpLibFileName: PAnsiChar ): Cardinal; stdcall; function LoadLibraryW(lpLibFileName: PWideChar ): Cardinal; stdcall; function LoadLibraryExA( lpLibFileName: PAnsiChar; hFile: Cardinal; dwFlags: Cardinal ): Cardinal; stdcall; function LoadLibraryExW( lpLibFileName: PWideChar; hFile: Cardinal; dwFlags: Cardinal ): Cardinal; stdcall;

Come si pu notare, entrambe le funzioni hanno una doppia implementazione: A per l'insieme dei caratteri ASCII e W per l'insieme dei caratteri UNICODE.

LoadLibrary:

  • lpLibFileName: nome del modulo da caricare

LoadLibrarEx:

  • lpLibFileName: nome del modulo da caricare
  • hFile: RISERVATO: va settato a 0
  • dwFlags: comportamento adottato dalla funzione: rimando al Platform SDK per approfondimenti; la cosa importante da dire che, se questo parametro impostato a 0 allora la LoadLibraryEx equivalente alla LoadLibrary. Inoltre il valore di questo parametro viene passato al parametro dwFlags della LdrLoadDll.

Il comportamento della LdrLoadDll quindi quello descritto nel Platform SDK per le 2 api win32 LoadLibrary e LoadLibraryEx: tenta di caricare il modulo specificato, nello spazio di memoria del processo chiamante; in particolare, se il modulo gi caricato allora si limita ad incrementare di 1 il Load Count del medesimo, altrimenti carica il modulo e chiama il suo entrypoint. E' da osservare che, se dwFlags settato a 2 (costante LOAD_LIBRARY_AS_DATAFILE), la dll viene caricata in memoria come un semplice file dati: non viene chiamato il suo entrypoint tantomeno potranno poi essere usate su di essa le normali api win32 tipo GetModuleHandle o GetProcAddress; utile effettuare un tale tipo di caricamento quando si vuole far riferimento ad una dll solo per estrarre messaggi o risorse da essa.

Tra i possibili output della funzione LdrLoadDll c' il seguente

$C0000135: corrisponde al codice di errore win32 126 (basta chiamare RtlNtStatusToDosError($C0000135)): sta ad indicare che non stato possibile trovare il modulo specificato (stringa descrittiva ottenuta tramite SysErrorMessage: "Impossibile trovare il modulo specificato")

3.2 LdrGetDllHandle

Ottiene l'indirizzo di base del modulo specificato, nello spazio di memoria del processo chiamante

API win32 corrispondenti: GetModuleHandle

function LdrGetDllHandle( pwPath : PWORD; Unused : pointer; ModuleFileName : PUNICODE_STRING; pHModule : pCardinal ): Integer; stdcall; external 'ntdll.dll';

  • pwPath: [input] opzionale: non lo usiamo
  • Unused: [input] opzionale: un nome, una garanzia: col cavolo che lo usiamo
  • ModuleFileName: [input] nome del modulo di cui si vuol prelevare l'indirizzo di base nello spazio di memoria del processo chiamante
  • pHModule: [output] indirizzo di base del modulo nello spazio di memoria del processo chiamante

L'API Native LdrGetDllHandle viene chiamata dall'API Win32 GetModuleHandle di cui di seguito vediamo la dichiarazione

function GetModuleHandleA( lpModuleName: PAnsiChar ): Cardinal; stdcall; function GetModuleHandleW( lpModuleName: PWideChar ): Cardinal; stdcall;

  • lpModuleName: nome del modulo di cui si vuole determinare l'indirizzo di base nello spazio di memoria del processo chiamante (non c' bisogno di specificare il percorso completo)

Se il modulo richiesto non presente in memoria, la funzione restituisce il valore $C0000135 (126 in win32)

3.3 LdrUnloadDll

Decrementa di 1 il Load Count del modulo specificato (modulo caricato nello spazio di memoria del processo chiamante) e, nel caso in cui tale valore diventi 0, allora provvede a scaricare il modulo medesimo

API win32 corrispondenti: FreeLibrary

function LdrUnloadDll( ModuleHandle: Cardinal ): Integer; stdcall; external 'ntdll.dll';

  • ModuleHandle: indirizzo di base, nello spazio di memoria del processo chiamante, del modulo da scaricare

L'API Native LdrUnloadDll viene chiamata dall'API Win32 FreeLibrary di cui di seguito vediamo la dichiarazione

function FreeLibrary(hLibModule: Cardinal): Boolean; stdcall;

  • hLibModule: indirizzo di base, nello spazio di memoria del processo chiamante, del modulo da scaricare

Se il modulo richiesto non presente in memoria, la funzione restituisce il valore $C0000135 (126 in win32)

3.4 LdrGetProcedureAddress

Ottiene l'indirizzo (nello spazio di memoria del processo chiamante) di una funzione esportata da una dll caricata nello spazio di memoria del processo chiamante.

API Win32 corrispondenti: GetProcAddress

function LdrGetProcedureAddress( hModule : Cardinal; psName : PANSI_STRING; dwOrdinal : Cardinal; var pProcedure : Pointer ): Integer; stdcall; external 'ntdll.dll';

  • hModule:[input] indirizzo di base, nello spazio di memoria del processo chiamante, della dll in cui si trova la funzione
  • psName:[input] nome della funzione (inuitle se si usa l'ordinal della funzione (vedi parametro dwOrdinal))
  • dwOrdinal:[input] ordinal della funzione (inutile se si usa il nome della funzione (vedi parametro psName))
  • pProcedure:[output] indirizzo della funzione

L'API Native LdrGetProcedureAddress viene chiamata dall'API Win32 GetProcAddress di cui di seguito vediamo la dichiarazione

function GetProcAddress( hModule: Cardinal; lpProcName: PAnsiChar ): Pointer; stdcall;

  • hModule: indirizzo di base, nello spazio di memoria del processo chiamante, della dll in cui si trova la funzione
  • lpProcName: valore a 4 byte che pu avere 2 significati distinti: 1) se il valore compreso tra 0 e $FFFF (ossia la High-Order Word pari a 0) allora il valore rappresenta l'ordinal della funzione; 2) altrimenti rappresenta un puntatore ad una stringa che mi definisce il nome della funzione 

L'output della GetProcAddress appunto l'indirizzo della funzione

4. Funzioni di esempio di utilizzo delle API Native

E' arrivato il momento di crearsi delle funzioni che utilizzano le API appena descritte. Dichiaro una variabile globale di nome status di tipo Integer che ricever di volta in volta l'output delle API Native: se il valore negativo allora c' stato un errore (questo concetto lo si era gi visto nel paragrafo 1. quando si parlato della struttura del codice di errore).

4.1 MyLoadLibrary

function MyLoadLibrary(DLL: PAnsiChar): Boolean; var hModule: Cardinal; AnsString: ANSI_STRING; UncString: UNICODE_STRING; begin Result := False; AnsString.Buffer := PAnsiChar(DLL); AnsString.Length := Length(DLL); AnsString.MaximumLength := AnsString.Length; status := RtlAnsiStringToUnicodeString( @UncString, @AnsString, True); if status < 0 then begin ErrStrNative('RtlAnsiStringToUnicodeString', status); Exit; end; status := LdrGetDllHandle(nil, nil, @UncString, @hModule); if (status < 0) and (status <> Integer($C0000135)) then begin ErrStrNative('LdrGetDllHandle', status); Exit; end; //la LdrGetDllHandle non ha trovato il modulo gi caricato in memoria if status = Integer($C0000135) then begin status := LdrLoadDll(nil, 0, @UncString, @hModule); if status < 0 then begin ErrStrNative('LdrLoadDll', status); Exit; end; end; RtlFreeUnicodeString(@UncString); result := True; end;

4.2 MyFreeLibrary

function MyFreeLibrary(DLL: PAnsiChar): Boolean; var hModule: Cardinal; AnsString: ANSI_STRING; UncString: UNICODE_STRING; begin result := False; AnsString.Buffer := PAnsiChar(DLL); AnsString.Length := Length(DLL); AnsString.MaximumLength := AnsString.Length; status := RtlAnsiStringToUnicodeString( @UncString, @AnsString, True); if status < 0 then begin ErrStrNative('RtlAnsiStringToUnicodeString', status); Exit; end; status := LdrGetDllHandle(nil, nil, @UncString, @hModule); if status < 0 then begin ErrStrNative('LdrGetDllHandle', status); Exit; end; if hModule <> 0 then begin status := LdrUnloadDll(hModule); if status < 0 then begin ErrStrNative('LdrUnloadDll', status); Exit; end; end; RtlFreeUnicodeString(@UncString); result := True; end;

4.3 MyGetProcAddress

function MyGetProcAddress(DLL: PAnsiChar; procName: PAnsiChar; var pFunction: Pointer): Boolean; var hModule: Cardinal; AnsString: ANSI_STRING; UncString: UNICODE_STRING; begin result := False; AnsString.Buffer := PAnsiChar(DLL); AnsString.Length := Length(DLL); AnsString.MaximumLength := AnsString.Length; status := RtlAnsiStringToUnicodeString( @UncString, @AnsString, True); if status < 0 then begin ErrStrNative('RtlAnsiStringToUnicodeString', status); Exit; end; status := LdrGetDllHandle(nil, nil, @UncString, @hModule); if status < 0 then begin ErrStrNative('LdrGetDllHandle', status); Exit; end; AnsString.Buffer := PAnsiChar(procName); AnsString.Length := Length(procName); AnsString.MaximumLength := AnsString.Length; status := LdrGetProcedureAddress(hModule, @AnsString, 0, pFunction); if status < 0 then begin ErrStrNative('LdrGetProcedureAddress', status); Exit; end; RtlFreeUnicodeString(@UncString); Result := True; end;

Uhm.. bene. Di seguito un esempio che utilizza le funzioni appena descritte e anche le classiche api win32

LoadUnloadNative.7z

 

 
 
Your Ad Here