// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Runtime.Serialization.Formatters.Binary; using System.Windows.Forms; namespace MemoryProfiler2 { public class FIOSSymbolParser : ISymbolParser { private const uint PACKAGE_FILE_TAG = 0x9E2A83C1; private const uint PACKAGE_FILE_TAG_SWAPPED = 0xC1832A9E; private const int LOADING_COMPRESSION_CHUNK_SIZE = 131072; protected struct FGenericPlatformSymbolInfo { public uint Line; public ulong Start; public ulong Length; public int PathIdx; public void Read(BinaryReader reader) { Line = reader.ReadUInt32(); Start = reader.ReadUInt64(); Length = reader.ReadUInt64(); PathIdx = reader.ReadInt32(); } } protected struct FGenericPlatformSymbolData { public ulong Start; public ulong Length; public int NameIdx; public FGenericPlatformSymbolInfo[] SymbolInfo; public void Read(BinaryReader reader) { Start = reader.ReadUInt64(); Length = reader.ReadUInt64(); NameIdx = reader.ReadInt32(); int nrSymbols = reader.ReadInt32(); SymbolInfo = new FGenericPlatformSymbolInfo[nrSymbols]; for (int i = 0; i < nrSymbols; i++) { SymbolInfo[i].Read(reader); } } } protected struct FGenericPlatformSymbolDatabase { string Signature; string Name; public FGenericPlatformSymbolData[] Symbols; public string[] StringTable; public void Read(BinaryReader reader) { int SignatureLen = reader.ReadInt32(); Signature = new string(reader.ReadChars(SignatureLen)); int NameLen = reader.ReadInt32(); Name = new string(reader.ReadChars(NameLen)); int nrSymbols = reader.ReadInt32(); Symbols = new FGenericPlatformSymbolData[nrSymbols]; for (int i = 0; i < nrSymbols; i++) { Symbols[i].Read(reader); } int nrStrings = reader.ReadInt32(); StringTable = new string[nrStrings]; for (int i = 0; i < nrStrings; i++) { int StringLen = reader.ReadInt32(); StringTable[i] = new string(reader.ReadChars(StringLen)); } } } struct FCompressedChunkInfo { /** Holds the data's compressed size. */ public long CompressedSize; /** Holds the data's uncompresses size. */ public long UncompressedSize; }; public static string StaticPlatformName() { return "IOS"; } protected class FIOSSymbol : IComparable { public FIOSSymbol(ulong _Address, string _OutFileName, string _OutFunction, int _OutLineNumber) { OutFileName = _OutFileName; OutFunction = _OutFunction; OutLineNumber = _OutLineNumber; Address = _Address; } public int CompareTo(FIOSSymbol that) { if (that == null) return 1; if (this.Address > that.Address) return 1; if (this.Address < that.Address) return -1; return 0; } public string OutFileName; public string OutFunction; public int OutLineNumber; public ulong Address; } protected class CompareAdresses : IComparer { public int Compare(FIOSSymbol x, FIOSSymbol y) { if (x.Address > y.Address) return 1; if (x.Address < y.Address) return -1; return 0; } } protected byte[] LoadCompressedChunk(BinaryReader reader) { try { FCompressedChunkInfo PackageFileTag = new FCompressedChunkInfo(); PackageFileTag.CompressedSize = reader.ReadInt64(); PackageFileTag.UncompressedSize = reader.ReadInt64(); FCompressedChunkInfo Summary = new FCompressedChunkInfo(); Summary.CompressedSize = reader.ReadInt64(); Summary.UncompressedSize = reader.ReadInt64(); bool bWasByteSwapped = PackageFileTag.CompressedSize != PACKAGE_FILE_TAG; bool bHeaderWasValid = true; if (bWasByteSwapped) { bHeaderWasValid = PackageFileTag.CompressedSize == PACKAGE_FILE_TAG_SWAPPED; if (bHeaderWasValid) { // not supported //Summary.CompressedSize = BYTESWAP_ORDER64(Summary.CompressedSize); //Summary.UncompressedSize = BYTESWAP_ORDER64(Summary.UncompressedSize); //PackageFileTag.UncompressedSize = BYTESWAP_ORDER64(PackageFileTag.UncompressedSize); } } else { bHeaderWasValid = PackageFileTag.CompressedSize == PACKAGE_FILE_TAG; } // Handle change in compression chunk size in backward compatible way. long LoadingCompressionChunkSize = PackageFileTag.UncompressedSize; if (LoadingCompressionChunkSize == PACKAGE_FILE_TAG) { LoadingCompressionChunkSize = LOADING_COMPRESSION_CHUNK_SIZE; } // Figure out how many chunks there are going to be based on uncompressed size and compression chunk size. long TotalChunkCount = (Summary.UncompressedSize + LoadingCompressionChunkSize - 1) / LoadingCompressionChunkSize; // Allocate compression chunk infos and serialize them, keeping track of max size of compression chunks used. FCompressedChunkInfo[] CompressionChunks = new FCompressedChunkInfo[TotalChunkCount]; long MaxCompressedSize = 0; for (int ChunkIndex = 0; ChunkIndex < TotalChunkCount; ChunkIndex++) { CompressionChunks[ChunkIndex].CompressedSize = reader.ReadInt64(); CompressionChunks[ChunkIndex].UncompressedSize = reader.ReadInt64(); if (bWasByteSwapped) { // not supported //CompressionChunks[ChunkIndex].CompressedSize = BYTESWAP_ORDER64(CompressionChunks[ChunkIndex].CompressedSize); //CompressionChunks[ChunkIndex].UncompressedSize = BYTESWAP_ORDER64(CompressionChunks[ChunkIndex].UncompressedSize); } MaxCompressedSize = Math.Max(CompressionChunks[ChunkIndex].CompressedSize, MaxCompressedSize); } byte[] DeCompressedBuffer = new byte[Summary.UncompressedSize]; int offset = 0; // Iterate over all chunks, serialize them into memory and decompress them directly into the destination pointer for (long ChunkIndex = 0; ChunkIndex < TotalChunkCount; ChunkIndex++) { // Read compressed data. byte[] initialSkip = reader.ReadBytes(2); byte[] CompressedBuffer = reader.ReadBytes((int)CompressionChunks[ChunkIndex].CompressedSize - 6); byte[] finalSkip = reader.ReadBytes(4); MemoryStream ms = new MemoryStream(CompressedBuffer); DeflateStream gzip = new DeflateStream(ms, CompressionMode.Decompress); gzip.Read(DeCompressedBuffer, offset, (int)CompressionChunks[ChunkIndex].UncompressedSize); // Decompress into dest pointer directly. offset += (int)CompressionChunks[ChunkIndex].UncompressedSize; } return DeCompressedBuffer; } catch { return null; } } public static void AppendAllBytes(string path, byte[] bytes) { using (var stream = new FileStream(path, FileMode.Append)) { stream.Write(bytes, 0, bytes.Length); } } public override bool InitializeSymbolService(string ExecutableName, FUIBroker UIBroker) { string FDsName = FStreamInfo.GlobalInstance.FileName; string FAdName; string FUcName; string FExtension = Path.GetExtension(FDsName).ToLower(); if (FExtension == ".mprof") { FAdName = Path.GetDirectoryName(FDsName) + "\\" + ExecutableName + ".udebugsymbols"; FUcName = Path.GetDirectoryName(FDsName) + "\\" + ExecutableName + ".udb"; } else { return false; } if (!File.Exists(FAdName)) { MessageBox.Show(String.Format("Failed to look up symbol file ({0}). Please check the path and try again!", FAdName), "Memory Profiler 2", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } byte[] uncompressed = new byte[0]; bool NeedsDecompress = false; if (!File.Exists(FUcName)) { NeedsDecompress = true; } else if (File.GetCreationTimeUtc(FAdName) > File.GetCreationTimeUtc(FUcName)) { NeedsDecompress = true; } if (NeedsDecompress) { byte[] data = File.ReadAllBytes(FAdName); MemoryStream memory = new MemoryStream(data); BinaryReader reader = new BinaryReader(memory); byte[] DeCompressedBuffer = LoadCompressedChunk(reader); while (DeCompressedBuffer != null) { AppendAllBytes(FUcName, DeCompressedBuffer); DeCompressedBuffer = LoadCompressedChunk(reader); } } uncompressed = File.ReadAllBytes(FUcName); if (uncompressed != null) { MemoryStream memory = new MemoryStream(uncompressed); BinaryReader reader = new BinaryReader(memory); Database.Read(reader); } return true; } protected ulong ModuleOffset = 0; public override void SetModuleOffset(ulong offset) { ModuleOffset = offset; } public override bool ResolveAddressToSymboInfo(ESymbolResolutionMode SymbolResolutionMode, ulong Address, out string OutFileName, out string OutFunction, out int OutLineNumber) { OutFileName = null; OutFunction = null; OutLineNumber = 0; bool bOk = false; foreach (var Symbol in Database.Symbols) { if ((Symbol.Start <= (Address - ModuleOffset)) && ((Symbol.Start + Symbol.Length) >= (Address - ModuleOffset))) { OutFunction = Database.StringTable[Symbol.NameIdx]; foreach(var SymbolInfo in Symbol.SymbolInfo) { if ((SymbolInfo.Start <= (Address - ModuleOffset)) && ((SymbolInfo.Start + SymbolInfo.Length) >= (Address - ModuleOffset))) { OutFileName = Database.StringTable[SymbolInfo.PathIdx]; OutLineNumber = (int)SymbolInfo.Line; break; } } bOk = true; break; } } return bOk; } protected FGenericPlatformSymbolDatabase Database = new FGenericPlatformSymbolDatabase(); } }