Skip to content

Environment.ProcessPath / Environment.GetCommandLineArgs()[0] discrepancies #101837

@xoofx

Description

@xoofx

Hello there, ☺️

This issue is related to the following previous issues (and there are more closed):

Environment.ProcessPath and Environment.GetCommandLineArgs()[0] are behaving quite differently (See the follow parts for more details):

  • between them
  • between platforms
  • between regular dotnet built app and NativeAOT
  • When we access the exe through a symbolic link

This is quite problematic when trying to build apps that expect to recover the C/C++ equivalent of argv[0] which should give exactly what was typed on the command line (e.g if it is a symbolic link, we should keep its name), and not something else.

Reproducible steps

Windows - .NET 8.0

Powershell with admin rights (for the symbolic link):

dotnet new console -n HelloWorld
cd HelloWorld
Write-Output "Console.WriteLine($""Environment.ProcessPath: {Environment.ProcessPath}"");" > Program.cs
Write-Output "Console.WriteLine($""Environment.GetCommandLineArgs()[0]: {Environment.GetCommandLineArgs()[0]}"");" >> Program.cs
dotnet build -c Release
cd .\bin\Release\net8.0

Then we create a symbolic link from special_hello.exe to HelloWorld.exe:

New-Item -ItemType SymbolicLink -Path special_hello.exe -Value HelloWorld.exe
.\special_hello.exe

Outputs:

Environment.ProcessPath: C:\code\HelloWorld\bin\Release\net8.0\special_hello.exe
Environment.GetCommandLineArgs()[0]: C:\code\HelloWorld\bin\Release\net8.0\HelloWorld.dll

Windows - NativeAOT .NET 8.0

Similar steps then before, but with dotnet publish -c Release -p:PublishAot=true

cd .\bin\Release\net8.0\win-x64\publish
New-Item -ItemType SymbolicLink -Path special_hello.exe -Value HelloWorld.exe
.\special_hello.exe

Outputs:

Environment.ProcessPath: C:\code\HelloWorld\bin\Release\net8.0\win-x64\publish\special_hello.exe
Environment.GetCommandLineArgs()[0]: C:\code\HelloWorld\bin\Release\net8.0\win-x64\publish\special_hello.exe

MacOS/Ubuntu - .NET 8.0

cd ./bin/Release/net8.0
ln -s HelloWorld special_hello
./special_hello

Outputs:

Environment.ProcessPath: /home/xoofx/code/HelloWorld/bin/Release/net8.0/HelloWorld
Environment.GetCommandLineArgs()[0]: /home/xoofx/code/HelloWorld/bin/Release/net8.0/HelloWorld.dll

MacOS/Ubuntu - NativeAOT .NET 8.0

cd ./bin/Release/net8.0/linux-x64/publish
ln -s HelloWorld special_hello
./special_hello

Outputs:

Environment.ProcessPath: /home/xoofx/code/HelloWorld/bin/Release/net8.0/linux-x64/publish/HelloWorld
Environment.GetCommandLineArgs()[0]: ./special_hello

Expected behavior

Environment.GetCommandLineArgs()[0]

This value should always be the strict equivalent of argv[0] in a C/C++ program (and MacOS/Ubuntu - NativeAOT .NET 8.0 is the only one to respect it from the above)

In particular:

  • It should not transform to an absolute path or resolve symbolic link
  • It should not give a dll whether we are running through corehost from a regular dotnet build

Environment.ProcessPath

From the above it is unclear if:

  • It should give an absolute path to the exe name but not following symbolic link? (as on Windows - NativeAOT .NET 8.0) e.g C:\code\HelloWorld\bin\Release\net8.0\win-x64\publish\special_hello.exe
  • It should give an absolute path to the fully resolve exe name following symbolic link? (as on MacOS/Ubuntu) e.g /home/xoofx/code/HelloWorld/bin/Release/net8.0/HelloWorld

Implementation problems

I tried to fix it but the problem is that we are not able to recover argv[0] from coreclr_execute_assembly, and I end up hacking argv to actually have argv[0] in it and putting 1 on bit 0 on the pointer to understand behind that it is a special argv, which I conceded is super ugly 😅 (Thinking about it I could hack it more easily by using argc as a negative number to convery that it includes argv[0], but it still ugly 😁 )

int coreclr_execute_assembly(
            void* hostHandle,
            unsigned int domainId,
            int argc,
            const char** argv,
            const char* managedAssemblyPath,
            unsigned int* exitCode)

and while coreclr_initialize receive an exePath (which is not argv[0]), it is processing it to create a commandline string and things get more convoluted here (and it's not used by Environment.GetCommandLineArgs()[0])

int coreclr_initialize(
            const char* exePath,
            const char* appDomainFriendlyName,
            int propertyCount,
            const char** propertyKeys,
            const char** propertyValues,
            void** hostHandle,
            unsigned int* domainId)

I assume that it is not possible to change the signature of these functions?

I would be happy to make a PR, but some guidance regarding coreclr_execute_assembly/coreclr_initialize on how to propagate argv[0] would be appreciated. (If I can add a new arg, that should be relatively easy to fix - but I would have change also e.g CorHost2::ExecuteAssembly)

Thanks! ☺️

cc: @jkotas @MichalStrehovsky @agocke

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions