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