Skip to content

Commit 37dffd6

Browse files
authored
Cache negative symbol lookups (dotnet#5729)
Fixes issue #675, but also just improves the amount of symbol lookups we do.
1 parent abf21e2 commit 37dffd6

5 files changed

Lines changed: 75 additions & 1 deletion

File tree

src/SOS/Strike/dbgengservices.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <stdint.h>
77
#include "hostservices.h"
88
#include "exts.h"
9+
#include "symbols.h"
910

1011
extern IMachine* GetTargetMachine(ULONG processorType);
1112

@@ -860,6 +861,9 @@ DbgEngServices::SetCurrentThreadIdFromSystemId(
860861
void
861862
DbgEngServices::InitializeSymbolStoreFromSymPath()
862863
{
864+
// Clear the negative symbol lookup cache so modules are retried with the new path.
865+
ClearSymbolLookupCache();
866+
863867
ISymbolService* symbolService = GetSymbolService();
864868
if (symbolService != nullptr)
865869
{

src/SOS/Strike/managedcommands.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
#include "exts.h"
5+
#include "symbols.h"
56

67
// Windows host only managed command stubs
78

@@ -142,6 +143,10 @@ DECLARE_API(ObjSize)
142143
DECLARE_API(SetSymbolServer)
143144
{
144145
INIT_API_EXT();
146+
147+
// Clear negative symbol lookup cache so modules are retried with the new configuration.
148+
ClearSymbolLookupCache();
149+
145150
return ExecuteManagedOnlyCommand("setsymbolserver", args);
146151
}
147152

src/SOS/Strike/symbols.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@
3131
#undef IfFailRet
3232
#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0)
3333

34+
// Tracks module PE addresses for which symbol loading has already failed,
35+
// so we don't repeatedly hit symbol servers for the same module.
36+
// Cleared when the symbol path changes (see ClearSymbolLookupCache).
37+
static std::set<ULONG64> g_failedSymbolLookups;
38+
39+
void ClearSymbolLookupCache()
40+
{
41+
g_failedSymbolLookups.clear();
42+
}
43+
3444
#ifndef FEATURE_PAL
3545
HMODULE g_hmoduleSymBinder = nullptr;
3646
ISymUnmanagedBinder3 *g_pSymBinder = nullptr;
@@ -314,6 +324,14 @@ HRESULT SymbolReader::LoadSymbols(___in IMetaDataImport* pMD, ___in IXCLRDataMod
314324
ExtOut("LoadSymbols GetClrModuleImages FAILED 0x%08x\n", hr);
315325
return hr;
316326
}
327+
328+
// Skip modules that have already failed symbol loading to avoid repeated
329+
// network round-trips to symbol servers (see dotnet/diagnostics#675).
330+
if (moduleBase != 0 && g_failedSymbolLookups.find(moduleBase) != g_failedSymbolLookups.end())
331+
{
332+
return E_FAIL;
333+
}
334+
317335
if (GetSymbolService() == nullptr || !HasPortablePDB(moduleBase))
318336
{
319337
hr = LoadSymbolsForWindowsPDB(pMD, moduleBase, pModuleName, FALSE);
@@ -328,6 +346,13 @@ HRESULT SymbolReader::LoadSymbols(___in IMetaDataImport* pMD, ___in IXCLRDataMod
328346
#endif
329347
}
330348

349+
// Skip modules that have already failed symbol loading to avoid repeated
350+
// network round-trips to symbol servers (see dotnet/diagnostics#675).
351+
if (moduleData.LoadedPEAddress != 0 && g_failedSymbolLookups.find(moduleData.LoadedPEAddress) != g_failedSymbolLookups.end())
352+
{
353+
return E_FAIL;
354+
}
355+
331356
#ifndef FEATURE_PAL
332357
if (GetSymbolService() == nullptr || !HasPortablePDB(moduleData.LoadedPEAddress))
333358
{
@@ -340,14 +365,21 @@ HRESULT SymbolReader::LoadSymbols(___in IMetaDataImport* pMD, ___in IXCLRDataMod
340365
}
341366
#endif // FEATURE_PAL
342367

343-
return LoadSymbolsForPortablePDB(
368+
hr = LoadSymbolsForPortablePDB(
344369
pModuleName,
345370
moduleData.IsInMemory,
346371
moduleData.IsFileLayout,
347372
moduleData.LoadedPEAddress,
348373
moduleData.LoadedPESize,
349374
moduleData.InMemoryPdbAddress,
350375
moduleData.InMemoryPdbSize);
376+
377+
if (FAILED(hr) && moduleData.LoadedPEAddress != 0)
378+
{
379+
g_failedSymbolLookups.insert(moduleData.LoadedPEAddress);
380+
}
381+
382+
return hr;
351383
}
352384

353385
#ifndef FEATURE_PAL

src/SOS/Strike/symbols.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,7 @@ GetLineByOffset(
7979
__out_ecount(cchFileName) WCHAR* pwszFileName,
8080
___in ULONG cchFileName,
8181
___in BOOL bAdjustOffsetForLineNumber = FALSE);
82+
83+
// Clears the negative cache for failed symbol lookups, allowing modules
84+
// to be retried. Call when the symbol path changes.
85+
void ClearSymbolLookupCache();

src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,35 @@ public void SymbolPathTests()
9191
symbolService.DisableSymbolStore();
9292
}
9393

94+
[Fact]
95+
public void OpenSymbolFile_ReturnsNull_ForInvalidPEStream()
96+
{
97+
SymbolService symbolService = new(this);
98+
99+
// OpenSymbolFile should return null (not throw) for non-PE data.
100+
// The native SymbolReader::LoadSymbols layer caches these failures
101+
// to avoid repeated symbol server lookups (dotnet/diagnostics#675),
102+
// but that caching is in native C++ and cannot be tested here.
103+
byte[] bogusData = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
104+
using System.IO.MemoryStream stream = new(bogusData);
105+
106+
ISymbolFile result = symbolService.OpenSymbolFile("bogus.dll", isFileLayout: false, stream);
107+
Assert.Null(result);
108+
}
109+
110+
[Fact]
111+
public void OpenSymbolFile_ReturnsNull_ForInvalidPdbStream()
112+
{
113+
SymbolService symbolService = new(this);
114+
115+
// A stream that is not a valid portable PDB should return null.
116+
byte[] bogusData = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 };
117+
using System.IO.MemoryStream stream = new(bogusData);
118+
119+
ISymbolFile result = symbolService.OpenSymbolFile(stream);
120+
Assert.Null(result);
121+
}
122+
94123
#region IHost
95124

96125
public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent();

0 commit comments

Comments
 (0)