xref: /reactos/sdk/lib/rtl/amd64/dynfntbl.c (revision 8521f6d7)
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