Skip to content

Commit 7cfe5e3

Browse files
authored
Set AppContext.BaseDirectory and the lookup path for AssemblyDirectory to the directory containing the NativeAOT module (#112457)
* Set AppContext.BaseDirectory and the lookup path for AssemblyDirectory to the directory containing the NativeAOT module * Fix calling convention * Fix missing using * Rewrite an add comment * Add test * Make TryGetFullPathToApplicationModule nullable * Don't use relative paths to load dependencies on Linux/macos. Those are relative to the current working directory. * Add test for AppContext.BaseDirectory * Fix test on Windows
1 parent f0aa3ab commit 7cfe5e3

File tree

9 files changed

+218
-3
lines changed

9 files changed

+218
-3
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/DeveloperExperience/DeveloperExperience.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public virtual string CreateStackTraceString(IntPtr ip, bool includeFileInfo, ou
2525
}
2626

2727
// If we don't have precise information, try to map it at least back to the right module.
28-
string moduleFullFileName = RuntimeAugments.TryGetFullPathToApplicationModule(ip, out IntPtr moduleBase);
28+
string? moduleFullFileName = RuntimeAugments.TryGetFullPathToApplicationModule(ip, out IntPtr moduleBase);
2929

3030
// Without any callbacks or the ability to map ip correctly we better admit that we don't know
3131
if (string.IsNullOrEmpty(moduleFullFileName))

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ public static RuntimeTypeHandle GetNullableType(RuntimeTypeHandle nullableType)
641641
/// </summary>
642642
/// <param name="ip">Address inside the module</param>
643643
/// <param name="moduleBase">Module base address</param>
644-
public static unsafe string TryGetFullPathToApplicationModule(IntPtr ip, out IntPtr moduleBase)
644+
public static unsafe string? TryGetFullPathToApplicationModule(IntPtr ip, out IntPtr moduleBase)
645645
{
646646
moduleBase = RuntimeImports.RhGetOSModuleFromPointer(ip);
647647
if (moduleBase == IntPtr.Zero)

src/coreclr/nativeaot/System.Private.CoreLib/src/System/AppContext.NativeAot.cs

+17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Internal.Runtime.Augments;
45
using System.Collections.Generic;
56
using System.Runtime;
67
using System.Runtime.ExceptionServices;
@@ -36,5 +37,21 @@ internal static void OnUnhandledException(object e)
3637
{
3738
UnhandledException?.Invoke(/* AppDomain */ null, new UnhandledExceptionEventArgs(e, true));
3839
}
40+
41+
private static unsafe string GetRuntimeModulePath()
42+
{
43+
// We aren't going to call this method, we just need an address that we know is in this module.
44+
// As this code is NativeAOT only, we know that this method will be AOT compiled into the executable,
45+
// so the entry point address will be in the module.
46+
void* ip = (void*)(delegate*<string>)&GetRuntimeModulePath;
47+
if (RuntimeAugments.TryGetFullPathToApplicationModule((nint)ip, out _) is string modulePath)
48+
{
49+
return modulePath;
50+
}
51+
52+
// If this method isn't in a dynamically loaded module,
53+
// then it's in the executable. In that case, we can use the process path.
54+
return Environment.ProcessPath;
55+
}
3956
}
4057
}

src/libraries/System.Private.CoreLib/src/System/AppContext.AnyOS.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ private static string GetBaseDirectoryCore()
1919
{
2020
// Fallback path for hosts that do not set APP_CONTEXT_BASE_DIRECTORY explicitly
2121
#if NATIVEAOT
22-
string? path = Environment.ProcessPath;
22+
string? path = GetRuntimeModulePath();
2323
#else
2424
string? path = Assembly.GetEntryAssembly()?.Location;
2525
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
project (SharedLibrary)
2+
include_directories(${INC_PLATFORM_DIR})
3+
4+
add_executable(SharedLibraryHost SharedLibraryHost.cpp)
5+
add_library(SharedLibraryDependency SHARED SharedLibraryDependency.cpp)
6+
7+
if (CLR_CMAKE_TARGET_UNIX)
8+
target_link_libraries (SharedLibraryHost PRIVATE ${CMAKE_DL_LIBS})
9+
endif()
10+
11+
# If there's a dynamic ASAN runtime, then copy it to project output.
12+
if (NOT "${ASAN_RUNTIME}" STREQUAL "")
13+
file(COPY "${ASAN_RUNTIME}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
14+
endif()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
#include <platformdefines.h>
5+
6+
extern "C" DLL_EXPORT int32_t STDMETHODCALLTYPE MultiplyIntegers(int32_t a, int32_t b)
7+
{
8+
return a * b;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
8+
namespace SharedLibrary
9+
{
10+
public class ClassLibrary
11+
{
12+
[UnmanagedCallersOnly(EntryPoint = "MultiplyIntegers", CallConvs = [typeof(CallConvStdcall)])]
13+
public static int MultiplyIntegersExport(int x, int y)
14+
{
15+
return MultiplyIntegers(x, y);
16+
}
17+
18+
[UnmanagedCallersOnly(EntryPoint = "GetBaseDirectory", CallConvs = [typeof(CallConvStdcall)])]
19+
public static IntPtr GetBaseDirectory()
20+
{
21+
return Marshal.StringToCoTaskMemAnsi(AppContext.BaseDirectory);
22+
}
23+
24+
[DllImport("SharedLibraryDependency")]
25+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
26+
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
27+
public static extern int MultiplyIntegers(int x, int y);
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Library</OutputType>
4+
<CLRTestKind>BuildAndRun</CLRTestKind>
5+
<CLRTestPriority>0</CLRTestPriority>
6+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
7+
<NativeLib>Shared</NativeLib>
8+
<BuildAsStandalone>false</BuildAsStandalone>
9+
<!-- Unable to compile a project with the Library output type for apple mobile devices -->
10+
<CLRTestTargetUnsupported Condition="'$(TargetsAppleMobile)' == 'true'">true</CLRTestTargetUnsupported>
11+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
12+
<SkipInferOutputType>true</SkipInferOutputType>
13+
</PropertyGroup>
14+
15+
<PropertyGroup>
16+
<CLRTestBatchPreCommands><![CDATA[
17+
$(CLRTestBatchPreCommands)
18+
mkdir native 2>nul
19+
mkdir subdir 2>nul
20+
copy /y clang_rt.* native\
21+
copy /y SharedLibraryDependency.dll subdir\
22+
copy /y native\SharedLibraryDependencyLoading* subdir\
23+
copy /y SharedLibraryHost.exe native\SharedLibraryDependencyLoading.exe
24+
]]></CLRTestBatchPreCommands>
25+
26+
<CLRTestBashPreCommands><![CDATA[
27+
$(CLRTestBashPreCommands)
28+
mkdir -p native
29+
mkdir -p subdir
30+
cp libclang_rt.* native/
31+
cp libSharedLibraryDependency.* subdir/
32+
cp native/SharedLibraryDependencyLoading* subdir/
33+
cp SharedLibraryHost native/SharedLibraryDependencyLoading
34+
]]></CLRTestBashPreCommands>
35+
</PropertyGroup>
36+
37+
<ItemGroup>
38+
<Compile Include="SharedLibraryDependencyLoading.cs" />
39+
</ItemGroup>
40+
<ItemGroup>
41+
<CMakeProjectReference Include="CMakeLists.txt" />
42+
</ItemGroup>
43+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
#ifdef TARGET_WINDOWS
5+
#include "windows.h"
6+
#else
7+
#include "dlfcn.h"
8+
#endif
9+
#include <cstdint>
10+
#include <string>
11+
#include <memory>
12+
#include <iostream>
13+
14+
#ifndef TARGET_WINDOWS
15+
#define __stdcall
16+
#endif
17+
18+
// typedef for shared lib exported methods
19+
using f_MultiplyIntegers = int32_t(__stdcall *)(int32_t, int32_t);
20+
using f_getBaseDirectory = const char*(__stdcall *)();
21+
22+
#ifdef TARGET_WINDOWS
23+
template<typename T>
24+
struct CoTaskMemDeleter
25+
{
26+
void operator()(T* p) const
27+
{
28+
CoTaskMemFree((void*)p);
29+
}
30+
};
31+
template<typename T>
32+
using CoTaskMemPtr = std::unique_ptr<T, CoTaskMemDeleter<T>>;
33+
#else
34+
template<typename T>
35+
using CoTaskMemPtr = std::unique_ptr<T>;
36+
#endif
37+
38+
#ifdef TARGET_WINDOWS
39+
int __cdecl main(int argc, char* argv[])
40+
#else
41+
int main(int argc, char* argv[])
42+
#endif
43+
{
44+
std::string pathToSubdir = argv[0];
45+
// Step out of the current directory and the parent directory.
46+
pathToSubdir = pathToSubdir.substr(0, pathToSubdir.find_last_of("/\\"));
47+
pathToSubdir = pathToSubdir.substr(0, pathToSubdir.find_last_of("/\\"));
48+
#ifdef TARGET_WINDOWS
49+
pathToSubdir += "subdir\\";
50+
// We need to include System32 to find system dependencies of SharedLibraryDependencyLoading.dll
51+
HINSTANCE handle = LoadLibraryEx("..\\subdir\\SharedLibraryDependencyLoading.dll", nullptr, LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
52+
#else
53+
#if TARGET_APPLE
54+
constexpr char const* ext = ".dylib";
55+
#else
56+
constexpr char const* ext = ".so";
57+
#endif
58+
59+
pathToSubdir += "subdir/";
60+
std::string path = pathToSubdir + "SharedLibraryDependencyLoading";
61+
path += ext;
62+
void* handle = dlopen(path.c_str(), RTLD_LAZY);
63+
#endif
64+
65+
if (!handle)
66+
return 1;
67+
68+
#ifdef TARGET_WINDOWS
69+
f_MultiplyIntegers multiplyIntegers = (f_MultiplyIntegers)GetProcAddress(handle, "MultiplyIntegers");
70+
#else
71+
f_MultiplyIntegers multiplyIntegers = (f_MultiplyIntegers)dlsym(handle, "MultiplyIntegers");
72+
#endif
73+
74+
if (multiplyIntegers(10, 7) != 70)
75+
return 2;
76+
77+
CoTaskMemPtr<const char> baseDirectory;
78+
#ifdef TARGET_WINDOWS
79+
f_getBaseDirectory getBaseDirectory = (f_getBaseDirectory)GetProcAddress(handle, "GetBaseDirectory");
80+
#else
81+
f_getBaseDirectory getBaseDirectory = (f_getBaseDirectory)dlsym(handle, "GetBaseDirectory");
82+
#endif
83+
84+
baseDirectory.reset(getBaseDirectory());
85+
if (baseDirectory == nullptr)
86+
return 3;
87+
88+
if (pathToSubdir != baseDirectory.get())
89+
{
90+
std::cout << "Expected base directory: " << pathToSubdir << std::endl;
91+
std::cout << "Actual base directory: " << baseDirectory.get() << std::endl;
92+
return 4;
93+
}
94+
95+
return 100;
96+
}
97+
98+
extern "C" const char* __stdcall __asan_default_options()
99+
{
100+
// NativeAOT is not designed to be unloadable, so we'll leak a few allocations from the shared library.
101+
// Disable leak detection as we don't care about these leaks as of now.
102+
return "detect_leaks=0 use_sigaltstack=0";
103+
}

0 commit comments

Comments
 (0)