вторник, 3 апреля 2018 г.

Каким алгоритмом жмется память в win10 при memory compression?

Таким странным вопросом я озадачился, просматривая видео про memory compression https://channel9.msdn.com/Blogs/Seth-Juarez/Memory-Compression-in-Windows-10-RTM.

Любопытство не порок, а вполне себе мотиватор. Поэтому я, недолго думая, взял отладчик в руки и углубился в ядро десятки.
Чтобы минимизировать потерю времени на анализ, я подумал над тем, о чем говорилось в видео.

Запись в store с сжатыми страничками идет асинхронно, то есть искать это можно, но это долго и не эффективно.
А вот получение странички из store идет синхронно при hard fault'е.

Соответственно, искать стоит не то место, где страничка сжимается, а место, где она разжимается в памяти.
Страничные фолты обрабатываются через прерывания, имя функции обработчика page fault'a я не помнил, но ничего мешает сдампить idt и вспомнить:

kd> !idt

Dumping IDT:

...
0c:    fffff8030be0b900 nt!KiStackFault
0d:    fffff8030be0ba40 nt!KiGeneralProtectionFault
0e:    fffff8030be0bb40 nt!KiPageFault <================ обработчик
10:    fffff8030be0c140 nt!KiFloatingErrorFault
11:    fffff8030be0c2c0 nt!KiAlignmentFault
...

После небольшого анализа в IDA получаем цепочку KiPageFault => MiIssueHardFault => MiIssueHardFaultIo => SmPageRead.

Префикс Sm в функции SmPageRead для меня был новым, вспоминая видео и слово store, можно предположить что префикс Sm означает store manager или что-то вроде того.

Смотрим тело функции и видим, что это просто обертка над store manager'ом, который и рулит всеми операциями связанными с имплементацией memory compression фичи:

__int64 SmPageRead(union _MM_STORE_KEY *a1, unsigned __int64 a2)
{
...
  SmKeyConvert(a1, (union _SM_PAGE_KEY *)&v7);
  return SMKM_STORE_MGR<SM_TRAITS>::SmPageRead(v3, &v7, v2, v5, v4);
}

Далее оставалось просто построить граф вызовов функции и прочекать их содержимое на предмет кода, связанного с расжатием данных.

В итоге поиск привел к ST_STORE<SM_TRAITS>::StDmSinglePageCopy, которая вызывала RtlDecompressBufferEx. То есть использовалась стандартная функция.

Первый аргумент у неё CompressionFormat, осталось лишь поставить брекпойнт и выяснить значение в отладчике.
Бряк сработал, на х64 первые 4 аргумента передаются в регистрах, смотрим чему равен rcx, он равен трем.

Это соответствует флагам COMPRESSION_FORMAT_DEFAULT | COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_STANDARD.
То есть алгоритм сжатия - LZ compression. На этом моё любопытство было удовлетворено.