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