* Sync with trunk r64401.
[reactos.git] / ntoskrnl / kdbg / kdb_symbols.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/dbg/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 */