From 8521f6d7b5849d3b07b6032f616d9fb48b8bc7ef Mon Sep 17 00:00:00 2001 From: Timo Kreuzer Date: Fri, 10 Jun 2022 18:44:51 +0200 Subject: [PATCH] [RTL] Implement dynamic function tables for x64 --- dll/ntdll/CMakeLists.txt | 2 +- dll/ntdll/def/ntdll.spec | 2 +- ntoskrnl/rtl/libsupp.c | 17 ++ sdk/lib/rtl/CMakeLists.txt | 1 + sdk/lib/rtl/amd64/dynfntbl.c | 326 +++++++++++++++++++++++++++++++++++ sdk/lib/rtl/amd64/unwind.c | 45 +---- 6 files changed, 355 insertions(+), 38 deletions(-) create mode 100644 sdk/lib/rtl/amd64/dynfntbl.c diff --git a/dll/ntdll/CMakeLists.txt b/dll/ntdll/CMakeLists.txt index be810e324f8..fbcd5272eb1 100644 --- a/dll/ntdll/CMakeLists.txt +++ b/dll/ntdll/CMakeLists.txt @@ -60,7 +60,7 @@ set_module_type(ntdll win32dll ENTRYPOINT 0) set_subsystem(ntdll console) ################# END HACK ################# -target_link_libraries(ntdll rtl ntdllsys libcntpr uuid ${PSEH_LIB}) +target_link_libraries(ntdll rtl rtl_vista ntdllsys libcntpr uuid ${PSEH_LIB}) if (STACK_PROTECTOR) target_sources(ntdll PRIVATE $) diff --git a/dll/ntdll/def/ntdll.spec b/dll/ntdll/def/ntdll.spec index 57ba0653d7c..80d27637413 100644 --- a/dll/ntdll/def/ntdll.spec +++ b/dll/ntdll/def/ntdll.spec @@ -881,7 +881,7 @@ @ stdcall RtlGetFrame() @ stdcall RtlGetFullPathName_U(wstr long ptr ptr) @ stdcall RtlGetFullPathName_UstrEx(ptr ptr ptr ptr ptr ptr ptr ptr) -@ stub -version=0x600+ -arch=x86_64 RtlGetFunctionTableListHead +@ stdcall -arch=x86_64 RtlGetFunctionTableListHead() @ stdcall RtlGetGroupSecurityDescriptor(ptr ptr ptr) @ stub -version=0x600+ RtlGetIntegerAtom @ stdcall RtlGetLastNtStatus() diff --git a/ntoskrnl/rtl/libsupp.c b/ntoskrnl/rtl/libsupp.c index e8d7b68df88..173944a0573 100644 --- a/ntoskrnl/rtl/libsupp.c +++ b/ntoskrnl/rtl/libsupp.c @@ -823,4 +823,21 @@ RtlCallVectoredContinueHandlers(_In_ PEXCEPTION_RECORD ExceptionRecord, return; } +#ifdef _M_AMD64 + +typedef PVOID PRUNTIME_FUNCTION, PUNWIND_HISTORY_TABLE; + +PRUNTIME_FUNCTION +NTAPI +RtlpLookupDynamicFunctionEntry( + _In_ DWORD64 ControlPc, + _Out_ PDWORD64 ImageBase, + _In_ PUNWIND_HISTORY_TABLE HistoryTable) +{ + /* No support for dynamic function tables in the kernel */ + return NULL; +} + +#endif + /* EOF */ diff --git a/sdk/lib/rtl/CMakeLists.txt b/sdk/lib/rtl/CMakeLists.txt index a8d2fdbe348..8550fe9fb8e 100644 --- a/sdk/lib/rtl/CMakeLists.txt +++ b/sdk/lib/rtl/CMakeLists.txt @@ -94,6 +94,7 @@ elseif(ARCH STREQUAL "amd64") list(APPEND SOURCE bitmap64.c byteswap.c + amd64/dynfntbl.c amd64/except.c amd64/unwind.c amd64/stubs.c diff --git a/sdk/lib/rtl/amd64/dynfntbl.c b/sdk/lib/rtl/amd64/dynfntbl.c new file mode 100644 index 00000000000..67c76e7f640 --- /dev/null +++ b/sdk/lib/rtl/amd64/dynfntbl.c @@ -0,0 +1,326 @@ +/* + * PROJECT: ReactOS RTL + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Dynamic function table support routines + * COPYRIGHT: Copyright 2022 Timo Kreuzer (timo.kreuzer@reactos.org) + */ + +#include + +#define NDEBUG +#include + +#define TAG_RTLDYNFNTBL 'tfDP' + +typedef +_Function_class_(GET_RUNTIME_FUNCTION_CALLBACK) +PRUNTIME_FUNCTION +GET_RUNTIME_FUNCTION_CALLBACK( + _In_ DWORD64 ControlPc, + _In_opt_ PVOID Context); +typedef GET_RUNTIME_FUNCTION_CALLBACK *PGET_RUNTIME_FUNCTION_CALLBACK; + +typedef +_Function_class_(OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK) +DWORD +OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK( + _In_ HANDLE Process, + _In_ PVOID TableAddress, + _Out_ PDWORD Entries, + _Out_ PRUNTIME_FUNCTION* Functions); +typedef OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK *POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK; + +typedef enum _FUNCTION_TABLE_TYPE +{ + RF_SORTED = 0x0, + RF_UNSORTED = 0x1, + RF_CALLBACK = 0x2, + RF_KERNEL_DYNAMIC = 0x3, +} FUNCTION_TABLE_TYPE; + +typedef struct _DYNAMIC_FUNCTION_TABLE +{ + LIST_ENTRY ListEntry; + PRUNTIME_FUNCTION FunctionTable; + LARGE_INTEGER TimeStamp; + ULONG64 MinimumAddress; + ULONG64 MaximumAddress; + ULONG64 BaseAddress; + PGET_RUNTIME_FUNCTION_CALLBACK Callback; + PVOID Context; + PWCHAR OutOfProcessCallbackDll; + FUNCTION_TABLE_TYPE Type; + ULONG EntryCount; +#if (NTDDI_VERSION <= NTDDI_WIN10) + // FIXME: RTL_BALANCED_NODE is defined in ntdef.h, it's impossible to get included here due to precompiled header + //RTL_BALANCED_NODE TreeNode; +#else + //RTL_BALANCED_NODE TreeNodeMin; + //RTL_BALANCED_NODE TreeNodeMax; +#endif +} DYNAMIC_FUNCTION_TABLE, *PDYNAMIC_FUNCTION_TABLE; + +RTL_SRWLOCK RtlpDynamicFunctionTableLock = { 0 }; +LIST_ENTRY RtlpDynamicFunctionTableList = { &RtlpDynamicFunctionTableList, &RtlpDynamicFunctionTableList }; + +static __inline +VOID +AcquireDynamicFunctionTableLockExclusive() +{ + RtlAcquireSRWLockExclusive(&RtlpDynamicFunctionTableLock); +} + +static __inline +VOID +ReleaseDynamicFunctionTableLockExclusive() +{ + RtlReleaseSRWLockExclusive(&RtlpDynamicFunctionTableLock); +} + +static __inline +VOID +AcquireDynamicFunctionTableLockShared() +{ + RtlAcquireSRWLockShared(&RtlpDynamicFunctionTableLock); +} + +static __inline +VOID +ReleaseDynamicFunctionTableLockShared() +{ + RtlReleaseSRWLockShared(&RtlpDynamicFunctionTableLock); +} + +/* + * https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlgetfunctiontablelisthead + */ +PLIST_ENTRY +NTAPI +RtlGetFunctionTableListHead(void) +{ + return &RtlpDynamicFunctionTableList; +} + +static +VOID +RtlpInsertDynamicFunctionTable(PDYNAMIC_FUNCTION_TABLE DynamicTable) +{ + //LARGE_INTEGER TimeStamp; + + AcquireDynamicFunctionTableLockExclusive(); + + /* Insert it into the list */ + InsertTailList(&RtlpDynamicFunctionTableList, &DynamicTable->ListEntry); + + // TODO: insert into RB-trees + + ReleaseDynamicFunctionTableLockExclusive(); +} + +BOOLEAN +NTAPI +RtlAddFunctionTable( + _In_ PRUNTIME_FUNCTION FunctionTable, + _In_ DWORD EntryCount, + _In_ DWORD64 BaseAddress) +{ + PDYNAMIC_FUNCTION_TABLE dynamicTable; + ULONG i; + + /* Allocate a dynamic function table */ + dynamicTable = RtlpAllocateMemory(sizeof(*dynamicTable), TAG_RTLDYNFNTBL); + if (dynamicTable == NULL) + { + DPRINT1("Failed to allocate dynamic function table\n"); + return FALSE; + } + + /* Initialize fields */ + dynamicTable->FunctionTable = FunctionTable; + dynamicTable->EntryCount = EntryCount; + dynamicTable->BaseAddress = BaseAddress; + dynamicTable->Callback = NULL; + dynamicTable->Context = NULL; + dynamicTable->Type = RF_UNSORTED; + + /* Loop all entries to find the margins */ + dynamicTable->MinimumAddress = ULONG64_MAX; + dynamicTable->MaximumAddress = 0; + for (i = 0; i < EntryCount; i++) + { + dynamicTable->MinimumAddress = min(dynamicTable->MinimumAddress, + FunctionTable[i].BeginAddress); + dynamicTable->MaximumAddress = max(dynamicTable->MaximumAddress, + FunctionTable[i].EndAddress); + } + + /* Insert the table into the list */ + RtlpInsertDynamicFunctionTable(dynamicTable); + + return TRUE; +} + +BOOLEAN +NTAPI +RtlInstallFunctionTableCallback( + _In_ DWORD64 TableIdentifier, + _In_ DWORD64 BaseAddress, + _In_ DWORD Length, + _In_ PGET_RUNTIME_FUNCTION_CALLBACK Callback, + _In_ PVOID Context, + _In_opt_z_ PCWSTR OutOfProcessCallbackDll) +{ + PDYNAMIC_FUNCTION_TABLE dynamicTable; + SIZE_T stringLength, allocationSize; + + /* Make sure the identifier is valid */ + if ((TableIdentifier & 3) != 3) + { + return FALSE; + } + + /* Check if we have a DLL name */ + if (OutOfProcessCallbackDll != NULL) + { + stringLength = wcslen(OutOfProcessCallbackDll) + 1; + } + else + { + stringLength = 0; + } + + /* Calculate required size */ + allocationSize = sizeof(DYNAMIC_FUNCTION_TABLE) + stringLength * sizeof(WCHAR); + + /* Allocate a dynamic function table */ + dynamicTable = RtlpAllocateMemory(allocationSize, TAG_RTLDYNFNTBL); + if (dynamicTable == NULL) + { + DPRINT1("Failed to allocate dynamic function table\n"); + return FALSE; + } + + /* Initialize fields */ + dynamicTable->FunctionTable = (PRUNTIME_FUNCTION)TableIdentifier; + dynamicTable->EntryCount = 0; + dynamicTable->BaseAddress = BaseAddress; + dynamicTable->Callback = Callback; + dynamicTable->Context = Context; + dynamicTable->Type = RF_CALLBACK; + dynamicTable->MinimumAddress = BaseAddress; + dynamicTable->MaximumAddress = BaseAddress + Length; + + /* If we have a DLL name, copy that, too */ + if (OutOfProcessCallbackDll != NULL) + { + dynamicTable->OutOfProcessCallbackDll = (PWCHAR)(dynamicTable + 1); + RtlCopyMemory(dynamicTable->OutOfProcessCallbackDll, + OutOfProcessCallbackDll, + stringLength * sizeof(WCHAR)); + } + else + { + dynamicTable->OutOfProcessCallbackDll = NULL; + } + + /* Insert the table into the list */ + RtlpInsertDynamicFunctionTable(dynamicTable); + + return TRUE; +} + +BOOLEAN +NTAPI +RtlDeleteFunctionTable( + _In_ PRUNTIME_FUNCTION FunctionTable) +{ + PLIST_ENTRY listLink; + PDYNAMIC_FUNCTION_TABLE dynamicTable; + BOOL removed = FALSE; + + AcquireDynamicFunctionTableLockExclusive(); + + /* Loop all tables to find the one to delete */ + for (listLink = RtlpDynamicFunctionTableList.Flink; + listLink != &RtlpDynamicFunctionTableList; + listLink = listLink->Flink) + { + dynamicTable = CONTAINING_RECORD(listLink, DYNAMIC_FUNCTION_TABLE, ListEntry); + + if (dynamicTable->FunctionTable == FunctionTable) + { + RemoveEntryList(&dynamicTable->ListEntry); + removed = TRUE; + break; + } + } + + ReleaseDynamicFunctionTableLockExclusive(); + + /* If we were successful, free the memory */ + if (removed) + { + RtlpFreeMemory(dynamicTable, TAG_RTLDYNFNTBL); + } + + return removed; +} + +PRUNTIME_FUNCTION +NTAPI +RtlpLookupDynamicFunctionEntry( + _In_ DWORD64 ControlPc, + _Out_ PDWORD64 ImageBase, + _In_ PUNWIND_HISTORY_TABLE HistoryTable) +{ + PLIST_ENTRY listLink; + PDYNAMIC_FUNCTION_TABLE dynamicTable; + PRUNTIME_FUNCTION functionTable, foundEntry = NULL; + PGET_RUNTIME_FUNCTION_CALLBACK callback; + ULONG i; + + AcquireDynamicFunctionTableLockShared(); + + /* Loop all tables to find the one matching ControlPc */ + for (listLink = RtlpDynamicFunctionTableList.Flink; + listLink != &RtlpDynamicFunctionTableList; + listLink = listLink->Flink) + { + dynamicTable = CONTAINING_RECORD(listLink, DYNAMIC_FUNCTION_TABLE, ListEntry); + + if ((ControlPc >= dynamicTable->MinimumAddress) && + (ControlPc < dynamicTable->MaximumAddress)) + { + /* Check if there is a callback */ + callback = dynamicTable->Callback; + if (callback != NULL) + { + PVOID context = dynamicTable->Context; + + *ImageBase = dynamicTable->BaseAddress; + ReleaseDynamicFunctionTableLockShared(); + return callback(ControlPc, context); + } + + /* Loop all entries in the function table */ + functionTable = dynamicTable->FunctionTable; + for (i = 0; i < dynamicTable->EntryCount; i++) + { + /* Check if this entry contains the address */ + if ((ControlPc >= functionTable[i].BeginAddress) && + (ControlPc < functionTable[i].EndAddress)) + { + foundEntry = &functionTable[i]; + *ImageBase = dynamicTable->BaseAddress; + goto Exit; + } + } + } + } + +Exit: + + ReleaseDynamicFunctionTableLockShared(); + + return foundEntry; +} diff --git a/sdk/lib/rtl/amd64/unwind.c b/sdk/lib/rtl/amd64/unwind.c index 4fe42801281..49e3740bec4 100644 --- a/sdk/lib/rtl/amd64/unwind.c +++ b/sdk/lib/rtl/amd64/unwind.c @@ -107,6 +107,13 @@ RtlLookupFunctionTable( return Table; } +PRUNTIME_FUNCTION +NTAPI +RtlpLookupDynamicFunctionEntry( + _In_ DWORD64 ControlPc, + _Out_ PDWORD64 ImageBase, + _In_ PUNWIND_HISTORY_TABLE HistoryTable); + /*! RtlLookupFunctionEntry * \brief Locates the RUNTIME_FUNCTION entry corresponding to a code address. * \ref http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx @@ -126,10 +133,10 @@ RtlLookupFunctionEntry( /* Find the corresponding table */ FunctionTable = RtlLookupFunctionTable(ControlPc, ImageBase, &TableLength); - /* Fail, if no table is found */ + /* If no table is found, try dynamic function tables */ if (!FunctionTable) { - return NULL; + return RtlpLookupDynamicFunctionEntry(ControlPc, ImageBase, HistoryTable); } /* Use relative virtual address */ @@ -164,40 +171,6 @@ RtlLookupFunctionEntry( return NULL; } -BOOLEAN -NTAPI -RtlAddFunctionTable( - IN PRUNTIME_FUNCTION FunctionTable, - IN DWORD EntryCount, - IN DWORD64 BaseAddress) -{ - UNIMPLEMENTED; - return FALSE; -} - -BOOLEAN -NTAPI -RtlDeleteFunctionTable( - IN PRUNTIME_FUNCTION FunctionTable) -{ - UNIMPLEMENTED; - return FALSE; -} - -BOOLEAN -NTAPI -RtlInstallFunctionTableCallback( - IN DWORD64 TableIdentifier, - IN DWORD64 BaseAddress, - IN DWORD Length, - IN PGET_RUNTIME_FUNCTION_CALLBACK Callback, - IN PVOID Context, - IN PCWSTR OutOfProcessCallbackDll) -{ - UNIMPLEMENTED; - return FALSE; -} - static __inline ULONG