xref: /reactos/ntoskrnl/kdbg/kdb_symbols.c (revision f986527d)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS kernel
4  * FILE:            ntoskrnl/kdbg/kdb_symbols.c
5  * PURPOSE:         Getting symbol information...
6  *
7  * PROGRAMMERS:     David Welch (welch@cwcom.net)
8  *                  Colin Finck (colin@reactos.org)
9  */
10 
11 /* INCLUDES *****************************************************************/
12 
13 #include <ntoskrnl.h>
14 
15 #define NDEBUG
16 #include <debug.h>
17 
18 /* GLOBALS ******************************************************************/
19 
20 typedef struct _IMAGE_SYMBOL_INFO_CACHE
21 {
22     LIST_ENTRY ListEntry;
23     ULONG RefCount;
24     UNICODE_STRING FileName;
25     PROSSYM_INFO RosSymInfo;
26 }
27 IMAGE_SYMBOL_INFO_CACHE, *PIMAGE_SYMBOL_INFO_CACHE;
28 
29 static BOOLEAN LoadSymbols;
30 static LIST_ENTRY SymbolFileListHead;
31 static KSPIN_LOCK SymbolFileListLock;
32 BOOLEAN KdbpSymbolsInitialized = FALSE;
33 
34 /* FUNCTIONS ****************************************************************/
35 
36 static BOOLEAN
37 KdbpSymSearchModuleList(
38     IN PLIST_ENTRY current_entry,
39     IN PLIST_ENTRY end_entry,
40     IN PLONG Count,
41     IN PVOID Address,
42     IN LPCWSTR Name,
43     IN INT Index,
44     OUT PLDR_DATA_TABLE_ENTRY* pLdrEntry)
45 {
46     while (current_entry && current_entry != end_entry)
47     {
48         *pLdrEntry = CONTAINING_RECORD(current_entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
49 
50         if ((Address && Address >= (PVOID)(*pLdrEntry)->DllBase && Address < (PVOID)((ULONG_PTR)(*pLdrEntry)->DllBase + (*pLdrEntry)->SizeOfImage)) ||
51             (Name && !_wcsnicmp((*pLdrEntry)->BaseDllName.Buffer, Name, (*pLdrEntry)->BaseDllName.Length / sizeof(WCHAR))) ||
52             (Index >= 0 && (*Count)++ == Index))
53         {
54             return TRUE;
55         }
56 
57         current_entry = current_entry->Flink;
58     }
59 
60     return FALSE;
61 }
62 
63 /*! \brief Find a module...
64  *
65  * \param Address      If \a Address is not NULL the module containing \a Address
66  *                     is searched.
67  * \param Name         If \a Name is not NULL the module named \a Name will be
68  *                     searched.
69  * \param Index        If \a Index is >= 0 the Index'th module will be returned.
70  * \param pLdrEntry    Pointer to a PLDR_DATA_TABLE_ENTRY which is filled.
71  *
72  * \retval TRUE    Module was found, \a pLdrEntry was filled.
73  * \retval FALSE   No module was found.
74  */
75 BOOLEAN
76 KdbpSymFindModule(
77     IN PVOID Address  OPTIONAL,
78     IN LPCWSTR Name  OPTIONAL,
79     IN INT Index  OPTIONAL,
80     OUT PLDR_DATA_TABLE_ENTRY* pLdrEntry)
81 {
82     LONG Count = 0;
83     PEPROCESS CurrentProcess;
84 
85     /* First try to look up the module in the kernel module list. */
86     if(KdbpSymSearchModuleList(PsLoadedModuleList.Flink,
87                                &PsLoadedModuleList,
88                                &Count,
89                                Address,
90                                Name,
91                                Index,
92                                pLdrEntry))
93     {
94         return TRUE;
95     }
96 
97     /* That didn't succeed. Try the module list of the current process now. */
98     CurrentProcess = PsGetCurrentProcess();
99 
100     if(!CurrentProcess || !CurrentProcess->Peb || !CurrentProcess->Peb->Ldr)
101         return FALSE;
102 
103     return KdbpSymSearchModuleList(CurrentProcess->Peb->Ldr->InLoadOrderModuleList.Flink,
104                                    &CurrentProcess->Peb->Ldr->InLoadOrderModuleList,
105                                    &Count,
106                                    Address,
107                                    Name,
108                                    Index,
109                                    pLdrEntry);
110 }
111 
112 PCHAR
113 NTAPI
114 KdbpSymUnicodeToAnsi(IN PUNICODE_STRING Unicode,
115                      OUT PCHAR Ansi,
116                      IN ULONG Length)
117 {
118     PCHAR p;
119     PWCHAR pw;
120     ULONG i;
121 
122     /* Set length and normalize it */
123     i = Unicode->Length / sizeof(WCHAR);
124     i = min(i, Length - 1);
125 
126     /* Set source and destination, and copy */
127     pw = Unicode->Buffer;
128     p = Ansi;
129     while (i--) *p++ = (CHAR)*pw++;
130 
131     /* Null terminate and return */
132     *p = ANSI_NULL;
133     return Ansi;
134 }
135 
136 /*! \brief Print address...
137  *
138  * Tries to lookup line number, file name and function name for the given
139  * address and prints it.
140  * If no such information is found the address is printed in the format
141  * <module: offset>, otherwise the format will be
142  * <module: offset (filename:linenumber (functionname))>
143  *
144  * \retval TRUE  Module containing \a Address was found, \a Address was printed.
145  * \retval FALSE  No module containing \a Address was found, nothing was printed.
146  */
147 BOOLEAN
148 KdbSymPrintAddress(
149     IN PVOID Address,
150     IN PKTRAP_FRAME Context)
151 {
152     PLDR_DATA_TABLE_ENTRY LdrEntry;
153     ULONG_PTR RelativeAddress;
154     NTSTATUS Status;
155     ULONG LineNumber;
156     CHAR FileName[256];
157     CHAR FunctionName[256];
158     CHAR ModuleNameAnsi[64];
159 
160     if (!KdbpSymbolsInitialized || !KdbpSymFindModule(Address, NULL, -1, &LdrEntry))
161         return FALSE;
162 
163     KdbpSymUnicodeToAnsi(&LdrEntry->BaseDllName,
164                          ModuleNameAnsi,
165                          sizeof(ModuleNameAnsi));
166 
167     RelativeAddress = (ULONG_PTR)Address - (ULONG_PTR)LdrEntry->DllBase;
168     Status = KdbSymGetAddressInformation(LdrEntry->PatchInformation,
169                                          RelativeAddress,
170                                          &LineNumber,
171                                          FileName,
172                                          FunctionName);
173     if (NT_SUCCESS(Status))
174     {
175         DbgPrint("<%s:%x (%s:%d (%s))>",
176             ModuleNameAnsi, RelativeAddress, FileName, LineNumber, FunctionName);
177     }
178     else
179     {
180         DbgPrint("<%s:%x>", ModuleNameAnsi, RelativeAddress);
181     }
182 
183     return TRUE;
184 }
185 
186 
187 /*! \brief Get information for an address (source file, line number,
188  *         function name)
189  *
190  * \param SymbolInfo       Pointer to ROSSYM_INFO.
191  * \param RelativeAddress  Relative address to look up.
192  * \param LineNumber       Pointer to an ULONG which is filled with the line
193  *                         number (can be NULL)
194  * \param FileName         Pointer to an array of CHARs which gets filled with
195  *                         the filename (can be NULL)
196  * \param FunctionName     Pointer to an array of CHARs which gets filled with
197  *                         the function name (can be NULL)
198  *
199  * \returns NTSTATUS error code.
200  * \retval STATUS_SUCCESS  At least one of the requested informations was found.
201  * \retval STATUS_UNSUCCESSFUL  None of the requested information was found.
202  */
203 NTSTATUS
204 KdbSymGetAddressInformation(
205     IN PROSSYM_INFO RosSymInfo,
206     IN ULONG_PTR RelativeAddress,
207     OUT PULONG LineNumber  OPTIONAL,
208     OUT PCH FileName  OPTIONAL,
209     OUT PCH FunctionName  OPTIONAL)
210 {
211     if (!KdbpSymbolsInitialized ||
212         !RosSymInfo ||
213         !RosSymGetAddressInformation(RosSymInfo, RelativeAddress, LineNumber, FileName, FunctionName))
214     {
215         return STATUS_UNSUCCESSFUL;
216     }
217 
218     return STATUS_SUCCESS;
219 }
220 
221 /*! \brief Find cached symbol file.
222  *
223  * Looks through the list of cached symbol files and tries to find an already
224  * loaded one.
225  *
226  * \param FileName  FileName of the symbol file to look for.
227  *
228  * \returns A pointer to the cached symbol info.
229  * \retval NULL  No cached info found.
230  *
231  * \sa KdbpSymAddCachedFile
232  */
233 static PROSSYM_INFO
234 KdbpSymFindCachedFile(
235     IN PUNICODE_STRING FileName)
236 {
237     PIMAGE_SYMBOL_INFO_CACHE Current;
238     PLIST_ENTRY CurrentEntry;
239     KIRQL Irql;
240 
241     DPRINT("Looking for cached symbol file %wZ\n", FileName);
242 
243     KeAcquireSpinLock(&SymbolFileListLock, &Irql);
244 
245     CurrentEntry = SymbolFileListHead.Flink;
246     while (CurrentEntry != (&SymbolFileListHead))
247     {
248         Current = CONTAINING_RECORD(CurrentEntry, IMAGE_SYMBOL_INFO_CACHE, ListEntry);
249 
250         DPRINT("Current->FileName %wZ FileName %wZ\n", &Current->FileName, FileName);
251         if (RtlEqualUnicodeString(&Current->FileName, FileName, TRUE))
252         {
253             Current->RefCount++;
254             KeReleaseSpinLock(&SymbolFileListLock, Irql);
255             DPRINT("Found cached file!\n");
256             return Current->RosSymInfo;
257         }
258 
259         CurrentEntry = CurrentEntry->Flink;
260     }
261 
262     KeReleaseSpinLock(&SymbolFileListLock, Irql);
263 
264     DPRINT("Cached file not found!\n");
265     return NULL;
266 }
267 
268 /*! \brief Add a symbol file to the cache.
269  *
270  * \param FileName    Filename of the symbol file.
271  * \param RosSymInfo  Pointer to the symbol info.
272  *
273  * \sa KdbpSymRemoveCachedFile
274  */
275 static VOID
276 KdbpSymAddCachedFile(
277     IN PUNICODE_STRING FileName,
278     IN PROSSYM_INFO RosSymInfo)
279 {
280     PIMAGE_SYMBOL_INFO_CACHE CacheEntry;
281     KIRQL Irql;
282 
283     DPRINT("Adding symbol file: RosSymInfo = %p\n", RosSymInfo);
284 
285     /* allocate entry */
286     CacheEntry = ExAllocatePoolWithTag(NonPagedPool, sizeof (IMAGE_SYMBOL_INFO_CACHE), TAG_KDBS);
287     ASSERT(CacheEntry);
288     RtlZeroMemory(CacheEntry, sizeof (IMAGE_SYMBOL_INFO_CACHE));
289 
290     /* fill entry */
291     CacheEntry->FileName.Buffer = ExAllocatePoolWithTag(NonPagedPool,
292                                                         FileName->Length,
293                                                         TAG_KDBS);
294     RtlCopyUnicodeString(&CacheEntry->FileName, FileName);
295     ASSERT(CacheEntry->FileName.Buffer);
296     CacheEntry->RefCount = 1;
297     CacheEntry->RosSymInfo = RosSymInfo;
298     KeAcquireSpinLock(&SymbolFileListLock, &Irql);
299     InsertTailList(&SymbolFileListHead, &CacheEntry->ListEntry);
300     KeReleaseSpinLock(&SymbolFileListLock, Irql);
301 }
302 
303 /*! \brief Remove a symbol file (reference) from the cache.
304  *
305  * Tries to find a cache entry matching the given symbol info and decreases
306  * it's reference count. If the refcount is 0 after decreasing it the cache
307  * entry will be removed from the list and freed.
308  *
309  * \param RosSymInfo  Pointer to the symbol info.
310  *
311  * \sa KdbpSymAddCachedFile
312  */
313 static VOID
314 KdbpSymRemoveCachedFile(
315     IN PROSSYM_INFO RosSymInfo)
316 {
317     PIMAGE_SYMBOL_INFO_CACHE Current;
318     PLIST_ENTRY CurrentEntry;
319     KIRQL Irql;
320 
321     KeAcquireSpinLock(&SymbolFileListLock, &Irql);
322 
323     CurrentEntry = SymbolFileListHead.Flink;
324     while (CurrentEntry != (&SymbolFileListHead))
325     {
326         Current = CONTAINING_RECORD(CurrentEntry, IMAGE_SYMBOL_INFO_CACHE, ListEntry);
327 
328         if (Current->RosSymInfo == RosSymInfo) /* found */
329         {
330             ASSERT(Current->RefCount > 0);
331             Current->RefCount--;
332             if (Current->RefCount < 1)
333             {
334                 RemoveEntryList(&Current->ListEntry);
335                 RosSymDelete(Current->RosSymInfo);
336                 ExFreePool(Current);
337             }
338 
339             KeReleaseSpinLock(&SymbolFileListLock, Irql);
340             return;
341         }
342 
343         CurrentEntry = CurrentEntry->Flink;
344     }
345 
346     KeReleaseSpinLock(&SymbolFileListLock, Irql);
347     DPRINT1("Warning: Removing unknown symbol file: RosSymInfo = %p\n", RosSymInfo);
348 }
349 
350 /*! \brief Loads a symbol file.
351  *
352  * \param FileName    Filename of the symbol file to load.
353  * \param RosSymInfo  Pointer to a ROSSYM_INFO which gets filled.
354  *
355  * \sa KdbpSymUnloadModuleSymbols
356  */
357 static VOID
358 KdbpSymLoadModuleSymbols(
359     IN PUNICODE_STRING FileName,
360     OUT PROSSYM_INFO *RosSymInfo)
361 {
362     OBJECT_ATTRIBUTES ObjectAttributes;
363     HANDLE FileHandle;
364     NTSTATUS Status;
365     IO_STATUS_BLOCK IoStatusBlock;
366 
367     /* Allow KDB to break on module load */
368     KdbModuleLoaded(FileName);
369 
370     if (!LoadSymbols)
371     {
372         *RosSymInfo = NULL;
373         return;
374     }
375 
376     /*  Try to find cached (already loaded) symbol file  */
377     *RosSymInfo = KdbpSymFindCachedFile(FileName);
378     if (*RosSymInfo)
379     {
380         DPRINT("Found cached symbol file %wZ\n", FileName);
381         return;
382     }
383 
384     /*  Open the file  */
385     InitializeObjectAttributes(&ObjectAttributes,
386                                FileName,
387                                0,
388                                NULL,
389                                NULL);
390 
391     DPRINT("Attempting to open image: %wZ\n", FileName);
392 
393     Status = ZwOpenFile(&FileHandle,
394                         FILE_READ_ACCESS | SYNCHRONIZE,
395                         &ObjectAttributes,
396                         &IoStatusBlock,
397                         FILE_SHARE_READ | FILE_SHARE_WRITE,
398                         FILE_SYNCHRONOUS_IO_NONALERT);
399     if (!NT_SUCCESS(Status))
400     {
401         DPRINT("Could not open image file: %wZ\n", FileName);
402         return;
403     }
404 
405     DPRINT("Loading symbols from %wZ...\n", FileName);
406 
407     if (!RosSymCreateFromFile(&FileHandle, RosSymInfo))
408     {
409         DPRINT("Failed to load symbols from %wZ\n", FileName);
410         return;
411     }
412 
413     ZwClose(FileHandle);
414 
415     DPRINT("Symbols loaded.\n");
416 
417     /* add file to cache */
418     KdbpSymAddCachedFile(FileName, *RosSymInfo);
419 
420     DPRINT("Installed symbols: %wZ %p\n", FileName, *RosSymInfo);
421 }
422 
423 VOID
424 KdbSymProcessSymbols(
425     IN PLDR_DATA_TABLE_ENTRY LdrEntry)
426 {
427     if (!LoadSymbols)
428     {
429         LdrEntry->PatchInformation = NULL;
430         return;
431     }
432 
433     /* Remove symbol info if it already exists */
434     if (LdrEntry->PatchInformation)
435         KdbpSymRemoveCachedFile(LdrEntry->PatchInformation);
436 
437     /* Load new symbol information */
438     if (! RosSymCreateFromMem(LdrEntry->DllBase,
439         LdrEntry->SizeOfImage,
440         (PROSSYM_INFO*)&LdrEntry->PatchInformation))
441     {
442         /* Error loading symbol info, try to load it from file */
443         KdbpSymLoadModuleSymbols(&LdrEntry->FullDllName,
444             (PROSSYM_INFO*)&LdrEntry->PatchInformation);
445 
446         /* It already added symbols to cache */
447     }
448     else
449     {
450         /* Add file to cache */
451         KdbpSymAddCachedFile(&LdrEntry->FullDllName, LdrEntry->PatchInformation);
452     }
453 
454     DPRINT("Installed symbols: %wZ@%p-%p %p\n",
455            &LdrEntry->BaseDllName,
456            LdrEntry->DllBase,
457            (PVOID)(LdrEntry->SizeOfImage + (ULONG_PTR)LdrEntry->DllBase),
458            LdrEntry->PatchInformation);
459 
460 }
461 
462 VOID
463 NTAPI
464 KdbDebugPrint(
465     PCH Message,
466     ULONG Length)
467 {
468     /* Nothing here */
469 }
470 
471 
472 /*! \brief Initializes the KDB symbols implementation.
473  *
474  * \param DispatchTable         Pointer to the KD dispatch table
475  * \param BootPhase             Phase of initialization
476  */
477 VOID
478 NTAPI
479 KdbInitialize(
480     PKD_DISPATCH_TABLE DispatchTable,
481     ULONG BootPhase)
482 {
483     PCHAR p1, p2;
484     SHORT Found = FALSE;
485     CHAR YesNo;
486     PLDR_DATA_TABLE_ENTRY LdrEntry;
487 
488     DPRINT("KdbSymInit() BootPhase=%d\n", BootPhase);
489 
490     LoadSymbols = FALSE;
491 
492 #if DBG
493     /* Load symbols only if we have 96Mb of RAM or more */
494     if (MmNumberOfPhysicalPages >= 0x6000)
495         LoadSymbols = TRUE;
496 #endif
497 
498     if (BootPhase == 0)
499     {
500         /* Write out the functions that we support for now */
501         DispatchTable->KdpInitRoutine = KdpKdbgInit;
502         DispatchTable->KdpPrintRoutine = KdbDebugPrint;
503 
504         /* Register as a Provider */
505         InsertTailList(&KdProviders, &DispatchTable->KdProvidersList);
506 
507         /* Perform actual initialization of symbol module */
508         //NtoskrnlModuleObject->PatchInformation = NULL;
509         //LdrHalModuleObject->PatchInformation = NULL;
510 
511         InitializeListHead(&SymbolFileListHead);
512         KeInitializeSpinLock(&SymbolFileListLock);
513 
514         /* Check the command line for /LOADSYMBOLS, /NOLOADSYMBOLS,
515         * /LOADSYMBOLS={YES|NO}, /NOLOADSYMBOLS={YES|NO} */
516         ASSERT(KeLoaderBlock);
517         p1 = KeLoaderBlock->LoadOptions;
518         while('\0' != *p1 && NULL != (p2 = strchr(p1, '/')))
519         {
520             p2++;
521             Found = 0;
522             if (0 == _strnicmp(p2, "LOADSYMBOLS", 11))
523             {
524                 Found = +1;
525                 p2 += 11;
526             }
527             else if (0 == _strnicmp(p2, "NOLOADSYMBOLS", 13))
528             {
529                 Found = -1;
530                 p2 += 13;
531             }
532             if (0 != Found)
533             {
534                 while (isspace(*p2))
535                 {
536                     p2++;
537                 }
538                 if ('=' == *p2)
539                 {
540                     p2++;
541                     while (isspace(*p2))
542                     {
543                         p2++;
544                     }
545                     YesNo = toupper(*p2);
546                     if ('N' == YesNo || 'F' == YesNo || '0' == YesNo)
547                     {
548                         Found = -1 * Found;
549                     }
550                 }
551                 LoadSymbols = (0 < Found);
552             }
553             p1 = p2;
554         }
555 
556         RosSymInitKernelMode();
557     }
558     else if (BootPhase == 1)
559     {
560         /* Load symbols for NTOSKRNL.EXE.
561            It is always the first module in PsLoadedModuleList. KeLoaderBlock can't be used here as its content is just temporary. */
562         LdrEntry = CONTAINING_RECORD(PsLoadedModuleList.Flink, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
563         KdbSymProcessSymbols(LdrEntry);
564 
565         /* Also load them for HAL.DLL. */
566         LdrEntry = CONTAINING_RECORD(PsLoadedModuleList.Flink->Flink, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
567         KdbSymProcessSymbols(LdrEntry);
568 
569         KdbpSymbolsInitialized = TRUE;
570     }
571 }
572 
573 /* EOF */
574