Describe the bug
Running dotnet test inside an aarch64 Debian rootfs under proot (Android Termux) fails with:
找不到“ARM64”体系结构的“dotnet”主机。
(Could not find a 'dotnet' host for the 'ARM64' architecture.)
uname -m = aarch64, RID linux-arm64. Reproduced with .NET SDK 8.0.424 and 10.0.400.
Root cause
In DotnetHostHelper.TryGetDotnetPathByArchitecture (src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs) the fast path only applies when the current process is the dotnet muxer:
if (_processHelper.GetCurrentProcessArchitecture() == targetArchitecture)
{
string currentProcessFileName = _processHelper.GetCurrentProcessFileName()!;
if (Path.GetFileName(currentProcessFileName) == _muxerName)
{
muxerPath = currentProcessFileName;
...
return true;
}
}
On Unix GetCurrentProcessFileName() comes from Process.MainModule.FileName, which .NET derives from the first mapping in /proc/self/maps. Under proot an empty placeholder loader file is injected at the lowest address of every process, so MainModule.FileName returns
/data/data/com.termux/files/usr/libexec/proot/loader
even though the real executable is /usr/share/dotnet/dotnet (probe from inside the same process):
Environment.ProcessPath = /usr/share/dotnet/dotnet
/proc/self/exe = /usr/share/dotnet/dotnet
Process.MainModule.FileName = /data/data/com.termux/files/usr/libexec/proot/loader
maps[0] = .../usr/libexec/proot/loader (injected placeholder)
Because the basename is not dotnet, resolution falls through to the search branch (DOTNET_ROOT_ARM64 / DOTNET_ROOT, global registration, default installation location). Every candidate is then rejected because IsValidArchitectureMuxer only inspects PE headers on Windows and Mach-O on macOS — on Linux it never determines an architecture (actual ''; see #4042, closed as By Design). Even a valid, explicitly configured muxer is refused:
DotnetHostHelper.IsValidArchitectureMuxer: Incompatible architecture muxer, target architecture 'ARM64', actual ''
DotnetHostHelper: Invalid muxer resolved using env var key 'DOTNET_ROOT' in '/usr/share/dotnet'
#4042 was closed as By Design because on regular Linux the current-process fast path always succeeds. This environment breaks that assumption: the process is the muxer, but the /proc/self/maps-based image-name check reports a different name, so vstest lands in a search branch that can never validate a muxer on Linux.
Expected behavior / suggested fixes
- Do not rely on the first
/proc/self/maps mapping to decide "current process is the muxer" — prefer Environment.ProcessPath / /proc/self/exe for GetCurrentProcessFileName(). (These return the correct /usr/share/dotnet/dotnet in the same environment.)
- Implement ELF
e_machine inspection in IsValidArchitectureMuxer on Linux, mirroring the PE (Windows) and Mach-O (macOS) paths, so the search branch can actually validate real muxers.
- At minimum, when an arch-specific
DOTNET_ROOT_ARM64 (or DOTNET_ROOT) explicitly points to an existing muxer, honour it instead of failing the check on a platform where the validator always reports "incompatible".
Steps to reproduce
- On an aarch64 Linux rootfs running inside Termux proot, install the .NET SDK (
linux-arm64).
- Create a minimal xUnit project and run
dotnet test -c Release.
- Observe the failure and the trace above with
--diag.
Environment: OS Debian 13 (trixie) aarch64 inside proot; runtime RID linux-arm64; .NET SDK 8.0.424 and 10.0.400.
Workaround
Run tests with the xunit.runner.console runner directly (bypasses vstest) — this confirms the failure is isolated to vstest's dotnet-host resolution.
Related: #4042, #4194, #4349, #1749
Describe the bug
Running
dotnet testinside an aarch64 Debian rootfs under proot (Android Termux) fails with:uname -m=aarch64, RIDlinux-arm64. Reproduced with .NET SDK 8.0.424 and 10.0.400.Root cause
In
DotnetHostHelper.TryGetDotnetPathByArchitecture(src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs) the fast path only applies when the current process is the dotnet muxer:On Unix
GetCurrentProcessFileName()comes fromProcess.MainModule.FileName, which .NET derives from the first mapping in/proc/self/maps. Under proot an empty placeholderloaderfile is injected at the lowest address of every process, soMainModule.FileNamereturns/data/data/com.termux/files/usr/libexec/proot/loadereven though the real executable is
/usr/share/dotnet/dotnet(probe from inside the same process):Because the basename is not
dotnet, resolution falls through to the search branch (DOTNET_ROOT_ARM64/DOTNET_ROOT, global registration, default installation location). Every candidate is then rejected becauseIsValidArchitectureMuxeronly inspects PE headers on Windows and Mach-O on macOS — on Linux it never determines an architecture (actual ''; see #4042, closed as By Design). Even a valid, explicitly configured muxer is refused:#4042 was closed as By Design because on regular Linux the current-process fast path always succeeds. This environment breaks that assumption: the process is the muxer, but the
/proc/self/maps-based image-name check reports a different name, so vstest lands in a search branch that can never validate a muxer on Linux.Expected behavior / suggested fixes
/proc/self/mapsmapping to decide "current process is the muxer" — preferEnvironment.ProcessPath//proc/self/exeforGetCurrentProcessFileName(). (These return the correct/usr/share/dotnet/dotnetin the same environment.)e_machineinspection inIsValidArchitectureMuxeron Linux, mirroring the PE (Windows) and Mach-O (macOS) paths, so the search branch can actually validate real muxers.DOTNET_ROOT_ARM64(orDOTNET_ROOT) explicitly points to an existing muxer, honour it instead of failing the check on a platform where the validator always reports "incompatible".Steps to reproduce
linux-arm64).dotnet test -c Release.--diag.Environment: OS Debian 13 (trixie) aarch64 inside proot; runtime RID
linux-arm64; .NET SDK 8.0.424 and 10.0.400.Workaround
Run tests with the
xunit.runner.consolerunner directly (bypasses vstest) — this confirms the failure is isolated to vstest's dotnet-host resolution.Related: #4042, #4194, #4349, #1749