diff options
Diffstat (limited to 'src/ToolBox/SOS/DacTableGen/main.cs')
-rw-r--r-- | src/ToolBox/SOS/DacTableGen/main.cs | 656 |
1 files changed, 656 insertions, 0 deletions
diff --git a/src/ToolBox/SOS/DacTableGen/main.cs b/src/ToolBox/SOS/DacTableGen/main.cs new file mode 100644 index 0000000000..03d1bfde89 --- /dev/null +++ b/src/ToolBox/SOS/DacTableGen/main.cs @@ -0,0 +1,656 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; + +#if !FEATURE_PAL +using Dia; +using Dia.Util; +#endif // !FEATURE_PAL +using System.Globalization; + +/****************************************************************************** + * + *****************************************************************************/ + +public abstract class SymbolProvider +{ + public enum SymType + { + GlobalData, + GlobalFunction, + }; + + public abstract UInt32 GetGlobalRVA(String symbolName, + SymType symType); + public abstract UInt32 GetVTableRVA(String symbolName, + String keyBaseName); +} + +#if !FEATURE_PAL +public class PdbSymbolProvider : SymbolProvider +{ + public PdbSymbolProvider(String symbolFilename, String dllFilename) + { + fPDB = new FileInfo(symbolFilename); + df = new DiaFile(fPDB.FullName, dllFilename); + } + + public UInt32 DebugTimestamp + { + get { return df.DebugTimestamp; } + } + + public string LoadedPdbPath + { + get { return df.LoadedPdbPath; } + } + + private UInt32 GetSymbolRva(DiaSymbol sy, + String symbolName, + String typeName) + { + if (sy == null) + { + // Ideally this would throw an exception and + // cause the whole process to fail but + // currently it's too complicated to get + // all the ifdef'ing right for all the + // mix of debug/checked/free multiplied by + // x86/AMD64/IA64/etc. + return UInt32.MaxValue; + } + if (sy.Address > UInt32.MaxValue) + { + throw new InvalidOperationException(typeName + + " symbol " + + symbolName + + " overflows UInt32"); + } + + return (UInt32)sy.Address; + } + + private DiaSymbol GetValidPublicSymbolEntry(String name) + { + IDiaEnumSymbols e = df.FindPublicSymbols(name); + + if (e.count != 1) + { + return null; + } + else + { + IDiaSymbol s; + UInt32 celt; + + e.Next(1, out s, out celt); + return new DiaSymbol(s); + } + } + + public override UInt32 GetGlobalRVA(String symbolName, + SymType symType) + { + DiaSymbol sy = df.GlobalSymbol.FindSymbol(symbolName); + if (sy == null && symType == SymType.GlobalFunction) + { + // Try looking for the symbol in public symbols, + // as assembly routines do not have normal + // global symbol table entries. We don't know + // how many parameters to use, so just guess + // at a few sizes. + for (int i = 0; i <= 16; i += 4) + { + // Non-fastcall. + sy = GetValidPublicSymbolEntry("_" + symbolName + "@" + i); + if (sy != null) + { + break; + } + // Fastcall. + sy = GetValidPublicSymbolEntry("@" + symbolName + "@" + i); + if (sy != null) + { + break; + } + } + } + + return GetSymbolRva(sy, symbolName, "Symbol"); + } + + public override UInt32 GetVTableRVA(String symbolName, + String keyBaseName) + { + String mangledName; + + // Single-inheritance vtable symbols have different + // mangling from multiple-inheritance so form the + // proper name based on which case this is. + mangledName = "??_7" + symbolName + "@@6B"; + if (keyBaseName != null) + { + mangledName += keyBaseName + "@@@"; + } + else + { + mangledName += "@"; + } + + return GetSymbolRva(GetValidPublicSymbolEntry(mangledName), + symbolName, "VTable"); + } + + FileInfo fPDB = null; + DiaFile df = null; +} +#endif // !FEATURE_PAL + +public class Shell +{ + const String dacSwitch = "/dac:"; + const String pdbSwitch = "/pdb:"; + const String mapSwitch = "/map:"; + const String binSwitch = "/bin:"; + const String dllSwitch = "/dll:"; + const String ignoreErrorsSwitch = "/ignoreerrors"; + + public static void Help() + { + HelpHdr(); + Console.WriteLine(); + HelpBody(); + } + + public static void HelpHdr() + { +String helpHdr = + +//////////// +@"Microsoft (R) CLR External Data Access Data Table Generator Version 0.3 +Copyright (C) Microsoft Corp. All rights reserved."; +//////////// + + Console.WriteLine(helpHdr); + } + + public static void HelpBody() + { + +String helpMsg = + +//////////// +@"Usage: + DacTableGen /dac:<file> [/pdb:<file> /dll:<file>] [/map:<file>] /bin:<file> [/ignoreerrors] + +Required: + /dac: The data access header file containing items to be added. + /pdb: The PDB file from which to get details. + /map: The MAP file from which to get details. + In Windows, this file is created by providing /MAP in link.exe. + In UNIX, this file is created by the nm utility. + OBSOLETE - Use DacTableGen.pl instead for UNIX systems + /dll: The DLL which matches the specified PDB or MAP file. + /bin: The binary output file. + /ignoreerrors: Turn errors into warnings. The produced binary may hit + runtime failures +"; +//////////// + + Console.WriteLine(helpMsg); + } + + public static bool MatchArg(String arg, String cmd) + { + if (arg.Length >= cmd.Length && + arg.Substring(0, cmd.Length).ToLower(CultureInfo.InvariantCulture).Equals(cmd.ToLower(CultureInfo.InvariantCulture))) + return true; + + return false; + } + + public static int DoMain(String[] args) + { + String dacFile = null; + String pdbFile = null; + String mapFile = null; + String binFile = null; + String dllFile = null; + + for (int i = 0; i < args.Length; i++) + { + if (MatchArg(args[i], dacSwitch)) + { + dacFile = args[i].Substring(dacSwitch.Length); + } + else if (MatchArg(args[i], pdbSwitch)) + { + pdbFile = args[i].Substring(pdbSwitch.Length); + } + else if (MatchArg(args[i], mapSwitch)) + { + mapFile = args[i].Substring(mapSwitch.Length); + } + else if (MatchArg(args[i], binSwitch)) + { + binFile = args[i].Substring(binSwitch.Length); + } + else if (MatchArg(args[i], dllSwitch)) + { + dllFile = args[i].Substring(dllSwitch.Length); + } + else if (MatchArg(args[i], ignoreErrorsSwitch)) + { + s_ignoreErrors = true; + } + else + { + Help(); + return 1; + } + } + + if (dacFile == null || + (pdbFile == null && mapFile == null) || + (dllFile == null && pdbFile != null) || + binFile == null) + { + HelpHdr(); + Console.WriteLine(); + Console.WriteLine("Required option missing."); + // Provide some extra help if just the new dllFile option is missing + if ((dllFile == null) && (dacFile != null) && (binFile != null) && (pdbFile != null)) + { + Console.WriteLine("NOTE that /dll is a new required argument which must point to mscorwks.dll."); + Console.WriteLine("Ideally all uses of DacTableGen.exe should use the build logic in ndp/clr/src/DacUpdateDll."); + } + Console.WriteLine(); + HelpBody(); + + return 1; + } + + // Validate the specified files exist + string[] inputFiles = new string[] { dacFile, pdbFile, mapFile, dllFile }; + foreach (string file in inputFiles) + { + if (file != null && !File.Exists(file)) + { + Console.WriteLine("ERROR, file does not exist: " + file); + return 1; + } + } + + HelpHdr(); + Console.WriteLine(); + + List<UInt32> rvaArray = new List<UInt32>(); + UInt32 numGlobals; + UInt32 debugTimestamp = 0; + + if (pdbFile != null) + { +#if FEATURE_PAL + throw new InvalidOperationException("Not supported in Rotor."); +#else + PdbSymbolProvider pdbSymProvider = new PdbSymbolProvider(pdbFile, dllFile); + + // Read the mscorwks debug directory timestamp + debugTimestamp = pdbSymProvider.DebugTimestamp; + if (debugTimestamp == 0) + { + throw new System.ApplicationException("Didn't get debug directory timestamp from DIA"); + } + DateTime dt = new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc); + dt = dt.AddSeconds(debugTimestamp).ToLocalTime(); + + // Output information about the PDB loaded + Console.WriteLine("Processing DLL with PDB timestamp: {0}", dt.ToString("F")); + Console.WriteLine("Loaded PDB file: " + pdbSymProvider.LoadedPdbPath); + if (Path.GetFullPath(pdbSymProvider.LoadedPdbPath).ToLowerInvariant() != Path.GetFullPath(pdbFile).ToLowerInvariant()) + { + // DIA loaded a PDB oter than the one the user asked for. This could possibly happen if the PDB + // also exists in a sub-directory that DIA automatically probes for ("retail" etc.). There doesn't + // appear to be any mechanism for turning this sub-directory probing off, but all other searching mechanisms + // should be turned off by the DiaLoadCallback. This could also happen if the user specified an incorrect + // (but still existing) filename in a path containing the real PDB. Since DIA loaded it, it must match the DLL, + // and so should only be an exact copy of the requested PDB (if the requested PDB actuall matches the DLL). So + // go ahead and use it anyway with a warning. To be less confusing, we could update the command-line syntax + // to take a PDB search path instead of a filename, but that inconsistent with the map path, and probably not + // worth changing semantics for. In practice this warning will probably never be hit. + Shell.Error("Loaded PDB path differs from requested path: " + pdbFile); + } + Console.WriteLine(); + + ScanDacFile(dacFile, + pdbSymProvider, + rvaArray, + out numGlobals); + + if (mapFile != null) + { + List<UInt32> mapRvaArray = new List<UInt32>(); + UInt32 mapNumGlobals; + + // check that both map file and pdb file produce same output to avoid breakages on Rotor + ScanDacFile(dacFile, + new MapSymbolProvider(mapFile), + mapRvaArray, + out mapNumGlobals); + + // Produce a nice message to include with any errors. For some reason, binplace will silently fail + // when a PDB can't be updated due to file locking. This means that problems of this nature usually + // occur when mscorwks.pdb was locked when mscorwks.dll was last rebuilt. + string diagMsg = String.Format(". This is usually caused by mscorwks.pdb and mscorwks.map being out of sync. " + + "Was {0} (last modified {1}) in-use and locked when {2} was built (last modified {3})? " + + "Both should have been created when {4} was last rebuilt (last modified {5}).", + pdbFile, File.GetLastWriteTime(pdbFile), + mapFile, File.GetLastWriteTime(mapFile), + dllFile, File.GetLastWriteTime(dllFile)); + + if (rvaArray.Count != mapRvaArray.Count) + throw new InvalidOperationException("Number of RVAs differes between pdb file and map file: " + + numGlobals + " " + mapNumGlobals + diagMsg); + + for (int i = 0; i < rvaArray.Count; i++) + { + if (rvaArray[i] != mapRvaArray[i] + // it is ok if we find more stuff in the MAP file + && rvaArray[i] != UInt32.MaxValue) + { + throw new InvalidOperationException("RVAs differ between pdb file and map file: " + + ToHexNB(rvaArray[i]) + " " + ToHexNB(mapRvaArray[i]) + diagMsg); + } + } + + if (numGlobals != mapNumGlobals) + throw new InvalidOperationException("Number of globals differes between pdb file and map file: " + + numGlobals + " " + mapNumGlobals + diagMsg); + } +#endif + } + else + { + ScanDacFile(dacFile, + new MapSymbolProvider(mapFile), + rvaArray, + out numGlobals); + } + + if (s_errors && !s_ignoreErrors) + { + Console.Error.WriteLine( + "DacTableGen : fatal error : Failing due to above validation errors. " + + "Do you have an #ifdef (or name) mismatch between the symbol definition and the entry specified? " + + "Or perhaps the symbol referenced was optimized away as unused? " + + "If you're stuck, send e-mail to 'ClrDac'. Worst case, these errors can be temporarily ignored by passing the /ignoreerrors switch - but you may cause runtime failures instead."); + return 1; + } + + UInt32 numVptrs; + numVptrs = (UInt32)rvaArray.Count - numGlobals; + + FileStream outFile = new FileStream(binFile, FileMode.Create, + FileAccess.Write); + BinaryWriter binWrite = new BinaryWriter(outFile); + + // Write header information + binWrite.Write(numGlobals); + binWrite.Write(numVptrs); + binWrite.Write(debugTimestamp); + binWrite.Write(0); // On Windows we only need a 4-byte timestamp, but on Mac we use + binWrite.Write(0); // a 16-byte UUID. We need to be consistent here. + binWrite.Write(0); + + // Write out the table of RVAs + for (int i = 0; i < numGlobals + numVptrs; i++) + { + binWrite.Write(rvaArray[i]); + } + + binWrite.Close(); + return 0; + } + + public static int Main(string[] args) + { + // Don't catch exceptions if a debugger is attached - makes debugging easier + if (System.Diagnostics.Debugger.IsAttached) + { + return DoMain(args); + } + + int exitCode; + try + { + exitCode = DoMain(args); + } + catch(Exception e) + { + Console.WriteLine("BUILDMSG: " + e.ToString()); + exitCode = 1; + } + return exitCode; + } + + private static void ScanDacFile(String file, + SymbolProvider sf, + List<UInt32> rvaArray, + out UInt32 numGlobals) + { + StreamReader strm = + new StreamReader(file, System.Text.Encoding.ASCII); + String line; + Hashtable vtables = new Hashtable(); // hashtable to guarantee uniqueness of entries + + // + // Scan through the data access header file looking + // for the globals structure. + // + + for (;;) + { + line = strm.ReadLine(); + if (line == null) + { + throw new + InvalidOperationException("Invalid dac header format"); + } + else if (line == "typedef struct _DacGlobals") + { + break; + } + } + + if (strm.ReadLine() != "{") + { + throw new InvalidOperationException("Invalid dac header format"); + } + + // + // All the globals come first so pick up each line that + // begins with ULONG. + // + + bool fFoundVptrs = false; + numGlobals = 0; + + for (;;) + { + line = strm.ReadLine().Trim(); + + if ( line.Equals("union {") + || line.Equals("struct {") + || line.Equals("};") + || line.StartsWith("#line ") + || line.StartsWith("# ")) + { + // Ignore. + } + else if (line.StartsWith("ULONG ")) + { + UInt32 rva = 0; + + line = line.Remove(0, 6); + line = line.TrimEnd(";".ToCharArray()); + + string vptrSuffixSingle = "__vtAddr"; + string vptrSuffixMulti = "__mvtAddr"; + string vptrSuffix = null; + + if (line.EndsWith(vptrSuffixSingle)) + { + vptrSuffix = vptrSuffixSingle; + } + else if (line.EndsWith(vptrSuffixMulti)) + { + vptrSuffix = vptrSuffixMulti; + } + + if (vptrSuffix != null) + { + if (!fFoundVptrs) + { + numGlobals = (UInt32)rvaArray.Count; + fFoundVptrs = true; + } + + line = line.Remove(line.Length - vptrSuffix.Length, + vptrSuffix.Length); + + string keyBaseName = null; + string descTail = null; + + if (vptrSuffix == vptrSuffixMulti) + { + // line now has the form <class>__<base>, so + // split off the base. + int basePrefix = line.LastIndexOf("__"); + if (basePrefix < 0) + { + throw new InvalidOperationException("VPTR_MULTI_CLASS has no keyBase."); + } + keyBaseName = line.Substring(basePrefix + 2); + line = line.Remove(basePrefix); + descTail = " for " + keyBaseName; + } + + rva = sf.GetVTableRVA(line, keyBaseName); + + if (rva == UInt32.MaxValue) + { + Console.WriteLine(" " + ToHexNB(rva)); + Shell.Error("Invalid vtable " + line + descTail); + } + else + { + String existing = (String)vtables[rva]; + if (existing != null) + { + throw new InvalidOperationException(existing + " and " + line + " are at the same offsets." + + " Add VPTR_UNIQUE(<a random unique number here>) to the offending classes to make their vtables unique."); + } + vtables[rva] = line; + + Console.WriteLine(" " + ToHexNB(rva) + + ", // vtable " + line + descTail); + } + } + else + { + SymbolProvider.SymType symType; + + if (fFoundVptrs) + throw new InvalidOperationException("Invalid dac header format. Vtable pointers must be last."); + + if (line.StartsWith("dac__")) + { + // Global variables, use the prefix. + line = line.Remove(0, 5); + symType = SymbolProvider.SymType.GlobalData; + } + else if (line.StartsWith("fn__")) + { + // Global or static functions, use the prefix. + line = line.Remove(0, 4); + line = line.Replace("__", "::"); + symType = SymbolProvider.SymType.GlobalFunction; + } + else + { + // Static member variable, use the full name with + // namespace replacement. + line = line.Replace("__", "::"); + symType = SymbolProvider.SymType.GlobalData; + } + + if (0 == rva) + { + rva = sf.GetGlobalRVA(line, symType); + + if (rva == UInt32.MaxValue) + { + Console.WriteLine(" " + ToHexNB(rva)); + Shell.Error("Invalid symbol " + line); + } + else + { + Console.WriteLine(" " + ToHexNB(rva) + ", // " + + line); + } + } + } + + rvaArray.Add(rva); + + } + else if (line == "") + { + // Skip blanks. + } + else + { + // We hit a non-global so we're done. + if (!line.Equals("} DacGlobals;")) + { + throw new + InvalidOperationException("Invalid dac header format at \"" + line + "\""); + } + break; + } + } + + if (!fFoundVptrs) + throw new InvalidOperationException("Invalid dac header format. Vtable pointers not found."); + } + + private static String ToHex(Object o) + { + if (o is UInt32 || o is Int32) + return String.Format("0x{0:x8}", o); + else if (o is UInt64 || o is Int64) + return String.Format("0x{0:x16}", o); + else + return null; + } + + private static String ToHexNB(Object o) + { + return String.Format("0x{0:x}", o); + } + + public static void Error(string message) + { + Console.Error.WriteLine((s_ignoreErrors ? "WARNING: " : "ERROR: ") + message); + s_errors = true; + } + + // Try to tolerate errors (as we've always done in the past), which may result in failures at run-time instead. + private static bool s_ignoreErrors = false; + private static bool s_errors = false; +} |