Bitdefender Hypervisor Memory Introspection
gpacache.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "gpacache.h"
6 #include "glue.h"
7 #include "introcrt.h"
8 
9 
10 static __inline DWORD
12  _In_ PGPA_CACHE Cache,
13  _In_ QWORD Gpa
14  )
27 {
28  return (Gpa >> 12) & (Cache->LinesCount - 1);
29 }
30 
31 
32 static INTSTATUS
34  _In_ PGPA_CACHE Cache,
36  )
49 {
50  GPA_CACHE_VICTIM *pVictim = HpAllocWithTag(sizeof(*pVictim), IC_TAG_GPCV);
51  if (NULL == pVictim)
52  {
54  }
55 
56  memcpy(&pVictim->Entry, Entry, sizeof(GPA_CACHE_ENTRY));
57 
58  InsertTailList(&Cache->Victims, &pVictim->Link);
59 
60  return INT_STATUS_SUCCESS;
61 }
62 
63 
64 void
66  _In_ PGPA_CACHE Cache
67  )
73 {
74  DWORD i, j;
75  QWORD totalHitCount = 0;
76 
77  LOG("Gpa Cache:\n");
78 
79  NLOG("Number of lines: %d\n", Cache->LinesCount);
80  NLOG("Entries per line: %d\n", Cache->EntriesCount);
81 
82  // Dump the entries inside the Cache
83  for (i = 0; i < Cache->LinesCount; i++)
84  {
85  QWORD lineHitCount = 0;
86 
87  for (j = 0; j < Cache->EntriesCount; j++)
88  {
89  if (Cache->Lines[i].Entries[j].Valid)
90  {
91  NLOG("%04d:%02d: 0x%016llx:%p, %04d %04d %d\n",
92  i, j,
93  Cache->Lines[i].Entries[j].Gpa,
94  Cache->Lines[i].Entries[j].Hva,
95  Cache->Lines[i].Entries[j].HitCount,
96  Cache->Lines[i].Entries[j].UseCount,
97  Cache->Lines[i].Entries[j].Valid);
98 
99  totalHitCount += Cache->Lines[i].Entries[j].HitCount;
100  lineHitCount += Cache->Lines[i].Entries[j].HitCount;
101  }
102  }
103 
104  if (lineHitCount > 0)
105  {
106  NLOG(" HitCount: %lld\n", lineHitCount);
107  }
108  }
109 
110  NLOG("TOTALHC: %lld\n", totalHitCount);
111 }
112 
113 
114 INTSTATUS
116  _Inout_ PGPA_CACHE *Cache,
117  _In_ DWORD LinesCount,
118  _In_ DWORD EntriesCount
119  )
135 {
136  INTSTATUS status;
137  GPA_CACHE *pCache;
138 
139  if (NULL == Cache)
140  {
142  }
143 
144  // LinesCount must be power of two.
145  if (0 != (LinesCount & (LinesCount - 1)))
146  {
148  }
149 
150  pCache = HpAllocWithTag(sizeof(*pCache), IC_TAG_GPCA);
151  if (NULL == pCache)
152  {
154  }
155 
156  pCache->LinesCount = LinesCount;
157  pCache->EntriesCount = EntriesCount;
158 
159  pCache->Lines = HpAllocWithTag(sizeof(*pCache->Lines) * LinesCount, IC_TAG_GPCA);
160  if (NULL == pCache->Lines)
161  {
163  goto cleanup_and_exit;
164  }
165 
166  for (DWORD i = 0; i < LinesCount; i++)
167  {
168  pCache->Lines[i].Entries = HpAllocWithTag(sizeof(*pCache->Lines[i].Entries) * EntriesCount, IC_TAG_GPCA);
169  if (NULL == pCache->Lines[i].Entries)
170  {
172  goto cleanup_and_exit;
173  }
174  }
175 
176  InitializeListHead(&pCache->Victims);
177 
178  status = INT_STATUS_SUCCESS;
179 
180 cleanup_and_exit:
181  if (!INT_SUCCESS(status))
182  {
183  if (NULL != pCache)
184  {
185  if (NULL != pCache->Lines)
186  {
187  for (DWORD i = 0; i < LinesCount; i++)
188  {
189  if (NULL != pCache->Lines[i].Entries)
190  {
192  }
193  }
194 
196  }
197 
199  }
200  }
201 
202  *Cache = pCache;
203 
204  return status;
205 }
206 
207 
208 INTSTATUS
210  _In_ PGPA_CACHE *Cache
211  )
224 {
225  DWORD i, j;
226  PGPA_CACHE pCache;
227  LIST_ENTRY *list;
228 
229  if (NULL == Cache)
230  {
232  }
233 
234  pCache = *Cache;
235  if (NULL == pCache)
236  {
238  }
239 
240  for (i = 0; i < pCache->LinesCount; i++)
241  {
242  for (j = 0; j < pCache->EntriesCount; j++)
243  {
244  if (pCache->Lines[i].Entries[j].Valid)
245  {
246  IntPhysMemUnmap(&pCache->Lines[i].Entries[j].Hva);
247  }
248  }
249 
251  }
252 
254 
255  list = pCache->Victims.Flink;
256 
257  while (list != &pCache->Victims)
258  {
260 
261  list = list->Flink;
262 
263  if (pVictim->Entry.Valid && (pVictim->Entry.UseCount > 0) && (NULL != pVictim->Entry.Hva))
264  {
265  IntPhysMemUnmap(&pVictim->Entry.Hva);
266  }
267 
268  RemoveEntryList(&pVictim->Link);
269 
271  }
272 
274 
275  return INT_STATUS_SUCCESS;
276 }
277 
278 
279 static INTSTATUS
281  _In_ PGPA_CACHE Cache,
282  _In_ QWORD Gpa,
283  _Out_ PGPA_CACHE_ENTRY *Entry
284  )
303 {
304  INTSTATUS status;
305  DWORD line, entry, minHits;
306 
307  entry = 0;
308  line = IntGpaCacheHashLine(Cache, Gpa);
309  minHits = 0xFFFFFFFF;
310 
311  // GPA must be 4K aligned.
312  Gpa &= PHYS_PAGE_MASK;
313 
314  for (DWORD i = 0; i < Cache->EntriesCount; i++)
315  {
316  if (!Cache->Lines[line].Entries[i].Valid)
317  {
318  entry = i;
319  break;
320  }
321  else
322  {
323  // Select the entry with the lowest LFU score, as long as it is not in use.
324  if ((minHits > Cache->Lines[line].Entries[i].HitCount) && (0 == Cache->Lines[line].Entries[i].UseCount))
325  {
326  minHits = Cache->Lines[line].Entries[i].HitCount;
327  entry = i;
328  }
329  }
330  }
331 
332  // Couldn't decide upon selecting a proper entry to evict, select a random one.
333  if (minHits == 0xFFFFFFFF)
334  {
335  entry = __rdtsc() % Cache->EntriesCount;
336  }
337 
338  // If the entry is in use, add it to the victim cache, and wait for it to be released.
339  if (Cache->Lines[line].Entries[entry].Valid && (Cache->Lines[line].Entries[entry].UseCount > 0))
340  {
341  TRACE("[GPACACHE] Adding victim entry for GPA %llx, use count %d...\n",
342  Cache->Lines[line].Entries[entry].Gpa, Cache->Lines[line].Entries[entry].UseCount);
343 
344  status = IntGpaCacheAddVictim(Cache, &Cache->Lines[line].Entries[entry]);
345  if (!INT_SUCCESS(status))
346  {
347  ERROR("[ERROR] IntGpaCacheAddVictim failed: 0x%08x\n", status);
348  goto cleanup_and_exit;
349  }
350  }
351  else if (Cache->Lines[line].Entries[entry].Valid && (Cache->Lines[line].Entries[entry].UseCount == 0))
352  {
353  IntPhysMemUnmap(&Cache->Lines[line].Entries[entry].Hva);
354  }
355 
356  // Map it and leave
357  status = IntPhysMemMap(Gpa, PAGE_SIZE, PHYS_MAP_FLG_NO_FASTMAP, &Cache->Lines[line].Entries[entry].Hva);
358 
359  if (INT_SUCCESS(status))
360  {
361  Cache->Lines[line].Entries[entry].Valid = TRUE;
362  Cache->Lines[line].Entries[entry].HitCount = 0;
363  Cache->Lines[line].Entries[entry].UseCount = 0;
364  Cache->Lines[line].Entries[entry].Gpa = Gpa;
365  }
366  else
367  {
368  Cache->Lines[line].Entries[entry].Valid = FALSE;
369  Cache->Lines[line].Entries[entry].Gpa = 0;
370  }
371 
372  *Entry = &Cache->Lines[line].Entries[entry];
373 
374 cleanup_and_exit:
375  return status;
376 }
377 
378 
379 static INTSTATUS
381  _In_ PGPA_CACHE Cache,
382  _In_ QWORD Gpa,
383  _Out_ PGPA_CACHE_ENTRY *Entry
384  )
397 {
398  DWORD line, i;
399  BOOLEAN bFound;
400  LIST_ENTRY *list;
401 
402  bFound = FALSE;
403 
404  line = IntGpaCacheHashLine(Cache, Gpa);
405 
406  // Gpa must be page-aligned.
407  Gpa &= PHYS_PAGE_MASK;
408 
409  for (i = 0; i < Cache->EntriesCount; i++)
410  {
411  if (Cache->Lines[line].Entries[i].Valid && (Cache->Lines[line].Entries[i].Gpa == Gpa))
412  {
413  *Entry = &Cache->Lines[line].Entries[i];
414  bFound = TRUE;
415  break;
416  }
417  }
418 
419  if (!bFound)
420  {
421  // Search the victim cache. If an entry is found in the victim cache, it could be brought back to the
422  // main cache.
423  list = Cache->Victims.Flink;
424 
425  while (list != &Cache->Victims)
426  {
428 
429  list = list->Flink;
430 
431  if (pVictim->Entry.Valid && pVictim->Entry.Gpa == Gpa)
432  {
433  TRACE("[GPACACHE] Entry %llx found in victim cache, will not add it back...\n", Gpa);
434  *Entry = &pVictim->Entry;
435  bFound = TRUE;
436  break;
437  }
438  }
439  }
440 
441  if (bFound)
442  {
443  return INT_STATUS_SUCCESS;
444  }
445 
446  return INT_STATUS_NOT_FOUND;
447 }
448 
449 
450 INTSTATUS
452  _In_ PGPA_CACHE Cache,
453  _In_ QWORD Gpa,
454  _Out_ void **Hva
455  )
472 {
473  INTSTATUS status;
474  PGPA_CACHE_ENTRY entry;
475 
476  if (NULL == Cache)
477  {
479  }
480 
481  if (NULL == Hva)
482  {
484  }
485 
486  entry = NULL;
487 
488  status = IntGpaCacheLookupEntry(Cache, Gpa, &entry);
489  if (!INT_SUCCESS(status))
490  {
491  status = IntGpaCacheAddEntry(Cache, Gpa, &entry);
492  if (!INT_SUCCESS(status))
493  {
494  return status;
495  }
496  }
497 
498  entry->HitCount++;
499  entry->UseCount++;
500 
501  *Hva = entry->Hva;
502 
503  return INT_STATUS_SUCCESS;
504 }
505 
506 
507 INTSTATUS
509  _In_ PGPA_CACHE Cache,
510  _In_ QWORD Gpa,
511  _In_ DWORD Size,
512  _Out_ PBYTE Buffer
513  )
531 {
532  INTSTATUS status;
533  PGPA_CACHE_ENTRY entry;
534  PBYTE data;
535  DWORD offset;
536 
537  if (NULL == Cache)
538  {
540  }
541 
542  if (NULL == Buffer)
543  {
545  }
546 
547  entry = NULL;
548  offset = Gpa & PAGE_OFFSET;
549 
550  if (offset + Size > PAGE_SIZE)
551  {
553  }
554 
555  status = IntGpaCacheLookupEntry(Cache, Gpa, &entry);
556  if (!INT_SUCCESS(status))
557  {
558  status = IntGpaCacheAddEntry(Cache, Gpa, &entry);
559  if (!INT_SUCCESS(status))
560  {
561  return status;
562  }
563  }
564 
565  entry->HitCount++;
566 
567  data = (PBYTE)entry->Hva + offset;
568 
569  switch (Size)
570  {
571  case 8:
572  *(PQWORD)Buffer = *(PQWORD)data;
573  break;
574  case 4:
575  *(PDWORD)Buffer = *(PDWORD)data;
576  break;
577  case 2:
578  *(PWORD)Buffer = *(PWORD)data;
579  break;
580  case 1:
581  *Buffer = *data;
582  break;
583  default:
584  memcpy(Buffer, data, Size);
585  break;
586  }
587 
588  return INT_STATUS_SUCCESS;
589 }
590 
591 
592 INTSTATUS
594  _In_ PGPA_CACHE Cache,
595  _In_ QWORD Gpa,
596  _In_ DWORD Size,
597  _In_ PBYTE Buffer
598  )
616 {
617  INTSTATUS status;
618  PGPA_CACHE_ENTRY entry;
619  PBYTE data;
620  DWORD offset;
621 
622  if (NULL == Cache)
623  {
625  }
626 
627  if (NULL == Buffer)
628  {
630  }
631 
632  entry = NULL;
633  offset = Gpa & PAGE_OFFSET;
634 
635  if (offset + Size > PAGE_SIZE)
636  {
638  }
639 
640  status = IntGpaCacheLookupEntry(Cache, Gpa, &entry);
641  if (!INT_SUCCESS(status))
642  {
643  status = IntGpaCacheAddEntry(Cache, Gpa, &entry);
644  if (!INT_SUCCESS(status))
645  {
646  return status;
647  }
648  }
649 
650  entry->HitCount++;
651 
652  data = (PBYTE)entry->Hva + offset;
653 
654  switch (Size)
655  {
656  case 8:
657  *(PQWORD)data = *(PQWORD)Buffer;
658  break;
659  case 4:
660  *(PDWORD)data = *(PDWORD)Buffer;
661  break;
662  case 2:
663  *(PWORD)data = *(PWORD)Buffer;
664  break;
665  case 1:
666  *data = *Buffer;
667  break;
668  default:
669  memcpy(data, Buffer, Size);
670  break;
671  }
672 
673  return INT_STATUS_SUCCESS;
674 }
675 
676 
677 INTSTATUS
679  _In_ PGPA_CACHE Cache,
680  _In_ QWORD Gpa
681  )
698 {
699  DWORD line, i;
700  BOOLEAN bFound;
701  LIST_ENTRY *list;
702 
703  if (NULL == Cache)
704  {
706  }
707 
708  bFound = FALSE;
709 
710  line = IntGpaCacheHashLine(Cache, Gpa);
711 
712  // Gpa must be page-aligned.
713  Gpa &= PHYS_PAGE_MASK;
714 
715  for (i = 0; i < Cache->EntriesCount; i++)
716  {
717  if (Cache->Lines[line].Entries[i].Valid && (Cache->Lines[line].Entries[i].Gpa == Gpa))
718  {
719  if (Cache->Lines[line].Entries[i].UseCount > 0)
720  {
721  Cache->Lines[line].Entries[i].UseCount--;
722  }
723 
724  bFound = TRUE;
725  break;
726  }
727  }
728 
729  if (!bFound)
730  {
731  TRACE("[GPACACHE] Entry %llx not found in main cache, searching victim cache...\n", Gpa);
732 
733  list = Cache->Victims.Flink;
734 
735  while (list != &Cache->Victims)
736  {
738 
739  list = list->Flink;
740 
741  if (pVictim->Entry.Gpa == Gpa)
742  {
743  if (pVictim->Entry.UseCount > 0)
744  {
745  pVictim->Entry.UseCount--;
746  }
747 
748  TRACE("[GPACACHE] Entry %llx is victim, use = %d\n", Gpa, pVictim->Entry.UseCount);
749 
750  if (0 == pVictim->Entry.UseCount)
751  {
752  IntPhysMemUnmap(&pVictim->Entry.Hva);
753 
754  RemoveEntryList(&pVictim->Link);
755 
757  }
758 
759  bFound = TRUE;
760 
761  break;
762  }
763  }
764  }
765 
766  if (bFound)
767  {
768  return INT_STATUS_SUCCESS;
769  }
770 
771  return INT_STATUS_NOT_FOUND;
772 }
773 
774 
775 INTSTATUS
777  _In_ PGPA_CACHE Cache
778  )
791 {
792  INTSTATUS status;
793  DWORD i, j;
794 
795  if (NULL == Cache)
796  {
798  }
799 
800  for (i = 0; i < Cache->LinesCount; i++)
801  {
802  for (j = 0; j < Cache->EntriesCount; j++)
803  {
804  if (Cache->Lines[i].Entries[j].Valid)
805  {
806  if (Cache->Lines[i].Entries[j].UseCount == 0)
807  {
808  IntPhysMemUnmap(&Cache->Lines[i].Entries[j].Hva);
809  }
810  else
811  {
812  status = IntGpaCacheAddVictim(Cache, &Cache->Lines[i].Entries[j]);
813  if (!INT_SUCCESS(status))
814  {
815  ERROR("[ERROR] IntGpaCacheAddVictim failed: 0x%08x\n", status);
816  }
817  }
818 
819  Cache->Lines[i].Entries[j].Valid = FALSE;
820  Cache->Lines[i].Entries[j].Gpa = 0;
821  Cache->Lines[i].Entries[j].HitCount = 0;
822  Cache->Lines[i].Entries[j].UseCount = 0;
823  }
824  }
825  }
826 
827  return INT_STATUS_SUCCESS;
828 }
_Bool BOOLEAN
Definition: intro_types.h:58
#define _Out_
Definition: intro_sal.h:22
#define CONTAINING_RECORD(List, Type, Member)
Definition: introlists.h:36
static INTSTATUS IntGpaCacheLookupEntry(PGPA_CACHE Cache, QWORD Gpa, PGPA_CACHE_ENTRY *Entry)
Search an entry in the GPA cache.
Definition: gpacache.c:380
uint16_t * PWORD
Definition: intro_types.h:48
INTSTATUS IntGpaCacheRelease(PGPA_CACHE Cache, QWORD Gpa)
Release a previously used cached entry.
Definition: gpacache.c:678
#define _In_
Definition: intro_sal.h:21
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
GPA_CACHE_ENTRY Entry
The actual cache entry.
Definition: gpacache.h:40
void IntGpaCacheDump(PGPA_CACHE Cache)
Dumps the entire contents of the GPA cache.
Definition: gpacache.c:65
struct _LIST_ENTRY * Flink
Definition: introlists.h:20
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
DWORD UseCount
Reference count, incremented by calls to IntGpaCacheFindAndAdd.
Definition: gpacache.h:19
#define PAGE_OFFSET
Definition: pgtable.h:32
#define ERROR(fmt,...)
Definition: glue.h:62
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
#define PHYS_MAP_FLG_NO_FASTMAP
Indicates that IntPhysMemMap should not use the fast memory mapping mechanism.
Definition: glue.h:71
#define PHYS_PAGE_MASK
Definition: pgtable.h:38
int INTSTATUS
The status data type.
Definition: introstatus.h:24
#define INT_STATUS_NOT_FOUND
Definition: introstatus.h:284
INTSTATUS IntGpaCacheFindAndAdd(PGPA_CACHE Cache, QWORD Gpa, void **Hva)
Search for an entry in the GPA cache, and add it, if it wasn&#39;t found.
Definition: gpacache.c:451
#define LOG(fmt,...)
Definition: glue.h:61
uint32_t * PDWORD
Definition: intro_types.h:49
LIST_ENTRY Victims
List of victim entries evicted from the cache while UseCount is not 0.
Definition: gpacache.h:55
#define IC_TAG_GPCA
The GPA cache.
Definition: memtags.h:18
#define _Inout_
Definition: intro_sal.h:20
QWORD Gpa
Gpa this entry maps to.
Definition: gpacache.h:16
uint8_t * PBYTE
Definition: intro_types.h:47
static BOOLEAN RemoveEntryList(LIST_ENTRY *Entry)
Definition: introlists.h:87
unsigned long long QWORD
Definition: intro_types.h:53
#define TRUE
Definition: intro_types.h:30
#define INT_STATUS_INVALID_PARAMETER_4
Definition: introstatus.h:71
GPA_CACHE_LINE * Lines
Actual array of cache lines.
Definition: gpacache.h:53
LIST_ENTRY Link
Linked list entry.
Definition: gpacache.h:39
#define TRACE(fmt,...)
Definition: glue.h:58
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
static INTSTATUS IntGpaCacheAddVictim(PGPA_CACHE Cache, PGPA_CACHE_ENTRY Entry)
Add an entry in the victim cache.
Definition: gpacache.c:33
static void InsertTailList(LIST_ENTRY *ListHead, LIST_ENTRY *Entry)
Definition: introlists.h:135
static void InitializeListHead(LIST_ENTRY *ListHead)
Definition: introlists.h:69
#define PAGE_SIZE
Definition: common.h:70
uint32_t DWORD
Definition: intro_types.h:49
static uint64_t __rdtsc(void)
Definition: intrinsics.h:306
void * Hva
Host pointer which maps to Gpa.
Definition: gpacache.h:17
DWORD HitCount
Number of times this entry was accessed.
Definition: gpacache.h:18
static INTSTATUS IntGpaCacheAddEntry(PGPA_CACHE Cache, QWORD Gpa, PGPA_CACHE_ENTRY *Entry)
Add a new entry inside the GPA cache.
Definition: gpacache.c:280
static __inline DWORD IntGpaCacheHashLine(PGPA_CACHE Cache, QWORD Gpa)
Compute the line index for a given address.
Definition: gpacache.c:11
INTSTATUS IntGpaCacheFetchAndAdd(PGPA_CACHE Cache, QWORD Gpa, DWORD Size, PBYTE Buffer)
Fetch data from a cached entry, or add it to the cache, of not already present.
Definition: gpacache.c:508
INTSTATUS IntGpaCacheUnInit(PGPA_CACHE *Cache)
Uninit a GPA cache.
Definition: gpacache.c:209
#define NLOG(fmt,...)
Definition: glue.h:43
__must_check INTSTATUS IntPhysMemMap(QWORD PhysAddress, DWORD Length, DWORD Flags, void **HostPtr)
Maps a guest physical address inside Introcore VA space.
Definition: glue.c:338
#define INT_STATUS_NOT_INITIALIZED_HINT
Definition: introstatus.h:320
#define IC_TAG_GPCV
GPA cache victim.
Definition: memtags.h:19
#define INT_STATUS_INVALID_PARAMETER_1
Definition: introstatus.h:62
#define INT_STATUS_NOT_SUPPORTED
Definition: introstatus.h:287
INTSTATUS IntGpaCacheFlush(PGPA_CACHE Cache)
Flush the entire GPA cache.
Definition: gpacache.c:776
INTSTATUS IntGpaCacheInit(PGPA_CACHE *Cache, DWORD LinesCount, DWORD EntriesCount)
Initialize a GPA cache.
Definition: gpacache.c:115
DWORD LinesCount
Number of lines.
Definition: gpacache.h:50
GPA_CACHE_ENTRY * Entries
An array of cache entries.
Definition: gpacache.h:29
unsigned long long * PQWORD
Definition: intro_types.h:53
DWORD EntriesCount
Number of entries per line.
Definition: gpacache.h:51
INTSTATUS IntPhysMemUnmap(void **HostPtr)
Unmaps an address previously mapped with IntPhysMemMap.
Definition: glue.c:396
INTSTATUS IntGpaCachePatchAndAdd(PGPA_CACHE Cache, QWORD Gpa, DWORD Size, PBYTE Buffer)
Patch data in a cached entry, or add it to the cache, of not already present.
Definition: gpacache.c:593
Definition: kthread.c:14
BOOLEAN Valid
True if the entry is valid, false otherwise.
Definition: gpacache.h:20
#define FALSE
Definition: intro_types.h:34
#define INT_STATUS_INSUFFICIENT_RESOURCES
Definition: introstatus.h:281
#define INT_STATUS_INVALID_PARAMETER_3
Definition: introstatus.h:68