/// <summary> /// Given a full filename path to an NGEN image, insure that there is an NGEN image for it /// in the symbol cache. If one already exists, this method simply returns that. If not /// it is generated and placed in the symbol cache. When generating the PDB this routine /// attempt to resolve line numbers, which DOES require looking up the PDB for the IL image. /// Thus routine may do network accesses (to download IL PDBs). /// /// Note that FindSymbolFilePathForModule calls this, so normally you don't need to call /// this method directly. /// </summary> public string GenerateNGenPdbForModule(string ngenImageFullPath) { SymbolReader symReader = this; var log = symReader.m_log; if (!File.Exists(ngenImageFullPath)) { log.WriteLine("Warning, NGEN image does not exist: {0}", ngenImageFullPath); return null; } // When V4.5 shipped, NGEN CreatePdb did not support looking up the IL pdb using symbol servers. // We work around by explicitly fetching the IL PDB and pointing NGEN CreatePdb at that. string ilPdbName = null; Guid ilPdbGuid = Guid.Empty; int ilPdbAge = 0; string pdbName; Guid pdbGuid; int pdbAge; using (var peFile = new PEFile(ngenImageFullPath)) { if (!peFile.GetPdbSignature(out pdbName, out pdbGuid, out pdbAge, true)) { log.WriteLine("Could not get PDB signature for {0}", ngenImageFullPath); return null; } // Also get the IL pdb information peFile.GetPdbSignature(out ilPdbName, out ilPdbGuid, out ilPdbAge, false); } // Fast path, the file already exists. pdbName = Path.GetFileName(pdbName); var relPath = pdbName + "\\" + pdbGuid.ToString("N") + pdbAge.ToString() + "\\" + pdbName; var pdbPath = Path.Combine(symReader.SymbolCacheDirectory, relPath); if (File.Exists(pdbPath)) return pdbPath; var clrDir = GetClrDirectoryForNGenImage(ngenImageFullPath, log); if (clrDir == null) return null; // See if this is a V4.5 CLR, if so we can do line numbers too.l var lineNumberArg = ""; var ngenexe = Path.Combine(clrDir, "ngen.exe"); log.WriteLine("Checking for V4.5 for NGEN image {0}", ngenexe); if (!File.Exists(ngenexe)) return null; var isV4_5Runtime = false; Match m; using (var peFile = new PEFile(ngenexe)) { var fileVersionInfo = peFile.GetFileVersionInfo(); if (fileVersionInfo != null) { var clrFileVersion = fileVersionInfo.FileVersion; m = Regex.Match(clrFileVersion, @"^[\d.]+\.(\d+) "); // Fetch the build number (last number) if (m.Success) { // Is this a V4.5 runtime? var buildNumber = int.Parse(m.Groups[1].Value); log.WriteLine("Got NGEN.exe Build number: {0}", buildNumber); if (buildNumber > 16000) { if (ilPdbName != null) { var ilPdbPath = symReader.FindSymbolFilePath(ilPdbName, ilPdbGuid, ilPdbAge); if (ilPdbPath != null) lineNumberArg = "/lines " + Command.Quote(Path.GetDirectoryName(ilPdbPath)); else log.WriteLine("Could not find IL PDB {0} Guid {1} Age {2}.", ilPdbName, ilPdbGuid, ilPdbAge); } else log.WriteLine("NGEN image did not have IL PDB information, giving up on line number info."); isV4_5Runtime = true; } } } } var options = new CommandOptions(); options.AddEnvironmentVariable("COMPLUS_NGenEnableCreatePdb", "1"); // NGenLocalWorker is needed for V4.0 runtims but interferes on V4.5 runtimes. if (!isV4_5Runtime) options.AddEnvironmentVariable("COMPLUS_NGenLocalWorker", "1"); options.AddEnvironmentVariable("_NT_SYMBOL_PATH", symReader.SymbolPath.ToString()); var newPath = "%PATH%;" + clrDir; options.AddEnvironmentVariable("PATH", newPath); options.AddOutputStream(log); options.AddNoThrow(); // For Metro Auto-NGEN images we need to use a location where the app can write the PDB file var outputDirectory = symReader.SymbolCacheDirectory; var outputPdbPath = pdbPath; // Find the tempDir where we can write. string tempDir = null; m = Regex.Match(ngenImageFullPath, @"(.*)\\Microsoft\\CLR_v(\d+)\.\d+(_(\d\d))?\\NativeImages", RegexOptions.IgnoreCase); if (m.Success) { tempDir = Path.Combine(m.Groups[1].Value, @"Temp\NGenPdb"); DirectoryUtilities.Clean(tempDir); Directory.CreateDirectory(tempDir); outputDirectory = tempDir; outputPdbPath = Path.Combine(tempDir, relPath); log.WriteLine("Updating NGEN createPdb output file to {0}", outputPdbPath); // TODO FIX NOW REMOVE (for debugging) } try { for (; ; ) // Loop for retrying without /lines { // TODO FIX NOW: there is a and ugly problem with persistance of suboptimial PDB files // This is made pretty bad because the not finding the IL pdbs is enough to make it fail. // TODO we need to figure out a convention show we know that we have fallen back to no-lines // and we should regenerate it if we ultimately get the PDB information var cmdLine = string.Format(@"{0}\ngen.exe createpdb {1} {2} {3}", clrDir, Command.Quote(ngenImageFullPath), Command.Quote(outputDirectory), lineNumberArg); // TODO FIX NOW REMOVE after V4.5 is out a while log.WriteLine("set COMPLUS_NGenEnableCreatePdb=1"); if (!isV4_5Runtime) log.WriteLine("set COMPLUS_NGenLocalWorker=1"); log.WriteLine("set PATH=" + newPath); log.WriteLine("set _NT_SYMBOL_PATH={0}", symReader.SymbolPath); log.WriteLine("*** NGEN CREATEPDB cmdline: {0}\r\n", cmdLine); options.AddOutputStream(log); var cmd = Command.Run(cmdLine, options); log.WriteLine("*** NGEN CREATEPDB returns: {0}", cmd.ExitCode); if (cmd.ExitCode != 0) { // ngen might make a bad PDB, so if it returns failure delete it. if (File.Exists(outputPdbPath)) File.Delete(outputPdbPath); // We may have failed because we could not get the PDB. if (lineNumberArg.Length != 0) { log.WriteLine("Ngen failed to generate pdb for {0}, trying again without /lines", ngenImageFullPath); lineNumberArg = ""; continue; } } if (cmd.ExitCode != 0 || !File.Exists(outputPdbPath)) { log.WriteLine("ngen failed to generate pdb for {0} at expected location {1}", ngenImageFullPath, outputPdbPath); return null; } // Copy the file to where we want the PDB to live. if (outputPdbPath != pdbPath) { Directory.CreateDirectory(Path.GetDirectoryName(pdbPath)); // Make sure the destination directory exists. File.Copy(outputPdbPath, pdbPath); } return pdbPath; } } finally { // Insure we have cleaned up any temporary files. if (tempDir != null) DirectoryUtilities.Clean(tempDir); } }