The call "Environment.GetEnvironmentVariable" affects subsequent calls

In the following code example, executed in .NET 4.5.1 or .NET 4.5.2 (same results), something strange happens when the code asks for a "non-existent" variable. The other perfectly existing variable "myvar", whose value is an empty string, ceases to be seen when called GetEnvironmentVariable

, but it is still watched through the entire environment iteration.

This behavior is probably not possible to recreate solely using the .NET APIs because they do not allow the environment variable to be set to an empty string; but the built-in APIs allow it.

It is very strange that the call Environment.GetEnvironmentVariable

will make another variable disappear or disappear from the environment.

The behavior with the target framework set in .NET 2.0 is slightly different. The inconsistency between fetching myvar

directly and iterating occurs right after the native call SetEnvironmentVariable

- no need to query another variable to see it.

Edit: The addition Charset=CharSet.Auto

, as suggested by Hans Passant (thanks!), Brings the .NET 4.5.x madness down to .NET 2.0 as described in the previous, apart from fixing Unicode handling. The DllImport

parameter is also missing SetLastError

, but this is just an artificial example and we know that this native call succeeds at the Win32 level. So there is no explanation yet. I know several ways to solve the problem, but I would like to better understand what I am seeing.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        [DllImport("Kernel32.dll")]
        public static extern int SetEnvironmentVariable(string name, string value);

        static void Main(string[] args)
        {
            ShowMyVar();

            Environment.SetEnvironmentVariable("myvar", "somevalue");
            ShowMyVar();

            Environment.SetEnvironmentVariable("myvar", String.Empty);
            ShowMyVar();

            SetEnvironmentVariable("myvar", String.Empty);
            ShowMyVar();
            // once again, for good measure.
            ShowMyVar();

            Console.WriteLine("\nOkay, sane results so far.  Now let query an unrelated non-existent variable.");

            Environment.GetEnvironmentVariable("nonexistent");
            ShowMyVar(); // Here we get weird results.

            Console.WriteLine("\nNow again, but purely through .NET APIs.");

            Environment.SetEnvironmentVariable("myvar", "somevalue");
            ShowMyVar();

            Environment.SetEnvironmentVariable("myvar", String.Empty);
            ShowMyVar();

            Environment.GetEnvironmentVariable("nonexistent");
            ShowMyVar();


        }

        private static void ShowMyVar()
        {
            if (Environment.GetEnvironmentVariable("myvar") != null)
            {
                Console.WriteLine("myvar is set to \"{0}\"", Environment.GetEnvironmentVariable("myvar"));
            }
            else
            {
                Console.WriteLine("myvar is not set");
            }
            foreach (var x in Environment.GetEnvironmentVariables().Keys)
            {
                if (x.ToString() == "myvar")
                {
                    Console.WriteLine("iteration gives value of myvar as \"{0}\"", Environment.GetEnvironmentVariable("myvar"));
                    return;
                }
            }
            Console.WriteLine("iteration over environment does not yield myvar");
        }
    }
}

      

+3


source to share


1 answer


The reason of that:

Okay, sane results so far.  Now let query an unrelated non-existent variable.

myvar is not set

      

.NET calls kernel32!GetEnvironmentVariableW ( "myvar", <pointer>, 128 )

and returns 0. GetLastError()

Set to 203 = The system could not find the environment setting that was entered.

This API is described here http://msdn.microsoft.com/en-us/library/windows/desktop/ms683188(v=vs.85).aspx

iteration gives value of myvar as ""

      

Step 2 calls GetEnvironmentStringsW()

, which returns a pointer to a block of environment strings. MyVar is present here.

This API is described here http://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx

This information was obtained using the Rohitab API monitor http://www.rohitab.com/apimonitor When using the .NET EXE API monitor, you may need to pause the start of your application, then add the API monitor later.

In this case, enable monitoring of system services -> Processes and Threads -> Process -> Kernel32.dll and unticked GetCurrentProcess ()

The .NET source code is called here http://referencesource.microsoft.com/#mscorlib/system/environment.cs

Win32Native.ERROR_ENVVAR_NOT_FOUND = 203, this causes null to be returned.

[System.Security.SecuritySafeCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public static String GetEnvironmentVariable(String variable)
        {
            if (variable == null)
                throw new ArgumentNullException("variable");
            Contract.EndContractBlock();

#if !FEATURE_CORECLR
            (new EnvironmentPermission(EnvironmentPermissionAccess.Read, variable)).Demand();
#endif //!FEATURE_CORECLR

            StringBuilder blob = new StringBuilder(128); // A somewhat reasonable default size
            int requiredSize = Win32Native.GetEnvironmentVariable(variable, blob, blob.Capacity); 

            if( requiredSize == 0) {  //  GetEnvironmentVariable failed
                if( Marshal.GetLastWin32Error() == Win32Native.ERROR_ENVVAR_NOT_FOUND)
                    return null;
            }

            while (requiredSize > blob.Capacity) { // need to retry since the environment variable might be changed 
                blob.Capacity = requiredSize;
                blob.Length = 0;
                requiredSize = Win32Native.GetEnvironmentVariable(variable, blob, blob.Capacity); 
            }
            return blob.ToString();
        }

      

In Win32 C ++, the following code works as you expect, unlike in .NET:

DWORD dwResult;
wchar_t buffer[128];

if (!SetEnvironmentVariableW(L"myvar", L""))
{
    dwResult = GetLastError();
    std::cout << "ERROR #" << dwResult << std::endl;

}

if (!GetEnvironmentVariableW(L"myvar", buffer,128))
{
    dwResult = GetLastError();
    std::cout << "ERROR #" << dwResult << std::endl;
}

std::wcout << "Buffer : '" << buffer << "'";

      

The following API calls are raised:

ConsoleTest.exe!SetEnvironmentVariableW ( "myvar", "" ) TRUE        
    KERNELBASE.dll!RtlSetEnvironmentVar ( NULL, "myvar", 5, "", 0 ) STATUS_SUCCESS      
ConsoleTest.exe!GetEnvironmentVariableW ( "myvar", <pointer 1>, 128 )   0   0 = The operation completed successfully.   
    KERNELBASE.dll!RtlQueryEnvironmentVariable ( NULL, "myvar", 5, <pointer 1>, 128, <pointer 2> )  STATUS_SUCCESS  

      

When called from .NET 4 In this case, I P / SetEnvironmentVariable and GetEnvironmentVariable are called directly one after the other:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetEnvironmentVariable(string lpName, string lpValue);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern uint GetEnvironmentVariable(string lpName, [Out] StringBuilder lpBuffer, uint nSize);

StringBuilder buffer = new StringBuilder(128);

SetEnvironmentVariable("myvar", String.Empty);
uint result=GetEnvironmentVariable("myvar", buffer, 128);
if (result==0)
{
    Console.WriteLine(string.Format("Error: {0}", Marshal.GetLastWin32Error()));
}

      



As a result of these API calls / results

 clr.dll->SetEnvironmentVariableW ( "myvar", "" )   TRUE    
       KERNELBASE.dll!RtlSetEnvironmentVar ( NULL, "myvar", 5, "", 0 )  STATUS_SUCCESS

    clr.dll->SetLastError ( ERROR_ENVVAR_NOT_FOUND )        
    clr.dll->SetLastError ( ERROR_ENVVAR_NOT_FOUND )            
    clr.dll->SetLastError ( ERROR_ENVVAR_NOT_FOUND )            
    clr.dll->SetLastError ( ERROR_ENVVAR_NOT_FOUND )            

    clr.dll!GetEnvironmentVariableW ( "myvar", <pointer 1>, 128 )   0   203 = The system could not find the environment option that was entered. 
       KERNELBASE.dll!RtlQueryEnvironmentVariable ( NULL, "myvar", 5, <pointer 1>, 128, <pointer 2> )   STATUS_SUCCESS  

      

In the .NET version, the call to KERNELBASE.dll! RtlQueryEnvironmentVariable succeeds

As far as I can see clr.dll affects the return value of GetEnvironmentVariableW.

If you run .NET exe in WinDbg and break after installing "somevalue"

Download the .NET debugging extension

0:003> .loadby sos clr 

      

Show Work Environment Block (PEB) to find the environment address

0:003> !peb
PEB at 7f40d000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            Yes
    ImageBaseAddress:         00250000
    Ldr                       77d891e0
      etc...
    Environment:  **006b0c68**
      etc..

      

Finding the location of our value in memory ...

0:003> s -u **006b0c68** **006b0c68**+1000 "somevalue"
**006b1214**  0073 006f 006d 0065 0076 0061 006c 0075  s.o.m.e.v.a.l.u.

      

Set a breakpoint for reading at this memory location ...

0:003> ba r 1 **006b1214**
0:000> du 6b1214
006b1214  "somevalue"
0:000> g
Breakpoint 1 hit
eax=00000000 ebx=006b1228 ecx=006b1214 edx=00000000 esi=00000000 edi=006b293a
eip=77ce37ad esp=003df094 ebp=003df108 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!RtlSetEnvironmentVar+0x516:
77ce37ad 83c102          add     ecx,2
0:000> du 6b1214
006b1214  ""
0:000> kv
ChildEBP RetAddr  Args to Child              
003df108 7774029e 00000000 01fe2b00 00000005 ntdll!RtlSetEnvironmentVar+0x516 (FPO: [SEH])
003df12c 005d04a3 01fe2b00 01fe1230 ed4737ca KERNELBASE!SetEnvironmentVariableW+0x47 (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
003df1f8 73cc2552 0068e698 003df258 73ccf237 0x5d04a3
003df204 73ccf237 003df29c 003df248 73e18ad2 clr!CallDescrWorkerInternal+0x34
003df258 73ccff60 00000000 00000001 003df2b8 clr!CallDescrWorkerWithHandler+0x6b (FPO: [Non-Fpo])
003df2d0 73de671c 003df3cc efea5369 004e37fc clr!MethodDescCallSite::CallTargetWorker+0x152 (FPO: [Non-Fpo])
003df3f4 73de6840 01fe2a68 00000000 efea5495 clr!RunMain+0x1aa (FPO: [Non-Fpo])
003df668 73e23dc5 00000000 efea56e5 00250000 clr!Assembly::ExecuteMainMethod+0x124 (FPO: [1,149,0])
003dfb68 73e23e68 efea5b5d 00000000 00000000 clr!SystemDomain::ExecuteMainMethod+0x63c (FPO: [0,313,0])
003dfbc0 73e23f7a efea5c9d 00000000 00000000 clr!ExecuteEXE+0x4c (FPO: [Non-Fpo])
003dfc00 73e26b86 efea5ca1 00000000 00000000 clr!_CorExeMainInternal+0xdc (FPO: [Non-Fpo])
003dfc3c 7436ffcc eff9e4d3 7573980c 74360000 clr!_CorExeMain+0x4d (FPO: [Non-Fpo])
003dfc74 743ebbb7 003dfc8c 743ebbcc 00000000 mscoreei!_CorExeMain+0x10a (FPO: [0,10,4])
003dfc7c 743ebbcc 00000000 00000000 003dfc98 MSCOREE!_CorExeMain_Exported+0x77 (FPO: [Non-Fpo])
003dfc8c 7573919f 7f40d000 003dfcdc 77cda8cb MSCOREE!_CorExeMain_Exported+0x8c (FPO: [Non-Fpo])
003dfc98 77cda8cb 7f40d000 eea2cea9 00000000 KERNEL32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
003dfcdc 77cda8a1 ffffffff 77ccf663 00000000 ntdll!__RtlUserThreadStart+0x20 (FPO: [SEH])
003dfcec 00000000 743ebb40 7f40d000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
0:000> !clrstack
OS Thread Id: 0x3464 (0)
Child SP       IP Call Site
003df140 77ce37ad [InlinedCallFrame: 003df140] 
003df13c 005d04a3 *** WARNING: Unable to verify checksum for ConsoleApplicationTest.exe
DomainBoundILStubClass.IL_STUB_PInvoke(System.String, System.String)
003df140 005d0106 [InlinedCallFrame: 003df140] ConsoleApplication1.Program.SetEnvironmentVariable(System.String, System.String)
003df1a4 005d0106 ConsoleApplication1.Program.Main(System.String[]) [c:\Users\mccafferym\Documents\Visual Studio 2013\Projects\ConsoleApplicationTest\ConsoleApplicationTest\Program.cs @ 32]
003df378 73cc2552 [GCFrame: 003df378] 
0:000> g

      

Now we hit the breakpoint when we return the value ...

Breakpoint 1 hit
eax=0000003d ebx=006b1212 ecx=006b1214 edx=01fe2b0a esi=006b1208 edi=00000001
eip=77cda2f7 esp=003def5c ebp=003def74 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!RtlpScanEnvironment+0xa7:
77cda2f7 75f7            jne     ntdll!RtlpScanEnvironment+0xa0 (77cda2f0) [br=0]
0:000> kv
ChildEBP RetAddr  Args to Child              
003def74 77cda138 01fe2b0a 003df000 00000080 ntdll!RtlpScanEnvironment+0xa7 (FPO: [Non-Fpo])
003defc8 77732b88 00000000 01fe2b00 00000005 ntdll!RtlQueryEnvironmentVariable+0xa7 (FPO: [SEH])
003defec 005d0682 01fe2b00 003df000 00000080 KERNELBASE!GetEnvironmentVariableW+0x39 (FPO: [Non-Fpo])

      

Tracking the execution of words here, we see that there is an error set here:

KERNELBASE!GetEnvironmentVariableW+0x52:
77732ba9 c20c00          ret     0Ch
006f0682 8b4d98          mov     ecx,dword ptr [ebp-68h] ss:002b:0033f040=004ee698
006f0685 c6410801        mov     byte ptr [ecx+8],1         ds:002b:004ee6a0=00
006f0689 833d64a32f7400  cmp     dword ptr [clr!g_TrapReturningThreads (742fa364)],0 ds:002b:742fa364=00000000
006f0690 7407            je      006f0699                                [br=1]
006f0699 c7458000000000  mov     dword ptr [ebp-80h],0 ss:002b:0033f028=006f0682
006f06a0 8945ac          mov     dword ptr [ebp-54h],eax ss:002b:0033f054=0033f288
006f06a3 e801545e73      call    clr!StubHelpers::SetLastError (73cd5aa9)

      

If this behavior has changed, I would recommend raising the support issue with Microsoft.

+2


source







All Articles