Кастомные memory backend

Как строить цепочки IMemoryBackend и добавлять поверх них кэширование, трекинг и другие возможности

Кастомные IMemoryBackend и их цепочки

IMemoryBackend в EyeAuras маленький, и это хорошо: его удобно использовать не только как "финальный reader", но и как слой-декоратор. Поэтому поверх одного backend'а легко навесить статистику, логирование, кэширование или временную трассировку.

Вместо такой схемы:

MemoryAccessor -> DriverBackend

можно собрать такую:

MemoryAccessor -> CacheBackend -> StatsBackend -> LoggingBackend -> DriverBackend

Две важные точки API

У IProcessMemory есть всё, что нужно для такой подмены:

csharp
var currentBackend = process.Memory.Backend;
process.Memory.UseMemoryBackend(new MyWrappedBackend(currentBackend));

После этого новый low-level слой начинает использоваться и для обычного чтения, и для MemoryOfModule(...), и для ReadExports(), ReadImports(), сигнатур и code cave.

Минимальный decorator backend

csharp
internal sealed class MyTracingBackend : IMemoryBackend
{
    private readonly IMemoryBackend inner;

    public MyTracingBackend(IMemoryBackend inner) => this.inner = inner;

    public bool IsValid => inner.IsValid;
    public long BaseAddress => inner.BaseAddress;
    public int MemorySize => inner.MemorySize;

    public bool TryReadMemory(IntPtr address, Span<byte> target)
    {
        return inner.TryReadMemory(address, target);
    }

    public bool TryWriteMemory(IntPtr address, ReadOnlySpan<byte> source)
    {
        return inner.TryWriteMemory(address, source);
    }

    public void Dispose() => inner.Dispose();
}

Это базовый шаблон. Дальше вы добавляете свой код до или после вызова inner.

Самые полезные сценарии

  • статистика — сколько чтений, записей и байт реально проходит через backend
  • suspicious logging — быстро ловить null, near-null, мусорные и подозрительные адреса
  • page cache — уменьшать число дорогих чтений у драйвера, DMA или удалённого bridge

Именно такие backend'ы уже были в ScriptShock.MemoryApi: StatsMemoryBackend, SuspiciousAddressLoggingBackend, PageCachingMemoryBackend, CompositeMemoryBackend.

Порядок слоёв важен

Эти две цепочки отвечают на разные вопросы:

text
Stats -> Cache -> Driver
Cache -> Stats -> Driver

В первом случае статистика видит итоговую пользовательскую картину. Во втором — только реальные обращения, которые дошли до драйвера. Обычно ближе к драйверу кладут то, что должно видеть физические обращения, а ближе к MemoryAccessor то, что должно видеть финальное поведение.

О чём легко забыть

  • Не меняйте семантику TryReadMemory(...) и TryWriteMemory(...) без явной причины.
  • Если есть кэш, запись должна инвалидировать затронутые диапазоны.
  • Dispose() должен корректно пробрасываться вниз.
  • Чем умнее backend, тем важнее thread-safety.

Что читать дальше