Debugger: Ability to define assert statements in comments

This commit is contained in:
Sour 2020-04-18 17:55:35 -04:00
parent 77e04a679e
commit 1ff7ed7b8c
5 changed files with 107 additions and 20 deletions

View file

@ -292,7 +292,7 @@ bool ExpressionEvaluator::ToRpn(string expression, ExpressionData &data)
if(!ProcessSpecialOperator(EvalOperators::Parenthesis, opStack, precedenceStack, data.RpnQueue)) { if(!ProcessSpecialOperator(EvalOperators::Parenthesis, opStack, precedenceStack, data.RpnQueue)) {
return false; return false;
} }
operatorExpected = true; operatorOrEndTokenExpected = true;
} else if(token[0] == '[') { } else if(token[0] == '[') {
bracketCount++; bracketCount++;
opStack.push(EvalOperators::Bracket); opStack.push(EvalOperators::Bracket);
@ -302,7 +302,7 @@ bool ExpressionEvaluator::ToRpn(string expression, ExpressionData &data)
if(!ProcessSpecialOperator(EvalOperators::Bracket, opStack, precedenceStack, data.RpnQueue)) { if(!ProcessSpecialOperator(EvalOperators::Bracket, opStack, precedenceStack, data.RpnQueue)) {
return false; return false;
} }
operatorExpected = true; operatorOrEndTokenExpected = true;
} else if(token[0] == '{') { } else if(token[0] == '{') {
braceCount++; braceCount++;
opStack.push(EvalOperators::Braces); opStack.push(EvalOperators::Braces);
@ -312,7 +312,7 @@ bool ExpressionEvaluator::ToRpn(string expression, ExpressionData &data)
if(!ProcessSpecialOperator(EvalOperators::Braces, opStack, precedenceStack, data.RpnQueue)){ if(!ProcessSpecialOperator(EvalOperators::Braces, opStack, precedenceStack, data.RpnQueue)){
return false; return false;
} }
operatorExpected = true; operatorOrEndTokenExpected = true;
} else { } else {
if(token[0] < '0' || token[0] > '9') { if(token[0] < '0' || token[0] > '9') {
return false; return false;
@ -605,5 +605,8 @@ void ExpressionEvaluator::RunTests()
test(":($1FFF+1)", EvalResultType::Numeric, -1); test(":($1FFF+1)", EvalResultType::Numeric, -1);
test(":$1FFF+1", EvalResultType::Numeric, 0x800); test(":$1FFF+1", EvalResultType::Numeric, 0x800);
test("1+:$100", EvalResultType::Numeric, 0x101); test("1+:$100", EvalResultType::Numeric, 0x101);
test("[$4100+[$4100]]", EvalResultType::Numeric, 0x41);
test("-($10+[$4100])", EvalResultType::Numeric, -0x51);
} }
#endif #endif

View file

@ -22,6 +22,15 @@ namespace Mesen.GUI.Debugger
} }
} }
public static List<Breakpoint> Asserts { internal get; set; } = new List<Breakpoint>();
public static List<Breakpoint> GetAllBreakpoints()
{
List<Breakpoint> breakpoints = new List<Breakpoint>(BreakpointManager.Breakpoints);
breakpoints.AddRange(BreakpointManager.Asserts);
return breakpoints;
}
public static void RefreshBreakpoints(Breakpoint bp = null) public static void RefreshBreakpoints(Breakpoint bp = null)
{ {
if(BreakpointsChanged != null) { if(BreakpointsChanged != null) {
@ -124,8 +133,15 @@ namespace Mesen.GUI.Debugger
public static void SetBreakpoints() public static void SetBreakpoints()
{ {
List<InteropBreakpoint> breakpoints = new List<InteropBreakpoint>(); List<InteropBreakpoint> breakpoints = new List<InteropBreakpoint>();
for(int i = 0; i < Breakpoints.Count; i++) {
breakpoints.Add(Breakpoints[i].ToInteropBreakpoint(i)); ReadOnlyCollection<Breakpoint> userBreakpoints = BreakpointManager.Breakpoints;
for(int i = 0; i < userBreakpoints.Count; i++) {
breakpoints.Add(userBreakpoints[i].ToInteropBreakpoint(breakpoints.Count));
}
List<Breakpoint> assertBreakpoints = BreakpointManager.Asserts;
for(int i = 0; i < assertBreakpoints.Count; i++) {
breakpoints.Add(assertBreakpoints[i].ToInteropBreakpoint(breakpoints.Count));
} }
InteropEmu.DebugSetBreakpoints(breakpoints.ToArray(), (UInt32)breakpoints.Count); InteropEmu.DebugSetBreakpoints(breakpoints.ToArray(), (UInt32)breakpoints.Count);
} }

View file

@ -410,7 +410,7 @@ namespace Mesen.GUI.Debugger
_scopes.Add(scope.ID, scope); _scopes.Add(scope.ID, scope);
return true; return true;
} else if(row.StartsWith("scope")) { } else if(row.StartsWith("scope")) {
System.Diagnostics.Debug.Fail("Regex doesn't match scope"); //System.Diagnostics.Debug.Fail("Regex doesn't match scope");
} }
return false; return false;
@ -559,8 +559,12 @@ namespace Mesen.GUI.Debugger
} }
} }
private string[] _splitOnNewLine = { Environment.NewLine };
private void LoadComments() private void LoadComments()
{ {
SortedDictionary<string, int> constants = GetConstants();
foreach(KeyValuePair<int, LineInfo> kvp in _lines) { foreach(KeyValuePair<int, LineInfo> kvp in _lines) {
try { try {
LineInfo line = kvp.Value; LineInfo line = kvp.Value;
@ -622,7 +626,18 @@ namespace Mesen.GUI.Debugger
if(address >= 0 && addressType != null) { if(address >= 0 && addressType != null) {
CodeLabel label = this.CreateLabel(address, addressType.Value, 1); CodeLabel label = this.CreateLabel(address, addressType.Value, 1);
if(label != null) { if(label != null) {
label.Comment = comment; //Parse and replace content of asserts as needed
string[] commentLines = comment.Split(_splitOnNewLine, StringSplitOptions.None);
for(int i = 0; i < commentLines.Length; i++) {
Match m = LabelManager.AssertRegex.Match(commentLines[i]);
if(m.Success) {
foreach(KeyValuePair<string, int> entry in constants) {
commentLines[i] = commentLines[i].Replace(entry.Key, entry.Value.ToString());
}
}
}
label.Comment = string.Join(Environment.NewLine, commentLines);
} }
} }
} }
@ -632,6 +647,26 @@ namespace Mesen.GUI.Debugger
} }
} }
private SortedDictionary<string, int> GetConstants()
{
SortedDictionary<string, int> constants = new SortedDictionary<string, int>(Comparer<string>.Create((string a, string b) => {
if(a.Length == b.Length) {
return a.CompareTo(b);
}
return b.Length - a.Length;
}));
foreach(Ld65DbgImporter.SymbolInfo symbol in GetSymbols()) {
AddressTypeInfo addressInfo = GetSymbolAddressInfo(symbol);
bool isConstant = addressInfo == null;
if(isConstant && symbol.Address.HasValue) {
constants[symbol.Name] = symbol.Address.Value;
}
}
return constants;
}
private void LoadFileData(string path) private void LoadFileData(string path)
{ {
Dictionary<int, int> maxLineCountByFile = new Dictionary<int, int>(); Dictionary<int, int> maxLineCountByFile = new Dictionary<int, int>();

View file

@ -55,6 +55,7 @@ namespace Mesen.GUI.Debugger
public class LabelManager public class LabelManager
{ {
public static Regex LabelRegex { get; } = new Regex("^[@_a-zA-Z]+[@_a-zA-Z0-9]*$", RegexOptions.Compiled); public static Regex LabelRegex { get; } = new Regex("^[@_a-zA-Z]+[@_a-zA-Z0-9]*$", RegexOptions.Compiled);
public static Regex AssertRegex { get; } = new Regex(@"assert\((.*)\)", RegexOptions.Compiled);
private static Dictionary<UInt32, CodeLabel> _labelsByKey = new Dictionary<UInt32, CodeLabel>(); private static Dictionary<UInt32, CodeLabel> _labelsByKey = new Dictionary<UInt32, CodeLabel>();
private static HashSet<CodeLabel> _labels = new HashSet<CodeLabel>(); private static HashSet<CodeLabel> _labels = new HashSet<CodeLabel>();
@ -98,10 +99,37 @@ namespace Mesen.GUI.Debugger
SetLabel(label.Address, label.AddressType, label.Label, label.Comment, false, label.Flags, label.Length); SetLabel(label.Address, label.AddressType, label.Label, label.Comment, false, label.Flags, label.Length);
} }
if(raiseEvents) { if(raiseEvents) {
OnLabelUpdated?.Invoke(null, null); ProcessLabelUpdate();
} }
} }
private static void ProcessLabelUpdate()
{
OnLabelUpdated?.Invoke(null, null);
UpdateAssertBreakpoints();
}
private static void UpdateAssertBreakpoints()
{
List<Breakpoint> asserts = new List<Breakpoint>();
foreach(CodeLabel label in LabelManager.GetLabels()) {
foreach(string commentLine in label.Comment.Split('\n')) {
Match m = LabelManager.AssertRegex.Match(commentLine);
if(m.Success) {
asserts.Add(new Breakpoint() {
BreakOnExec = true,
MemoryType = label.AddressType.ToMemoryType(),
Address = label.Address,
Condition = "!(" + m.Groups[1].Value + ")"
});
}
}
}
BreakpointManager.Asserts = asserts;
BreakpointManager.SetBreakpoints();
}
public static List<CodeLabel> GetLabels() public static List<CodeLabel> GetLabels()
{ {
return _labels.ToList<CodeLabel>(); return _labels.ToList<CodeLabel>();
@ -154,7 +182,7 @@ namespace Mesen.GUI.Debugger
} }
if(raiseEvent) { if(raiseEvent) {
OnLabelUpdated?.Invoke(null, null); ProcessLabelUpdate();
} }
return true; return true;
@ -180,7 +208,7 @@ namespace Mesen.GUI.Debugger
} }
if(needEvent) { if(needEvent) {
OnLabelUpdated?.Invoke(null, null); ProcessLabelUpdate();
} }
} }

View file

@ -451,9 +451,11 @@ namespace Mesen.GUI.Debugger
BreakpointType bpType = (BreakpointType)(byte)((param >> 8) & 0x0F); BreakpointType bpType = (BreakpointType)(byte)((param >> 8) & 0x0F);
UInt16 bpAddress = (UInt16)(param >> 16); UInt16 bpAddress = (UInt16)(param >> 16);
ReadOnlyCollection<Breakpoint> breakpoints = BreakpointManager.Breakpoints; int regularBpCount = BreakpointManager.Breakpoints.Count;
List<Breakpoint> breakpoints = BreakpointManager.GetAllBreakpoints();
if(breakpointId >= 0 && breakpointId < breakpoints.Count) { if(breakpointId >= 0 && breakpointId < breakpoints.Count) {
Breakpoint bp = breakpoints[breakpointId]; Breakpoint bp = breakpoints[breakpointId];
if(breakpointId < regularBpCount) {
if(bpType != BreakpointType.Global) { if(bpType != BreakpointType.Global) {
message += ": " + ResourceHelper.GetEnumText(bpType) + " ($" + bpAddress.ToString("X4") + ":$" + bpValue.ToString("X2") + ")"; message += ": " + ResourceHelper.GetEnumText(bpType) + " ($" + bpAddress.ToString("X4") + ":$" + bpValue.ToString("X2") + ")";
} }
@ -465,6 +467,9 @@ namespace Mesen.GUI.Debugger
message += Environment.NewLine + cond; message += Environment.NewLine + cond;
} }
} }
} else {
message = "Assert failed: " + bp.Condition.Substring(2, bp.Condition.Length - 3);
}
} }
} else if(source == BreakSource.BreakOnUninitMemoryRead) { } else if(source == BreakSource.BreakOnUninitMemoryRead) {
UInt16 address = (UInt16)(param >> 16); UInt16 address = (UInt16)(param >> 16);