Files
UnrealEngineUWP/Engine/Source/Programs/MemoryProfiler2/ElfSymbolParser.cs
Ben Marsh 13d012685f Merging copyright update from 4.19 branch.
#rb none
#rnx
#jira

[CL 3818977 by Ben Marsh in Staging-4.19 branch]
2018-01-02 15:30:26 -05:00

199 lines
6.3 KiB
C#

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
namespace MemoryProfiler2
{
public abstract class FElfSymbolParser : ISymbolParser
{
// each platform needs to return the location to the Nm that can parse the data
public abstract string GetNmPath();
public virtual ulong GetSymbolOffset()
{
// look for some default metadata to compute an offset by comparing runtime address with nm-discovered address
string OffsetSymbolName = null;
string OffsetRuntimeAddressString = null;
if (FStreamInfo.GlobalInstance.MetaData.TryGetValue("ModuleOffsetSymbolName", out OffsetSymbolName) &&
FStreamInfo.GlobalInstance.MetaData.TryGetValue("ModuleOffsetRuntimeAddress", out OffsetRuntimeAddressString))
{
// convert string to int
ulong RuntimeAddress = ulong.Parse(OffsetRuntimeAddressString);
// find the matching symbol
int Index = 0;
foreach (string NmSymbol in Symbols)
{
if (NmSymbol == OffsetSymbolName)
{
// return the offset to go from nm address to runtime address
return RuntimeAddress - Addresses[Index];
}
Index++;
}
}
return 0;
}
public virtual string GetDebugInfoPath(string ExecutableName, out bool bVerifyInfoWithUser)
{
// since we are only going on the path, we can't guarantee that it's the correct version (could be newer)
// we'd have to transmit some extra data all the way through external means, like the PS4 does
bVerifyInfoWithUser = true;
// try to use a generic metadata value to get the debug info
string RuntimeExecutablePath = null;
if (FStreamInfo.GlobalInstance.MetaData.TryGetValue("ExecutablePath", out RuntimeExecutablePath))
{
// and change the extension based on the platforms debug info extension
return Path.ChangeExtension(RuntimeExecutablePath, GetDebugInfoExtension());
}
return "";
}
public abstract string GetDebugInfoExtension();
public override bool InitializeSymbolService(string ExecutableName, FUIBroker UIBroker)
{
// ask the platform code for the actual path
string NmPath = GetNmPath();
bool bVerifyInfoWithUser;
string DebugInfoPath = GetDebugInfoPath(ExecutableName, out bVerifyInfoWithUser);
// Nm must exist!!!
if (!File.Exists(NmPath))
{
return false;
}
// if the platform couldn't guarantee where the debug info was, let the user select it
if (string.IsNullOrEmpty(DebugInfoPath) || !File.Exists(DebugInfoPath) || bVerifyInfoWithUser)
{
var DebugInfoFileDialog = new OpenFileDialog();
DebugInfoFileDialog.Title = "Open the debug info (or executable) that this profile was generated from";
DebugInfoFileDialog.Filter = string.Format("Debugging Info (*.{0})|*.{0}", GetDebugInfoExtension());
DebugInfoFileDialog.FileName = String.Format("{0}.{1}", ExecutableName, GetDebugInfoExtension());
DebugInfoFileDialog.SupportMultiDottedExtensions = true;
DebugInfoFileDialog.RestoreDirectory = false;
// prepare the file if we had a valid file already
if (!string.IsNullOrEmpty(DebugInfoPath) && File.Exists(DebugInfoPath))
{
DebugInfoFileDialog.Title = "Verify the expected debug info below is correct:";
DebugInfoFileDialog.InitialDirectory = Path.GetDirectoryName(DebugInfoPath);
DebugInfoFileDialog.FileName = Path.GetFileName(DebugInfoPath);
}
DebugInfoPath = UIBroker.ShowOpenFileDialog(DebugInfoFileDialog);
// early out if we canceled
if (!File.Exists(DebugInfoPath))
{
return false;
}
}
string Args = string.Format("-n -C \"{0}\"", DebugInfoPath);
string Output = RunLocalProcessAndReturnStdOut(NmPath, Args);
// parse the output a line at a time
using (StringReader sr = new StringReader(Output))
{
string Line;
while ((Line = sr.ReadLine()) != null)
{
if (Line.StartsWith("0"))
{
try
{
// break it apart
ulong Address = ulong.Parse(Line.Substring(0, 16), System.Globalization.NumberStyles.HexNumber);
string Symbol = Line.Substring(19);
// add to the lists
Addresses.Add(Address);
Symbols.Add(Symbol);
}
catch (Exception)
{
}
}
}
}
// now we can cache off the offset, and let the platform calculate it (could be slow)
CachedSymbolOffset = GetSymbolOffset();
return true;
}
public static string RunLocalProcessAndReturnStdOut(string Command, string Args)
{
ProcessStartInfo StartInfo = new ProcessStartInfo(Command, Args);
StartInfo.UseShellExecute = false;
StartInfo.RedirectStandardOutput = true;
StartInfo.CreateNoWindow = true;
string FullOutput = "";
using (System.Diagnostics.Process LocalProcess = System.Diagnostics.Process.Start(StartInfo))
{
StreamReader OutputReader = LocalProcess.StandardOutput;
// trim off any extraneous new lines, helpful for those one-line outputs
FullOutput = OutputReader.ReadToEnd().Trim();
}
return FullOutput;
}
public override bool ResolveAddressToSymboInfo(ESymbolResolutionMode SymbolResolutionMode, ulong Address, out string OutFileName, out string OutFunction, out int OutLineNumber)
{
OutFileName = null;
OutFunction = null;
OutLineNumber = 0;
// subtract off a known amount for where we were loaded
Address -= CachedSymbolOffset;
// verify the address
if (Address < 0 || Address > Addresses[Addresses.Count - 1])
{
return false;
}
// fast binary search of the address to symbol
int FoundIndex = Addresses.BinarySearch(Address);
// negative means that the result is the binary complement of the one _bigger_ than the value, we want the one lower
if (FoundIndex < 0)
{
FoundIndex = ~FoundIndex;
// if the index is Count, then we didn't find it
if (FoundIndex == Addresses.Count)
{
return false;
}
// move to the one before it
FoundIndex -= 1;
}
// return the matching symbol
OutFunction = Symbols[FoundIndex];
return true;
}
// the symbols may be relocated at runtime compared to what NM expects. this will address that offset (needs to come from mprof runtime data)
protected ulong CachedSymbolOffset;
protected List<ulong> Addresses = new List<ulong>();
protected List<string> Symbols = new List<string>();
}
}