1 //-----------------------------------------------------------------------------
2 //
3 // Copyright (c) Microsoft. All rights reserved.
4 // This code is licensed under the Microsoft Public License.
5 // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
6 // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
7 // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
8 // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
9 //
10 //-----------------------------------------------------------------------------
11 using System;
12 
13 using System.Collections.Generic;
14 using System.IO;
15 using Mono.Cecil.Cil;
16 
17 namespace Microsoft.Cci.Pdb {
18   internal class PdbFile {
PdbFile()19     private PdbFile()   // This class can't be instantiated.
20     {
21     }
22 
LoadGuidStream(BitAccess bits, out Guid doctype, out Guid language, out Guid vendor)23     static void LoadGuidStream(BitAccess bits, out Guid doctype, out Guid language, out Guid vendor) {
24       bits.ReadGuid(out language);
25       bits.ReadGuid(out vendor);
26       bits.ReadGuid(out doctype);
27     }
28 
LoadNameIndex(BitAccess bits, out int age, out Guid guid)29     static Dictionary<string, int> LoadNameIndex(BitAccess bits, out int age, out Guid guid) {
30       Dictionary<string, int> result = new Dictionary<string, int>();
31       int ver;
32       int sig;
33       bits.ReadInt32(out ver);    //  0..3  Version
34       bits.ReadInt32(out sig);    //  4..7  Signature
35       bits.ReadInt32(out age);    //  8..11 Age
36       bits.ReadGuid(out guid);       // 12..27 GUID
37 
38       //if (ver != 20000404) {
39       //  throw new PdbDebugException("Unsupported PDB Stream version {0}", ver);
40       //}
41 
42       // Read string buffer.
43       int buf;
44       bits.ReadInt32(out buf);    // 28..31 Bytes of Strings
45 
46       int beg = bits.Position;
47       int nxt = bits.Position + buf;
48 
49       bits.Position = nxt;
50 
51       // Read map index.
52       int cnt;        // n+0..3 hash size.
53       int max;        // n+4..7 maximum ni.
54 
55       bits.ReadInt32(out cnt);
56       bits.ReadInt32(out max);
57 
58       BitSet present = new BitSet(bits);
59       BitSet deleted = new BitSet(bits);
60       if (!deleted.IsEmpty) {
61         throw new PdbDebugException("Unsupported PDB deleted bitset is not empty.");
62       }
63 
64       int j = 0;
65       for (int i = 0; i < max; i++) {
66         if (present.IsSet(i)) {
67           int ns;
68           int ni;
69           bits.ReadInt32(out ns);
70           bits.ReadInt32(out ni);
71 
72           string name;
73           int saved = bits.Position;
74           bits.Position = beg + ns;
75           bits.ReadCString(out name);
76           bits.Position = saved;
77 
78           result.Add(name.ToUpperInvariant(), ni);
79           j++;
80         }
81       }
82       if (j != cnt) {
83         throw new PdbDebugException("Count mismatch. ({0} != {1})", j, cnt);
84       }
85       return result;
86     }
87 
LoadNameStream(BitAccess bits)88     static IntHashTable LoadNameStream(BitAccess bits) {
89       IntHashTable ht = new IntHashTable();
90 
91       uint sig;
92       int ver;
93       bits.ReadUInt32(out sig);   //  0..3  Signature
94       bits.ReadInt32(out ver);    //  4..7  Version
95 
96       // Read (or skip) string buffer.
97       int buf;
98       bits.ReadInt32(out buf);    //  8..11 Bytes of Strings
99 
100       if (sig != 0xeffeeffe || ver != 1) {
101         throw new PdbDebugException("Unsupported Name Stream version. "+
102                                             "(sig={0:x8}, ver={1})",
103                                     sig, ver);
104       }
105       int beg = bits.Position;
106       int nxt = bits.Position + buf;
107       bits.Position = nxt;
108 
109       // Read hash table.
110       int siz;
111       bits.ReadInt32(out siz);    // n+0..3 Number of hash buckets.
112       nxt = bits.Position;
113 
114       for (int i = 0; i < siz; i++) {
115         int ni;
116         string name;
117 
118         bits.ReadInt32(out ni);
119 
120         if (ni != 0) {
121           int saved = bits.Position;
122           bits.Position = beg + ni;
123           bits.ReadCString(out name);
124           bits.Position = saved;
125 
126           ht.Add(ni, name);
127         }
128       }
129       bits.Position = nxt;
130 
131       return ht;
132     }
133 
FindFunction(PdbFunction[] funcs, ushort sec, uint off)134     private static int FindFunction(PdbFunction[] funcs, ushort sec, uint off) {
135       var match = new PdbFunction {
136         segment = sec,
137         address = off,
138       };
139 
140       return Array.BinarySearch(funcs, match, PdbFunction.byAddress);
141     }
142 
LoadManagedLines(PdbFunction[] funcs, IntHashTable names, BitAccess bits, MsfDirectory dir, Dictionary<string, int> nameIndex, PdbReader reader, uint limit)143     static void LoadManagedLines(PdbFunction[] funcs,
144                                  IntHashTable names,
145                                  BitAccess bits,
146                                  MsfDirectory dir,
147                                  Dictionary<string, int> nameIndex,
148                                  PdbReader reader,
149                                  uint limit) {
150       Array.Sort(funcs, PdbFunction.byAddressAndToken);
151 
152       int begin = bits.Position;
153       IntHashTable checks = ReadSourceFileInfo(bits, limit, names, dir, nameIndex, reader);
154 
155       // Read the lines next.
156       bits.Position = begin;
157       while (bits.Position < limit) {
158         int sig;
159         int siz;
160         bits.ReadInt32(out sig);
161         bits.ReadInt32(out siz);
162         int endSym = bits.Position + siz;
163 
164         switch ((DEBUG_S_SUBSECTION)sig) {
165           case DEBUG_S_SUBSECTION.LINES: {
166               CV_LineSection sec;
167 
168               bits.ReadUInt32(out sec.off);
169               bits.ReadUInt16(out sec.sec);
170               bits.ReadUInt16(out sec.flags);
171               bits.ReadUInt32(out sec.cod);
172               int funcIndex = FindFunction(funcs, sec.sec, sec.off);
173               if (funcIndex < 0) break;
174               var func = funcs[funcIndex];
175               if (func.lines == null) {
176                 while (funcIndex > 0) {
177                   var f = funcs[funcIndex-1];
178                   if (f.lines != null || f.segment != sec.sec || f.address != sec.off) break;
179                   func = f;
180                   funcIndex--;
181                 }
182               } else {
183                 while (funcIndex < funcs.Length-1 && func.lines != null) {
184                   var f = funcs[funcIndex+1];
185                   if (f.segment != sec.sec || f.address != sec.off) break;
186                   func = f;
187                   funcIndex++;
188                 }
189               }
190               if (func.lines != null) break;
191 
192               // Count the line blocks.
193               int begSym = bits.Position;
194               int blocks = 0;
195               while (bits.Position < endSym) {
196                 CV_SourceFile file;
197                 bits.ReadUInt32(out file.index);
198                 bits.ReadUInt32(out file.count);
199                 bits.ReadUInt32(out file.linsiz);   // Size of payload.
200                 int linsiz = (int)file.count * (8 + ((sec.flags & 1) != 0 ? 4 : 0));
201                 bits.Position += linsiz;
202                 blocks++;
203               }
204 
205               func.lines = new PdbLines[blocks];
206               int block = 0;
207 
208               bits.Position = begSym;
209               while (bits.Position < endSym) {
210                 CV_SourceFile file;
211                 bits.ReadUInt32(out file.index);
212                 bits.ReadUInt32(out file.count);
213                 bits.ReadUInt32(out file.linsiz);   // Size of payload.
214 
215                 PdbSource src = (PdbSource)checks[(int)file.index];
216                 PdbLines tmp = new PdbLines(src, file.count);
217                 func.lines[block++] = tmp;
218                 PdbLine[] lines = tmp.lines;
219 
220                 int plin = bits.Position;
221                 int pcol = bits.Position + 8 * (int)file.count;
222 
223                 for (int i = 0; i < file.count; i++) {
224                   CV_Line line;
225                   CV_Column column = new CV_Column();
226 
227                   bits.Position = plin + 8 * i;
228                   bits.ReadUInt32(out line.offset);
229                   bits.ReadUInt32(out line.flags);
230 
231                   uint lineBegin = line.flags & (uint)CV_Line_Flags.linenumStart;
232                   uint delta = (line.flags & (uint)CV_Line_Flags.deltaLineEnd) >> 24;
233                   //bool statement = ((line.flags & (uint)CV_Line_Flags.fStatement) == 0);
234                   if ((sec.flags & 1) != 0) {
235                     bits.Position = pcol + 4 * i;
236                     bits.ReadUInt16(out column.offColumnStart);
237                     bits.ReadUInt16(out column.offColumnEnd);
238                   }
239 
240                   lines[i] = new PdbLine(line.offset,
241                                          lineBegin,
242                                          column.offColumnStart,
243                                          lineBegin+delta,
244                                          column.offColumnEnd);
245                 }
246               }
247               break;
248             }
249         }
250         bits.Position = endSym;
251       }
252     }
253 
LoadFuncsFromDbiModule(BitAccess bits, DbiModuleInfo info, IntHashTable names, List<PdbFunction> funcList, bool readStrings, MsfDirectory dir, Dictionary<string, int> nameIndex, PdbReader reader)254     static void LoadFuncsFromDbiModule(BitAccess bits,
255                                        DbiModuleInfo info,
256                                        IntHashTable names,
257                                        List<PdbFunction> funcList,
258                                        bool readStrings,
259                                        MsfDirectory dir,
260                                        Dictionary<string, int> nameIndex,
261                                        PdbReader reader) {
262       PdbFunction[] funcs = null;
263 
264       bits.Position = 0;
265       int sig;
266       bits.ReadInt32(out sig);
267       if (sig != 4) {
268         throw new PdbDebugException("Invalid signature. (sig={0})", sig);
269       }
270 
271       bits.Position = 4;
272       // Console.WriteLine("{0}:", info.moduleName);
273       funcs = PdbFunction.LoadManagedFunctions(/*info.moduleName,*/
274                                                bits, (uint)info.cbSyms,
275                                                readStrings);
276       if (funcs != null) {
277         bits.Position = info.cbSyms + info.cbOldLines;
278         LoadManagedLines(funcs, names, bits, dir, nameIndex, reader,
279                          (uint)(info.cbSyms + info.cbOldLines + info.cbLines));
280 
281         for (int i = 0; i < funcs.Length; i++) {
282           funcList.Add(funcs[i]);
283         }
284       }
285     }
286 
LoadDbiStream(BitAccess bits, out DbiModuleInfo[] modules, out DbiDbgHdr header, bool readStrings)287     static void LoadDbiStream(BitAccess bits,
288                               out DbiModuleInfo[] modules,
289                               out DbiDbgHdr header,
290                               bool readStrings) {
291       DbiHeader dh = new DbiHeader(bits);
292       header = new DbiDbgHdr();
293 
294       //if (dh.sig != -1 || dh.ver != 19990903) {
295       //  throw new PdbException("Unsupported DBI Stream version, sig={0}, ver={1}",
296       //                         dh.sig, dh.ver);
297       //}
298 
299       // Read gpmod section.
300       List<DbiModuleInfo> modList = new List<DbiModuleInfo>();
301       int end = bits.Position + dh.gpmodiSize;
302       while (bits.Position < end) {
303         DbiModuleInfo mod = new DbiModuleInfo(bits, readStrings);
304         modList.Add(mod);
305       }
306       if (bits.Position != end) {
307         throw new PdbDebugException("Error reading DBI stream, pos={0} != {1}",
308                                     bits.Position, end);
309       }
310 
311       if (modList.Count > 0) {
312         modules = modList.ToArray();
313       } else {
314         modules = null;
315       }
316 
317       // Skip the Section Contribution substream.
318       bits.Position += dh.secconSize;
319 
320       // Skip the Section Map substream.
321       bits.Position += dh.secmapSize;
322 
323       // Skip the File Info substream.
324       bits.Position += dh.filinfSize;
325 
326       // Skip the TSM substream.
327       bits.Position += dh.tsmapSize;
328 
329       // Skip the EC substream.
330       bits.Position += dh.ecinfoSize;
331 
332       // Read the optional header.
333       end = bits.Position + dh.dbghdrSize;
334       if (dh.dbghdrSize > 0) {
335         header = new DbiDbgHdr(bits);
336       }
337       bits.Position = end;
338     }
339 
LoadFunctions(Stream read, out Dictionary<uint, PdbTokenLine> tokenToSourceMapping, out string sourceServerData, out int age, out Guid guid)340     internal static PdbFunction[] LoadFunctions(Stream read, out Dictionary<uint, PdbTokenLine> tokenToSourceMapping, out string sourceServerData, out int age, out Guid guid) {
341       tokenToSourceMapping = new Dictionary<uint, PdbTokenLine>();
342       BitAccess bits = new BitAccess(512 * 1024);
343       PdbFileHeader head = new PdbFileHeader(read, bits);
344       PdbReader reader = new PdbReader(read, head.pageSize);
345       MsfDirectory dir = new MsfDirectory(reader, head, bits);
346       DbiModuleInfo[] modules = null;
347       DbiDbgHdr header;
348 
349       dir.streams[1].Read(reader, bits);
350       Dictionary<string, int> nameIndex = LoadNameIndex(bits, out age, out guid);
351       int nameStream;
352       if (!nameIndex.TryGetValue("/NAMES", out nameStream)) {
353         throw new PdbException("No `name' stream");
354       }
355       dir.streams[nameStream].Read(reader, bits);
356       IntHashTable names = LoadNameStream(bits);
357 
358       int srcsrvStream;
359       if (!nameIndex.TryGetValue("SRCSRV", out srcsrvStream))
360         sourceServerData = string.Empty;
361       else {
362         DataStream dataStream = dir.streams[srcsrvStream];
363         byte[] bytes = new byte[dataStream.contentSize];
364         dataStream.Read(reader, bits);
365         sourceServerData = bits.ReadBString(bytes.Length);
366       }
367 
368       dir.streams[3].Read(reader, bits);
369       LoadDbiStream(bits, out modules, out header, true);
370 
371       List<PdbFunction> funcList = new List<PdbFunction>();
372 
373       if (modules != null) {
374         for (int m = 0; m < modules.Length; m++) {
375           var module = modules[m];
376           if (module.stream > 0) {
377             dir.streams[module.stream].Read(reader, bits);
378             if (module.moduleName == "TokenSourceLineInfo") {
379               LoadTokenToSourceInfo(bits, module, names, dir, nameIndex, reader, tokenToSourceMapping);
380               continue;
381             }
382             LoadFuncsFromDbiModule(bits, module, names, funcList, true, dir, nameIndex, reader);
383           }
384         }
385       }
386 
387       PdbFunction[] funcs = funcList.ToArray();
388 
389       // After reading the functions, apply the token remapping table if it exists.
390       if (header.snTokenRidMap != 0 && header.snTokenRidMap != 0xffff) {
391         dir.streams[header.snTokenRidMap].Read(reader, bits);
392         uint[] ridMap = new uint[dir.streams[header.snTokenRidMap].Length / 4];
393         bits.ReadUInt32(ridMap);
394 
395         foreach (PdbFunction func in funcs) {
396           func.token = 0x06000000 | ridMap[func.token & 0xffffff];
397         }
398       }
399 
400       //
401       Array.Sort(funcs, PdbFunction.byAddressAndToken);
402       //Array.Sort(funcs, PdbFunction.byToken);
403       return funcs;
404     }
405 
LoadTokenToSourceInfo(BitAccess bits, DbiModuleInfo module, IntHashTable names, MsfDirectory dir, Dictionary<string, int> nameIndex, PdbReader reader, Dictionary<uint, PdbTokenLine> tokenToSourceMapping)406     private static void LoadTokenToSourceInfo(BitAccess bits, DbiModuleInfo module, IntHashTable names, MsfDirectory dir,
407       Dictionary<string, int> nameIndex, PdbReader reader, Dictionary<uint, PdbTokenLine> tokenToSourceMapping) {
408       bits.Position = 0;
409       int sig;
410       bits.ReadInt32(out sig);
411       if (sig != 4) {
412         throw new PdbDebugException("Invalid signature. (sig={0})", sig);
413       }
414 
415       bits.Position = 4;
416 
417       while (bits.Position < module.cbSyms) {
418         ushort siz;
419         ushort rec;
420 
421         bits.ReadUInt16(out siz);
422         int star = bits.Position;
423         int stop = bits.Position + siz;
424         bits.Position = star;
425         bits.ReadUInt16(out rec);
426 
427         switch ((SYM)rec) {
428           case SYM.S_OEM:
429             OemSymbol oem;
430 
431             bits.ReadGuid(out oem.idOem);
432             bits.ReadUInt32(out oem.typind);
433             // internal byte[]   rgl;        // user data, force 4-byte alignment
434 
435             if (oem.idOem == PdbFunction.msilMetaData) {
436               string name = bits.ReadString();
437               if (name == "TSLI") {
438                 uint token;
439                 uint file_id;
440                 uint line;
441                 uint column;
442                 uint endLine;
443                 uint endColumn;
444                 bits.ReadUInt32(out token);
445                 bits.ReadUInt32(out file_id);
446                 bits.ReadUInt32(out line);
447                 bits.ReadUInt32(out column);
448                 bits.ReadUInt32(out endLine);
449                 bits.ReadUInt32(out endColumn);
450                 PdbTokenLine tokenLine;
451                 if (!tokenToSourceMapping.TryGetValue(token, out tokenLine))
452                   tokenToSourceMapping.Add(token, new PdbTokenLine(token, file_id, line, column, endLine, endColumn));
453                 else {
454                   while (tokenLine.nextLine != null) tokenLine = tokenLine.nextLine;
455                   tokenLine.nextLine = new PdbTokenLine(token, file_id, line, column, endLine, endColumn);
456                 }
457               }
458               bits.Position = stop;
459               break;
460             } else {
461               throw new PdbDebugException("OEM section: guid={0} ti={1}",
462                                           oem.idOem, oem.typind);
463               // bits.Position = stop;
464             }
465 
466           case SYM.S_END:
467             bits.Position = stop;
468             break;
469 
470           default:
471             //Console.WriteLine("{0,6}: {1:x2} {2}",
472             //                  bits.Position, rec, (SYM)rec);
473             bits.Position = stop;
474             break;
475         }
476       }
477 
478       bits.Position = module.cbSyms + module.cbOldLines;
479       int limit = module.cbSyms + module.cbOldLines + module.cbLines;
480       IntHashTable sourceFiles = ReadSourceFileInfo(bits, (uint)limit, names, dir, nameIndex, reader);
481       foreach (var tokenLine in tokenToSourceMapping.Values) {
482         tokenLine.sourceFile = (PdbSource)sourceFiles[(int)tokenLine.file_id];
483       }
484 
485     }
486 
ReadSourceFileInfo(BitAccess bits, uint limit, IntHashTable names, MsfDirectory dir, Dictionary<string, int> nameIndex, PdbReader reader)487     private static IntHashTable ReadSourceFileInfo(BitAccess bits, uint limit, IntHashTable names, MsfDirectory dir,
488       Dictionary<string, int> nameIndex, PdbReader reader) {
489       IntHashTable checks = new IntHashTable();
490 
491       int begin = bits.Position;
492       while (bits.Position < limit) {
493         int sig;
494         int siz;
495         bits.ReadInt32(out sig);
496         bits.ReadInt32(out siz);
497         int place = bits.Position;
498         int endSym = bits.Position + siz;
499 
500         switch ((DEBUG_S_SUBSECTION)sig) {
501           case DEBUG_S_SUBSECTION.FILECHKSMS:
502             while (bits.Position < endSym) {
503               CV_FileCheckSum chk;
504 
505               int ni = bits.Position - place;
506               bits.ReadUInt32(out chk.name);
507               bits.ReadUInt8(out chk.len);
508               bits.ReadUInt8(out chk.type);
509 
510               string name = (string)names[(int)chk.name];
511               int guidStream;
512               Guid doctypeGuid = DocumentType.Text.ToGuid();
513               Guid languageGuid = Guid.Empty;
514               Guid vendorGuid = Guid.Empty;
515               if (nameIndex.TryGetValue("/SRC/FILES/"+name.ToUpperInvariant(), out guidStream)) {
516                 var guidBits = new BitAccess(0x100);
517                 dir.streams[guidStream].Read(reader, guidBits);
518                 LoadGuidStream(guidBits, out doctypeGuid, out languageGuid, out vendorGuid);
519               }
520 
521               PdbSource src = new PdbSource(/*(uint)ni,*/ name, doctypeGuid, languageGuid, vendorGuid);
522               checks.Add(ni, src);
523               bits.Position += chk.len;
524               bits.Align(4);
525             }
526             bits.Position = endSym;
527             break;
528 
529           default:
530             bits.Position = endSym;
531             break;
532         }
533       }
534       return checks;
535     }
536   }
537 }
538