LPC (Local Procedure Call) je část jádra Windows NT, určená k rychlé komunikaci mezi thready nebo mezi procesy ve Windows NT. Je možné ji použít i pro komunikaci mezi komponentami v uživatelském režimu (aplikace a systémové služby) a režimu jádra (ovladače). Tento článek obsahuje popis použití LPC, včetně příkladu komunikace mezi dvěma vlákny.
Poznámka: Tento článek je určen pokročilým programátorům. U čtenáře předpokládá znalost procesů, vláken, meziprocesové komunikace. Výhodou (ne však podmínkou) při studování článku je aspoň částečná znalost nativních API funkcí Windows řady NT a zkušenosti v programování s použitím Windows DDK.
LPC je součástí pouze Windows řady NT (Windows NT 3.51, NT 4.0, Windows 2000, XP, 2003 Server a Longhorn). Navíc jde o (zatím) nedokumentovanou část Windows, která je ale kompatibilní na všech verzích Windows řady NT. Je obsažena pouze ve skupině tzv. nativních NT API, které jsou exportovány knihovnou ntdll.dll (Resp. ntoskrnl.exe v režimu jádra). K sestavení aplikace používající ntdll.dll je nutné přilinkovat knihovnu ntdll.lib/ntoskrnl.lib, která je součástí Windows DDK.
Komunikace pomocí LPC spočívá v posílání bloků dat, tzv. zpráv LPC (LPC messages) mezi klientem a serverem. Klient a server mohou být různá vlákna, různé procesy a mohou se dokonce nacházet v odlišném režimu jádra (klient může být ovladač, běžící v režimu jádra, server může být uživatelská aplikace).
Komunikace probíhá pomocí tzv. portu LPC, vytvořeného serverem. Při vytvoření portu server specifikuje jméno portu a přístupová práva pomocí přístupového deskriptoru (security descriptor). Po úspěšném vytvoření portu server čeká na připojení klienta. Jakmile klient požádá o spojení na daný port, server může zkontrolovat kdo se chce připojit a případně spojení odmítnout. Pokud je spojení přijato, může klient zaslat požadavek a případně čekat na odpověď serveru.
Při každé komunikaci mezi klientem a serverem je poslána tzv. hlavička zprávy LPC (LPC message header), reprezentovaná strukturou PORT_MESSAGE.
Data předaná mezi klientem a serverem mohou být přenesena dvěma způsoby
Komunikaci LPC znázorňuje následující schéma:
Klient |
Server |
|
---|---|---|
Server vytvoří LPC port pomocí funkce NtCreatePort. Při vytvoření portu je zadáno jméno, pomocí kterého se klient na port připojí. Volitelně je možné zadat i SECURITY_DESCRIPTOR, a tím specifikovat práva k vytvořenému portu. |
||
Server čeká na požadavek na spojení (funkce NtListenPort). |
||
Klient se pokusí připojit k portu pomocí funkce NtConnectPort. Při volání funkce klient specifikuje jméno portu, na který se chce připojit. |
||
Server přijme hlavičku zprávy LPC (struktura MESSAGE_HEADER). Tato hlavička obsahuje tzv. CLIENT_ID, což je struktura obsahující ID procesu a vlákna, které se chce připojit k portu. |
||
Server se rozhodne zda požadavek na spojení přijme nebo odmítne (obojí pomocí parametru funkce NtAcceptConnectPort). |
||
Server dokončí operaci spojování pomocí funkce NtCompleteConnectPort. Po zavolání této funkce bude klient pokračovat zasláním požadavku (pokud server souhlasil se spojením v předchozím kroku). |
||
Funkce NtConnectPort vrátí výsledek pokusu o spojení. Návratová hodnota může být STATUS_SUCCESS (spojení bylo navázáno), STATUS_ACCESS_DENIED (klient nemá přístup k portu), STATUS_OBJECT_NAME_NOT_FOUND (neexistuje port požadovaného jména), STATUS_PORT_CONNECTION_REFUSED (server odmítl spojení), nebo jiná chyba. Pokud bylo spojení navázáno, klient obdrží handle portu. |
||
Server čeká na data od klienta (funkce NtReplyWaitReceivePort). |
||
Klient pošle data do serveru pomocí funkce NtRequestPort (klient nečeká na odpověď) nebo funkce NtRequestWaitReplyPort (klient čeká na odpověď). |
||
Server zpracuje data, která přijal od klienta. Pokud klient čeká na odpověď, server zavolá funkci NtReplyPort. Tato funkce zajistí zaslání dat klientovi. |
||
Klient ukončí proces komunikace uzavřením handlu, které ziskal při navázání spojení (NtClose). Komunikace je dokončena |
Serverový proces komunikace pomocí LPC je ukončen, server se vrací do stavu čekání na další připojení (NtListenPort). |
Následující kapitola popisuje některé funkce a struktury důležité pro systém LPC. Seznam funkcí a struktur není úplný, kompletní popis hledejte v doprovodném materiálu ke stažení.
typedef struct _PORT_MESSAGE { union { struct { USHORT DataLength; // Length of data following the header (bytes) USHORT TotalLength; // Length of data + sizeof(PORT_MESSAGE) } s1; ULONG Length; } u1; union { struct { USHORT Type; USHORT DataInfoOffset; } s2; ULONG ZeroInit; } u2; union { CLIENT_ID ClientId; double DoNotUseThisField; // Force quadword alignment }; ULONG MessageId; // Identifier of the particular message instance union { ULONG_PTR ClientViewSize; // Size of section created by the sender (in bytes) ULONG CallbackId; // }; } PORT_MESSAGE, *PPORT_MESSAGE;
Struktura PORT_MESSAGE se používá k popisu dat zasílaných pomocí LPC. Každé poslání dat obsahuje vždy informaci o datech, která jsou zaslána. Spolu s touto strukturou je možné poslat i data, pokud jejich velikost nepřesáhne 0x130 bytů.
typedef struct _PORT_VIEW { ULONG Length; // Velikost této struktury HANDLE SectionHandle; // Handle na sekci. Musí mít přístup // SECTION_MAP_WRITE a SECTION_MAP_READ ULONG SectionOffset; // Offset v sekci, který bude namapován // do paměťového prostoru příjemce. Musí být násobkem // alokační granularity systému. ULONG ViewSize; // Velikost mapovaných dat v bytech PVOID ViewBase; // Bázová adresa namapovaných dat v paměťovém // prostoru odesílatele PVOID ViewRemoteBase; // Bázová adresa namapovaných dat v adresovém prostoru // cílového procesu (příjemce zprávy LPC). } PORT_VIEW, *PPORT_VIEW;
Struktura PORT_VIEW se používá k popisu paměťové sekce (vytvořené funkcí NtCreateSection nebo CreateFileMapping), která bude zaslána příjemci. Sekce musí být vytvořena ve stránkovacím souboru (handle souboru při vytvoření sekce musí být INVALID_HANDLE_VALUE). Struktura musí být naplněna procesem, který sekci vytvořil. Velikost sekce musí být násobkem alokační granularity systému.
typedef struct _REMOTE_PORT_VIEW { ULONG Length; // Velikost struktury ULONG ViewSize; // Velikost bloku dat namapovaného // do paměťového prostoru příjemce zprávy LPC PVOID ViewBase; // Bázová adresa bloku dat } REMOTE_PORT_VIEW, *PREMOTE_PORT_VIEW;
Pokud odesílatel zprávy LPC pošle data ve formě paměťově mapované sekce, systém LPC provede přemapování sekce do paměťového prostoru příjemce a předá data ve formě struktury REMOTE_PORT_VIEW.
Funkce NtCreatePort je použita serverem LPC k vytvoření portu.
NTSTATUS NTAPI NtCreatePort( OUT PHANDLE PortHandle, IN POBJECT_ATTRIBUTES ObjectAttributes, IN ULONG MaxConnectionInfoLength, IN ULONG MaxMessageLength, IN ULONG MaxPoolUsage );Parametry
Výsledek operace jako hodnota typu NTSTATUS.
PoznámkaNtCreatePort kontroluje zda (MaxConnectionInfoLength <= 0x104) a (MaxMessageLength <= 0x148).
Považuji za zbytečné přepisovat popis všech funkcí k LPC. Podrobná dokumentace je k dispozici v doprovodném souboru ke stažení. Tento balíček obsahuje
Pokud spustíte 32-bitovou verze příkladu pod 64-bitovými Windows, zjistíte že na několika místech příklad nefunguje správně. Jak se ukázalo, vrstva mezi 32-bitovou Ntdll.dll a 64-bitovou Ntdll.dll nepřekládá strukturu PORT_MESSAGE na její 64-bitový tvar. Funkce LPC v kernelu (které jsou pouze 64-bitové) pak nemohou rozpoznat formát strutury PORT_MESSAGE a obvykle vrací STATUS_INVALID_PARAMETER (0xC000000D). Pro 64-bitová Windows vždy použijte 64-bitovou verzi příkladu.
Copyright (c) Ladislav Zezula 2009