From fac849315ff420358753dca8bc480d01dac0e47c Mon Sep 17 00:00:00 2001 From: Sour Date: Thu, 31 Jan 2019 19:45:25 -0500 Subject: [PATCH] Debugger: Watch - Added format specifiers + Added help icon to watch window --- Docs/content/debugging/Debugger.md | 50 ++++- Docs/static/images/WatchList.png | Bin 7212 -> 4842 bytes Docs/static/images/WatchWindow.png | Bin 0 -> 15237 bytes GUI.NET/Config/DebugInfo.cs | 2 +- .../Debugger/Controls/ctrlWatch.Designer.cs | 184 ++++++++++++++++-- GUI.NET/Debugger/Controls/ctrlWatch.cs | 137 +++++++++++-- GUI.NET/Debugger/WatchManager.cs | 105 ++++++++-- GUI.NET/Debugger/frmDebugger.cs | 7 +- GUI.NET/Debugger/frmWatchWindow.Designer.cs | 16 ++ GUI.NET/Debugger/frmWatchWindow.cs | 2 + 10 files changed, 449 insertions(+), 54 deletions(-) create mode 100644 Docs/static/images/WatchWindow.png diff --git a/Docs/content/debugging/Debugger.md b/Docs/content/debugging/Debugger.md index 62e0f5c6..d07078ea 100644 --- a/Docs/content/debugging/Debugger.md +++ b/Docs/content/debugging/Debugger.md @@ -105,24 +105,33 @@ While execution is paused, most fields are editable. Altering the value of any f This section lets you force certain buttons to be held down on the NES' controller. This is often useful when trying to debug input-related code. Clicking on a button on the mini NES controllers will toggle its state - green buttons are currently being held down. -## Watch Window ## +## Watch List/Window ##
- + Watch Window
-The watch window allows you to evaluate expression and see their value. Mesen supports complex expressions in C/C++ style syntax. +
+ + Watch List +
-**To add a new watch expression**, click on the last empty line in the list and start typing. -**To edit a watch expression**, double-click on it and start typing. -**To switch between hex and decimal**, right-click in the watch and toggle the **Hexadecimal Display** option. +The watch window and watch list allow you to evaluate expression and see their value. The `Watch Window` is a standalone window that can be resized and moved independently from everything else, whereas the `Watch List` is a part of the main debugger window for quick access to watch expressions. + +**To add a new watch expression**, click on the last empty line in the list to enter edit mode. +**To edit a watch expression**, double-click on it to enter edit mode. + +You can use the right-click context menu to delete or move entries, as well as select formatting options. +An import and export feature is also available to save/load watch expressions from a plain text file. ### Syntax ### -The used syntax is identical to C/C++ syntax (e.g && for and, || for or, etc.) and have the same operator precedence as C/C++. +The syntax is identical to C/C++ (e.g `&&` for AND, `||` for OR) and uses the same operator precedence as well. -**Note:** Use the $ prefix to denote hexadecimal values. +{{% notice tip %}} +Use the $ prefix to denote hexadecimal values (e.g: `$FF`) or the % prefix for binary values (e.g: `%1101`) +{{% /notice %}} #### Special values #### @@ -153,6 +162,30 @@ The following "variables" can be used in both the watch window and contional bre * **SpriteOverflow**: true if the PPU's "Sprite Overflow" flag is set * **VerticalBlank**: true if the PPU's "Vertical Blank" flag is set +#### Formatting #### + +It is possible to customize the format of each entry by adding a suffix to the expression. +Suffixes contain a single letter and are optionally followed by a number indicating the number of bytes expected in the return value (up to 4). + +The available suffixes are: + +* `S` - Signed decimal value +* `U` - Unsigned decimal value +* `H` - Hexadecimal +* `B` - Binary + +For example, suffixing an expression with: + +* `, H2` will display the result as a 2-byte hexadecimal value (e.g: `26, H2` will display as `$001A`) +* `, B` will display the result as a binary value (e.g: `141,B` will display as `%10001101`) +* `, S2` will display the result as a 16-bit signed decimal value (e.g: `$FE4F, S2` will display as `-433`) +* `, U` will display the result as an unsigned decimal value (e.g: `180, U` will display as `180`) + +You can select the default format to use for entries without prefixes by right-clicking and choosing between: + +* **Decimal Display** (equivalent to `S4` to all entries - displays the result as 32-bit signed decimal values) +* **Hexadecimal Display** (equivalent to `H1` to all entries) +* **Binary Display** (equivalent to `B1` to all entries) #### Usage Examples #### ``` @@ -162,6 +195,7 @@ scanline == 10 && (cycle >= 55 && cycle <= 100) x == [$150] || y == [10] [[$15] + y] //Reads the value at address $15, adds Y to it and reads the value at the resulting address. {$FFFA} //Returns the NMI handler's address. +[$14] | ([$15] << 8), H2 //Display the value of the 2-byte variable stored at $14 in hexadecimal format. ``` **Using labels** diff --git a/Docs/static/images/WatchList.png b/Docs/static/images/WatchList.png index 6dbf7a07f3c39f9faf93c7ce3428551ce9956237..88cd02ea3faf3c658b1eaac66ab293e01f9a20a1 100644 GIT binary patch literal 4842 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 literal 7212 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 0) { @@ -90,13 +99,12 @@ namespace Mesen.GUI.Debugger public void UpdateWatch(bool autoResizeColumns = true) { - List watchContent = WatchManager.GetWatchContent(mnuHexDisplay.Checked, _previousValues); + List watchContent = WatchManager.GetWatchContent(_previousValues); _previousValues = watchContent; - int currentSelection = lstWatch.FocusedItem?.Selected == true ? (lstWatch.FocusedItem?.Index ?? -1) : -1; - bool updating = false; if(watchContent.Count != lstWatch.Items.Count - 1) { + int currentFocus = lstWatch.FocusedItem?.Selected == true ? (lstWatch.FocusedItem?.Index ?? -1) : -1; lstWatch.BeginUpdate(); lstWatch.Items.Clear(); @@ -111,6 +119,9 @@ namespace Mesen.GUI.Debugger lastItem.SubItems.Add(""); itemsToAdd.Add(lastItem); lstWatch.Items.AddRange(itemsToAdd.ToArray()); + if(currentFocus >= 0 && currentFocus < lstWatch.Items.Count) { + SetSelectedItem(currentFocus); + } updating = true; } else { for(int i = 0; i < watchContent.Count; i++) { @@ -144,19 +155,8 @@ namespace Mesen.GUI.Debugger } lstWatch.EndUpdate(); } - - if(currentSelection >= 0 && lstWatch.Items.Count > currentSelection) { - SetSelectedItem(currentSelection); - } } - private void mnuHexDisplay_Click(object sender, EventArgs e) - { - ConfigManager.Config.DebugInfo.HexDisplay = this.mnuHexDisplay.Checked; - ConfigManager.ApplyChanges(); - UpdateWatch(); - } - private void lstWatch_SelectedIndexChanged(object sender, EventArgs e) { mnuRemoveWatch.Enabled = lstWatch.SelectedItems.Count >= 1; @@ -165,6 +165,11 @@ namespace Mesen.GUI.Debugger private void UpdateActions() { + mnuHexDisplay.Checked = ConfigManager.Config.DebugInfo.WatchFormat == WatchFormatStyle.Hex; + mnuDecimalDisplay.Checked = ConfigManager.Config.DebugInfo.WatchFormat == WatchFormatStyle.Signed; + mnuBinaryDisplay.Checked = ConfigManager.Config.DebugInfo.WatchFormat == WatchFormatStyle.Binary; + mnuRowDisplayFormat.Enabled = lstWatch.SelectedItems.Count > 0; + mnuEditInMemoryViewer.Enabled = false; mnuViewInDisassembly.Enabled = false; mnuMoveUp.Enabled = false; @@ -396,5 +401,107 @@ namespace Mesen.GUI.Debugger } } } + + private void mnuHexDisplay_Click(object sender, EventArgs e) + { + ConfigManager.Config.DebugInfo.WatchFormat = WatchFormatStyle.Hex; + ConfigManager.ApplyChanges(); + UpdateWatch(); + } + + private void mnuDecimalDisplay_Click(object sender, EventArgs e) + { + ConfigManager.Config.DebugInfo.WatchFormat = WatchFormatStyle.Signed; + ConfigManager.ApplyChanges(); + UpdateWatch(); + } + + private void mnuBinaryDisplay_Click(object sender, EventArgs e) + { + ConfigManager.Config.DebugInfo.WatchFormat = WatchFormatStyle.Binary; + ConfigManager.ApplyChanges(); + UpdateWatch(); + } + + private string GetFormatString(WatchFormatStyle format, int byteLength) + { + string formatString = ", "; + switch(format) { + case WatchFormatStyle.Binary: formatString += "B"; break; + case WatchFormatStyle.Hex: formatString += "H"; break; + case WatchFormatStyle.Signed: formatString += "S"; break; + case WatchFormatStyle.Unsigned: formatString += "U"; break; + default: throw new Exception("Unsupported type"); + } + if(byteLength > 1) { + formatString += byteLength.ToString(); + } + return formatString; + } + + private void SetSelectionFormat(WatchFormatStyle format, int byteLength) + { + SetSelectionFormat(GetFormatString(format, byteLength)); + } + + private void SetSelectionFormat(string formatString) + { + List entries = WatchManager.WatchEntries; + foreach(int i in lstWatch.SelectedIndices) { + if(i < entries.Count) { + Match match = WatchManager.FormatSuffixRegex.Match(entries[i]); + if(match.Success) { + WatchManager.UpdateWatch(i, match.Groups[1].Value + formatString); + } else { + WatchManager.UpdateWatch(i, entries[i] + formatString); + } + } + } + } + + private void mnuRowBinary_Click(object sender, EventArgs e) + { + SetSelectionFormat(WatchFormatStyle.Binary, 1); + } + + private void mnuRowHex1_Click(object sender, EventArgs e) + { + SetSelectionFormat(WatchFormatStyle.Hex, 1); + } + + private void mnuRowHex2_Click(object sender, EventArgs e) + { + SetSelectionFormat(WatchFormatStyle.Hex, 2); + } + + private void mnuRowHex3_Click(object sender, EventArgs e) + { + SetSelectionFormat(WatchFormatStyle.Hex, 3); + } + + private void mnuRowSigned1_Click(object sender, EventArgs e) + { + SetSelectionFormat(WatchFormatStyle.Signed, 1); + } + + private void mnuRowSigned2_Click(object sender, EventArgs e) + { + SetSelectionFormat(WatchFormatStyle.Signed, 2); + } + + private void mnuRowSigned3_Click(object sender, EventArgs e) + { + SetSelectionFormat(WatchFormatStyle.Unsigned, 1); + } + + private void mnuRowUnsigned_Click(object sender, EventArgs e) + { + SetSelectionFormat(WatchFormatStyle.Unsigned, 1); + } + + private void mnuRowClearFormat_Click(object sender, EventArgs e) + { + SetSelectionFormat(""); + } } } diff --git a/GUI.NET/Debugger/WatchManager.cs b/GUI.NET/Debugger/WatchManager.cs index 37b01a71..449c55bf 100644 --- a/GUI.NET/Debugger/WatchManager.cs +++ b/GUI.NET/Debugger/WatchManager.cs @@ -1,4 +1,5 @@ -using System; +using Mesen.GUI.Config; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -13,6 +14,7 @@ namespace Mesen.GUI.Debugger public static event EventHandler WatchChanged; private static List _watchEntries = new List(); private static Regex _arrayWatchRegex = new Regex(@"\[((\$[0-9A-Fa-f]+)|(\d+)|([@_a-zA-Z0-9]+))\s*,\s*(\d+)\]", RegexOptions.Compiled); + public static Regex FormatSuffixRegex = new Regex(@"^(.*),\s*([B|H|S|U])([\d]){0,1}$", RegexOptions.Compiled); public static List WatchEntries { @@ -24,28 +26,39 @@ namespace Mesen.GUI.Debugger } } - public static List GetWatchContent(bool useHex, List previousValues) + public static List GetWatchContent(List previousValues) { + WatchFormatStyle defaultStyle = ConfigManager.Config.DebugInfo.WatchFormat; + int defaultByteLength = 1; + if(defaultStyle == WatchFormatStyle.Signed) { + defaultByteLength = 4; + } + var list = new List(); for(int i = 0; i < _watchEntries.Count; i++) { string expression = _watchEntries[i].Trim(); string newValue = ""; EvalResultType resultType; + string exprToEvaluate = expression; + WatchFormatStyle style = defaultStyle; + int byteLength = defaultByteLength; + if(expression.StartsWith("{") && expression.EndsWith("}")) { + //Default to 2-byte values when using {} syntax + byteLength = 2; + } + + ProcessFormatSpecifier(ref exprToEvaluate, ref style, ref byteLength); + bool forceHasChanged = false; Match match = _arrayWatchRegex.Match(expression); if(match.Success) { //Watch expression matches the array display syntax (e.g: [$300,10] = display 10 bytes starting from $300) - newValue = ProcessArrayDisplaySyntax(useHex, ref forceHasChanged, match); + newValue = ProcessArrayDisplaySyntax(style, ref forceHasChanged, match); } else { - Int32 result = InteropEmu.DebugEvaluateExpression(expression, out resultType, true); + Int32 result = InteropEmu.DebugEvaluateExpression(exprToEvaluate, out resultType, true); switch(resultType) { - case EvalResultType.Numeric: - //When using {$00} syntax to show the value of a word, always display 4 hex characters. - bool displayAsWord = expression.StartsWith("{") && expression.EndsWith("}"); - newValue = useHex ? ("$" + result.ToString(displayAsWord ? "X4" : "X2")) : result.ToString(); - break; - + case EvalResultType.Numeric: newValue = FormatValue(result, style, byteLength); break; case EvalResultType.Boolean: newValue = result == 0 ? "false" : "true"; break; case EvalResultType.Invalid: newValue = ""; forceHasChanged = true; break; case EvalResultType.DivideBy0: newValue = ""; forceHasChanged = true; break; @@ -59,7 +72,67 @@ namespace Mesen.GUI.Debugger return list; } - private static string ProcessArrayDisplaySyntax(bool useHex, ref bool forceHasChanged, Match match) + private static string FormatValue(int value, WatchFormatStyle style, int byteLength) + { + switch(style) { + case WatchFormatStyle.Unsigned: return ((UInt32)value).ToString(); + case WatchFormatStyle.Hex: return "$" + value.ToString("X" + byteLength * 2); + case WatchFormatStyle.Binary: + string binary = Convert.ToString(value, 2).PadLeft(byteLength * 8, '0'); + for(int i = binary.Length - 4; i > 0; i -= 4) { + binary = binary.Insert(i, "."); + } + return "%" + binary; + case WatchFormatStyle.Signed: + int bitCount = byteLength * 8; + if(bitCount < 32) { + if(((value >> (bitCount - 1)) & 0x01) == 0x01) { + //Negative value + return (value | (-(1 << bitCount))).ToString(); + } else { + //Position value + return value.ToString(); + } + } else { + return value.ToString(); + } + + default: throw new Exception("Unsupported format"); + } + } + + public static bool IsArraySyntax(string expression) + { + return _arrayWatchRegex.IsMatch(expression); + } + + private static bool ProcessFormatSpecifier(ref string expression, ref WatchFormatStyle style, ref int byteLength) + { + Match match = WatchManager.FormatSuffixRegex.Match(expression); + if(!match.Success) { + return false; + } + + string format = match.Groups[2].Value.ToUpperInvariant(); + switch(format[0]) { + case 'S': style = WatchFormatStyle.Signed; break; + case 'H': style = WatchFormatStyle.Hex; break; + case 'B': style = WatchFormatStyle.Binary; break; + case 'U': style = WatchFormatStyle.Unsigned; break; + default: throw new Exception("Invalid format"); + } + + if(match.Groups[3].Success) { + byteLength = Math.Max(Math.Min(Int32.Parse(match.Groups[3].Value), 4), 1); + } else { + byteLength = 1; + } + + expression = match.Groups[1].Value; + return true; + } + + private static string ProcessArrayDisplaySyntax(WatchFormatStyle style, ref bool forceHasChanged, Match match) { string newValue; int address; @@ -81,7 +154,7 @@ namespace Mesen.GUI.Debugger List values = new List(elemCount); for(int j = address, end = address + elemCount; j < end; j++) { int memValue = InteropEmu.DebugGetMemoryValue(DebugMemoryType.CpuMemory, (uint)j); - values.Add(useHex ? memValue.ToString("X2") : memValue.ToString()); + values.Add(FormatValue(memValue, style, 1)); } newValue = string.Join(" ", values); } else { @@ -141,4 +214,12 @@ namespace Mesen.GUI.Debugger public string Value { get; set; } public bool HasChanged { get; set; } } + + public enum WatchFormatStyle + { + Unsigned, + Signed, + Hex, + Binary + } } diff --git a/GUI.NET/Debugger/frmDebugger.cs b/GUI.NET/Debugger/frmDebugger.cs index e66d1c1a..76b5a94a 100644 --- a/GUI.NET/Debugger/frmDebugger.cs +++ b/GUI.NET/Debugger/frmDebugger.cs @@ -167,12 +167,7 @@ namespace Mesen.GUI.Debugger LastCodeWindow = ctrlDebuggerCode; - this.toolTip.SetToolTip(this.picWatchHelp, - frmBreakpoint.GetConditionTooltip(true) + Environment.NewLine + Environment.NewLine + - "Additionally, the watch window supports a syntax to display X bytes starting from a specific address. e.g:" + Environment.NewLine + - "[$10, 16]: Display 16 bytes starting from address $10" + Environment.NewLine + - "[MyLabel, 4]: Display 4 bytes starting from the address the specified label (MyLabel) refers to" - ); + this.toolTip.SetToolTip(this.picWatchHelp, ctrlWatch.GetTooltipText()); _notifListener = new InteropEmu.NotificationListener(ConfigManager.Config.DebugInfo.DebugConsoleId); _notifListener.OnNotification += _notifListener_OnNotification; diff --git a/GUI.NET/Debugger/frmWatchWindow.Designer.cs b/GUI.NET/Debugger/frmWatchWindow.Designer.cs index 78159151..0bd59cc5 100644 --- a/GUI.NET/Debugger/frmWatchWindow.Designer.cs +++ b/GUI.NET/Debugger/frmWatchWindow.Designer.cs @@ -28,6 +28,8 @@ private void InitializeComponent() { this.ctrlWatch = new Mesen.GUI.Debugger.ctrlWatch(); + this.picWatchHelp = new System.Windows.Forms.PictureBox(); + ((System.ComponentModel.ISupportInitialize)(this.picWatchHelp)).BeginInit(); this.SuspendLayout(); // // ctrlWatch @@ -38,15 +40,28 @@ this.ctrlWatch.Size = new System.Drawing.Size(317, 322); this.ctrlWatch.TabIndex = 0; // + // picWatchHelp + // + this.picWatchHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.picWatchHelp.Image = global::Mesen.GUI.Properties.Resources.Help; + this.picWatchHelp.Location = new System.Drawing.Point(297, 4); + this.picWatchHelp.Name = "picWatchHelp"; + this.picWatchHelp.Size = new System.Drawing.Size(16, 16); + this.picWatchHelp.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.picWatchHelp.TabIndex = 2; + this.picWatchHelp.TabStop = false; + // // frmWatchWindow // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(317, 322); + this.Controls.Add(this.picWatchHelp); this.Controls.Add(this.ctrlWatch); this.MinimumSize = new System.Drawing.Size(248, 137); this.Name = "frmWatchWindow"; this.Text = "Watch Window"; + ((System.ComponentModel.ISupportInitialize)(this.picWatchHelp)).EndInit(); this.ResumeLayout(false); } @@ -54,5 +69,6 @@ #endregion private ctrlWatch ctrlWatch; + private System.Windows.Forms.PictureBox picWatchHelp; } } \ No newline at end of file diff --git a/GUI.NET/Debugger/frmWatchWindow.cs b/GUI.NET/Debugger/frmWatchWindow.cs index 7d7c62a4..2a4a5a15 100644 --- a/GUI.NET/Debugger/frmWatchWindow.cs +++ b/GUI.NET/Debugger/frmWatchWindow.cs @@ -26,6 +26,8 @@ namespace Mesen.GUI.Debugger this.Size = ConfigManager.Config.DebugInfo.WatchWindowSize; this.Location = ConfigManager.Config.DebugInfo.WatchWindowLocation; } + + this.toolTip.SetToolTip(picWatchHelp, ctrlWatch.GetTooltipText()); } }