//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: Process.cs // // Note: // // #define TEST_CREATE_PROCESS_TIME // #define VERBOSE using System; using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Singularity; using Microsoft.Singularity.Io; using Microsoft.Singularity.Xml; using Microsoft.Singularity.Loader; using Microsoft.Singularity.Memory; using Microsoft.Singularity.Directory; using Microsoft.Singularity.Scheduling; using Microsoft.Singularity.V1.Threads; using Microsoft.Singularity.V1.Services; using Microsoft.Singularity.Security; namespace Microsoft.Singularity { [CLSCompliant(false)] public enum ProcessEvent : ushort { CreateKernelProcess = 1, CreateUserProcess = 2, ImageLoad = 3, } [NoCCtor] [CLSCompliant(false)] public class Process { // Per-Class Members: internal static HandleTable kernelHandles; public static bool systemSilentLoad; public static Process kernelProcess; public static Process idleProcess; public static Process[] processTable; private static SpinLock processTableLock; private static int processIndexGenerator; internal const int maxProcesses = 1024; // Must be power of 2 >= 64 private static bool useSeparateAddressSpaces; private static ProcessGroup [] processGroups; // Xml processing constants internal const string PolicyXmlTag = "policy"; internal const string ProcessSetXmlTag = "processSet"; internal const string SeparateAddressSpaceXmlAttribute = "separateAddressSpace"; internal const string NameXmlAttribute = "name"; internal const string SetXmlAttribute = "set"; internal const string KernelDomainXmlAttribute = "kernelDomain"; public enum ParameterCode { Success, OutOfRange, NotSet, Retrieved, AlreadySet, Undefined, } // Arrays used set/get process arguments private class BoolArg { public bool Value; public bool Set; public bool Retrieved; public BoolArg (bool value) { this.Value = value; this.Set = false; this.Retrieved = false; } } private class LongArg { public long Value; public bool Set; public bool Retrieved; public LongArg (long value) { this.Value = value; this.Set = false; this.Retrieved = false; } } private class StringArg { public string Value; public bool Set; public bool Retrieved; public StringArg (string value) { this.Value = value; this.Set = false; this.Retrieved = false; } } private class StringArrayArg { public string[] Value; public bool Set; public bool Retrieved; public StringArrayArg (string[] value) { this.Value = value; this.Set = false; this.Retrieved = false; } } internal class ProcessGroup { public string GroupName; public string [] ProcessSet; public bool KernelDomain; public ProcessGroup(string name, string[] procs, bool kernelDomain) { this.GroupName = name; this.ProcessSet = procs; this.KernelDomain = kernelDomain; } } internal static string [] Tokenize(string str) { int start = 0; int end = str.Length; int count = 0; int at = 0; ArrayList tokens = new ArrayList(); string[] retVal = null; if (str == null) return null; while((start <= end) && (at > -1)) { count = end - start; at = str.IndexOf(' ', start, count); if (at == -1) { tokens.Add(str.Substring(start)); break; } string s = str.Substring(start, at-start); tokens.Add(s); start = at+1; } retVal = new string[tokens.Count]; for (int i=0; i< tokens.Count; i++){ retVal[i] = (string) tokens[i]; } return retVal; } internal static void ProcessConfig (XmlNode config) { if (config == null) { return; } // process address space policy XmlNode policy = config.GetChild(PolicyXmlTag); if (policy != null) { useSeparateAddressSpaces = policy.GetAttribute(SeparateAddressSpaceXmlAttribute, false); } int groupNodes = config.CountNamedChildren(ProcessSetXmlTag); if (groupNodes != 0) { // allocate memory for groups processGroups = new ProcessGroup[groupNodes]; int idx = 0; foreach (XmlNode group in config.Children) { if (group.Name != ProcessSetXmlTag) { continue; } string groupName = group.GetAttribute(NameXmlAttribute, ""); string appSet = group.GetAttribute(SetXmlAttribute, ""); // break up appset into array of names string[] procs = Tokenize(appSet); bool kernelDomain = group.GetAttribute(KernelDomainXmlAttribute,false); processGroups[idx] = new ProcessGroup(groupName, procs, kernelDomain); idx++; } } } internal static void VisitSpecialData(System.GCs.NonNullReferenceVisitor visitor) { visitor.VisitReferenceFields(Process.processTable); for (int i = 0; i < processTable.Length; i++) { if (processTable[i] != null) { processTable[i].handles.VisitSpecialData(visitor); } } } internal static int AllocateProcessTableSlot(Process process) { Thread currentThread = Thread.CurrentThread; Kernel.Waypoint(621); bool disabled = Processor.DisableInterrupts(); processTableLock.Acquire(currentThread); int foundIndex = -1; try { for (int i = 0; i < processTable.Length; i++) { int index = (processIndexGenerator + i) % processTable.Length; if (processTable[index] == null) { Kernel.Waypoint(622); foundIndex = index; processTable[foundIndex] = process; processIndexGenerator = (index + 1) % processTable.Length; break; } } } finally { processTableLock.Release(currentThread); Processor.RestoreInterrupts(disabled); } Kernel.Waypoint(623); DebugStub.Assert(foundIndex >= 0, "Out of thread slots!"); return foundIndex; } internal static void Initialize(XmlNode config) { // Create pre-existent processes. kernelProcess = new Process(1); idleProcess = new Process(2); // Initialize process management table. processTable = new Process[maxProcesses]; processTable[0] = kernelProcess; // ensure slot zero is never used processTable[1] = kernelProcess; // 1 is the kernel process. processTable[2] = idleProcess; // 2 is the idle process. processIndexGenerator = 3; // Create table told hold kernel handles. kernelHandles = new HandleTable(null); Thread.initialThread.process = kernelProcess; //policy config data ProcessConfig(config); } internal static void InitializeSharedHeapWalker() { // TODO The walker needs to become per-protection-domain // on paging systems SharedHeapWalker.Initialize( SharedHeap.KernelSharedHeap.DataOwnerId, SharedHeap.KernelSharedHeap.EndpointOwnerId); } // The next 2 functions are used by the loader to determine // what address space SIPs should be created in public static bool RunInSeparateSpace() { return useSeparateAddressSpaces; } public static string GetGroupFromName(string name, out bool isKernelDomain) { isKernelDomain = false; if (processGroups == null) return null; for (int i=0; i < processGroups.Length; i++) { for (int j=0; j < processGroups[i].ProcessSet.Length; j++) { if (processGroups[i].ProcessSet[j] == name) { isKernelDomain = processGroups[i].KernelDomain; return processGroups[i].GroupName; } } } return null; } [NoHeapAllocation] public static Process GetProcessByID(int procID) { return processTable[procID]; } // Per-Instance Members: private ProcessState state; // acquire processMutex before modifying private Mutex suspendMutex; // lock ordering: if you acquire both suspendMutex and processMutex, acquire suspendMutex first private Mutex processMutex; // lock ordering: acquire child lock before acquiring parent lock private int processIndex; private uint processTag; private Process parent; private IoConfig ioconfig; private Process[] children; private Thread[] threads; private int startedChildCount; private int startedThreadCount; private HandleTable handles; private ManualResetEvent joinEvent; private int exitCode; private bool silentLoad; private UIntPtr pagesNow; private UIntPtr pagesMax; private PEImage image; private IoMemory loadedImage; private ProcessStart entry; private String imageName; private String[] args; private long deadThreadCount; private TimeSpan deadThreadExecutionTime; private int gcCount; private TimeSpan gcTotalTime; private long gcTotalBytes; private unsafe SharedHeap.Allocation * [] endpointSet; private BoolArg[] boolArgSet; private StringArg[] stringArgSet; private LongArg[] longArgSet; private StringArrayArg[] stringArrayArgSet; #if PAGING private ProtectionDomain protectionDomain; #endif // The principal of the process private Principal principal; private Process(int index) { this.state = ProcessState.Unstarted; this.suspendMutex = new Mutex(); this.processMutex = new Mutex(); this.processIndex = index; this.processTag = ((uint)processIndex) << 16; this.children = new Process[16]; this.threads = new Thread[16]; this.handles = new HandleTable(this); this.parent = null; this.joinEvent = new ManualResetEvent(false); this.imageName = index == 1 ? "kernel" : "idle"; this.deadThreadCount = 0; this.deadThreadExecutionTime = new TimeSpan(0); this.gcCount = 0; this.gcTotalTime = TimeSpan.Zero; this.gcTotalBytes = 0; //DebugStub.WriteLine("new process(index): clearing StringArray"); this.stringArrayArgSet = null; #if PAGING this.protectionDomain = ProtectionDomain.DefaultDomain; protectionDomain.AddRef(); DebugStub.WriteLine("Loaded process \"{0}\" into {1}protection domain \"{2}\"", __arglist(this.imageName, this.protectionDomain.KernelMode ? "kernel " : "", this.protectionDomain.Name)); #endif // This constructor is currently used only to create the // process for the kernel. If this changes, the code below should // change as well. In general, use the other constructor, as it // provides sufficient information to create the process principal. this.principal = PrincipalImpl.Self(); Monitoring.Log(Monitoring.Provider.Process, (ushort)ProcessEvent.CreateKernelProcess, GetProcessName()); } private Process(Process parent) { Kernel.Waypoint(620); this.processIndex = AllocateProcessTableSlot(this); Kernel.Waypoint(625); this.state = ProcessState.Unstarted; this.suspendMutex = new Mutex(); this.processMutex = new Mutex(); this.processTag = ((uint)processIndex) << 16; this.children = new Process[16]; this.threads = new Thread[16]; this.handles = new HandleTable(this); this.parent = parent; this.imageName = parent.imageName; this.ioconfig = parent.ioconfig; // replicate IoConfig to children. Kernel.Waypoint(626); this.joinEvent = new ManualResetEvent(false); //DebugStub.WriteLine("clearing StringArray"); this.stringArrayArgSet = null; Monitoring.Log(Monitoring.Provider.Process, (ushort)ProcessEvent.CreateUserProcess, 0, (uint)this.ProcessId, (uint)parent.ProcessId, 0, 0, 0); } public Process(Process parent, IoMemory rawImage, String role, String[] args, Manifest appManifest) : this(parent) { this.imageName = appManifest.Name; this.args = args; #if PAGING if (RunInSeparateSpace()) { bool kernelMode; string domainName = GetGroupFromName(this.imageName, out kernelMode); if (domainName != null) { this.protectionDomain = ProtectionDomain.FindOrCreateByName(domainName, kernelMode); } } if (this.protectionDomain == null) { // Just inherit from our parent this.protectionDomain = parent.protectionDomain; } protectionDomain.AddRef(); try { // Temporarily switch to the domain of the process // we are creating Thread.SwitchToDomain(this.protectionDomain); this.protectionDomain.InitHook(); // we might be first #endif Kernel.Waypoint(510); // haryadi -- this is a normal load (for BSP), so // set the 4th argument (isForMp) to false this.image = PEImage.Load(this, rawImage, out loadedImage, false); Monitoring.Log(Monitoring.Provider.Process, (ushort)ProcessEvent.ImageLoad, imageName); Kernel.Waypoint(511); // Trap-door to turn off debugger notification for micro-benchmark. if (args.Length > 0) { if (args[args.Length-1] == "!") { silentLoad = true; } else if (args[args.Length-1] == "!+") { DebugStub.WriteLine(); DebugStub.WriteLine("*** Disabled debugger load notifications. Type \".reload\" to retrieve full symbols."); DebugStub.WriteLine(); systemSilentLoad = true; } else if (args[args.Length-1] == "!-") { DebugStub.WriteLine(); DebugStub.WriteLine("*** Enabled debugger load notifications."); DebugStub.WriteLine(); systemSilentLoad = false; } } if (!silentLoad && systemSilentLoad) { silentLoad = true; } #if DEBUG DebugStub.WriteLine( "Loading {0} [{1:x}...{2:x} bytes]", __arglist(imageName, loadedImage.VirtualAddress, loadedImage.VirtualAddress + loadedImage.Length)); #endif #if PAGING DebugStub.WriteLine( "Loaded process \"{0}\" into {1}protection domain \"{2}\"", __arglist(this.imageName, this.protectionDomain.KernelMode ? "kernel " : "", this.protectionDomain.Name)); #endif DebugStub.LoadedBinary(loadedImage.VirtualAddress, (UIntPtr) loadedImage.Length, imageName, image.checkSum, image.timeDateStamp, silentLoad); Kernel.Waypoint(512); threads[0] = Thread.CreateThread(this, new ThreadStart(this.PeStart)); Kernel.Waypoint(513); #if PAGING } finally { Thread.RevertToParentDomain(); } #endif // create the principal of the process this.principal = PrincipalImpl.NewInvocation(parent.Principal, appManifest, role, null); Tracing.Log(Tracing.Audit, "Created PE process at {0:x8}", loadedImage.VirtualAddress); } public static Process KernelProcess { [NoHeapAllocation] get { return kernelProcess; } } public int ProcessId { [NoHeapAllocation] get { return processIndex; } } public Principal Principal { [NoHeapAllocation] get { return this.principal; } } public uint ProcessTag { [NoHeapAllocation] get { return processTag; } } public IoConfig IoConfig { [NoHeapAllocation] get { return ioconfig; } [NoHeapAllocation] set { ioconfig = value; } } public SharedHeap ProcessSharedHeap { [Inline] #if PAGING get { return protectionDomain.UserSharedHeap; } #else // Everyone uses the same heap get { return SharedHeap.KernelSharedHeap; } #endif } #if PAGING public ProtectionDomain Domain { get { return protectionDomain; } } public bool RunsAtKernelPrivilege { get { return protectionDomain.KernelMode; } } #else public bool RunsAtKernelPrivilege { get { // All processes run at ring-0 on // the non-PAGING build return true; } } #endif // Routines to maintain child process list. // // Called when a child process starts (not called when // a child Process object is allocated). private void AddChild(Process child) { Kernel.Waypoint(514); processMutex.AcquireMutex(); try { for (int i = 0; i < children.Length; i++) { if (children[i] == null) { children[i] = child; startedChildCount++; return; } } // Grow array if necessary Process[] nc = new Process[children.Length * 2]; for (int i = 0; i < children.Length; i++) { nc[i] = children[i]; } nc[children.Length] = child; children = nc; startedChildCount++; } finally { processMutex.ReleaseMutex(); } } // Should only be called from the kernel service thread. private bool ServiceRemoveChild(Process child) { Kernel.Waypoint(515); processMutex.AcquireMutex(); try { for (int i = 0; i < children.Length; i++) { if (children[i] == child) { children[i] = null; startedChildCount--; return true; } } } finally { processMutex.ReleaseMutex(); } return false; } // Routines to maintain child thread list. // // Called when a thread is created (not when a thread // is started) internal void AddThread(Thread thread, out ThreadHandle handle) { Kernel.Waypoint(516); processMutex.AcquireMutex(); try { handle = new ThreadHandle(AllocateHandle(thread)); thread.threadHandle = handle; for (int i = 0; i < threads.Length; i++) { if (threads[i] == null) { threads[i] = thread; return; } } // Grow array if necessary Thread[] nc = new Thread[threads.Length * 2]; for (int i = 0; i < threads.Length; i++) { nc[i] = threads[i]; } nc[threads.Length] = thread; threads = nc; } finally { processMutex.ReleaseMutex(); } } // Should be called only by the service thread private bool ServiceRemoveThread(Thread thread) { Kernel.Waypoint(517); // An unstarted thread cannot exit (and should not appear in // startedThreadCount): VTable.Assert(thread.ThreadState != System.Threading.ThreadState.Unstarted); processMutex.AcquireMutex(); try { for (int i = 0; i < threads.Length; i++) { if (threads[i] == thread) { threads[i] = null; startedThreadCount--; return true; } } } finally { processMutex.ReleaseMutex(); } return false; } public bool CreateThread(ThreadStart start) { Thread thread = Thread.CreateThread(this, new ThreadStart(start)); if (thread == null) { Tracing.Log(Tracing.Audit, "Failed CreateThread request too late."); return false; } unchecked { Tracing.Log(Tracing.Audit, "Created new PE thread {1:x3}.", (UIntPtr)unchecked((uint)thread.threadIndex)); } return true; } public unsafe bool CreateThread(int threadIndex, out ThreadHandle handle, out UIntPtr threadContext) { Thread thread = Thread.CreateThread(this, new ThreadStart(this.PeStartThread)); if (thread != null) { thread.processThreadIndex = threadIndex; handle = thread.threadHandle; fixed (void *contextAddr = &thread.context) { threadContext = (UIntPtr) contextAddr; } } else { handle = new ThreadHandle(); threadContext = 0; Tracing.Log(Tracing.Audit, "Failed CreateThread lid={0:x3}.", (UIntPtr)unchecked((uint)threadIndex)); return false; } unchecked { Tracing.Log(Tracing.Audit, "Created new PE thread tid={0:x3}, lid={1:x3}.", (UIntPtr)unchecked((uint)thread.threadIndex), (UIntPtr)unchecked((uint)threadIndex)); } return true; } // Should only be called from the kernel service thread. private void ServiceRelease() { Kernel.Waypoint(521); processMutex.AcquireMutex(); try { if (state == ProcessState.Stopped) { return; } state = ProcessState.Stopped; } finally { processMutex.ReleaseMutex(); } bool found = parent.ServiceRemoveChild(this); VTable.Assert(found); VTable.Assert(processIndex >= 0); Kernel.Waypoint(522); UIntPtr imageBytes = UIntPtr.Zero; UIntPtr handleBytes; UIntPtr pageBytes; // Must switch to the target process' address space // to free memory properly #if PAGING try { Thread.SwitchToDomain(this.protectionDomain); #endif if (loadedImage != null) { imageBytes = (UIntPtr)loadedImage.Length; Tracing.Log(Tracing.Debug, "UnloadingBinary at {0:x8}", loadedImage.VirtualAddress); Kernel.Waypoint(523); DebugStub.UnloadedBinary(loadedImage.VirtualAddress, silentLoad); Kernel.Waypoint(524); IoMemory.Release(this, loadedImage); loadedImage = null; image = null; entry = null; } Kernel.Waypoint(525); handleBytes = handles.FreeAllPages(); Kernel.Waypoint(526); pageBytes = Memory.MemoryManager.FreeProcessMemory(this); Kernel.Waypoint(527); #if PAGING } finally { Thread.RevertToParentDomain(); } #endif Tracing.Log(Tracing.Audit, "Memory: [image={0:x}, pages={1:x}, max={2:x}, handles={3:x}] total = {4} bytes", imageBytes, pageBytes, pagesMax, handleBytes, imageBytes + pageBytes + handleBytes); // This must follow FreeAll, which relies on the processIndex // (or rather, on the processTag). PrincipalImpl.Dispose(principal); processTable[processIndex] = null; processIndex = -1; joinEvent.Set(); SharedHeapWalker.walker.RequestWalk(); parent.ServiceCheckForExit(); #if PAGING // Release our enclosing protection domain protectionDomain.Release(); #endif #if false DebugStub.WriteLine( "Memory: [image={0:x8}, pages={1:x8}, maxpages={2:x8}, " + "handles={3:x8}] total={4:x8} bytes\n", __arglist( imageBytes, pageBytes, pagesMax, handleBytes, (long)(imageBytes + pageBytes + handleBytes))); #endif } internal unsafe bool CheckEndpointsSet () { if (endpointSet == null) return true; for (int i=0; i < endpointSet.Length; i++){ if (endpointSet[i] == null){ DebugStub.WriteLine("Process.Start: Endpoint ({0}) is not set. Process will not start", __arglist(i)); return false; } } return true; } // Start the process. public bool Start() { // If we're to be suspended, wait until after resumption to start // other processes. SuspendBarrierCheckParents(); processMutex.AcquireMutex(); try { if (state != ProcessState.Unstarted) { DebugStub.WriteLine(" process not in unstarted state!\n"); return false; } if (!CheckEndpointsSet() ) { return false; } parent.AddChild(this); state = ProcessState.Running; threads[0].SetMainThreadRunning(); } finally { processMutex.ReleaseMutex(); } Kernel.Waypoint(528); threads[0].StartRunningThread(); Kernel.Waypoint(529); return true; } internal void StartThread(ref System.Threading.ThreadState threadState) { // If we're being suspended, wait until after resumption to start. SuspendBarrier(); processMutex.AcquireMutex(); try { if (threadState != System.Threading.ThreadState.Unstarted) { throw new ThreadStateException("Cannot start thread in state " + threadState); } threadState = System.Threading.ThreadState.Running; startedThreadCount++; } finally { processMutex.ReleaseMutex(); } } // precondition: processMutex held internal void StartMainThread(ref System.Threading.ThreadState threadState) { VTable.Assert(threadState == System.Threading.ThreadState.Unstarted); VTable.Assert(state == ProcessState.Running); VTable.Assert(startedThreadCount == 0); threadState = System.Threading.ThreadState.Running; startedThreadCount++; } // started==true indicates a started process; false // indicates an unstarted process. public void Join(out bool started) { Join(SchedulerTime.MaxValue, out started); } public void Join() { bool started; Join(out started); if (!started) { throw new ProcessStateException("joining unstarted process"); } } // started==true indicates a started process; false // indicates an unstarted process. // Returns false if the join timed out, true otherwise. public bool Join(TimeSpan timeout, out bool started) { return Join(SchedulerTime.Now + timeout, out started); } // started==true indicates a started process; false // indicates an unstarted process. // Returns false if the join timed out, true otherwise. public bool Join(SchedulerTime timeOut, out bool started) { if (state == ProcessState.Unstarted) { started = false; return false; } started = true; return joinEvent.WaitOne(timeOut); } // Freeze all the threads in a process. If recursive==true, then // freeze all threads in all descendant processes as well. This // method blocks until all freezing is complete. Before a thread // in kernel mode is frozen, it is allowed to run until it reaches // a blocking operation, until it exits, or until it enters process // mode. Semantically, suspended thread execution should be // equivalent to running thread execution, except that suspended // threads appear to receive time slices of length 0. // Returns true if successful; returns false to indicate an // unstarted process. public bool Suspend(bool recursive) { // Ask the kernel service thread to call ServiceSuspend. return ThreadLocalServiceRequest.SuspendProcess(this, recursive); } // Should only execute in the kernel service thread internal bool ServiceSuspend(bool recursive) { return ServiceSuspend(recursive, false); } // Should only execute in the kernel service thread private bool ServiceSuspend(bool recursive, bool aboutToStop) { bool alreadySuspended = false; processMutex.AcquireMutex(); try { // The service thread should not already be in the middle of an operation: Debug.Assert(state != ProcessState.Suspending, "unexpected process state"); Debug.Assert(state != ProcessState.SuspendingRecursive, "unexpected process state"); Debug.Assert(state != ProcessState.Stopping, "unexpected process state"); if (state == ProcessState.Running) { state = (recursive)?(ProcessState.SuspendingRecursive):(ProcessState.Suspending); } else if (!aboutToStop && state == ProcessState.Unstarted) { return false; } else { alreadySuspended = true; } } finally { processMutex.ReleaseMutex(); } if (!alreadySuspended) { // Hold suspendMutex until suspension is complete, so that other // threads in this process block when acquiring suspendMutex. // Only try to acquire suspendMutex if the process isn't already // suspended, though -- otherwise, one of the suspended threads // might be holding suspendMutex. suspendMutex.AcquireMutex(); } if (recursive) { // Note: now that this.state==Suspending[Recursive], a barrier // prevents new child processes, so we can safely traverse the // existing children without missing any. for (int index = 0;; index++) { Process child = null; processMutex.AcquireMutex(); try { if (index >= children.Length) { break; } else if (children[index] == null) { continue; } child = children[index]; } finally { processMutex.ReleaseMutex(); } if (child != null) { child.ServiceSuspend(true, aboutToStop); } } } if (alreadySuspended) { // This process is already suspended, the children are // suspended (if recursive==true), so we're done. return true; } bool yieldAndRepeat; do { yieldAndRepeat = false; // Note: now that this.state==Suspending[Recursive], a barrier // prevents starting threads, so we can safely traverse the // existing threads without missing any started threads. for (int index = 0;; index++) { Thread thread = null; processMutex.AcquireMutex(); try { if (index >= threads.Length) { break; } else if (threads[index] == null) { continue; } thread = threads[index]; } finally { processMutex.ReleaseMutex(); } if (thread != null) { bool suspended = thread.Suspend(aboutToStop); if (!suspended) yieldAndRepeat = true; } } if (yieldAndRepeat) { Thread.Yield(); } } while (yieldAndRepeat); processMutex.AcquireMutex(); state = ProcessState.Suspended; processMutex.ReleaseMutex(); suspendMutex.ReleaseMutex(); return true; } // Returns true if successful; returns false to indicate an // unstarted process. public bool Resume(bool recursive) { // Ask the kernel service thread to call ServiceResume. return ThreadLocalServiceRequest.ResumeProcess(this, recursive); } // Should only execute in the kernel service thread internal bool ServiceResume(bool recursive) { bool alreadyResumed = false; processMutex.AcquireMutex(); try { // The service thread should not already be in the middle of an operation: Debug.Assert(state != ProcessState.Suspending, "unexpected process state"); Debug.Assert(state != ProcessState.SuspendingRecursive, "unexpected process state"); Debug.Assert(state != ProcessState.Stopping, "unexpected process state"); if (state == ProcessState.Suspended) { state = ProcessState.Running; } else if (state == ProcessState.Unstarted) { return false; } else { alreadyResumed = true; } } finally { processMutex.ReleaseMutex(); } if (recursive) { for (int index = 0;; index++) { Process child = null; processMutex.AcquireMutex(); try { if (index >= children.Length) { break; } else if (children[index] == null) { continue; } child = children[index]; } finally { processMutex.ReleaseMutex(); } if (child != null) { child.ServiceResume(true); } } } if (alreadyResumed) { // This process is already resumed, the children are // resumed (if recursive==true), so we're done. return true; } for (int index = 0;; index++) { Thread thread = null; processMutex.AcquireMutex(); try { if (index >= threads.Length) { break; } else if (threads[index] == null) { continue; } thread = threads[index]; } finally { processMutex.ReleaseMutex(); } if (thread != null) { thread.Resume(); } } return true; } public void Stop(int exitCode) { // // Terminate the process forcefully. // Should remove all user code from threads, then force them // to terminate ASAP, close out objects, etc. // Ask the kernel service thread to call ServiceStop. ThreadLocalServiceRequest.StopProcess(this, exitCode); } // Should only execute in the kernel service thread // // ServiceStop does not block waiting for threads to exit, // so it is recommended that whoever makes the service // request (currently ThreadLocalServiceRequest) perform // a Process.Join to block until the threads have exited // and the process has stopped. internal void ServiceStop(int exitCode) { processMutex.AcquireMutex(); try { if (state == ProcessState.Stopped) { return; } } finally { processMutex.ReleaseMutex(); } // Make sure that any suspended process we're about to stop has // a chance to process any pending signals before getting stopped // forever: ServiceResume(true); // Now suspend it for good: ServiceSuspend(true, true); ServiceStopAfterSuspend(exitCode); } // Should only execute in the kernel service thread private void ServiceStopAfterSuspend(int exitCode) { processMutex.AcquireMutex(); try { Debug.Assert(state == ProcessState.Suspended || state == ProcessState.Unstarted, "unexpected process state"); state = ProcessState.Stopping; } finally { processMutex.ReleaseMutex(); } processMutex.AcquireMutex(); Process[] childArray = children; processMutex.ReleaseMutex(); // Don't hold processMutex while calling ServiceStop; it // would try to double-acquire the lock in RemoveChild. // No locking should be necessary here, because a // suspended process should not add new children. foreach (Process c in childArray) { if (c != null) { c.ServiceStopAfterSuspend(exitCode); } } // Throw an exception in each suspended thread. for (int index = 0;; index++) { Thread thread = null; processMutex.AcquireMutex(); try { if (index >= threads.Length) { break; } else if (threads[index] == null) { continue; } thread = threads[index]; } finally { processMutex.ReleaseMutex(); } if (thread != null) { thread.Stop(); } } if (exitCode >= (int) ProcessExitCode.StopMin && exitCode <= (int) ProcessExitCode.StopMax) { this.exitCode = exitCode; } else { this.exitCode = (int) ProcessExitCode.StopDefault; } } // Should only execute in the kernel service thread internal void ServiceOnThreadStop(Thread thread) { #if THREAD_TIME_ACCOUNTING deadThreadExecutionTime += thread.ExecutionTime; deadThreadCount++; #endif bool found = ServiceRemoveThread(thread); VTable.Assert(found); ServiceCheckForExit(); } // Check to see if a process should exit. If so, release its // resources. // Should only execute in the kernel service thread private void ServiceCheckForExit() { if (startedThreadCount == 0 && startedChildCount == 0) { ServiceRelease(); } } // If the current process is about to suspend, block until // the process resumes. Note: if efficiency is more important than // immediate blocking, you can check ThreadContext.suspendAlert // before calling SuspendBarrier, which (assuming a reasonable // multiprocessor memory model) will eventually become // true during a suspension if the thread runs long enough: // if (Thread.CurrentThread.context.suspendAlert) SuspendBarrier(); // The important thing is that each running thread eventually enters a // suspendable state (process-unblocked-running or // kernel-blocked-running) so that the loop in ServiceSuspend will // terminate. internal static void SuspendBarrier() { Process p = Thread.CurrentProcess; // The service thread holds the suspendMutex throughout the // suspension process, so we'll block if we try to acquire // it during suspension: p.suspendMutex.AcquireMutex(); Thread.CurrentThread.context.suspendAlert = false; // Ok, we must be running now. Release the mutex. p.suspendMutex.ReleaseMutex(); } // Block if any the current process is suspending or if any of its // ancestors is known by this processor to be suspending recursively. internal static void SuspendBarrierCheckParents() { bool yieldAndRepeat; do { yieldAndRepeat = false; SuspendBarrier(); for (Process p = Thread.CurrentProcess.parent; p != null; p = p.parent) { // Don't try to acquire another process's suspendMutex; // instead, just check its state field. // MULTIPROCESSOR NOTE: It's not // important that we see the most up-to-date value of // p.state immediately -- we just need to see // SuspendingRecursive eventually if we call this // barrier enough times. if (p.state == ProcessState.SuspendingRecursive) { // An ancestor is suspending recursively. This // means we'll be suspended soon. Just wait // for this to happen by yielding repeatedly; // we'll eventually block in SuspendBarrier(). yieldAndRepeat = true; } } if (yieldAndRepeat) { Thread.Yield(); } } while (yieldAndRepeat); } public void Allocated(UIntPtr bytes) { #if THREAD_SAFE_INTERNAL_ONLY UIntPtr used; UIntPtr now; do { now = pagesNow; used = now + bytes; } while (now != Interlocked.CompareExchange(ref pagesNow, used, now)); do { now = pagesMax; } while (used > now && now != Interlocked.CompareExchange(ref pagesMax, used, now)); #else pagesNow += bytes; if (pagesMax < pagesNow) { pagesMax = pagesNow; } #endif } public void Freed(UIntPtr bytes) { #if THREAD_SAFE_INTERNAL_ONLY UIntPtr used; UIntPtr now; do { now = pagesNow; used = now - bytes; } while (now != Interlocked.CompareExchange(ref pagesNow, used, now)); #else pagesNow -= bytes; #endif } // Returns the currently allocated memory in bytes. public UIntPtr AllocatedMemory { [NoHeapAllocation] get { return pagesNow; } } // Returns the peak allocated memory in bytes. public UIntPtr PeakAllocatedMemory { [NoHeapAllocation] get { return pagesMax; } } // Returns number of pages in the handle table public int NumHandlePages { [NoHeapAllocation] get { return handles.GetPageCount(); } } public int ExitCode { [NoHeapAllocation] get { return exitCode; } } // Return parameter is really: DirectoryService.Imp opt(ExHeap) * public unsafe SharedHeap.Allocation * GetNamespaceEndpoint() { return DirectoryService.NewClientEndpointEx(); } // // Endpoint Argument Processing // // Set the size of the endpoint set. public unsafe bool SetEndpointCount(int count) { if (state != ProcessState.Unstarted) { throw new ProcessStateException("process not unstarted"); } if (count == 0) { // don't create array if the count is zero. return true; } endpointSet = new SharedHeap.Allocation * [count]; //DebugStub.WriteLine("-- Process.SetEndpointCount({0})", //__arglist(count)); return true; } // Set the endpoint and individual endpoint. public unsafe bool SetEndpoint(int index, ref SharedHeap.Allocation * endpoint) { // FIXFIX: I short-circuited the check below to bypass // issues with stdin pipes. // if (state != ProcessState.Unstarted) { // throw new ProcessStateException("process not unstarted"); // if (endpointSet == null) { DebugStub.WriteLine("Process.SetEndpoint is NULL!"); throw new ProcessStateException("endpoint set not allocated, attempting to set"); } if (index > endpointSet.Length-1) { DebugStub.WriteLine("Process.SetEndpoint {0} out of range!",__arglist(index)); throw new ProcessStateException("endpoint index out of range"); } if (endpointSet[index] != null) { DebugStub.Break(); DebugStub.WriteLine("Process.SetEndpoint {0} was already set!",__arglist(index)); throw new ProcessStateException("endpoint already non-null"); } // We first move it to the kernel. When the target process retrieves it, // we move it into its heap. endpointSet[index] = Microsoft.Singularity.Channels.EndpointCore.MoveEndpoint( Thread.CurrentProcess.ProcessSharedHeap, SharedHeap.KernelSharedHeap, this, endpoint); endpoint = null; return true; } public unsafe int GetStartupEndpointCount() { //DebugStub.WriteLine("-- Process.GetStartupEndpointCount() -> {0}", //__arglist((int)(endpointSet != null ? endpointSet.Length : 0))); return endpointSet != null ? endpointSet.Length : 0; } public unsafe SharedHeap.Allocation * GetStartupEndpoint(int arg) { // Endpoints can only be retrieved once. if (endpointSet == null || arg >= endpointSet.Length) { // legacy code will always ask for a null endpoint 0. //DebugStub.WriteLine("-- Process.GetStartupEndpoint({0}) -> {1:x8}", //__arglist(arg, 0)); return null; } SharedHeap.Allocation * used = endpointSet[arg]; endpointSet[arg] = null; #if PAGING // now move it from kernel to user process used = Microsoft.Singularity.Channels.EndpointCore.MoveEndpoint( SharedHeap.KernelSharedHeap, this.ProcessSharedHeap, this, used); #endif return used; } // String Argument Processing // StringArg // StringArgCount public bool SetStringArgCount(int count) { if (state != ProcessState.Unstarted) { throw new ProcessStateException("process not unstarted"); } if (count == 0) { // don't create array if the count is zero. return true; } stringArgSet = new StringArg [count]; //DebugStub.WriteLine("-- Process.SetStringArgCount({0})", //__arglist(count)); return true; } public bool SetStringArrayArgCount(int count) { //DebugStub.WriteLine("SetStringArrayArg called. count={0}", __arglist(count)); if (state != ProcessState.Unstarted) { throw new ProcessStateException("process not unstarted"); } if (count == 0) { // don't create array if the count is zero. return true; } stringArrayArgSet = new StringArrayArg [count]; if (stringArrayArgSet == null) { throw new Exception("out of memory in process SetStringArrayArgCount"); } return true; } public int GetStartupStringArrayArgCount() { return stringArrayArgSet != null ? stringArrayArgSet.Length : 0; } public ParameterCode GetStartupStringArrayArg(int index, out string [] strings) { //DebugStub.WriteLine("GetStartupStringArrayArg called. index={0}", __arglist(index)); if (stringArrayArgSet == null) { strings = null; return ParameterCode.NotSet; } if (index > stringArrayArgSet.Length-1) { strings = null; return ParameterCode.OutOfRange; } if (stringArrayArgSet[index] == null) { strings = null; return ParameterCode.NotSet; } if (!stringArrayArgSet[index].Set) { strings = null; return ParameterCode.NotSet; } //DebugStub.WriteLine("GetStartupStringArrayArg getting value"); strings = stringArrayArgSet[index].Value; return ParameterCode.Success; } public ParameterCode SetStartupStringArrayArg(int index, string[] strings) { //DebugStub.WriteLine("Setting string Arg array"); if (index > stringArrayArgSet.Length-1) { return ParameterCode.OutOfRange; } if (stringArrayArgSet[index] == null) { stringArrayArgSet[index] = new StringArrayArg(strings); } else { stringArrayArgSet[index].Value = strings; } stringArrayArgSet[index].Set = true; return ParameterCode.Success; } public ParameterCode SetStartupStringArg(int index, string value) { //DebugStub.WriteLine("set string attempting to set idx={0}", __arglist(index)); if (index > stringArgSet.Length-1) { //DebugStub.WriteLine("Process.SetStringArg {0} out of range!",__arglist(index)); //throw new ProcessStateException("StringArg index out of range"); return ParameterCode.OutOfRange; } if (stringArgSet[index] != null) { //DebugStub.Break(); //DebugStub.WriteLine("Process.SetStringArg {0} was already set!",__arglist(index)); //throw new ProcessStateException("StringArg already non-null"); return ParameterCode.AlreadySet; } stringArgSet[index] = new StringArg(value); stringArgSet[index].Set = true; return ParameterCode.Success; } public unsafe int GetStartupStringArgCount() { return stringArgSet != null ? stringArgSet.Length : 0; } public unsafe ParameterCode GetStartupStringArg(int arg, out string value) { if (stringArgSet == null) { value = null; return ParameterCode.NotSet; } if (arg >= stringArgSet.Length) { value = null; return ParameterCode.OutOfRange; } if (stringArgSet[arg] == null ) { value = null; return ParameterCode.Success; } if (!stringArgSet[arg].Set) { value = null; return ParameterCode.NotSet; } value = stringArgSet[arg].Value; return ParameterCode.Success; } // int Argument Processing // StringArg // StringArgCount public unsafe bool SetLongArgCount(int count) { if (state != ProcessState.Unstarted) { throw new ProcessStateException("process not unstarted"); } if (count == 0) { // don't create array if the count is zero. return true; } longArgSet = new LongArg [count]; //DebugStub.WriteLine("-- Process.SetLongArgCount({0})", //__arglist(count)); return true; } public unsafe ParameterCode SetStartupLongArg(int index, long value) { if (index > longArgSet.Length-1) { //DebugStub.WriteLine("Process.SetLongArg {0} out of range!",__arglist(index)); //throw new ProcessStateException("LongArg index out of range"); return ParameterCode.OutOfRange; } // TODO checks for setting more than once if (longArgSet[index] != null) { return ParameterCode.AlreadySet; } longArgSet[index] = new LongArg(value); longArgSet[index].Set = true; return ParameterCode.Success; } public unsafe int GetStartupLongArgCount() { return longArgSet != null ? longArgSet.Length : 0; } public unsafe ParameterCode GetStartupLongArg(int arg, out long value) { if (longArgSet == null || arg >= longArgSet.Length) { //DebugStub.WriteLine("Process.GetLongArg {0} out of range!",__arglist(arg)); //throw new ProcessStateException("LongArg index out of range"); value = -1; return ParameterCode.OutOfRange; } if ( longArgSet[arg] == null) { value = -1; return ParameterCode.NotSet; } if (longArgSet[arg].Set == false) { value = -1; return ParameterCode.NotSet; } #if EXTRA if (longArgSet[arg].Retrieved == true) { value = -1; return ParameterCode.Retrieved; } #endif value = longArgSet[arg].Value; longArgSet[arg].Retrieved = true; return ParameterCode.Success; } // bool Argument Processing // StringArg // StringArgCount public unsafe bool SetBoolArgCount(int count) { if (state != ProcessState.Unstarted) { throw new ProcessStateException("process not unstarted"); } if (count == 0) { // don't create array if the count is zero. return true; } boolArgSet = new BoolArg [count]; //DebugStub.WriteLine("-- Process.SetBoolArgCount({0})", //__arglist(boolArgSet.Length)); return true; } public unsafe ParameterCode SetStartupBoolArg(int index, bool value) { if (boolArgSet == null) return ParameterCode.OutOfRange; if (index > boolArgSet.Length-1) { //DebugStub.WriteLine("Process.SetBoolArg {0} out of range!",__arglist(index)); //throw new ProcessStateException("BoolArg index out of range"); return ParameterCode.OutOfRange; } // TODO checks for setting more than once boolArgSet[index] = new BoolArg(value); boolArgSet[index].Set = true; return ParameterCode.Success; } public unsafe ParameterCode GetStartupBoolArg(int index, out bool value) { value = false; if (boolArgSet == null || index >= boolArgSet.Length) { //DebugStub.WriteLine("Process.GetBoolArg {0} out of range!",__arglist(index)); //throw new ProcessStateException("BoolArg index out of range");\ value = false; return ParameterCode.OutOfRange; } if ( boolArgSet[index] == null) { value = false; return ParameterCode.NotSet; } if (boolArgSet[index].Set == false) { return ParameterCode.NotSet; } value = boolArgSet[index].Value; boolArgSet[index].Retrieved = true; return ParameterCode.Success; } public unsafe int GetStartupBoolArgCount() { //if ( boolArgSet != null) DebugStub.WriteLine("bool args=", __arglist(boolArgSet.Length)); return boolArgSet != null ? boolArgSet.Length : 0; } public void SetArgs(string[] args) { this.args = args; } [NoHeapAllocation] public int GetStartupArgCount() { return args.Length; } [NoHeapAllocation] public string GetStartupArg(int arg) { if (arg >= args.Length) { return null; } return args[arg]; } [NoHeapAllocation] public string GetProcessName() { return imageName; } [NoHeapAllocation] public void SetGcPerformanceCounters(TimeSpan timeSpent, long bytes) { gcCount++; gcTotalTime = gcTotalTime.AddNoAlloc(timeSpent); gcTotalBytes += bytes; } [NoHeapAllocation] public void GetGcPerformanceCounters( out int count, out TimeSpan time, out long bytes) { count = gcCount; time = gcTotalTime; bytes = gcTotalBytes; } public long GetThreadTimes() { #if THREAD_TIME_ACCOUNTING long total = 0; for (int i = 0; i < threads.Length; i++) { if (threads[i] != null) { total += threads[i].ExecutionTime.Ticks; } } return total; #else return 0; #endif } public long DeadThreadCount { get { return deadThreadCount; } } public long DeadThreadTime { get { return deadThreadExecutionTime.Ticks; } } public int [] GetThreadIDs() { int threadCount = 0; int [] temp = null; processMutex.AcquireMutex(); try { for (int i = 0; i < threads.Length; i++) { if (threads[i] != null) threadCount++; } if (threadCount > 0) { temp = new int[threadCount]; int current = 0; for (int i = 0; i < threads.Length; i++) { if (threads[i] != null) { temp[current++] = threads[i].GetThreadId(); } } } //else DebugStub.Break(); } finally { processMutex.ReleaseMutex(); } return temp; } // Start the execution within the PE Image. private void PeStart() { Tracing.Log(Tracing.Audit, "** Start of process {0}.", args[0]); UIntPtr entryPoint = image.GetEntryPoint(loadedImage); #if VERBOSE DebugStub.WriteLine("calling CallEntryPoint"); DebugStub.WriteLine(" entry:{0:x8}, args{1}",__arglist((uint)entryPoint, args)); for (int i = 0; i < args.Length; i++) { DebugStub.WriteLine(" {0}: [{1}]", __arglist(i, args[i])); } #endif try { Tracing.Log(Tracing.Audit, "Calling entryPoint={0:x}", entryPoint); Kernel.Waypoint(530); #if THREAD_TIME_ACCOUNTING Processor.GetCurrentThread().context.lastExecutionTimeUpdate = Processor.CycleCount; #endif int code = PEImage.CallEntryPoint(entryPoint, -1, !RunsAtKernelPrivilege); if (code >= (int) ProcessExitCode.MainMin && code <= (int) ProcessExitCode.MainMax) { exitCode = code; } else { exitCode = (int) ProcessExitCode.ErrorDefault; } Kernel.Waypoint(531); unchecked { Tracing.Log(Tracing.Audit, "Main thread exited with (exit={0})", (UIntPtr)(uint)exitCode); } } catch (Exception e) { // Don't set exitCode here; the only exception we should see is // ProcessStopException, and Stop sets exitCode in this case. Tracing.Log(Tracing.Fatal, "Main thread failed with exception {0}.{1}", e.GetType().Namespace, e.GetType().Name); Tracing.Log(Tracing.Trace, "Exception message was {0}", e.ToString()); DebugStub.WriteLine("Process.cs: Caught exception {0}.", __arglist(e.ToString())); } } // Start the execution of the new thread within the PE Image. private void PeStartThread() { UIntPtr entryPoint = image.GetEntryPoint(loadedImage); int threadIndex = Thread.CurrentThread.processThreadIndex; int threadExit = 0; Tracing.Log(Tracing.Debug, "PeStartThread(entry={0:x8}, threadIndex={1}", entryPoint, (uint)threadIndex); #if VERBOSE DebugStub.WriteLine("calling CallEntryPoint for thread"); DebugStub.WriteLine(" entry:{0:x8}, threadIndex={1}", __arglist((uint)entryPoint, threadIndex)); #endif try { Tracing.Log(Tracing.Audit, "Calling thread entryPoint={0:x}", entryPoint); Kernel.Waypoint(532); #if THREAD_TIME_ACCOUNTING Processor.GetCurrentThread().context.lastExecutionTimeUpdate = Processor.CycleCount; #endif threadExit = PEImage.CallEntryPoint(entryPoint, threadIndex, !RunsAtKernelPrivilege); Kernel.Waypoint(533); unchecked { Tracing.Log(Tracing.Audit, "Thread exited with (exit={0})", (UIntPtr)(uint)threadExit); } } catch (Exception e) { Tracing.Log(Tracing.Fatal, "Process thread failed with exception {0}.{1}", e.GetType().Namespace, e.GetType().Name); Tracing.Log(Tracing.Trace, "Exception message was {0}", e.ToString()); DebugStub.WriteLine("Caught exception {0}.", __arglist(e.ToString())); } } public ProcessState State { get { return this.state; } } #region HandleTable public unsafe UIntPtr AllocateHandle() { return handles.AllocateHandle(); } public unsafe UIntPtr AllocateHandle(object obj) { DebugStub.Assert(obj != null, "Can't allocate handle for null object."); UIntPtr handle = handles.AllocateHandle(); HandleTable.SetHandle(handle, obj); return handle; } public void ReleaseHandle(UIntPtr id) { unchecked { Tracing.Log(Tracing.Debug, "Process[{0:x3}].ReleaseHandle(id={1:x})", (UIntPtr)(uint)processIndex, id); } handles.FreeHandle(id); } #endregion } }