Skip to content

Commit 1d44636

Browse files
authored
Add test for external assembly probe mechanism (#113007)
- Only try to convert to a loaded layout by file mapping for images that are files - Add test that uses corerun with external assembly probe
1 parent 9c8ec9c commit 1d44636

File tree

5 files changed

+164
-4
lines changed

5 files changed

+164
-4
lines changed

src/coreclr/hosts/corerun/corerun.cpp

+72-2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ namespace envvar
7070

7171
// Variable used to preload a mock hostpolicy for testing.
7272
const char_t* mockHostPolicy = W("MOCK_HOSTPOLICY");
73+
74+
// Variable used to indicate how app assemblies should be provided to the runtime
75+
// - PROPERTY: corerun will pass the paths vias the TRUSTED_PLATFORM_ASSEMBLIES property
76+
// - EXTERNAL: corerun will pass an external assembly probe to the runtime for app assemblies
77+
// - Not set: same as PROPERTY
78+
const char_t* appAssemblies = W("APP_ASSEMBLIES");
7379
}
7480

7581
static void wait_for_debugger()
@@ -242,6 +248,37 @@ size_t HOST_CONTRACT_CALLTYPE get_runtime_property(
242248
return -1;
243249
}
244250

251+
// Paths for external assembly probe
252+
static char* s_core_libs_path = nullptr;
253+
static char* s_core_root_path = nullptr;
254+
255+
static bool HOST_CONTRACT_CALLTYPE external_assembly_probe(
256+
const char* path,
257+
void** data_start,
258+
int64_t* size)
259+
{
260+
// Get just the file name
261+
const char* name = path;
262+
const char* pos = strrchr(name, '/');
263+
if (pos != NULL)
264+
name = pos + 1;
265+
266+
// Try to map the file from our known app assembly paths
267+
for (const char* dir : { s_core_libs_path, s_core_root_path })
268+
{
269+
if (dir == nullptr)
270+
continue;
271+
272+
std::string full_path = dir;
273+
assert(full_path.back() == pal::dir_delim);
274+
full_path.append(name);
275+
if (pal::try_map_file_readonly(full_path.c_str(), data_start, size))
276+
return true;
277+
}
278+
279+
return false;
280+
}
281+
245282
static int run(const configuration& config)
246283
{
247284
platform_specific_actions actions;
@@ -295,7 +332,37 @@ static int run(const configuration& config)
295332
native_search_dirs << core_root << pal::env_path_delim;
296333
}
297334

298-
string_t tpa_list = build_tpa(core_root, core_libs);
335+
string_t tpa_list;
336+
string_t app_assemblies_env = pal::getenv(envvar::appAssemblies);
337+
bool use_external_assembly_probe = false;
338+
if (app_assemblies_env.empty() || app_assemblies_env == W("PROPERTY"))
339+
{
340+
// Use the TRUSTED_PLATFORM_ASSEMBLIES property to pass the app assemblies to the runtime.
341+
tpa_list = build_tpa(core_root, core_libs);
342+
}
343+
else if (app_assemblies_env == W("EXTERNAL"))
344+
{
345+
// Use the external assembly probe to load assemblies from the app assembly paths.
346+
use_external_assembly_probe = true;
347+
if (!core_libs.empty())
348+
{
349+
pal::string_utf8_t core_libs_utf8 = pal::convert_to_utf8(core_libs.c_str());
350+
s_core_libs_path = (char*)::malloc(core_libs_utf8.length() + 1);
351+
::strcpy(s_core_libs_path, core_libs_utf8.c_str());
352+
}
353+
354+
if (!core_root.empty())
355+
{
356+
pal::string_utf8_t core_root_utf8 = pal::convert_to_utf8(core_root.c_str());
357+
s_core_root_path = (char*)::malloc(core_root_utf8.length() + 1);
358+
::strcpy(s_core_root_path, core_root_utf8.c_str());
359+
}
360+
}
361+
else
362+
{
363+
pal::fprintf(stderr, W("Unknown value for APP_ASSEMBLIES environment variable: %s\n"), app_assemblies_env.c_str());
364+
return -1;
365+
}
299366

300367
{
301368
// Load hostpolicy if requested.
@@ -376,7 +443,8 @@ static int run(const configuration& config)
376443
(void*)&config,
377444
&get_runtime_property,
378445
nullptr,
379-
nullptr };
446+
nullptr,
447+
use_external_assembly_probe ? &external_assembly_probe : nullptr };
380448
propertyKeys.push_back(HOST_PROPERTY_RUNTIME_CONTRACT);
381449
std::stringstream ss;
382450
ss << "0x" << std::hex << (size_t)(&host_contract);
@@ -457,6 +525,8 @@ static int run(const configuration& config)
457525
if (exit_code != -1)
458526
exit_code = latched_exit_code;
459527

528+
::free((void*)s_core_libs_path);
529+
::free((void*)s_core_root_path);
460530
return exit_code;
461531
}
462532

src/coreclr/hosts/corerun/corerun.hpp

+57-1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,34 @@ namespace pal
134134
return INVALID_FILE_ATTRIBUTES != ::GetFileAttributesW(file_path.c_str());
135135
}
136136

137+
inline bool try_map_file_readonly(const char* path, void** mapped, int64_t* size)
138+
{
139+
HANDLE file = ::CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
140+
if (file == INVALID_HANDLE_VALUE)
141+
return false;
142+
143+
HANDLE file_mapping = ::CreateFileMappingA(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
144+
if (file_mapping == nullptr)
145+
{
146+
::CloseHandle(file);
147+
return false;
148+
}
149+
150+
void* mapped_local = ::MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, 0);
151+
if (mapped_local == nullptr)
152+
{
153+
::CloseHandle(file);
154+
::CloseHandle(file_mapping);
155+
return false;
156+
}
157+
158+
*size = ::GetFileSize(file, nullptr);
159+
*mapped = mapped_local;
160+
::CloseHandle(file_mapping);
161+
::CloseHandle(file);
162+
return true;
163+
}
164+
137165
// Forward declaration
138166
void ensure_trailing_delimiter(pal::string_t& dir);
139167

@@ -300,7 +328,9 @@ class platform_specific_actions final
300328
#else // !TARGET_WINDOWS
301329
#include <dirent.h>
302330
#include <dlfcn.h>
331+
#include <fcntl.h>
303332
#include <limits.h>
333+
#include <sys/mman.h>
304334
#include <sys/stat.h>
305335
#include <unistd.h>
306336

@@ -309,7 +339,6 @@ class platform_specific_actions final
309339
#include <sys/sysctl.h>
310340
#include <sys/types.h>
311341
#else // !__APPLE__
312-
#include <fcntl.h>
313342
#include <ctype.h>
314343
#endif // !__APPLE__
315344

@@ -434,6 +463,33 @@ namespace pal
434463
return true;
435464
}
436465

466+
inline bool try_map_file_readonly(const char* path, void** mapped, int64_t* size)
467+
{
468+
int fd = open(path, O_RDONLY);
469+
if (fd == -1)
470+
return false;
471+
472+
struct stat buf;
473+
if (fstat(fd, &buf) == -1)
474+
{
475+
close(fd);
476+
return false;
477+
}
478+
479+
int64_t size_local = buf.st_size;
480+
void* mapped_local = mmap(NULL, size_local, PROT_READ, MAP_PRIVATE, fd, 0);
481+
if (mapped == MAP_FAILED)
482+
{
483+
close(fd);
484+
return false;
485+
}
486+
487+
*mapped = mapped_local;
488+
*size = size_local;
489+
close(fd);
490+
return true;
491+
}
492+
437493
// Forward declaration
438494
template<size_t LEN>
439495
bool string_ends_with(const string_t& str, const char_t(&suffix)[LEN]);

src/coreclr/vm/peimagelayout.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,9 @@ ConvertedImageLayout::ConvertedImageLayout(FlatImageLayout* source, bool disable
456456
LOG((LF_LOADER, LL_INFO100, "PEImage: Opening manually mapped stream\n"));
457457

458458
#ifdef TARGET_WINDOWS
459-
if (!disableMapping)
459+
// LoadImageByMappingParts assumes the source has a file mapping handle created/managed by the runtime.
460+
// This is not the case for non-file images (for example, external data). Only try to load by mapping for files.
461+
if (!disableMapping && m_pOwner->IsFile())
460462
{
461463
loadedImage = source->LoadImageByMappingParts(this->m_imageParts);
462464
if (loadedImage == NULL)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using Xunit;
6+
7+
public unsafe class ExternalAssemblyProbe
8+
{
9+
[Fact]
10+
public static void ExternalAppAssemblies()
11+
{
12+
// In order to get to this point, the runtime must have been able to find the app assemblies
13+
// Check that the TPA is indeed empty - that is, the runtime is not relying on that property.
14+
string tpa = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string;
15+
Assert.True(string.IsNullOrEmpty(tpa), "TRUSTED_PLATFORM_ASSEMBLIES should be empty");
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<!-- Needed for CLRTestEnvironmentVariable -->
4+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
5+
<NativeAotIncompatible>true</NativeAotIncompatible>
6+
<!-- External assembly probe via host-runtime contract is not implemented.
7+
The test uses probing to start at all, so it needs to be disabled in the project, not via an attribute -->
8+
<CLRTestTargetUnsupported Condition="'$(RuntimeFlavor)' != 'coreclr'">true</CLRTestTargetUnsupported>
9+
</PropertyGroup>
10+
<ItemGroup>
11+
<Compile Include="ExternalAssemblyProbe.cs" />
12+
13+
<CLRTestEnvironmentVariable Include="APP_ASSEMBLIES" Value="EXTERNAL" />
14+
</ItemGroup>
15+
</Project>

0 commit comments

Comments
 (0)