diff --git a/src/installer/tests/AppHost.Bundle.Tests/StaticHost.cs b/src/installer/tests/AppHost.Bundle.Tests/StaticHost.cs index 1aa93f860be6f3..30c78cd2d1877a 100644 --- a/src/installer/tests/AppHost.Bundle.Tests/StaticHost.cs +++ b/src/installer/tests/AppHost.Bundle.Tests/StaticHost.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Runtime.InteropServices; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; using Xunit; @@ -26,5 +27,57 @@ private void NotMarkedAsBundle_RunSelfContainedApp() .And.HaveStdOutContaining("Hello World"); } } + + [Fact] + private void ExpectedExports() + { + string[] expectedExports = + [ + "DotNetRuntimeInfo", + "g_dacTable", + "MetaDataGetDispenser", + "DotNetRuntimeContractDescriptor", + ]; + + string singleFileHostPath = Binaries.SingleFileHost.FilePath; + + if (OperatingSystem.IsWindows()) + { + IntPtr handle = NativeLibrary.Load(singleFileHostPath); + try + { + foreach (string exportName in expectedExports) + { + Assert.True( + NativeLibrary.TryGetExport(handle, exportName, out _), + $"Expected singlefilehost to export {exportName}"); + } + } + finally + { + NativeLibrary.Free(handle); + } + } + else + { + // Use nm to check for exported defined symbols. + // On macOS, -gUj shows external defined symbol names only. + // On Linux, -D --defined-only shows defined dynamic symbols. + Command command = OperatingSystem.IsMacOS() + ? Command.Create("nm", "-gUj", singleFileHostPath) + : Command.Create("nm", "-D", "--defined-only", singleFileHostPath); + + CommandResult result = command + .CaptureStdOut() + .CaptureStdErr() + .Execute(); + result.Should().Pass(); + + foreach (string exportName in expectedExports) + { + Assert.Contains(exportName, result.StdOut); + } + } + } } } diff --git a/src/native/corehost/apphost/static/CMakeLists.txt b/src/native/corehost/apphost/static/CMakeLists.txt index 6a2ced4343facc..3a973afd31be7c 100644 --- a/src/native/corehost/apphost/static/CMakeLists.txt +++ b/src/native/corehost/apphost/static/CMakeLists.txt @@ -266,6 +266,24 @@ endif() set_property(TARGET singlefilehost PROPERTY ENABLE_EXPORTS 1) +# Force the linker to pull in symbols from coreclr_static even though nothing in +# singlefilehost references them directly. Without this, the linker's dead code +# elimination can drop object files and the exports are missing from the binary. +# This should be kept in sync with singlefilehost_unixexports.src. +if(CLR_CMAKE_TARGET_APPLE) + target_link_options(singlefilehost PRIVATE + -Wl,-u,_DotNetRuntimeInfo + -Wl,-u,_g_dacTable + -Wl,-u,_MetaDataGetDispenser + -Wl,-u,_DotNetRuntimeContractDescriptor) +elseif(NOT CLR_CMAKE_TARGET_WIN32) + target_link_options(singlefilehost PRIVATE + -Wl,-u,DotNetRuntimeInfo + -Wl,-u,g_dacTable + -Wl,-u,MetaDataGetDispenser + -Wl,-u,DotNetRuntimeContractDescriptor) +endif() + target_link_libraries( singlefilehost PRIVATE