Add separate handler for process modules (#1947)

This commit is contained in:
srkizer 2024-07-19 16:03:53 +09:00 committed by GitHub
parent 4b73e26443
commit 7707da079c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 141 additions and 1 deletions

View file

@ -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;
/// <summary>Tracks the loaded process modules.</summary>
[ServiceManager.EarlyLoadedService]
internal sealed unsafe partial class CurrentProcessModules : IInternalDisposableService
{
private static readonly ConcurrentQueue<string> 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,
}
/// <summary>Gets all the loaded modules, up to date.</summary>
public static ProcessModuleCollection ModuleCollection =>
(cookie == 0 ? Process.GetCurrentProcess() : process ??= Process.GetCurrentProcess()).Modules;
/// <inheritdoc/>
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<char>(data->FullDllName->Buffer, data->FullDllName->Length / 2);
LogQueue.Enqueue(
$"[{nameof(CurrentProcessModules)}]: {reason}: {name} @ 0x{data->DllBase:X} ({data->SizeOfImage}:X bytes)");
LogSemaphore.Release();
}
/// <summary>
/// Registers for notification when a DLL is first loaded.
/// This notification occurs before dynamic linking takes place.<br /><br />
/// <a href="https://learn.microsoft.com/en-us/windows/win32/devnotes/ldrregisterdllnotification">Docs.</a>
/// </summary>
/// <param name="flags">This parameter must be zero.</param>
/// <param name="notificationFunction">A pointer to a callback function to call when the DLL is loaded.</param>
/// <param name="context">A pointer to context data for the callback function.</param>
/// <param name="cookie">A pointer to a variable to receive an identifier for the callback function.
/// This identifier is used to unregister the notification callback function.</param>
/// <returns>Returns an NTSTATUS or error code.</returns>
[LibraryImport("ntdll.dll", SetLastError = true)]
private static partial int LdrRegisterDllNotification(
uint flags,
delegate* unmanaged<LdrDllNotificationReason, LdrDllNotificationData*, nint, void>
notificationFunction,
nint context,
out nint cookie);
/// <summary>
/// Cancels DLL load notification previously registered by calling the LdrRegisterDllNotification function.<br />
/// <br />
/// <a href="https://learn.microsoft.com/en-us/windows/win32/devnotes/ldrunregisterdllnotification">Docs.</a>
/// </summary>
/// <param name="cookie">A pointer to the callback identifier received from the LdrRegisterDllNotification call
/// that registered for notification.
/// </param>
/// <returns>Returns an NTSTATUS or error code.</returns>
[LibraryImport("ntdll.dll", SetLastError = true)]
private static partial int LdrUnregisterDllNotification(nint cookie);
[StructLayout(LayoutKind.Sequential)]
private struct LdrDllNotificationData
{
/// <summary>Reserved.</summary>
public uint Flags;
/// <summary>The full path name of the DLL module.</summary>
public UNICODE_STRING* FullDllName;
/// <summary>The base file name of the DLL module.</summary>
public UNICODE_STRING* BaseDllName;
/// <summary>A pointer to the base address for the DLL in memory.</summary>
public nint DllBase;
/// <summary>The size of the DLL image, in bytes.</summary>
public uint SizeOfImage;
}
}

View file

@ -27,6 +27,8 @@ using Windows.Win32.Storage.FileSystem;
using Windows.Win32.System.Memory; using Windows.Win32.System.Memory;
using Windows.Win32.System.Ole; using Windows.Win32.System.Ole;
using Dalamud.Support;
using static TerraFX.Interop.Windows.Windows; using static TerraFX.Interop.Windows.Windows;
using Win32_PInvoke = Windows.Win32.PInvoke; using Win32_PInvoke = Windows.Win32.PInvoke;
@ -191,7 +193,7 @@ public static class Util
public static unsafe string DescribeAddress(nint p) public static unsafe string DescribeAddress(nint p)
{ {
Span<char> namebuf = stackalloc char[9]; Span<char> namebuf = stackalloc char[9];
var modules = Process.GetCurrentProcess().Modules; var modules = CurrentProcessModules.ModuleCollection;
for (var i = 0; i < modules.Count; i++) for (var i = 0; i < modules.Count; i++)
{ {
if (p < modules[i].BaseAddress) continue; if (p < modules[i].BaseAddress) continue;