/// <summary> /// Finds a module of given MVID in one of the processes being debugged and returns its baseline metadata. /// Shall only be called while in debug mode. /// Shall only be called on MTA thread. /// </summary> public DebuggeeModuleInfo TryGetBaselineModuleInfo(Guid mvid) { Contract.ThrowIfFalse(Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA); return(_baselineMetadata.GetOrAdd(mvid, m => { using (DebuggerComponent.ManagedEditAndContinueService()) { var clrModuleInstance = FindClrModuleInstance(m); if (clrModuleInstance == null) { return default; } if (!TryGetBaselineModuleInfo(clrModuleInstance, out var metadata, out var symReader)) { return default; } // hook up a callback on module unload (the call blocks until the message is processed): DkmCustomMessage.Create( Connection: clrModuleInstance.Process.Connection, Process: clrModuleInstance.Process, SourceId: DebuggerService.MessageSourceId, MessageCode: 0, Parameter1: this, Parameter2: clrModuleInstance).SendLower(); return new DebuggeeModuleInfo(metadata, symReader); }
/// <summary> /// Finds a module of given MVID in one of the processes being debugged and returns its baseline metadata. /// Shall only be called while in debug mode. /// </summary> public ModuleMetadata TryGetBaselineMetadata(Guid mvid) { return(_baselineMetadata.GetOrAdd(mvid, m => { using (DebuggerComponent.ManagedEditAndContinueService()) { var clrModuleInstance = FindClrModuleInstance(m); if (clrModuleInstance == null) { return default; } var metadata = GetBaselineModuleMetadata(clrModuleInstance); if (metadata == null) { return default; } // hook up a callback on module unload (the call blocks until the message is processed): DkmCustomMessage.Create( Connection: clrModuleInstance.Process.Connection, Process: clrModuleInstance.Process, SourceId: DebuggerService.MessageSourceId, MessageCode: 0, Parameter1: this, Parameter2: clrModuleInstance).SendLower(); return metadata; } })); }
public void StartDebugging(DebugSessionOptions options) { _debuggingService.OnBeforeDebuggingStateChanged(DebuggingState.Design, DebuggingState.Run); if ((options & DebugSessionOptions.EditAndContinueDisabled) == 0) { _encService = _workspace.Services.GetRequiredService <IEditAndContinueWorkspaceService>(); // hook up a callbacks (the call blocks until the message is processed): using (DebuggerComponent.ManagedEditAndContinueService()) { DkmCustomMessage.Create( Connection: null, Process: null, SourceId: DebuggerService.MessageSourceId, MessageCode: 0, Parameter1: _encService, Parameter2: null).SendLower(); } _encService.StartDebuggingSession(); } else { _encService = null; } }
/// <summary> /// Called by the debugger when a debugging session starts and managed debugging is being used. /// </summary> public void StartDebugging() { // Hook up a callbacks (the call blocks until the message is processed). using (DebuggerComponent.ManagedEditAndContinueService()) { DkmCustomMessage.Create( Connection: null, Process: null, SourceId: DebuggerService.MessageSourceId, MessageCode: 0, Parameter1: this, Parameter2: null).SendLower(); } _debuggingService.OnBeforeDebuggingStateChanged(DebuggingState.Design, DebuggingState.Run); _encService.StartDebuggingSession(); }
/// <summary> /// Finds a module of given MVID in one of the processes being debugged and returns its baseline metadata. /// Shall only be called while in debug mode. /// Shall only be called on MTA thread. /// </summary> /// <returns>Null, if the module with the specified MVID is not found.</returns> public DebuggeeModuleInfo TryGetBaselineModuleInfo(Guid mvid) { Contract.ThrowIfFalse(Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA); return(_baselineMetadata.GetOrAdd(mvid, m => { using (DebuggerComponent.ManagedEditAndContinueService()) { var clrModuleInstance = EnumerateClrModuleInstances(m).FirstOrDefault(); if (clrModuleInstance == null) { return null; } // The DebuggeeModuleInfo holds on a pointer to module metadata and on a SymReader instance. // // The module metadata lifetime is that of the module instance, so we need to stop using the module // as soon as the module instance is unloaded. We do so by associating a DataItem with the module // instance and removing the metadata from our cache when the DataItem is disposed. // // The SymReader instance is shared across all instancese of the module. if (!clrModuleInstance.TryGetModuleInfo(out var info)) { return null; } // hook up a callback on module unload (the call blocks until the message is processed): DkmCustomMessage.Create( Connection: clrModuleInstance.Process.Connection, Process: clrModuleInstance.Process, SourceId: DebuggerService.MessageSourceId, MessageCode: 0, Parameter1: this, Parameter2: clrModuleInstance).SendLower(); return info; }
/// <summary> /// Retrieves active statements from the debuggee process. /// Shall only be called while in debug mode. /// Can be invoked on any thread. /// </summary> public Task <ImmutableArray <ActiveStatementDebugInfo> > GetActiveStatementsAsync(CancellationToken cancellationToken) { using (DebuggerComponent.ManagedEditAndContinueService()) { // TODO: return empty outside of debug session. // https://github.com/dotnet/roslyn/issues/24325 int unexpectedError = 0; var completion = new TaskCompletionSource <ImmutableArray <ActiveStatementDebugInfo> >(); var builders = default(ArrayBuilder <ArrayBuilder <ActiveStatementDebugInfo> >); int pendingRuntimes = 0; int runtimeCount = 0; var workList = DkmWorkList.Create(CompletionRoutine: _ => { completion.TrySetException(new InvalidOperationException($"Unexpected error enumerating active statements: 0x{unexpectedError:X8}")); }); void CancelWork() { if (builders != null) { FreeBuilders(builders); builders = null; // TODO: DkmWorkList.Cancel doesn't currently work when invoked on the completion callback. // We continue execute all the queued callbacks -- they will be no-ops. // See https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/562781. // // workList.Cancel(); // make sure we cancel with the token we received from the caller: completion.TrySetCanceled(cancellationToken); } } foreach (var process in DkmProcess.GetProcesses()) { foreach (var runtimeInstance in process.GetRuntimeInstances()) { if (runtimeInstance.TagValue == DkmRuntimeInstance.Tag.ClrRuntimeInstance) { var clrRuntimeInstance = (DkmClrRuntimeInstance)runtimeInstance; int runtimeIndex = runtimeCount; runtimeCount++; clrRuntimeInstance.GetActiveStatements(workList, activeStatementsResult => { if (cancellationToken.IsCancellationRequested) { CancelWork(); return; } if (activeStatementsResult.ErrorCode != 0) { unexpectedError = activeStatementsResult.ErrorCode; return; } // group active statement by instruction and aggregate flags and threads: var instructionMap = PooledDictionary <ActiveInstructionId, (DkmInstructionSymbol Symbol, ArrayBuilder <Guid> Threads, int Index, ActiveStatementFlags Flags)> .GetInstance(); GroupActiveStatementsByInstructionId(instructionMap, activeStatementsResult.ActiveStatements); int pendingStatements = instructionMap.Count; builders[runtimeIndex] = ArrayBuilder <ActiveStatementDebugInfo> .GetInstance(pendingStatements); builders[runtimeIndex].Count = pendingStatements; foreach (var(instructionId, (symbol, threads, index, flags)) in instructionMap) { var immutableThreads = threads.ToImmutableAndFree(); symbol.GetSourcePosition(workList, DkmSourcePositionFlags.None, InspectionSession: null, sourcePositionResult => { if (cancellationToken.IsCancellationRequested) { CancelWork(); return; } int errorCode = sourcePositionResult.ErrorCode; if (errorCode != 0) { unexpectedError = errorCode; } DkmSourcePosition position; string documentNameOpt; LinePositionSpan span; if (errorCode == 0 && (position = sourcePositionResult.SourcePosition) != null) { documentNameOpt = position.DocumentName; span = ToLinePositionSpan(position.TextSpan); } else { // The debugger can't determine source location for the active statement. // The PDB might not be available or the statement is in a method that doesn't have debug information. documentNameOpt = null; span = default; } builders[runtimeIndex][index] = new ActiveStatementDebugInfo( instructionId, documentNameOpt, span, immutableThreads, flags); // the last active statement of the current runtime has been processed: if (Interlocked.Decrement(ref pendingStatements) == 0) { // the last active statement of the last runtime has been processed: if (Interlocked.Decrement(ref pendingRuntimes) == 0) { completion.TrySetResult(builders.ToFlattenedImmutableArrayAndFree()); } } }); } instructionMap.Free(); });
/// <summary> /// Retrieves active statements from the debuggee process. /// Shall only be called while in debug mode. /// Can be invoked on any thread. /// </summary> public Task <ImmutableArray <ActiveStatementDebugInfo> > GetActiveStatementsAsync(CancellationToken cancellationToken) { using (DebuggerComponent.ManagedEditAndContinueService()) { // TODO: return empty outside of debug session. // https://github.com/dotnet/roslyn/issues/24325 var completion = new TaskCompletionSource <ImmutableArray <ActiveStatementDebugInfo> >(); var builders = default(ArrayBuilder <ArrayBuilder <ActiveStatementDebugInfo> >); int pendingRuntimes = 0; int runtimeCount = 0; // No exception should be thrown in case of errors on the debugger side. // The debugger is responsible to provide telemetry for error cases. // The callback should not be called, but it's there to guarantee that the task completes and a hang is avoided. var workList = DkmWorkList.Create(_ => { completion.TrySetResult(ImmutableArray <ActiveStatementDebugInfo> .Empty); }); void CancelWork() { if (builders != null) { FreeBuilders(builders); builders = null; workList.Cancel(blockOnCompletion: false); // make sure we cancel with the token we received from the caller: completion.TrySetCanceled(cancellationToken); } } foreach (var process in DkmProcess.GetProcesses()) { foreach (var runtimeInstance in process.GetRuntimeInstances()) { if (runtimeInstance.TagValue == DkmRuntimeInstance.Tag.ClrRuntimeInstance) { var clrRuntimeInstance = (DkmClrRuntimeInstance)runtimeInstance; int runtimeIndex = runtimeCount; runtimeCount++; clrRuntimeInstance.GetActiveStatements(workList, activeStatementsResult => { try { if (cancellationToken.IsCancellationRequested) { CancelWork(); return; } var localBuilders = builders; if (localBuilders == null) // e.g. cancelled { return; } if (activeStatementsResult.ErrorCode != 0) { localBuilders[runtimeIndex] = ArrayBuilder <ActiveStatementDebugInfo> .GetInstance(0); // the last active statement of the last runtime has been processed: if (Interlocked.Decrement(ref pendingRuntimes) == 0) { completion.TrySetResult(localBuilders.ToFlattenedImmutableArrayAndFree()); } return; } // group active statement by instruction and aggregate flags and threads: var instructionMap = PooledDictionary <ActiveInstructionId, (DkmInstructionSymbol Symbol, ArrayBuilder <Guid> Threads, int Index, ActiveStatementFlags Flags)> .GetInstance(); GroupActiveStatementsByInstructionId(instructionMap, activeStatementsResult.ActiveStatements); int pendingStatements = instructionMap.Count; localBuilders[runtimeIndex] = ArrayBuilder <ActiveStatementDebugInfo> .GetInstance(pendingStatements); localBuilders[runtimeIndex].Count = pendingStatements; if (instructionMap.Count == 0) { if (Interlocked.Decrement(ref pendingRuntimes) == 0) { completion.TrySetResult(localBuilders.ToFlattenedImmutableArrayAndFree()); } return; } foreach (var(instructionId, (symbol, threads, index, flags)) in instructionMap) { var immutableThreads = threads.ToImmutableAndFree(); symbol.GetSourcePosition(workList, DkmSourcePositionFlags.None, InspectionSession: null, sourcePositionResult => { try { if (cancellationToken.IsCancellationRequested) { CancelWork(); return; } DkmSourcePosition position; string documentNameOpt; LinePositionSpan span; if (sourcePositionResult.ErrorCode == 0 && (position = sourcePositionResult.SourcePosition) != null) { documentNameOpt = position.DocumentName; span = ToLinePositionSpan(position.TextSpan); } else { // The debugger can't determine source location for the active statement. // The PDB might not be available or the statement is in a method that doesn't have debug information. documentNameOpt = null; span = default; } localBuilders[runtimeIndex][index] = new ActiveStatementDebugInfo( instructionId, documentNameOpt, span, immutableThreads, flags); // the last active statement of the current runtime has been processed: if (Interlocked.Decrement(ref pendingStatements) == 0) { // the last active statement of the last runtime has been processed: if (Interlocked.Decrement(ref pendingRuntimes) == 0) { completion.TrySetResult(localBuilders.ToFlattenedImmutableArrayAndFree()); } } } catch (Exception e) { completion.TrySetException(e); } }); } instructionMap.Free(); }