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