Unit Testing for x86 LargeAddressAware compatibility
Asked Answered
N

1

8

For a win32 executable (x86) we can set the LargeAddressAware flag so it can access a virtual address space of 4 GB (instead of just 2 GB) when running on x64 Windows.
This looks very appealing. However, there are risks involved.
For example see: Drawbacks of using /LARGEADDRESSAWARE for 32 bit Windows executables?

So let's go ahead and configure the system that is executing some unit tests with the system-wide registry switch AllocationPreference set to MEM_TOP_DOWN.
That should do, shouldn't it?

It doesn't!
The problem is that the x86 "test runner" (execution engine) of Visual Studio is not LAA enabled itself.
This parent process will only see the "lower" 2 GB of VAS, and so will our modules to be tested.

Examples from VS2013.1

  • mstest.exe spawns QTAgent32.exe
  • vstest.console.exe spawns vstest.executionengine.x86.exe

All of them are not LAA enabled!

So what is the recommended way to use a x86 test runner that is LAA enabled?


here's a little code snippet (VS unit test, csharp) to check for LAA execution environment.
unless it succeeds your test environment is not suitable to have your set of unit tests (also) cover the compatibility with LAA:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestCheckEnv32LAA
{
    [TestClass]
    public class CheckEnv32LAA
    {
        #region [Native DLL import]

        [Flags()]
        public enum AllocationType : uint
        {
            COMMIT = 0x1000,
            RESERVE = 0x2000,
            RESET = 0x80000,
            LARGE_PAGES = 0x20000000,
            PHYSICAL = 0x400000,
            TOP_DOWN = 0x100000,
            WRITE_WATCH = 0x200000
        }

        [Flags()]
        public enum MemoryProtection : uint
        {
            EXECUTE = 0x10,
            EXECUTE_READ = 0x20,
            EXECUTE_READWRITE = 0x40,
            EXECUTE_WRITECOPY = 0x80,
            NOACCESS = 0x01,
            READONLY = 0x02,
            READWRITE = 0x04,
            WRITECOPY = 0x08,
            GUARD_Modifierflag = 0x100,
            NOCACHE_Modifierflag = 0x200,
            WRITECOMBINE_Modifierflag = 0x400
        }

        [StructLayout(LayoutKind.Sequential)]
        struct MEMORYSTATUSEX
        {
            public uint dwLength;
            public uint dwMemoryLoad;
            public ulong ullTotalPhys;
            public ulong ullAvailPhys;
            public ulong ullTotalPageFile;
            public ulong ullAvailPageFile;
            public ulong ullTotalVirtual;
            public ulong ullAvailVirtual;
            public ulong ullAvailExtendedVirtual;
        }

        [DllImport("kernel32.dll")]
        extern static void GlobalMemoryStatusEx(ref MEMORYSTATUSEX status);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern UIntPtr VirtualAlloc(UIntPtr lpAddress, UIntPtr dwSize,
        AllocationType flAllocationType, MemoryProtection flProtect);

        #endregion

        public CheckEnv32LAA()
        {
        }

        [TestMethod]
        public void CheckEnvironment32LAA()
        {
            // check for a suitable environment to test modules for compatibility with LargeAddressAware (LAA):
            // 1) OS must be x64
            // 2) test runner must be x86
            // 3) test runner must be LAA enabled itself
            // 4) memory allocation (with manual TopDown flag) must happen beyond the 2 GB boundary
            // 5) memory allocation (with default settings) must happen beyond the 2 GB boundary
            //
            // RE 3) this requirement is true for "regular" unit tests (to test DLL modules). it does not apply
            // for any tests spawning the application (EXE) to be tested as a separate process.
            // 
            // RE 5) a failure indicates the following registry switch has not been set:
            // [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management]
            // "AllocationPreference"=dword:00100000
            //
            // see:
            // https://stackoverflow.com/questions/2288728/

            String sParentProcName = Process.GetCurrentProcess().MainModule.FileName;

            //CHECK_1
            Assert.IsTrue(Environment.Is64BitOperatingSystem, "Test is not executing on x64 OS");

            //CHECK_2
            Assert.IsFalse(Environment.Is64BitProcess, "Test runner is not x86: " + sParentProcName);

            //CHECK_3
            MEMORYSTATUSEX tmpStatus = new MEMORYSTATUSEX();
            tmpStatus.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
            tmpStatus.ullTotalPhys = 0;
            GlobalMemoryStatusEx(ref tmpStatus);
            ulong uVM = tmpStatus.ullTotalVirtual;
            Assert.IsTrue(uVM > 0x80000000, "Test runner is not LAA enabled (max: " + uVM / (1024 * 1024) + "): " + sParentProcName);
            Assert.IsTrue(uVM <= 0x100000000, "Test runner is not x86 (max: " + uVM / (1024 * 1024) + "): " + sParentProcName);

            //CHECK_4
            UIntPtr pMem = UIntPtr.Zero;
            ulong uAddress = 0;
            pMem = VirtualAlloc(UIntPtr.Zero, (UIntPtr)1024, AllocationType.RESERVE | AllocationType.TOP_DOWN, MemoryProtection.READWRITE);
            uAddress = (ulong)pMem;
            Assert.IsTrue(uAddress > 0x80000000, "Test runner is not LAA enabled (highest: " + uAddress / (1024 * 1024) + "): " + sParentProcName);

            //CHECK_5
            pMem = VirtualAlloc(UIntPtr.Zero, (UIntPtr)1024, AllocationType.RESERVE, MemoryProtection.READWRITE);
            uAddress = (ulong)pMem;
            Assert.IsTrue(uAddress > 0x80000000, "System-wide MEM_TOP_DOWN is not set (allocated at: " + uAddress / (1024 * 1024) + ")");
        }
    }
}
Norvol answered 16/4, 2014 at 15:40 Comment(2)
Please note that the 4 GB address space is only available on 64 bit systems. On 32 bit systems large address aware applications can only address 3 GB.Technicality
Patching the EXE with editbin.exe is the only pragmatic solution.Simmons
N
4

So far I have only come across suggestions to mess with the Microsoft binaries listed in the question (i.e. use editbin.exe to manually "patch" them). But this has the following disadvantages:

  • I need to repeat the patching once any future service pack is installed for Visual Studio
  • I can no longer test in parallel: "regular" x86 and "extended" x86 with LAA

It seems a proper long-term solution would have to be implemented by microsoft?:
http://visualstudio.uservoice.com/forums/196039-microsoft-test-tools/suggestions/5781437

Norvol answered 8/5, 2014 at 21:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.