未验证 提交 71142487 编写于 作者: V Vladimir Sadov 提交者: GitHub

Singlefile: enabling compression for managed assemblies. (#50817)

* enable compression of assemblies

* fix Unix build

* map should use converted layout for compressed

* enable execution for R2R

* fixes for OSX

* PR feedback (comments)

* more PR feedback

* shorter include path to pal_zlib.h

* Apply suggestions from code review
Co-authored-by: NVitek Karas <vitek.karas@microsoft.com>
Co-authored-by: NVitek Karas <vitek.karas@microsoft.com>
上级 40a4cb66
get_filename_component(CLR_REPO_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../.. ABSOLUTE)
set(CLR_ENG_NATIVE_DIR ${CMAKE_CURRENT_LIST_DIR})
get_filename_component(CLR_SRC_NATIVE_DIR ${CMAKE_CURRENT_LIST_DIR}/../../src/native ABSOLUTE)
get_filename_component(CLR_SRC_LIBS_NATIVE_DIR ${CMAKE_CURRENT_LIST_DIR}/../../src/libraries/Native ABSOLUTE)
......@@ -126,7 +126,7 @@ CORECLR_HOSTING_API(coreclr_execute_assembly,
//
// Callback types used by the hosts
//
typedef bool(CORECLR_CALLING_CONVENTION BundleProbeFn)(const char* path, int64_t* offset, int64_t* size);
typedef bool(CORECLR_CALLING_CONVENTION BundleProbeFn)(const char* path, int64_t* offset, int64_t* size, int64_t* compressedSize);
typedef const void* (CORECLR_CALLING_CONVENTION PInvokeOverrideFn)(const char* libraryName, const char* entrypointName);
......
......@@ -19,13 +19,15 @@ struct BundleFileLocation
{
INT64 Size;
INT64 Offset;
INT64 UncompresedSize;
BundleFileLocation()
{
LIMITED_METHOD_CONTRACT;
Size = 0;
Size = 0;
Offset = 0;
UncompresedSize = 0;
}
static BundleFileLocation Invalid() { LIMITED_METHOD_CONTRACT; return BundleFileLocation(); }
......
......@@ -236,7 +236,7 @@ class PEDecoder
BOOL IsILOnly() const;
CHECK CheckILOnly() const;
void LayoutILOnly(void *base, BOOL allowFullPE = FALSE) const;
void LayoutILOnly(void *base, bool enableExecution) const;
// Strong name & hashing support
......
......@@ -1724,12 +1724,11 @@ CHECK PEDecoder::CheckILOnlyEntryPoint() const
#ifndef DACCESS_COMPILE
void PEDecoder::LayoutILOnly(void *base, BOOL allowFullPE) const
void PEDecoder::LayoutILOnly(void *base, bool enableExecution) const
{
CONTRACT_VOID
{
INSTANCE_CHECK;
PRECONDITION(allowFullPE || CheckILOnlyFormat());
PRECONDITION(CheckZeroedMemory(base, VAL32(FindNTHeaders()->OptionalHeader.SizeOfImage)));
// Ideally we would require the layout address to honor the section alignment constraints.
// However, we do have 8K aligned IL only images which we load on 32 bit platforms. In this
......@@ -1774,18 +1773,22 @@ void PEDecoder::LayoutILOnly(void *base, BOOL allowFullPE) const
for (section = sectionStart; section < sectionEnd; section++)
{
// Add appropriate page protection.
#if defined(CROSSGEN_COMPILE) || defined(TARGET_UNIX)
if (section->Characteristics & IMAGE_SCN_MEM_WRITE)
continue;
DWORD newProtection;
if (!enableExecution)
{
if (section->Characteristics & IMAGE_SCN_MEM_WRITE)
continue;
DWORD newProtection = PAGE_READONLY;
#else
DWORD newProtection = section->Characteristics & IMAGE_SCN_MEM_EXECUTE ?
PAGE_EXECUTE_READ :
section->Characteristics & IMAGE_SCN_MEM_WRITE ?
PAGE_READWRITE :
PAGE_READONLY;
#endif
newProtection = PAGE_READONLY;
}
else
{
newProtection = section->Characteristics & IMAGE_SCN_MEM_EXECUTE ?
PAGE_EXECUTE_READ :
section->Characteristics & IMAGE_SCN_MEM_WRITE ?
PAGE_READWRITE :
PAGE_READONLY;
}
if (!ClrVirtualProtect((void*)((BYTE*)base + VAL32(section->VirtualAddress)),
VAL32(section->Misc.VirtualSize),
......
......@@ -5,6 +5,12 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${ARCH_SOURCES_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../interop/inc)
# needed when zLib compression is used
include_directories(${CLR_SRC_LIBS_NATIVE_DIR}/AnyOS/zlib)
if(NOT CLR_CMAKE_TARGET_WIN32)
include_directories(${CLR_SRC_LIBS_NATIVE_DIR}/Unix/Common)
endif()
add_definitions(-DUNICODE)
add_definitions(-D_UNICODE)
......@@ -106,7 +112,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON
onstackreplacement.cpp
pefile.cpp
peimage.cpp
peimagelayout.cpp
perfmap.cpp
perfinfo.cpp
pgo.cpp
......@@ -892,6 +897,11 @@ endif(CLR_CMAKE_TARGET_WIN32)
set (VM_SOURCES_WKS_SPECIAL
codeman.cpp
ceemain.cpp
peimagelayout.cpp
)
list(APPEND VM_SOURCES_DAC
peimagelayout.cpp
)
# gcdecode.cpp is included by both JIT and VM. to avoid duplicate definitions we need to
......
......@@ -80,7 +80,21 @@ BundleFileLocation Bundle::Probe(const SString& path, bool pathIsBundleRelative)
}
}
m_probe(utf8Path, &loc.Offset, &loc.Size);
INT64 fileSize = 0;
INT64 compressedSize = 0;
m_probe(utf8Path, &loc.Offset, &fileSize, &compressedSize);
if (compressedSize)
{
loc.Size = compressedSize;
loc.UncompresedSize = fileSize;
}
else
{
loc.Size = fileSize;
loc.UncompresedSize = 0;
}
return loc;
}
......
......@@ -887,7 +887,9 @@ void PEImage::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
PEImage::PEImage():
m_path(),
m_refCount(1),
m_bundleFileLocation(),
m_bIsTrustedNativeImage(FALSE),
m_bInHashMap(FALSE),
#ifdef METADATATRACKER_DATA
......
......@@ -148,6 +148,7 @@ public:
HANDLE GetFileHandle();
INT64 GetOffset() const;
INT64 GetSize() const;
INT64 GetUncompressedSize() const;
void SetFileHandle(HANDLE hFile);
HRESULT TryOpenFile();
......
......@@ -59,6 +59,12 @@ inline INT64 PEImage::GetSize() const
return m_bundleFileLocation.Size;
}
inline INT64 PEImage::GetUncompressedSize() const
{
LIMITED_METHOD_CONTRACT;
return m_bundleFileLocation.UncompresedSize;
}
inline void PEImage::SetModuleFileNameHintForDAC()
{
LIMITED_METHOD_DAC_CONTRACT;
......
......@@ -13,6 +13,13 @@
#include "amsi.h"
#endif
#if defined(CORECLR_EMBEDDED)
extern "C"
{
#include "pal_zlib.h"
}
#endif
#ifndef DACCESS_COMPILE
PEImageLayout* PEImageLayout::CreateFlat(const void *flat, COUNT_T size,PEImage* pOwner)
{
......@@ -94,15 +101,21 @@ PEImageLayout* PEImageLayout::Map(PEImage* pOwner)
}
CONTRACT_END;
PEImageLayoutHolder pAlloc(new MappedImageLayout(pOwner));
PEImageLayoutHolder pAlloc = pOwner->GetUncompressedSize() != 0 ?
LoadConverted(pOwner, /* isInBundle */ true):
new MappedImageLayout(pOwner);
if (pAlloc->GetBase()==NULL)
{
//cross-platform or a bad image
pAlloc = LoadConverted(pOwner);
}
else
if(!pAlloc->CheckFormat())
{
if (!pAlloc->CheckFormat())
ThrowHR(COR_E_BADIMAGEFORMAT);
}
RETURN pAlloc.Extract();
}
......@@ -397,7 +410,6 @@ ConvertedImageLayout::ConvertedImageLayout(PEImageLayout* source, BOOL isInBundl
m_Layout=LAYOUT_LOADED;
m_pOwner=source->m_pOwner;
_ASSERTE(!source->IsMapped());
m_isInBundle = isInBundle;
m_pExceptionDir = NULL;
......@@ -405,34 +417,41 @@ ConvertedImageLayout::ConvertedImageLayout(PEImageLayout* source, BOOL isInBundl
EEFileLoadException::Throw(GetPath(), COR_E_BADIMAGEFORMAT);
LOG((LF_LOADER, LL_INFO100, "PEImage: Opening manually mapped stream\n"));
#if !defined(CROSSGEN_COMPILE) && !defined(TARGET_UNIX)
// on Windows we may want to enable execution if the image contains R2R sections
// in bundle we may want to enable execution if the image contains R2R sections
// so must ensure the mapping is compatible with that
m_FileMap.Assign(WszCreateFileMapping(INVALID_HANDLE_VALUE, NULL,
PAGE_EXECUTE_READWRITE, 0,
source->GetVirtualSize(), NULL));
bool enableExecution = isInBundle &&
source->HasCorHeader() &&
(source->HasNativeHeader() || source->HasReadyToRunHeader()) &&
g_fAllowNativeImages;
DWORD mapAccess = PAGE_READWRITE;
DWORD viewAccess = FILE_MAP_ALL_ACCESS;
#if !defined(CROSSGEN_COMPILE) && !defined(TARGET_UNIX)
if (enableExecution)
{
// to make sections executable on Windows the view must have EXECUTE permissions
mapAccess = PAGE_EXECUTE_READWRITE;
viewAccess = FILE_MAP_EXECUTE | FILE_MAP_WRITE;
}
#endif
DWORD allAccess = FILE_MAP_EXECUTE | FILE_MAP_WRITE;
#else
m_FileMap.Assign(WszCreateFileMapping(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0,
mapAccess, 0,
source->GetVirtualSize(), NULL));
DWORD allAccess = FILE_MAP_ALL_ACCESS;
#endif
if (m_FileMap == NULL)
ThrowLastError();
m_FileView.Assign(CLRMapViewOfFile(m_FileMap, allAccess, 0, 0, 0,
m_FileView.Assign(CLRMapViewOfFile(m_FileMap, viewAccess, 0, 0, 0,
(void *) source->GetPreferredBase()));
if (m_FileView == NULL)
m_FileView.Assign(CLRMapViewOfFile(m_FileMap, allAccess, 0, 0, 0));
m_FileView.Assign(CLRMapViewOfFile(m_FileMap, viewAccess, 0, 0, 0));
if (m_FileView == NULL)
ThrowLastError();
source->LayoutILOnly(m_FileView, TRUE); //@TODO should be false for streams
source->LayoutILOnly(m_FileView, enableExecution);
IfFailThrow(Init(m_FileView));
#if defined(CROSSGEN_COMPILE)
......@@ -440,11 +459,9 @@ ConvertedImageLayout::ConvertedImageLayout(PEImageLayout* source, BOOL isInBundl
{
ApplyBaseRelocations();
}
#elif !defined(TARGET_UNIX)
if (m_isInBundle &&
HasCorHeader() &&
(HasNativeHeader() || HasReadyToRunHeader()) &&
g_fAllowNativeImages)
#else
if (enableExecution)
{
if (!IsNativeMachineFormat())
ThrowHR(COR_E_BADIMAGEFORMAT);
......@@ -453,8 +470,8 @@ ConvertedImageLayout::ConvertedImageLayout(PEImageLayout* source, BOOL isInBundl
// otherwise R2R will be disabled for this image.
ApplyBaseRelocations();
// Check if there is a static function table and install it. (except x86)
#if !defined(TARGET_X86)
// Check if there is a static function table and install it. (Windows only, except x86)
#if !defined(TARGET_UNIX) && !defined(TARGET_X86)
COUNT_T cbSize = 0;
PT_RUNTIME_FUNCTION pExceptionDir = (PT_RUNTIME_FUNCTION)GetDirectoryEntryData(IMAGE_DIRECTORY_ENTRY_EXCEPTION, &cbSize);
DWORD tableSize = cbSize / sizeof(T_RUNTIME_FUNCTION);
......@@ -502,6 +519,7 @@ MappedImageLayout::MappedImageLayout(PEImage* pOwner)
HANDLE hFile = pOwner->GetFileHandle();
INT64 offset = pOwner->GetOffset();
_ASSERTE(pOwner->GetUncompressedSize() == 0);
// If mapping was requested, try to do SEC_IMAGE mapping
LOG((LF_LOADER, LL_INFO100, "PEImage: Opening OS mapped %S (hFile %p)\n", (LPCWSTR) GetPath(), hFile));
......@@ -704,6 +722,8 @@ FlatImageLayout::FlatImageLayout(PEImage* pOwner)
}
}
LPVOID addr = 0;
// It's okay if resource files are length zero
if (size > 0)
{
......@@ -721,14 +741,68 @@ FlatImageLayout::FlatImageLayout(PEImage* pOwner)
_ASSERTE((offset - mapBegin) < mapSize);
_ASSERTE(mapSize >= (UINT64)size);
char *addr = (char*)CLRMapViewOfFile(m_FileMap, FILE_MAP_READ, mapBegin >> 32, (DWORD)mapBegin, (DWORD)mapSize);
if (addr == NULL)
LPVOID view = CLRMapViewOfFile(m_FileMap, FILE_MAP_READ, mapBegin >> 32, (DWORD)mapBegin, (DWORD)mapSize);
if (view == NULL)
ThrowLastError();
addr += (offset - mapBegin);
m_FileView.Assign((LPVOID)addr);
m_FileView.Assign(view);
addr = (LPVOID)((size_t)view + offset - mapBegin);
INT64 uncompressedSize = pOwner->GetUncompressedSize();
if (uncompressedSize > 0)
{
#if defined(CORECLR_EMBEDDED)
// The mapping we have just created refers to the region in the bundle that contains compressed data.
// We will create another anonymous memory-only mapping and uncompress file there.
// The flat image will refer to the anonymous mapping instead and we will release the original mapping.
HandleHolder anonMap = WszCreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, uncompressedSize >> 32, (DWORD)uncompressedSize, NULL);
if (anonMap == NULL)
ThrowLastError();
LPVOID anonView = CLRMapViewOfFile(anonMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if (anonView == NULL)
ThrowLastError();
//NB: PE cannot be larger than 4GB and we are decompressing a managed assembly, which is a PE image,
// thus converting sizes to uint32 is ok.
PAL_ZStream zStream;
zStream.nextIn = (uint8_t*)addr;
zStream.availIn = (uint32_t)size;
zStream.nextOut = (uint8_t*)anonView;
zStream.availOut = (uint32_t)uncompressedSize;
// we match the compression side here. 15 is the window sise, negative means no zlib header.
const int Deflate_DefaultWindowBits = -15;
if (CompressionNative_InflateInit2_(&zStream, Deflate_DefaultWindowBits) != PAL_Z_OK)
ThrowHR(COR_E_BADIMAGEFORMAT);
int ret = CompressionNative_Inflate(&zStream, PAL_Z_NOFLUSH);
// decompression should have consumed the entire input
// and the entire output budgets
if ((ret < 0) ||
!(zStream.availIn == 0 && zStream.availOut == 0))
{
CompressionNative_InflateEnd(&zStream);
ThrowHR(COR_E_BADIMAGEFORMAT);
}
CompressionNative_InflateEnd(&zStream);
addr = anonView;
size = uncompressedSize;
// Replace file handles with the handles to anonymous map. This will release the handles to the original view and map.
m_FileView.Assign(anonView);
m_FileMap.Assign(anonMap);
#else
_ASSERTE(!"Failure extracting contents of the application bundle. Compressed files used with a standalone (not singlefile) apphost.");
ThrowHR(E_FAIL); // we don't have any indication of what kind of failure. Possibly a corrupt image.
#endif
}
}
Init(m_FileView, (COUNT_T)size);
Init(addr, (COUNT_T)size);
}
NativeImageLayout::NativeImageLayout(LPCWSTR fullPath)
......
......@@ -113,7 +113,6 @@ public:
virtual ~ConvertedImageLayout();
#endif
private:
bool m_isInBundle;
PT_RUNTIME_FUNCTION m_pExceptionDir;
};
......
......@@ -74,6 +74,7 @@ private bool ShouldCompress(FileType type)
{
case FileType.Symbols:
case FileType.NativeBinary:
case FileType.Assembly:
return true;
default:
......
......@@ -13,16 +13,22 @@ public static class Program
// The bundle-probe callback is only called from native code in the product
// Therefore the type on this test is adjusted to circumvent the failure.
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate byte BundleProbeDelegate([MarshalAs(UnmanagedType.LPUTF8Str)] string path, IntPtr size, IntPtr offset);
public delegate byte BundleProbeDelegate([MarshalAs(UnmanagedType.LPUTF8Str)] string path, IntPtr offset, IntPtr size, IntPtr compressedSize);
unsafe static bool Probe(BundleProbeDelegate bundleProbe, string path, bool isExpected)
{
Int64 size, offset;
bool exists = bundleProbe(path, (IntPtr)(&offset), (IntPtr)(&size)) != 0;
Int64 size, offset, compressedSize;
bool exists = bundleProbe(path, (IntPtr)(&offset), (IntPtr)(&size), (IntPtr)(&compressedSize)) != 0;
switch (exists, isExpected)
{
case (true, true):
if (compressedSize < 0 || compressedSize > size)
{
Console.WriteLine($"Invalid compressedSize obtained for {path} within bundle.");
return false;
}
if (size > 0 && offset > 0)
{
return true;
......
......@@ -19,8 +19,9 @@ set(SKIP_VERSIONING 1)
include_directories(..)
include_directories(../../json)
include_directories(${CLR_SRC_LIBS_NATIVE_DIR}/AnyOS/zlib)
if(NOT CLR_CMAKE_TARGET_WIN32)
include_directories(../../../../libraries/Native/Unix/Common)
include_directories(${CLR_SRC_LIBS_NATIVE_DIR}/Unix/Common)
endif()
set(SOURCES
......
......@@ -10,7 +10,7 @@
#if defined(NATIVE_LIBS_EMBEDDED)
extern "C"
{
#include "../../../libraries/Native/AnyOS/zlib/pal_zlib.h"
#include "pal_zlib.h"
}
#endif
......
......@@ -37,6 +37,7 @@ namespace bundle
file_entry_t()
: m_offset(0)
, m_size(0)
, m_compressedSize(0)
, m_type(file_type_t::__last)
, m_relative_path()
, m_disabled(false)
......
......@@ -61,7 +61,7 @@ const file_entry_t* runner_t::probe(const pal::string_t &relative_path) const
return nullptr;
}
bool runner_t::probe(const pal::string_t& relative_path, int64_t* offset, int64_t* size) const
bool runner_t::probe(const pal::string_t& relative_path, int64_t* offset, int64_t* size, int64_t* compressedSize) const
{
const bundle::file_entry_t* entry = probe(relative_path);
......@@ -76,6 +76,7 @@ bool runner_t::probe(const pal::string_t& relative_path, int64_t* offset, int64_
*offset = entry->offset();
*size = entry->size();
*compressedSize = entry->compressedSize();
return true;
}
......
......@@ -27,7 +27,7 @@ namespace bundle
const pal::string_t& extraction_path() const { return m_extraction_path; }
bool has_base(const pal::string_t& base) const { return base.compare(base_path()) == 0; }
bool probe(const pal::string_t& relative_path, int64_t* offset, int64_t* size) const;
bool probe(const pal::string_t& relative_path, int64_t* offset, int64_t* size, int64_t* compressedSize) const;
const file_entry_t* probe(const pal::string_t& relative_path) const;
bool locate(const pal::string_t& relative_path, pal::string_t& full_path, bool& extracted_to_disk) const;
bool locate(const pal::string_t& relative_path, pal::string_t& full_path) const
......
......@@ -23,7 +23,7 @@ namespace
// This function is an API exported to the runtime via the BUNDLE_PROBE property.
// This function used by the runtime to probe for bundled assemblies
// This function assumes that the currently executing app is a single-file bundle.
bool STDMETHODCALLTYPE bundle_probe(const char* path, int64_t* offset, int64_t* size)
bool STDMETHODCALLTYPE bundle_probe(const char* path, int64_t* offset, int64_t* size, int64_t* compressedSize)
{
if (path == nullptr)
{
......@@ -40,7 +40,7 @@ namespace
return false;
}
return bundle::runner_t::app()->probe(file_path, offset, size);
return bundle::runner_t::app()->probe(file_path, offset, size, compressedSize);
}
#if defined(NATIVE_LIBS_EMBEDDED)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册