210 lines
8.0 KiB
C#
210 lines
8.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace ProfilerHelpers
|
|
{
|
|
public class MethodStore : IDisposable
|
|
{
|
|
// JITed methods information (start address + size + signature)
|
|
private readonly List<MethodInfo> _methods;
|
|
|
|
// addresses from callstacks already matching (address -> full name)
|
|
private readonly Dictionary<ulong, string> _cache;
|
|
|
|
// for native methods, rely on dbghelp API
|
|
// so the process handle is required
|
|
private IntPtr _hProcess;
|
|
private Process _process;
|
|
private readonly int _pid;
|
|
|
|
public MethodStore( int pid, bool loadModules = false )
|
|
{
|
|
// it may be possible to open the process
|
|
// in that case, _hProcess = IntPtr.Zero
|
|
_pid = pid;
|
|
|
|
_methods = new List<MethodInfo>( 1024 );
|
|
_cache = new Dictionary<ulong, string>();
|
|
|
|
_hProcess = BindToProcess( pid, loadModules );
|
|
}
|
|
|
|
private IntPtr BindToProcess( int pid, bool loadModules )
|
|
{
|
|
try
|
|
{
|
|
_process = Process.GetProcessById( pid );
|
|
|
|
if( !SymInitialize( _process.Handle, loadModules ) )
|
|
return IntPtr.Zero;
|
|
|
|
return _process.Handle;
|
|
}
|
|
catch( Exception x )
|
|
{
|
|
Console.WriteLine( $"Error while binding pid #{pid} to DbgHelp:" );
|
|
Console.WriteLine( x.Message );
|
|
return IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
private bool SymInitialize( IntPtr hProcess, bool loadModules = false )
|
|
{
|
|
// read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions for more details
|
|
// maybe SYMOPT_NO_PROMPTS and SYMOPT_FAIL_CRITICAL_ERRORS could be used
|
|
NativeDbgHelp.SymSetOptions(
|
|
NativeDbgHelp.SYMOPT_DEFERRED_LOADS | // performance optimization
|
|
NativeDbgHelp.SYMOPT_UNDNAME // C++ names are not mangled
|
|
);
|
|
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize
|
|
// search path for symbols:
|
|
// - The current working directory of the application
|
|
// - The _NT_SYMBOL_PATH environment variable
|
|
// - The _NT_ALTERNATE_SYMBOL_PATH environment variable
|
|
//
|
|
// passing false as last parameter means that we will need to call SymLoadModule64
|
|
// each time a module is loaded in the process
|
|
return NativeDbgHelp.SymInitialize( hProcess, null, loadModules );
|
|
}
|
|
|
|
public MethodInfo Add( ulong address, int size, string namespaceAndTypeName, string name, string signature )
|
|
{
|
|
var method = new MethodInfo( address, size, namespaceAndTypeName, name, signature );
|
|
_methods.Add( method );
|
|
return method;
|
|
}
|
|
|
|
public string GetFullName( ulong address )
|
|
{
|
|
if( _cache.TryGetValue( address, out var fullName ) )
|
|
return fullName;
|
|
|
|
// look for managed methods
|
|
for( int i = 0; i < _methods.Count; i++ )
|
|
{
|
|
var method = _methods[i];
|
|
|
|
if( ( address >= method.StartAddress ) && ( address < method.StartAddress + (ulong)method.Size ) )
|
|
{
|
|
fullName = method.FullName;
|
|
_cache[address] = fullName;
|
|
return fullName;
|
|
}
|
|
}
|
|
|
|
// look for native methods
|
|
fullName = GetNativeMethodName( address );
|
|
_cache[address] = fullName;
|
|
|
|
return fullName;
|
|
}
|
|
|
|
private string GetNativeMethodName( ulong address )
|
|
{
|
|
var symbol = new NativeDbgHelp.SYMBOL_INFO();
|
|
symbol.MaxNameLen = 1024;
|
|
symbol.SizeOfStruct = (uint)Marshal.SizeOf( symbol ) - 1024; // char buffer is not counted
|
|
// the ANSI version of SymFromAddr is called so each character is 1 byte long
|
|
|
|
if( NativeDbgHelp.SymFromAddr( _hProcess, address, out var displacement, ref symbol ) )
|
|
{
|
|
var buffer = new StringBuilder( symbol.Name.Length );
|
|
|
|
// remove weird "$##" at the end of some symbols
|
|
var pos = symbol.Name.LastIndexOf( "$##" );
|
|
if( pos == -1 )
|
|
buffer.Append( symbol.Name );
|
|
else
|
|
buffer.Append( symbol.Name, 0, pos );
|
|
|
|
// add offset if any
|
|
if( displacement != 0 )
|
|
buffer.Append( $"+0x{displacement}" );
|
|
|
|
return buffer.ToString();
|
|
}
|
|
|
|
// default value is just the address in HEX
|
|
#if DEBUG
|
|
return ( $"0x{address:x} (SymFromAddr failed with 0x{Marshal.GetLastWin32Error():x})" );
|
|
#else
|
|
return $"0x{address:x}";
|
|
#endif
|
|
|
|
}
|
|
|
|
const int ERROR_SUCCESS = 0;
|
|
public void AddModule( string filename, ulong baseOfDll, int sizeOfDll )
|
|
{
|
|
var baseAddress = NativeDbgHelp.SymLoadModule64( _hProcess, IntPtr.Zero, filename, null, baseOfDll, (uint)sizeOfDll );
|
|
if( baseAddress == 0 )
|
|
{
|
|
// should work if the same module is added more than once
|
|
if( Marshal.GetLastWin32Error() == ERROR_SUCCESS )
|
|
return;
|
|
|
|
Console.WriteLine( $"SymLoadModule64 failed for {filename}" );
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if( _hProcess == IntPtr.Zero )
|
|
return;
|
|
_hProcess = IntPtr.Zero;
|
|
|
|
_process.Dispose();
|
|
}
|
|
}
|
|
|
|
internal static class NativeDbgHelp
|
|
{
|
|
// from C:\Program Files (x86)\Windows Kits\10\Debuggers\inc\dbghelp.h
|
|
public const uint SYMOPT_UNDNAME = 0x00000002;
|
|
public const uint SYMOPT_DEFERRED_LOADS = 0x00000004;
|
|
|
|
[StructLayout( LayoutKind.Sequential )]
|
|
public struct SYMBOL_INFO
|
|
{
|
|
public uint SizeOfStruct;
|
|
public uint TypeIndex; // Type Index of symbol
|
|
private ulong Reserved1;
|
|
private ulong Reserved2;
|
|
public uint Index;
|
|
public uint Size;
|
|
public ulong ModBase; // Base Address of module containing this symbol
|
|
public uint Flags;
|
|
public ulong Value; // Value of symbol, ValuePresent should be 1
|
|
public ulong Address; // Address of symbol including base address of module
|
|
public uint Register; // register holding value or pointer to value
|
|
public uint Scope; // scope of the symbol
|
|
public uint Tag; // pdb classification
|
|
public uint NameLen; // Actual length of name
|
|
public uint MaxNameLen;
|
|
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 1024 )]
|
|
public string Name;
|
|
}
|
|
|
|
[DllImport( "dbghelp.dll", SetLastError = true )]
|
|
public static extern bool SymInitialize( IntPtr hProcess, string userSearchPath, bool invadeProcess );
|
|
|
|
[DllImport( "dbghelp.dll", SetLastError = true )]
|
|
public static extern uint SymSetOptions( uint symOptions );
|
|
|
|
[DllImport( "dbghelp.dll", SetLastError = true, CharSet = CharSet.Ansi )]
|
|
public static extern ulong SymLoadModule64( IntPtr hProcess, IntPtr hFile, string imageName, string moduleName, ulong baseOfDll, uint sizeOfDll );
|
|
|
|
// use ANSI version to ensure the right size of the structure
|
|
// read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info
|
|
[DllImport( "dbghelp.dll", SetLastError = true, CharSet = CharSet.Ansi )]
|
|
public static extern bool SymFromAddr( IntPtr hProcess, ulong address, out ulong displacement, ref SYMBOL_INFO symbol );
|
|
|
|
[DllImport( "dbghelp.dll", SetLastError = true )]
|
|
public static extern bool SymCleanup( IntPtr hProcess );
|
|
}
|
|
}
|