Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / cdf / src / NetFx40 / System.Activities / System / Activities / Debugger / StateManager.cs / 1305376 / StateManager.cs
//------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.Activities.Debugger { using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.SymbolStore; using System.Globalization; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Runtime; using System.Runtime.InteropServices; using System.Security.Cryptography; // Manager for supporting debugging a state machine. // The general usage is to call: // - DefineState() for each state // - Bake() once you've defined all the states you need to enter. // - EnterState() / LeaveState() for each state. // You can Define new states and bake them, such as if the script loads a new file. // Baking is expensive, so it's best to define as many states in each batch. [DebuggerNonUserCode] // This class need not serialized. [Fx.Tag.XamlVisible(false)] public sealed class StateManager : IDisposable { static readonly Guid WorkflowLanguageGuid = new Guid("1F1149BB-9732-4EB8-9ED4-FA738768919C"); static readonly LocalsItemDescription[] debugInfoDescriptions = new LocalsItemDescription[]{ new LocalsItemDescription("debugInfo", typeof(DebugInfo)) }; static Type threadWorkerControllerType = typeof(ThreadWorkerController); static MethodInfo islandWorkerMethodInfo = threadWorkerControllerType.GetMethod("IslandWorker", BindingFlags.Static | BindingFlags.Public); const string Md5Identifier = "406ea660-64cf-4c82-b6f0-42d48172a799"; internal const string MethodWithPrimingPrefix = "_"; Listthreads; // Don't expose this, because that would expose the setters. Changing the properties // after baking types has undefined semantics and would be confusing to the user. Properties properties; // List of all state that have been created with DefineState. List states; // Index into states array of the last set of states baked. // So Bake() will build islands for each state // { states[x], where indexLastBaked <= x < states.Length; } int indexLastBaked; // Mapping from State --> MethodInfo for that state. // This gets populated as states get baked Dictionary islands; Dictionary islandsWithPriming; ModuleBuilder dynamicModule; Dictionary sourceDocuments; bool debugStartedAtRoot; // Simple default constructor. internal StateManager() : this(new Properties(), true) { } // Constructor. // Properties must be set at creation time. internal StateManager(Properties properties, bool debugStartedAtRoot) { this.properties = properties; //this.virtualCallStack = new Stack (); this.threads = new List (); this.states = new List (); this.islands = new Dictionary (); this.debugStartedAtRoot = debugStartedAtRoot; if (!this.debugStartedAtRoot) { this.islandsWithPriming = new Dictionary (); } this.sourceDocuments = new Dictionary (); InitDynamicModule(this.properties.ModuleNamePrefix); } internal Properties ManagerProperties { get { return this.properties; } } internal bool IsPriming { get; set; } // Whether debugging is started at the root workflow (contrast to attaching in the middle // of a running workflow. internal bool DebugStartedAtRoot { get { return this.debugStartedAtRoot; } } // Declare a new state associated with the given source location. // States should have disjoint source locations. // location is Source location associated with this state. // This returns a state object, which can be passed to EnterState. internal State DefineState(SourceLocation location) { return DefineState(location, string.Empty, null, 0); } internal State DefineState(SourceLocation location, string name) { return DefineState(location, name, null, 0); } internal State DefineState(SourceLocation location, string name, LocalsItemDescription[] earlyLocals, int numberOfEarlyLocals) { State newState = new State(location, name, earlyLocals, numberOfEarlyLocals); this.states.Add(newState); return newState; } internal State DefineStateWithDebugInfo(SourceLocation location, string name) { return DefineState(location, name, debugInfoDescriptions, debugInfoDescriptions.Length); } // Bake all states using the default type namespace. // States must be baked before calling EnterState(). internal void Bake() { Bake(this.properties.TypeNamePrefix); } // Bake all newly defined states. States must be baked before calling EnterState(). // typeName is the type name that the islands are contained in. This // may show up on the callstack. If this is not unique, it will be appended with a unique // identifier. internal void Bake(string typeName) { //ThreadWorkerController controller = new ThreadWorkerController(); //controller.Initialize(this); // Ensure typename is unique. Append a number if needed. int suffix = 1; while (this.dynamicModule.GetType(typeName) != null) { typeName = this.properties.TypeNamePrefix + "_" + suffix.ToString(CultureInfo.InvariantCulture); ++suffix; } TypeBuilder typeBuilder = this.dynamicModule.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class); for (int i = indexLastBaked; i < this.states.Count; i++) { MethodBuilder methodBuilder = this.CreateIsland(typeBuilder, this.states[i], false); Fx.Assert(methodBuilder != null, "CreateIsland call should have succeeded"); if (!this.DebugStartedAtRoot) { MethodBuilder methodBuilderWithPriming = this.CreateIsland(typeBuilder, this.states[i], true); Fx.Assert(methodBuilderWithPriming != null, "CreateIsland call should have succeeded"); } // Save information needed to call Type.GetMethod() later. this.states[i].CacheMethodInfo(typeBuilder, methodBuilder.Name); } // Actual baking. typeBuilder.CreateType(); // Calling Type.GetMethod() is slow (10,000 calls can take ~1 minute). // So defer that to later. this.indexLastBaked = this.states.Count; //controller.Exit(); } internal int CreateLogicalThread(string threadName) { int threadId = -1; // Reuse thread if exists // Start from 1 since main thread never disposed earlier. for (int i = 1; i < this.threads.Count; ++i) { if (this.threads[i] == null) { this.threads[i] = new LogicalThread(i, threadName, this); threadId = i; break; } } // If can't reuse old thread. if (threadId < 0) { threadId = this.threads.Count; this.threads.Add(new LogicalThread(threadId, threadName, this)); } return threadId; } // Push the state onto the virtual callstack, with no locals. // State is the state to push onto stack. //internal void EnterState(int threadIndex, State state) //{ // this.EnterState(threadIndex, state, null); //} // Enter a state and push it onto the 'virtual callstack'. // If the user set a a breakpoint at the source location associated with // this state, this call will hit that breakpoint. // Call LeaveState when the interpretter is finished with this state. // // State is state to enter. // "locals" is local variables (both early-bound and late-bound) associated with this state. // Early-bound locals match by name with the set passed into DefineState. // Late-bound will be displayed read-only to the user in the watch window. // // EnterState can be called reentrantly. If code calls Enter(A); Enter(B); Enter(C); // Then on the call to Enter(C), the virtual callstack will be A-->B-->C. // Each call to Enter() will rebuild the virtual callstack. // internal void EnterState(int threadIndex, State state, IDictionary locals) { this.EnterState(threadIndex, new VirtualStackFrame(state, locals)); } // Enter a state and push it onto the 'virtual callstack'. // Stackframe describing state to enter, along with the // locals in that state. internal void EnterState(int threadIndex, VirtualStackFrame stackFrame) { Fx.Assert(threadIndex < this.threads.Count, "Index out of range for thread"); Fx.Assert(this.threads[threadIndex] != null, "LogicalThread is null"); // Validate that Bake was called. This is more restrictive than is needed since // the state we're entering may well already be baked. // Regardless, I'd rather be more restrictive and loosen up as needed. if (this.states.Count > this.indexLastBaked) { Fx.Assert(false, "Can't Enter state (" + stackFrame + ") until it is baked. Missing call to StateManager.Bake()"); } this.threads[threadIndex].EnterState(stackFrame); } // Pop the state most recently pushed by EnterState. internal void LeaveState(int threadIndex, State state) { Fx.Assert(threadIndex < this.threads.Count, "Index out of range for thread"); Fx.Assert(this.threads[threadIndex] != null, "LogicalThread is null"); this.threads[threadIndex].LeaveState(state); } // Common helper to invoke an Stack frame. // This handles marshaling the args. // islandArguments - arbitrary argument passed ot the islands. // [DebuggerHidden] internal void InvokeWorker(object islandArguments, VirtualStackFrame stackFrame) { State state = stackFrame.State; MethodInfo methodInfo = this.GetIsland(state); IDictionary allLocals = stackFrame.Locals; // Package up the raw arguments array. const int numberOfBaseArguments = 2; int numberOfEarlyLocals = state.NumberOfEarlyLocals; object[] arguments = new object[numberOfBaseArguments + numberOfEarlyLocals]; // +1 for IslandArguments and +1 for IsPriming arguments[0] = this.IsPriming; arguments[1] = islandArguments; if (numberOfEarlyLocals > 0) { int i = numberOfBaseArguments; foreach (LocalsItemDescription localsItemDescription in state.EarlyLocals) { string name = localsItemDescription.Name; object value; if (allLocals.TryGetValue(name, out value)) { // We could assert that val.GetType() is assignable to localsItemDescription.Type. // MethodInfo invoke will check this anyways; but we could check // it and give a better error. } else { // Local not supplied in the array! Use a default. value = Activator.CreateInstance(localsItemDescription.Type); } arguments[i] = value; i++; } } methodInfo.Invoke(null, arguments); } internal MethodBuilder CreateMethodBuilder(TypeBuilder typeBuilder, Type typeIslandArguments, State state, bool withPriming) { // create the method string methodName = (state.Name != null ? state.Name : ("Line_" + state.Location.StartLine)); if (withPriming) { methodName = MethodWithPrimingPrefix + methodName; } // Parameters to the islands: // 1. Args // 2. IDict of late-bound locals. // 3 ... N. list of early bound locals. const int numberOfBaseArguments = 2; IEnumerable earlyLocals = state.EarlyLocals; int numberOfEarlyLocals = state.NumberOfEarlyLocals; Type[] parameterTypes = new Type[numberOfBaseArguments + numberOfEarlyLocals]; parameterTypes[0] = typeof(bool); parameterTypes[1] = typeIslandArguments; if (numberOfEarlyLocals > 0) { int i = numberOfBaseArguments; foreach (LocalsItemDescription localsItemDescription in earlyLocals) { parameterTypes[i] = localsItemDescription.Type; i++; } } Type returnType = typeof(void); MethodBuilder methodbuilder = typeBuilder.DefineMethod( methodName, MethodAttributes.Static | MethodAttributes.Public, returnType, parameterTypes); // Need to define parameter here, otherwise EE cannot get the correct IDebugContainerField // for debugInfo. methodbuilder.DefineParameter(1, ParameterAttributes.None, "isPriming"); methodbuilder.DefineParameter(2, ParameterAttributes.None, "typeIslandArguments"); // Define the parameter names // Note that we can hide implementation-specific arguments from VS by not defining parameter // info for them. Eg., the StepInTarget argument doesn't appear to show up in VS at all. if (numberOfEarlyLocals > 0) { int i = numberOfBaseArguments + 1; foreach (LocalsItemDescription localsItemDescription in earlyLocals) { methodbuilder.DefineParameter(i, ParameterAttributes.None, localsItemDescription.Name); i++; } } return methodbuilder; } [Fx.Tag.InheritThrows(From = "GetILGenerator", FromDeclaringType = typeof(MethodBuilder))] MethodBuilder CreateIsland(TypeBuilder typeBuilder, State state, bool withPrimingTest) { MethodBuilder methodbuilder = this.CreateMethodBuilder(typeBuilder, threadWorkerControllerType, state, withPrimingTest); ILGenerator ilGenerator = methodbuilder.GetILGenerator(); const int lineHidden = 0xFeeFee; // #line hidden directive // Island: // void MethodName(Manager m) // { // .line // nop // call Worker(m) // ret; // } SourceLocation stateLocation = state.Location; ISymbolDocumentWriter document = this.GetSourceDocument(stateLocation.FileName); Label islandWorkerLabel = ilGenerator.DefineLabel(); // Hide all the opcodes before the real source line. // This is needed for Island which is called during priming (Attach to Process): // It should skip the line directive during priming, thus it won't break at user's // breakpoints at the beginning during priming the callstack. if (withPrimingTest) { ilGenerator.MarkSequencePoint(document, lineHidden, 1, lineHidden, 100); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Brtrue, islandWorkerLabel); } // Emit sequence point before the IL instructions to map it to a source location. ilGenerator.MarkSequencePoint(document, stateLocation.StartLine, stateLocation.StartColumn, stateLocation.EndLine, stateLocation.EndColumn); ilGenerator.Emit(OpCodes.Nop); ilGenerator.MarkLabel(islandWorkerLabel); ilGenerator.Emit(OpCodes.Ldarg_1); ilGenerator.EmitCall(OpCodes.Call, islandWorkerMethodInfo, null); ilGenerator.Emit(OpCodes.Ret); return methodbuilder; } MethodInfo GetIsland(State state) { MethodInfo island = null; if (this.IsPriming) { Fx.Assert(!this.DebugStartedAtRoot, "Priming should not happen when debuging is done from start"); if (!islandsWithPriming.TryGetValue(state, out island)) { island = state.GetMethodInfo(true); islandsWithPriming[state] = island; } } else { if (!islands.TryGetValue(state, out island)) { island = state.GetMethodInfo(false); islands[state] = island; } } return island; } void InitDynamicModule(string asmName) { // See http://blogs.msdn.com/[....]/archive/2005/02/03/366429.aspx for a simple example // of debuggable reflection-emit. Fx.Assert(dynamicModule == null, "can only be initialized once"); // create a dynamic assembly and module AssemblyName assemblyName = new AssemblyName(); assemblyName.Name = asmName; AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, (string) null); // Mark generated code as debuggable. // See http://blogs.msdn.com/rmbyers/archive/2005/06/26/432922.aspx for explanation. Type debuggableAttributeType = typeof(DebuggableAttribute); ConstructorInfo constructorInfo = debuggableAttributeType.GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) }); CustomAttributeBuilder builder = new CustomAttributeBuilder(constructorInfo, new object[] { DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.Default }); assemblyBuilder.SetCustomAttribute(builder); dynamicModule = assemblyBuilder.DefineDynamicModule(asmName, true); // <-- pass 'true' to track debug info. } internal ISymbolDocumentWriter GetSourceDocument(string fileName) { ISymbolDocumentWriter documentWriter; if (!this.sourceDocuments.TryGetValue(fileName, out documentWriter)) { documentWriter = dynamicModule.DefineDocument( fileName, StateManager.WorkflowLanguageGuid, SymLanguageVendor.Microsoft, SymDocumentType.Text); this.sourceDocuments.Add(fileName, documentWriter); MD5 md5 = new MD5CryptoServiceProvider(); byte[] checksumBytes = null; using (StreamReader streamReader = new StreamReader(fileName)) { checksumBytes = md5.ComputeHash(streamReader.BaseStream); } if (checksumBytes != null) { documentWriter.SetCheckSum(new Guid(Md5Identifier), checksumBytes); } } return documentWriter; } // Release any unmanaged resources. // This may not necessarily unload islands or dynamic modules that were created until the calling appdomain has exited. public void Dispose() { foreach (LogicalThread logicalThread in this.threads) { if (logicalThread != null) { logicalThread.Exit(); } } this.threads.Clear(); } // Release any unmanaged resources. // This may not necessarily unload islands or dynamic modules that were created until the calling appdomain has exited. public void Exit(int threadIndex) { Fx.Assert(threadIndex >= 0, "Invalid thread index"); Fx.Assert(this.threads[threadIndex] != null, "Cannot dispose null LogicalThread"); LogicalThread thread = this.threads[threadIndex]; thread.Exit(); // Null the entry on the List for future reuse. this.threads[threadIndex] = null; } // Property bag for Manager. These provide customization hooks. // All properties have valid default values. [DebuggerNonUserCode] internal class Properties { public Properties() : this("Locals", "Script", "States", "WorkflowDebuggerThread", true) { } public Properties(string defaultLocalsName, string moduleNamePrefix, string typeNamePrefix, string auxiliaryThreadName, bool breakOnStartup) { this.DefaultLocalsName = defaultLocalsName; this.ModuleNamePrefix = moduleNamePrefix; this.TypeNamePrefix = typeNamePrefix; this.AuxiliaryThreadName = auxiliaryThreadName; this.BreakOnStartup = breakOnStartup; } public string DefaultLocalsName { get; set; } // The name of the dynamic module (not including extension) that the states are emitted to. // This may show up on the callstack. // This is a prefix because there may be multiple modules for the islands. public string ModuleNamePrefix { get; set; } // Typename that states are created in. // This is a prefix because there may be multiple Types for the islands // (such as if islands are created lazily). public string TypeNamePrefix { get; set; } // If UseAuxiliaryThread is true, sets the friendly name of that thread as visible // in the debugger's window. public string AuxiliaryThreadName { get; set; } // If true, the VM issues a Debugger.Break() before entering the first state. // This can be useful for an F11 experience on startup to stop at the first state. // If this is false, then the interpreter will run until it hits a breakpoint or some // other stopping event. public bool BreakOnStartup { get; set; } } [DebuggerNonUserCode] class LogicalThread { int threadId; Stack callStack; ThreadWorkerController controller; public LogicalThread(int threadId, string threadName, StateManager stateManager) { this.threadId = threadId; this.callStack = new Stack (); this.controller = new ThreadWorkerController(); this.controller.Initialize(threadName + "." + threadId.ToString(CultureInfo.InvariantCulture), stateManager); } // Unwind call stack cleanly. void UnwindCallStack() { while (this.callStack.Count > 0) { // LeaveState will do the popping. this.LeaveState(this.callStack.Peek().State); } } public void Exit() { this.UnwindCallStack(); this.controller.Exit(); } // Enter a state and push it onto the 'virtual callstack'. // Stackframe describing state to enter, along with the // locals in that state. public void EnterState(VirtualStackFrame stackFrame) { if (stackFrame != null && stackFrame.State != null) { this.callStack.Push(stackFrame); this.controller.EnterState(stackFrame); } else { // signify "Uninstrumented call" this.callStack.Push(null); } } // Pop the state most recently pushed by EnterState. [SuppressMessage(FxCop.Category.Usage, FxCop.Rule.ReviewUnusedParameters, Justification = "Revisit for bug 36860")] public void LeaveState(State state) { if (this.callStack.Count > 0) { VirtualStackFrame stackFrame = this.callStack.Pop(); Fx.Assert( (state == null && stackFrame == null) || (stackFrame != null && stackFrame.State == state), "Unmatched LeaveState: " + ((state == null) ? "null" : state.Name) + " with top stack frame: " + ((stackFrame == null || stackFrame.State == null) ? "null" : stackFrame.State.Name)); if (stackFrame != null) // Matches with an uninstrumented Activity. { this.controller.LeaveState(); } } else { Fx.Assert("Unmatched LeaveState: " + ((state != null) ? state.Name : "null")); } } } } [ ComImport, Guid("B01FAFEB-C450-3A4D-BEEC-B4CEEC01E006"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), ComVisible(false) ] internal interface ISymUnmanagedDocumentWriter { void SetSource(int sourceSize, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] byte[] source); void SetCheckSum(Guid algorithmId, int checkSumSize, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] checkSum); }; } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- DataSpaceManager.cs
- CommonProperties.cs
- InvalidFilterCriteriaException.cs
- DelegateSerializationHolder.cs
- EventLogEntry.cs
- ExternalCalls.cs
- NumberFormatter.cs
- MenuCommands.cs
- FontStyles.cs
- CreateBookmarkScope.cs
- TableLayoutRowStyleCollection.cs
- PermissionToken.cs
- SqlUserDefinedTypeAttribute.cs
- Vector3D.cs
- SqlParameterCollection.cs
- PreviewPrintController.cs
- ClientSideQueueItem.cs
- Region.cs
- DoubleCollection.cs
- ObjectAssociationEndMapping.cs
- SecureUICommand.cs
- XmlLanguage.cs
- StructuredType.cs
- ProjectionCamera.cs
- SymbolType.cs
- UrlMappingsModule.cs
- ApplicationInterop.cs
- AuthenticatedStream.cs
- ProfilePropertyMetadata.cs
- MergeExecutor.cs
- EventListener.cs
- IUnknownConstantAttribute.cs
- LazyInitializer.cs
- PropertyMapper.cs
- SystemNetHelpers.cs
- BoundColumn.cs
- RootBrowserWindowAutomationPeer.cs
- MultipartContentParser.cs
- TcpConnectionPoolSettingsElement.cs
- objectquery_tresulttype.cs
- PageVisual.cs
- SendParametersContent.cs
- IERequestCache.cs
- SQLMembershipProvider.cs
- OletxDependentTransaction.cs
- XmlCompatibilityReader.cs
- Accessors.cs
- XmlMtomReader.cs
- DetailsViewUpdateEventArgs.cs
- CodeDirectoryCompiler.cs
- RequestSecurityTokenResponseCollection.cs
- SQLConvert.cs
- TabControl.cs
- WeakReference.cs
- ReflectionHelper.cs
- MulticastDelegate.cs
- Cursors.cs
- DiffuseMaterial.cs
- WebBrowsableAttribute.cs
- oledbmetadatacollectionnames.cs
- Int32Collection.cs
- ToolStripSettings.cs
- ConstructorArgumentAttribute.cs
- Token.cs
- ParserStreamGeometryContext.cs
- ModelItemCollectionImpl.cs
- recordstatescratchpad.cs
- SqlUtil.cs
- ServicePerformanceCounters.cs
- EFAssociationProvider.cs
- ServiceControllerDesigner.cs
- GridViewDeletedEventArgs.cs
- XamlSerializer.cs
- DescendantQuery.cs
- parserscommon.cs
- IntegerValidator.cs
- AdjustableArrowCap.cs
- ComponentGuaranteesAttribute.cs
- ReaderOutput.cs
- Trace.cs
- TargetControlTypeAttribute.cs
- PageParser.cs
- SqlTypesSchemaImporter.cs
- PenContexts.cs
- SafeFindHandle.cs
- ProjectionPlanCompiler.cs
- SocketInformation.cs
- ProfileWorkflowElement.cs
- RtfFormatStack.cs
- StringConverter.cs
- StringWriter.cs
- XmlSchemaObjectTable.cs
- ResourceReader.cs
- AudioFormatConverter.cs
- MethodAccessException.cs
- BooleanExpr.cs
- OperatingSystem.cs
- DataGridHeaderBorder.cs
- MobileUserControl.cs
- BinaryNode.cs