1 /* 2 * PROJECT: ReactOS RTL 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Dynamic function table support routines 5 * COPYRIGHT: Copyright 2022 Timo Kreuzer (timo.kreuzer@reactos.org) 6 */ 7 8 #include <rtl.h> 9 10 #define NDEBUG 11 #include <debug.h> 12 13 #define TAG_RTLDYNFNTBL 'tfDP' 14 15 typedef 16 _Function_class_(GET_RUNTIME_FUNCTION_CALLBACK) 17 PRUNTIME_FUNCTION 18 GET_RUNTIME_FUNCTION_CALLBACK( 19 _In_ DWORD64 ControlPc, 20 _In_opt_ PVOID Context); 21 typedef GET_RUNTIME_FUNCTION_CALLBACK *PGET_RUNTIME_FUNCTION_CALLBACK; 22 23 typedef 24 _Function_class_(OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK) 25 DWORD 26 OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK( 27 _In_ HANDLE Process, 28 _In_ PVOID TableAddress, 29 _Out_ PDWORD Entries, 30 _Out_ PRUNTIME_FUNCTION* Functions); 31 typedef OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK *POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK; 32 33 typedef enum _FUNCTION_TABLE_TYPE 34 { 35 RF_SORTED = 0x0, 36 RF_UNSORTED = 0x1, 37 RF_CALLBACK = 0x2, 38 RF_KERNEL_DYNAMIC = 0x3, 39 } FUNCTION_TABLE_TYPE; 40 41 typedef struct _DYNAMIC_FUNCTION_TABLE 42 { 43 LIST_ENTRY ListEntry; 44 PRUNTIME_FUNCTION FunctionTable; 45 LARGE_INTEGER TimeStamp; 46 ULONG64 MinimumAddress; 47 ULONG64 MaximumAddress; 48 ULONG64 BaseAddress; 49 PGET_RUNTIME_FUNCTION_CALLBACK Callback; 50 PVOID Context; 51 PWCHAR OutOfProcessCallbackDll; 52 FUNCTION_TABLE_TYPE Type; 53 ULONG EntryCount; 54 #if (NTDDI_VERSION <= NTDDI_WIN10) 55 // FIXME: RTL_BALANCED_NODE is defined in ntdef.h, it's impossible to get included here due to precompiled header 56 //RTL_BALANCED_NODE TreeNode; 57 #else 58 //RTL_BALANCED_NODE TreeNodeMin; 59 //RTL_BALANCED_NODE TreeNodeMax; 60 #endif 61 } DYNAMIC_FUNCTION_TABLE, *PDYNAMIC_FUNCTION_TABLE; 62 63 RTL_SRWLOCK RtlpDynamicFunctionTableLock = { 0 }; 64 LIST_ENTRY RtlpDynamicFunctionTableList = { &RtlpDynamicFunctionTableList, &RtlpDynamicFunctionTableList }; 65 66 static __inline 67 VOID 68 AcquireDynamicFunctionTableLockExclusive() 69 { 70 RtlAcquireSRWLockExclusive(&RtlpDynamicFunctionTableLock); 71 } 72 73 static __inline 74 VOID 75 ReleaseDynamicFunctionTableLockExclusive() 76 { 77 RtlReleaseSRWLockExclusive(&RtlpDynamicFunctionTableLock); 78 } 79 80 static __inline 81 VOID 82 AcquireDynamicFunctionTableLockShared() 83 { 84 RtlAcquireSRWLockShared(&RtlpDynamicFunctionTableLock); 85 } 86 87 static __inline 88 VOID 89 ReleaseDynamicFunctionTableLockShared() 90 { 91 RtlReleaseSRWLockShared(&RtlpDynamicFunctionTableLock); 92 } 93 94 /* 95 * https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlgetfunctiontablelisthead 96 */ 97 PLIST_ENTRY 98 NTAPI 99 RtlGetFunctionTableListHead(void) 100 { 101 return &RtlpDynamicFunctionTableList; 102 } 103 104 static 105 VOID 106 RtlpInsertDynamicFunctionTable(PDYNAMIC_FUNCTION_TABLE DynamicTable) 107 { 108 //LARGE_INTEGER TimeStamp; 109 110 AcquireDynamicFunctionTableLockExclusive(); 111 112 /* Insert it into the list */ 113 InsertTailList(&RtlpDynamicFunctionTableList, &DynamicTable->ListEntry); 114 115 // TODO: insert into RB-trees 116 117 ReleaseDynamicFunctionTableLockExclusive(); 118 } 119 120 BOOLEAN 121 NTAPI 122 RtlAddFunctionTable( 123 _In_ PRUNTIME_FUNCTION FunctionTable, 124 _In_ DWORD EntryCount, 125 _In_ DWORD64 BaseAddress) 126 { 127 PDYNAMIC_FUNCTION_TABLE dynamicTable; 128 ULONG i; 129 130 /* Allocate a dynamic function table */ 131 dynamicTable = RtlpAllocateMemory(sizeof(*dynamicTable), TAG_RTLDYNFNTBL); 132 if (dynamicTable == NULL) 133 { 134 DPRINT1("Failed to allocate dynamic function table\n"); 135 return FALSE; 136 } 137 138 /* Initialize fields */ 139 dynamicTable->FunctionTable = FunctionTable; 140 dynamicTable->EntryCount = EntryCount; 141 dynamicTable->BaseAddress = BaseAddress; 142 dynamicTable->Callback = NULL; 143 dynamicTable->Context = NULL; 144 dynamicTable->Type = RF_UNSORTED; 145 146 /* Loop all entries to find the margins */ 147 dynamicTable->MinimumAddress = ULONG64_MAX; 148 dynamicTable->MaximumAddress = 0; 149 for (i = 0; i < EntryCount; i++) 150 { 151 dynamicTable->MinimumAddress = min(dynamicTable->MinimumAddress, 152 FunctionTable[i].BeginAddress); 153 dynamicTable->MaximumAddress = max(dynamicTable->MaximumAddress, 154 FunctionTable[i].EndAddress); 155 } 156 157 /* Insert the table into the list */ 158 RtlpInsertDynamicFunctionTable(dynamicTable); 159 160 return TRUE; 161 } 162 163 BOOLEAN 164 NTAPI 165 RtlInstallFunctionTableCallback( 166 _In_ DWORD64 TableIdentifier, 167 _In_ DWORD64 BaseAddress, 168 _In_ DWORD Length, 169 _In_ PGET_RUNTIME_FUNCTION_CALLBACK Callback, 170 _In_ PVOID Context, 171 _In_opt_z_ PCWSTR OutOfProcessCallbackDll) 172 { 173 PDYNAMIC_FUNCTION_TABLE dynamicTable; 174 SIZE_T stringLength, allocationSize; 175 176 /* Make sure the identifier is valid */ 177 if ((TableIdentifier & 3) != 3) 178 { 179 return FALSE; 180 } 181 182 /* Check if we have a DLL name */ 183 if (OutOfProcessCallbackDll != NULL) 184 { 185 stringLength = wcslen(OutOfProcessCallbackDll) + 1; 186 } 187 else 188 { 189 stringLength = 0; 190 } 191 192 /* Calculate required size */ 193 allocationSize = sizeof(DYNAMIC_FUNCTION_TABLE) + stringLength * sizeof(WCHAR); 194 195 /* Allocate a dynamic function table */ 196 dynamicTable = RtlpAllocateMemory(allocationSize, TAG_RTLDYNFNTBL); 197 if (dynamicTable == NULL) 198 { 199 DPRINT1("Failed to allocate dynamic function table\n"); 200 return FALSE; 201 } 202 203 /* Initialize fields */ 204 dynamicTable->FunctionTable = (PRUNTIME_FUNCTION)TableIdentifier; 205 dynamicTable->EntryCount = 0; 206 dynamicTable->BaseAddress = BaseAddress; 207 dynamicTable->Callback = Callback; 208 dynamicTable->Context = Context; 209 dynamicTable->Type = RF_CALLBACK; 210 dynamicTable->MinimumAddress = BaseAddress; 211 dynamicTable->MaximumAddress = BaseAddress + Length; 212 213 /* If we have a DLL name, copy that, too */ 214 if (OutOfProcessCallbackDll != NULL) 215 { 216 dynamicTable->OutOfProcessCallbackDll = (PWCHAR)(dynamicTable + 1); 217 RtlCopyMemory(dynamicTable->OutOfProcessCallbackDll, 218 OutOfProcessCallbackDll, 219 stringLength * sizeof(WCHAR)); 220 } 221 else 222 { 223 dynamicTable->OutOfProcessCallbackDll = NULL; 224 } 225 226 /* Insert the table into the list */ 227 RtlpInsertDynamicFunctionTable(dynamicTable); 228 229 return TRUE; 230 } 231 232 BOOLEAN 233 NTAPI 234 RtlDeleteFunctionTable( 235 _In_ PRUNTIME_FUNCTION FunctionTable) 236 { 237 PLIST_ENTRY listLink; 238 PDYNAMIC_FUNCTION_TABLE dynamicTable; 239 BOOL removed = FALSE; 240 241 AcquireDynamicFunctionTableLockExclusive(); 242 243 /* Loop all tables to find the one to delete */ 244 for (listLink = RtlpDynamicFunctionTableList.Flink; 245 listLink != &RtlpDynamicFunctionTableList; 246 listLink = listLink->Flink) 247 { 248 dynamicTable = CONTAINING_RECORD(listLink, DYNAMIC_FUNCTION_TABLE, ListEntry); 249 250 if (dynamicTable->FunctionTable == FunctionTable) 251 { 252 RemoveEntryList(&dynamicTable->ListEntry); 253 removed = TRUE; 254 break; 255 } 256 } 257 258 ReleaseDynamicFunctionTableLockExclusive(); 259 260 /* If we were successful, free the memory */ 261 if (removed) 262 { 263 RtlpFreeMemory(dynamicTable, TAG_RTLDYNFNTBL); 264 } 265 266 return removed; 267 } 268 269 PRUNTIME_FUNCTION 270 NTAPI 271 RtlpLookupDynamicFunctionEntry( 272 _In_ DWORD64 ControlPc, 273 _Out_ PDWORD64 ImageBase, 274 _In_ PUNWIND_HISTORY_TABLE HistoryTable) 275 { 276 PLIST_ENTRY listLink; 277 PDYNAMIC_FUNCTION_TABLE dynamicTable; 278 PRUNTIME_FUNCTION functionTable, foundEntry = NULL; 279 PGET_RUNTIME_FUNCTION_CALLBACK callback; 280 ULONG i; 281 282 AcquireDynamicFunctionTableLockShared(); 283 284 /* Loop all tables to find the one matching ControlPc */ 285 for (listLink = RtlpDynamicFunctionTableList.Flink; 286 listLink != &RtlpDynamicFunctionTableList; 287 listLink = listLink->Flink) 288 { 289 dynamicTable = CONTAINING_RECORD(listLink, DYNAMIC_FUNCTION_TABLE, ListEntry); 290 291 if ((ControlPc >= dynamicTable->MinimumAddress) && 292 (ControlPc < dynamicTable->MaximumAddress)) 293 { 294 /* Check if there is a callback */ 295 callback = dynamicTable->Callback; 296 if (callback != NULL) 297 { 298 PVOID context = dynamicTable->Context; 299 300 *ImageBase = dynamicTable->BaseAddress; 301 ReleaseDynamicFunctionTableLockShared(); 302 return callback(ControlPc, context); 303 } 304 305 /* Loop all entries in the function table */ 306 functionTable = dynamicTable->FunctionTable; 307 for (i = 0; i < dynamicTable->EntryCount; i++) 308 { 309 /* Check if this entry contains the address */ 310 if ((ControlPc >= functionTable[i].BeginAddress) && 311 (ControlPc < functionTable[i].EndAddress)) 312 { 313 foundEntry = &functionTable[i]; 314 *ImageBase = dynamicTable->BaseAddress; 315 goto Exit; 316 } 317 } 318 } 319 } 320 321 Exit: 322 323 ReleaseDynamicFunctionTableLockShared(); 324 325 return foundEntry; 326 } 327