[scudo] Show the resident pages from the secondary cache. (#170568)

Move the test utility to get the resident pages into linux.cpp.

Add some specific tests for this function.

Add displaying of resident pages from in the secondary cache when
calling getStats.

NOKEYCHECK=True
GitOrigin-RevId: efd472c88d7bed50615eab6db844a766d8f1964f
diff --git a/lib/scudo/standalone/common.h b/lib/scudo/standalone/common.h
index 8adcebd..9097532 100644
--- a/lib/scudo/standalone/common.h
+++ b/lib/scudo/standalone/common.h
@@ -188,6 +188,11 @@
 constexpr uptr MaxRandomLength = 256U;
 bool getRandom(void *Buffer, uptr Length, bool Blocking = false);
 
+// Get the total number of resident pages for BaseAddress to BaseAddress + Size.
+// This function can run slowly, and is only expected to be called
+// from getStats functions where performance does not matter.
+u64 getResidentPages(uptr BaseAddress, uptr Size);
+
 // Platform memory mapping functions.
 
 #define MAP_ALLOWNOMEM (1U << 0)
diff --git a/lib/scudo/standalone/fuchsia.cpp b/lib/scudo/standalone/fuchsia.cpp
index 2144f1b..7c15bff 100644
--- a/lib/scudo/standalone/fuchsia.cpp
+++ b/lib/scudo/standalone/fuchsia.cpp
@@ -231,6 +231,9 @@
 
 void setAbortMessage(const char *Message) {}
 
+// TODO: Implement for fuchsia.
+u64 getResidentPages(uptr BaseAddress, uptr Size) { return 0; }
+
 } // namespace scudo
 
 #endif // SCUDO_FUCHSIA
diff --git a/lib/scudo/standalone/linux.cpp b/lib/scudo/standalone/linux.cpp
index 57171ed..0c26be2 100644
--- a/lib/scudo/standalone/linux.cpp
+++ b/lib/scudo/standalone/linux.cpp
@@ -246,6 +246,38 @@
     android_set_abort_message(Message);
 }
 
+u64 getResidentPages(uptr BaseAddress, uptr Size) {
+  unsigned char PageData[256];
+
+  uptr PageSize = getPageSizeCached();
+  uptr PageSizeLog = getPageSizeLogCached();
+
+  // Make sure the address is page aligned.
+  uptr CurrentAddress = BaseAddress & ~(PageSize - 1);
+  uptr LastAddress = roundUp(BaseAddress + Size, PageSize);
+  u64 ResidentPages = 0;
+  while (CurrentAddress < LastAddress) {
+    uptr Length = LastAddress - CurrentAddress;
+    if ((Length >> PageSizeLog) > sizeof(PageData)) {
+      Length = sizeof(PageData) << PageSizeLog;
+    }
+    if (mincore(reinterpret_cast<void *>(CurrentAddress), Length, PageData) ==
+        -1) {
+      ScopedString Str;
+      Str.append("mincore failed: %s\n", strerror(errno));
+      Str.output();
+      break;
+    }
+    for (size_t I = 0; I < Length >> PageSizeLog; ++I) {
+      if (PageData[I])
+        ++ResidentPages;
+    }
+    CurrentAddress += Length;
+  }
+
+  return ResidentPages;
+}
+
 } // namespace scudo
 
 #endif // SCUDO_LINUX
diff --git a/lib/scudo/standalone/secondary.h b/lib/scudo/standalone/secondary.h
index 2509db2..04e33c0 100644
--- a/lib/scudo/standalone/secondary.h
+++ b/lib/scudo/standalone/secondary.h
@@ -9,6 +9,12 @@
 #ifndef SCUDO_SECONDARY_H_
 #define SCUDO_SECONDARY_H_
 
+#ifndef __STDC_FORMAT_MACROS
+// Ensure PRId64 macro is available
+#define __STDC_FORMAT_MACROS 1
+#endif
+#include <inttypes.h>
+
 #include "chunk.h"
 #include "common.h"
 #include "list.h"
@@ -221,9 +227,17 @@
 
     for (CachedBlock &Entry : LRUEntries) {
       Str->append("  StartBlockAddress: 0x%zx, EndBlockAddress: 0x%zx, "
-                  "BlockSize: %zu %s\n",
+                  "BlockSize: %zu%s",
                   Entry.CommitBase, Entry.CommitBase + Entry.CommitSize,
-                  Entry.CommitSize, Entry.Time == 0 ? "[R]" : "");
+                  Entry.CommitSize, Entry.Time == 0 ? " [R]" : "");
+#if SCUDO_LINUX
+      // getResidentPages only works on linux systems currently.
+      Str->append(", Resident Pages: %" PRId64 "/%zu\n",
+                  getResidentPages(Entry.CommitBase, Entry.CommitSize),
+                  Entry.CommitSize / getPageSizeCached());
+#else
+      Str->append("\n");
+#endif
     }
   }
 
diff --git a/lib/scudo/standalone/tests/common_test.cpp b/lib/scudo/standalone/tests/common_test.cpp
index 71f810e..c037f49 100644
--- a/lib/scudo/standalone/tests/common_test.cpp
+++ b/lib/scudo/standalone/tests/common_test.cpp
@@ -21,50 +21,74 @@
 
 namespace scudo {
 
-static void getResidentPages(void *BaseAddress, size_t TotalPages,
-                             size_t *ResidentPages) {
-  std::vector<unsigned char> Pages(TotalPages, 0);
-  ASSERT_EQ(
-      0, mincore(BaseAddress, TotalPages * getPageSizeCached(), Pages.data()))
-      << strerror(errno);
-  *ResidentPages = 0;
-  for (unsigned char Value : Pages) {
-    if (Value & 1) {
-      ++*ResidentPages;
-    }
-  }
+TEST(ScudoCommonTest, VerifyGetResidentPages) {
+  if (!SCUDO_LINUX)
+    GTEST_SKIP() << "Only valid on linux systems.";
+
+  constexpr uptr NumPages = 512;
+  const uptr SizeBytes = NumPages * getPageSizeCached();
+
+  MemMapT MemMap;
+  ASSERT_TRUE(MemMap.map(/*Addr=*/0U, SizeBytes, "ResidentMemorySize"));
+  ASSERT_NE(MemMap.getBase(), 0U);
+
+  // Only android seems to properly detect when single pages are touched.
+#if SCUDO_ANDROID
+  // Verify nothing should be mapped in right after the map is created.
+  EXPECT_EQ(0U, getResidentPages(MemMap.getBase(), SizeBytes));
+
+  // Touch a page.
+  u8 *Data = reinterpret_cast<u8 *>(MemMap.getBase());
+  Data[0] = 1;
+  EXPECT_EQ(1U, getResidentPages(MemMap.getBase(), SizeBytes));
+
+  // Touch a non-consective page.
+  Data[getPageSizeCached() * 2] = 1;
+  EXPECT_EQ(2U, getResidentPages(MemMap.getBase(), SizeBytes));
+
+  // Touch a page far enough that the function has to make multiple calls
+  // to mincore.
+  Data[getPageSizeCached() * 300] = 1;
+  EXPECT_EQ(3U, getResidentPages(MemMap.getBase(), SizeBytes));
+
+  // Touch another page in the same range to make sure the second
+  // read is working.
+  Data[getPageSizeCached() * 400] = 1;
+  EXPECT_EQ(4U, getResidentPages(MemMap.getBase(), SizeBytes));
+#endif
+
+  // Now write the whole thing.
+  memset(reinterpret_cast<void *>(MemMap.getBase()), 1, SizeBytes);
+  EXPECT_EQ(NumPages, getResidentPages(MemMap.getBase(), SizeBytes));
+
+  MemMap.unmap();
 }
 
-// Fuchsia needs getResidentPages implementation.
-TEST(ScudoCommonTest, SKIP_ON_FUCHSIA(ResidentMemorySize)) {
-  // Make sure to have the size of the map on a page boundary.
-  const uptr PageSize = getPageSizeCached();
-  const size_t NumPages = 1000;
-  const uptr SizeBytes = NumPages * PageSize;
+TEST(ScudoCommonTest, VerifyReleasePagesToOS) {
+  if (!SCUDO_LINUX)
+    GTEST_SKIP() << "Only valid on linux systems.";
+
+  constexpr uptr NumPages = 1000;
+  const uptr SizeBytes = NumPages * getPageSizeCached();
 
   MemMapT MemMap;
   ASSERT_TRUE(MemMap.map(/*Addr=*/0U, SizeBytes, "ResidentMemorySize"));
   ASSERT_NE(MemMap.getBase(), 0U);
 
   void *P = reinterpret_cast<void *>(MemMap.getBase());
-  size_t ResidentPages;
-  getResidentPages(P, NumPages, &ResidentPages);
-  EXPECT_EQ(0U, ResidentPages);
+  EXPECT_EQ(0U, getResidentPages(MemMap.getBase(), SizeBytes));
 
   // Make the entire map resident.
   memset(P, 1, SizeBytes);
-  getResidentPages(P, NumPages, &ResidentPages);
-  EXPECT_EQ(NumPages, ResidentPages);
+  EXPECT_EQ(NumPages, getResidentPages(MemMap.getBase(), SizeBytes));
 
   // Should release the memory to the kernel immediately.
   MemMap.releasePagesToOS(MemMap.getBase(), SizeBytes);
-  getResidentPages(P, NumPages, &ResidentPages);
-  EXPECT_EQ(0U, ResidentPages);
+  EXPECT_EQ(0U, getResidentPages(MemMap.getBase(), SizeBytes));
 
   // Make the entire map resident again.
   memset(P, 1, SizeBytes);
-  getResidentPages(P, NumPages, &ResidentPages);
-  EXPECT_EQ(NumPages, ResidentPages);
+  EXPECT_EQ(NumPages, getResidentPages(MemMap.getBase(), SizeBytes));
 
   MemMap.unmap();
 }
diff --git a/lib/scudo/standalone/trusty.cpp b/lib/scudo/standalone/trusty.cpp
index 26b349c..fb1f279 100644
--- a/lib/scudo/standalone/trusty.cpp
+++ b/lib/scudo/standalone/trusty.cpp
@@ -113,6 +113,9 @@
 
 void setAbortMessage(UNUSED const char *Message) {}
 
+// Trusty doesn't support a full linux system, so don't implement this yet.
+u64 getResidentPages(UNUSED uptr BaseAddress, UNUSED uptr Size) { return 0; }
+
 } // namespace scudo
 
 #endif // SCUDO_TRUSTY