Skip to content

Set AppContext.BaseDirectory and the lookup path for AssemblyDirectory to the directory containing the NativeAOT module #112457

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ public static RuntimeTypeHandle GetNullableType(RuntimeTypeHandle nullableType)
/// </summary>
/// <param name="ip">Address inside the module</param>
/// <param name="moduleBase">Module base address</param>
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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*<string>)&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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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 <platformdefines.h>

extern "C" DLL_EXPORT int32_t STDMETHODCALLTYPE MultiplyIntegers(int32_t a, int32_t b)
{
return a * b;
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<CLRTestKind>BuildAndRun</CLRTestKind>
<CLRTestPriority>0</CLRTestPriority>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NativeLib>Shared</NativeLib>
<BuildAsStandalone>false</BuildAsStandalone>
<!-- Unable to compile a project with the Library output type for apple mobile devices -->
<CLRTestTargetUnsupported Condition="'$(TargetsAppleMobile)' == 'true'">true</CLRTestTargetUnsupported>
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<SkipInferOutputType>true</SkipInferOutputType>
</PropertyGroup>

<PropertyGroup>
<CLRTestBatchPreCommands><![CDATA[
$(CLRTestBatchPreCommands)
mkdir native 2>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
]]></CLRTestBatchPreCommands>

<CLRTestBashPreCommands><![CDATA[
$(CLRTestBashPreCommands)
mkdir -p native
mkdir -p subdir
cp libclang_rt.* native/
cp libSharedLibraryDependency.* subdir/
cp native/SharedLibraryDependencyLoading* subdir/
cp SharedLibraryHost native/SharedLibraryDependencyLoading
]]></CLRTestBashPreCommands>
</PropertyGroup>

<ItemGroup>
<Compile Include="SharedLibraryDependencyLoading.cs" />
</ItemGroup>
<ItemGroup>
<CMakeProjectReference Include="CMakeLists.txt" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -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 <cstdint>
#include <string>
#include <memory>
#include <iostream>

#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<typename T>
struct CoTaskMemDeleter
{
void operator()(T* p) const
{
CoTaskMemFree((void*)p);
}
};
template<typename T>
using CoTaskMemPtr = std::unique_ptr<T, CoTaskMemDeleter<T>>;
#else
template<typename T>
using CoTaskMemPtr = std::unique_ptr<T>;
#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<const char> 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";
}
Loading