diff --git a/Dalamud/Support/CurrentProcessModules.cs b/Dalamud/Support/CurrentProcessModules.cs new file mode 100644 index 000000000..961c9828f --- /dev/null +++ b/Dalamud/Support/CurrentProcessModules.cs @@ -0,0 +1,138 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +using Serilog; + +using TerraFX.Interop.Windows; + +namespace Dalamud.Support; + +/// Tracks the loaded process modules. +[ServiceManager.EarlyLoadedService] +internal sealed unsafe partial class CurrentProcessModules : IInternalDisposableService +{ + private static readonly ConcurrentQueue LogQueue = new(); + private static readonly SemaphoreSlim LogSemaphore = new(0); + + private static Process? process; + private static nint cookie; + + private readonly CancellationTokenSource logTaskStop = new(); + private readonly Task logTask; + + [ServiceManager.ServiceConstructor] + private CurrentProcessModules() + { + var res = LdrRegisterDllNotification(0, &DllNotificationCallback, 0, out cookie); + if (res != STATUS.STATUS_SUCCESS) + { + Log.Error("{what}: LdrRegisterDllNotification failure: 0x{err}", nameof(CurrentProcessModules), res); + cookie = 0; + this.logTask = Task.CompletedTask; + return; + } + + this.logTask = Task.Factory.StartNew( + () => + { + while (!this.logTaskStop.IsCancellationRequested) + { + LogSemaphore.Wait(); + while (LogQueue.TryDequeue(out var log)) + Log.Verbose(log); + } + }, + this.logTaskStop.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Default); + } + + private enum LdrDllNotificationReason : uint + { + Loaded = 1, + Unloaded = 2, + } + + /// Gets all the loaded modules, up to date. + public static ProcessModuleCollection ModuleCollection => + (cookie == 0 ? Process.GetCurrentProcess() : process ??= Process.GetCurrentProcess()).Modules; + + /// + void IInternalDisposableService.DisposeService() + { + if (Interlocked.Exchange(ref cookie, 0) is var copy and not 0) + LdrUnregisterDllNotification(copy); + if (!this.logTask.IsCompleted) + { + this.logTaskStop.Cancel(); + LogSemaphore.Release(); + this.logTask.Wait(); + } + } + + [UnmanagedCallersOnly] + private static void DllNotificationCallback( + LdrDllNotificationReason reason, + LdrDllNotificationData* data, + nint context) + { + process = null; + var name = new ReadOnlySpan(data->FullDllName->Buffer, data->FullDllName->Length / 2); + LogQueue.Enqueue( + $"[{nameof(CurrentProcessModules)}]: {reason}: {name} @ 0x{data->DllBase:X} ({data->SizeOfImage}:X bytes)"); + LogSemaphore.Release(); + } + + /// + /// Registers for notification when a DLL is first loaded. + /// This notification occurs before dynamic linking takes place.

+ /// Docs. + ///
+ /// This parameter must be zero. + /// A pointer to a callback function to call when the DLL is loaded. + /// A pointer to context data for the callback function. + /// A pointer to a variable to receive an identifier for the callback function. + /// This identifier is used to unregister the notification callback function. + /// Returns an NTSTATUS or error code. + [LibraryImport("ntdll.dll", SetLastError = true)] + private static partial int LdrRegisterDllNotification( + uint flags, + delegate* unmanaged + notificationFunction, + nint context, + out nint cookie); + + /// + /// Cancels DLL load notification previously registered by calling the LdrRegisterDllNotification function.
+ ///
+ /// Docs. + ///
+ /// A pointer to the callback identifier received from the LdrRegisterDllNotification call + /// that registered for notification. + /// + /// Returns an NTSTATUS or error code. + [LibraryImport("ntdll.dll", SetLastError = true)] + private static partial int LdrUnregisterDllNotification(nint cookie); + + [StructLayout(LayoutKind.Sequential)] + private struct LdrDllNotificationData + { + /// Reserved. + public uint Flags; + + /// The full path name of the DLL module. + public UNICODE_STRING* FullDllName; + + /// The base file name of the DLL module. + public UNICODE_STRING* BaseDllName; + + /// A pointer to the base address for the DLL in memory. + public nint DllBase; + + /// The size of the DLL image, in bytes. + public uint SizeOfImage; + } +} diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 927f7b310..e6bf8c1f3 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -27,6 +27,8 @@ using Windows.Win32.Storage.FileSystem; using Windows.Win32.System.Memory; using Windows.Win32.System.Ole; +using Dalamud.Support; + using static TerraFX.Interop.Windows.Windows; using Win32_PInvoke = Windows.Win32.PInvoke; @@ -191,7 +193,7 @@ public static class Util public static unsafe string DescribeAddress(nint p) { Span namebuf = stackalloc char[9]; - var modules = Process.GetCurrentProcess().Modules; + var modules = CurrentProcessModules.ModuleCollection; for (var i = 0; i < modules.Count; i++) { if (p < modules[i].BaseAddress) continue;