public static (Win32Job, List <AccountPrivilege>) AttachToProcess(uint pid, SessionSettings session) { var currentProcessHandle = Process.GetCurrentProcess().SafeHandle; var dbgpriv = AccountPrivilegeModule.EnablePrivileges(currentProcessHandle, new[] { "SeDebugPrivilege" }); try { var hProcess = CheckWin32Result(PInvoke.OpenProcess_SafeHandle( PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_SET_QUOTA | PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE | PROCESS_ACCESS_RIGHTS.PROCESS_TERMINATE, false, pid)); if (!Win32JobModule.TryOpen(hProcess, pid, session.PropagateOnChildProcesses, session.ClockTimeLimitInMilliseconds, out var job)) { job = Win32JobModule.CreateAndAssignToProcess(hProcess, pid, session.PropagateOnChildProcesses, session.ClockTimeLimitInMilliseconds); } Debug.Assert(job != null); Win32JobModule.SetLimits(job, session); var privs = AccountPrivilegeModule.EnablePrivileges(hProcess, session.Privileges); return(job, privs); } finally { AccountPrivilegeModule.RestorePrivileges(currentProcessHandle, dbgpriv); } }
public void LoadCustomEnvironmentVariablesTest() { var envVarsFile = Path.GetTempFileName(); try { using (var writer = new StreamWriter(envVarsFile, false)) { writer.WriteLine("TEST=TESTVAL"); writer.WriteLine(" TEST2 = TEST VAL2 "); } var session = new SessionSettings(); Program.LoadCustomEnvironmentVariables(session, envVarsFile); CollectionAssert.AreEqual(new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase) { { "TEST", "TESTVAL" }, { "TEST2", "TEST VAL2" } }, session.AdditionalEnvironmentVars); using (var writer = new StreamWriter(envVarsFile, false)) { writer.WriteLine(" = TEST VAL2 "); } Assert.Throws <ArgumentException>(() => { Program.LoadCustomEnvironmentVariables(session, envVarsFile); }); } finally { if (File.Exists(envVarsFile)) { File.Delete(envVarsFile); } } }
static unsafe void SetNumaAffinity(Win32Job job, SessionSettings session, ulong systemAffinityMask) { if (session.NumaNode != 0xffff) { CheckWin32Result(PInvoke.GetNumaNodeProcessorMaskEx(session.NumaNode, out var affinity)); /* * NUMA node processor mask looks a bit strange (at least to me). In my test Hyper-V environment, * I set the number of NUMA nodes to four with two cores per node. The results of running the following * loop are presented below: * * GetNumaHighestNodeNumber(out var highestNode); * * for (ulong i = 0; i <= highestNode; i++) { * Console.WriteLine($"Node: {i:X}"); * GetNumaNodeProcessorMaskEx((ushort)i, out var affinity); * $"Mask: {affinity.Mask:X}".Dump(); * } * * Results: * * Node: 0 * Mask: 3 * Node: 1 * Mask: 12 * Node: 2 * Mask: 48 * Node: 3 * Mask: 192 */ var nodeAffinityMask = Convert.ToUInt64(affinity.Mask); if (session.CpuAffinityMask != 0) { // When CPU affinity is set, we can't simply use // NUMA affinity, but rather need to apply the CPU affinity // settings to the select NUMA node. var firstNonZeroBitPosition = 0; while ((nodeAffinityMask & (1UL << firstNonZeroBitPosition)) == 0) { firstNonZeroBitPosition++; } session.CpuAffinityMask <<= firstNonZeroBitPosition & 0x3f; } else { session.CpuAffinityMask = nodeAffinityMask; } // NOTE: this could result in an overflow on 32-bit apps, but I can't // think of a nice way of handling it here affinity.Mask = new UIntPtr(session.CpuAffinityMask & systemAffinityMask); var size = (uint)Marshal.SizeOf(affinity); CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectGroupInformationEx, &affinity, size)); } }
static unsafe void SetMaxBandwith(Win32Job job, SessionSettings session) { if (session.MaxBandwidth > 0) { var limitInfo = new JOBOBJECT_NET_RATE_CONTROL_INFORMATION { ControlFlags = JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE | JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_MAX_BANDWIDTH, MaxBandwidth = session.MaxBandwidth, DscpTag = 0 }; var size = (uint)Marshal.SizeOf(limitInfo); CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectNetRateControlInformation, &limitInfo, size)); } }
public void PrepareDebuggerCommandStringTest() { var session = new SessionSettings() { CpuAffinityMask = 0x2, MaxProcessMemory = 1024 * 1024, MaxJobMemory = 2048 * 1024, ProcessUserTimeLimitInMilliseconds = 500, JobUserTimeLimitInMilliseconds = 1000, ClockTimeLimitInMilliseconds = 2000, CpuMaxRate = 90, MaxBandwidth = 100, MinWorkingSetSize = 1024, MaxWorkingSetSize = 1024 * 1024, NumaNode = 1, Privileges = new[] { "SeDebugPrivilege", "SeShutdownPrivilege" }, PropagateOnChildProcesses = true, SpawnNewConsoleWindow = true }; session.AdditionalEnvironmentVars.Add("TEST", "TESTVAL"); session.AdditionalEnvironmentVars.Add("TEST2", "TESTVAL2"); var appImageExe = Path.GetFileName(@"C:\temp\test.exe"); var debugger = Program.PrepareDebuggerCommandString(session, appImageExe, true); var envFilePath = Program.GetAppEnvironmentFilePath(appImageExe); Assert.True(File.Exists(envFilePath)); try { var txt = File.ReadAllText(envFilePath); Assert.AreEqual("TEST=TESTVAL\r\nTEST2=TESTVAL2\r\n", txt); var expectedCmdLine = $"\"{Environment.GetCommandLineArgs()[0]}\" --nogui --debugger --env=\"{envFilePath}\" --cpu=0x2 --maxmem=1048576 " + "--maxjobmem=2097152 --maxws=1048576 --minws=1024 --node=1 --cpurate=90 --bandwidth=100 --recursive " + "--timeout=2000 --process-utime=500 --job-utime=1000 --enable-privileges=SeDebugPrivilege,SeShutdownPrivilege --nowait"; Assert.AreEqual(expectedCmdLine, debugger); } finally { File.Delete(envFilePath); } }
public static void SetLimits(Win32Job job, SessionSettings session) { if (session.NumaNode != 0xffff) { CheckWin32Result(PInvoke.GetNumaHighestNodeNumber(out var highestNodeNumber)); if (session.NumaNode > highestNodeNumber) { throw new ArgumentException($"Incorrect NUMA node number. The highest accepted number is {highestNodeNumber}."); } } CheckWin32Result(PInvoke.GetProcessAffinityMask(job.ProcessHandle, out _, out var aff)); var systemAffinityMask = (ulong)aff; SetBasicLimits(job, session, systemAffinityMask); SetMaxCpuRate(job, session, systemAffinityMask); SetNumaAffinity(job, session, systemAffinityMask); SetMaxBandwith(job, session); }
private static unsafe void SetMaxCpuRate(Win32Job job, SessionSettings session, ulong systemAffinityMask) { if (session.CpuMaxRate > 0) { // Set CpuRate to a percentage times 100. For example, to let the job use 20% of the CPU, // set CpuRate to 20 times 100, or 2,000. uint finalCpuRate = session.CpuMaxRate * 100; // CPU rate is set for the whole system so includes all the logical CPUs. When // we have the CPU affinity set, we will divide the rate accordingly. if (session.CpuAffinityMask != 0) { ulong affinity = systemAffinityMask & session.CpuAffinityMask; uint numberOfSelectedCores = 0; for (int i = 0; i < sizeof(ulong) * 8; i++) { numberOfSelectedCores += (affinity & (1UL << i)) == 0 ? 0u : 1u; } Debug.Assert(numberOfSelectedCores < Environment.ProcessorCount); finalCpuRate /= ((uint)Environment.ProcessorCount / numberOfSelectedCores); } // configure CPU rate limit var limitInfo = new JOBOBJECT_CPU_RATE_CONTROL_INFORMATION { ControlFlags = JOB_OBJECT_CPU_RATE_CONTROL.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL.JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP, Anonymous = new JOBOBJECT_CPU_RATE_CONTROL_INFORMATION._Anonymous_e__Union { CpuRate = finalCpuRate } }; var size = (uint)Marshal.SizeOf(limitInfo); CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectCpuRateControlInformation, &limitInfo, size)); } }
private static unsafe void SetBasicLimits(Win32Job job, SessionSettings session, ulong systemAffinityMask) { var limitInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); var size = (uint)Marshal.SizeOf(limitInfo); var length = 0u; CheckWin32Result(PInvoke.QueryInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, &limitInfo, size, &length)); Debug.Assert(length == size); JOB_OBJECT_LIMIT flags = 0; if (!session.PropagateOnChildProcesses) { flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; } if (session.MaxProcessMemory > 0) { flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_PROCESS_MEMORY; limitInfo.ProcessMemoryLimit = (UIntPtr)session.MaxProcessMemory; } if (session.MaxJobMemory > 0) { flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_JOB_MEMORY; limitInfo.JobMemoryLimit = (UIntPtr)session.MaxJobMemory; } if (session.MaxWorkingSetSize > 0) { flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_WORKINGSET; limitInfo.BasicLimitInformation.MaximumWorkingSetSize = (UIntPtr)session.MaxWorkingSetSize; limitInfo.BasicLimitInformation.MinimumWorkingSetSize = (UIntPtr)session.MinWorkingSetSize; } if (session.ProcessUserTimeLimitInMilliseconds > 0) { flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_PROCESS_TIME; limitInfo.BasicLimitInformation.PerProcessUserTimeLimit = 10_000 * session.ProcessUserTimeLimitInMilliseconds; // in 100ns } if (session.JobUserTimeLimitInMilliseconds > 0) { flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_JOB_TIME; limitInfo.BasicLimitInformation.PerJobUserTimeLimit = 10_000 * session.JobUserTimeLimitInMilliseconds; // in 100ns } // only if NUMA node is not provided we will set the CPU affinity, // otherwise we will set the affinity on a selected NUMA node if (session.CpuAffinityMask != 0 && session.NumaNode == 0xffff) { flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_AFFINITY; // NOTE: this could result in an overflow on 32-bit apps, but I can't // think of a nice way of handling it here limitInfo.BasicLimitInformation.Affinity = new UIntPtr(systemAffinityMask & session.CpuAffinityMask); } if (flags != 0) { limitInfo.BasicLimitInformation.LimitFlags = flags | limitInfo.BasicLimitInformation.LimitFlags; CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, &limitInfo, size)); } }
public static unsafe (Win32Job, List <AccountPrivilege>) StartProcessUnderDebuggerAndDetach(IList <string> procargs, SessionSettings session) { var pi = new PROCESS_INFORMATION(); var si = new STARTUPINFOW(); var processCreationFlags = PROCESS_CREATION_FLAGS.CREATE_UNICODE_ENVIRONMENT | PROCESS_CREATION_FLAGS.DEBUG_ONLY_THIS_PROCESS; if (session.SpawnNewConsoleWindow) { processCreationFlags |= PROCESS_CREATION_FLAGS.CREATE_NEW_CONSOLE; } var args = string.Join(" ", procargs); var env = GetEnvironmentString(session.AdditionalEnvironmentVars); fixed(char *pargs = args) { fixed(char *penv = env) { CheckWin32Result(PInvoke.CreateProcess(null, pargs, null, null, false, processCreationFlags, penv, null, si, out pi)); } } var hProcess = new SafeFileHandle(pi.hProcess, true); CheckWin32Result(PInvoke.DebugSetProcessKillOnExit(false)); var job = Win32JobModule.CreateAndAssignToProcess(hProcess, pi.dwProcessId, session.PropagateOnChildProcesses, session.ClockTimeLimitInMilliseconds); Win32JobModule.SetLimits(job, session); var privs = AccountPrivilegeModule.EnablePrivileges(hProcess, session.Privileges); // resume process main thread by detaching from the debuggee CheckWin32Result(PInvoke.DebugActiveProcessStop(pi.dwProcessId)); // and we can close the thread handle PInvoke.CloseHandle(pi.hThread); return(job, privs); }
public static unsafe (Win32Job, List <AccountPrivilege>) StartProcess(IList <string> procargs, SessionSettings session) { var pi = new PROCESS_INFORMATION(); var si = new STARTUPINFOW(); var processCreationFlags = PROCESS_CREATION_FLAGS.CREATE_UNICODE_ENVIRONMENT | PROCESS_CREATION_FLAGS.CREATE_SUSPENDED; if (session.SpawnNewConsoleWindow) { processCreationFlags |= PROCESS_CREATION_FLAGS.CREATE_NEW_CONSOLE; } var args = string.Join(" ", procargs.Select((string s) => s.Contains(' ') ? "\"" + s + "\"" : s)); var env = GetEnvironmentString(session.AdditionalEnvironmentVars); fixed(char *pargs = args) { fixed(char *penv = env) { CheckWin32Result(PInvoke.CreateProcess(null, pargs, null, null, false, processCreationFlags, penv, null, si, out pi)); } } var hProcess = new SafeFileHandle(pi.hProcess, true); try { var job = Win32JobModule.CreateAndAssignToProcess(hProcess, pi.dwProcessId, session.PropagateOnChildProcesses, session.ClockTimeLimitInMilliseconds); Win32JobModule.SetLimits(job, session); var privs = AccountPrivilegeModule.EnablePrivileges(hProcess, session.Privileges); // resume process main thread CheckWin32Result(PInvoke.ResumeThread(pi.hThread)); return(job, privs); } catch { PInvoke.TerminateProcess(hProcess, 1); throw; } finally { // and we can close the thread handle PInvoke.CloseHandle(pi.hThread); } }