8000 Implementation for Invoke-Command step-in remote debugging by PaulHigin · Pull Request #3015 · PowerShell/PowerShell · GitHub
[go: up one dir, main page]

Skip to content

Implementation for Invoke-Command step-in remote debugging #3015

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
455 changes: 427 additions & 28 deletions src/System.Management.Automation/engine/debugger/debugger.cs

Large diffs are not rendered by default.

47 changes: 0 additions & 47 deletions src/System.Management.Automation/engine/remoting/client/Job.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2803,15 +2803,6 @@ internal void ConnectAsync()
_remotePipeline.ConnectAsync();
}

/// <summary>
/// Removes job data aggregation callbacks. Used for jobs
/// stopped in debugger so that debugger can access data.
/// </summary>
internal void RemoveJobAggregation()
{
RemoveAggreateCallbacksFromHelper(Helper);
}

#endregion

#region stop
Expand Down Expand Up @@ -3077,32 +3068,6 @@ protected void HandleURIDirectionReported(object sender, RemoteDataEventArgs<Uri
this.WriteWarning(message);
}

/// <summary>
/// Used to detect an Invoke-Command running command breakpoint hit.
/// In this case disconnect the runspace so that a debugger can be
/// attached later by the user.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void HandleRunspaceAvailabilityChangedForInvoke(object sender, RunspaceAvailabilityEventArgs e)
{
RemoteRunspace remoteRunspace = sender as RemoteRunspace;
if (remoteRunspace != null &&
e.RunspaceAvailability == RunspaceAvailability.RemoteDebug)
{
remoteRunspace.AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke;

try
{
remoteRunspace.DisconnectAsync();
}
catch (PSNotImplementedException) { }
catch (InvalidRunspacePoolStateException) { }
catch (InvalidRunspaceStateException) { }
catch (PSInvalidOperationException) { }
}
}

/// <summary>
/// Handle method executor stream events.
/// </summary>
Expand Down Expand Up @@ -3146,10 +3111,6 @@ protected virtual void HandlePipelineStateChanged(object sender, PipelineStateEv
// since we got state changed event..we dont need to listen on
// URI redirections anymore
((RemoteRunspace)Runspace).URIRedirectionReported -= HandleURIDirectionReported;

// We monitor runspace RemoteDebug availability only while
// this pipeline is running.
((RemoteRunspace)Runspace).AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke;
}

PipelineState state = e.PipelineStateInfo.State;
Expand Down Expand Up @@ -3628,12 +3589,6 @@ private void HandleInformationAdded(object sender, DataAddedEventArgs eventArgs)
/// aggregation has to be stopped</param>
protected void StopAggregateResultsFromHelper(ExecutionCmdletHelper helper)
{
// Ensure the Runspace availability handler is removed on command completion.
if (helper.PipelineRunspace != null)
{
helper.PipelineRunspace.AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke;
}

// Get the pipeline associated with this helper and register for appropriate events
RemoveAggreateCallbacksFromHelper(helper);

Expand Down Expand Up @@ -4123,7 +4078,6 @@ internal PSInvokeExpressionSyncJob(List<IThrottleOperation> operations, Throttle
RemoteRunspace remoteRS = helper.Pipeline.Runspace as RemoteRunspace;
if (null != remoteRS)
{
remoteRS.AvailabilityChanged += HandleRunspaceAvailabilityChangedForInvoke;
remoteRS.StateChanged += HandleRunspaceStateChanged;

if (remoteRS.RunspaceStateInfo.State == RunspaceState.BeforeOpen)
Expand Down Expand Up @@ -4357,7 +4311,6 @@ private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs e)
if (e.RunspaceStateInfo.State != RunspaceState.Opened)
{
remoteRS.StateChanged -= HandleRunspaceStateChanged;
remoteRS.AvailabilityChanged -= HandleRunspaceAvailabilityChangedForInvoke;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1351,7 +1351,7 @@ private bool SetDebugInfo(PSPrimitiveDictionary psApplicationPrivateData)
var psVersionTable = psApplicationPrivateData[PSVersionInfo.PSVersionTableName] as PSPrimitiveDictionary;
if (psVersionTable.ContainsKey(PSVersionInfo.PSVersionName))
{
ServerVersion = psVersionTable[PSVersionInfo.PSVersionName] as Version;
ServerVersion = PSObject.Base(psVersionTable[PSVersionInfo.PSVersionName]) as Version;
}
}
}
Expand Down Expand Up @@ -2435,7 +2435,7 @@ private void ProcessDebuggerStopEventProc(object state)
finally
{
_handleDebuggerStop = false;
if (!_detachCommand)
if (!_detachCommand && !args.SuspendRemote)
{
SetDebuggerAction(args.ResumeAction);
}
Expand Down Expand Up @@ -2468,7 +2468,7 @@ private void ProcessDebuggerStopEventProc(object state)
finally
{
// Restore runspace availability.
if (restoreAvailability)
if (restoreAvailability && (_runspace.RunspaceAvailability == RunspaceAvailability.RemoteDebug))
{
SetRemoteDebug(false, prevAvailability);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,35 @@ public override Hashtable[] SSHConnection

#endregion

#region Remote Debug Parameters

/// <summary>
/// When selected this parameter causes a debugger Step-Into action for each running remote session.
/// </summary>
[Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.SessionParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.UriParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathSessionParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathUriParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.VMIdParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.VMNameParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.ContainerIdParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.SSHHostHashParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)]
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostHashParameterSet)]
public virtual SwitchParameter RemoteDebug
{
get;
set;
}

#endregion

#endregion Parameters

#region Overrides
Expand Down Expand Up @@ -767,6 +796,19 @@ protected override void BeginProcessing()
throw new InvalidOperationException(RemotingErrorIdStrings.SessionNameWithoutInvokeDisconnected);
}

// Adjust RemoteDebug value based on current state
var hostDebugger = GetHostDebugger();
if (hostDebugger == null)
{
// Do not allow RemoteDebug if there is no host debugger available. Otherwise script will hang indefinitely.
RemoteDebug = false;
}
else if (hostDebugger.IsDebuggerSteppingEnabled)
{
// If host debugger is in step-in mode then always make RemoteDebug true
RemoteDebug = true;
}

// Checking session's availability and reporting errors in early stage, unless '-AsJob' is specified.
// When '-AsJob' is specified, Invoke-Command should return a job object without throwing error, even
// if the session is not in available state -- this is the PSv3 behavior and we should not break it.
Expand Down Expand Up @@ -985,7 +1027,7 @@ protected override void ProcessRecord()
_inputStreamClosed = true;
}

if (!ParameterSetName.Equals("InProcess"))
if (!ParameterSetName.Equals(InProcParameterSet))
{
// at this point there is nothing to do for
// inproc case. The script block is executed
Expand Down Expand Up @@ -1209,6 +1251,17 @@ protected override void EndProcessing()
/// </remarks>
protected override void StopProcessing()
{
// Ensure that any runspace debug processing is ended
var hostDebugger = GetHostDebugger();
if (hostDebugger != null)
{
try
{
hostDebugger.CancelDebuggerProcessing();
}
catch (PSNotImplementedException) { }
}

if (!ParameterSetName.Equals(InvokeCommandCommand.InProcParameterSet))
{
if (!_asjob)
Expand Down Expand Up @@ -1247,6 +1300,20 @@ protected override void StopProcessing()

#region Private Methods

private Debugger GetHostDebugger()
{
Debugger hostDebugger = null;
try
{
System.Management.Automation.Internal.Host.InternalHost chost =
this.Host as System.Management.Automation.Internal.Host.InternalHost;
hostDebugger = chost.Runspace.Debugger;
}
catch (PSNotImplementedException) { }

return hostDebugger;
}

/// <summary>
/// Handle event from the throttle manager indicating that all
/// operations are complete
Expand Down Expand Up @@ -1298,11 +1365,32 @@ private void CreateAndRunSyncJob()
// Add robust connection retry notification handler.
AddConnectionRetryHandler(_job);

// Enable all Invoke-Command synchronous jobs for remote debugging (in case Wait-Debugger or
// or line breakpoints are set in script).
foreach (var operation in Operations)
{
operation.RunspaceDebuggingEnabled = true;
operation.RunspaceDebugStepInEnabled = RemoteDebug;
operation.RunspaceDebugStop += HandleRunspaceDebugStop;
}

_job.StartOperations(Operations);
}
}
}

private void HandleRunspaceDebugStop(object sender, StartRunspaceDebugProcessingEventArgs args)
{
var operation = sender as IThrottleOperation;
operation.RunspaceDebugStop -= HandleRunspaceDebugStop;

var hostDebugger = GetHostDebugger();
if (hostDebugger != null)
{
hostDebugger.QueueRunspaceForDebug(args.Runspace);
}
}

private void HandleJobStateChanged(object sender, JobStateEventArgs e)
{
JobState state = e.JobStateInfo.State;
Expand Down Expand Up @@ -1617,8 +1705,6 @@ private void WriteJobResults(bool nonblocking)
// pipelines.
_asjob = true;

List<Job> removedDebugStopJobs = new List<Job>();

// Write warnings to user about each disconnect.
foreach (var cjob in rtnJob.ChildJobs)
{
Expand All @@ -1629,41 +1715,17 @@ private void WriteJobResults(bool nonblocking)
PSSession session = GetPSSession(childJob.Runspace.InstanceId);
if (session != null)
{
RemoteDebugger remoteDebugger = session.Runspace.Debugger as RemoteDebugger;
if (remoteDebugger != null &&
remoteDebugger.IsRemoteDebug)
{
// The session was disconnected because it hit a debug breakpoint.
// Write network failed, auto-disconnect error
WriteNetworkFailedError(session);

// Remove child job data aggregation so debugger can show data.
childJob.RemoveJobAggregation();
removedDebugStopJobs.Add(childJob);

// Write appropriate warning.
WriteWarning(
StringUtil.Format(RemotingErrorIdStrings.RCDisconnectDebug,
// Session disconnected message.
WriteWarning(
StringUtil.Format(RemotingErrorIdStrings.RCDisconnectSession,
session.Name, session.InstanceId, session.ComputerName));
}
else
{
// Write network failed, auto-disconnect error
WriteNetworkFailedError(session);

// Session disconnected message.
WriteWarning(
StringUtil.Format(RemotingErrorIdStrings.RCDisconnectSession,
session.Name, session.InstanceId, session.ComputerName));
}
}
}
}

// Remove debugger stopped jobs
foreach (var dJob in removedDebugStopJobs)
{
rtnJob.ChildJobs.Remove(dJob);
}

if (rtnJob.ChildJobs.Count > 0)
{
JobRepository.Add(rtnJob);
Expand All @@ -1686,25 +1748,13 @@ private void WriteJobResults(bool nonblocking)
// Add to session repository.
this.RunspaceRepository.AddOrReplace(session);

RemoteRunspace remoteRunspace = session.Runspace as RemoteRunspace;
if (remoteRunspace != null &&
remoteRunspace.RunspacePool.RemoteRunspacePoolInternal.IsRemoteDebugStop)
{
// The session was disconnected because it hit a debug breakpoint.
WriteWarning(
StringUtil.Format(RemotingErrorIdStrings.RCDisconnectDebug,
session.Name, session.InstanceId, session.ComputerName));
}
else
{
// Write network failed, auto-disconnect error
WriteNetworkFailedError(session);
// Write network failed, auto-disconnect error
WriteNetworkFailedError(session);

// Session disconnected message.
WriteWarning(
StringUtil.Format(RemotingErrorIdStrings.RCDisconnectSession,
session.Name, session.InstanceId, session.ComputerName));
}
// Session disconnected message.
WriteWarning(
StringUtil.Format(RemotingErrorIdStrings.RCDisconnectSession,
session.Name, session.InstanceId, session.ComputerName));

// Session created message.
WriteWarning(
Expand Down
Loading
0