[Tizen] Add API to flush cache of PE files
authorashaurtaev <a.shaurtaev@partner.samsung.com>
Fri, 7 Jun 2024 17:54:04 +0000 (20:54 +0300)
committerGleb Balykov/Advanced System SW Lab /SRR/Staff Engineer/Samsung Electronics <g.balykov@samsung.com>
Sun, 23 Jun 2024 13:39:01 +0000 (16:39 +0300)
Co-authored-by: Gleb Balykov <g.balykov@samsung.com>
This API allows to drop clean pages of IL-only and R2R dlls
!This patch should be rebased with caution, see comment for PEImageLayout::CleanAllReadOnlyPages!

How to call from c# code via reflection:

Type type = typeof(AssemblyLoadContext);
MethodInfo method = type.GetMethod("FlushPECaches");
if (method != null)
  method.Invoke(null, new object[]{});

14 files changed:
src/coreclr/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs
src/coreclr/pal/inc/pal.h
src/coreclr/pal/src/include/pal/map.hpp
src/coreclr/pal/src/include/pal/module.h
src/coreclr/pal/src/loader/module.cpp
src/coreclr/pal/src/map/map.cpp
src/coreclr/vm/assemblynative.cpp
src/coreclr/vm/assemblynative.hpp
src/coreclr/vm/ecalllist.h
src/coreclr/vm/peimage.cpp
src/coreclr/vm/peimage.h
src/coreclr/vm/peimagelayout.cpp
src/coreclr/vm/peimagelayout.h
src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs

index 7b663d4..e005c59 100644 (file)
@@ -11,6 +11,9 @@ namespace System.Runtime.Loader
 {
     public partial class AssemblyLoadContext
     {
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        public static extern void FlushPECaches();
+
         [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
         private static extern IntPtr InitializeAssemblyLoadContext(IntPtr ptrAssemblyLoadContext, bool fRepresentsTPALoadContext, bool isCollectible);
 
index b84f9f8..06b5edf 100644 (file)
@@ -2717,6 +2717,22 @@ BOOL
 PALAPI
 PAL_LOADMarkSectionAsNotNeeded(void * ptr);
 
+/*++
+    PAL_LOADMarkDontNeedCleanPages
+    Try to mark clean pages from range as not needed.
+Parameters:
+    IN base - base address of pe image
+    IN ptr - start address of range
+    IN size - size of range in bytes
+Return value:
+    TRUE - success
+    FALSE - failure
+--*/
+PALIMPORT
+BOOL
+PALAPI
+PAL_LOADMarkDontNeedCleanPages(PVOID base, PVOID ptr, SIZE_T size);
+
 #ifdef UNICODE
 #define LoadLibrary LoadLibraryW
 #define LoadLibraryEx LoadLibraryExW
index bf83651..d97937e 100644 (file)
@@ -171,6 +171,18 @@ extern "C"
         returns TRUE if successful, FALSE otherwise
     --*/
     BOOL MAPMarkSectionAsNotNeeded(LPCVOID lpAddress);
+
+    /*++
+        MAPMarkDontNeedCleanPages - try to mark clean pages from range as not needed
+    Parameters:
+        IN lpBase - base address of pe image
+        IN lpAddress - start address of range
+        IN size - size of range in bytes
+    Return value:
+        TRUE - success
+        FALSE - failure
+    --*/
+    BOOL MAPMarkDontNeedCleanPages(LPCVOID lpBase, LPCVOID lpAddress, SIZE_T size);
 }
 
 namespace CorUnix
index 56d3015..bcbc094 100644 (file)
@@ -183,6 +183,19 @@ Return value:
 BOOL PAL_LOADUnloadPEFile(void * ptr);
 
 /*++
+    PAL_LOADMarkDontNeedCleanPages
+    Try to mark clean pages from range as not needed.
+Parameters:
+    IN base - base address of pe image
+    IN ptr - start address of range
+    IN size - size of range in bytes
+Return value:
+    TRUE - success
+    FALSE - failure
+--*/
+BOOL PAL_LOADMarkDontNeedCleanPages(PVOID base, PVOID ptr, SIZE_T size);
+
+/*++
 Function:
   PAL_LOADPreloadPEFile
 
index d0d4624..fe2f4a7 100644 (file)
@@ -1049,6 +1049,37 @@ PAL_LOADMarkSectionAsNotNeeded(void * ptr)
 }
 
 /*++
+    PAL_LOADMarkDontNeedCleanPages
+    Try to mark clean pages from range as not needed.
+Parameters:
+    IN base - base address of pe image
+    IN ptr - start address of range
+    IN size - size of range in bytes
+Return value:
+    TRUE - success
+    FALSE - failure
+--*/
+BOOL
+PALAPI
+PAL_LOADMarkDontNeedCleanPages(PVOID base, PVOID ptr, SIZE_T size)
+{
+    BOOL retval = FALSE;
+    ENTRY("PAL_LOADMarkDontNeedCleanPages (base=%p, ptr=%p, size=%llu)\n", base, ptr, (long long unsigned) size);
+
+    if (nullptr == base)
+    {
+        ERROR( "Invalid pointer value\n" );
+    }
+    else
+    {
+        retval = MAPMarkDontNeedCleanPages(base, ptr, size);
+    }
+
+    LOGEXIT("PAL_LOADMarkDontNeedCleanPages returns %d\n", retval);
+    return retval;
+}
+
+/*++
     PAL_GetSymbolModuleBase
 
     Get base address of the module containing a given symbol
index ce8a844..48f0253 100644 (file)
@@ -3237,3 +3237,85 @@ BOOL MAPMarkSectionAsNotNeeded(LPCVOID lpAddress)
     TRACE_(LOADER)("MAPMarkSectionAsNotNeeded returning %d\n", retval);
     return retval;
 }
+
+/*++
+    MAPMarkDontNeedCleanPages does similar thing as MAPMarkSectionAsNotNeeded, but marks specific read-only memory range instead of marking whole section
+Parameters:
+    IN lpBase - base address of pe image
+    IN lpAddress - start address of range
+    IN size - size of range in bytes
+Return value:
+    TRUE - success
+    FALSE - failure
+--*/
+BOOL MAPMarkDontNeedCleanPages(LPCVOID lpBase, LPCVOID lpAddress, SIZE_T size)
+{
+    TRACE_(LOADER)("MAPMarkDontNeedCleanPages(lpBase=%p, lpAddress=%p, size=%llu)\n", lpBase, lpAddress, (long long unsigned) size);
+
+    if ( NULL == lpBase )
+    {
+        ERROR_(LOADER)( "lpBase cannot be NULL\n" );
+        return FALSE;
+    }
+
+    BOOL retval = TRUE;
+    CPalThread * pThread = InternalGetCurrentThread();
+    InternalEnterCriticalSection(pThread, &mapping_critsec);
+    PLIST_ENTRY pLink, pLinkNext;
+
+    // MappedViewList contains records for all mmaped dlls (both il-only and r2r), 1 record for il-only, multiple records for r2r
+    for(pLink = MappedViewList.Flink;
+        pLink != &MappedViewList;
+        pLink = pLinkNext)
+    {
+        pLinkNext = pLink->Flink;
+        PMAPPED_VIEW_LIST pView = CONTAINING_RECORD(pLink, MAPPED_VIEW_LIST, Link);
+
+        // Skip views that do not correspond to lpBase dll
+        // pView->lpPEBaseAddress is set to 0, and pView->lpAddress is set to starting address of vma for il-only dll
+        // pView->lpPEBaseAddress is set to starting address of vma for R2R dll
+        if (!(pView->lpPEBaseAddress == lpBase || pView->lpPEBaseAddress == 0 && pView->lpAddress == lpBase))
+        {
+            continue;
+        }
+        // madvise for read only pages of PE files
+        if (pView->dwDesiredAccess != FILE_MAP_READ)
+        {
+            continue;
+        }
+
+        BYTE *pViewStart = (BYTE *)pView->lpAddress;
+        BYTE *pViewEnd = pViewStart + pView->NumberOfBytesToMap;
+        BYTE *pRangeStart = (lpAddress == NULL) ? pViewStart : (BYTE *)lpAddress;
+        BYTE *pRangeEnd = (size == 0) ? pViewEnd : pRangeStart + size;
+
+        if (pRangeEnd <= pRangeStart)
+        {
+            continue;
+        }
+        if (pViewEnd <= pRangeStart || pRangeEnd <= pViewStart)
+        {
+            continue;
+        }
+
+        BYTE *pStart = (pRangeStart <= pViewStart) ? pViewStart : pRangeStart;
+        BYTE *pEnd = (pRangeEnd <= pViewEnd) ? pRangeEnd : pViewEnd;
+        LPVOID lpStart = (LPVOID) pStart;
+        SIZE_T curSize = (SIZE_T)(pEnd - pStart);
+
+        if (lpStart != nullptr && curSize > 0)
+        {
+            if (-1 == madvise(lpStart, curSize, MADV_DONTNEED))
+            {
+                ERROR_(LOADER)("Unable to mark the section as NotNeeded.\n");
+                retval = FALSE;
+                break;
+            }
+        }
+    }
+
+    InternalLeaveCriticalSection(pThread, &mapping_critsec);
+
+    TRACE_(LOADER)("MAPMarkDontNeedCleanPages returning %d\n", retval);
+    return retval;
+}
\ No newline at end of file
index 6819867..62c6817 100644 (file)
@@ -1468,3 +1468,11 @@ BOOL QCALLTYPE AssemblyNative::IsApplyUpdateSupported()
 
     return result;
 }
+
+FCIMPL0(void, AssemblyNative::FlushPECaches)
+{
+    FCALL_CONTRACT;
+
+    PEImage::CleanAllReadOnlyPages();
+}
+FCIMPLEND
index 54ca987..3e30391 100644 (file)
@@ -32,7 +32,7 @@ public:
     void QCALLTYPE GetExecutingAssembly(QCall::StackCrawlMarkHandle stackMark, QCall::ObjectHandleOnStack retAssembly);
 
     static FCDECL0(FC_BOOL_RET, IsTracingEnabled);
-
+    static FCDECL0(void, FlushPECaches);
     //
     // instance FCALLs
     //
index 363ae6f..1e118a8 100644 (file)
@@ -464,6 +464,7 @@ FCFuncStart(gAssemblyLoadContextFuncs)
     QCFuncElement("TraceAssemblyResolveHandlerInvoked", AssemblyNative::TraceAssemblyResolveHandlerInvoked)
     QCFuncElement("TraceAssemblyLoadFromResolveHandlerInvoked", AssemblyNative::TraceAssemblyLoadFromResolveHandlerInvoked)
     QCFuncElement("TraceSatelliteSubdirectoryPathProbed", AssemblyNative::TraceSatelliteSubdirectoryPathProbed)
+    FCFuncElement("FlushPECaches", AssemblyNative::FlushPECaches)
 FCFuncEnd()
 
 FCFuncStart(gAssemblyNameFuncs)
index 7a38a55..90691f5 100644 (file)
@@ -153,6 +153,29 @@ void PEImage::GetAll(SArray<PEImage*> &images)
     }
 }
 
+void PEImage::CleanAllReadOnlyPages()
+{
+    CONTRACTL
+    {
+        THROWS;
+        GC_TRIGGERS;
+        MODE_ANY;
+    }
+    CONTRACTL_END;
+
+    CrstHolder holder(&s_hashLock);
+
+    for (PtrHashMap::PtrIterator i = s_Images->begin(); !i.end(); ++i)
+    {
+        PEImage *image = (PEImage*) i.GetValue();
+        PEImageLayoutHolder pLayout(image->GetLayout(PEImageLayout::LAYOUT_ANY, 0));
+        if ((PEImageLayout*)pLayout != NULL)
+        {
+            pLayout->CleanAllReadOnlyPages();
+        }
+    }
+}
+
 PEImage::~PEImage()
 {
     CONTRACTL
index 51b9dbc..5856465 100644 (file)
@@ -73,6 +73,8 @@ public:
     BOOL IsOpened();
     BOOL HasLoadedLayout();
 
+    static void CleanAllReadOnlyPages();
+
 public:
     // ------------------------------------------------------------
     // Public API
index 3fa0b8a..024f618 100644 (file)
@@ -358,6 +358,113 @@ void PEImageLayout::ApplyBaseRelocations(BOOL isRelocated)
     }
 }
 
+/*
+ CleanAllReadOnlyPages is called for PEImageLayout of each PEImage,
+ and it traverses all relocations and calls MAPMarkDontNeedCleanPages for all memory regions
+ between relocations (if there're no relocations, then simply calls MAPMarkDontNeedCleanPages for whole memory region of dll).
+ MAPMarkDontNeedCleanPages then traverses all views (i.e. mmaped regions) in MappedViewList and finds the ones that intersect with memory region passed in as argument.
+ MappedViewList is global and shared between all mmapping (in other words contains views for all dlls), and also each view has information about access rights.
+ However, pages with relocations might have read-only access rights in views,
+ because in ApplyBaseRelocations before relocation application access rights are saved and then restored after relocation is applied.
+ So, we can't rely on access right that are stored in view (madvise for dirty page will drop it and relocations won't be applied on next page error, so it can lead to sigsegv).
+ On the other hand, some views with write access might be places between relocations, so we have to check access rights to skip writable views.
+
+ This function does similar thing as PEImageLayout::ApplyBaseRelocations for MappedImageLayout.
+ Verify this during rebase. Note that part of logic that does relocations traversal should only be called for images that support it (verify this by rechecking all usages of ApplyBaseRelocations).
+ */
+void PEImageLayout::CleanAllReadOnlyPages()
+{
+    STANDARD_VM_CONTRACT;
+
+#ifdef TARGET_UNIX
+    SSIZE_T delta = (SIZE_T) GetBase() - (SIZE_T) GetPreferredBase();
+    PTR_VOID pBase = GetBase();
+
+    if ((!HasNativeHeader() && !HasReadyToRunHeader())
+        || delta == 0)
+    {
+        PAL_LOADMarkDontNeedCleanPages(pBase, 0, 0);
+        return;
+    }
+
+    LOG((LF_LOADER, LL_INFO100, "PEImage: cleaning read only pages\n"));
+
+    COUNT_T dirSize;
+    TADDR dir = GetDirectoryEntryData(IMAGE_DIRECTORY_ENTRY_BASERELOC, &dirSize);
+
+    BYTE * pCleanRegion = (BYTE *) pBase;
+    // The page size of PE file relocs is always 4096 bytes
+    const SIZE_T cbPageSize = 4096;
+
+    COUNT_T dirPos = 0;
+    while (dirPos < dirSize)
+    {
+        PIMAGE_BASE_RELOCATION r = (PIMAGE_BASE_RELOCATION)(dir + dirPos);
+
+        COUNT_T fixupsSize = VAL32(r->SizeOfBlock);
+        USHORT *fixups = (USHORT *) (r + 1);
+
+        _ASSERTE(fixupsSize > sizeof(IMAGE_BASE_RELOCATION));
+        _ASSERTE((fixupsSize - sizeof(IMAGE_BASE_RELOCATION)) % 2 == 0);
+
+        COUNT_T fixupsCount = (fixupsSize - sizeof(IMAGE_BASE_RELOCATION)) / 2;
+
+        _ASSERTE((BYTE *)(fixups + fixupsCount) <= (BYTE *)(dir + dirSize));
+
+        DWORD rva = VAL32(r->VirtualAddress);
+
+        BYTE * pageAddress = (BYTE *) pBase + rva;
+        bool coversNextPage = false;
+
+        for (COUNT_T fixupIndex = 0; fixupIndex < fixupsCount && !coversNextPage; fixupIndex++)
+        {
+            USHORT fixup = VAL16(fixups[fixupIndex]);
+
+            BYTE * address = pageAddress + (fixup & 0xfff);
+
+            switch (fixup>>12)
+            {
+            case IMAGE_REL_BASED_PTR:
+                if (address + sizeof(TADDR) > pageAddress + cbPageSize)
+                {
+                    coversNextPage = true;
+                }
+                break;
+
+#ifdef TARGET_ARM
+            case IMAGE_REL_BASED_THUMB_MOV32:
+                if (address + 8 > pageAddress + cbPageSize)
+                {
+                    coversNextPage = true;
+                }
+                break;
+#endif
+
+            case IMAGE_REL_BASED_ABSOLUTE:
+                //no adjustment
+                break;
+
+            default:
+                _ASSERTE(!"Unhandled reloc type!");
+            }
+        }
+        SIZE_T size = (SIZE_T) (pageAddress - pCleanRegion);
+
+        if (size > 0)
+        {
+            PAL_LOADMarkDontNeedCleanPages(pBase, pCleanRegion, size);
+        }
+
+        pCleanRegion = pageAddress + cbPageSize + (coversNextPage ? cbPageSize : 0);
+
+        dirPos += fixupsSize;
+    }
+    _ASSERTE(dirSize == dirPos);
+
+    PAL_LOADMarkDontNeedCleanPages(pBase, pCleanRegion, 0);
+#endif
+}
+
 
 RawImageLayout::RawImageLayout(const void *flat, COUNT_T size, PEImage* pOwner)
 {
index b780fe9..9f37946 100644 (file)
@@ -70,6 +70,8 @@ public:
 
     void ApplyBaseRelocations(BOOL isRelocated);
 
+    void CleanAllReadOnlyPages();
+
 public:
 #ifdef DACCESS_COMPILE
     void EnumMemoryRegions(CLRDataEnumMemoryFlags flags);
index 74b1dea..855aa35 100644 (file)
@@ -78,6 +78,7 @@ namespace System.Runtime.Loader
         public void StartProfileOptimization(string? profile) { }
         public override string ToString() { throw null; }
         public void Unload() { }
+        public static void FlushPECaches() { }
         [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
         public partial struct ContextualReflectionScope : System.IDisposable
         {