2019-08-23 11:56:54 -07:00
|
|
|
using System;
|
2020-04-29 06:40:03 -07:00
|
|
|
using System.Collections;
|
2019-08-23 11:56:54 -07:00
|
|
|
using System.Text;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using Beefy.gfx;
|
|
|
|
using Beefy.theme.dark;
|
|
|
|
using System.IO;
|
|
|
|
|
|
|
|
namespace IDE.Debugger
|
|
|
|
{
|
|
|
|
public class Breakpoint : TrackedTextElement
|
|
|
|
{
|
|
|
|
[Reflect]
|
|
|
|
public enum HitCountBreakKind
|
|
|
|
{
|
|
|
|
None,
|
|
|
|
Equals,
|
|
|
|
GreaterEquals,
|
|
|
|
MultipleOf
|
|
|
|
};
|
|
|
|
|
2019-12-13 14:25:15 -08:00
|
|
|
public enum SetKind
|
|
|
|
{
|
|
|
|
Toggle,
|
|
|
|
Force,
|
|
|
|
EnsureExists,
|
|
|
|
MustExist
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum SetFlags
|
|
|
|
{
|
|
|
|
None,
|
|
|
|
Configure,
|
|
|
|
Disable
|
|
|
|
}
|
|
|
|
|
2019-08-23 11:56:54 -07:00
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void Breakpoint_Delete(void* nativeBreakpoint);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern int32 Breakpoint_GetPendingHotBindIdx(void* nativeBreakpoint);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void Breakpoint_HotBindBreakpoint(void* breakpoint, int32 lineNum, int32 hotIdx);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern int Breakpoint_GetAddress(void* nativeBreakpoint, out void* linkedSibling);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern bool Breakpoint_SetCondition(void* nativeBreakpoint, char8* condition);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern bool Breakpoint_SetLogging(void* nativeBreakpoint, char8* logging, bool breakAfterLogging);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern bool Breakpoint_IsMemoryBreakpointBound(void* nativeBreakpoint);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern int32 Breakpoint_GetLineNum(void* nativeBreakpoint);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void Breakpoint_Move(void* nativeBreakpoint, int32 wantLineNum, int32 wantColumn, bool rebindNow);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void Breakpoint_MoveMemoryBreakpoint(void* nativeBreakpoint, int addr, int32 byteCount);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void Breakpoint_Disable(void* nativeBreakpoint);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void* Debugger_CreateBreakpoint(char8* fileName, int32 wantLineNum, int32 wantColumn, int32 instrOffset);
|
|
|
|
|
|
|
|
[StdCall,CLink]
|
|
|
|
static extern void* Debugger_CreateSymbolBreakpoint(char8* symbolName);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void* Breakpoint_Check(void* nativeBreakpoint);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void Breakpoint_SetThreadId(void* nativeBreakpoint, int threadId);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern int32 Breakpoint_GetHitCount(void* nativeBreakpoint);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern int32 Breakpoint_ClearHitCount(void* nativeBreakpoint);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void Breakpoint_SetHitCountTarget(void* nativeBreakpoint, int hitCountTarget, HitCountBreakKind breakKind);
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
|
|
|
static extern void* Debugger_GetActiveBreakpoint();
|
|
|
|
|
|
|
|
[StdCall, CLink]
|
2020-03-09 06:34:16 -07:00
|
|
|
static extern void* Debugger_CreateMemoryBreakpoint(int addr, int32 byteCount);
|
2019-08-23 11:56:54 -07:00
|
|
|
|
|
|
|
public void* mNativeBreakpoint;
|
|
|
|
public String mSymbol ~ delete _;
|
|
|
|
public bool mIsDisposed;
|
|
|
|
public int32 mInstrOffset;
|
|
|
|
public bool mAddressRequested;
|
|
|
|
public bool mIsMemoryBreakpoint;
|
|
|
|
public uint8 mByteCount;
|
|
|
|
public int mMemoryAddress;
|
|
|
|
public HitCountBreakKind mHitCountBreakKind;
|
|
|
|
public int mHitCountTarget;
|
|
|
|
|
|
|
|
public String mAddrType ~ delete _;
|
|
|
|
public String mMemoryWatchExpression ~ delete _;
|
|
|
|
public String mCondition ~ delete _;
|
|
|
|
public String mLogging ~ delete _;
|
|
|
|
public bool mBreakAfterLogging;
|
|
|
|
public int mThreadId = -1;
|
|
|
|
public bool mDisabled;
|
|
|
|
public bool mDeleteOnUnbind;
|
|
|
|
|
|
|
|
public Event<Action> mOnDelete;
|
|
|
|
|
2020-03-09 06:34:16 -07:00
|
|
|
public ~this()
|
2019-08-23 11:56:54 -07:00
|
|
|
{
|
|
|
|
mOnDelete();
|
|
|
|
mIsDisposed = true;
|
|
|
|
DisposeNative();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RehupBreakpointSettings()
|
|
|
|
{
|
|
|
|
if (!String.IsNullOrEmpty(mCondition))
|
|
|
|
SetCondition();
|
|
|
|
if (!String.IsNullOrEmpty(mLogging))
|
|
|
|
SetLogging();
|
|
|
|
if (mHitCountBreakKind != .None)
|
|
|
|
Breakpoint_SetHitCountTarget(mNativeBreakpoint, mHitCountTarget, mHitCountBreakKind);
|
|
|
|
if (mThreadId != -1)
|
|
|
|
Breakpoint_SetThreadId(mNativeBreakpoint, mThreadId);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void CreateNative(bool bindNow = true)
|
|
|
|
{
|
|
|
|
//var debugger = IDEApp.sApp.mDebugger;
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
{
|
|
|
|
if (mIsMemoryBreakpoint)
|
|
|
|
{
|
|
|
|
// Wait for a 'rehup'
|
|
|
|
//mNativeBreakpoint = Debugger_CreateMemoryBreakpoint((void*)mMemoryAddress, mByteCount);
|
|
|
|
}
|
|
|
|
else if (mAddressRequested)
|
|
|
|
{
|
|
|
|
// Wait for a 'rehup'
|
|
|
|
//mNativeBreakpoint = Debugger_CreateAddressBreakpoint(mMemoryAddress);
|
|
|
|
}
|
|
|
|
else if (mFileName != null)
|
|
|
|
{
|
|
|
|
mNativeBreakpoint = Debugger_CreateBreakpoint(mFileName, mLineNum, mColumn, mInstrOffset);
|
|
|
|
RehupBreakpointSettings();
|
|
|
|
if (bindNow)
|
|
|
|
Breakpoint_Check(mNativeBreakpoint);
|
|
|
|
CheckBreakpointHotBinding();
|
|
|
|
}
|
|
|
|
else if (mSymbol != null)
|
|
|
|
{
|
|
|
|
mNativeBreakpoint = Debugger_CreateSymbolBreakpoint(mSymbol);
|
|
|
|
RehupBreakpointSettings();
|
|
|
|
if (bindNow)
|
|
|
|
Breakpoint_Check(mNativeBreakpoint);
|
|
|
|
CheckBreakpointHotBinding();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void Kill()
|
|
|
|
{
|
|
|
|
DisposeNative();
|
|
|
|
base.Kill();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void DisposeNative()
|
|
|
|
{
|
|
|
|
if (mNativeBreakpoint != null)
|
|
|
|
{
|
|
|
|
Breakpoint_Delete(mNativeBreakpoint);
|
|
|
|
mNativeBreakpoint = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool IsBound()
|
|
|
|
{
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
void* linkedSibling;
|
|
|
|
if (mIsMemoryBreakpoint)
|
|
|
|
return Breakpoint_IsMemoryBreakpointBound(mNativeBreakpoint);
|
|
|
|
else
|
|
|
|
return Breakpoint_GetAddress(mNativeBreakpoint, out linkedSibling) != (int)0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool IsActiveBreakpoint()
|
|
|
|
{
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
return false;
|
|
|
|
return mNativeBreakpoint == Debugger_GetActiveBreakpoint();
|
|
|
|
}
|
|
|
|
|
|
|
|
public int GetAddress(ref void* curBreakpoint)
|
|
|
|
{
|
|
|
|
if (curBreakpoint == null)
|
|
|
|
curBreakpoint = mNativeBreakpoint;
|
|
|
|
if (curBreakpoint == null)
|
|
|
|
return 0;
|
|
|
|
return Breakpoint_GetAddress(curBreakpoint, out curBreakpoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool ContainsAddress(int addr)
|
|
|
|
{
|
|
|
|
void* breakItr = mNativeBreakpoint;
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
if (breakItr == null)
|
|
|
|
return false;
|
|
|
|
int breakAddr = Breakpoint_GetAddress(breakItr, out breakItr);
|
|
|
|
if (breakAddr == addr)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetCondition()
|
|
|
|
{
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (String.IsNullOrWhiteSpace(mCondition))
|
|
|
|
{
|
|
|
|
Breakpoint_SetCondition(mNativeBreakpoint, "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
String condition = scope .();
|
|
|
|
GetEvalStrWithSubject(mCondition, condition);
|
|
|
|
Breakpoint_SetCondition(mNativeBreakpoint, condition);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SetCondition(String condition)
|
|
|
|
{
|
|
|
|
String.NewOrSet!(mCondition, condition);
|
|
|
|
mCondition.Trim();
|
|
|
|
SetCondition();
|
|
|
|
if (mCondition.Length == 0)
|
|
|
|
{
|
|
|
|
delete mCondition;
|
|
|
|
mCondition = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SetThreadId(int threadId)
|
|
|
|
{
|
|
|
|
mThreadId = threadId;
|
|
|
|
if (mNativeBreakpoint != null)
|
|
|
|
Breakpoint_SetThreadId(mNativeBreakpoint, mThreadId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GetEvalStrWithSubject(String exprStr, String outStr)
|
|
|
|
{
|
|
|
|
if (mAddrType == null)
|
|
|
|
{
|
|
|
|
outStr.Append(exprStr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var enumerator = mAddrType.Split('\t');
|
|
|
|
int langVal = int.Parse(enumerator.GetNext().Get()).Get();
|
|
|
|
StringView addrVal = enumerator.GetNext().Get();
|
|
|
|
if (langVal == (.)DebugManager.Language.C)
|
|
|
|
outStr.Append("@C:");
|
|
|
|
else
|
|
|
|
outStr.Append("@Beef:");
|
|
|
|
outStr.Append(exprStr);
|
|
|
|
outStr.AppendF("\n({0}*)0x", addrVal);
|
|
|
|
mMemoryAddress.ToString(outStr, "X", null);
|
|
|
|
outStr.Append("L");
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetLogging()
|
|
|
|
{
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (String.IsNullOrWhiteSpace(mLogging))
|
|
|
|
{
|
|
|
|
Breakpoint_SetLogging(mNativeBreakpoint, "", false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
String logging = scope .();
|
|
|
|
GetEvalStrWithSubject(mLogging, logging);
|
|
|
|
Breakpoint_SetLogging(mNativeBreakpoint, logging, mBreakAfterLogging);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SetLogging(String logging, bool breakAfterLogging)
|
|
|
|
{
|
|
|
|
String.NewOrSet!(mLogging, logging);
|
|
|
|
mLogging.Trim();
|
|
|
|
mBreakAfterLogging = breakAfterLogging;
|
|
|
|
SetLogging();
|
|
|
|
if (mLogging.Length == 0)
|
|
|
|
{
|
|
|
|
delete mLogging;
|
|
|
|
mLogging = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SetHitCountTarget(int hitCountTarget, HitCountBreakKind hitCountBreakKind)
|
|
|
|
{
|
|
|
|
mHitCountTarget = hitCountTarget;
|
|
|
|
mHitCountBreakKind = hitCountBreakKind;
|
|
|
|
if (mNativeBreakpoint != null)
|
|
|
|
Breakpoint_SetHitCountTarget(mNativeBreakpoint, hitCountTarget, hitCountBreakKind);
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool HasNativeBreakpoint()
|
|
|
|
{
|
|
|
|
return mNativeBreakpoint != null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int32 GetLineNum()
|
|
|
|
{
|
|
|
|
/*if (mNativeBreakpoint == null)
|
|
|
|
return mLineNum;*/
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
return mLineNum;
|
|
|
|
int32 lineNum = Breakpoint_GetLineNum(mNativeBreakpoint);
|
|
|
|
if (lineNum != -1)
|
|
|
|
return lineNum;
|
|
|
|
return mLineNum;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void Move(int wantLineNum, int wantColumn)
|
|
|
|
{
|
|
|
|
//Breakpoint_Move(mNativeBreakpoint, wantLineNum, wantColumn);
|
|
|
|
mLineNum = (int32)wantLineNum;
|
|
|
|
mColumn = (int32)wantColumn;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void MoveMemoryBreakpoint(int addr, int byteCount, String addrType)
|
|
|
|
{
|
|
|
|
mMemoryAddress = addr;
|
|
|
|
mByteCount = (uint8)byteCount;
|
|
|
|
if (addrType != null)
|
|
|
|
String.NewOrSet!(mAddrType, addrType);
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
{
|
|
|
|
mNativeBreakpoint = Debugger_CreateMemoryBreakpoint(addr, (.)byteCount);
|
|
|
|
RehupBreakpointSettings();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
Breakpoint_MoveMemoryBreakpoint(mNativeBreakpoint, addr, (.)byteCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Rehup(bool rebindNow)
|
|
|
|
{
|
|
|
|
Breakpoint_Move(mNativeBreakpoint, mLineNum, mColumn, rebindNow);
|
|
|
|
CheckBreakpointHotBinding();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Disable()
|
|
|
|
{
|
|
|
|
if (mNativeBreakpoint != null)
|
|
|
|
{
|
|
|
|
//Breakpoint_Disable(mNativeBreakpoint);
|
|
|
|
Breakpoint_Delete(mNativeBreakpoint);
|
|
|
|
mNativeBreakpoint = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void CheckBreakpointHotBinding()
|
|
|
|
{
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ProjectSource projectSource;
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
int32 hotIdx = Breakpoint_GetPendingHotBindIdx(mNativeBreakpoint);
|
|
|
|
if (hotIdx == -1)
|
|
|
|
break;
|
|
|
|
|
|
|
|
projectSource = gApp.FindProjectSourceItem(mFileName);
|
|
|
|
if (projectSource == null)
|
|
|
|
break;
|
|
|
|
|
|
|
|
var editData = gApp.GetEditData(projectSource, true);
|
|
|
|
int char8Id = editData.mEditWidget.Content.GetSourceCharIdAtLineChar(mLineNum, mColumn);
|
|
|
|
if (char8Id == 0)
|
|
|
|
break;
|
|
|
|
int lineNum = mLineNum;
|
|
|
|
int column = mColumn;
|
|
|
|
if (!gApp.mWorkspace.GetProjectSourceCharIdPosition(projectSource, hotIdx, char8Id, ref lineNum, ref column))
|
|
|
|
lineNum = -1; // Don't bind
|
|
|
|
Breakpoint_HotBindBreakpoint(mNativeBreakpoint, (int32)lineNum, hotIdx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Draw(Graphics g, bool isOldCompiledVersion)
|
|
|
|
{
|
|
|
|
uint32 color = Color.White;
|
|
|
|
bool isBreakEx = (mCondition != null) || (mLogging != null) || (mHitCountBreakKind != .None);
|
|
|
|
Image image = DarkTheme.sDarkTheme.GetImage(isBreakEx ? .RedDotEx : .RedDot);
|
|
|
|
Image threadImage = null;
|
|
|
|
|
|
|
|
if (this == gApp.mDebugger.mRunToCursorBreakpoint)
|
|
|
|
{
|
|
|
|
image = DarkTheme.sDarkTheme.GetImage(.RedDotRunToCursor);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (mDisabled)
|
|
|
|
{
|
|
|
|
image = DarkTheme.sDarkTheme.GetImage(isBreakEx ? DarkTheme.ImageIdx.RedDotExDisabled : DarkTheme.ImageIdx.RedDotDisabled);
|
|
|
|
}
|
|
|
|
else if (!isOldCompiledVersion)
|
|
|
|
{
|
|
|
|
//color = (breakpoint.IsBound()) ? Color.White : 0x8080FFFF;
|
|
|
|
if ((!IsBound()) || (mThreadId == 0))
|
|
|
|
image = DarkTheme.sDarkTheme.GetImage(isBreakEx ? DarkTheme.ImageIdx.RedDotExUnbound : DarkTheme.ImageIdx.RedDotUnbound);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
color = 0x8080FFFF;
|
|
|
|
|
|
|
|
if (mThreadId == -1)
|
|
|
|
{
|
|
|
|
// Nothing
|
|
|
|
}
|
|
|
|
else if (mThreadId == 0)
|
|
|
|
threadImage = DarkTheme.sDarkTheme.GetImage(.ThreadBreakpointUnbound);
|
|
|
|
else if ((!gApp.mDebugger.IsPaused()) || (mThreadId == gApp.mDebugger.GetActiveThread()))
|
|
|
|
threadImage = DarkTheme.sDarkTheme.GetImage(.ThreadBreakpointMatch);
|
|
|
|
else
|
|
|
|
threadImage = DarkTheme.sDarkTheme.GetImage(.ThreadBreakpointNoMatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
using (g.PushColor(color))
|
|
|
|
{
|
|
|
|
g.Draw(image, 0, 0);
|
|
|
|
if (threadImage != null)
|
|
|
|
g.Draw(threadImage, GS!(-11), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public int GetHitCount()
|
|
|
|
{
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
return 0;
|
|
|
|
return Breakpoint_GetHitCount(mNativeBreakpoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ClearHitCount()
|
|
|
|
{
|
|
|
|
if (mNativeBreakpoint == null)
|
|
|
|
return;
|
|
|
|
Breakpoint_ClearHitCount(mNativeBreakpoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ToString_Location(String str)
|
|
|
|
{
|
|
|
|
if (mFileName != null)
|
|
|
|
{
|
|
|
|
Path.GetFileName(mFileName, str);
|
|
|
|
str.AppendF(":{0}", mLineNum + 1);
|
|
|
|
if (mInstrOffset >= 0)
|
|
|
|
str.AppendF("+{0}", mInstrOffset);
|
|
|
|
}
|
|
|
|
else if (mAddressRequested)
|
|
|
|
{
|
|
|
|
void* curSubBreakpoint = null;
|
|
|
|
int addr = GetAddress(ref curSubBreakpoint);
|
|
|
|
str.AppendF("0x{0:X08}", addr);
|
|
|
|
}
|
|
|
|
else if (mIsMemoryBreakpoint)
|
|
|
|
{
|
|
|
|
void* curSubBreakpoint = null;
|
|
|
|
if (IsBound())
|
|
|
|
{
|
|
|
|
#unwarn
|
|
|
|
int addr = GetAddress(ref curSubBreakpoint);
|
|
|
|
str.AppendF("When '0x{0:X08}' ({1}) changes", mMemoryAddress, mMemoryWatchExpression);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
str.AppendF("Unbound: When ({0}) changes", mMemoryWatchExpression);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (mSymbol != null)
|
|
|
|
{
|
|
|
|
str.Append(mSymbol);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ToString_HitCount(String str)
|
|
|
|
{
|
|
|
|
str.AppendF("{0}", GetHitCount());
|
|
|
|
switch (mHitCountBreakKind)
|
|
|
|
{
|
|
|
|
case .Equals: str.AppendF(", Break={0}", mHitCountTarget);
|
|
|
|
case .GreaterEquals: str.AppendF(", Break>={0}", mHitCountTarget);
|
|
|
|
case .MultipleOf: str.AppendF(", Break%{0}", mHitCountTarget);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|