Dbghelp API fails to find symbol files in downstream store
Over time the local store that I used for downloaded symbol files had become quite large. I considered using the AgeStore utility to prune it but it balked because file system last access updating was disabled. So I decided to write a utility using the Dbghelp API to find the file system location of the symbol files that correspond to my system's current binaries. With that information I would be able to delete all other old and irrelevant symbol files that uselessly consumed disk space. I had experimented with this API in the past and knew that the SymFindFileInPath function was capable of enumerating the symbol store using a callback function that compared symbol information from a binary to the corresponding information from the enumerated symbol files. This comparison would enable me to achieve my objective.
However, SymFindFileInPath consistently failed to enumerate any symbol files in the local store, returning a file not found error. Consequently, the callback function was not executed. Apparently, the issue is caused by downloaded symbol files being stored in a sub-folder of the local store that is not searched by symsrv.dll.
I have reported this on the developer community site at https://developercommunity.visualstudio.com/t/Dbghelp-API-fails-to-find-symbol-files-i/11062806
Windows development | Windows API - Win32
-
Taki Ly (WICLOUD CORPORATION) • 150 Reputation points • Microsoft External Staff • Moderator
2026-03-20T10:33:38.2866667+00:00 Hi @RLWA32 ,
I am looking into this and will response back to you as soon as possible.
-
Gary Nebbett • 6,476 Reputation points2026-03-20T11:15:03.6866667+00:00 Hello @RLWA32 ,
Can you try the following (improvising, as you see fit): start debugging something simple (e.g. "cdb calc" or "windbg calc") and then, in the debugger, issue the commands "x kernel32!AddAtom*" and "lm m kernel32".
Does the path to the .PDB file shown by "lm" include the "stripped" path component?
I searched the .DLL and .EXE files in the DBGHELPDIRECTORY and the only file that seemed to use "stripped" as part of a path was "symstore.exe". Do you use "symstore"?
I often delete my entire cached symbol store and let it build up again for a while.
How were you planning to perform the correlation: enumerate the system binaries, checking if a corresponding symbol file is present or enumerate the cached symbol files, checking if the system binary is present?
Are there non-Microsoft symbol files in your local symbol cache?
Gary
P.S.
It is a bit "off topic", but the "LoadLibraryA" call in your demo application will probably be a problem if you want to check system drivers (.sys) files and perhaps ntoskrnl.exe; ImageDirectoryEntryToData perhaps preceded by LoadLibraryExA with suitable flags or MapViewOfFile might be better.
-
RLWA32 • 52,261 Reputation points2026-03-20T11:51:32.15+00:00 @Gary Nebbett -- Thanks for your feedback.
Can you try the following (improvising, as you see fit): start debugging something simple (e.g. "cdb calc" or "windbg calc") and then, in the debugger, issue the commands "x kernel32!AddAtom*" and "lm m kernel32".
What purpose does this serve with respect to the identified issue? The problem isn't that a debugger fails to load symbol files but that the Dbghelp API function SymFindFileInPath doesn't.
Do you use "symstore"?
No.
How were you planning to perform the correlation
Enumerate the binaries and find matching symbol files.
Are there non-Microsoft symbol files in your local symbol cache?
No.
Finally, the demo program just loaded kernel32.dll for demonstration purposes and to keep it simple. :)
-
Gary Nebbett • 6,476 Reputation points2026-03-20T12:03:20.98+00:00 Hello @RLWA32 ,
The purpose of the "windbg calc" test was to see how the standard Microsoft tools that use the symbol cache were working. From the debug output, there did not seem to be a copy of kernel32.pd* in the expected place. Executing that test might show the path including "stripped" or it might download kernel32.pdb to the cache at the expected location and show that.
Gary
-
RLWA32 • 52,261 Reputation points2026-03-20T12:12:24.9066667+00:00 @Gary Nebbett The VS2022 debugger finds the symbol files in the "stripped" sub-folder and the path is shown in the Modules pane when debugging.
-
Gary Nebbett • 6,476 Reputation points2026-03-20T12:37:07.2333333+00:00 Hello @RLWA32 ,
My suspicion would be that the Visual Studio debugger is managing the symbol cache itself (and not using Dbghelp routines like SymFindFileInPath), but I don't have Visual Studio installed (so I can't verify that).
You could still benefit from trying the "windbg" test - you might then see that, afterwards, your test application could find and verify the presence of the kernel32 symbols.
Knowing the simple directory structure and naming conventions used in the symbol cache, you could just jettison use of Dbghelp and construct the file paths manually, accommodating factors such as "stripped" sub-directories.
BTW: what are the contents of the "origin.txt" file in the "stripped" directory?
Gary
-
RLWA32 • 52,261 Reputation points2026-03-20T12:59:44.7133333+00:00 @Gary Nebbett The original.txt file just contains the value of the _NT_SYMBOL_PATH environment variable -- srv*F:\Symbols*https://msdl.microsoft.com/download/symbols I had hoped to avoid writing implementation dependent code by using one documented API function. It worked in the past so unless my code is bad something has changed on Microsoft's side of things.
-
Gary Nebbett • 6,476 Reputation points2026-03-20T13:19:08.68+00:00 Hello @RLWA32 ,
My recommendation (and what I do), if your symbol cache just contains Microsoft symbols, is just to delete everything.
Here is a directory listing, sorted by date, of my ntdll symbols:
Directory of C:\Windows\Symbols\ntdll.pdb 03/09/2025 17:00 <DIR> 21EB609F5CD42F738B9AD508893634BD1 15/09/2025 10:21 <DIR> DAD4BF763E723284BC97F7DB68FA41781 12/10/2025 18:13 <DIR> 3D25C9880F065F55B402EE8376F300E81 15/10/2025 11:10 <DIR> 3DF97D250AD59D01BC65E275570F19041 30/10/2025 20:49 <DIR> 52E797D9694BBE7D8F3819BB7A7C075B1 10/12/2025 12:34 <DIR> E010FE8BE35C4C961B7E7658B5BCAE9D1 14/01/2026 10:20 <DIR> 08A413EE85E91D0377BA33DC3A2641941 30/01/2026 22:17 <DIR> 3DE7E8F4BDA1BF7181DE9D18EB5676AB1 11/02/2026 08:47 <DIR> 2CF5F86ACB68735923D72913BBD9B0E31 25/02/2026 20:32 <DIR> . 25/02/2026 20:32 <DIR> 5A9F4004DB806B50BCBD7B6C55417B041 20/03/2026 13:44 <DIR> .. 0 File(s) 0 bytes 12 Dir(s) 58,093,019,136 bytes freeAbout every two weeks ("Patch Tuesdays" and "Preview Update" days), changes are normally made to the heavily used binaries and new symbols are needed anyway.
Gary
-
RLWA32 • 52,261 Reputation points2026-03-20T13:48:06.43+00:00 My recommendation (and what I do), if your symbol cache just contains Microsoft symbols, is just to delete everything.
Yes, the nuclear option is always available.
About every two weeks ("Patch Tuesdays" and "Preview Update" days), changes are normally made to the heavily used binaries and new symbols are needed anyway.
I don't install previews so the update cycle is not as frequent for me.
Thanks again for your comments.
-
Viorel • 126.8K Reputation points2026-03-20T16:19:55.4633333+00:00 Meanwhile, for a limited period, the function could be helped to search in stripped folder as well. Something like this:
WCHAR wszGuid[39]; int n = StringFromGUID2( pInfo->guid, wszGuid, 39 ); std::string g( wszGuid, wszGuid + n - 1 ); g.erase( std::remove_if( g.begin( ), g.end( ), []( char c ) { return c == '{' || c == '}' || c == '-'; } ), g.end( ) ); std::string sp = std::string( "F:\\Symbols;F:\\Symbols\\" ) + pInfo->name + '\\' + g + std::to_string( pInfo->age ) + "\\stripped"; if( SymFindFileInPath( h, sp.c_str( ), pInfo->name, &pInfo->guid, pInfo->age, 0, SSRVOPT_GUIDPTR, szFound, ComparePDBInfo, pInfo ) ) printf_s( "SymFindFileInPath found %s\n", szFound ); else printf_s( "SymFindFileInPath for %s failed with %u\n", pInfo->name, GetLastError( ) ); -
Gary Nebbett • 6,476 Reputation points2026-03-20T19:53:10.0366667+00:00 Hello All,
I “bit the bullet” and installed the community version of Visual Studio :-)
The symbol file management within Visual Studio shares concepts, but not common binary code, with Dbghelp. Here is one stack trace (more are available, upon request) that captures a moment in the process of Visual Studio debug symbol file management:
%1 %2 %3 %4 %5 %6 %7 1 Irp = 0xFFFFD9092B48B0F8 (TDH_INTYPE_POINTER, TDH_OUTTYPE_HEXINT64, 0, 8, 1, ) 2 FileObject = 0xFFFFD90931CB07F0 (TDH_INTYPE_POINTER, TDH_OUTTYPE_HEXINT64, 0, 8, 1, ) 3 IssuingThreadId = 6404 (TDH_INTYPE_UINT32, TDH_OUTTYPE_UNSIGNEDINT, 0, 4, 1, ) 4 CreateOptions = 0x5000060 (TDH_INTYPE_UINT32, TDH_OUTTYPE_HEXINT32, 0, 4, 1, ) 5 CreateAttributes = 0x80 (TDH_INTYPE_UINT32, TDH_OUTTYPE_HEXINT32, 0, 4, 1, ) 6 ShareAccess = 0x0 (TDH_INTYPE_UINT32, TDH_OUTTYPE_HEXINT32, 0, 4, 1, ) 7 FileName = \Device\HarddiskVolume1\Users\Gary\AppData\Local\Temp\SymbolCache\oleaut32.pdb\87091c6a28419368378aae5938a2b2d61\stripped\origin.txt (TDH_INTYPE_UNICODESTRING, TDH_OUTTYPE_STRING, 0, 0, 1, ) Child-SP RetAddr Call Site 000000dd`46e3eb30 00007ff9`eecdb5c7 symbollocator!SymbolLocatorLib::CSymbolLocator::WriteOriginFile+0xc3 000000dd`46e3ee60 00007ff9`eecdd9dd symbollocator!SymbolLocatorLib::CSymbolLocator::LocateSymbolFile+0xff3 000000dd`46e3f360 00000294`69b35853 symbollocator!SymbolLocatorLib::CSymbolLocator::LocateAnyFormatPdbWithChecksums+0xfd 000000dd`46e3f480 00000294`69b069c9 vsdebugeng_impl!SymProvider::CSymbolLocator::LocateAnyFormatPdbWithChecksums+0xe3 000000dd`46e3f530 00000294`69b058ed vsdebugeng_impl!SymProvider::CDiaLoader::LoadPDB+0x675 000000dd`46e3f6f0 00000294`69b04203 vsdebugeng_impl!SymProvider::CDiaLoader::ReloadSymbols+0x43d 000000dd`46e3f890 00007ff9`83f99dbb vsdebugeng_impl!SymProvider::CDiaLoader::TryLoadSymbols+0x83 000000dd`46e3f8e0 00000294`69bba30d vsdebugeng!dispatcher::DkmModuleInstance::TryLoadSymbols+0x93 000000dd`46e3f980 00007ff9`f5860cd3 vsdebugeng_impl!ad7::CALModule::LoadSymbolsThreadPool+0x4d 000000dd`46e3f9b0 00007ff9`f583d79a ntdll!RtlpTpWorkCallback+0x173 000000dd`46e3fa90 00007ff9`f3957374 ntdll!TppWorkerThread+0x68a 000000dd`46e3fd90 00007ff9`f583cc91 KERNEL32!BaseThreadInitThunk+0x14 000000dd`46e3fdc0 00000000`00000000 ntdll!RtlUserThreadStart+0x21The paths to symallocator and vsdebugeng_impl are “\Program Files\Microsoft Visual Studio\18\Community\Common7\Packages\Debugger\symbollocator.dll” and “\Program Files\Microsoft Visual Studio\18\Community\Common7\Packages\Debugger\vsdebugeng.impl.dll" – quite separate from dbghelp.dll (where SymFindFileInPath lives).
Personally, I would not follow Viorel’s proposal – that looks like it is working around a bug in SymFindFileInPath, which gives the wrong impression. Providing the “calculated” path to the symbol file in the “SearchPath” parameter and getting the same path back (plus filename) via the “FoundFile” parameter is not helpful and a waste of (execution) time.
I am fairly sure that there is no documented method of achieving your goal; practical approaches include deleting everything, enabling “Last Access” updating (I thought that that was the default) and trusting in agestore, or using your undoubted knowledge of the internal symbol file hierarchy structure (GUIDs and Ages) and constructing paths manually.
Gary
-
RLWA32 • 52,261 Reputation points2026-03-21T07:13:09.0633333+00:00 Gary & Viorel,
Thanks to both for your comments and observations.
Perhaps when @Taki Ly (WICLOUD CORPORATION) revisits this issue we will get some feedback on Microsoft's view of the Dbghelp API and related sysmsrv.dll failures with respect to symbol file paths.
-
RLWA32 • 52,261 Reputation points2026-03-22T01:19:22.09+00:00 @Gary Nebbett Your observations about the VS debugger prompted me to reconsider earlier suggestions about testing with windbg. They were right on target. I used it to debug some applications and it downloaded symbols to the expected locations in the local store. It also showed the expected paths when examining loaded modules.
After windbg usage I checked the local store. It contained two identical copies of the symbol files. One in the "stripped" subfolder created by the VS debugger and the other in the expected location.
To add insult to injury, the VS debugger now loaded the symbol files downloaded by the windbg session!
I didn't see any setting in the VS debugging options with respect to this extremely annoying behavior.
Update:
I deleted the symbol file downloaded during the windbg session and manually created the "file.ptr" file containing the fully qualified path to the symbol file in the "stripped" subfolder created by the VS debugger.
Upon starting a new session with windbg.exe it was able to find and load the symbol file in the "stripped" subfolder. Also, the SymFindFileInPath function now correctly located the VS debugger symbol file.
-
Gary Nebbett • 6,476 Reputation points2026-03-22T10:25:37.9733333+00:00 Hello @RLWA32 ,
I guess that you have been reading "Symbol Storage Format".
I asked earlier "How were you planning to perform the correlation" and you replied "Enumerate the binaries and find matching symbol files" - that was not the answer that I expected.
Consider this perspective: the cache possibly contains lots of "never to be used/viewed again" files and "unintended" "damage" to the cache can be "fixed" by causing the symbol files to be downloaded again; the structure of the cache is easily inferable and can differ slightly between tools (e.g. WinDbg, Visual Studio, Windows Performance Analyzer); there is no API for symbol cache management.
Your needs would seem to be best served by traversing the symbol cache, searching just a few known locations (e.g. %SystemRoot%\System32) for matching executable names and then checking the CodeView GUID/age match; if there is no match then delete the cache entry. You could even refine this clean-up process to remove "duplicate" .pdb files and create file.ptr entries.
If you "Enumerate the binaries", then it would make sense to check a wide variety of locations, such as "\Program Files (x86)\Microsoft\Edge\Application" - a single msedge.dll.pdb file is almost a gigabyte in size (you probably don't want to collect too many of them).
Gary
Update: some additional considerations.
- If you use old .dmp or .etl files or receive copies of those files from other systems, then old .pdb files might still be useful.
- Some old .pdb files contain valuable information (see the "Anti-Trust Settlement Forensics" section of https://www.geoffchappell.com/studies/windows/km/ntoskrnl/source/inc/ntmmapi.htm)
-
RLWA32 • 52,261 Reputation points2026-03-22T11:11:45.1833333+00:00 @Gary Nebbett Since I had not experienced this problem in the past when I was experimenting with symbol files I went back and tested using VS2019. The VS2019 debugger downloaded symbol files to their expected location in the local store. So apparently a change made for VS2022 is the culprit. I haven't installed VS2026 so I don't know how its debugger behaves.
I'd rather not have to tweak the content of the local store so that symbols downloaded by VS2022 are accessible to windbg and the dbghelp API. After all, any solution that is implemented will need wash, rinse and repeat every time new symbols are downloaded by the VS debugger.
IMHO, the best solution is for VS2022 (and maybe VS2026) to create the correct file.ptr file at the time it downloads from the Microsoft symbol server when it saves symbol files to a "non-standard" folder in the local store.
The breadth and depth of Geoff Chappell's knowledge was amazing. I was saddened to read of his passing.
-
Gary Nebbett • 6,476 Reputation points2026-03-22T11:17:12.7533333+00:00 Hello @RLWA32 ,
I thought about saying a few words about Geoff Chappell - I am glad that you actually did.
Gary
Sign in to comment