1 /// Copyright: Copyright (c) 2017-2019 Andrey Penechko.
2 /// License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
3 /// Authors: Andrey Penechko.
4 
5 /// .pdb reader and writer
6 /// PDB docs http://llvm.org/docs/PDB/index.html
7 /// MSF docs http://llvm.org/docs/PDB/MsfFile.html
8 /// CodeView http://pierrelib.pagesperso-orange.fr/exec_formats/MS_Symbol_Type_v1.0.pdf
9 /// https://github.com/mountainstorm/pdbfile
10 /// https://github.com/willglynn/pdb
11 /// https://github.com/smx-smx/PDBSharp
12 
13 /// todo:
14 ///   - S_BPREL32
15 ///   - C13 DEBUG SUBSECTIONS: fileChecksums, line numbers, GlobalRefs
16 ///   - TPI hash stream, IPI hash stream
17 module vox.be.debug_info.pdb;
18 
19 public import vox.be.debug_info.pdb.symbol;
20 public import vox.be.debug_info.pdb.codeview;
21 
22 import std.bitmanip : bitfields;
23 import std.file;
24 import std.format : formattedWrite;
25 import std.path;
26 import std.range : repeat;
27 import std.string : fromStringz;
28 
29 import vox.utils;
30 
31 /*
32 pdb format
33 
34 MSF - Multistream File
35 
36 file consists of pages of size 512, 1024, 2048 or 4096
37 Meta-stream stores information about all streams in .pdb file. It has following format:
38 	uint numStreams
39 	uint[numStreams] streamSizes
40 	uint[][numStreams] streamPages
41 	Note: some streams produced by MSVC have uint.max size. We interpret that as 0 size.
42 	It is stored in a list of pages.
43 	Array of indices to those pages are stored in a page `metaStreamHeaderPage`
44 Free page bitmap is a page that stores a bit per page. 1 if page is free, 0 if not.
45 Page map...
46 
47 Page 0
48 	MSF magic
49 	MSF header
50 Page 1  \ freePageBitmapIndex is 1 or 2
51 Page 2  /
52 Page metaStreamHeaderPage
53 ...
54 Page
55 */
56 
57 struct PdbInfo
58 {
59 	string filename;
60 	ubyte[] fileData;
61 	MsfHeader msfHeader;
62 
63 	// Those are valid if != 0
64 	uint linkInfoStreamIndex; // /LinkInfo
65 	uint headerblockStreamIndex; // /src/headerblock
66 	uint namesStreamIndex; // /names
67 	uint globalStreamIndex;
68 	uint publicStreamIndex;
69 	uint symRecordStream;
70 
71 	StreamInfo[] streamInfos;
72 	void setStreamName(uint index, string name)
73 	{
74 		if (index == ushort.max) return; // this index is not valid
75 		enforce(index < streamInfos.length);
76 		enforce(streamInfos[index].name is null,
77 			format("stream %s, already has name '%s', trying to set to '%s'",
78 				index, streamInfos[index].name, name));
79 		streamInfos[index].name = name;
80 	}
81 
82 	void printStreamInfos()
83 	{
84 		foreach(i, ref info; streamInfos)
85 		{
86 			writefln("%s %s %s bytes", i, info.name, info.streamBytes);
87 		}
88 	}
89 }
90 
91 struct StreamInfo
92 {
93 	string name;
94 	uint streamBytes;
95 }
96 
97 struct PdbReader
98 {
99 	PdbInfo pdb;
100 	string filename;
101 	ubyte[] fileData;
102 
103 	MsfHeader* msfHeader;
104 	string[100] longestNames;
105 
106 	void visitString(string str)
107 	{
108 		foreach(ref longStr; longestNames)
109 			if (str.length > longStr.length)
110 				swap(longStr, str);
111 	}
112 
113 	static PdbReader fromFile(string filename)
114 	{
115 		enforce(exists(filename), format("%s does not exist", filename));
116 		enforce(extension(filename) == ".pdb", format("%s must have .pdb extension", filename));
117 
118 		ubyte[] fileData = cast(ubyte[])std.file.read(filename);
119 		return fromBytes(fileData, filename);
120 	}
121 
122 	static PdbReader fromBytes(ubyte[] fileData, string filename = null)
123 	{
124 		PdbReader reader;
125 		reader.filename = filename;
126 		reader.fileData = fileData;
127 		reader.load();
128 		return reader;
129 	}
130 
131 	/// Performs actual processing of .pdb data stored in fileData
132 	void load()
133 	{
134 		writefln("Load %s %s bytes", filename, fileData.length);
135 
136 		auto slicer = FileDataSlicer(fileData);
137 
138 		// MSF Magic
139 		enforce(slicer.fileData.length >= MsfMagic.length, format("%s: Error: Invalid PDB file, MSF magic is truncated", filename));
140 		ubyte[] msfMagic = slicer.getArrayOf!ubyte(MsfMagic.length);
141 		enforce(msfMagic == MsfMagic, "Invalid magic");
142 
143 		// MSF Header
144 		pdb.msfHeader = *slicer.getPtrTo!MsfHeader;
145 		pdb.msfHeader.validate;
146 		enforce(pdb.msfHeader.numPages * pdb.msfHeader.pageSize == fileData.length,
147 			format("Invalid file size (%s) != num pages (%s) * page size (%s)",
148 				fileData.length, pdb.msfHeader.numPages, pdb.msfHeader.pageSize));
149 
150 		writeln(pdb.msfHeader);
151 
152 		// Meta stream contains info about all streams
153 		slicer.fileCursor = pdb.msfHeader.metaStreamHeaderPage * pdb.msfHeader.pageSize;
154 		writefln("meta stream header page %s, offset 0x%X", pdb.msfHeader.metaStreamHeaderPage, slicer.fileCursor);
155 
156 		uint[] metaStreamPagesIndices = slicer.getArrayOf!uint(pdb.msfHeader.numMetaStreamPages);
157 		writefln("meta stream pages %s", metaStreamPagesIndices);
158 
159 		StreamReader metaStream;
160 		metaStream.fileData = fileData;
161 		metaStream.streamBytes = pdb.msfHeader.metaStreamBytes;
162 		metaStream.pageSize = pdb.msfHeader.pageSize;
163 		metaStream.pages = metaStreamPagesIndices;
164 
165 		uint numStreams = metaStream.read!uint;
166 		writefln("Number of streams %s", numStreams);
167 
168 		pdb.streamInfos = new StreamInfo[numStreams];
169 		pdb.setStreamName(FixedStream.old_directory, "Old directory");
170 		pdb.setStreamName(FixedStream.pdb_stream, "PDB stream");
171 		pdb.setStreamName(FixedStream.tpi_stream, "TPI stream");
172 		pdb.setStreamName(FixedStream.dbi_stream, "DBI stream");
173 		pdb.setStreamName(FixedStream.ipi_stream, "IPI stream");
174 
175 		StreamReader[] streams = new StreamReader[numStreams];
176 		foreach(i, ref stream; streams)
177 		{
178 			stream.fileData = fileData;
179 			stream.streamBytes = metaStream.read!uint;
180 			pdb.streamInfos[i].streamBytes = stream.streamBytes;
181 			if (stream.streamBytes == uint.max) stream.streamBytes = 0;
182 
183 			stream.pageSize = pdb.msfHeader.pageSize;
184 		}
185 
186 		foreach(streamIndex, ref stream; streams)
187 		{
188 			uint numPages = divCeil(stream.streamBytes, stream.pageSize);
189 			stream.pages = new uint[numPages];
190 			metaStream.readIntoArray(stream.pages);
191 			foreach(page; stream.pages)
192 			{
193 				enforce(page < pdb.msfHeader.numPages,
194 					format("Stream %s contains page %s. Number of pages is %s",
195 						streamIndex, page, pdb.msfHeader.numPages));
196 			}
197 		}
198 		writeln;
199 
200 
201 		writefln("Streams:");
202 		foreach(i, ref stream; streams)
203 		{
204 			writefln("  %s %s", i, stream);
205 		}
206 		writeln;
207 
208 
209 		writefln("#   PDB stream %s", cast(uint)FixedStream.pdb_stream);
210 		StreamReader* pdbStream = &streams[FixedStream.pdb_stream];
211 		auto pdbStreamHeader = pdbStream.read!PdbStreamHeader;
212 		writefln("PDB stream header %s", pdbStreamHeader);
213 
214 		writefln("  Named streams hashmap");
215 		uint stringBufLength = pdbStream.read!uint;
216 		string namedStreamStringBuf = cast(string)pdbStream.readArray!char(stringBufLength);
217 		writefln("    string buffer %s", namedStreamStringBuf);
218 
219 		// size == number of present keys == number of present values
220 		uint hashmapSize = pdbStream.read!uint;
221 		// capacity == number of keys + number of values
222 		uint hashmapCapacity = pdbStream.read!uint;
223 		writefln("    size %s", hashmapSize);
224 		writefln("    capacity %s", hashmapCapacity);
225 
226 		uint presentBitmapWords = pdbStream.read!uint;
227 		uint[] presentBitmap = pdbStream.readArray!uint(presentBitmapWords);
228 		writefln("    presentBitmap %s", presentBitmap);
229 
230 		uint deletedBitmapWords = pdbStream.read!uint;
231 		uint[] deletedBitmap = pdbStream.readArray!uint(deletedBitmapWords);
232 		writefln("    deletedBitmap %s", deletedBitmap);
233 
234 		writefln("    buckets:");
235 
236 		// gather named streams
237 		// iterate set bits in uint
238 		// for each bit set an entry follows
239 		foreach(size_t bucketIndex; presentBitmap.bitsSet)
240 		{
241 			uint nameBufOffset = pdbStream.read!uint;
242 			uint streamIndex = pdbStream.read!uint;
243 
244 			string name = namedStreamStringBuf[nameBufOffset..$].ptr.fromStringz;
245 			writefln("  % 4s %s", streamIndex, name);
246 
247 			switch(name)
248 			{
249 				case "/LinkInfo": pdb.linkInfoStreamIndex = streamIndex; break;
250 				case "/src/headerblock": pdb.headerblockStreamIndex = streamIndex; break;
251 				case "/names": pdb.namesStreamIndex = streamIndex; break;
252 				default: break; // ignore unknown
253 			}
254 
255 			pdb.setStreamName(streamIndex, name);
256 		}
257 
258 		uint pdbFeatures;
259 		write("  PDB features:");
260 		while(!pdbStream.empty)
261 		{
262 			uint featureCode = pdbStream.read!uint;
263 			switch(featureCode) with(Pdb_FeatureCodeMagic)
264 			{
265 				case vc110: pdbFeatures |= Pdb_FeatureFlags.vc110; write(" vc110"); break;
266 				case vc140: pdbFeatures |= Pdb_FeatureFlags.vc140; write(" vc140"); break;
267 				case noTypeMerge: pdbFeatures |= Pdb_FeatureFlags.noTypeMerge; write(" noTypeMerge"); break;
268 				case minimalDebugInfo: pdbFeatures |= Pdb_FeatureFlags.minimalDebugInfo; write(" minimalDebugInfo"); break;
269 				default:
270 					writef(" feature(0x%x)", featureCode);
271 					break; // ignore unknown feature
272 			}
273 		}
274 		writeln;
275 		writeln;
276 
277 
278 		StreamReader* tpiStream = &streams[FixedStream.tpi_stream];
279 		writefln("#   TPI stream %s, %s bytes", cast(uint)FixedStream.tpi_stream, tpiStream.remainingBytes);
280 		auto tpiStreamHeader = tpiStream.read!TpiStreamHeader;
281 		tpiStreamHeader.print;
282 		pdb.setStreamName(tpiStreamHeader.hashStreamIndex, "TPI Hash stream");
283 		pdb.setStreamName(tpiStreamHeader.hashAuxStreamIndex, "TPI Hash aux stream");
284 
285 		size_t typeIndex;
286 		while(tpiStream.remainingBytes)
287 		{
288 			auto len = tpiStream.read!ushort;
289 			auto start = tpiStream.streamCursor;
290 			auto end = start + len;
291 
292 			auto kind = tpiStream.read!ushort;
293 			writefln("- TPI:%s %s %s bytes", typeIndex, cast(TypeRecordKind)kind, len);
294 
295 			switch(kind) with(TypeRecordKind)
296 			{
297 				case LF_ARGLIST:
298 					auto argcount = tpiStream.read!uint;
299 					writefln("    number of parameters %s", argcount);
300 					foreach(index; 0..argcount)
301 					{
302 						auto arg = tpiStream.read!TypeIndex;
303 						writefln("    arg %s", arg);
304 					}
305 					break;
306 				case LF_PROCEDURE:
307 					auto proc = tpiStream.read!CVType_PROCEDURE;
308 					writefln("    return type %s", proc.returnType);
309 					writefln("    calling convention %s", proc.callConv);
310 					writefln("    number of parameters %s", proc.numParams);
311 					writefln("    arguments %s", proc.argList);
312 					break;
313 				default:
314 					tpiStream.drop(len - 2);
315 					break;
316 			}
317 
318 			auto padding = end - tpiStream.streamCursor;
319 			assert(padding < 4);
320 			if (padding) writefln("  padding %s bytes", padding);
321 
322 			// skip padding
323 			tpiStream.streamCursor = end;
324 
325 			++typeIndex;
326 		}
327 		assert(typeIndex + 0x1000 == tpiStreamHeader.typeIndexEnd);
328 		writeln;
329 
330 
331 		StreamReader* ipiStream = &streams[FixedStream.ipi_stream];
332 		writefln("#   IPI stream header %s, %s bytes", cast(uint)FixedStream.ipi_stream, ipiStream.remainingBytes);
333 		auto ipiStreamHeader = ipiStream.read!TpiStreamHeader;
334 		ipiStreamHeader.print;
335 		pdb.setStreamName(ipiStreamHeader.hashStreamIndex, "IPI Hash stream");
336 		pdb.setStreamName(ipiStreamHeader.hashAuxStreamIndex, "IPI Hash aux stream");
337 
338 		while(ipiStream.remainingBytes)
339 		{
340 			auto start = ipiStream.streamCursor;
341 			auto len = ipiStream.read!ushort;
342 			auto end = start + len + 2;
343 
344 			auto kind = ipiStream.read!ushort;
345 			writefln("- IPI:%s (%06X) length %s kind %s", typeIndex + 0x1000, start, len, cast(TypeRecordKind)kind);
346 
347 			switch(kind) with(TypeRecordKind)
348 			{
349 				case LF_FUNC_ID:
350 					auto funcId = ipiStream.read!CVType_FUNC_ID;
351 					writefln("    scope id %s", funcId.scopeId);
352 					writefln("    type %s, size %s", funcId.type, CVType_FUNC_ID.sizeof);
353 					string name = ipiStream.readNameBefore(end);
354 					visitString(name);
355 					writefln("    name `%s`", name);
356 					break;
357 
358 				case LF_MFUNC_ID:
359 					auto funcId = ipiStream.read!CVType_MFUNC_ID;
360 					writefln("    type %s", funcId.type);
361 					writefln("    parent type %s", funcId.parentType);
362 					string name = ipiStream.readNameBefore(end);
363 					visitString(name);
364 					writefln("    name `%s`", name);
365 					break;
366 
367 				case LF_STRING_ID:
368 					auto stringId = ipiStream.read!CVType_STRING_ID;
369 					writefln("    id 0x%X", stringId.id);
370 					string name = ipiStream.readNameBefore(end);
371 					visitString(name);
372 					writefln("    name `%s`", name);
373 					break;
374 
375 				case LF_UDT_SRC_LINE:
376 					auto udtSrcLine = ipiStream.read!UdtSrcLine;
377 					writefln("    type = %s, source file = %s, line = %s", udtSrcLine.udt, udtSrcLine.sourceFile, udtSrcLine.lineNumber);
378 					break;
379 
380 				case LF_SUBSTR_LIST:
381 					auto argcount = ipiStream.read!uint;
382 					writefln("    number of substrings %s", argcount);
383 					foreach(index; 0..argcount)
384 					{
385 						auto arg = ipiStream.read!TypeIndex;
386 						writefln("      substr %s", arg);
387 					}
388 					break;
389 
390 				case LF_BUILDINFO:
391 					auto buildInfo = ipiStream.read!CVType_BUILDINFO;
392 					writefln("    number of args %s", buildInfo.count);
393 					writefln("    CurrentDirectory %s", buildInfo.args[CV_BuildInfo.CurrentDirectory]);
394 					writefln("    BuildTool %s", buildInfo.args[CV_BuildInfo.BuildTool]);
395 					writefln("    SourceFile %s", buildInfo.args[CV_BuildInfo.SourceFile]);
396 					writefln("    ProgramDatabaseFile %s", buildInfo.args[CV_BuildInfo.ProgramDatabaseFile]);
397 					writefln("    CommandArguments %s", buildInfo.args[CV_BuildInfo.CommandArguments]);
398 					break;
399 
400 				default:
401 					string stringBuf2 = new char[len - 2];
402 					ipiStream.readIntoArray(cast(char[])stringBuf2);
403 					writefln("    buf `%s`", stringBuf2);
404 					break;
405 			}
406 
407 			auto padding = end - ipiStream.streamCursor;
408 			assert(padding < 4);
409 			if (padding) writefln("  padding: %s bytes", padding);
410 
411 			// skip padding
412 			ipiStream.streamCursor = end;
413 
414 			++typeIndex;
415 		}
416 		writeln;
417 
418 
419 		StreamReader* dbiStream = &streams[FixedStream.dbi_stream];
420 		auto dbiStreamHeader = dbiStream.read!DbiStreamHeader;
421 		writefln("#   DBI stream %s, %s bytes", cast(uint)FixedStream.dbi_stream, dbiStream.remainingBytes);
422 		writefln("  DbiStreamHeader %s", dbiStreamHeader);
423 
424 		pdb.globalStreamIndex = dbiStreamHeader.GlobalStreamIndex;
425 		pdb.publicStreamIndex = dbiStreamHeader.PublicStreamIndex;
426 		pdb.symRecordStream = dbiStreamHeader.SymRecordStream;
427 		pdb.setStreamName(pdb.globalStreamIndex, "Global Symbol stream");
428 		pdb.setStreamName(pdb.publicStreamIndex, "Public Symbol stream");
429 		pdb.setStreamName(pdb.symRecordStream, "Symbol record");
430 
431 		uint substreamsSize = dbiStreamHeader.ModInfoSize
432 			+ dbiStreamHeader.SectionContributionSize
433 			+ dbiStreamHeader.SectionMapSize
434 			+ dbiStreamHeader.SourceInfoSize
435 			+ dbiStreamHeader.TypeServerMapSize
436 			+ dbiStreamHeader.OptionalDbgHeaderSize
437 			+ dbiStreamHeader.ECSubstreamSize;
438 
439 		enforce(substreamsSize == dbiStream.remainingBytes,
440 			format("substream size sum (%s) != remainingBytes (%s)",
441 			substreamsSize, dbiStream.remainingBytes));
442 
443 		StreamReader dbiModInfoStream = dbiStream.substream(dbiStreamHeader.ModInfoSize);
444 		StreamReader dbiSectionContributionStream = dbiStream.substream(dbiStreamHeader.SectionContributionSize);
445 		StreamReader dbiSectionMapStream = dbiStream.substream(dbiStreamHeader.SectionMapSize);
446 		StreamReader dbiSourceInfoStream = dbiStream.substream(dbiStreamHeader.SourceInfoSize);
447 		StreamReader dbiTypeServerMapStream = dbiStream.substream(dbiStreamHeader.TypeServerMapSize);
448 		StreamReader dbiECSubstreamStream = dbiStream.substream(dbiStreamHeader.ECSubstreamSize);
449 		StreamReader dbiOptionalDbgHeaderStream = dbiStream.substream(dbiStreamHeader.OptionalDbgHeaderSize);
450 		enforce(dbiStream.remainingBytes == 0);
451 		writeln;
452 
453 
454 		writefln("##  dbiModInfo substream: %s bytes", dbiModInfoStream.remainingBytes);
455 		while(dbiModInfoStream.remainingBytes)
456 		{
457 			auto modInfoStart = dbiModInfoStream.streamCursor;
458 			auto modInfo = dbiModInfoStream.read!ModInfo;
459 			writefln("(%06X) ModInfo", modInfoStart);
460 			string moduleName = dbiModInfoStream.readZString;
461 			pdb.setStreamName(modInfo.ModuleSymStream, format("module sym stream: %s", moduleName));
462 			writefln("    moduleName %s", moduleName);
463 			string objFileName = dbiModInfoStream.readZString;
464 			writefln("    objFileName %s", objFileName);
465 			dbiModInfoStream.dropPadding(4);
466 			writefln("    % 8s module sym stream", modInfo.ModuleSymStream);
467 			writefln("    %04X Flags", modInfo.Flags);
468 			writefln("    SectionContr %s", modInfo.SectionContr);
469 			writefln("    % 8s SymByteSize", modInfo.SymByteSize);
470 			writefln("    % 8s C11ByteSize", modInfo.C11ByteSize);
471 			writefln("    % 8s C13ByteSize", modInfo.C13ByteSize);
472 			writefln("    % 8s SourceFileCount", modInfo.SourceFileCount);
473 			writefln("    % 8s SourceFileNameIndex", modInfo.SourceFileNameIndex);
474 			writefln("    % 8s PdbFilePathNameIndex", modInfo.PdbFilePathNameIndex);
475 
476 			if (modInfo.ModuleSymStream != ushort.max)
477 			{
478 				StreamReader modSymStream = streams[modInfo.ModuleSymStream];
479 				writefln("#   Module symbol stream %s: %s bytes", modInfo.ModuleSymStream, modSymStream.remainingBytes);
480 
481 				// SYMBOLS
482 				StreamReader symSubstream = modSymStream.substream(modInfo.SymByteSize);
483 				auto debugMagic = symSubstream.read!uint;
484 				enforce(debugMagic == COFF_DEBUG_SECTION_MAGIC,
485 					format("Invalid magic (%s), expected %s",
486 						debugMagic, COFF_DEBUG_SECTION_MAGIC));
487 				parseSymbols(symSubstream);
488 				enforce(symSubstream.remainingBytes == 0);
489 				writeln;
490 
491 				// C11, legacy
492 				StreamReader c11Substream = modSymStream.substream(modInfo.C11ByteSize);
493 				if (c11Substream.remainingBytes) {
494 					writefln("--- C11 DEBUG SUBSECTIONS: %s bytes ---", modInfo.C11ByteSize);
495 					printHex(c11Substream.readArray!ubyte(c11Substream.remainingBytes), 16, PrintAscii.yes);
496 				}
497 
498 				// C13
499 				StreamReader c13Substream = modSymStream.substream(modInfo.C13ByteSize);
500 				if (c13Substream.remainingBytes) {
501 					writefln("--- C13 DEBUG SUBSECTIONS: %s bytes ---", modInfo.C13ByteSize);
502 					// read C13 debug subsections
503 					while(c13Substream.remainingBytes)
504 					{
505 						// read DebugSubsectionHeader
506 						auto kind = c13Substream.read!DebugSubsectionKind;
507 						if (kind == DebugSubsectionKind.none) break; // last entry
508 						auto length = c13Substream.read!uint;
509 						writefln("  - %s: %s bytes", kind, length);
510 						printHex(c13Substream.readArray!ubyte(length), 16, PrintAscii.yes);
511 					}
512 					enforce(c13Substream.remainingBytes == 0);
513 				}
514 
515 				// GlobalRefs, unknown purpose
516 				uint GlobalRefsSize = modSymStream.read!uint;
517 				if (GlobalRefsSize) {
518 					writefln("--- GlobalRefs: %s bytes ---", GlobalRefsSize);
519 					printHex(modSymStream.readArray!ubyte(GlobalRefsSize), 16, PrintAscii.yes);
520 				}
521 
522 				enforce(modSymStream.remainingBytes == 0);
523 			}
524 			writeln;
525 		}
526 		enforce(dbiModInfoStream.remainingBytes == 0);
527 		writeln;
528 
529 
530 		writefln("##  dbiSectionContribution substream: %s bytes", dbiSectionContributionStream.remainingBytes);
531 		auto secContribVer = dbiSectionContributionStream.read!SectionContrSubstreamVersion;
532 		writefln("  Section Contr Substream Version %s", secContribVer);
533 		enforce(secContribVer == SectionContrSubstreamVersion.Ver60, "only SectionContrSubstreamVersion.Ver60 is supported");
534 		while(dbiSectionContributionStream.remainingBytes) {
535 			auto entry = dbiSectionContributionStream.read!SectionContribEntry;
536 			writefln("  %s", entry);
537 		}
538 		enforce(dbiSectionContributionStream.remainingBytes == 0);
539 		writeln;
540 
541 
542 		writefln("##  dbiSectionMap substream: %s bytes", dbiSectionMapStream.remainingBytes);
543 		auto sectionMapHeader = dbiSectionMapStream.read!SectionMapHeader;
544 		writefln("Section map header %s", sectionMapHeader);
545 		while(dbiSectionMapStream.remainingBytes) {
546 			auto entry = dbiSectionMapStream.read!SectionMapEntry;
547 			writefln("  %s", entry);
548 		}
549 		writeln;
550 
551 		//writefln("Longest string:");
552 		//import std.algorithm : uniq;
553 		//foreach(str; longestNames[].uniq) writeln(str);
554 
555 		writefln("##  dbiSourceInfo substream: %s bytes", dbiSourceInfoStream.remainingBytes);
556 		auto sourceInfoHeader = dbiSourceInfoStream.read!SourceInfoHeader;
557 		writefln("Source info %s", dbiSourceInfoStream.remainingBytes);
558 		writefln("  num modules %s", sourceInfoHeader.numModules);
559 		writefln("  num sources %s", sourceInfoHeader.numSourceFiles);
560 
561 		ushort[] modIndices = new ushort[sourceInfoHeader.numModules];
562 		dbiSourceInfoStream.readIntoArray(modIndices);
563 		writefln("  mod indices %s", modIndices);
564 
565 		ushort[] modFileCounts = new ushort[sourceInfoHeader.numModules];
566 		dbiSourceInfoStream.readIntoArray(modFileCounts);
567 		writefln("  mod file counts %s", modFileCounts);
568 
569 		uint numSourceFiles;
570 		foreach(count; modFileCounts)
571 			numSourceFiles += count;
572 		uint[] fileNameOffsets = new uint[numSourceFiles];
573 		dbiSourceInfoStream.readIntoArray(fileNameOffsets);
574 		writefln("  File name offsets %s", fileNameOffsets);
575 
576 		{
577 			writefln("  File name strings:");
578 			uint numStrings;
579 			uint stringsStart = dbiSourceInfoStream.streamCursor;
580 			while (!dbiSourceInfoStream.empty)
581 			{
582 				++numStrings;
583 				uint offset = dbiSourceInfoStream.streamCursor - stringsStart;
584 				writefln("    %s %s", offset, dbiSourceInfoStream.readZString);
585 			}
586 			writefln("  Num name strings: %s", numStrings);
587 		}
588 		dbiSourceInfoStream.dropPadding(4);
589 		enforce(dbiSourceInfoStream.remainingBytes == 0);
590 		writeln;
591 
592 
593 		writefln("##  dbiTypeServerMap substream: %s bytes", dbiTypeServerMapStream.remainingBytes);
594 		// dbiTypeServerMapStream unknown purpose
595 		writefln("##  dbiECSubstream subtream: %s bytes", dbiECSubstreamStream.remainingBytes);
596 		// dbiECSubstreamStream edit and continue in MSVC
597 
598 
599 		writefln("##  dbiOptionalDbgHeader subtream: %s bytes", dbiOptionalDbgHeaderStream.remainingBytes);
600 		ushort[11] dbgStreamArray;
601 		dbgStreamArray[] = ushort.max; // init with unknown stream id if less than 11 recors present
602 		uint numDbgStreamIndices = min(dbiOptionalDbgHeaderStream.remainingBytes / 2, 11); // we only understand 11 records, ignore if more present
603 		dbiOptionalDbgHeaderStream.readIntoArray(dbgStreamArray[0..numDbgStreamIndices]);
604 		pdb.setStreamName(dbgStreamArray[0], "FPO Data");
605 		writefln("  FPO Data: %s", dbgStreamArray[0]);
606 		pdb.setStreamName(dbgStreamArray[1], "Exception Data");
607 		writefln("  Exception Data: %s", dbgStreamArray[1]);
608 		pdb.setStreamName(dbgStreamArray[2], "Fixup Data");
609 		writefln("  Fixup Data: %s", dbgStreamArray[2]);
610 		pdb.setStreamName(dbgStreamArray[3], "Omap To Src Data");
611 		writefln("  Omap To Src Data: %s", dbgStreamArray[3]);
612 		pdb.setStreamName(dbgStreamArray[4], "Omap From Src Data");
613 		writefln("  Omap From Src Data: %s", dbgStreamArray[4]);
614 		pdb.setStreamName(dbgStreamArray[5], "Section Header Data");
615 		writefln("  Section Header Data: %s", dbgStreamArray[5]);
616 		pdb.setStreamName(dbgStreamArray[6], "Token / RID Map");
617 		writefln("  Token / RID Map: %s", dbgStreamArray[6]);
618 		pdb.setStreamName(dbgStreamArray[7], "Xdata");
619 		writefln("  Xdata: %s", dbgStreamArray[7]);
620 		pdb.setStreamName(dbgStreamArray[8], "Pdata");
621 		writefln("  Pdata: %s", dbgStreamArray[8]);
622 		pdb.setStreamName(dbgStreamArray[9], "New FPO Data");
623 		writefln("  New FPO Data: %s", dbgStreamArray[9]);
624 		pdb.setStreamName(dbgStreamArray[10], "Original Section Header Data");
625 		writefln("  Original Section Header Data: %s", dbgStreamArray[10]);
626 		writeln;
627 
628 
629 		writefln("Stream names:");
630 		pdb.printStreamInfos;
631 		writeln;
632 
633 
634 		StreamReader globalStream = streams[pdb.globalStreamIndex];
635 		writefln("#   Globals symbol stream %s: %s bytes", pdb.globalStreamIndex, globalStream.remainingBytes);
636 		readGSIHashTable(globalStream);
637 		enforce(globalStream.remainingBytes == 0);
638 		writeln;
639 
640 
641 		// Publics stream
642 		StreamReader publicStream = streams[pdb.publicStreamIndex];
643 		writefln("#   Publics sym stream %s: %s bytes", pdb.publicStreamIndex, publicStream.remainingBytes);
644 		enforce(publicStream.remainingBytes >= PublicsStreamHeader.sizeof,
645 			format("Cannot read PublicsStreamHeader from Publics stream, not enough bytes left (%s)",
646 			publicStream.remainingBytes));
647 		auto publicsHeader = publicStream.read!PublicsStreamHeader;
648 		writefln("  PublicsStreamHeader:");
649 		writefln("    symHash %s", publicsHeader.symHash);
650 		writefln("    address map bytes %s", publicsHeader.addrMap);
651 		writefln("    numThunks %s", publicsHeader.numThunks);
652 		writefln("    sizeOfThunk %s", publicsHeader.sizeOfThunk);
653 		writefln("    isectThunkTable %s", publicsHeader.isectThunkTable);
654 		writefln("    offThunkTable %s", publicsHeader.offThunkTable);
655 		writefln("    numSections %s", publicsHeader.numSections);
656 		readGSIHashTable(publicStream);
657 		uint[] addressMap = publicStream.readArray!uint(publicsHeader.addrMap / uint.sizeof);
658 		writefln("  address map %(0x%X, %)", addressMap);
659 		uint[] thunkMap = publicStream.readArray!uint(publicsHeader.numThunks);
660 		writefln("  thunk map %(0x%X, %)", thunkMap);
661 		SectionOffset[] sectionOffsets = publicStream.readArray!SectionOffset(publicsHeader.numSections);
662 		writefln("  section map:");
663 		foreach(sect; sectionOffsets) writefln("    isect 0x%04X offset 0x%08X", sect.isect, sect.offset);
664 		enforce(publicStream.remainingBytes == 0);
665 		writeln;
666 
667 
668 		StreamReader tpiHashStream = streams[tpiStreamHeader.hashStreamIndex];
669 		writefln("#   TPI hash stream %s: %s bytes", tpiStreamHeader.hashStreamIndex, tpiHashStream.remainingBytes);
670 		ubyte[] buf2 = tpiHashStream.readArray!ubyte(tpiHashStream.remainingBytes);
671 		printHex(buf2, 16, PrintAscii.yes);
672 		enforce(tpiHashStream.remainingBytes == 0, "Found bytes past the stream data");
673 		writeln;
674 
675 
676 		StreamReader ipiHashStream = streams[ipiStreamHeader.hashStreamIndex];
677 		writefln("#   IPI hash stream %s: %s bytes", ipiStreamHeader.hashStreamIndex, ipiHashStream.remainingBytes);
678 		ubyte[] buf3 = ipiHashStream.readArray!ubyte(ipiHashStream.remainingBytes);
679 		printHex(buf3, 16, PrintAscii.yes);
680 		enforce(ipiHashStream.remainingBytes == 0, "Found bytes past the stream data");
681 		writeln;
682 
683 
684 		StreamReader symRecordStream = streams[pdb.symRecordStream];
685 		writefln("#   Symbol record stream %s: %s bytes", pdb.symRecordStream, symRecordStream.remainingBytes);
686 		parseSymbols(symRecordStream);
687 		enforce(symRecordStream.remainingBytes == 0, "Found bytes past the stream data");
688 		writeln;
689 
690 		StreamReader namesStream = streams[pdb.namesStreamIndex];
691 		auto stringtableHeader = namesStream.read!StringTableHeader;
692 		writefln("#   Names stream %s: %s bytes", pdb.namesStreamIndex, namesStream.remainingBytes);
693 		writeln ("  StringTableHeader:");
694 		writefln("    signature    0x%X", stringtableHeader.signature);
695 		writefln("    hashVersion  %s", stringtableHeader.hashVersion);
696 		writefln("    byteSize     %s", stringtableHeader.byteSize);
697 		enforce(stringtableHeader.hashVersion == 1 || stringtableHeader.hashVersion == 2,
698 			format("Unsupported hash version %s", stringtableHeader.hashVersion));
699 
700 		StreamReader stringsSubstream = namesStream.substream(stringtableHeader.byteSize);
701 		writeln ("  String buffer:");
702 		size_t strOffset;
703 		size_t strIndex;
704 		while (!stringsSubstream.empty)
705 		{
706 			string str = stringsSubstream.readZString;
707 			writefln("    % 6s %08X %s", strIndex, strOffset, str);
708 			strOffset += str.length + 1;
709 			++strIndex;
710 		}
711 
712 		uint numHashSlots = namesStream.read!uint;
713 		writefln("  Num hash slots: %s", numHashSlots);
714 
715 		uint[] hashSlots = namesStream.readArray!uint(numHashSlots);
716 		writefln("  Slots: %s", hashSlots);
717 
718 		uint numNames = namesStream.read!uint;
719 		writefln("  Num names: %s", numNames);
720 
721 		enforce(namesStream.remainingBytes == 0);
722 		writeln;
723 	}
724 
725 	void readGSIHashTable(ref StreamReader stream)
726 	{
727 		auto gsiHashHeader = stream.read!GSIHashHeader;
728 		enforce(gsiHashHeader.verSignature == GSIHashHeader.init.verSignature, "GSIHashHeader.verSignature is not valid");
729 		enforce(gsiHashHeader.verHdr == GSIHashHeader.init.verHdr, "GSIHashHeader.verHdr is not valid");
730 		writefln("  hrSize %s numBuckets %s bytes %s", gsiHashHeader.hrSize, gsiHashHeader.numBuckets, stream.remainingBytes);
731 		enforce(gsiHashHeader.hrSize + gsiHashHeader.numBuckets <= stream.remainingBytes,
732 			format("Not enough bytes left in the stream to read GSI hash map (needed %s + %s, while got %s)",
733 				gsiHashHeader.hrSize, gsiHashHeader.numBuckets, stream.remainingBytes));
734 
735 		// read hash records
736 		enforce(gsiHashHeader.hrSize % PSHashRecord.sizeof == 0, format("GSIHashHeader.hrSize is not multiple of %s", PSHashRecord.sizeof));
737 		uint numHRRecords = gsiHashHeader.hrSize / PSHashRecord.sizeof;
738 		PSHashRecord[] hashRecords = stream.readArray!PSHashRecord(numHRRecords);
739 		writefln("  Hash records (%s items)", numHRRecords);
740 		foreach(record; hashRecords) {
741 			writefln("    offset: 0x%X, cref: 0x%X", record.offset, record.cref);
742 		}
743 
744 		// read hash buckets
745 		// bitmap is followed by buckets
746 		// calculate bitmap size (it depends on verSignature and verHdr, but we only support the modern version)
747 		enum IPHR_HASH = 4096;
748 		// extra 1 slot (1 bit in bitmap) is reserved for technical reasons, and with 4 byte alignment gives extra 32 bits in addition to IPHR_HASH = 4096
749 		uint bitmapBits = alignValue(IPHR_HASH + 1, 32);
750 		uint bitmapBytes = bitmapBits / 8;
751 		uint bitmapUints = bitmapBytes / uint.sizeof;
752 		uint[] hashBitmap = stream.readArray!uint(bitmapUints);
753 		enforce(stream.remainingBytes % uint.sizeof == 0, format("Space after GSI hash bitmap is not multiple of 4 (%s)", stream.remainingBytes));
754 		writefln("  Buckets: %s", stream.remainingBytes / uint.sizeof);
755 		// iterate all set bits in bitmap
756 		// there are 1 bucket per set bit after bitmap
757 		foreach(size_t bitIndex; hashBitmap.bitsSet)
758 		{
759 			uint bucket = stream.read!uint;
760 			// max 4096 buckets
761 			writefln("    % 4s 0x%08X", bitIndex, bucket);
762 		}
763 	}
764 
765 	void parseSymbols(ref StreamReader stream)
766 	{
767 		char[] indentation;
768 		int indentLevel = 0;
769 		void ind(char[] i = indentation) { // prints indentation
770 			write(i);
771 		}
772 		void indPush() {
773 			++indentLevel;
774 			indentation ~= "| ";
775 		}
776 		void indPop() {
777 			--indentLevel;
778 			indentation = indentation[0..$-2];
779 		}
780 		writefln("--- SYMBOLS %s bytes ---", stream.remainingBytes);
781 		while(stream.remainingBytes)
782 		{
783 			auto start = stream.streamCursor;
784 			auto len = stream.read!ushort;
785 			auto end = start + len + 2;
786 			SymbolKind kind = stream.read!SymbolKind;
787 
788 			switch(kind)
789 			{
790 				case SymbolKind.S_UDT:
791 					auto udtsym = stream.read!UdtSym;
792 					string name = stream.readZString;
793 					visitString(name);
794 					ind; writefln("(%06X) %s: Type %s, %s", start, kind, udtsym.type, name);
795 					break;
796 
797 				case SymbolKind.S_PUB32:
798 					auto pubsym = stream.read!PublicSym32;
799 					string name = stream.readZString;
800 					visitString(name);
801 					ind; writefln("(%06X) %s: [%04X:%08X] Flags %04b, %s", start, kind, pubsym.segment, pubsym.offset, pubsym.flags, name);
802 					break;
803 
804 				// Those start a new level of indentation
805 				// then follow arguments terminated with S_ENDARG
806 				// then other data terminated with S_END
807 				case SymbolKind.S_GPROC32:
808 				case SymbolKind.S_LPROC32:
809 				case SymbolKind.S_GPROC32_ID:
810 				case SymbolKind.S_LPROC32_ID:
811 				case SymbolKind.S_LPROC32_DPC:
812 				case SymbolKind.S_LPROC32_DPC_ID:
813 					auto procsym = stream.read!ProcSym;
814 					string name = stream.readZString;
815 					visitString(name);
816 					ind; writef("(%06X) %s: [%04X:%08X] Flags:", start, kind, procsym.segment, procsym.offset);
817 					printProcSymFlags(procsym.flags);
818 					writefln(", %s", name);
819 					ind; writefln("|        Parent %06X End %06X Next %06X", procsym.parent, procsym.end, procsym.next);
820 					ind; writefln("|        Length %s, Dbg Start %08X, Dbg End %08X, Type %s",
821 						procsym.length, procsym.dbgStart, procsym.dbgEnd, procsym.typeIndex);
822 					indPush;
823 					ind; writeln;
824 					break;
825 
826 				case SymbolKind.S_ENDARG:
827 					ind(indentation[0..$-2]); writefln("+-(%06X) %s", start, kind);
828 					break;
829 
830 				case SymbolKind.S_REGREL32:
831 					// (0000C8) S_REGREL32: rsp+00000008, Type:    T_64PVOID(0603), hInstance
832 					auto regRelative = stream.read!RegRelativeSym;
833 					string name = stream.readZString;
834 					visitString(name);
835 					ind; writefln("(%06X) %s: %s+%08X, Type %s, %s", start, kind, regRelative.register, regRelative.offset, regRelative.type, name);
836 					break;
837 
838 				case SymbolKind.S_LDATA32:
839 				case SymbolKind.S_GDATA32:
840 				case SymbolKind.S_LMANDATA:
841 				case SymbolKind.S_GMANDATA:
842 					// S_GDATA32: [0002:000236E8], Type:             0x1A60, RTInfoImpl
843 					auto dataSym = stream.read!DataSym;
844 					string name = stream.readZString;
845 					visitString(name);
846 					ind; writefln("(%06X) %s: [%04X:%08X] Type %s, %s", start, kind, dataSym.segment, dataSym.dataOffset, dataSym.type, name);
847 					break;
848 
849 				case SymbolKind.S_LTHREAD32:
850 				case SymbolKind.S_GTHREAD32:
851 					// (1FCD30) S_GTHREAD32: [0006:00000270], Type:             0x168D, binaryCondStrings
852 					auto threadData = stream.read!ThreadDataSym;
853 					string name = stream.readZString;
854 					visitString(name);
855 					ind; writefln("(%06X) %s: [%04X:%08X] Type %s, %s", start, kind, threadData.segment, threadData.dataOffset, threadData.type, name);
856 					break;
857 
858 				case SymbolKind.S_BUILDINFO:
859 					auto buildInfo = stream.read!BuildInfoSym;
860 					ind; writefln("(%06X) %s: %s", start, kind, buildInfo.buildId);
861 					break;
862 
863 				case SymbolKind.S_INLINESITE:
864 					// (0002D8)  S_INLINESITE: Parent: 000001E8, End: 00000354, Inlinee:             0x259F
865 					//     BinaryAnnotations:    CodeLengthAndCodeOffset 29 20
866 					//     BinaryAnnotation Length: 4 bytes (1 bytes padding)
867 					auto inlineSite = stream.read!InlineSiteSym;
868 					ind; writefln("(%06X) %s: Parent %06X, End %06X, Inlinee %s", start, kind, inlineSite.parent, inlineSite.end, inlineSite.inlinee);
869 
870 					// binary annotations stream
871 					StreamReader binAnnot = stream.substreamUntil(end);
872 					uint annotationsBytes = binAnnot.remainingBytes;
873 					ind; writefln("|        BinaryAnnotations: %s bytes", annotationsBytes);
874 
875 					while (!binAnnot.empty)
876 					{
877 						auto instr = cast(BinaryAnnotationsOpcode)cvReadCompressedUint(binAnnot);
878 						enforce(instr != uint.max, "Invalid value inside binary annotations");
879 						if (instr == BinaryAnnotationsOpcode.invalid) {
880 							// only happens when first byte of an instruction is 0, which is a padding at the end of data
881 							binAnnot.unread(1);
882 							break;
883 						}
884 
885 						ind; write("|          ");
886 
887 						switch (instr) with(BinaryAnnotationsOpcode)
888 						{
889 							case codeOffset:
890 								uint arg = cvReadCompressedUint(binAnnot);
891 								writefln("code offset: %s", arg);
892 								break;
893 							case changeCodeOffsetBase:
894 								uint arg = cvReadCompressedUint(binAnnot);
895 								writefln("segment number: %s", arg);
896 								break;
897 							case changeCodeOffset:
898 								uint arg = cvReadCompressedUint(binAnnot);
899 								writefln("change code offset: %s (delta)", arg);
900 								break;
901 							case changeCodeLength:
902 								uint arg = cvReadCompressedUint(binAnnot);
903 								writefln("change code length: %s", arg);
904 								break;
905 							case changeFile:
906 								uint arg = cvReadCompressedUint(binAnnot);
907 								writefln("change file: fileId %s", arg);
908 								break;
909 							case changeLineOffset:
910 								uint arg = cvDecodeSignedInt32(cvReadCompressedUint(binAnnot));
911 								writefln("change line offset: %s (signed)", arg);
912 								break;
913 							case changeLineEndDelta:
914 								uint arg = cvReadCompressedUint(binAnnot);
915 								writefln("change line end: %s (delta)", arg);
916 								break;
917 							case changeRangeKind:
918 								uint arg = cvReadCompressedUint(binAnnot);
919 								writef("change range kind to %s", arg);
920 								switch(arg) {
921 									case 0: writeln(" (expression)"); break;
922 									case 1: writeln(" (statement)"); break;
923 									default: writeln; break;
924 								}
925 								break;
926 							case changeColumnStart:
927 								uint arg = cvReadCompressedUint(binAnnot);
928 								writef("change column start: %s", arg);
929 								if (arg == 0) writeln(" (0 means no column info)");
930 								else writeln;
931 								break;
932 							case changeColumnEndDelta:
933 								uint arg = cvDecodeSignedInt32(cvReadCompressedUint(binAnnot));
934 								writefln("end column number delta: %s (signed)", arg);
935 								break;
936 							case changeCodeOffsetAndLineOffset:
937 								uint arg = cvReadCompressedUint(binAnnot);
938 								writefln("end column number delta: %s (signed)", arg & 0b1111);
939 								ind; writefln("|          and change code offset: %s (delta)", arg >> 4);
940 								break;
941 							case changeCodeLengthAndCodeOffset:
942 								uint arg = cvReadCompressedUint(binAnnot);
943 								writefln("change code length: %s", arg);
944 								uint arg2 = cvReadCompressedUint(binAnnot);
945 								ind; writefln("|          and change code offset: %s (delta)", arg2);
946 								break;
947 							case changeColumnEnd:
948 								uint arg = cvReadCompressedUint(binAnnot);
949 								writef("end column number: %s", arg);
950 								break;
951 							default:
952 								writefln("??? 0x%04X", cast(uint)instr); break;
953 						}
954 					}
955 					ind; writefln("|        BinaryAnnotations padding: %s bytes", binAnnot.remainingBytes);
956 
957 					indPush;
958 					break;
959 
960 				case SymbolKind.S_CONSTANT:
961 				case SymbolKind.S_MANCONSTANT: // was not observed
962 					auto con = stream.read!ConstSym;
963 					ind; writef("(%06X) %s: Type %s, Value: ", start, kind, con.type);
964 					void printNum()
965 					{
966 						if (con.value < CV_TYPE.LF_NUMERIC)
967 						{
968 							// `con.value` is a value
969 							writef("%s", con.value);
970 							return;
971 						}
972 
973 						// `con.value` is a type of value
974 						switch(cast(CV_TYPE)con.value)
975 						{
976 							case CV_TYPE.LF_CHAR: writef("(LF_CHAR) 0x%02X", stream.read!ubyte); break;
977 							case CV_TYPE.LF_SHORT: writef("(LF_SHORT) 0x%04X", stream.read!short); break;
978 							case CV_TYPE.LF_USHORT: writef("(LF_USHORT) 0x%04X", stream.read!ushort); break;
979 							case CV_TYPE.LF_LONG: writef("(LF_LONG) 0x%08X", stream.read!int); break;
980 							case CV_TYPE.LF_ULONG: writef("(LF_ULONG) 0x%08X", stream.read!uint); break;
981 							case CV_TYPE.LF_QUADWORD: writef("(LF_QUADWORD) 0x%016X", stream.read!long); break;
982 							case CV_TYPE.LF_UQUADWORD: writef("(LF_UQUADWORD) 0x%016X", stream.read!ulong); break;
983 							case CV_TYPE.LF_OCTWORD:
984 								writef("(LF_OCTWORD) [%(%02X %)]", stream.read!(ubyte[16]));
985 								break;
986 							case CV_TYPE.LF_UOCTWORD:
987 								writef("(LF_UOCTWORD) [%(%02X %)]", stream.read!(ubyte[16]));
988 								break;
989 							case CV_TYPE.LF_REAL16:
990 								writef("(LF_REAL16) 0x04X", stream.read!ushort);
991 								break;
992 							case CV_TYPE.LF_REAL32:
993 								union U {
994 									ubyte[4] bytes;
995 									float f;
996 								}
997 								U u;
998 								u.bytes = stream.read!(ubyte[4]);
999 								writef("(LF_REAL32) [%(%02X %)] %s", u.bytes, u.f);
1000 								break;
1001 							case CV_TYPE.LF_REAL64:
1002 								union U2 {
1003 									ubyte[8] bytes;
1004 									double f;
1005 								}
1006 								U2 u;
1007 								u.bytes = stream.read!(ubyte[8]);
1008 								writef("(LF_REAL64) [%(%02X %)] %s", u.bytes, u.f);
1009 								break;
1010 							case CV_TYPE.LF_REAL80:
1011 								writef("(LF_REAL80) [%(%02X %)]", stream.read!(ubyte[10]));
1012 								break;
1013 							case CV_TYPE.LF_REAL128:
1014 								writef("(LF_REAL128) [%(%02X %)]", stream.read!(ubyte[16]));
1015 								break;
1016 							case CV_TYPE.LF_REAL48:
1017 								writef("(LF_REAL48) [%(%02X %)]", stream.read!(ubyte[6]));
1018 								break;
1019 							case CV_TYPE.LF_COMPLEX32:
1020 								writef("(LF_COMPLEX32) [%(%02X %)]", stream.read!(ubyte[8]));
1021 								break;
1022 							case CV_TYPE.LF_COMPLEX64:
1023 								writef("(LF_COMPLEX64) [%(%02X %)]", stream.read!(ubyte[16]));
1024 								break;
1025 							case CV_TYPE.LF_COMPLEX80:
1026 								writef("(LF_COMPLEX80) [%(%02X %)]", stream.read!(ubyte[20]));
1027 								break;
1028 							case CV_TYPE.LF_COMPLEX128:
1029 								writef("(LF_COMPLEX128) [%(%02X %)]", stream.read!(ubyte[32]));
1030 								break;
1031 							case CV_TYPE.LF_VARSTRING:
1032 								ushort len = stream.read!ushort;
1033 								writef("(LF_VARSTRING) %s", stream.readArray!char(len));
1034 								break;
1035 							case CV_TYPE.LF_DATE:
1036 								writef("(LF_DATE) 0x%016X", stream.read!ulong); // DATE is alias of double
1037 								break;
1038 							case CV_TYPE.LF_DECIMAL:
1039 								writef("(LF_DECIMAL) [%(%02X %)]", stream.read!(ubyte[12])); // DECIMAL is 12 bytes
1040 								break;
1041 							case CV_TYPE.LF_UTF8STRING:
1042 								writef("(LF_UTF8STRING) %s", stream.readZString);
1043 								break;
1044 							default:
1045 								write("Invalid Numeric Leaf");
1046 								break;
1047 						}
1048 					}
1049 					printNum;
1050 					writefln(", %s", stream.readZString);
1051 					break;
1052 
1053 				case SymbolKind.S_LOCAL:
1054 					auto localsym = stream.read!LocalSym;
1055 					string name = stream.readZString;
1056 					visitString(name);
1057 					ind; writef("(%06X) %s: Type %s, Flags:", start, kind, localsym.typeIndex);
1058 					printLocalSymFlags(localsym.flags);
1059 					writefln(", %s", name);
1060 					break;
1061 
1062 				case SymbolKind.S_FRAMEPROC:
1063 					// (0000A0)  S_FRAMEPROC:
1064 					//           Frame size = 0x00000028 bytes
1065 					//           Pad size = 0x00000000 bytes
1066 					//           Offset of pad in frame = 0x00000000
1067 					//           Size of callee save registers = 0x00000000
1068 					//           Address of exception handler = 0000:00000000
1069 					//           Function info: invalid_pgo_counts opt_for_speed Local=rsp Param=rsp (0x00114000)
1070 					auto frameProc = stream.read!FrameProcSym;
1071 					ind; writefln("(%06X) %s:", start, kind);
1072 					ind; writefln("         Frame size = 0x%08X bytes", frameProc.totalFrameBytes);
1073 					ind; writefln("         Pad size = 0x%08X bytes", frameProc.paddingFrameBytes);
1074 					ind; writefln("         Offset of pad in frame = 0x%08X", frameProc.offsetToPadding);
1075 					ind; writefln("         Size of callee save registers = 0x%08X", frameProc.bytesOfCalleeSavedRegisters);
1076 					ind; writefln("         Address of exception handler = %04X:%08X", frameProc.sectionIdOfExceptionHandler, frameProc.offsetOfExceptionHandler);
1077 					ind; write("         Function info:");
1078 					if (frameProc.hasAlloca) write(" alloca");
1079 					if (frameProc.hasSetJmp) write(" setjmp");
1080 					if (frameProc.hasLongJmp) write(" longjmp");
1081 					if (frameProc.hasInlineAssembly) write(" inlasm");
1082 					if (frameProc.hasExceptionHandling) write(" EH");
1083 					if (frameProc.markedInline) write(" marked_inline");
1084 					if (frameProc.hasStructuredExceptionHandling) write(" SEH");
1085 					if (frameProc.naked) write(" naked");
1086 					if (frameProc.securityChecks) write(" gschecks");
1087 					if (frameProc.asynchronousExceptionHandling) write(" asyncEH");
1088 					if (frameProc.noStackOrderingForSecurityChecks) write(" gs_no_stack_ordering");
1089 					if (frameProc.inlined) write(" wasinlined");
1090 					if (frameProc.strictSecurityChecks) write(" strict_gs_check");
1091 					if (frameProc.safeBuffers) write(" safe_buffers");
1092 					if (frameProc.encodedLocalBasePointer) write(" Local=%s", frameProc.encodedLocalBasePointer);
1093 					if (frameProc.encodedParamBasePointer) write(" Param=%s", frameProc.encodedParamBasePointer);
1094 					if (frameProc.profileGuidedOptimization) write(" pgo_on");
1095 					if (frameProc.validProfileCounts) write(" valid_pgo_counts"); else write(" invalid_pgo_counts");
1096 					if (frameProc.optimizedForSpeed) write(" opt_for_speed");
1097 					if (frameProc.guardCfg) write(" guard_cfg");
1098 					if (frameProc.guardCfw) write(" guard_cfw");
1099 					writeln;
1100 					break;
1101 
1102 				case SymbolKind.S_TRAMPOLINE:
1103 					// (000184) S_TRAMPOLINE: subtype Incremental, code size = 5 bytes
1104 					//          Thunk address: [0001:00000005]
1105 					//          Thunk target:  [0001:00000010]
1106 					auto trampSym = stream.read!TrampolineSym;
1107 					ind; writefln("(%06X) %s: Subtype %s, Code size %s bytes", start, kind, trampSym.type, trampSym.size);
1108 					ind; writefln("         Thunk address [%04X:%08X]", trampSym.thunkSection, trampSym.thunkOffset);
1109 					ind; writefln("         Thunk target  [%04X:%08X]", trampSym.targetSection, trampSym.targetOffset);
1110 					ind; writeln;
1111 					break;
1112 
1113 				case SymbolKind.S_THUNK32:
1114 					// (000048) S_THUNK32: [0001:004B4984], Cb: 00000006, CommandLineToArgvW
1115 					//          Parent: 00000000, End: 00000074, Next: 00000000
1116 					auto thunk = stream.read!ThunkSym;
1117 					string name = stream.readZString;
1118 					visitString(name);
1119 					//ubyte[] variantData = stream.readArrayBefore!ubyte(end);
1120 					ind; writefln("(%06X) %s: [%04X:%08X] Length %08X, Type %s, %s", start, kind, thunk.segment, thunk.offset, thunk.length, thunk.type, name);
1121 					ind; writefln("|        Parent %06X, End %06X, Next %06X", thunk.parent, thunk.end, thunk.next);
1122 					//ind; writefln("    variant data: %(%02x %)", variantData);
1123 					indPush;
1124 					break;
1125 
1126 				case SymbolKind.S_COMPILE:
1127 					auto compilesym = stream.read!CompileSym;
1128 					string verstring = stream.readZString;
1129 					ind; writefln("(%06X) %s:", start, kind);
1130 					ind; writefln("         Language: %s", compilesym.sourceLanguage);
1131 					ind; writefln("         Target processor: %s", cast(CV_CPUType)compilesym.machine);
1132 					ind; writefln("         Floating-point precision: %s", compilesym.floatprec);
1133 					static immutable string[4] floatPackageStrings = ["hardware", "emulator", "altmath", "???"];
1134 					ind; writefln("         Floating-point package: %s", floatPackageStrings[compilesym.floatpkg]);
1135 					static immutable string[4] modelStrings = ["near", "far", "huge", "???"];
1136 					ind; writefln("         Ambient data: %s", modelStrings[compilesym.ambdata]);
1137 					ind; writefln("         Ambient code: %s", modelStrings[compilesym.ambcode]);
1138 					ind; writefln("         PCode present: %s", compilesym.pcode);
1139 					ind; writefln("         mode32: %s", compilesym.mode32);
1140 					if (compilesym.pad) {
1141 						ind; writefln("         pad: 0x%03X", compilesym.pad);
1142 					}
1143 					ind; writefln("         Compiler Version: %s", verstring);
1144 					ind; writeln;
1145 					break;
1146 
1147 				case SymbolKind.S_COMPILE2:
1148 					auto compilesym = stream.read!CompileSym2;
1149 					string verstring = stream.readZString;
1150 					ind; writefln("(%06X) %s:", start, kind);
1151 					ind; writefln("         Language: %s", compilesym.sourceLanguage);
1152 					ind; writefln("         Target processor: %s", compilesym.machine);
1153 					ind; writefln("         Compiled for edit and continue: %s", compilesym.EC);
1154 					ind; writefln("         Compiled without debugging info: %s", compilesym.NoDbgInfo);
1155 					ind; writefln("         Compiled with LTCG: %s", compilesym.LTCG);
1156 					ind; writefln("         Compiled with /bzalign: %s", compilesym.NoDataAlign);
1157 					ind; writefln("         Managed code present: %s", compilesym.ManagedPresent);
1158 					ind; writefln("         Compiled with /GS: %s", compilesym.SecurityChecks);
1159 					ind; writefln("         Compiled with /hotpatch: %s", compilesym.HotPatch);
1160 					ind; writefln("         Converted by CVTCIL: %s", compilesym.CVTCIL);
1161 					ind; writefln("         MSIL module: %s", compilesym.MSILModule);
1162 					ind; writefln("         Pad bits = 0x%04X", compilesym.padding);
1163 					ind; writefln("         Frontend Version: Major = %s, Minor = %s, Build = %s",
1164 						compilesym.verFEMajor, compilesym.verFEMinor, compilesym.verFEBuild);
1165 					ind; writefln("         Backend Version: Major = %s, Minor = %s, Build = %s",
1166 						compilesym.verMajor, compilesym.verMinor, compilesym.verBuild);
1167 					ind; writefln("         Version string: %s", verstring);
1168 					ind; writefln("         Command block: %s", verstring);
1169 					while(!stream.empty)
1170 					{
1171 						string cmdName = stream.readZString;
1172 						if (cmdName is null) break; // terminated by empty string
1173 
1174 						string cmd = stream.readZString;
1175 						ind; writefln("         %s = '%s'", cmdName, cmd);
1176 					}
1177 					ind; writeln;
1178 					break;
1179 
1180 				case SymbolKind.S_COMPILE3:
1181 					auto compilesym = stream.read!CompileSym3;
1182 					string verstring = stream.readZString;
1183 					ind; writefln("(%06X) %s:", start, kind);
1184 					ind; writefln("         Language: %s", compilesym.sourceLanguage);
1185 					ind; writefln("         Target processor: %s", compilesym.machine);
1186 					ind; writefln("         Compiled for edit and continue: %s", compilesym.EC);
1187 					ind; writefln("         Compiled without debugging info: %s", compilesym.NoDbgInfo);
1188 					ind; writefln("         Compiled with LTCG: %s", compilesym.LTCG);
1189 					ind; writefln("         Compiled with /bzalign: %s", compilesym.NoDataAlign);
1190 					ind; writefln("         Managed code present: %s", compilesym.ManagedPresent);
1191 					ind; writefln("         Compiled with /GS: %s", compilesym.SecurityChecks);
1192 					ind; writefln("         Compiled with /hotpatch: %s", compilesym.HotPatch);
1193 					ind; writefln("         Converted by CVTCIL: %s", compilesym.CVTCIL);
1194 					ind; writefln("         MSIL module: %s", compilesym.MSILModule);
1195 					ind; writefln("         Compiled with /sdl: %s", compilesym.Sdl);
1196 					ind; writefln("         Compiled with pgo: %s", compilesym.PGO);
1197 					ind; writefln("         .EXP module: %s", compilesym.Exp);
1198 					ind; writefln("         Pad bits = 0x%04X", compilesym.padding);
1199 					ind; writefln("         Frontend Version: Major = %s, Minor = %s, Build = %s, QFE = %s",
1200 						compilesym.verFEMajor, compilesym.verFEMinor, compilesym.verFEBuild, compilesym.verFEQFE);
1201 					ind; writefln("         Backend Version: Major = %s, Minor = %s, Build = %s, QFE = %s",
1202 						compilesym.verMajor, compilesym.verMinor, compilesym.verBuild, compilesym.verQFE);
1203 					ind; writefln("         Version string: %s", verstring);
1204 					ind; writeln;
1205 					break;
1206 
1207 				case SymbolKind.S_LABEL32:
1208 					auto labelSym = stream.read!LabelSym;
1209 					string name = stream.readZString;
1210 					visitString(name);
1211 					ind; writef("(%06X) %s: [%04X:%08X] Flags:",
1212 						start, kind, labelSym.segment, labelSym.offset);
1213 					printProcSymFlags(labelSym.flags);
1214 					writefln(", %s", name);
1215 					break;
1216 
1217 				case SymbolKind.S_BLOCK32:
1218 					// (000930)  S_BLOCK32: [0001:0005AE21], Cb: 00000027,
1219 					//           Parent: 00000118, End: 00000964
1220 					auto blockSym = stream.read!BlockSym;
1221 					string name = stream.readZString;
1222 					visitString(name);
1223 					ind; writefln("(%06X) %s: [%04X:%08X] Code size %08X bytes, %s",
1224 						start, kind, blockSym.codeSegment, blockSym.codeOffset, blockSym.codeSize, name);
1225 					ind; writefln("|        Parent %06X, End %06X", blockSym.parent, blockSym.end);
1226 					assert(len == 22);
1227 					indPush;
1228 					break;
1229 
1230 				case SymbolKind.S_OBJNAME:
1231 					// (000004) S_OBJNAME: Signature: 00000000, * Linker *
1232 					auto objname = stream.read!ObjNameSym;
1233 					string name = stream.readZString;
1234 					visitString(name);
1235 					ind; writefln("(%06X) %s: Signature %08X, %s", start, kind, objname.signature, name);
1236 					ind; writeln;
1237 					break;
1238 
1239 				case SymbolKind.S_ENVBLOCK:
1240 					auto env = stream.read!EnvBlockSym;
1241 					ind; writefln("(%06X) %s:", start, kind);
1242 					ind; writefln("         Compiled for edit and continue: %s", env.EC);
1243 					ind; writefln("         Command block:");
1244 
1245 					// at least 4 bytes are needed for 2 non-empty strings
1246 					while(!stream.empty)
1247 					{
1248 						string cmdName = stream.readZString;
1249 						if (cmdName is null) break; // terminated by empty string
1250 
1251 						string cmd = stream.readZString;
1252 						ind; writefln("         %s = '%s'", cmdName, cmd);
1253 					}
1254 					ind; writeln;
1255 					break;
1256 
1257 				case SymbolKind.S_SECTION:
1258 					// (000198) S_SECTION: [0001], RVA = 00001000, Cb = 00001030, Align = 00001000, Characteristics = 60000020, .text
1259 					auto sectionsym = stream.read!SectionSym;
1260 					string name = stream.readZString;
1261 					visitString(name);
1262 					ind; writefln("(%06X) %s: [%04X] RVA %08X, %08X bytes, Align %08X, Char %08X, %s", start, kind,
1263 						sectionsym.section, sectionsym.rva, sectionsym.length,
1264 						1 << sectionsym.alignmentPower, sectionsym.characteristics, name);
1265 					break;
1266 
1267 				case SymbolKind.S_COFFGROUP:
1268 					// (0001B4) S_COFFGROUP: [0001:00000000], Cb: 00001030, Characteristics = 60000020, .text$mn
1269 					auto coffGroupSym = stream.read!CoffGroupSym;
1270 					string name = stream.readZString;
1271 					visitString(name);
1272 					ind; writefln("(%06X) %s: [%04X:%08X] %08X bytes, Char %08X, %s", start, kind,
1273 						coffGroupSym.symbolSegment, coffGroupSym.symbolOffset,
1274 						coffGroupSym.length, coffGroupSym.characteristics, name);
1275 					break;
1276 
1277 				case SymbolKind.S_EXPORT:
1278 					auto exportSym = stream.read!ExportSym;
1279 					string name = stream.readZString;
1280 					visitString(name);
1281 					ind; writef("(%06X) %s: Ordinal %s, Flags:", start, kind, exportSym.ordinal);
1282 					if (exportSym.isConstant) write(" isConstant");
1283 					if (exportSym.isData) write(" isData");
1284 					if (exportSym.isPrivate) write(" isPrivate");
1285 					if (exportSym.hasNoName) write(" hasNoName");
1286 					if (exportSym.hasExplicitOrdinal) write(" hasExplicitOrdinal");
1287 					if (exportSym.isForwarder) write(" isForwarder");
1288 					if (exportSym.pad) writef(" pad = 0x%X", exportSym.pad);
1289 					writefln(", %s", name);
1290 					break;
1291 
1292 				case SymbolKind.S_DEFRANGE_REGISTER:
1293 					// (000230)  S_DEFRANGE_REGISTER: rcx
1294 					//     Range: [0001:004A706C] - [0001:004A708C], 0 Gaps
1295 					auto reg = stream.read!DefRangeRegisterSym;
1296 					auto gaps = stream.readArrayBefore!LocalVariableAddrGap(end);
1297 					ind; writefln("(%06X) %s: %s", start, kind, reg.register);
1298 					ind; writef("         Range %s, %s Gaps", reg.range, gaps.length);
1299 					if (gaps.length) write(" (Start offset, Length):");
1300 					foreach(gap; gaps) writef(" (%04X, %02X)", gap.startOffset, gap.length);
1301 					writeln;
1302 					break;
1303 
1304 				case SymbolKind.S_DEFRANGE_SUBFIELD_REGISTER:
1305 					auto reg = stream.read!DefRangeSubfieldRegisterSym;
1306 					auto gaps = stream.readArrayBefore!LocalVariableAddrGap(end);
1307 					ind; writefln("(%06X) %s: offset at %04X: %s", start, kind, reg.offsetInParent, reg.register);
1308 					ind; writef("         Range %s, %s Gaps", reg.range, gaps.length);
1309 					if (gaps.length) write(" (Start offset, Length):");
1310 					foreach(gap; gaps) writef(" (%04X, %02X)", gap.startOffset, gap.length);
1311 					writeln;
1312 					break;
1313 
1314 				case SymbolKind.S_DEFRANGE_REGISTER_REL:
1315 					auto reg = stream.read!DefRangeRegisterRelSym;
1316 					ind; writefln("(%06X) %s: %s+%08X, UDT %s, Offset in parent %s", start, kind,
1317 						reg.baseReg, reg.basePointerOffset, reg.spilledUdtMember, reg.offsetInParent);
1318 					ind; writefln("         Range: %s", reg.range);
1319 					break;
1320 
1321 				case SymbolKind.S_DEFRANGE_FRAMEPOINTER_REL:
1322 					auto reg = stream.read!DefRangeFramePointerRelSym;
1323 					auto gaps = stream.readArrayBefore!LocalVariableAddrGap(end);
1324 					ind; writefln("(%06X) %s: FrameOffset: %04X ", start, kind, reg.offFramePointer);
1325 					ind; writef("         Range: %s, %s Gaps", reg.range, gaps.length);
1326 					if (gaps.length) write(" (Start offset, Length):");
1327 					foreach(gap; gaps) writef(" (%04X, %02X)", gap.startOffset, gap.length);
1328 					writeln;
1329 					break;
1330 
1331 				case SymbolKind.S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE:
1332 					auto reg = stream.read!DefRangeFramePointerRelFullScopeSym;
1333 					ind; writefln("(%06X) %s: FrameOffset: %04X", start, kind, reg.offFramePointer);
1334 					break;
1335 
1336 				case SymbolKind.S_PROCREF:
1337 				case SymbolKind.S_LPROCREF:
1338 					auto procref = stream.read!ProcRefSym;
1339 					string name = stream.readZString;
1340 					visitString(name);
1341 					ind; writefln("(%06X) %s: [%04X:%08X] Sum name %s, %s", start, kind, procref.mod,
1342 						procref.symOffset, procref.sumName, name);
1343 					break;
1344 
1345 				case SymbolKind.S_CALLERS:
1346 				case SymbolKind.S_CALLEES:
1347 				case SymbolKind.S_INLINEES:
1348 					auto funclist = stream.read!FunctionListSym;
1349 					TypeIndex[] funcs = stream.readArray!TypeIndex(funclist.numFuncs);
1350 					uint[] numInvocationsPerFunc = stream.readArrayBefore!uint(end);
1351 					ind; writefln("(%06X) %s: count %s", start, kind, funclist.numFuncs);
1352 					foreach(i, func; funcs) {
1353 						uint numInvocations = 0;
1354 						if (i < numInvocationsPerFunc.length)
1355 							numInvocations = numInvocationsPerFunc[i];
1356 						ind; writefln("         %s (%s)", func, numInvocations);
1357 					}
1358 					break;
1359 
1360 				case SymbolKind.S_FRAMECOOKIE:
1361 					auto cookie = stream.read!FrameCookieSym;
1362 					ind; writefln("(%06X) %s: %s+%08X, Type %s, Flags 0x%02X", start, kind,
1363 						cookie.reg, cookie.offset, cookie.type, cookie.flags);
1364 					break;
1365 
1366 				case SymbolKind.S_HEAPALLOCSITE:
1367 					auto heapAlloc = stream.read!HeapAllocationSiteSym;
1368 					ind; writefln("(%06X) %s: [%04X:%08X] Instr length %08X bytes, Type %s", start, kind,
1369 						heapAlloc.section, heapAlloc.offset, heapAlloc.instrBytes, heapAlloc.type);
1370 					break;
1371 
1372 				case SymbolKind.S_CALLSITEINFO:
1373 					auto callSite = stream.read!CallSiteInfoSym;
1374 					ind; writefln("(%06X) %s: [%04X:%08X] Type %s", start, kind,
1375 						callSite.section, callSite.offset, callSite.type);
1376 					break;
1377 
1378 				case SymbolKind.S_UNAMESPACE:
1379 					string name = stream.readZString;
1380 					visitString(name);
1381 					ind; writefln("(%06X) %s: %s", start, kind, name);
1382 					break;
1383 
1384 				// terminates S_*PROC*, S_THUNK32, S_BLOCK32
1385 				case SymbolKind.S_END:
1386 				// terminates S_INLINESITE
1387 				case SymbolKind.S_INLINESITE_END:
1388 					enforce(indentLevel > 0, format("%s found when depth is 0", kind));
1389 					indPop;
1390 					ind; writefln("`-(%06X) %s", start, kind);
1391 					if (indentLevel == 0) {
1392 						ind; writeln;
1393 					}
1394 					break;
1395 
1396 				case SymbolKind.S_FILESTATIC:
1397 					auto fileStatic = stream.read!FileStaticSym;
1398 					string name = stream.readZString;
1399 					ind; writef("(%06X) %s: Mod name offset %08X, Type %s, Flags:", start, kind,
1400 						fileStatic.modOffset, fileStatic.type);
1401 					printLocalSymFlags(fileStatic.flags);
1402 					writefln(", %s", name);
1403 					break;
1404 
1405 				default:
1406 					string stringBuf2 = new char[len - 2];
1407 					stream.readIntoArray(cast(char[])stringBuf2);
1408 					ind; writefln("(%06X) %s: UNKNOWN `%s`", start, kind, stringBuf2);
1409 					printHex(cast(ubyte[])stringBuf2, 16, PrintAscii.yes, indentation, 9);
1410 			}
1411 
1412 			auto padding = end - stream.streamCursor;
1413 			if (padding >= 4) {
1414 				ind; writefln("         padding: %s bytes", padding);
1415 			}
1416 
1417 			// skip padding
1418 			stream.streamCursor = end;
1419 		}
1420 		enforce(indentLevel == 0, format("Wrong nesting detected, depth is %s", indentLevel));
1421 	}
1422 }
1423 
1424 enum FixedStream
1425 {
1426 	old_directory,
1427 	pdb_stream,
1428 	tpi_stream,
1429 	dbi_stream,
1430 	ipi_stream,
1431 }
1432 
1433 struct StreamReader
1434 {
1435 	ubyte[] fileData;
1436 	uint[] pages;
1437 	uint pageSize;
1438 	uint streamBytes;
1439 	uint streamCursor = 0;
1440 
1441 	void readIntoArray(T)(T[] buf)
1442 	{
1443 		ubyte[] byteBuf = cast(ubyte[])buf;
1444 		while(byteBuf.length > 0)
1445 		{
1446 			size_t pageArrayIndex = streamCursor / pageSize;
1447 			enforce(pageArrayIndex < pages.length, "Reading past last page");
1448 			size_t page = pages[pageArrayIndex];
1449 			size_t pageOffset = streamCursor % pageSize;
1450 			size_t pageFrom = page * pageSize;
1451 			size_t pageTo = pageFrom + pageSize;
1452 			pageFrom += pageOffset;
1453 			ubyte[] pageData = fileData[pageFrom..pageTo];
1454 			size_t pageBytesToRead = min(pageData.length, byteBuf.length);
1455 			//writefln("cur %s page %s pageOffset %s read %s", streamCursor, page, pageOffset, pageBytesToRead);
1456 			//printHex(pageData[0..pageBytesToRead], 16, PrintAscii.yes);
1457 			enforce(streamCursor + pageBytesToRead <= streamBytes,
1458 				format("attempt to read past the end of stream, %s %s %s",
1459 					streamCursor, pageBytesToRead, streamBytes));
1460 			byteBuf[0..pageBytesToRead] = pageData[0..pageBytesToRead];
1461 			byteBuf = byteBuf[pageBytesToRead..$];
1462 			streamCursor += pageBytesToRead;
1463 		}
1464 	}
1465 
1466 	T[] readArrayBefore(T)(uint cursorAfterArray)
1467 	{
1468 		uint numBytes = cursorAfterArray - streamCursor;
1469 		enforce(numBytes % T.sizeof == 0,
1470 			format("readArrayBefore: %s is not multiple of %s.sizeof (%s)",
1471 				numBytes, T.stringof, T.sizeof));
1472 		uint numItems = numBytes / T.sizeof;
1473 		return readArray!T(numItems);
1474 	}
1475 
1476 	T[] readArray(T)(uint size)
1477 	{
1478 		auto buf = new T[size];
1479 		readIntoArray(buf);
1480 		return buf;
1481 	}
1482 
1483 	T read(T)()
1484 	{
1485 		ubyte[T.sizeof] buf;
1486 		readIntoArray(buf);
1487 		return *cast(T*)buf.ptr;
1488 	}
1489 
1490 	void unread(uint numBytes)
1491 	{
1492 		streamCursor -= numBytes;
1493 	}
1494 
1495 	/// Creates stream reader that contains `byteSize` bytes since current cursor.
1496 	/// Advances cursor of current stream.
1497 	StreamReader substream(uint byteSize)
1498 	{
1499 		StreamReader sub = this;
1500 		sub.streamBytes = streamCursor + byteSize;
1501 		drop(byteSize);
1502 		return sub;
1503 	}
1504 
1505 	StreamReader substreamUntil(uint end)
1506 	{
1507 		StreamReader sub = this;
1508 		sub.streamBytes = end;
1509 		assert(end >= streamCursor);
1510 		assert(end <= streamBytes);
1511 		uint byteSize = end - streamCursor;
1512 		drop(byteSize);
1513 		return sub;
1514 	}
1515 
1516 	void drop(size_t bytesToDrop)
1517 	{
1518 		enforce(streamCursor + bytesToDrop <= streamBytes,
1519 				format("attempt to drop past the end of stream, (stream cursor %s, bytes to drop %s, bytes left %s)",
1520 					streamCursor, bytesToDrop, remainingBytes));
1521 		streamCursor += bytesToDrop;
1522 	}
1523 
1524 	uint remainingBytes() {
1525 		return streamBytes - streamCursor;
1526 	}
1527 
1528 	bool empty() {
1529 		return remainingBytes == 0;
1530 	}
1531 
1532 	void toString(scope void delegate(const(char)[]) sink)
1533 	{
1534 		sink.formattedWrite("pages %s, size %s, cursor %s", pages, streamBytes, streamCursor);
1535 	}
1536 
1537 	// reads zero-terminated string of unknown length including zero char. Returns string without zero char.
1538 	string readZString()
1539 	{
1540 		uint cursorCopy = streamCursor;
1541 		while (true)
1542 		{
1543 			char[1] buf;
1544 			readIntoArray(buf);
1545 			char c = buf[0];
1546 
1547 			if (c == '\0')
1548 			{
1549 				uint nameLength = streamCursor - cursorCopy - 1;
1550 				streamCursor = cursorCopy; // restore cursor
1551 				string str = new char[nameLength];
1552 				readIntoArray(cast(char[])str);
1553 				drop(1); // skip 0
1554 				return str;
1555 			}
1556 		}
1557 	}
1558 
1559 	string readNameBefore(uint end)
1560 	{
1561 		uint nameLength = end - streamCursor;
1562 		string name = new char[nameLength];
1563 		readIntoArray(cast(char[])name);
1564 		while (true)
1565 		{
1566 			if (name.length == 0) return null; // handle empty string
1567 			if (name[$-1] != '\0') break; // we found non-zero
1568 			name = name[0..$-1]; // peel zero terminators
1569 		}
1570 		return name;
1571 	}
1572 
1573 	string readZNameBefore(uint end)
1574 	{
1575 		uint nameLength = end - streamCursor;
1576 		string name = new char[nameLength];
1577 		readIntoArray(cast(char[])name);
1578 		//printHex(cast(ubyte[])name, 16, PrintAscii.yes);
1579 		// find zero terminator first
1580 		while (true)
1581 		{
1582 			if (name.length == 0) return null; // handle empty string
1583 			if (name[$-1] == '\0') break; // we found non-zero
1584 			name = name[0..$-1]; // peel padding
1585 		}
1586 		// find last char
1587 		while (true)
1588 		{
1589 			if (name.length == 0) return null; // handle empty string
1590 			if (name[$-1] != '\0') break; // we found non-zero
1591 			name = name[0..$-1]; // peel zero terminators
1592 		}
1593 		return name;
1594 	}
1595 
1596 	// alignment is PoT
1597 	void dropPadding(uint alignment) {
1598 		uint padding = paddingSize(streamCursor, alignment);
1599 		drop(padding);
1600 	}
1601 }
1602 
1603 ubyte[32] MsfMagic = [
1604 	0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, // Microsof
1605 	0x74, 0x20, 0x43, 0x2F, 0x43, 0x2B, 0x2B, 0x20, // t C/C++
1606 	0x4D, 0x53, 0x46, 0x20, 0x37, 0x2E, 0x30, 0x30, // MSF 7.00
1607 	0x0D, 0x0A, 0x1A, 0x44, 0x53, 0x00, 0x00, 0x00, // ...DS...
1608 ];
1609 
1610 bool isValidPageSize(uint pageSize)
1611 {
1612 	return pageSize ==  512 ||
1613 		   pageSize == 1024 ||
1614 		   pageSize == 2048 ||
1615 		   pageSize == 4096;
1616 }
1617 
1618 uint[4] validPageSizes = [512, 1024, 2048, 4096];
1619 
1620 struct MsfHeader
1621 {
1622 	uint pageSize;
1623 	uint freePageBitmapIndex;
1624 	/// Total size of MSF file is numPages * pageSize
1625 	uint numPages;
1626 	/// Length of stream that stores stream infos
1627 	uint metaStreamBytes;
1628 	uint _reserved;
1629 	uint metaStreamHeaderPage;
1630 
1631 	uint numMetaStreamPages() {
1632 		return divCeil(metaStreamBytes, pageSize);
1633 	}
1634 
1635 	void toString(scope void delegate(const(char)[]) sink)
1636 	{
1637 		sink.formattedWrite("MsfHeader:");
1638 		sink.formattedWrite("  page size 0x%X\n", pageSize);
1639 		sink.formattedWrite("  number of pages %s\n", numPages);
1640 		sink.formattedWrite("  free page bitmap index %s\n", freePageBitmapIndex);
1641 		sink.formattedWrite("  page map index %s\n", metaStreamHeaderPage);
1642 		sink.formattedWrite("  meta-stream bytes %s\n", metaStreamBytes);
1643 	}
1644 
1645 	void validate()
1646 	{
1647 		enforce(isValidPageSize(pageSize),
1648 			format("Invalid page size %s. Valid page sizes are %s",
1649 				pageSize, validPageSizes));
1650 		enforce(freePageBitmapIndex < numPages,
1651 			format("Free page map index (%s) out of bounds. Number of pages is %s",
1652 				freePageBitmapIndex, numPages));
1653 		enforce(freePageBitmapIndex == 1 || freePageBitmapIndex == 2,
1654 			format("Free page map index must be 1 or 2, not %s", freePageBitmapIndex));
1655 		enforce(metaStreamHeaderPage < numPages,
1656 			format("Page map index (%s) out of bounds. Number of pages is %s",
1657 				metaStreamHeaderPage, numPages));
1658 		enforce(metaStreamHeaderPage != 0, "Page map index cannot be located in page 0");
1659 
1660 		uint directoryBytes = cast(uint)(numMetaStreamPages * uint.sizeof);
1661 		enforce(directoryBytes <= pageSize,
1662 			format("Array of directory pages (%s pages, %s bytes) cannot fit into single page (%s bytes)",
1663 				numMetaStreamPages, directoryBytes, pageSize));
1664 	}
1665 }
1666 
1667 struct PdbStreamHeader {
1668 	uint ver;
1669 	uint signature;
1670 	uint age;
1671 	ubyte[16] guid;
1672 }
1673 
1674 enum PdbStreamVersion : uint {
1675 	VC2 = 19941610,
1676 	VC4 = 19950623,
1677 	VC41 = 19950814,
1678 	VC50 = 19960307,
1679 	VC98 = 19970604,
1680 	VC70Dep = 19990604,
1681 	VC70 = 20000404,
1682 	VC80 = 20030901,
1683 	VC110 = 20091201,
1684 	VC140 = 20140508,
1685 }
1686 
1687 enum Pdb_FeatureCodeMagic : uint {
1688 	vc110 = 20091201,
1689 	vc140 = 20140508,
1690 	noTypeMerge = 0x4D544F4E,
1691 	minimalDebugInfo = 0x494E494D,
1692 };
1693 
1694 enum Pdb_FeatureFlags : uint {
1695 	vc110 = 1 << 0,
1696 	vc140 = 1 << 1,
1697 	noTypeMerge = 1 << 2,
1698 	minimalDebugInfo = 1 << 3,
1699 };
1700 
1701 struct TpiStreamHeader {
1702 	uint ver;
1703 	uint headerSize;
1704 	uint typeIndexBegin;
1705 	uint typeIndexEnd;
1706 	uint typeRecordBytes;
1707 
1708 	ushort hashStreamIndex;
1709 	ushort hashAuxStreamIndex;
1710 	uint hashKeySize;
1711 	uint numHashBuckets;
1712 
1713 	int hashValueBufferOffset;
1714 	uint hashValueBufferLength;
1715 
1716 	int indexOffsetBufferOffset;
1717 	uint indexOffsetBufferLength;
1718 
1719 	int hashAdjBufferOffset;
1720 	uint hashAdjBufferLength;
1721 
1722 	void print()
1723 	{
1724 		writefln("  Stream header:");
1725 		writefln("  % 10s ver", ver);
1726 		writefln("  % 10s headerSize", headerSize);
1727 		writefln("  % 10s typeIndexBegin", typeIndexBegin);
1728 		writefln("  % 10s typeIndexEnd", typeIndexEnd);
1729 		writefln("  % 10s typeRecordBytes", typeRecordBytes);
1730 		writefln("  % 10s hashStreamIndex", hashStreamIndex);
1731 		writefln("  % 10s hashAuxStreamIndex", hashAuxStreamIndex);
1732 		writefln("  % 10s hashKeySize", hashKeySize);
1733 		writefln("  % 10s numHashBuckets", numHashBuckets);
1734 		writefln("  % 10s hashValueBufferOffset", hashValueBufferOffset);
1735 		writefln("  % 10s hashValueBufferLength", hashValueBufferLength);
1736 		writefln("  % 10s indexOffsetBufferOffset", indexOffsetBufferOffset);
1737 		writefln("  % 10s indexOffsetBufferLength", indexOffsetBufferLength);
1738 		writefln("  % 10s hashAdjBufferOffset", hashAdjBufferOffset);
1739 		writefln("  % 10s hashAdjBufferLength", hashAdjBufferLength);
1740 	}
1741 }
1742 
1743 struct DbiStreamHeader {
1744 	int VersionSignature;
1745 	uint VersionHeader;
1746 	uint Age;
1747 	ushort GlobalStreamIndex;
1748 	ushort BuildNumber;
1749 	ushort PublicStreamIndex;
1750 	ushort PdbDllVersion;
1751 	ushort SymRecordStream;
1752 	ushort PdbDllRbld;
1753 	int ModInfoSize;
1754 	int SectionContributionSize;
1755 	int SectionMapSize;
1756 	int SourceInfoSize;
1757 	int TypeServerMapSize;
1758 	uint MFCTypeServerIndex;
1759 	int OptionalDbgHeaderSize;
1760 	int ECSubstreamSize;
1761 	ushort Flags;
1762 	ushort Machine;
1763 	uint Padding;
1764 }
1765 static assert(DbiStreamHeader.sizeof == 64);
1766 
1767 enum COFF_DEBUG_SECTION_MAGIC = 4;
1768 
1769 enum DebugSubsectionKind : uint {
1770 	none = 0,
1771 	symbols = 0xf1,
1772 	lines = 0xf2,
1773 	stringTable = 0xf3,
1774 	fileChecksums = 0xf4,
1775 	frameData = 0xf5,
1776 	inlineeLines = 0xf6,
1777 	crossScopeImports = 0xf7,
1778 	crossScopeExports = 0xf8,
1779 	iLLines = 0xf9,
1780 	funcMDTokenMap = 0xfa,
1781 	typeMDTokenMap = 0xfb,
1782 	mergedAssemblyInput = 0xfc,
1783 	coffSymbolRVA = 0xfd,
1784 }
1785 
1786 struct DebugSubsectionHeader
1787 {
1788 	DebugSubsectionKind kind;
1789 	uint length;
1790 }
1791 
1792 struct ModInfo {
1793 	uint Unused1;
1794 	SectionContribEntry SectionContr;
1795 	ushort Flags;
1796 	ushort ModuleSymStream;
1797 	uint SymByteSize;
1798 	uint C11ByteSize;
1799 	uint C13ByteSize;
1800 	ushort SourceFileCount;
1801 	ubyte[2] Padding;
1802 	uint Unused2;
1803 	uint SourceFileNameIndex;
1804 	uint PdbFilePathNameIndex;
1805 	/// two zero-terminated strings follow. They are padded to 4 byte alignment
1806 	//char ModuleName[];
1807 	//char ObjFileName[];
1808 }
1809 static assert(ModInfo.sizeof == 64);
1810 
1811 enum SectionContrSubstreamVersion : uint {
1812 	Ver60 = 0xeffe0000 + 19970605,
1813 	V2 = 0xeffe0000 + 20140516
1814 }
1815 
1816 struct SectionContribEntry
1817 {
1818 	ushort section;
1819 	int    offset;
1820 	int    size;
1821 	uint   characteristics;
1822 	ushort moduleIndex;
1823 	uint   dataCrc;
1824 	uint   relocCrc;
1825 }
1826 static assert(SectionContribEntry.sizeof == 28);
1827 
1828 struct SectionContribution2
1829 {
1830 	SectionContribEntry sc;
1831 	uint CoffSectionIndex;
1832 }
1833 
1834 struct SectionMapHeader
1835 {
1836 	ushort count;    // Number of segment descriptors
1837 	ushort logCount; // Number of logical segment descriptors
1838 }
1839 
1840 struct SectionMapEntry
1841 {
1842 	ushort flags;         // See the SectionMapEntryFlags enum below.
1843 	ushort ovl;           // Logical overlay number
1844 	ushort group;         // Group index into descriptor array.
1845 	ushort frame;
1846 	ushort sectionName;   // Byte index of segment / group name in string table, or 0xFFFF.
1847 	ushort className;     // Byte index of class in string table, or 0xFFFF.
1848 	uint   offset;        // Byte offset of the logical segment within physical segment.  If group is set in flags, this is the offset of the group.
1849 	uint   sectionLength; // Byte count of the segment or group.
1850 }
1851 
1852 enum SectionMapEntryFlags : ushort {
1853   read = 1 << 0,              // Segment is readable.
1854   write = 1 << 1,             // Segment is writable.
1855   execute = 1 << 2,           // Segment is executable.
1856   addressIs32Bit = 1 << 3,    // Descriptor describes a 32-bit linear address.
1857   isSelector = 1 << 8,        // Frame represents a selector.
1858   isAbsoluteAddress = 1 << 9, // Frame represents an absolute address.
1859   isGroup = 1 << 10           // If set, descriptor represents a group.
1860 }
1861 
1862 struct SourceInfoHeader {
1863 	ushort numModules;
1864 	ushort numSourceFiles;
1865 
1866 	// following are variable size arrays
1867 	// ushort ModIndices[numModules];
1868 	// ushort ModFileCounts[numModules];
1869 	// uint FileNameOffsets[numSourceFiles];
1870 	// char NamesBuffer[][numSourceFiles];
1871 };
1872 
1873 enum PointerKind : ubyte {
1874 	near16 = 0x00,                // 16 bit pointer
1875 	far16 = 0x01,                 // 16:16 far pointer
1876 	huge16 = 0x02,                // 16:16 huge pointer
1877 	basedOnSegment = 0x03,        // based on segment
1878 	basedOnValue = 0x04,          // based on value of base
1879 	basedOnSegmentValue = 0x05,   // based on segment value of base
1880 	basedOnAddress = 0x06,        // based on address of base
1881 	basedOnSegmentAddress = 0x07, // based on segment address of base
1882 	basedOnType = 0x08,           // based on type
1883 	basedOnSelf = 0x09,           // based on self
1884 	near32 = 0x0a,                // 32 bit pointer
1885 	far32 = 0x0b,                 // 16:32 pointer
1886 	near64 = 0x0c                 // 64 bit pointer
1887 }
1888 enum PointerMode : ubyte {
1889 	pointer = 0x00,                 // "normal" pointer
1890 	lValueReference = 0x01,         // "old" reference
1891 	pointerToDataMember = 0x02,     // pointer to data member
1892 	pointerToMemberFunction = 0x03, // pointer to member function
1893 	rValueReference = 0x04          // r-value reference
1894 }
1895 enum PointerModifiers : ubyte {
1896 	none = 0x00,                    // "normal" pointer
1897 	flat32 = 0x01,                  // "flat" pointer
1898 	volatile = 0x02,                // pointer is marked volatile
1899 	constant = 0x04,                // pointer is marked const
1900 	unaligned = 0x08,               // pointer is marked unaligned
1901 	restrict = 0x10,                // pointer is marked restrict
1902 }
1903 enum PointerFlags : ubyte {
1904 	winRTSmartPointer = 0x01,       // pointer is a WinRT smart pointer
1905 	lValueRefThisPointer = 0x02,    // pointer is a 'this' pointer of a member function with ref qualifier (e.g. void X::foo() &)
1906 	rValueRefThisPointer = 0x04     // pointer is a 'this' pointer of a member function with ref qualifier (e.g. void X::foo() &&)
1907 }
1908 
1909 enum TypeRecordKind : ushort {
1910 	// Those are found in TPI stream
1911 	LF_POINTER          = 0x1002,
1912 	LF_MODIFIER         = 0x1001,
1913 	LF_PROCEDURE        = 0x1008,
1914 	LF_MFUNCTION        = 0x1009,
1915 	LF_LABEL            = 0x000e,
1916 
1917 	LF_ARGLIST          = 0x1201,
1918 	LF_FIELDLIST        = 0x1203,
1919 
1920 	LF_ARRAY            = 0x1503,
1921 	LF_CLASS            = 0x1504,
1922 	LF_STRUCTURE        = 0x1505,
1923 	LF_UNION            = 0x1506,
1924 	LF_ENUM             = 0x1507,
1925 	LF_TYPESERVER2      = 0x1515,
1926 
1927 	// Those are found in IPI stream
1928 	LF_FUNC_ID          = 0x1601, // global func ID
1929 	LF_MFUNC_ID         = 0x1602, // member func ID
1930 	LF_BUILDINFO        = 0x1603, // build info: tool, version, command line, src/pdb file
1931 	LF_SUBSTR_LIST      = 0x1604, // similar to LF_ARGLIST, for list of sub strings
1932 	LF_STRING_ID        = 0x1605, // string ID
1933 	LF_UDT_SRC_LINE     = 0x1606, // source and line on where an UDT is defined, only generated by compiler
1934 	LF_UDT_MOD_SRC_LINE = 0x1607, // module, source and line on where an UDT is defined, only generated by linker
1935 }
1936 
1937 /// http://llvm.org/docs/PDB/TpiStream.html#id4
1938 /// 32bit index
1939 struct TypeIndex
1940 {
1941 	union {
1942 		uint asUint;
1943 		mixin(bitfields!(
1944 			SimpleTypeKind, "kind",            8,
1945 			SimpleTypeMode, "mode",            4,
1946 			uint,            "",              19,
1947 			bool,            "isInIPIStream",  1
1948 		));
1949 	}
1950 
1951 	void toString(scope void delegate(const(char)[]) sink)
1952 	{
1953 		TypeIndex copy = this;
1954 		copy.isInIPIStream = false;
1955 		if (copy.asUint >= 0x1000)
1956 		{
1957 			if (isInIPIStream)
1958 			{
1959 				sink.formattedWrite("IPI:%s", asUint - 0x1000);
1960 			}
1961 			else
1962 			{
1963 				sink.formattedWrite("TPI:%s", asUint - 0x1000);
1964 			}
1965 		}
1966 		else
1967 		{
1968 			sink.formattedWrite("%s:%s", kind, mode);
1969 		}
1970 	}
1971 }
1972 
1973 enum SimpleTypeKind : ushort {
1974 	None = 0x0000,          // uncharacterized type (no type)
1975 	Void = 0x0003,          // void
1976 	NotTranslated = 0x0007, // type not translated by cvpack
1977 	HResult = 0x0008,       // OLE/COM HRESULT
1978 
1979 	SignedCharacter = 0x0010,   // 8 bit signed
1980 	UnsignedCharacter = 0x0020, // 8 bit unsigned
1981 	NarrowCharacter = 0x0070,   // really a char
1982 	WideCharacter = 0x0071,     // wide char
1983 	Character16 = 0x007a,       // char16_t
1984 	Character32 = 0x007b,       // char32_t
1985 
1986 	SByte = 0x0068,       // 8 bit signed int
1987 	Byte = 0x0069,        // 8 bit unsigned int
1988 	Int16Short = 0x0011,  // 16 bit signed
1989 	UInt16Short = 0x0021, // 16 bit unsigned
1990 	Int16 = 0x0072,       // 16 bit signed int
1991 	UInt16 = 0x0073,      // 16 bit unsigned int
1992 	Int32Long = 0x0012,   // 32 bit signed
1993 	UInt32Long = 0x0022,  // 32 bit unsigned
1994 	Int32 = 0x0074,       // 32 bit signed int
1995 	UInt32 = 0x0075,      // 32 bit unsigned int
1996 	Int64Quad = 0x0013,   // 64 bit signed
1997 	UInt64Quad = 0x0023,  // 64 bit unsigned
1998 	Int64 = 0x0076,       // 64 bit signed int
1999 	UInt64 = 0x0077,      // 64 bit unsigned int
2000 	Int128Oct = 0x0014,   // 128 bit signed int
2001 	UInt128Oct = 0x0024,  // 128 bit unsigned int
2002 	Int128 = 0x0078,      // 128 bit signed int
2003 	UInt128 = 0x0079,     // 128 bit unsigned int
2004 
2005 	Float16 = 0x0046,                 // 16 bit real
2006 	Float32 = 0x0040,                 // 32 bit real
2007 	Float32PartialPrecision = 0x0045, // 32 bit PP real
2008 	Float48 = 0x0044,                 // 48 bit real
2009 	Float64 = 0x0041,                 // 64 bit real
2010 	Float80 = 0x0042,                 // 80 bit real
2011 	Float128 = 0x0043,                // 128 bit real
2012 
2013 	Complex16 = 0x0056,                 // 16 bit complex
2014 	Complex32 = 0x0050,                 // 32 bit complex
2015 	Complex32PartialPrecision = 0x0055, // 32 bit PP complex
2016 	Complex48 = 0x0054,                 // 48 bit complex
2017 	Complex64 = 0x0051,                 // 64 bit complex
2018 	Complex80 = 0x0052,                 // 80 bit complex
2019 	Complex128 = 0x0053,                // 128 bit complex
2020 
2021 	Boolean8 = 0x0030,   // 8 bit boolean
2022 	Boolean16 = 0x0031,  // 16 bit boolean
2023 	Boolean32 = 0x0032,  // 32 bit boolean
2024 	Boolean64 = 0x0033,  // 64 bit boolean
2025 	Boolean128 = 0x0034, // 128 bit boolean
2026 }
2027 
2028 enum SimpleTypeMode : ubyte {
2029 	Direct = 0,        // Not a pointer
2030 	NearPointer = 1,   // Near pointer
2031 	FarPointer = 2,    // Far pointer
2032 	HugePointer = 3,   // Huge pointer
2033 	NearPointer32 = 4, // 32 bit near pointer
2034 	FarPointer32 = 5,  // 32 bit far pointer
2035 	NearPointer64 = 6, // 64 bit near pointer
2036 	NearPointer128 = 7 // 128 bit near pointer
2037 };
2038 
2039 
2040 
2041 /// Leaf record types
2042 /// 32 bit types are the same as 16 bit but have 0x1000 bit set
2043 
2044 // LF_SKIP
2045 struct CVType_SKIP
2046 {
2047 	uint type; // next valid index
2048 }
2049 
2050 // LF_ARGLIST, LF_SUBSTR_LIST
2051 struct CVType_ARGLIST
2052 {
2053 	uint count; // number of arguments
2054 	// next follow `count` type indices of type TypeIndex
2055 }
2056 
2057 // LF_DERIVED
2058 
2059 // LF_PROCEDURE
2060 struct CVType_PROCEDURE
2061 {
2062 	TypeIndex returnType;
2063 	CV_CallConv callConv;
2064 	ubyte funcAttributes;
2065 	ushort numParams;
2066 	TypeIndex argList;
2067 }
2068 
2069 
2070 
2071 // LF_FUNC_ID
2072 struct CVType_FUNC_ID
2073 {
2074 	uint scopeId;   // parent scope of the ID, 0 if global
2075 	TypeIndex type; // function type
2076 	// zero terminated string follows (name)
2077 }
2078 
2079 // LF_STRING_ID
2080 struct CVType_STRING_ID
2081 {
2082 	uint id; // ID to list of sub string IDs
2083 	// zero terminated string follows (name)
2084 }
2085 
2086 // struct lfUdtSrcLine
2087 // LF_UDT_SRC_LINE
2088 struct UdtSrcLine {
2089 	TypeIndex udt;        // UDT's type index
2090 	TypeIndex sourceFile; // index to LF_STRING_ID record where source file name is saved
2091 	uint lineNumber;      // line number
2092 }
2093 
2094 // LF_MFUNC_ID
2095 struct CVType_MFUNC_ID {
2096 	TypeIndex parentType; // type index of parent
2097 	TypeIndex type;       // function type
2098 	// zero terminated string follows (name)
2099 }
2100 
2101 
2102 // LF_BUILDINFO
2103 struct CVType_BUILDINFO
2104 {
2105 	ushort count; // number of arguments
2106 	TypeIndex[CV_BuildInfo.KNOWN] args;
2107 }
2108 
2109 enum CV_BuildInfo
2110 {
2111 	CurrentDirectory    = 0,
2112 	BuildTool           = 1, // Cl.exe
2113 	SourceFile          = 2, // foo.cpp
2114 	ProgramDatabaseFile = 3, // foo.pdb
2115 	CommandArguments    = 4, // -I etc
2116 	KNOWN
2117 }
2118 
2119 // --------------------------------------------
2120 // Symbols
2121 
2122 /// Header of the hash tables found in the globals and publics sections.
2123 // GSIHashHdr
2124 struct GSIHashHeader
2125 {
2126 	uint verSignature = 0xFFFF_FFFF;
2127 	uint verHdr = 0xeffe0000 + 19990810;
2128 	// hrSize + numBuckets == remainingBytes of the stream
2129 	uint hrSize;
2130 	uint numBuckets;
2131 }
2132 
2133 // Comes first in Publics stream
2134 // PSGSIHDR
2135 struct PublicsStreamHeader
2136 {
2137 	uint symHash;
2138 	uint addrMap;
2139 	uint numThunks;
2140 	uint sizeOfThunk;
2141 	ushort isectThunkTable;
2142 	ubyte[2] padding;
2143 	uint offThunkTable;
2144 	uint numSections;
2145 }
2146 
2147 struct PSHashRecord
2148 {
2149 	uint offset; // Offset in the symbol record stream
2150 	uint cref;
2151 }
2152 
2153 // struct SO in langapi/include/pdb.h
2154 struct SectionOffset
2155 {
2156 	uint offset;
2157 	ushort isect;
2158 	ubyte[2] pad;
2159 };
2160 
2161 
2162 // The header preceeding the /names stream.
2163 struct StringTableHeader
2164 {
2165 	uint signature = 0xEFFEEFFE;
2166 	uint hashVersion; // 1 or 2
2167 	uint byteSize;    // Number of bytes of names buffer.
2168 
2169 	uint stringTableHashString(const(char)[] str) {
2170 		if (hashVersion == 1) return stringTableHashStringV1(str);
2171 		if (hashVersion == 2) return stringTableHashStringV2(str);
2172 		assert(false);
2173 	}
2174 };
2175 
2176 // Corresponds to `Hasher::lhashPbCb` in PDB/include/misc.h.
2177 // Used for name hash table and TPI/IPI hashes.
2178 uint stringTableHashStringV1(const(char)[] str) {
2179 	uint hash = 0;
2180 	uint length = cast(uint)str.length;
2181 
2182 	uint[] uints = (cast(uint*)str.ptr)[0..str.length / uint.sizeof];
2183 	foreach (u; uints)
2184 		hash ^= u;
2185 
2186 	const(ubyte)* remainder = cast(const(ubyte)*)(uints.ptr + uints.length);
2187 	uint remainderSize = length % 4;
2188 
2189 	// Maximum of 3 bytes left.  Hash a 2 byte word if possible, then hash the
2190 	// possibly remaining 1 byte.
2191 	if (remainderSize >= 2) {
2192 		ushort value = *cast(ushort*)(remainder);
2193 		hash ^= value;
2194 		remainder += 2;
2195 		remainderSize -= 2;
2196 	}
2197 
2198 	// hash possible odd byte
2199 	if (remainderSize == 1) {
2200 		hash ^= *(remainder++);
2201 	}
2202 
2203 	immutable uint toLowerMask = 0x20202020;
2204 	hash |= toLowerMask;
2205 	hash ^= (hash >> 11);
2206 
2207 	return hash ^ (hash >> 16);
2208 }
2209 
2210 // Corresponds to `HasherV2::HashULONG` in PDB/include/misc.h.
2211 // Used for name hash table.
2212 uint stringTableHashStringV2(const(char)[] str) {
2213 	uint hash = 0xb170a1bf;
2214 
2215 	// hash whole uints from the start of the string
2216 	uint[] uints = (cast(uint*)str.ptr)[0..str.length / uint.sizeof];
2217 	foreach (uint item; uints) {
2218 		hash += item;
2219 		hash += (hash << 10);
2220 		hash ^= (hash >> 6);
2221 	}
2222 
2223 	// hash remaining bytes
2224 	const(ubyte)[] remainingBytes = cast(const(ubyte)[])str[uints.length * uint.sizeof..$];
2225 	foreach (ubyte item; remainingBytes) {
2226 		hash += item;
2227 		hash += (hash << 10);
2228 		hash ^= (hash >> 6);
2229 	}
2230 
2231 	return hash * 1664525U + 1013904223U;
2232 }