Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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);
Comment thread
WardenGnaw marked this conversation as resolved.
_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()
{
Comment thread
WardenGnaw marked this conversation as resolved.
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)
{
Comment thread
WardenGnaw marked this conversation as resolved.
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
33 changes: 32 additions & 1 deletion src/MIDebugEngine/Engine.Impl/BreakpointManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,29 @@ 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.
// Guard on hit count change to avoid looping: -break-after
// itself triggers =breakpoint-modified.
if (boundBp.HitCount != previousHitCount)
Comment thread
WardenGnaw marked this conversation as resolved.
Outdated
{
await boundBp.RearmBreakAfterAsync();
}
}
}
}

string warning = bkpt.TryFindString("warning");
if (!string.IsNullOrEmpty(warning))
{
Expand Down Expand Up @@ -212,7 +235,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())
{
Comment thread
WardenGnaw marked this conversation as resolved.
hitBoundBreakpoints.Add(currBoundBp);
}
}

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