Кастомные memory backend
Как строить цепочки IMemoryBackend и добавлять поверх них кэширование, трекинг и другие возможности
Кастомные IMemoryBackend и их цепочки
IMemoryBackend в EyeAuras маленький, и это хорошо: его удобно использовать не только как "финальный reader", но и как слой-декоратор. Поэтому поверх одного backend'а легко навесить статистику, логирование, кэширование или временную трассировку.
Вместо такой схемы:
MemoryAccessor -> DriverBackend
можно собрать такую:
MemoryAccessor -> CacheBackend -> StatsBackend -> LoggingBackend -> DriverBackend
Временно поставить декоратор над существующей памятью
Если process уже принадлежит хосту, передайте его в обычный метод. Сначала остановите параллельные чтения: замена backend не является синхронизированной транзакцией.
static int ReadThroughDecorator(EyeAuras.Memory.IProcess process, long address)
{
var original = process.Memory.Backend;
using var tracing = new MyTracingBackend(original);
process.Memory.UseMemoryBackend(tracing);
try
{
return process.Memory.Read<int>(address);
}
finally
{
process.Memory.UseMemoryBackend(original);
}
}
Полный класс MyTracingBackend ниже пока просто перенаправляет вызовы, поэтому результат совпадает с обычным чтением int. Добавлять сбор статистики можно в его raw-методы. Он заимствует inner, а не освобождает чужой reader.
В EyeAuras 20fd3d4 UseMemoryBackend(IMemoryBackend) только меняет ссылку. Он не освобождает старый backend и не принимает новый во владение; сам MemoryAccessor.Dispose() также не закрывает backend. Восстанавливайте ссылку до освобождения временного декоратора. Представление от MemoryOfModule обращается к исходной памяти и увидит подмену, но это не обещание для любого независимо созданного accessor. Адреса raw-вызовов остаются абсолютными; BaseAddress не прибавляется автоматически.
Две важные точки API
У IProcessMemory есть всё, что нужно для такой подмены:
var currentBackend = process.Memory.Backend;
process.Memory.UseMemoryBackend(new MyTracingBackend(currentBackend));
После этого новый low-level слой начинает использоваться и для обычного чтения, и для MemoryOfModule(...), и для ReadExports(), ReadImports(), сигнатур и code cave.
Минимальный decorator backend
using System;
using EyeAuras.Memory;
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);
}
// В этом варианте inner принадлежит вызывающему коду.
public void Dispose() { }
}
Это базовый шаблон. Дальше вы добавляете свой код до или после вызова inner.
Самые полезные сценарии
- статистика — сколько чтений, записей и байт реально проходит через backend
- suspicious logging — быстро ловить
null, near-null, мусорные и подозрительные адреса - page cache — уменьшать число дорогих чтений у драйвера, DMA или удалённого bridge
Именно такие backend'ы уже были в ScriptShock.MemoryApi: StatsMemoryBackend, SuspiciousAddressLoggingBackend, PageCachingMemoryBackend, CompositeMemoryBackend. Это исторические примеры, не обещание наличия этих классов в базовом SDK.
Порядок слоёв важен
Эти две цепочки отвечают на разные вопросы:
Stats -> Cache -> Driver
Cache -> Stats -> Driver
В первом случае статистика видит итоговую пользовательскую картину. Во втором — только реальные обращения, которые дошли до драйвера. Обычно ближе к драйверу кладут то, что должно видеть физические обращения, а ближе к MemoryAccessor то, что должно видеть финальное поведение.
О чём легко забыть
- Не меняйте семантику
TryReadMemory(...)иTryWriteMemory(...)без явной причины. - Если есть кэш, запись должна инвалидировать затронутые диапазоны.
- Заранее выберите владельца нижнего слоя. Заимствующий декоратор не закрывает
inner; владеющий закрывает его ровно один раз. - Чем умнее backend, тем важнее thread-safety.