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
AcquireDynamicFunctionTableLockExclusive()68 AcquireDynamicFunctionTableLockExclusive()
69 {
70 RtlAcquireSRWLockExclusive(&RtlpDynamicFunctionTableLock);
71 }
72
73 static __inline
74 VOID
ReleaseDynamicFunctionTableLockExclusive()75 ReleaseDynamicFunctionTableLockExclusive()
76 {
77 RtlReleaseSRWLockExclusive(&RtlpDynamicFunctionTableLock);
78 }
79
80 static __inline
81 VOID
AcquireDynamicFunctionTableLockShared()82 AcquireDynamicFunctionTableLockShared()
83 {
84 RtlAcquireSRWLockShared(&RtlpDynamicFunctionTableLock);
85 }
86
87 static __inline
88 VOID
ReleaseDynamicFunctionTableLockShared()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
RtlGetFunctionTableListHead(void)99 RtlGetFunctionTableListHead(void)
100 {
101 return &RtlpDynamicFunctionTableList;
102 }
103
104 static
105 VOID
RtlpInsertDynamicFunctionTable(PDYNAMIC_FUNCTION_TABLE DynamicTable)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
RtlAddFunctionTable(_In_ PRUNTIME_FUNCTION FunctionTable,_In_ DWORD EntryCount,_In_ DWORD64 BaseAddress)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
RtlInstallFunctionTableCallback(_In_ DWORD64 TableIdentifier,_In_ DWORD64 BaseAddress,_In_ DWORD Length,_In_ PGET_RUNTIME_FUNCTION_CALLBACK Callback,_In_ PVOID Context,_In_opt_z_ PCWSTR OutOfProcessCallbackDll)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
RtlDeleteFunctionTable(_In_ PRUNTIME_FUNCTION FunctionTable)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
RtlpLookupDynamicFunctionEntry(_In_ DWORD64 ControlPc,_Out_ PDWORD64 ImageBase,_In_ PUNWIND_HISTORY_TABLE HistoryTable)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