Skip to content
Open
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
9 changes: 9 additions & 0 deletions src/MICore/CommandFactories/MICommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,15 @@ public virtual async Task BreakCondition(string bkptno, string expr)
await _debugger.CmdAsync(command, ResultClass.done);
}

/// <summary>
/// Sends -break-after to set an ignore count on a breakpoint.
/// </summary>
public virtual async Task<Results> BreakAfter(string bkptno, uint count)
{
string command = string.Format(CultureInfo.InvariantCulture, "-break-after {0} {1}", bkptno, count);
return await _debugger.CmdAsync(command, ResultClass.done);
}

public virtual IEnumerable<Guid> GetSupportedExceptionCategories()
{
return new Guid[0];
Expand Down
91 changes: 78 additions & 13 deletions src/MIDebugEngine/AD7.Impl/AD7BoundBreakpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.VisualStudio.Debugger.Interop;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Microsoft.MIDebugEngine
{
Expand All @@ -19,6 +20,8 @@ internal class AD7BoundBreakpoint : IDebugBoundBreakpoint2
private BoundBreakpoint _bp;

private bool _deleted;
private enum_BP_PASSCOUNT_STYLE _passCountStyle;
private uint _passCountValue;

internal bool Enabled
{
Expand All @@ -37,6 +40,7 @@ internal bool Enabled
internal string Number { get { return _bp.Number; } }
internal AD7PendingBreakpoint PendingBreakpoint { get { return _pendingBreakpoint; } }
internal bool IsDataBreakpoint { get { return PendingBreakpoint.IsDataBreakpoint; } }
internal bool HasPassCount { get { return _passCountStyle != enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_NONE; } }

public AD7BoundBreakpoint(AD7Engine engine, AD7PendingBreakpoint pendingBreakpoint, AD7BreakpointResolution breakpointResolution, BoundBreakpoint bp)
{
Expand Down Expand Up @@ -143,8 +147,7 @@ int IDebugBoundBreakpoint2.GetState(enum_BP_STATE[] pState)
return Constants.S_OK;
}

// The sample engine does not support hit counts on breakpoints. A real-world debugger will want to keep track
// of how many times a particular bound breakpoint has been hit and return it here.
// Returns the number of times this breakpoint has been hit.
int IDebugBoundBreakpoint2.GetHitCount(out uint pdwHitCount)
{
pdwHitCount = _bp.HitCount;
Expand All @@ -156,29 +159,91 @@ int IDebugBoundBreakpoint2.SetCondition(BP_CONDITION bpCondition)
return ((IDebugPendingBreakpoint2)_pendingBreakpoint).SetCondition(bpCondition); // setting on the pending break will set the condition
}

// The sample engine does not support hit counts on breakpoints. A real-world debugger will want to keep track
// of how many times a particular bound breakpoint has been hit. The debugger calls SetHitCount when the user
// resets a breakpoint's hit count.
// Called by the debugger when the user resets a breakpoint's hit count.
int IDebugBoundBreakpoint2.SetHitCount(uint dwHitCount)
{
throw new NotImplementedException();
_bp.SetHitCount(dwHitCount);
_pendingBreakpoint?.RecomputeBreakAfter(dwHitCount);

return Constants.S_OK;
}

/// <summary>
/// Syncs the hit count from GDB's "times" field using a delta
/// to preserve any user-initiated hit count reset.
/// </summary>
internal void SetHitCount(uint hitCount)
{
_bp.SetGdbHitCount(hitCount);
}

// The sample engine does not support pass counts on breakpoints.
// This is used to specify the breakpoint hit count condition.
int IDebugBoundBreakpoint2.SetPassCount(BP_PASSCOUNT bpPassCount)
{
if (bpPassCount.stylePassCount != enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_NONE)
{
Delete();
_engine.Callback.OnBreakpointUnbound(this, enum_BP_UNBOUND_REASON.BPUR_BREAKPOINT_ERROR);
return Constants.E_FAIL;
}
_passCountStyle = bpPassCount.stylePassCount;
_passCountValue = bpPassCount.dwPassCount;
return Constants.S_OK;
}

#endregion

internal uint HitCount => _bp.HitCount;

internal void IncrementHitCount()
{
_bp.IncrementHitCount();
}

/// <summary>
/// Evaluates whether the debugger should break at this breakpoint based on the
/// current hit count and the configured pass count condition.
/// Must be called after IncrementHitCount.
/// </summary>
internal bool ShouldBreak()
{
uint hitCount = _bp.HitCount;
switch (_passCountStyle)
{
case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_NONE:
return true;
case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_EQUAL:
return hitCount == _passCountValue;
case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_EQUAL_OR_GREATER:
return hitCount >= _passCountValue;
case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_MOD:
return _passCountValue != 0 && (hitCount % _passCountValue) == 0;
default:
return true;
}
}

/// <summary>
/// Re-sends -break-after to GDB after a pass count breakpoint fires.
/// MOD: skips passCount-1 hits. EQUAL: clears the ignore count.
/// </summary>
internal async Task RearmBreakAfterAsync()
{
uint ignoreCount;
switch (_passCountStyle)
{
case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_MOD:
if (_passCountValue == 0) return;
ignoreCount = _passCountValue - 1;
break;
case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_EQUAL:
ignoreCount = 0;
break;
default:
return;
}

PendingBreakpoint bp = _pendingBreakpoint?.PendingBreakpoint;
if (bp != null && _engine?.DebuggedProcess != null)
{
await bp.SetBreakAfterAsync(ignoreCount, _engine.DebuggedProcess);
}
}

internal void UpdateAddr(ulong addr)
{
_bp.Addr = addr;
Expand Down
112 changes: 103 additions & 9 deletions src/MIDebugEngine/AD7.Impl/AD7PendingBreakpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,6 @@ private bool CanBind()
return false;
}
}
if ((_bpRequestInfo.dwFields & enum_BPREQI_FIELDS.BPREQI_PASSCOUNT) != 0)
{
this.SetError(new AD7ErrorBreakpoint(this, ResourceStrings.UnsupportedPassCountBreakpoint, enum_BP_ERROR_TYPE.BPET_GENERAL_ERROR));
return false;
}

return true;
}
Expand Down Expand Up @@ -393,6 +388,40 @@ internal async Task BindAsync()
}
}
}

// Set ignore count via -break-after if a pass count is configured
if (_bp != null && (_bpRequestInfo.dwFields & enum_BPREQI_FIELDS.BPREQI_PASSCOUNT) != 0
&& _bpRequestInfo.bpPassCount.stylePassCount != enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_NONE)
{
uint ignoreCount = ComputeIgnoreCount(_bpRequestInfo.bpPassCount.stylePassCount, _bpRequestInfo.bpPassCount.dwPassCount, 0);
await _bp.SetBreakAfterAsync(ignoreCount, _engine.DebuggedProcess);
}
}
}

/// <summary>
/// Computes the ignore count for -break-after, accounting for hits already
/// counted from a prior breakpoint (<paramref name="currentHits"/>).
/// </summary>
private static uint ComputeIgnoreCount(enum_BP_PASSCOUNT_STYLE style, uint passCount, uint currentHits)
{
if (passCount == 0)
{
return 0;
}

switch (style)
{
case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_EQUAL:
case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_EQUAL_OR_GREATER:
// Need to stop at hit N. Already counted currentHits, so skip (N - 1 - currentHits) more.
return passCount - 1 > currentHits ? passCount - 1 - currentHits : 0;
case enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_MOD:
// Next stop is at the next multiple of passCount after currentHits.
uint remainder = currentHits % passCount;
return remainder == 0 ? passCount - 1 : passCount - 1 - remainder;
default:
return 0;
}
}

Expand All @@ -406,6 +435,11 @@ internal AD7BoundBreakpoint AddBoundBreakpoint(BoundBreakpoint bp)
}
AD7BreakpointResolution breakpointResolution = new AD7BreakpointResolution(_engine, IsDataBreakpoint, bp.Addr, bp.FunctionName, bp.DocumentContext(_engine));
AD7BoundBreakpoint boundBreakpoint = new AD7BoundBreakpoint(_engine, this, breakpointResolution, bp);
// Apply pass count (hit count condition) from the original request to the bound breakpoint
if ((_bpRequestInfo.dwFields & enum_BPREQI_FIELDS.BPREQI_PASSCOUNT) != 0)
{
((IDebugBoundBreakpoint2)boundBreakpoint).SetPassCount(_bpRequestInfo.bpPassCount);
}
//check can bind one last time. If the pending breakpoint was deleted before now, we need to clean up gdb side
if (CanBind())
{
Expand Down Expand Up @@ -645,17 +679,77 @@ int IDebugPendingBreakpoint2.SetCondition(BP_CONDITION bpCondition)
return Constants.S_OK;
}

// The sample engine does not support pass counts on breakpoints.
int IDebugPendingBreakpoint2.SetPassCount(BP_PASSCOUNT bpPassCount)
{
if (bpPassCount.stylePassCount != enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_NONE)
_bpRequestInfo.bpPassCount = bpPassCount;
_bpRequestInfo.dwFields |= enum_BPREQI_FIELDS.BPREQI_PASSCOUNT;

PendingBreakpoint bp = null;
lock (_boundBreakpoints)
{
this.SetError(new AD7ErrorBreakpoint(this, ResourceStrings.UnsupportedPassCountBreakpoint, enum_BP_ERROR_TYPE.BPET_GENERAL_ERROR), true);
return Constants.E_FAIL;
foreach (AD7BoundBreakpoint boundBp in _boundBreakpoints)
{
((IDebugBoundBreakpoint2)boundBp).SetPassCount(bpPassCount);
}
if (_bp != null)
{
bp = _bp;
}
}

// When the pass count is cleared (NONE), send ignore count 0 to clear
// any stale GDB ignore count from the previous condition.
if (bp != null)
{
uint ignoreCount = 0;
if (bpPassCount.stylePassCount != enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_NONE)
{
uint currentHits = 0;
lock (_boundBreakpoints)
{
foreach (AD7BoundBreakpoint boundBp in _boundBreakpoints)
{
uint hc;
if (((IDebugBoundBreakpoint2)boundBp).GetHitCount(out hc) == Constants.S_OK && hc > currentHits)
{
currentHits = hc;
}
}
}
ignoreCount = ComputeIgnoreCount(bpPassCount.stylePassCount, bpPassCount.dwPassCount, currentHits);
}
_engine.DebuggedProcess.WorkerThread.RunOperation(() =>
{
_engine.DebuggedProcess.AddInternalBreakAction(
() => bp.SetBreakAfterAsync(ignoreCount, _engine.DebuggedProcess)
);
});
}
return Constants.S_OK;
}

/// <summary>
/// Re-sends -break-after to GDB after a hit count reset.
/// </summary>
internal void RecomputeBreakAfter(uint currentHits)
{
if (_bp == null
|| (_bpRequestInfo.dwFields & enum_BPREQI_FIELDS.BPREQI_PASSCOUNT) == 0
|| _bpRequestInfo.bpPassCount.stylePassCount == enum_BP_PASSCOUNT_STYLE.BP_PASSCOUNT_NONE)
{
return;
}

PendingBreakpoint bp = _bp;
uint ignoreCount = ComputeIgnoreCount(_bpRequestInfo.bpPassCount.stylePassCount, _bpRequestInfo.bpPassCount.dwPassCount, currentHits);
_engine.DebuggedProcess.WorkerThread.RunOperation(() =>
{
_engine.DebuggedProcess.AddInternalBreakAction(
() => bp.SetBreakAfterAsync(ignoreCount, _engine.DebuggedProcess)
);
});
}

// Toggles the virtualized state of this pending breakpoint. When a pending breakpoint is virtualized,
// the debug engine will attempt to bind it every time new code loads into the program.
// The sample engine will does not support this.
Expand Down
34 changes: 33 additions & 1 deletion src/MIDebugEngine/Engine.Impl/BreakpointManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ public async Task BreakpointModified(object sender, EventArgs args)
return;
}

// Sync GDB's hit count ("times") for pass count breakpoints.
// e.g. =breakpoint-modified,bkpt={number="1",...,times="5",ignore="2",...}
string timesStr = bkpt.TryFindString("times");
if (!string.IsNullOrEmpty(timesStr) && uint.TryParse(timesStr, out uint times))
{
foreach (AD7BoundBreakpoint boundBp in pending.EnumBoundBreakpoints())
{
if (boundBp.HasPassCount)
{
uint previousHitCount = boundBp.HitCount;
boundBp.SetHitCount(times);

// Re-arm GDB's ignore count so it skips to the next target hit.
// HitCount guard: -break-after itself triggers =breakpoint-modified.
// ShouldBreak guard: GDB also emits =breakpoint-modified on ignored
// hits, and re-arming then would shift the next stop.
if (boundBp.HitCount != previousHitCount && boundBp.ShouldBreak())
{
await boundBp.RearmBreakAfterAsync();
}
}
}
}

string warning = bkpt.TryFindString("warning");
if (!string.IsNullOrEmpty(warning))
{
Expand Down Expand Up @@ -212,7 +236,15 @@ public AD7BoundBreakpoint[] FindHitBreakpoints(string bkptno, ulong addr, /*OPTI
continue;
}

hitBoundBreakpoints.Add(currBoundBp);
// Pass count breakpoints get their hit count from =breakpoint-modified.
if (!currBoundBp.HasPassCount)
{
currBoundBp.IncrementHitCount();
}
if (currBoundBp.ShouldBreak())
{
hitBoundBreakpoints.Add(currBoundBp);
}
}

fContinue = (hitBoundBreakpoints.Count == 0 && hitBps.Length != 0);
Expand Down
Loading
Loading