DBG¶
Level: 201 · for anyone with a hex editor open
One line: A Windows separate-debug file starts with the two ASCII bytes DI, which are 0x4944 read little-endian and 0x4449 read big-endian — Ghidra keeps both constants — and its debug directory points at a CodeView record whose GUID is written in three byte orders at once and whose PDB path is in no declared encoding at all.
What the loader reads¶
A .dbg file is the debug information that Windows NT's linker could split off a PE — section headers, exported names and a debug directory — leaving the executable smaller and the symbols in a file beside it. Microsoft documents the header as IMAGE_SEPARATE_DEBUG_HEADER in winnt.h; the PE Format ↗ page documents the directory entries it holds. Everything is little-endian, by specification.
| offset | field | width | what it says |
|---|---|---|---|
| 0 | Signature |
2 | 44 49, the letters DI |
| 2 | Flags |
2 | |
| 4 | Machine |
2 | the image's, from the same table as COFF |
| 6 | Characteristics |
2 | the image's |
| 8 | TimeDateStamp, CheckSum, ImageBase, SizeOfImage |
4 each | the image's |
| 24 | NumberOfSections |
4 | 40-byte section headers follow the 48-byte header |
| 28 | ExportedNamesSize |
4 | then this many bytes of NUL-terminated names |
| 32 | DebugDirectorySize |
4 | then this many bytes of 28-byte IMAGE_DEBUG_DIRECTORY entries |
| 36 | SectionAlignment |
4 | |
| 40 | Reserved[2] |
8 |
Each directory entry has a Type — 1 COFF, 2 CodeView, 3 FPO, 4 Misc — a size, and a PointerToRawData. Type 2 points at the record a debugger cares about: RSDS, a GUID, an age, and the path of the .pdb file that holds the symbols.
In Python¶
Verified output of dbg_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. THE SIGNATURE IS TWO BYTES, AND TWO NUMBERS
------------------------------------------------------------------------
bytes 0..2 44 49 b'DI' little 0x4944 big 0x4449
Microsoft's IMAGE_SEPARATE_DEBUG_SIGNATURE is 0x4944, the two
letters read little-endian. Ghidra's SeparateDebugHeader keeps a
second constant, 0x4449, named _MAC -- the same picture read the
other way. Two names for one pair of bytes is the honest version.
2. THE HEADER IS 48 BYTES OF 16- AND 32-BIT FIELDS
------------------------------------------------------------------------
Signature 0x00004944 18756
Flags 0x00000000 0
Machine 0x0000014c 332
Characteristics 0x00000102 258
TimeDateStamp 0x00000000 0
CheckSum 0x00000000 0
ImageBase 0x00400000 4194304
SizeOfImage 0x00003000 12288
NumberOfSections 0x00000001 1
ExportedNamesSize 0x0000000c 12
DebugDirectorySize 0x0000001c 28
SectionAlignment 0x00001000 4096
Reserved[0] 0x00000000 0
Reserved[1] 0x00000000 0
Machine and Characteristics are the PE's own, copied here so the
file can say which image it belongs to. Then NumberOfSections
section headers, ExportedNamesSize bytes of NUL-terminated names,
and DebugDirectorySize bytes of 28-byte directory entries.
3. THE DEBUG DIRECTORY POINTS AT A CODEVIEW RECORD
------------------------------------------------------------------------
exported names ['main', '_start']
entry 0 Type 2 CODEVIEW SizeOfData 43 PointerToRawData 0x80
b'RSDS' e0 04 25 3f 89 4f d3 11 9a 0c 03 05 e8 2c 33 01 age 7
path bytes 43 3a 5c 62 75 69 6c 64 5c 63 61 66 c3 a9 2e 70 64 62
as UTF-8 'C:\\build\\café.pdb'
as cp1252 'C:\\build\\café.pdb'
RSDS is the PDB 7.0 record: signature, GUID, age, path. Nothing in
it says which encoding the path is in -- the linker wrote whatever
bytes it had, and 'c3 a9' is one character or two depending on
the reader. A debugger that opens the wrong file is the bug.
4. A GUID IS THREE BYTE ORDERS IN SIXTEEN BYTES
------------------------------------------------------------------------
as text {3F2504E0-4F89-11D3-9A0C-0305E82C3301}
.bytes 3f 25 04 e0 4f 89 11 d3 9a 0c 03 05 e8 2c 33 01 the text, left to right
.bytes_le e0 04 25 3f 89 4f d3 11 9a 0c 03 05 e8 2c 33 01 what the file holds
Data1 (4 bytes) and Data2, Data3 (2 each) are little-endian; the
last eight are in text order. So the file's e0 04 25 3f 89 4f d3 11
reads back as 3F2504E0-4F89-11D3 -- three fields reversed and one
not, in one 16-byte value. Python spells the choice bytes_le.
One picture, two numbers, both named¶
DI is 44 49. Read little-endian that is 0x4944, and Microsoft's constant is spelled that way; Ghidra's SeparateDebugHeader defines it, and beside it a second one, IMAGE_SEPARATE_DEBUG_SIGNATURE_MAC = 0x4449, the same two bytes read the other way. The header class compares against the first. The second exists because somebody once met the bytes from a big-endian reader and gave the number a name rather than a bug report — which is the honest way to handle a text signature, and the MZ page has the same pair for 4d 5a.
A GUID has three byte orders¶
Section 4 is the reason this page is in an encodings library. A GUID is written as text in five groups — 3F2504E0-4F89-11D3-9A0C-0305E82C3301 — and stored as sixteen bytes in which the first three groups are little-endian integers of 4, 2 and 2 bytes and the last eight bytes are in text order. So the file holds e0 04 25 3f 89 4f d3 11 9a 0c 03 05 e8 2c 33 01: three fields reversed, one not, inside one value that every tool prints as a single string. Python's uuid module spells the choice bytes against bytes_le, and a program that reads a CodeView GUID with the wrong one gets a GUID that is valid, unique, and matches no PDB on earth. The bytes do not say which end is usually a question about one integer; here it is asked four times inside sixteen bytes.
The path with no encoding¶
The PDB path after the GUID is NUL-terminated bytes. Nothing in the record says what encoding they are in, and the linker wrote whatever it had — on the machine that built it, under that machine's ANSI code page. Section 3 decodes the same eighteen bytes as UTF-8 and as Windows-1252 and gets café.pdb and café.pdb; a debugger looking for the second file will not find it, and the failure reads as a missing file rather than an encoding. That is mojibake in a place nobody thinks of as text.
What Ghidra checks¶
DbgLoader ↗ parses a SeparateDebugHeader, accepts the file when getSignature() equals 0x4944, and takes the language from the header's Machine field, exactly as the PE loader does. Its name is Debug Symbols (DBG), and it extends the same base class as the PE loader's debug handling, since the directory entries are the PE's own.
If you are coming from Python or ABAP¶
Python. uuid.UUID(bytes_le=data[4:20]) is the one line that matters, and uuid.UUID(bytes=...) is the wrong one for a Windows GUID — both succeed. The header is struct.unpack_from('<HHHHIIIIIIIIII', data, 0); the directory entry '<IIHHIIII'. Decode the path with the code page the building machine used, which the file does not say: try utf-8 and fall back to cp1252, and treat the difference as a fact to report rather than hide.
ABAP. (Not machine-checked — CI cannot run ABAP.) A GUID in SAP is sysuuid_x16, 16 bytes, and cl_system_uuid renders it as 32 hex characters in byte order — no fields are reversed, because SAP's GUIDs were never Windows GUIDs. So a Windows GUID moved into a sysuuid_x16 prints with its first three groups reversed, and looks wrong to anyone who has the Windows text beside it. Reverse the first 4, 2 and 2 bytes with +off(len) slices before comparing, and write down which convention a stored value is in, because the bytes do not say.
Try it¶
- Find any
.pdb-bearing Windows binary and search it forRSDS:grep -c RSDS file.exeunderLC_ALL=C. Dump 24 bytes after it and read the GUID withuuid.UUID(bytes_le=...). - Read the same 16 bytes with
bytes=instead and compare the two strings. Note which groups moved. strings -n 6 file.exe | grep '\.pdb'shows the path. Check whether it has any byte above0x7F, and if so, which code page the build machine was using.- On an old Windows installation, look for
*.dbgundersymbols/andxxd -l 2one.DI. - Write
44 49on paper and read it as a little-endian and a big-endian 16-bit number. Then decide which of the two your own signature constants are, in any format you have defined.
See also¶
- PE — the image this file was split from, and the debug directory it normally carries itself
- MZ — the other two-byte text signature on this list, with the same two numbers
- The bytes do not say which end — a GUID asks the question four times in sixteen bytes
- Mojibake —
café.pdb, in a header field rather than a text file - Windows-1252 vs Latin-1 — the code page a Windows linker most likely wrote the path in
- PE Format, the debug section ↗ —
IMAGE_DEBUG_DIRECTORYand the type table