Skip to content

Commit f0230db

Browse files
authored
Remove always searching executable directory for native libraries in single-file applications (#115236)
Avoid always searching the application directory for native libraries in single-file applications. The assembly directory, which is searched by default and when `DllImportSearchPath.AssemblyDirectory` is specified, is treated as the application directory. - Single-file - Stop adding the app directory and extraction directory to `NATIVE_DLL_SEARCH_DIRECTORIES` (paths which are always searched) - When looking for native libraries, treat assembly directory for bundled assemblies as the bundle directory - If the bundle has extracted assets, also look in the extraction path (if not found in the bundle directory) - NativeAOT - Update p/invoke default to search assembly directory - Don't set RPATH by default - Remove `IlcRPath` property
1 parent fc85a87 commit f0230db

File tree

20 files changed

+127
-84
lines changed

20 files changed

+127
-84
lines changed

docs/design/features/host-runtime-information.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ List of directory paths corresponding to shared store paths and additional probi
8080

8181
### Single-file
8282

83+
`BUNDLE_EXTRACTION_PATH`
84+
85+
**Added in .NET 10** Path to extraction directory, if the single-file bundle extracted any files. This is used by the runtime to search for native libraries associated with bundled managed assemblies.
86+
8387
`BUNDLE_PROBE`
8488

8589
Hex string representation of a function pointer. It is set when running a single-file application. The function is called by the runtime to look for assemblies bundled into the application. The expected signature is defined as `BundleProbeFn` in [`coreclrhost.h`](/src/coreclr/hosts/inc/coreclrhost.h)

src/coreclr/binder/assemblybindercommon.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,10 @@ namespace BINDER_SPACE
858858
ProbeExtensionResult probeExtensionResult = AssemblyProbeExtension::Probe(assemblyFileName, /* pathIsBundleRelative */ true);
859859
if (probeExtensionResult.IsValid())
860860
{
861-
SString assemblyFilePath(Bundle::AppIsBundle() ? Bundle::AppBundle->BasePath() : SString::Empty());
861+
SString assemblyFilePath;
862+
if (Bundle::AppIsBundle())
863+
assemblyFilePath.SetUTF8(Bundle::AppBundle->BasePath());
864+
862865
assemblyFilePath.Append(assemblyFileName);
863866

864867
hr = GetAssembly(assemblyFilePath,

src/coreclr/inc/bundle.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,13 @@ class Bundle
4343
Bundle(LPCSTR bundlePath, BundleProbeFn *probe);
4444
BundleFileLocation Probe(const SString& path, bool pathIsBundleRelative = false) const;
4545

46-
const SString &Path() const { LIMITED_METHOD_CONTRACT; return m_path; }
47-
const SString &BasePath() const { LIMITED_METHOD_CONTRACT; return m_basePath; }
46+
// Paths do not change and should remain valid for the lifetime of the Bundle
47+
const SString& Path() const { LIMITED_METHOD_CONTRACT; return m_path; }
48+
const UTF8* BasePath() const { LIMITED_METHOD_CONTRACT; return m_basePath.GetUTF8(); }
49+
50+
// Extraction path does not change and should remain valid for the lifetime of the Bundle
51+
bool HasExtractedFiles() const { LIMITED_METHOD_CONTRACT; return !m_extractionPath.IsEmpty(); }
52+
const WCHAR* ExtractionPath() const { LIMITED_METHOD_CONTRACT; return m_extractionPath.GetUnicode(); }
4853

4954
static Bundle* AppBundle; // The BundleInfo for the current app, initialized by coreclr_initialize.
5055
static bool AppIsBundle() { LIMITED_METHOD_CONTRACT; return AppBundle != nullptr; }
@@ -53,6 +58,7 @@ class Bundle
5358
private:
5459
SString m_path; // The path to single-file executable
5560
BundleProbeFn *m_probe;
61+
SString m_extractionPath; // The path to the extraction location, if bundle extracted any files
5662

5763
SString m_basePath; // The prefix to denote a path within the bundle
5864
COUNT_T m_basePathLength;

src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,6 @@ The .NET Foundation licenses this file to you under the MIT license.
6464
<TargetTriple Condition="'$(CrossCompileArch)' != '' and '$(_IsAlpineExitCode)' == '0'">$(CrossCompileArch)-alpine-linux-$(CrossCompileAbi)</TargetTriple>
6565
<TargetTriple Condition="'$(CrossCompileArch)' != '' and ($(CrossCompileRid.StartsWith('freebsd')))">$(CrossCompileArch)-unknown-freebsd12</TargetTriple>
6666

67-
<IlcRPath Condition="'$(IlcRPath)' == '' and '$(_IsApplePlatform)' != 'true'">$ORIGIN</IlcRPath>
68-
<IlcRPath Condition="'$(IlcRPath)' == '' and '$(_IsApplePlatform)' == 'true'">@executable_path</IlcRPath>
69-
7067
<SharedLibraryInstallName Condition="'$(SharedLibraryInstallName)' == '' and '$(_IsApplePlatform)' == 'true' and '$(NativeLib)' == 'Shared'">@rpath/$(TargetName)$(NativeBinaryExt)</SharedLibraryInstallName>
7168

7269
<EventPipeName>libeventpipe-disabled</EventPipeName>
@@ -234,8 +231,6 @@ The .NET Foundation licenses this file to you under the MIT license.
234231
<LinkerArg Include="--target=$(TargetTriple)" Condition="'$(TargetTriple)' != ''" />
235232
<LinkerArg Include="-g" Condition="$(NativeDebugSymbols) == 'true'" />
236233
<LinkerArg Include="-Wl,--strip-debug" Condition="$(NativeDebugSymbols) != 'true' and '$(_IsApplePlatform)' != 'true'" />
237-
<LinkerArg Include="-Wl,-rpath,'$(IlcRPath)'" Condition="'$(StaticExecutable)' != 'true' and !$([MSBuild]::IsOSPlatform('Windows'))" />
238-
<LinkerArg Include="-Wl,-rpath,&quot;$(IlcRPath)&quot;" Condition="'$(StaticExecutable)' != 'true' and $([MSBuild]::IsOSPlatform('Windows'))" />
239234
<LinkerArg Include="-Wl,-install_name,&quot;$(SharedLibraryInstallName)&quot;" Condition="'$(_IsApplePlatform)' == 'true' and '$(NativeLib)' == 'Shared'" />
240235
<LinkerArg Include="-Wl,--build-id=sha1" Condition="'$(_IsApplePlatform)' != 'true'" />
241236
<LinkerArg Include="-Wl,--as-needed" Condition="'$(_IsApplePlatform)' != 'true'" />

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ internal static unsafe void FixupModuleCell(ModuleFixupCell* pCell)
292292
{
293293
string moduleName = GetModuleName(pCell);
294294

295-
uint dllImportSearchPath = 0;
295+
uint dllImportSearchPath = (uint)DllImportSearchPath.AssemblyDirectory;
296296
bool hasDllImportSearchPath = (pCell->DllImportSearchPathAndCookie & InteropDataConstants.HasDllImportSearchPath) != 0;
297297
if (hasDllImportSearchPath)
298298
{

src/coreclr/vm/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON
5353
assembly.cpp
5454
assemblybinder.cpp
5555
binder.cpp
56-
bundle.cpp
5756
castcache.cpp
5857
callcounting.cpp
5958
cdacplatformmetadata.cpp
@@ -298,6 +297,7 @@ set(VM_SOURCES_WKS
298297
assemblyprobeextension.cpp
299298
assemblyspec.cpp
300299
baseassemblyspec.cpp
300+
bundle.cpp
301301
${RUNTIME_DIR}/CachedInterfaceDispatch.cpp
302302
CachedInterfaceDispatch_Coreclr.cpp
303303
cachelinealloc.cpp

src/coreclr/vm/bundle.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "common.h"
1111
#include "bundle.h"
12+
#include "hostinformation.h"
1213
#include <utilcode.h>
1314
#include <corhost.h>
1415
#include <sstring.h>
@@ -47,6 +48,10 @@ Bundle::Bundle(LPCSTR bundlePath, BundleProbeFn *probe)
4748
size_t baseLen = pos - bundlePath + 1; // Include DIRECTORY_SEPARATOR_CHAR_A in m_basePath
4849
m_basePath.SetUTF8(bundlePath, (COUNT_T)baseLen);
4950
m_basePathLength = (COUNT_T)baseLen;
51+
52+
SString extractionPathMaybe;
53+
if (HostInformation::GetProperty(HOST_PROPERTY_BUNDLE_EXTRACTION_PATH, extractionPathMaybe))
54+
m_extractionPath.Set(extractionPathMaybe.GetUnicode());
5055
}
5156

5257
BundleFileLocation Bundle::Probe(const SString& path, bool pathIsBundleRelative) const

src/coreclr/vm/nativelibrary.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,12 +472,20 @@ namespace
472472
NATIVE_LIBRARY_HANDLE LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
473473
{
474474
STANDARD_VM_CONTRACT;
475+
_ASSERTE(libName != NULL);
475476

476-
if (pAssembly->GetPEAssembly()->GetPath().IsEmpty())
477+
SString path{ pAssembly->GetPEAssembly()->GetPath() };
478+
479+
// Bundled assembly - path will be empty, path to load should point to the single-file bundle
480+
bool isBundledAssembly = pAssembly->GetPEAssembly()->HasPEImage() && pAssembly->GetPEAssembly()->GetPEImage()->IsInBundle();
481+
_ASSERTE(!isBundledAssembly || Bundle::AppBundle != NULL);
482+
if (isBundledAssembly)
483+
path.Set(pAssembly->GetPEAssembly()->GetPEImage()->GetPathToLoad());
484+
485+
if (path.IsEmpty())
477486
return NULL;
478487

479488
NATIVE_LIBRARY_HANDLE hmod = NULL;
480-
SString path{ pAssembly->GetPEAssembly()->GetPath() };
481489
_ASSERTE(!Path::IsRelative(path));
482490

483491
SString::Iterator lastPathSeparatorIter = path.End();
@@ -490,6 +498,15 @@ namespace
490498
hmod = LocalLoadLibraryHelper(path, flags, pErrorTracker);
491499
}
492500

501+
// Bundle with additional files extracted - also treat the extraction path as the assembly directory for native library load
502+
if (hmod == NULL && isBundledAssembly && Bundle::AppBundle->HasExtractedFiles())
503+
{
504+
path.Set(Bundle::AppBundle->ExtractionPath());
505+
path.Append(DIRECTORY_SEPARATOR_CHAR_W);
506+
path.Append(libName);
507+
hmod = LocalLoadLibraryHelper(path, flags, pErrorTracker);
508+
}
509+
493510
return hmod;
494511
}
495512

src/coreclr/vm/peimage.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -758,8 +758,6 @@ PTR_PEImage PEImage::CreateFromHMODULE(HMODULE hMod)
758758
}
759759
#endif // !TARGET_UNIX
760760

761-
#endif //DACCESS_COMPILE
762-
763761
HANDLE PEImage::GetFileHandle()
764762
{
765763
CONTRACTL
@@ -776,11 +774,7 @@ HANDLE PEImage::GetFileHandle()
776774

777775
if (m_hFile == INVALID_HANDLE_VALUE)
778776
{
779-
#if !defined(DACCESS_COMPILE)
780777
EEFileLoadException::Throw(GetPathToLoad(), hr);
781-
#else // defined(DACCESS_COMPILE)
782-
ThrowHR(hr);
783-
#endif // !defined(DACCESS_COMPILE)
784778
}
785779

786780
return m_hFile;
@@ -819,6 +813,7 @@ HRESULT PEImage::TryOpenFile(bool takeLock)
819813
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
820814
}
821815

816+
#endif // !DACCESS_COMPILE
822817

823818
BOOL PEImage::IsPtrInImage(PTR_CVOID data)
824819
{

src/coreclr/vm/peimage.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ class PEImage final
145145
INT64 GetSize() const;
146146
BOOL IsCompressed(INT64* uncompressedSize = NULL) const;
147147

148+
#ifndef DACCESS_COMPILE
148149
HANDLE GetFileHandle();
149150
HRESULT TryOpenFile(bool takeLock = false);
151+
#endif
150152

151153
void GetMVID(GUID *pMvid);
152154
IMDInternalImport* GetMDImport();

src/installer/tests/AppHost.Bundle.Tests/NativeLibraries.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ private void PInvoke(bool selfContained, bool bundleNative)
5050
.Should().Pass()
5151
.And.CallPInvoke(null, true)
5252
.And.CallPInvoke(DllImportSearchPath.AssemblyDirectory, true)
53-
// Single-file always looks in application directory, even when only System32 is specified
54-
.And.CallPInvoke(DllImportSearchPath.System32, true);
53+
.And.CallPInvoke(DllImportSearchPath.System32, false);
5554
}
5655

5756
[Fact]
@@ -84,8 +83,7 @@ private void TryLoad(bool selfContained, bool bundleNative)
8483
.Should().Pass()
8584
.And.TryLoadLibrary(null, true)
8685
.And.TryLoadLibrary(DllImportSearchPath.AssemblyDirectory, true)
87-
// Single-file always looks in application directory, even when only System32 is specified
88-
.And.TryLoadLibrary(DllImportSearchPath.System32, true);
86+
.And.TryLoadLibrary(DllImportSearchPath.System32, false);
8987
}
9088

9189
internal static string GetLibraryName(DllImportSearchPath? flags)

src/installer/tests/AppHost.Bundle.Tests/SingleFileApiTests.cs

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -64,45 +64,6 @@ public void SelfContained_BundleAllContent()
6464
.And.HaveStdOutContaining("System.Console location: " + extractionDir); // System.Console should be from extracted location
6565
}
6666

67-
[Fact]
68-
public void NativeSearchDirectories()
69-
{
70-
string singleFile = sharedTestState.BundledAppPath;
71-
string extractionRoot = sharedTestState.App.GetNewExtractionRootPath();
72-
string bundleDir = Directory.GetParent(singleFile).FullName;
73-
74-
// If we don't extract anything to disk, the extraction dir shouldn't
75-
// appear in the native search dirs.
76-
Command.Create(singleFile, "native_search_dirs")
77-
.CaptureStdErr()
78-
.CaptureStdOut()
79-
.EnvironmentVariable(Constants.BundleExtractBase.EnvironmentVariable, extractionRoot)
80-
.Execute()
81-
.Should().Pass()
82-
.And.HaveStdOutContaining(bundleDir)
83-
.And.NotHaveStdOutContaining(extractionRoot);
84-
}
85-
86-
[Fact]
87-
public void NativeSearchDirectories_WithExtraction()
88-
{
89-
SingleFileTestApp app = sharedTestState.App;
90-
string singleFile = app.Bundle(BundleOptions.BundleNativeBinaries, out Manifest manifest);
91-
92-
string extractionRoot = app.GetNewExtractionRootPath();
93-
string extractionDir = app.GetExtractionDir(extractionRoot, manifest).FullName;
94-
string bundleDir = Directory.GetParent(singleFile).FullName;
95-
96-
Command.Create(singleFile, "native_search_dirs")
97-
.CaptureStdErr()
98-
.CaptureStdOut()
99-
.EnvironmentVariable(Constants.BundleExtractBase.EnvironmentVariable, extractionRoot)
100-
.Execute()
101-
.Should().Pass()
102-
.And.HaveStdOutContaining(extractionDir)
103-
.And.HaveStdOutContaining(bundleDir);
104-
}
105-
10667
public class SharedTestState : IDisposable
10768
{
10869
public SingleFileTestApp App { get; set; }

src/installer/tests/Assets/Projects/SingleFileApiTests/Program.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,6 @@ public static int Main(string[] args)
7171
Console.WriteLine("AppContext.BaseDirectory: " + AppContext.BaseDirectory);
7272
break;
7373

74-
case "native_search_dirs":
75-
var native_search_dirs = AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES");
76-
Console.WriteLine("NATIVE_DLL_SEARCH_DIRECTORIES: " + native_search_dirs);
77-
break;
78-
7974
default:
8075
Console.WriteLine("test failure");
8176
return -1;

src/native/corehost/host_runtime_contract.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#define HOST_PROPERTY_RUNTIME_CONTRACT "HOST_RUNTIME_CONTRACT"
1818
#define HOST_PROPERTY_APP_PATHS "APP_PATHS"
1919
#define HOST_PROPERTY_BUNDLE_PROBE "BUNDLE_PROBE"
20+
#define HOST_PROPERTY_BUNDLE_EXTRACTION_PATH "BUNDLE_EXTRACTION_PATH"
2021
#define HOST_PROPERTY_ENTRY_ASSEMBLY_NAME "ENTRY_ASSEMBLY_NAME"
2122
#define HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES "NATIVE_DLL_SEARCH_DIRECTORIES"
2223
#define HOST_PROPERTY_PINVOKE_OVERRIDE "PINVOKE_OVERRIDE"

src/native/corehost/hostpolicy/deps_resolver.cpp

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -854,19 +854,6 @@ bool deps_resolver_t::resolve_probe_dirs(
854854
}
855855
}
856856

857-
// If this is a single-file app, add the app's dir to the native search directories.
858-
if (bundle::info_t::is_single_file_bundle() && !is_resources)
859-
{
860-
auto bundle = bundle::runner_t::app();
861-
add_unique_path(asset_type, bundle->base_path(), &items, output, &non_serviced, core_servicing);
862-
863-
// Add the extraction path if it exists.
864-
if (pal::directory_exists(bundle->extraction_path()))
865-
{
866-
add_unique_path(asset_type, bundle->extraction_path(), &items, output, &non_serviced, core_servicing);
867-
}
868-
}
869-
870857
output->append(non_serviced);
871858

872859
return true;

src/native/corehost/hostpolicy/hostpolicy_context.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ namespace
120120
return pal::pal_utf8string(get_filename_without_ext(context->application), value_buffer, value_buffer_size);
121121
}
122122

123+
if (::strcmp(key, HOST_PROPERTY_BUNDLE_EXTRACTION_PATH) == 0)
124+
{
125+
if (!bundle::info_t::is_single_file_bundle())
126+
return -1;
127+
128+
auto bundle = bundle::runner_t::app();
129+
if (bundle->extraction_path().empty())
130+
return -1;
131+
132+
return pal::pal_utf8string(bundle->extraction_path(), value_buffer, value_buffer_size);
133+
}
134+
123135
// Properties from runtime initialization
124136
pal::string_t key_str;
125137
if (pal::clr_palstring(key, &key_str))

src/tests/Directory.Build.targets

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -527,11 +527,6 @@
527527
variables is horribly slow (seeing 1.4 seconds on a 11th gen Intel Core i7) -->
528528
<IlcUseEnvironmentalTools>true</IlcUseEnvironmentalTools>
529529

530-
<!-- NativeAOT compiled output is placed into a 'native' subdirectory: we need to tweak
531-
rpath so that the test can load its native library dependencies if there's any -->
532-
<IlcRPath Condition="'$(TargetOS)' == 'osx' or '$(TargetsAppleMobile)' == 'true'">@executable_path/..</IlcRPath>
533-
<IlcRPath Condition="'$(IlcRPath)' == ''">$ORIGIN/..</IlcRPath>
534-
535530
<!-- Works around "Error: Native compilation can run on x64 and arm64 hosts only" -->
536531
<DisableUnsupportedError>true</DisableUnsupportedError>
537532

@@ -576,6 +571,11 @@
576571
<IlcArg Include="--trim:xunit.core" />
577572
<IlcArg Include="--trim:xunit.execution.dotnet" />
578573
<IlcArg Include="--trim:xunit.abstractions" />
574+
575+
<!-- NativeAOT compiled output is placed into a 'native' subdirectory: we need to set
576+
rpath so that the test can load its native library dependencies if there's any -->
577+
<LinkerArg Include="-Wl,-rpath,'@executable_path/..'" Condition="'$(SetIlcRPath)' != 'false' and '$(TargetOS)' != 'win' and ('$(TargetOS)' == 'osx' or '$(TargetsAppleMobile)' == 'true')" />
578+
<LinkerArg Include="-Wl,-rpath,'$ORIGIN/..'" Condition="'$(SetIlcRPath)' != 'false' and '$(TargetOS)' != 'win' and '$(TargetOS)' != 'osx' and '$(TargetsAppleMobile)' != 'true'" />
579579
</ItemGroup>
580580

581581
<Import Project="$(RepositoryEngineeringDir)nativeSanitizers.targets" Condition="'$(TestBuildMode)' == 'nativeaot'" />

0 commit comments

Comments
 (0)