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"; +}