diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp index 7868ebbc6025cd..d576327a0e038f 100644 --- a/src/coreclr/hosts/corerun/corerun.cpp +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -15,6 +15,14 @@ #include +#if defined(TARGET_UNIX) +#include +#endif + +#if defined(TARGET_WINDOWS) +#include +#endif + using char_t = pal::char_t; using string_t = pal::string_t; @@ -81,6 +89,9 @@ namespace envvar // - Not set: same as PROPERTY // - The TPA list as a platform delimited list of paths. The same format as the system's PATH env var. const char_t* appAssemblies = W("APP_ASSEMBLIES"); + + // Variable indicating if a callback to support platform-native R2R should be provided to the runtime + const char_t* platformNativeR2R = W("PLATFORM_NATIVE_R2R"); } static void wait_for_debugger() @@ -273,6 +284,58 @@ size_t HOST_CONTRACT_CALLTYPE get_runtime_property( static char* s_core_libs_path = nullptr; static char* s_core_root_path = nullptr; +static bool HOST_CONTRACT_CALLTYPE get_native_code_data( + const host_runtime_contract_native_code_context* context, + host_runtime_contract_native_code_data* data) +{ +#if !defined(TARGET_WINDOWS) && !defined(TARGET_OSX) + // Platform-native R2R is only supported on Windows and macOS + return false; +#else + if (context == nullptr || data == nullptr) + return false; + + const char* assembly_path = context->assembly_path; + const char* owner_name = context->owner_composite_name; + + if (assembly_path == nullptr || owner_name == nullptr) + return false; + + // Look for native library next to the assembly + pal::string_t native_image_path; + { + pal::string_t file; + pal::split_path_to_dir_filename(pal::convert_from_utf8(assembly_path), native_image_path, file); + pal::ensure_trailing_delimiter(native_image_path); + } + native_image_path.append(pal::convert_from_utf8(owner_name)); + + pal::mod_t native_image; + if (!pal::try_load_library(native_image_path, native_image)) + return false; + + // Get the R2R header export + void* header_ptr = pal::get_module_symbol(native_image, "RTR_HEADER"); + if (header_ptr == nullptr) + return false; + + // Get module information (base address and size) + void* base_address = pal::get_image_base(native_image, header_ptr); + if (base_address == nullptr) + return false; + + size_t image_size = pal::get_image_size(base_address); + if (image_size == 0) + return false; + + data->size = sizeof(host_runtime_contract_native_code_data); + data->r2r_header_ptr = header_ptr; + data->image_size = image_size; + data->image_base = base_address; + return true; +#endif // TARGET_WINDOWS || TARGET_OSX +} + static bool HOST_CONTRACT_CALLTYPE external_assembly_probe( const char* path, void** data_start, @@ -464,7 +527,8 @@ static int run(const configuration& config) &get_runtime_property, nullptr, nullptr, - use_external_assembly_probe ? &external_assembly_probe : nullptr }; + use_external_assembly_probe ? &external_assembly_probe : nullptr, + pal::getenv(envvar::platformNativeR2R) == W("1") ? &get_native_code_data : nullptr }; propertyKeys.push_back(HOST_PROPERTY_RUNTIME_CONTRACT); std::stringstream ss; ss << "0x" << std::hex << (size_t)(&host_contract); diff --git a/src/coreclr/hosts/corerun/corerun.hpp b/src/coreclr/hosts/corerun/corerun.hpp index 09d6086e983d9c..ec5a978814ec3c 100644 --- a/src/coreclr/hosts/corerun/corerun.hpp +++ b/src/coreclr/hosts/corerun/corerun.hpp @@ -223,6 +223,39 @@ namespace pal return { buffer.get() }; } + inline string_t convert_from_utf8(const char* str) + { + int wchar_req = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); + + malloc_ptr buffer{ (wchar_t*)::malloc(wchar_req * sizeof(wchar_t)) }; + assert(buffer != nullptr); + + int written = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, buffer.get(), wchar_req); + assert(wchar_req == written); + + return { buffer.get() }; + } + + inline void* get_image_base(mod_t m, void* sym) + { + // On Windows, the HMODULE is the base address + return (void*)m; + } + + inline size_t get_image_size(void* base_address) + { + IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)base_address; + if (dos_header->e_magic == IMAGE_DOS_SIGNATURE) + { + IMAGE_NT_HEADERS* nt_headers = (IMAGE_NT_HEADERS*)((BYTE*)base_address + dos_header->e_lfanew); + if (nt_headers->Signature == IMAGE_NT_SIGNATURE) + { + return nt_headers->OptionalHeader.SizeOfImage; + } + } + return 0; + } + inline bool try_load_hostpolicy(pal::string_t mock_hostpolicy_value) { const char_t* hostpolicyName = W("hostpolicy.dll"); @@ -333,6 +366,7 @@ class platform_specific_actions final #include #include #include +#include // Needed for detecting the debugger attach scenario #if defined(__APPLE__) @@ -342,6 +376,16 @@ class platform_specific_actions final #include #endif // !__APPLE__ +// For getting image size +#if defined(TARGET_APPLE) +#include +#include +#elif !defined(TARGET_WASM) +#include +#include +#include +#endif + // CMake generated #include #include @@ -568,6 +612,89 @@ namespace pal return { str }; } + inline string_t convert_from_utf8(const char* str) + { + return { str }; + } + + inline void* get_image_base(mod_t m, void* sym) + { +#ifndef TARGET_WASM + Dl_info info; + if (dladdr(sym, &info) != 0) + { + return info.dli_fbase; + } +#endif + return nullptr; + } + + inline size_t get_image_size(void* base_address) + { + if (base_address == nullptr) + return 0; + +#if defined(TARGET_APPLE) + uint32_t image_count = _dyld_image_count(); + for (uint32_t i = 0; i < image_count; ++i) + { + const struct mach_header_64* header = + reinterpret_cast(_dyld_get_image_header(i)); + if (reinterpret_cast(header) != base_address) + continue; + + const struct load_command* cmd = + reinterpret_cast( + reinterpret_cast(header) + sizeof(struct mach_header_64)); + + size_t image_size = 0; + for (uint32_t j = 0; j < header->ncmds; ++j) + { + if (cmd->cmd == LC_SEGMENT_64) + { + const struct segment_command_64* seg = + reinterpret_cast(cmd); + size_t end_addr = static_cast(seg->vmaddr + seg->vmsize); + if (end_addr > image_size) + image_size = end_addr; + } + + cmd = reinterpret_cast( + reinterpret_cast(cmd) + cmd->cmdsize); + } + + return image_size; + } +#elif !defined(TARGET_WASM) + ElfW(Ehdr)* ehdr = reinterpret_cast(base_address); + if (std::memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) + return 0; + + ElfW(Phdr)* phdr = reinterpret_cast( + reinterpret_cast(base_address) + ehdr->e_phoff); + + size_t max_addr = 0; + size_t min_addr = static_cast(-1); + + for (int i = 0; i < ehdr->e_phnum; ++i) + { + if (phdr[i].p_type == PT_LOAD) + { + size_t seg_start = phdr[i].p_vaddr; + size_t seg_end = phdr[i].p_vaddr + phdr[i].p_memsz; + if (seg_start < min_addr) + min_addr = seg_start; + if (seg_end > max_addr) + max_addr = seg_end; + } + } + + if (max_addr > min_addr) + return max_addr - min_addr; +#endif + return 0; + } + inline bool try_load_hostpolicy(pal::string_t mock_hostpolicy_value) { #ifndef TARGET_WASM diff --git a/src/coreclr/inc/hostinformation.h b/src/coreclr/inc/hostinformation.h index 5a9a62fec48ab0..cf41ed5f4fdaf3 100644 --- a/src/coreclr/inc/hostinformation.h +++ b/src/coreclr/inc/hostinformation.h @@ -14,6 +14,8 @@ class HostInformation static bool HasExternalProbe(); static bool ExternalAssemblyProbe(_In_ const SString& path, _Out_ void** data, _Out_ int64_t* size); + + static bool GetNativeCodeData(_In_ const SString& assemblyPath, _In_z_ const char* ownerCompositeName, _Out_ void** header, _Out_ size_t* image_size, _Out_ void** image_base); }; #endif // _HOSTINFORMATION_H_ diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 134b685ff9fbe6..6271d23a4fb1bc 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -83,6 +83,7 @@ enum ReadyToRunFlag READYTORUN_FLAG_COMPONENT = 0x00000020, // This is the header describing a component assembly of composite R2R READYTORUN_FLAG_MULTIMODULE_VERSION_BUBBLE = 0x00000040, // This R2R module has multiple modules within its version bubble (For versions before version 6.2, all modules are assumed to possibly have this characteristic) READYTORUN_FLAG_UNRELATED_R2R_CODE = 0x00000080, // This R2R module has code in it that would not be naturally encoded into this module + READYTORUN_FLAG_PLATFORM_NATIVE_IMAGE = 0x00000100, // The owning composite executable is in the platform native format }; enum class ReadyToRunSectionType : uint32_t diff --git a/src/coreclr/vm/assemblybinder.cpp b/src/coreclr/vm/assemblybinder.cpp index 94b25361312754..9f055457ea87e1 100644 --- a/src/coreclr/vm/assemblybinder.cpp +++ b/src/coreclr/vm/assemblybinder.cpp @@ -27,16 +27,15 @@ HRESULT AssemblyBinder::BindAssemblyByName(AssemblyNameData* pAssemblyNameData, } -NativeImage* AssemblyBinder::LoadNativeImage(Module* componentModule, LPCUTF8 nativeImageName) +NativeImage* AssemblyBinder::LoadNativeImage(Module* componentModule, LPCUTF8 nativeImageName, bool isPlatformNative) { STANDARD_VM_CONTRACT; AppDomain::LoadLockHolder lock(AppDomain::GetCurrentDomain()); - AssemblyBinder* binder = componentModule->GetPEAssembly()->GetAssemblyBinder(); PTR_LoaderAllocator moduleLoaderAllocator = componentModule->GetLoaderAllocator(); bool isNewNativeImage; - NativeImage* nativeImage = NativeImage::Open(componentModule, nativeImageName, binder, moduleLoaderAllocator, &isNewNativeImage); + NativeImage* nativeImage = NativeImage::Open(componentModule->GetPath(), nativeImageName, this, moduleLoaderAllocator, isPlatformNative, &isNewNativeImage); return nativeImage; } diff --git a/src/coreclr/vm/assemblybinder.h b/src/coreclr/vm/assemblybinder.h index 640ef6c0a2f99d..89b501a6759a23 100644 --- a/src/coreclr/vm/assemblybinder.h +++ b/src/coreclr/vm/assemblybinder.h @@ -47,7 +47,7 @@ class AssemblyBinder m_ptrAssemblyLoadContext = ptrManagedDefaultBinderInstance; } - NativeImage* LoadNativeImage(Module* componentModule, LPCUTF8 nativeImageName); + NativeImage* LoadNativeImage(Module* componentModule, LPCUTF8 nativeImageName, bool isPlatformNative); void AddLoadedAssembly(Assembly* loadedAssembly); void GetNameForDiagnostics(/*out*/ SString& alcName); diff --git a/src/coreclr/vm/hostinformation.cpp b/src/coreclr/vm/hostinformation.cpp index acaa23bb141848..d47cfb25b75a63 100644 --- a/src/coreclr/vm/hostinformation.cpp +++ b/src/coreclr/vm/hostinformation.cpp @@ -58,3 +58,35 @@ bool HostInformation::ExternalAssemblyProbe(_In_ const SString& path, _Out_ void utf8Path.SetAndConvertToUTF8(path.GetUnicode()); return s_hostContract.external_assembly_probe(utf8Path.GetUTF8(), data, size); } + +bool HostInformation::GetNativeCodeData(_In_ const SString& assemblyPath, _In_z_ const char* ownerCompositeName, _Out_ void** header, _Out_ size_t* image_size, _Out_ void** image_base) +{ + _ASSERT(header != nullptr); + _ASSERT(image_base != nullptr); + _ASSERT(image_size != nullptr); + + size_t requiredSize = offsetof(host_runtime_contract, get_native_code_data) + sizeof(s_hostContract.get_native_code_data); + if (s_hostContract.size < requiredSize || s_hostContract.get_native_code_data == nullptr) + return false; + + StackSString utf8Path; + utf8Path.SetAndConvertToUTF8(assemblyPath.GetUnicode()); + host_runtime_contract_native_code_context context + { + sizeof(host_runtime_contract_native_code_context), + utf8Path.GetUTF8(), + ownerCompositeName + }; + host_runtime_contract_native_code_data data = { sizeof(host_runtime_contract_native_code_data) }; + if (!s_hostContract.get_native_code_data(&context, &data)) + return false; + + if (data.r2r_header_ptr == nullptr || data.image_size == 0 || data.image_base == nullptr) + return false; + + _ASSERT(data.size >= offsetof(host_runtime_contract_native_code_data, image_base) + sizeof(data.image_base)); + *header = data.r2r_header_ptr; + *image_size = data.image_size; + *image_base = data.image_base; + return true; +} diff --git a/src/coreclr/vm/nativeimage.cpp b/src/coreclr/vm/nativeimage.cpp index 175ed954e47a08..1d6a0593d09897 100644 --- a/src/coreclr/vm/nativeimage.cpp +++ b/src/coreclr/vm/nativeimage.cpp @@ -8,6 +8,7 @@ #include "common.h" #include "nativeimage.h" +#include "hostinformation.h" // -------------------------------------------------------------------------------- // Headers @@ -108,11 +109,122 @@ NativeImage::~NativeImage() } #ifndef DACCESS_COMPILE +namespace +{ + ReadyToRunLoadedImage* OpenR2RFromPE(const SString& componentModulePath, LPCUTF8 nativeImageFileName, READYTORUN_HEADER** header) + { + SString path{ componentModulePath }; + SString::Iterator lastPathSeparatorIter = path.End(); + size_t pathDirLength = 0; + if (PEAssembly::FindLastPathSeparator(path, lastPathSeparatorIter)) + { + pathDirLength = (lastPathSeparatorIter - path.Begin()) + 1; + } + + SString compositeImageFileName(SString::Utf8, nativeImageFileName); + SString fullPath; + fullPath.Set(path, path.Begin(), (COUNT_T)pathDirLength); + fullPath.Append(compositeImageFileName); + LPWSTR searchPathsConfig; + IfFailThrow(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_NativeImageSearchPaths, &searchPathsConfig)); + + PEImageLayoutHolder peLoadedImage; + + ProbeExtensionResult probeExtensionResult = AssemblyProbeExtension::Probe(fullPath, /*pathIsBundleRelative */ true); + if (probeExtensionResult.IsValid()) + { + // No need to use cache for this PE image. + // Composite r2r PE image is not a part of anyone's identity. + // We only need it to obtain the native image, which will be cached at AppDomain level. + PEImageHolder pImage = PEImage::OpenImage(fullPath, MDInternalImport_NoCache, probeExtensionResult); + PEImageLayout* loaded = pImage->GetOrCreateLayout(PEImageLayout::LAYOUT_LOADED); + // We will let pImage instance be freed after exiting this scope, but we will keep the layout, + // thus the layout needs an AddRef, or it will be gone together with pImage. + loaded->AddRef(); + peLoadedImage = loaded; + } + + if (peLoadedImage.IsNull()) + { + EX_TRY + { + peLoadedImage = PEImageLayout::LoadNative(fullPath); + } + EX_CATCH + { + SString searchPaths(searchPathsConfig); + SString::CIterator start = searchPaths.Begin(); + while (start != searchPaths.End()) + { + SString::CIterator end = start; + if (!searchPaths.Find(end, PATH_SEPARATOR_CHAR_W)) + { + end = searchPaths.End(); + } + fullPath.Set(searchPaths, start, (COUNT_T)(end - start)); + + if (end != searchPaths.End()) + { + // Skip path separator character + ++end; + } + start = end; + + if (fullPath.GetCount() == 0) + { + continue; + } + + fullPath.Append(DIRECTORY_SEPARATOR_CHAR_W); + fullPath.Append(compositeImageFileName); + + EX_TRY + { + peLoadedImage = PEImageLayout::LoadNative(fullPath); + break; + } + EX_CATCH + { + } + EX_END_CATCH + } + } + EX_END_CATCH + + if (peLoadedImage.IsNull()) + { + // Failed to locate the native composite R2R image +#ifdef LOGGING + SString searchPaths(searchPathsConfig != nullptr ? searchPathsConfig : W("")); + LOG((LF_LOADER, LL_ALWAYS, "LOADER: failed to load native image '%s' for component assembly '%s' using search paths: '%s'\n", + nativeImageFileName, + path.GetUTF8(), + searchPaths.GetUTF8())); +#endif // LOGGING + RaiseFailFastException(nullptr, nullptr, 0); + } + } + + *header = (READYTORUN_HEADER *)peLoadedImage->GetExport("RTR_HEADER"); + if (*header == NULL) + { + COMPlusThrowHR(COR_E_BADIMAGEFORMAT); + } + + return new ReadyToRunLoadedImage( + (TADDR)peLoadedImage->GetBase(), + peLoadedImage->GetVirtualSize(), + peLoadedImage.Extract(), + [](void* img) { delete (PEImageLayout*)img; }); + } +} + NativeImage *NativeImage::Open( - Module *componentModule, + const SString& componentModulePath, LPCUTF8 nativeImageFileName, AssemblyBinder *pAssemblyBinder, LoaderAllocator *pLoaderAllocator, + bool isPlatformNative, /* out */ bool *isNewNativeImage) { STANDARD_VM_CONTRACT; @@ -131,103 +243,39 @@ NativeImage *NativeImage::Open( } } - SString path{ componentModule->GetPath() }; - SString::Iterator lastPathSeparatorIter = path.End(); - size_t pathDirLength = 0; - if (PEAssembly::FindLastPathSeparator(path, lastPathSeparatorIter)) - { - pathDirLength = (lastPathSeparatorIter - path.Begin()) + 1; - } - - SString compositeImageFileName(SString::Utf8, nativeImageFileName); - SString fullPath; - fullPath.Set(path, path.Begin(), (COUNT_T)pathDirLength); - fullPath.Append(compositeImageFileName); - LPWSTR searchPathsConfig; - IfFailThrow(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_NativeImageSearchPaths, &searchPathsConfig)); - - PEImageLayoutHolder peLoadedImage; - - ProbeExtensionResult probeExtensionResult = AssemblyProbeExtension::Probe(fullPath, /*pathIsBundleRelative */ true); - if (probeExtensionResult.IsValid()) + READYTORUN_HEADER *pHeader = nullptr; + NewHolder loadedImageHolder; + if (isPlatformNative) { - // No need to use cache for this PE image. - // Composite r2r PE image is not a part of anyone's identity. - // We only need it to obtain the native image, which will be cached at AppDomain level. - PEImageHolder pImage = PEImage::OpenImage(fullPath, MDInternalImport_NoCache, probeExtensionResult); - PEImageLayout* loaded = pImage->GetOrCreateLayout(PEImageLayout::LAYOUT_LOADED); - // We will let pImage instance be freed after exiting this scope, but we will keep the layout, - // thus the layout needs an AddRef, or it will be gone together with pImage. - loaded->AddRef(); - peLoadedImage = loaded; - } - - if (peLoadedImage.IsNull()) - { - EX_TRY + // Call into the host to load the composite native image + size_t image_size; + void* image_base; + if (HostInformation::GetNativeCodeData(componentModulePath, nativeImageFileName, reinterpret_cast(&pHeader), &image_size, &image_base)) { - peLoadedImage = PEImageLayout::LoadNative(fullPath); + loadedImageHolder = new ReadyToRunLoadedImage((TADDR)image_base, (uint32_t)image_size); } - EX_CATCH - { - SString searchPaths(searchPathsConfig); - SString::CIterator start = searchPaths.Begin(); - while (start != searchPaths.End()) - { - SString::CIterator end = start; - if (!searchPaths.Find(end, PATH_SEPARATOR_CHAR_W)) - { - end = searchPaths.End(); - } - fullPath.Set(searchPaths, start, (COUNT_T)(end - start)); - - if (end != searchPaths.End()) - { - // Skip path separator character - ++end; - } - start = end; - - if (fullPath.GetCount() == 0) - { - continue; - } - - fullPath.Append(DIRECTORY_SEPARATOR_CHAR_W); - fullPath.Append(compositeImageFileName); - - EX_TRY - { - peLoadedImage = PEImageLayout::LoadNative(fullPath); - break; - } - EX_CATCH - { - } - EX_END_CATCH - } - } - EX_END_CATCH - - if (peLoadedImage.IsNull()) + else { - // Failed to locate the native composite R2R image +#ifdef TARGET_WINDOWS + // For platform-native on Windows, fall back to runtime loading the PE + loadedImageHolder = OpenR2RFromPE(componentModulePath, nativeImageFileName, &pHeader); +#else + // Match failure behaviour for failing to load from PE #ifdef LOGGING - SString searchPaths(searchPathsConfig != nullptr ? searchPathsConfig : W("")); - LOG((LF_LOADER, LL_ALWAYS, "LOADER: failed to load native image '%s' for component assembly '%s' using search paths: '%s'\n", + SString path { componentModulePath }; + LOG((LF_LOADER, LL_ALWAYS, "LOADER: failed to load platform-native image '%s' for component assembly '%s' using host callback\n", nativeImageFileName, - path.GetUTF8(), - searchPaths.GetUTF8())); + path.GetUTF8())); #endif // LOGGING RaiseFailFastException(nullptr, nullptr, 0); +#endif } } - - READYTORUN_HEADER *pHeader = (READYTORUN_HEADER *)peLoadedImage->GetExport("RTR_HEADER"); - if (pHeader == NULL) + else { - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); + loadedImageHolder = OpenR2RFromPE(componentModulePath, nativeImageFileName, &pHeader); } + if (pHeader->Signature != READYTORUN_SIGNATURE) { COMPlusThrowHR(COR_E_BADIMAGEFORMAT); @@ -237,13 +285,7 @@ NativeImage *NativeImage::Open( COMPlusThrowHR(COR_E_BADIMAGEFORMAT); } - NewHolder peLoadedImageHolder = new ReadyToRunLoadedImage( - (TADDR)peLoadedImage->GetBase(), - peLoadedImage->GetVirtualSize(), - peLoadedImage.Extract(), - [](void* img) { delete (PEImageLayout*)img; }); - - NewHolder image = new NativeImage(pAssemblyBinder, peLoadedImageHolder.Extract(), nativeImageFileName); + NewHolder image = new NativeImage(pAssemblyBinder, loadedImageHolder.Extract(), nativeImageFileName); AllocMemTracker amTracker; image->Initialize(pHeader, pLoaderAllocator, &amTracker); pExistingImage = AppDomain::GetCurrentDomain()->SetNativeImage(nativeImageFileName, image); diff --git a/src/coreclr/vm/nativeimage.h b/src/coreclr/vm/nativeimage.h index b5511e5a930077..47aa95c512c839 100644 --- a/src/coreclr/vm/nativeimage.h +++ b/src/coreclr/vm/nativeimage.h @@ -107,10 +107,11 @@ class NativeImage ~NativeImage(); static NativeImage *Open( - Module *componentModule, + const SString& componentModulePath, LPCUTF8 nativeImageFileName, AssemblyBinder *pAssemblyBinder, LoaderAllocator *pLoaderAllocator, + bool isPlatformNative, /* out */ bool *isNewNativeImage); Crst *EagerFixupsLock() { return &m_eagerFixupsLock; } diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 42b7e010028323..39ad5fafc6f70e 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -552,7 +552,8 @@ static NativeImage *AcquireCompositeImage(Module * pModule, PEImageLayout * pLay if (ownerCompositeExecutableName != NULL) { AssemblyBinder *binder = pModule->GetPEAssembly()->GetAssemblyBinder(); - return binder->LoadNativeImage(pModule, ownerCompositeExecutableName); + bool isPlatformNative = (pHeader->CoreHeader.Flags & READYTORUN_FLAG_PLATFORM_NATIVE_IMAGE) != 0; + return binder->LoadNativeImage(pModule, ownerCompositeExecutableName, isPlatformNative); } return NULL; @@ -640,6 +641,7 @@ PTR_ReadyToRunInfo ReadyToRunInfo::Initialize(Module * pModule, AllocMemTracker } else { + _ASSERTE((pHeader->CoreHeader.Flags & READYTORUN_FLAG_PLATFORM_NATIVE_IMAGE) == 0 && "Non-component assembly should not be marked with READYTORUN_FLAG_PLATFORM_NATIVE_IMAGE"); if (!AcquireImage(pModule, pLayout, pHeader)) { DoLog("Ready to Run disabled - module already loaded in another assembly load context"); diff --git a/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs b/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs index 4498f4de9c7e2d..8894f3d7da114a 100644 --- a/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs +++ b/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs @@ -19,6 +19,7 @@ internal struct host_runtime_contract public delegate* unmanaged[Stdcall] bundle_probe; public IntPtr pinvoke_override; public delegate* unmanaged[Stdcall] external_assembly_probe; + public delegate* unmanaged[Stdcall] get_native_code_data; } #pragma warning restore CS0649 diff --git a/src/native/corehost/host_runtime_contract.h b/src/native/corehost/host_runtime_contract.h index 65bc3c7464f0b4..558a7093379efa 100644 --- a/src/native/corehost/host_runtime_contract.h +++ b/src/native/corehost/host_runtime_contract.h @@ -24,6 +24,23 @@ #define HOST_PROPERTY_PLATFORM_RESOURCE_ROOTS "PLATFORM_RESOURCE_ROOTS" #define HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES "TRUSTED_PLATFORM_ASSEMBLIES" +// Context passed to get_native_code_data callback +struct host_runtime_contract_native_code_context +{ + size_t size; // size of this struct + const char* assembly_path; // component assembly path + const char* owner_composite_name; // name from component R2R header +}; + +// Data returned by get_native_code_data callback +struct host_runtime_contract_native_code_data +{ + size_t size; // size of this struct + void* r2r_header_ptr; // ReadyToRun header + size_t image_size; // size of the image + void* image_base; // base address where the image was loaded +}; + // Any callbacks set on this contract are expected to be valid for the lifetime of the process struct host_runtime_contract { @@ -60,6 +77,10 @@ struct host_runtime_contract const char* path, /*out*/ void **data_start, /*out*/ int64_t* size); -}; + // Get native code data for the assembly specified by the supplied context + bool(HOST_CONTRACT_CALLTYPE* get_native_code_data)( + const struct host_runtime_contract_native_code_context* context, + /*out*/ struct host_runtime_contract_native_code_data* data); +}; #endif // __HOST_RUNTIME_CONTRACT_H__ diff --git a/src/tests/Loader/PlatformNativeR2R/PlatformNativeR2R.cs b/src/tests/Loader/PlatformNativeR2R/PlatformNativeR2R.cs new file mode 100644 index 00000000000000..426e811a1d69c3 --- /dev/null +++ b/src/tests/Loader/PlatformNativeR2R/PlatformNativeR2R.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using Xunit; + +public class PlatformNativeR2R +{ + [Fact] + public static void EntryPoint() + { + Console.WriteLine("Testing platform-native R2R composite image loading..."); + + // Run R2RDump to validate header information + string coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT"); + coreRoot = string.IsNullOrEmpty(coreRoot) ? Path.GetDirectoryName(Environment.ProcessPath) : coreRoot; + string r2rDumpPath = Path.Join(coreRoot, "R2RDump", "R2RDump.dll"); + + string testDotNetCmd = Environment.GetEnvironmentVariable("__TestDotNetCmd"); + ProcessStartInfo psi = new ProcessStartInfo + { + UseShellExecute = false, + FileName = string.IsNullOrEmpty(testDotNetCmd) ? "dotnet" : testDotNetCmd, + Arguments = $"\"{r2rDumpPath}\" --in \"{Assembly.GetExecutingAssembly().Location}\" --header", + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using (Process process = Process.Start(psi)) + { + process.WaitForExit(); + + string output = process.StandardOutput.ReadToEnd(); + if (process.ExitCode != 0) + { + string stderr = process.StandardError.ReadToEnd(); + Console.WriteLine($"R2RDump failed with exit code {process.ExitCode}"); + Console.WriteLine($"stdout: {output}"); + Console.WriteLine($"stderr: {stderr}"); + Assert.Fail("R2RDump failed to execute"); + } + + Assert.True(output.Contains("READYTORUN_FLAG_Component"), "Component assembly should be associated with a platform-native composite image"); + // TODO: Uncomment assert when crossgen2 adds support for the flag + // Assert.True(output.Contains("READYTORUN_FLAG_PlatformNativeImage"), "Component assembly should be associated with a platform-native composite image"); + } + } +} diff --git a/src/tests/Loader/PlatformNativeR2R/PlatformNativeR2R.csproj b/src/tests/Loader/PlatformNativeR2R/PlatformNativeR2R.csproj new file mode 100644 index 00000000000000..49166366efae82 --- /dev/null +++ b/src/tests/Loader/PlatformNativeR2R/PlatformNativeR2R.csproj @@ -0,0 +1,17 @@ + + + + true + + true + + true + + + + + + + + +