diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/DeveloperExperience/DeveloperExperience.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/DeveloperExperience/DeveloperExperience.cs
index c7253c85b1e1b9..1afcfd799e631b 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/DeveloperExperience/DeveloperExperience.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/DeveloperExperience/DeveloperExperience.cs
@@ -25,7 +25,7 @@ public virtual string CreateStackTraceString(IntPtr ip, bool includeFileInfo, ou
}
// If we don't have precise information, try to map it at least back to the right module.
- string moduleFullFileName = RuntimeAugments.TryGetFullPathToApplicationModule(ip, out IntPtr moduleBase);
+ string? moduleFullFileName = RuntimeAugments.TryGetFullPathToApplicationModule(ip, out IntPtr moduleBase);
// Without any callbacks or the ability to map ip correctly we better admit that we don't know
if (string.IsNullOrEmpty(moduleFullFileName))
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs
index 6fb23a261f3337..00fe842cf9f3d1 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs
@@ -621,7 +621,7 @@ public static RuntimeTypeHandle GetNullableType(RuntimeTypeHandle nullableType)
///
/// Address inside the module
/// Module base address
- public static unsafe string TryGetFullPathToApplicationModule(IntPtr ip, out IntPtr moduleBase)
+ public static unsafe string? TryGetFullPathToApplicationModule(IntPtr ip, out IntPtr moduleBase)
{
moduleBase = RuntimeImports.RhGetOSModuleFromPointer(ip);
if (moduleBase == IntPtr.Zero)
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/AppContext.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/AppContext.NativeAot.cs
index 535eb123ddf0f3..e4b354899dd8aa 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/AppContext.NativeAot.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/AppContext.NativeAot.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Internal.Runtime.Augments;
using System.Collections.Generic;
using System.Runtime;
using System.Runtime.ExceptionServices;
@@ -36,5 +37,21 @@ internal static void OnUnhandledException(object e)
{
UnhandledException?.Invoke(/* AppDomain */ null, new UnhandledExceptionEventArgs(e, true));
}
+
+ private static unsafe string GetRuntimeModulePath()
+ {
+ // We aren't going to call this method, we just need an address that we know is in this module.
+ // As this code is NativeAOT only, we know that this method will be AOT compiled into the executable,
+ // so the entry point address will be in the module.
+ void* ip = (void*)(delegate*)&GetRuntimeModulePath;
+ if (RuntimeAugments.TryGetFullPathToApplicationModule((nint)ip, out _) is string modulePath)
+ {
+ return modulePath;
+ }
+
+ // If this method isn't in a dynamically loaded module,
+ // then it's in the executable. In that case, we can use the process path.
+ return Environment.ProcessPath;
+ }
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/AppContext.AnyOS.cs b/src/libraries/System.Private.CoreLib/src/System/AppContext.AnyOS.cs
index b2c83e67dd4538..ed002f35600ccb 100644
--- a/src/libraries/System.Private.CoreLib/src/System/AppContext.AnyOS.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/AppContext.AnyOS.cs
@@ -19,7 +19,7 @@ private static string GetBaseDirectoryCore()
{
// Fallback path for hosts that do not set APP_CONTEXT_BASE_DIRECTORY explicitly
#if NATIVEAOT
- string? path = Environment.ProcessPath;
+ string? path = GetRuntimeModulePath();
#else
string? path = Assembly.GetEntryAssembly()?.Location;
#endif
diff --git a/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/CMakeLists.txt b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/CMakeLists.txt
new file mode 100644
index 00000000000000..311de58864d465
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/CMakeLists.txt
@@ -0,0 +1,14 @@
+project (SharedLibrary)
+include_directories(${INC_PLATFORM_DIR})
+
+add_executable(SharedLibraryHost SharedLibraryHost.cpp)
+add_library(SharedLibraryDependency SHARED SharedLibraryDependency.cpp)
+
+if (CLR_CMAKE_TARGET_UNIX)
+ target_link_libraries (SharedLibraryHost PRIVATE ${CMAKE_DL_LIBS})
+endif()
+
+# If there's a dynamic ASAN runtime, then copy it to project output.
+if (NOT "${ASAN_RUNTIME}" STREQUAL "")
+ file(COPY "${ASAN_RUNTIME}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
+endif()
diff --git a/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryDependency.cpp b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryDependency.cpp
new file mode 100644
index 00000000000000..d9f36cbd88fa9b
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryDependency.cpp
@@ -0,0 +1,9 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include
+
+extern "C" DLL_EXPORT int32_t STDMETHODCALLTYPE MultiplyIntegers(int32_t a, int32_t b)
+{
+ return a * b;
+}
diff --git a/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryDependencyLoading.cs b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryDependencyLoading.cs
new file mode 100644
index 00000000000000..ea84bcb3b3c04f
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryDependencyLoading.cs
@@ -0,0 +1,29 @@
+// 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.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SharedLibrary
+{
+ public class ClassLibrary
+ {
+ [UnmanagedCallersOnly(EntryPoint = "MultiplyIntegers", CallConvs = [typeof(CallConvStdcall)])]
+ public static int MultiplyIntegersExport(int x, int y)
+ {
+ return MultiplyIntegers(x, y);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "GetBaseDirectory", CallConvs = [typeof(CallConvStdcall)])]
+ public static IntPtr GetBaseDirectory()
+ {
+ return Marshal.StringToCoTaskMemAnsi(AppContext.BaseDirectory);
+ }
+
+ [DllImport("SharedLibraryDependency")]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
+ public static extern int MultiplyIntegers(int x, int y);
+ }
+}
diff --git a/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryDependencyLoading.csproj b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryDependencyLoading.csproj
new file mode 100644
index 00000000000000..6d4d2b52a8c28a
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryDependencyLoading.csproj
@@ -0,0 +1,43 @@
+
+
+ Library
+ BuildAndRun
+ 0
+ true
+ Shared
+ false
+
+ true
+ true
+ true
+
+
+
+ nul
+mkdir subdir 2>nul
+copy /y clang_rt.* native\
+copy /y SharedLibraryDependency.dll subdir\
+copy /y native\SharedLibraryDependencyLoading* subdir\
+copy /y SharedLibraryHost.exe native\SharedLibraryDependencyLoading.exe
+]]>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryHost.cpp b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryHost.cpp
new file mode 100644
index 00000000000000..f7fa40c5947331
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/SharedLibraryDependencyLoading/SharedLibraryHost.cpp
@@ -0,0 +1,103 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#ifdef TARGET_WINDOWS
+#include "windows.h"
+#else
+#include "dlfcn.h"
+#endif
+#include
+#include
+#include
+#include
+
+#ifndef TARGET_WINDOWS
+#define __stdcall
+#endif
+
+// typedef for shared lib exported methods
+using f_MultiplyIntegers = int32_t(__stdcall *)(int32_t, int32_t);
+using f_getBaseDirectory = const char*(__stdcall *)();
+
+#ifdef TARGET_WINDOWS
+template
+struct CoTaskMemDeleter
+{
+ void operator()(T* p) const
+ {
+ CoTaskMemFree((void*)p);
+ }
+};
+template
+using CoTaskMemPtr = std::unique_ptr>;
+#else
+template
+using CoTaskMemPtr = std::unique_ptr;
+#endif
+
+#ifdef TARGET_WINDOWS
+int __cdecl main(int argc, char* argv[])
+#else
+int main(int argc, char* argv[])
+#endif
+{
+ std::string pathToSubdir = argv[0];
+ // Step out of the current directory and the parent directory.
+ pathToSubdir = pathToSubdir.substr(0, pathToSubdir.find_last_of("/\\"));
+ pathToSubdir = pathToSubdir.substr(0, pathToSubdir.find_last_of("/\\"));
+#ifdef TARGET_WINDOWS
+ pathToSubdir += "subdir\\";
+ // We need to include System32 to find system dependencies of SharedLibraryDependencyLoading.dll
+ HINSTANCE handle = LoadLibraryEx("..\\subdir\\SharedLibraryDependencyLoading.dll", nullptr, LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
+#else
+#if TARGET_APPLE
+ constexpr char const* ext = ".dylib";
+#else
+ constexpr char const* ext = ".so";
+#endif
+
+ pathToSubdir += "subdir/";
+ std::string path = pathToSubdir + "SharedLibraryDependencyLoading";
+ path += ext;
+ void* handle = dlopen(path.c_str(), RTLD_LAZY);
+#endif
+
+ if (!handle)
+ return 1;
+
+#ifdef TARGET_WINDOWS
+ f_MultiplyIntegers multiplyIntegers = (f_MultiplyIntegers)GetProcAddress(handle, "MultiplyIntegers");
+#else
+ f_MultiplyIntegers multiplyIntegers = (f_MultiplyIntegers)dlsym(handle, "MultiplyIntegers");
+#endif
+
+ if (multiplyIntegers(10, 7) != 70)
+ return 2;
+
+ CoTaskMemPtr baseDirectory;
+#ifdef TARGET_WINDOWS
+ f_getBaseDirectory getBaseDirectory = (f_getBaseDirectory)GetProcAddress(handle, "GetBaseDirectory");
+#else
+ f_getBaseDirectory getBaseDirectory = (f_getBaseDirectory)dlsym(handle, "GetBaseDirectory");
+#endif
+
+ baseDirectory.reset(getBaseDirectory());
+ if (baseDirectory == nullptr)
+ return 3;
+
+ if (pathToSubdir != baseDirectory.get())
+ {
+ std::cout << "Expected base directory: " << pathToSubdir << std::endl;
+ std::cout << "Actual base directory: " << baseDirectory.get() << std::endl;
+ return 4;
+ }
+
+ return 100;
+}
+
+extern "C" const char* __stdcall __asan_default_options()
+{
+ // NativeAOT is not designed to be unloadable, so we'll leak a few allocations from the shared library.
+ // Disable leak detection as we don't care about these leaks as of now.
+ return "detect_leaks=0 use_sigaltstack=0";
+}