Description
PEImageReader::DebugDirectoryInformation() rejects a perfectly valid CodeView record whenever it is 25 bytes long:
// snapshot/win/pe_image_reader.cc:180
if (debug_directory.SizeOfData < sizeof(CodeViewRecordPDB70)) {
LOG(WARNING) << "CodeView debug entry of unexpected size in " ...
continue;
}
sizeof(CodeViewRecordPDB70) is 28, not 25: uint32 signature + UUID uuid (16) + uint32 age + uint8 pdb_name[1] is 25 bytes, rounded up to 28 by tail padding. A record whose PDB name is the empty string is exactly 25 bytes - and that is what ld.lld writes into every MinGW-built module: an RSDS record carrying a real per-module GUID and no PDB path.
crashpad's own minidump reader gets this right, comparing against the smallest valid record instead of the padded struct:
// snapshot/minidump/module_snapshot_minidump.cc:106
if (cv_record.size() < offsetof(CodeViewRecordPDB70, pdb_name) + 1) {
Because the PE reader returns false, ModuleSnapshotWin leaves uuid_ zeroed and falls back to pdb_name_ = base::WideToUTF8(name_) (snapshot/win/module_snapshot_win.cc:76). Every MinGW module then appears in the minidump with debug id 00000000-0000-0000-0000-000000000000, age 0, and its own path where the PDB name belongs, so no uploaded debug file can ever be matched to it and those frames never symbolicate. Microsoft-built modules are unaffected, since their records carry a PDB name and clear the 28-byte bar.
Suggested fix, mirroring the minidump reader:
if (debug_directory.SizeOfData < offsetof(CodeViewRecordPDB70, pdb_name) + 1) {
When does the problem happen
Environment
- OS: Windows 11, 64-bit
- Compiler: clang 20 with
ld.lld (MSYS2 CLANG64); the shipped Qt DLLs come from the same toolchain
- CMake version and config: sentry-native 0.16.2,
SENTRY_BACKEND=crashpad
Steps To Reproduce
A/B against the same application, changing one header field and nothing else:
-
Crash an app built with MinGW/lld that also loads lld-linked DLLs, and read the module list out of the minidump:
Qt6Core.dll code_id=6a24f1e05e9000 RSDS uuid=00000000-0000-0000-0000-000000000000 age=0 pdb='C:\...\Qt6Core.dll'
The DLL on disk does have a usable record: SizeOfData=25, RSDS, GUID 79775ce9-b980-b827-4c4c-44205044422e, age 1, empty PDB name.
-
Patch that module's IMAGE_DEBUG_DIRECTORY.SizeOfData from 25 to 28 - the three bytes following the record are zero padding inside .buildid, and the record contents are untouched - then crash again:
Qt6Core.dll code_id=6a24f1e05e9000 RSDS uuid=79775ce9-b980-b827-4c4c-44205044422e age=1 pdb=''
Modules left unpatched in that same dump stay zeroed, and sentry-cli debug-files check reports the same Debug ID and Code ID for the DLL before and after, confirming only the declared record length changed.
Log output
The handler says so itself - 149 lines, one per MinGW-built module, in a single crash:
[6152:7452:20260819,114422.474:WARNING pe_image_reader.cc:181] CodeView debug entry of unexpected size in C:\...\app-4.22.0\Mudlet.exe
[6152:7452:20260819,114422.474:WARNING pe_image_reader.cc:181] CodeView debug entry of unexpected size in C:\...\app-4.22.0\Qt6Multimedia.dll
[6152:7452:20260819,114422.474:WARNING pe_image_reader.cc:181] CodeView debug entry of unexpected size in C:\...\app-4.22.0\libc++.dll
[6152:7452:20260819,114422.474:WARNING pe_image_reader.cc:181] CodeView debug entry of unexpected size in C:\...\app-4.22.0\lua51.dll
...
Filed here rather than on the fork because getsentry/crashpad has issues disabled; the same code is in upstream chromium/crashpad, so it presumably wants forwarding there too. Happy to send a PR if that helps.
Description
PEImageReader::DebugDirectoryInformation()rejects a perfectly valid CodeView record whenever it is 25 bytes long:sizeof(CodeViewRecordPDB70)is 28, not 25:uint32 signature+UUID uuid(16) +uint32 age+uint8 pdb_name[1]is 25 bytes, rounded up to 28 by tail padding. A record whose PDB name is the empty string is exactly 25 bytes - and that is whatld.lldwrites into every MinGW-built module: an RSDS record carrying a real per-module GUID and no PDB path.crashpad's own minidump reader gets this right, comparing against the smallest valid record instead of the padded struct:
Because the PE reader returns false,
ModuleSnapshotWinleavesuuid_zeroed and falls back topdb_name_ = base::WideToUTF8(name_)(snapshot/win/module_snapshot_win.cc:76). Every MinGW module then appears in the minidump with debug id00000000-0000-0000-0000-000000000000, age 0, and its own path where the PDB name belongs, so no uploaded debug file can ever be matched to it and those frames never symbolicate. Microsoft-built modules are unaffected, since their records carry a PDB name and clear the 28-byte bar.Suggested fix, mirroring the minidump reader:
When does the problem happen
Environment
ld.lld(MSYS2 CLANG64); the shipped Qt DLLs come from the same toolchainSENTRY_BACKEND=crashpadSteps To Reproduce
A/B against the same application, changing one header field and nothing else:
Crash an app built with MinGW/lld that also loads lld-linked DLLs, and read the module list out of the minidump:
The DLL on disk does have a usable record:
SizeOfData=25,RSDS, GUID79775ce9-b980-b827-4c4c-44205044422e, age 1, empty PDB name.Patch that module's
IMAGE_DEBUG_DIRECTORY.SizeOfDatafrom 25 to 28 - the three bytes following the record are zero padding inside.buildid, and the record contents are untouched - then crash again:Modules left unpatched in that same dump stay zeroed, and
sentry-cli debug-files checkreports the same Debug ID and Code ID for the DLL before and after, confirming only the declared record length changed.Log output
The handler says so itself - 149 lines, one per MinGW-built module, in a single crash:
Filed here rather than on the fork because getsentry/crashpad has issues disabled; the same code is in upstream chromium/crashpad, so it presumably wants forwarding there too. Happy to send a PR if that helps.