/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // /////////////////////////////////////////////////////////////////////////////// using System; using System.Collections; using System.Globalization; using System.IO; namespace ProfMap { /// /// ProfMap is a quick and dirty utility that is used to post-process the result // of profiling a Singularity application. The log contains entries with EIP values // rather than symbolic names. By consulting the Map for the application, we can // replace those EIP values with symbolic names, prior to feeding the log to the // Profiler. /// // Here's an example of a source line we would like to update: // // f 0x8 Function_EIP_0x120f32b4 (UNKNOWN ARGUMENTS) 0 0 // // If we find that EIP value 0x120f32b4 is contained within System.RuntimeType.Name's // property getter, and that method starts at offset 120f3200, we would rewrite this // as: // // f 0x8 ?m_get$_Name@Class_System_RuntimeType@@SIPAUClass_System_String@@PAU1@@Z (UNKNOWN ARGUMENTS) 0x120f3200 1 // // If you don't like mangled names, feel free to add an unmangler at AddMapEntry below. // This would make it much easier to read the profiles in CLR Profiler. // // The following app is very brittle if the log format or map format changes. Also // it uses heuristics to determine where the image was loaded into memory so it can // bias the EIPs back to image addresses. These heuristics are brittle. public class ProfMap { internal class Entry : IComparable { internal string symbol; internal ulong addr; // Always sort on address, since that is the typical lookup public int CompareTo(Object o) { Entry other = (Entry) o; return (addr > other.addr ? 1 : (addr == other.addr ? 0 : -1)); } } static ArrayList entries; static StreamReader logStream, mapStream; static StreamWriter newStream; static ulong loadBias; // In order to calculate the load address of our image into memory, we need to find // a well-known symbol in the map. static ulong FindWriteHeader() { // For WebServer.x86, the symbol we need is represented in the map as: // // ?m_WriteHeader@Class_Microsoft_Singularity_WebServer_CLRProfiler@@SIXPAU1@PAUClass_Microsoft_Singularity_WebServer_CLRProfiler_Buffer@@@Z // // Of course, the Microsoft_Singularity_WebServer part is application-specific. // So the fragments we are looking for are: // // ?m_WriteHeader@Class_ + // + // _CLRProfiler@@SIXPAU1@PAU + // + // _CLRProfiler_Buffer@@@Z // // We only do this operation once, so a linear search isn't outrageous foreach (Entry e in entries) { int index1 = e.symbol.IndexOf("?m_WriteHeader@Class_"); if (index1 >= 0) { int index2 = e.symbol.IndexOf("_CLRProfiler@@SIXPAU1@PAU", index1 + 21); if (index2 >= 0) { int index3 = e.symbol.IndexOf("", index2 + 24); if (index3 >= 0) { return e.addr; } } } } Console.WriteLine("Failure: Couldn't discover the WriteHeader symbol in this map"); return 0; } static Entry FindEntry(ulong eip) { Entry temp = new Entry(); temp.addr = eip - loadBias; // convert from physical to map addresses int res = entries.BinarySearch(temp); // If we get a negative entry, it's not an exact match. But it gives us the index of // the next largest entry. Of course, we want the index of the prior entry, since that // should cover our address. At least, that's what MSDN says. I'm obviously doing // something wrong, because I'm off by one. if (res < 0) { res = -res - 2; } return (Entry) entries[res]; } static void AddMapEntry(ulong addr, String symbol) { Entry e = new Entry(); e.symbol = symbol; e.addr = addr; entries.Add(e); } static void ReadMap() { string textSection = "0001:"; // it usually is, but we will check bool defaultTextSection = true; Console.WriteLine("Reading map "); AddMapEntry(~(ulong) 0, "Unknown Address"); int counter = 0; while (true) { // End of file? String line = mapStream.ReadLine(); if (line == null) { break; } // read the section table and find the section containing ".text" if (defaultTextSection && line.IndexOf(".text") > 0 && line.IndexOf("CODE") > 0) { textSection = line.Substring(1, 5); Console.WriteLine("Updating text Section to {0}", textSection); defaultTextSection = false; // stop looking continue; } if (line.Length > 31 && line.Substring(1, 5) == textSection) { if ((counter++ % 500) == 0) Console.Write("."); ulong addr = ulong.Parse(line.Substring(6, 8), NumberStyles.HexNumber); int temp = line.IndexOf(" ", 21); String symbol = (temp > 21 ? line.Substring(21, temp - 21) : line.Substring(21)); AddMapEntry(addr, symbol); } } entries.Sort(); /* dump the map for (int i=0; i 30 && line[0] == 'z' && line.IndexOf("CLRProfiler_WriteHeader") > 0) { string eipString = line.Substring(4, line.IndexOf(" ", 4)-4); ulong eip = ulong.Parse(eipString, NumberStyles.HexNumber); ulong addr = FindWriteHeader(); // round down to a page, discarding any noise in our calculation due to the // method prolog loadBias = (eip - addr) & ~(ulong)0xfff; Console.WriteLine("Deducing .text segment was loaded at {0:x}", loadBias); } // Process any function entries, replacing process eip values with symbolic values. if (line.Length > 30 && line[0] == 'f') { int iStart = line.IndexOf("Function_EIP_0x"); if (iStart > 0) { int iEnd = line.IndexOf(" (UNKNOWN ARGUMENTS) ", iStart); if (iEnd > 0) { string eipString = line.Substring(iStart + 15, iEnd - iStart - 15); ulong eip = ulong.Parse(eipString, NumberStyles.HexNumber); Entry e = FindEntry(eip); ulong adjustEip = e.addr + loadBias; // Recompose a new line with the symbol and the EIP for the start // of that symbol, rather than the EIP of the stack trace. line = line.Substring(0, iStart) + e.symbol + " (UNKNOWN ARGUMENTS) 0x" + adjustEip.ToString("x") + " 1 "; } } } newStream.WriteLine(line); if ((counter++ % 1000) == 0) Console.Write("*"); } newStream.Close(); Console.WriteLine(" Okay"); } static void Main(string[] args) { if (args.Length != 2) { Console.WriteLine("Usage: ProfMap "); return; } string logName = args[0]; string mapName = args[1]; if (!File.Exists(logName)) { Console.WriteLine("Cannot find log file {0}", logName); return; } logStream = new StreamReader(logName); if (!File.Exists(mapName)) { Console.WriteLine("Cannot find map file {1}", mapName); return; } mapStream = new StreamReader(mapName); string newName = logName + "new"; newStream = new StreamWriter(newName); Console.WriteLine("Old log: {0}, map: {1}, creating new log {2}", logName, mapName, newName); entries = new ArrayList(); ReadMap(); ProcessLog(); Console.WriteLine("Done"); } } }