Skip to content

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

  1. Find any .pdb-bearing Windows binary and search it for RSDS: grep -c RSDS file.exe under LC_ALL=C. Dump 24 bytes after it and read the GUID with uuid.UUID(bytes_le=...).
  2. Read the same 16 bytes with bytes= instead and compare the two strings. Note which groups moved.
  3. strings -n 6 file.exe | grep '\.pdb' shows the path. Check whether it has any byte above 0x7F, and if so, which code page the build machine was using.
  4. On an old Windows installation, look for *.dbg under symbols/ and xxd -l 2 one. DI.
  5. Write 44 49 on 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
  • Mojibakecafé.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_DIRECTORY and the type table