mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
Merge remote-tracking branch 'origin/Ghidra_10.1'
This commit is contained in:
commit
567e7cc21d
59 changed files with 3191 additions and 678 deletions
|
@ -7,14 +7,16 @@
|
||||||
|
|
||||||
<BODY>
|
<BODY>
|
||||||
|
|
||||||
<H1 align="center">Ghidra 10.1 Change History (November 2021)</H1>
|
<H1 align="center">Ghidra 10.1 Change History (December 2021)</H1>
|
||||||
<blockquote><p><u>New Features</u></p>
|
<blockquote><p><u>New Features</u></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><I>Build</I>. Ghidra now builds on 64-bit Linux ARM and macOS M1 platforms. (GP-1106, Issue #3197)</li>
|
<li><I>Build</I>. Ghidra now builds on 64-bit Linux ARM and macOS M1 platforms. (GP-1106, Issue #3197)</li>
|
||||||
<li><I>Build</I>. Native binaries for the current platform can now be built/rebuilt from within a release using the <B>support/buildNatives(.bat)</B> script. Please see the "Building Ghidra Native Components" section of the Installation Guide for additional information. (GP-1209, Issue #3387)</li>
|
<li><I>Build</I>. Native binaries for the current platform can now be built/rebuilt from within a release using the <B>support/buildNatives(.bat)</B> script. Please see the "Building Ghidra Native Components" section of the Installation Guide for additional information. (GP-1209, Issue #3387)</li>
|
||||||
<li><I>Data Types</I>. Added encoding methods to DataType. (GP-1265)</li>
|
<li><I>Data Types</I>. DataType API: Added <code>encodeValue</code> and <code>encodeRepresentation</code> methods which facilitate patching. (GP-1265)</li>
|
||||||
<li><I>Debugger</I>. Added Memory view (raw bytes) to the Debugger. (GP-80)</li>
|
<li><I>Debugger</I>. Added Memory view (raw bytes) to the Debugger. (GP-80)</li>
|
||||||
<li><I>Debugger</I>. Added new agent for lldb on macOS and Linux. (GP-1005, Issue #2591, #2967)</li>
|
<li><I>Debugger</I>. Added new agent for LLDB on macOS and Linux. (GP-1005, Issue #2591, #2967)</li>
|
||||||
|
<li><I>Debugger</I>. Added <B>Copy Into Current Program</B> and <B>Copy Into New Program</B> actions to Debugger. (GP-1214)</li>
|
||||||
|
<li><I>Debugger</I>. Added <B>Compare</B> action to Dynamic Listing to compare points in time. (GP-1222)</li>
|
||||||
<li><I>Debugger</I>. Added Events/Exceptions to Objects View. (GP-1288, Issue #3049)</li>
|
<li><I>Debugger</I>. Added Events/Exceptions to Objects View. (GP-1288, Issue #3049)</li>
|
||||||
<li><I>Debugger:Emulator</I>. Added <B>Emulate Program</B> and <B>Add Emulated Thread</B> actions for loading a program into a purely emulated trace. (GP-660)</li>
|
<li><I>Debugger:Emulator</I>. Added <B>Emulate Program</B> and <B>Add Emulated Thread</B> actions for loading a program into a purely emulated trace. (GP-660)</li>
|
||||||
<li><I>Decompiler</I>. Added support for <code><B>else if</B></code> syntax in Decompiler output. (GP-1172, Issue #1609)</li>
|
<li><I>Decompiler</I>. Added support for <code><B>else if</B></code> syntax in Decompiler output. (GP-1172, Issue #1609)</li>
|
||||||
|
@ -24,23 +26,29 @@
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<blockquote><p><u>Improvements</u></p>
|
<blockquote><p><u>Improvements</u></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><I>Analysis</I>. The ___chkstk_ms() function is now properly recognized and handled. (GP-1347, Issue #1888, #1889)</li>
|
<li><I>Analysis</I>. The called <code>___chkstk_ms()</code> function is now properly recognized and handled with a call fixup for windows x86-64. (GP-1347, Issue #1888, #1889)</li>
|
||||||
<li><I>Analysis</I>. Added support for Objective-C small methods. (GP-1397, Issue #2719, #2732)</li>
|
<li><I>Analysis</I>. Added support for Objective-C small methods. (GP-1397, Issue #2719, #2732)</li>
|
||||||
<li><I>Analysis</I>. Several memory usage issues with constant propagation for very large functions have been fixed. These fixes have also resulted in an average 10-20 percent time savings for constant propagation and stack analysis. (GP-1418, Issue #3508)</li>
|
<li><I>Analysis</I>. Fixed several memory usage issues with constant propagation for very large functions, resulting in an average 10-20 percent time savings for constant propagation and stack analysis. (GP-1418, Issue #3508)</li>
|
||||||
<li><I>API</I>. Updated API methods of the DataTypeChooserDialog. (GP-1349, Issue #3140)</li>
|
<li><I>API</I>. Updated API methods of the DataTypeChooserDialog. (GP-1349, Issue #3140)</li>
|
||||||
<li><I>Basic Infrastructure</I>. Symbol performance in Ghidra was significantly improved. Specifically, new database indexes were created to improve finding primary symbols as well as improving lookups by combinations of name, namespace, and address. (GP-1082)</li>
|
<li><I>Basic Infrastructure</I>. Symbol performance in Ghidra was significantly improved. Specifically, new database indexes were created to improve finding primary symbols as well as improving lookups by combinations of name, namespace, and address. (GP-1082)</li>
|
||||||
<li><I>Basic Infrastructure</I>. Added optional columns in the Functions table for several boolean-valued function attributes. (GP-1393)</li>
|
<li><I>Basic Infrastructure</I>. Added optional columns in the Functions table for several boolean-valued function attributes. (GP-1393)</li>
|
||||||
<li><I>Build</I>. Extension builds can now declare jar dependencies from standard Gradle repositories such as Maven Central. (GP-1144, Issue #2219, #2226)</li>
|
<li><I>Build</I>. Extension builds can now declare jar dependencies from standard Gradle repositories such as Maven Central. (GP-1144, Issue #2219, #2226)</li>
|
||||||
|
<li><I>Build</I>. Increased minimum supported Gradle version from 6.0 to 6.4. (GP-1521, Issue #3650)</li>
|
||||||
<li><I>Data Types</I>. Added support for zero-element arrays and zero-length components within structures and unions. Eliminated flex-array API methods and added/improved other Structure methods to handle multiple components which share the same offset. (GP-943)</li>
|
<li><I>Data Types</I>. Added support for zero-element arrays and zero-length components within structures and unions. Eliminated flex-array API methods and added/improved other Structure methods to handle multiple components which share the same offset. (GP-943)</li>
|
||||||
<li><I>Data Types</I>. Added the ability to set comments on enum values. (GP-1316, Issue #1680, #2421)</li>
|
<li><I>Data Types</I>. Added the ability to set comments on enum values. (GP-1316, Issue #1680, #2421)</li>
|
||||||
|
<li><I>Data Types</I>. Updated Windows and generic clib data type archives to take advantage of improved CParser including changes to handle sizeof() correctly. (GP-1551, Issue #615)</li>
|
||||||
<li><I>Debugger</I>. Respond to CLI-driven memory changes in dbgeng. (GP-853)</li>
|
<li><I>Debugger</I>. Respond to CLI-driven memory changes in dbgeng. (GP-853)</li>
|
||||||
<li><I>Debugger</I>. User can now override the Debugger's processor selection when manually activating the <B>Record</B> (<B>R</B>) action. (GP-1233)</li>
|
<li><I>Debugger</I>. User can now override the Debugger's processor selection when manually activating the <B>Record</B> (<B>R</B>) action. (GP-1233)</li>
|
||||||
<li><I>Debugger</I>. User can now double-click in Listing margin to toggle breakpoints. (GP-1395)</li>
|
<li><I>Debugger</I>. User can now double-click in Listing margin to toggle breakpoints. (GP-1395)</li>
|
||||||
<li><I>Debugger</I>. Adjusted alignment of <B>Description</B> tag in Debugger's <B>Connect</B> dialog. (GP-1416)</li>
|
<li><I>Debugger</I>. Adjusted alignment of <B>Description</B> tag in Debugger's <B>Connect</B> dialog. (GP-1416)</li>
|
||||||
<li><I>Debugger:Emulator</I>. Added more accessor methods to PcodeThread, Machine, Executor, and similar classes. (GP-1223)</li>
|
<li><I>Debugger:Emulator</I>. Added more accessor methods to PcodeThread, Machine, Executor, and similar classes. (GP-1223)</li>
|
||||||
<li><I>Debugger:Emulator</I>. Added more accessor methods to PairedCodeArithmetic, ExecutorState, ExecutorStatePiece, and similar classes. (GP-1224)</li>
|
<li><I>Debugger:Emulator</I>. Added more accessor methods to PairedCodeArithmetic, ExecutorState, ExecutorStatePiece, and similar classes. (GP-1224)</li>
|
||||||
|
<li><I>Debugger:Emulator</I>. Emulator now responds better to memory and register edits. (GP-1486)</li>
|
||||||
|
<li><I>Debugger:Emulator</I>. Registers window can now modify emulated register values. (GP-1530)</li>
|
||||||
<li><I>Debugger:GDB</I>. GDB manager handles <code>=cmd-param-changed</code> events. (GP-1330)</li>
|
<li><I>Debugger:GDB</I>. GDB manager handles <code>=cmd-param-changed</code> events. (GP-1330)</li>
|
||||||
<li><I>Debugger:GDB</I>. Ported GDB's SSH connector to JSch. (GP-1387)</li>
|
<li><I>Debugger:GDB</I>. Ported GDB's SSH connector to JSch. (GP-1387)</li>
|
||||||
|
<li><I>Debugger:LLDB</I>. Improved build scripts for LLDB Java language bindings. (GP-1477)</li>
|
||||||
|
<li><I>Debugger:Memory</I>. Added <B>Force Full View</B> override toggle to Debugger's <B>Regions</B> window. (GP-1447)</li>
|
||||||
<li><I>Debugger:Stack</I>. Fixed various <code>NullPointerExceptions</code> among the Debugger Stack and Threads windows. (GP-1475)</li>
|
<li><I>Debugger:Stack</I>. Fixed various <code>NullPointerExceptions</code> among the Debugger Stack and Threads windows. (GP-1475)</li>
|
||||||
<li><I>Debugger:Trace</I>. Trace API now supports <B>Overlay</B> spaces. (GP-484)</li>
|
<li><I>Debugger:Trace</I>. Trace API now supports <B>Overlay</B> spaces. (GP-484)</li>
|
||||||
<li><I>Decompiler</I>. Added the <B>Rename Label</B> Decompiler action to allow label name editing. (GP-1195, Issue #1751)</li>
|
<li><I>Decompiler</I>. Added the <B>Rename Label</B> Decompiler action to allow label name editing. (GP-1195, Issue #1751)</li>
|
||||||
|
@ -66,7 +74,7 @@
|
||||||
<li><I>GUI</I>. Added an option to group the XRef field in the Listing by function. (GP-1093, Issue #1305)</li>
|
<li><I>GUI</I>. Added an option to group the XRef field in the Listing by function. (GP-1093, Issue #1305)</li>
|
||||||
<li><I>GUI</I>. Symbol tree has been changed to improve its behavior in the presence of large scale changes such as analysis, loading PDB, etc. It now will auto-close the label or function category if the internal organization becomes too much out of balance. This will also improve the analysis performance when the root category nodes are closed. (GP-1198)</li>
|
<li><I>GUI</I>. Symbol tree has been changed to improve its behavior in the presence of large scale changes such as analysis, loading PDB, etc. It now will auto-close the label or function category if the internal organization becomes too much out of balance. This will also improve the analysis performance when the root category nodes are closed. (GP-1198)</li>
|
||||||
<li><I>GUI</I>. Improved composite interior selection of components with shared offset such as bit-fields. Previous behavior was forcing selection of multiple components. (GP-1261)</li>
|
<li><I>GUI</I>. Improved composite interior selection of components with shared offset such as bit-fields. Previous behavior was forcing selection of multiple components. (GP-1261)</li>
|
||||||
<li><I>GUI</I>. Fixed exception due to the Patch action incorrectly being added to the Function Graph context menu. (GP-1334, Issue #3288)</li>
|
<li><I>GUI</I>. Fixed ClassCastException due to the Patch action incorrectly being added to the Function Graph context menu. (GP-1334, Issue #3288)</li>
|
||||||
<li><I>GUI</I>. Updated the Search Memory dialog to allow the user to enter a single wildcard character to search for any byte value. Previously, two consecutive wildcard characters were required. (GP-1358, Issue #3351)</li>
|
<li><I>GUI</I>. Updated the Search Memory dialog to allow the user to enter a single wildcard character to search for any byte value. Previously, two consecutive wildcard characters were required. (GP-1358, Issue #3351)</li>
|
||||||
<li><I>GUI</I>. Updated auto-comments to show user-defined repeatable comments from the reference destination. (GP-1361, Issue #2475)</li>
|
<li><I>GUI</I>. Updated auto-comments to show user-defined repeatable comments from the reference destination. (GP-1361, Issue #2475)</li>
|
||||||
<li><I>GUI</I>. Changed the Context column to allow for filtering of special characters in the results table of the <B>Find Uses of</B> action. (GP-1370, Issue #3473)</li>
|
<li><I>GUI</I>. Changed the Context column to allow for filtering of special characters in the results table of the <B>Find Uses of</B> action. (GP-1370, Issue #3473)</li>
|
||||||
|
@ -77,12 +85,13 @@
|
||||||
<li><I>GUI</I>. Updated the Comments Dialog to allow the <B>Shift-Enter</B> keystroke to insert a newline at the cursor position. (GP-1428, Issue #3548)</li>
|
<li><I>GUI</I>. Updated the Comments Dialog to allow the <B>Shift-Enter</B> keystroke to insert a newline at the cursor position. (GP-1428, Issue #3548)</li>
|
||||||
<li><I>GUI</I>. Updated the Symbol Table to allow users to enter optional namespaces when editing a symbol name. (GP-1430)</li>
|
<li><I>GUI</I>. Updated the Symbol Table to allow users to enter optional namespaces when editing a symbol name. (GP-1430)</li>
|
||||||
<li><I>GUI</I>. Fixed issue with shared actions across windows sometimes getting the wrong (non-focused) context. This was mostly related to windows with snapshot components. (GP-1440)</li>
|
<li><I>GUI</I>. Fixed issue with shared actions across windows sometimes getting the wrong (non-focused) context. This was mostly related to windows with snapshot components. (GP-1440)</li>
|
||||||
<li><I>GUI</I>. Fixed issue when attempting to rename a datatype that has the same name as a category in the same parent cateogory. The rename would attempt to rename the category instead of the datatype. (GP-1445)</li>
|
<li><I>GUI</I>. Updated the Data Types context menu to include all actions when showing the menu from the keyboard via <B>Shift-F10</B>. (GP-1566, Issue #3678)</li>
|
||||||
<li><I>Importer</I>. Added support for new Mach-O load commands and file types. (GP-398, Issue #2487, #3572)</li>
|
<li><I>Importer</I>. Added support for new Mach-O load commands and file types. (GP-398, Issue #2487, #3572)</li>
|
||||||
<li><I>Importer</I>. Added method to Memory to find addresses where a specific byte from a loaded FileBytes object is used in memory. (GP-1166)</li>
|
<li><I>Importer</I>. Added method to Memory to find addresses where a specific byte from a loaded FileBytes object is used in memory. (GP-1166)</li>
|
||||||
<li><I>Importer:Mach-O</I>. The Mach-O loader now outputs a warning when it encounters encrypted sections. (GP-1406, Issue #1935)</li>
|
<li><I>Importer:Mach-O</I>. The Mach-O loader now outputs a warning when it encounters encrypted sections. (GP-1406, Issue #1935)</li>
|
||||||
<li><I>Importer:PE</I>. Added support for long section names (e.g., "/1234" as offset in the string table) in PE binaries. (GP-1177, Issue #1267)</li>
|
<li><I>Importer:Mach-O</I>. Added support for the new iOS 15 and macOS Monterey dyld_shared_cache format. (GP-1524, Issue #3345, #3666)</li>
|
||||||
<li><I>Multi-User</I>. Upgraded YAJSW to 13.01. Ghidra Server can now run with JDK 17. (GP-1266, Issue #3406)</li>
|
<li><I>Importer:PE</I>. Added support for long section names (e.g., "/1234" indicates offset into string table where actual section name is found) in PE binaries. (GP-1177, Issue #1267)</li>
|
||||||
|
<li><I>Multi-User</I>. Upgraded YAJSW to 13.01-beta. Ghidra Server can now run with JDK 17. (GP-1266, Issue #3406)</li>
|
||||||
<li><I>PDB</I>. Improved processing time on huge PDBs, especially when many labels are seen at the same address, such as with Identical COMDAT Folding. This change also allows some additional valid labels to be applied at these addresses. (GP-1298)</li>
|
<li><I>PDB</I>. Improved processing time on huge PDBs, especially when many labels are seen at the same address, such as with Identical COMDAT Folding. This change also allows some additional valid labels to be applied at these addresses. (GP-1298)</li>
|
||||||
<li><I>Processors</I>. Added pcodetests for ARM version 5, which does not support thumb mode. (GP-1078)</li>
|
<li><I>Processors</I>. Added pcodetests for ARM version 5, which does not support thumb mode. (GP-1078)</li>
|
||||||
<li><I>Processors</I>. Added 65C02 opcodes to the 6502 processor. (GP-1112, Issue #1261, #3170)</li>
|
<li><I>Processors</I>. Added 65C02 opcodes to the 6502 processor. (GP-1112, Issue #1261, #3170)</li>
|
||||||
|
@ -92,42 +101,55 @@
|
||||||
<li><I>Processors</I>. Updated x86 and AARCH64 processor manual index files. (GP-1234)</li>
|
<li><I>Processors</I>. Updated x86 and AARCH64 processor manual index files. (GP-1234)</li>
|
||||||
<li><I>Processors</I>. Added <code><B>longMode</B></code> bit to x64 language spec for mixed 32-/64-bit use cases; e.g., WoW64. (GP-1255)</li>
|
<li><I>Processors</I>. Added <code><B>longMode</B></code> bit to x64 language spec for mixed 32-/64-bit use cases; e.g., WoW64. (GP-1255)</li>
|
||||||
<li><I>Processors</I>. Made minor improvements to the RISC-V language module. (GP-1409)</li>
|
<li><I>Processors</I>. Made minor improvements to the RISC-V language module. (GP-1409)</li>
|
||||||
|
<li><I>Processors</I>. Corrected <code>swap</code> instruction semantics for PIC-24,30,33 processors. (GP-1565, Issue #3670)</li>
|
||||||
<li><I>Scripting</I>. Improved RecoverClassesFromRTTIScript to better define virtual function data definitions to be more generically used by all related class structures. (GP-1311, Issue #3417)</li>
|
<li><I>Scripting</I>. Improved RecoverClassesFromRTTIScript to better define virtual function data definitions to be more generically used by all related class structures. (GP-1311, Issue #3417)</li>
|
||||||
<li><I>Scripting</I>. Added options to allow removal of replaced class structure data types when replaced with ones created by RecoverClassesFromRTTIScript. (GP-1315, Issue #3443)</li>
|
<li><I>Scripting</I>. Added options to allow removal of replaced class structure data types when replaced with ones created by RecoverClassesFromRTTIScript. (GP-1315, Issue #3443)</li>
|
||||||
<li><I>Scripting</I>. Changed class structures created by RecoverClassesfromRTTI so that the vftable pointers are separated from the class data structures inside a derived class. This allows the derived class vftables structures to be accessed correctly by the Decompiler. (GP-1408)</li>
|
<li><I>Scripting</I>. Changed class structures created by RecoverClassesfromRTTI so that the vftable pointers are separated from the class data structures inside a derived class. This allows the derived class vftables structures to be accessed correctly by the Decompiler. (GP-1408)</li>
|
||||||
<li><I>Sleigh</I>. Modeled undocumented encoding of <code>REP</code> prefix for x86 instructions. (GP-1294, Issue #731)</li>
|
<li><I>Sleigh</I>. Modeled undocumented encoding of <code>REP</code> prefix for x86 instructions. (GP-1294, Issue #731)</li>
|
||||||
|
<li><I>Version Tracking</I>. Updated Version Tracking to address multiple performance issues. (GP-1421, Issue #3221)</li>
|
||||||
<li><I>Version Tracking</I>. Slightly relaxed score thresholds for the reference correlator portions of auto version tracking to enable discovery of more high scoring matches. (GP-1448)</li>
|
<li><I>Version Tracking</I>. Slightly relaxed score thresholds for the reference correlator portions of auto version tracking to enable discovery of more high scoring matches. (GP-1448)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<blockquote><p><u>Bugs</u></p>
|
<blockquote><p><u>Bugs</u></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><I>Analysis</I>. Fixed a bug that would result in the <B>COFF Header Annotation</B> analyzer from running on PIC binaries when it was not intended to. (GP-1366, Issue #3386)</li>
|
<li><I>Analysis</I>. Fixed a bug that would result in the <B>COFF Header Annotation</B> analyzer running on PIC binaries when it was not intended to. (GP-1366, Issue #3386)</li>
|
||||||
<li><I>Analysis</I>. The Objective-C analyzer no longer crashes when encountering categories with an implementation in an external binary. (GP-1413, Issue #3510)</li>
|
<li><I>Analysis</I>. The Objective-C analyzer no longer crashes when encountering categories with an implementation in an external binary. (GP-1413, Issue #3510)</li>
|
||||||
<li><I>Analysis</I>. Fixed a stack overflow in the <B>Objective-C 2 Class</B> analyzer. (GP-1420, Issue #2378)</li>
|
<li><I>Analysis</I>. Fixed a stack overflow in the <B>Objective-C 2 Class</B> analyzer. (GP-1420, Issue #2378)</li>
|
||||||
|
<li><I>Analysis</I>. Fixed a bug with recovering Objective-C method names. (GP-1548, Issue #3611)</li>
|
||||||
|
<li><I>Analysis</I>. Corrected a potential infinite loop in stack analysis and constant propagation due to recurring call-fixup injection to the same location. (GP-1554, Issue #3683)</li>
|
||||||
|
<li><I>Analysis</I>. Fixed certain ELF exception records in ELF binaries marked as <code>DW_EH_PE_absptr</code> that are not relocated correctly when the binary is loaded in an alternate image base. (GP-1575)</li>
|
||||||
<li><I>API</I>. Fixed issues related to moving memory blocks where the source and/or destination have pinned symbols. This could have resulted in addresses with symbols where no symbol is primary or having multiple symbols at an address that are primary. It could also have resulted in pinned symbols being moved from the destination to the source address range. (GP-1103)</li>
|
<li><I>API</I>. Fixed issues related to moving memory blocks where the source and/or destination have pinned symbols. This could have resulted in addresses with symbols where no symbol is primary or having multiple symbols at an address that are primary. It could also have resulted in pinned symbols being moved from the destination to the source address range. (GP-1103)</li>
|
||||||
<li><I>API</I>. Fixed an issue with the SymbolManager method getClassNamespaces() where it was only returning class namespaces in the global namespace. (GP-1346)</li>
|
<li><I>API</I>. Fixed an issue with the SymbolManager method getClassNamespaces() where it was only returning class namespaces in the global namespace. (GP-1346)</li>
|
||||||
|
<li><I>API</I>. Critical Ghidra 10.1-BETA Issue: Corrected external function bug introduced in Ghidra 10.1-BETA which caused new functions to not be marked as primary. This is a critical bug which could impact most programs imported with 10.1-BETA. Such imports should be re-imported with this fix in place. (GP-1525)</li>
|
||||||
<li><I>C Parsing</I>. Several issues parsing C header files have been fixed including ternary macro expression evaluation, #line preprocessor markup within functions and structures, far/near recognized as a keyword, and handling of __asm syntax. (GP-1335, Issue #1069, #1082, #2667, #464, #929)</li>
|
<li><I>C Parsing</I>. Several issues parsing C header files have been fixed including ternary macro expression evaluation, #line preprocessor markup within functions and structures, far/near recognized as a keyword, and handling of __asm syntax. (GP-1335, Issue #1069, #1082, #2667, #464, #929)</li>
|
||||||
<li><I>Debugger</I>. Fixed program actions (<B>Save</B>, <B>Close</B>, <B>Undo</B>, etc.) to work properly in the Debugger. (GP-508)</li>
|
<li><I>Debugger</I>. Fixed program actions (<B>Save</B>, <B>Close</B>, <B>Undo</B>, etc.) to work properly in the Debugger. (GP-508)</li>
|
||||||
<li><I>Debugger</I>. Fixed issue getting registers on ARM targets with GDB where command exceeded 4096 characters. (GP-1356, Issue #3297, #3509)</li>
|
<li><I>Debugger</I>. Fixed issue getting registers on ARM targets with GDB where command exceeded 4096 characters. (GP-1356, Issue #3297, #3509)</li>
|
||||||
<li><I>Debugger</I>. Fixed several issues with the GDB connector's <b>use existing session</b> option. (GP-1365)</li>
|
<li><I>Debugger</I>. Fixed several issues with the GDB connector's <b>use existing session</b> option. (GP-1365)</li>
|
||||||
<li><I>Debugger</I>. Fixed a NullPointerException from canceling a debug launch. (GP-1442)</li>
|
<li><I>Debugger</I>. Fixed a NullPointerException from canceling a debug launch. (GP-1442)</li>
|
||||||
<li><I>Debugger</I>. Fixed <B>Select Addresses</B> button for Debugger Modules pane. (GP-1450)</li>
|
<li><I>Debugger</I>. Fixed <B>Select Addresses</B> button for Debugger Modules pane. (GP-1450)</li>
|
||||||
<li><I>Debugger</I>. Fixed issue with duplicate selection actions in the debugger tool. (GP-1452)</li>
|
<li><I>Debugger</I>. Fixed issue with duplicate selection actions in the Debugger tool. (GP-1452)</li>
|
||||||
|
<li><I>Debugger</I>. Fixed a bug in emulation where read/write ranges include the max address. (GP-1493)</li>
|
||||||
|
<li><I>Debugger</I>. Fixed exception behavior for toggled Continue/Handled options. (GP-1558, Issue #3049)</li>
|
||||||
<li><I>Debugger:Emulator</I>. Fixed Debugger integration and trace emulation for WoW64. (GP-1245)</li>
|
<li><I>Debugger:Emulator</I>. Fixed Debugger integration and trace emulation for WoW64. (GP-1245)</li>
|
||||||
|
<li><I>Debugger:Emulator</I>. Relaxed and corrected some logging of UNKNOWN/uninitialized values during emulation. (GP-1488)</li>
|
||||||
|
<li><I>Debugger:Emulator</I>. Fixed several issues in Emulator with respect to Harvard architectures, memory-mapped registers, and word-addressable systems. (GP-1540)</li>
|
||||||
<li><I>Debugger:GDB</I>. Fixed issue with GDB/GADP hang in development mode. (GP-1360)</li>
|
<li><I>Debugger:GDB</I>. Fixed issue with GDB/GADP hang in development mode. (GP-1360)</li>
|
||||||
<li><I>Debugger:GDB</I>. Fixed issue interrupting GDB targets launched without temporary breakpoint on main. (GP-1362)</li>
|
<li><I>Debugger:GDB</I>. Fixed issue interrupting GDB targets launched without temporary breakpoint on main. (GP-1362)</li>
|
||||||
<li><I>Debugger:GDB</I>. Fixed issues parsing and displaying various types of GDB breakpoints. (GP-1364)</li>
|
<li><I>Debugger:GDB</I>. Fixed issues parsing and displaying various types of GDB breakpoints. (GP-1364)</li>
|
||||||
<li><I>Debugger:GDB</I>. Fixed problem passing arguments to GDB in IN-VM and SSH modes. (GP-1368)</li>
|
<li><I>Debugger:GDB</I>. Fixed problem passing arguments to GDB in IN-VM and SSH modes. (GP-1368)</li>
|
||||||
<li><I>Debugger:GDB</I>. Fixed a NullPointerException when terminating GDB. Changed PtySession API to prevent future occurrence. (GP-1399, Issue #3487)</li>
|
<li><I>Debugger:GDB</I>. Fixed a NullPointerException when terminating GDB. Changed PtySession API to prevent future occurrence. (GP-1399, Issue #3487)</li>
|
||||||
<li><I>Debugger:Trace</I>. Fixed <B>ram</B> not in this trace/language error. (GP-1411, Issue #3509)</li>
|
<li><I>Debugger:Listing</I>. Fixed stack trace when switching to trace of a different processor language. (GP-1547)</li>
|
||||||
|
<li><I>Debugger:Trace</I>. Fixed <B>'ram' not in this trace/language</B> error. (GP-1411, Issue #3509)</li>
|
||||||
<li><I>Decompiler</I>. Fixed a corner case in the manipulation of integer ranges by the Decompiler. (GP-1243, Issue #3064)</li>
|
<li><I>Decompiler</I>. Fixed a corner case in the manipulation of integer ranges by the Decompiler. (GP-1243, Issue #3064)</li>
|
||||||
<li><I>Decompiler</I>. Fixed a bug in the Decompiler's renaming algorithm that could cause memory corruption in rare cases. (GP-1380, Issue #3429)</li>
|
<li><I>Decompiler</I>. Fixed a bug in the Decompiler's renaming algorithm that could cause memory corruption in rare cases. (GP-1380, Issue #3429)</li>
|
||||||
<li><I>Demangler</I>. Fixed GNU Demangling bug encountered when Address Table types have spaces in the parent namespace name. (GP-1051)</li>
|
<li><I>Demangler</I>. Fixed GNU Demangling bug encountered when Address Table types have spaces in the parent namespace name. (GP-1051)</li>
|
||||||
|
<li><I>DWARF</I>. Fixed check for invalid function addresses. (GP-1573)</li>
|
||||||
<li><I>Eclipse Integration</I>. Fixed an exception in the GhidraDev Eclipse plugin that occurred when performing a <B>Link Ghidra</B> operation on projects that use a Gradle classpath container. (GP-1149, Issue #3087, #3088)</li>
|
<li><I>Eclipse Integration</I>. Fixed an exception in the GhidraDev Eclipse plugin that occurred when performing a <B>Link Ghidra</B> operation on projects that use a Gradle classpath container. (GP-1149, Issue #3087, #3088)</li>
|
||||||
<li><I>Exporter</I>. IDA exporter no longer fails when function stack variables have comments. (GP-1190, Issue #2350, #3309, #748)</li>
|
<li><I>Exporter</I>. IDA exporter no longer fails when function stack variables have comments. (GP-1190, Issue #2350, #3309, #748)</li>
|
||||||
|
<li><I>Exporter</I>. Fixed an issue with the ElfExporter not correctly undoing relocations when they spanned partially file-backed memory blocks. (GP-1570, Issue #3696)</li>
|
||||||
<li><I>FileSystems</I>. Fixed Ext4 handling of longer symlink paths and added support for inline data. (GP-1088)</li>
|
<li><I>FileSystems</I>. Fixed Ext4 handling of longer symlink paths and added support for inline data. (GP-1088)</li>
|
||||||
<li><I>FileSystems</I>. Fixed Ext4 file system to handle volumes with blocksize 1024 and a first data block value of 1. Also added support for old style block maps. (GP-1094, Issue #1877)</li>
|
<li><I>FileSystems</I>. Fixed Ext4 file system to handle volumes with blocksize 1024 and a first data block value of 1. Also added support for old style block maps. (GP-1094, Issue #1877)</li>
|
||||||
<li><I>Framework</I>. Fixed error causing exception in the Specification Extensions panel, when importing a new callotherfixup. (GP-1414, Issue #3502)</li>
|
<li><I>Framework</I>. Fixed error causing exception in the Specification Extensions panel when importing a new callotherfixup. (GP-1414, Issue #3502)</li>
|
||||||
<li><I>GUI</I>. Fixed potential infinite loop in Function Graph edge painting. (GP-1019, Issue #2114)</li>
|
<li><I>GUI</I>. Fixed potential infinite loop in Function Graph edge painting. (GP-1019, Issue #2114)</li>
|
||||||
<li><I>GUI</I>. Fixed minor memory leak encountered when using <B>Search -> For Address Tables</B>. (GP-1030, Issue #3013)</li>
|
<li><I>GUI</I>. Fixed minor memory leak encountered when using <B>Search -> For Address Tables</B>. (GP-1030, Issue #3013)</li>
|
||||||
<li><I>GUI</I>. Fixed bug that prevented the Decompiler scalar hover tooltip from showing. (GP-1071, Issue #3142)</li>
|
<li><I>GUI</I>. Fixed bug that prevented the Decompiler scalar hover tooltip from showing. (GP-1071, Issue #3142)</li>
|
||||||
|
@ -139,8 +161,9 @@
|
||||||
<li><I>GUI</I>. Fixed stack trace in the Function Call Graph when using the <B>Show Incoming Level Edges</B> action. (GP-1302, Issue #3327)</li>
|
<li><I>GUI</I>. Fixed stack trace in the Function Call Graph when using the <B>Show Incoming Level Edges</B> action. (GP-1302, Issue #3327)</li>
|
||||||
<li><I>GUI</I>. Fixed the Search Memory dialog issue that caused odd resize behavior when using the Advanced button. (GP-1333, Issue #3158)</li>
|
<li><I>GUI</I>. Fixed the Search Memory dialog issue that caused odd resize behavior when using the Advanced button. (GP-1333, Issue #3158)</li>
|
||||||
<li><I>GUI</I>. Fixed tracking of <B>Favorite</B> data types when switching between multiple open programs. (GP-1391)</li>
|
<li><I>GUI</I>. Fixed tracking of <B>Favorite</B> data types when switching between multiple open programs. (GP-1391)</li>
|
||||||
<li><I>GUI</I>. Fix user list scrollbar in shared project dialog when there is a large number of users. (GP-1410)</li>
|
<li><I>GUI</I>. Fixed user list scrollbar in shared project dialog when there is a large number of users. (GP-1410)</li>
|
||||||
<li><I>GUI</I>. Fixed bug that cause a structure field name to change when using the Retype Field action without picking a new data type. (GP-1429, Issue #3483)</li>
|
<li><I>GUI</I>. Fixed bug that cause a structure field name to change when using the Retype Field action without picking a new data type. (GP-1429, Issue #3483)</li>
|
||||||
|
<li><I>GUI</I>. Fixed issue when attempting to rename a datatype that has the same name as a category in the same parent cateogory. The rename would attempt to rename the category instead of the datatype. (GP-1445)</li>
|
||||||
<li><I>Importer</I>. Fixed issue with <B>Extract and Import</B> action trying to create invalid filenames. (GP-1024, Issue #3114)</li>
|
<li><I>Importer</I>. Fixed issue with <B>Extract and Import</B> action trying to create invalid filenames. (GP-1024, Issue #3114)</li>
|
||||||
<li><I>Importer</I>. Fixed <B>Extract and Import</B> action when highlighting bytes in the debugger view. (GP-1449)</li>
|
<li><I>Importer</I>. Fixed <B>Extract and Import</B> action when highlighting bytes in the debugger view. (GP-1449)</li>
|
||||||
<li><I>Importer:ELF</I>. Corrected ELF importer error which could occur when processing memory section overlay blocks caused by AddressOutOfBoundsException exception. (GP-1052, Issue #3128)</li>
|
<li><I>Importer:ELF</I>. Corrected ELF importer error which could occur when processing memory section overlay blocks caused by AddressOutOfBoundsException exception. (GP-1052, Issue #3128)</li>
|
||||||
|
@ -152,6 +175,7 @@
|
||||||
<li><I>Processors</I>. Corrected pcode for ARM/ARM-Thumb <code>adcs</code> and <code>sbcs</code> carry and overflow flag updates. (GP-1043)</li>
|
<li><I>Processors</I>. Corrected pcode for ARM/ARM-Thumb <code>adcs</code> and <code>sbcs</code> carry and overflow flag updates. (GP-1043)</li>
|
||||||
<li><I>Processors</I>. Corrected flag handling for some 6502 instructions. (GP-1054, Issue #3096)</li>
|
<li><I>Processors</I>. Corrected flag handling for some 6502 instructions. (GP-1054, Issue #3096)</li>
|
||||||
<li><I>Processors</I>. Fixed issues with PPC register overwrites. (GP-1075, Issue #1672)</li>
|
<li><I>Processors</I>. Fixed issues with PPC register overwrites. (GP-1075, Issue #1672)</li>
|
||||||
|
<li><I>Processors</I>. Fixed 6502 <code>bit</code> instruction semantics. (GP-1115, Issue #2558, #3095)</li>
|
||||||
<li><I>Processors</I>. Fixed MIPS 32-bit little endian floating point register ordering. (GP-1129, Issue #3212)</li>
|
<li><I>Processors</I>. Fixed MIPS 32-bit little endian floating point register ordering. (GP-1129, Issue #3212)</li>
|
||||||
<li><I>Processors</I>. Corrected PowerPC ISA instruction manual index page numbers. (GP-1218, Issue #2927)</li>
|
<li><I>Processors</I>. Corrected PowerPC ISA instruction manual index page numbers. (GP-1218, Issue #2927)</li>
|
||||||
<li><I>Processors</I>. Updated Tricore manual index file to match correct page numbers. (GP-1220, Issue #2926)</li>
|
<li><I>Processors</I>. Updated Tricore manual index file to match correct page numbers. (GP-1220, Issue #2926)</li>
|
||||||
|
@ -166,9 +190,14 @@
|
||||||
<li><I>Processors</I>. Corrected MIPS pcodeop error in <code>tlbr</code> instruction. (GP-1363, Issue #3463)</li>
|
<li><I>Processors</I>. Corrected MIPS pcodeop error in <code>tlbr</code> instruction. (GP-1363, Issue #3463)</li>
|
||||||
<li><I>Processors</I>. Corrected ARM Thumb conditional instruction <code>it</code> to allow the <code>al</code> (always) conditional. (GP-1402, Issue #3499)</li>
|
<li><I>Processors</I>. Corrected ARM Thumb conditional instruction <code>it</code> to allow the <code>al</code> (always) conditional. (GP-1402, Issue #3499)</li>
|
||||||
<li><I>Processors</I>. Removed extraneous <code>sb</code> from ARM <code>ldrsb</code> instruction. (GP-1412, Issue #3522)</li>
|
<li><I>Processors</I>. Removed extraneous <code>sb</code> from ARM <code>ldrsb</code> instruction. (GP-1412, Issue #3522)</li>
|
||||||
|
<li><I>Processors</I>. Implemented M68000 <code>CHK</code>, <code>CHK2</code>, and <code>CMP2</code> instructions. (GP-1478, Issue #2856, #3616)</li>
|
||||||
|
<li><I>Processors</I>. Corrected SuperH <code>trapa</code> instruction to use a <code>call</code> p-code op instead of a <code>goto</code>. (GP-1504, Issue #3600)</li>
|
||||||
|
<li><I>Processors</I>. Corrected x86 instruction parse and semantics for <code>RDRAND</code> and <code>RDSEED</code>. (GP-1564)</li>
|
||||||
<li><I>ProgramDB</I>. Corrected language upgrade issue which could result in lost memory reference due to <code>RefType</code> change. (GP-1392)</li>
|
<li><I>ProgramDB</I>. Corrected language upgrade issue which could result in lost memory reference due to <code>RefType</code> change. (GP-1392)</li>
|
||||||
<li><I>Scripting</I>. RecoverClassesFromRTTIScript now consistently applies its class structures in programs that have PDB information applied. Also, an option was added so users can decide whether to replace existing class data in thiscall functions regardless of whether they originated as PDB or not. (GP-1464)</li>
|
<li><I>Scripting</I>. RecoverClassesFromRTTIScript now consistently applies its class structures in programs that have PDB information applied. Also, an option was added so users can decide whether to replace existing class data in thiscall functions regardless of whether they originated as PDB or not. (GP-1464)</li>
|
||||||
|
<li><I>Scripting</I>. Fixed an issue where some GhidraScript print methods were not getting output to the script log file. (GP-1541, Issue #3657)</li>
|
||||||
<li><I>Sleigh</I>. Corrected sleigh-language endian-mismatch error-message formatting. (GP-1132, Issue #3215)</li>
|
<li><I>Sleigh</I>. Corrected sleigh-language endian-mismatch error-message formatting. (GP-1132, Issue #3215)</li>
|
||||||
|
<li><I>Sleigh</I>. Made numerous fixes to the PowerPC SLEIGH language module. Note: minor language version upgrade. (GP-1250)</li>
|
||||||
<li><I>Version Tracking</I>. Fixed UnsupportedOperationException in Version Tracking when attempting to find references to register or stack addresses. (GP-1084, Issue #1152)</li>
|
<li><I>Version Tracking</I>. Fixed UnsupportedOperationException in Version Tracking when attempting to find references to register or stack addresses. (GP-1084, Issue #1152)</li>
|
||||||
<li><I>Version Tracking</I>. Fixed Version Tracking <B>Swap</B> button to not trigger the reloading of programs. (GP-1183)</li>
|
<li><I>Version Tracking</I>. Fixed Version Tracking <B>Swap</B> button to not trigger the reloading of programs. (GP-1183)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
vulnerabilities in networks and systems.
|
vulnerabilities in networks and systems.
|
||||||
</P>
|
</P>
|
||||||
|
|
||||||
<H1>What's new in Ghidra 10.1 BETA</H1>
|
<H1>What's new in Ghidra 10.1</H1>
|
||||||
|
|
||||||
<H2>The not-so-fine print: Please Read!</H2>
|
<H2>The not-so-fine print: Please Read!</H2>
|
||||||
|
|
||||||
|
@ -52,6 +52,12 @@
|
||||||
<P>This release includes many new features and capabilities, performance improvements, quite a few bug fixes, and many pull-request
|
<P>This release includes many new features and capabilities, performance improvements, quite a few bug fixes, and many pull-request
|
||||||
contributions. Thanks to all those who have contributed their time, thoughts, and code. The Ghidra user community
|
contributions. Thanks to all those who have contributed their time, thoughts, and code. The Ghidra user community
|
||||||
thanks you too!</P>
|
thanks you too!</P>
|
||||||
|
|
||||||
|
<P>NOTE: Please note that any programs imported with a Ghidra beta versions or code built directly from source outside of a release tag may not be compatible
|
||||||
|
and may have flaws that have been corrected. Any programs analyzed with a beta should be considered experimental and re-imported and analyzed with
|
||||||
|
a release version. As an example, Ghidra 10.1 beta had an import flaw affecting symbol de-mangling that was not correctable.
|
||||||
|
Programs imported with previous release versions should upgrade correctly through various automatic upgrade mechanisms. Any program
|
||||||
|
you will continue to reverse engineer should be imported fresh with a release version or a build you trust with the latest code fixes.</P>
|
||||||
|
|
||||||
<P>NOTE: Ghidra Server: The Ghidra 10.1 server is compatible with Ghidra 9.2 and later Ghidra clients. Ghidra 10.1
|
<P>NOTE: Ghidra Server: The Ghidra 10.1 server is compatible with Ghidra 9.2 and later Ghidra clients. Ghidra 10.1
|
||||||
clients are compatible with all 9.x servers.</P>
|
clients are compatible with all 9.x servers.</P>
|
||||||
|
@ -89,6 +95,11 @@
|
||||||
and build LLDB with language bindings for Java. Once done, the new connectors for LLDB can be used in the normal fashion. While intended for macOS,
|
and build LLDB with language bindings for Java. Once done, the new connectors for LLDB can be used in the normal fashion. While intended for macOS,
|
||||||
these connectors also work on Linux, and may work on Windows, too. This offers an alternative for those who prefer lldb to gdb.</P>
|
these connectors also work on Linux, and may work on Windows, too. This offers an alternative for those who prefer lldb to gdb.</P>
|
||||||
|
|
||||||
|
<H2>Decompiler</H2>
|
||||||
|
<P>Many improvements have been made to the decompiler output to improve readability. These include the production of <i>else-if</i> syntax in control flow,
|
||||||
|
and the reduction of casting when typedefs are involved. In addition, pointer calculation during sub-expression elimination has been improved, and
|
||||||
|
a new API for iterating and accessing the decompiler output syntax tokens has been added.</P>
|
||||||
|
|
||||||
<H2>Data Types</H2>
|
<H2>Data Types</H2>
|
||||||
|
|
||||||
<P>Support for zero-length data types and components has been improved, although such types will continue to
|
<P>Support for zero-length data types and components has been improved, although such types will continue to
|
||||||
|
@ -100,14 +111,23 @@
|
||||||
The static method <i>DataTypeComponent.usesZeroLengthComponent(DataType)</i> may be used to determine if a zero-length component
|
The static method <i>DataTypeComponent.usesZeroLengthComponent(DataType)</i> may be used to determine if a zero-length component
|
||||||
will be used for a specific data type. Due to the overlapping behavior of zero-length components, a data type which returns <i>true</i>
|
will be used for a specific data type. Due to the overlapping behavior of zero-length components, a data type which returns <i>true</i>
|
||||||
for <i>isNotYetDefined()</i> will not produce a zero-length component.</P>
|
for <i>isNotYetDefined()</i> will not produce a zero-length component.</P>
|
||||||
|
|
||||||
|
<P>Improved parsing of C header files to correctly extract data type definitions, including corrected sizeof() handling, expression
|
||||||
|
simplification to a constant for many types such as array size and enumeration value, and handling of type declarations within function
|
||||||
|
and structure declarations. We have re-parsed most of the included data type archives to take advantage of the changes, and plan to
|
||||||
|
update the archives to more recent versions of the header files in the near future.</P>
|
||||||
|
|
||||||
<H2>Mach-O Binary Import</H2>
|
<H2>Mach-O Binary Import</H2>
|
||||||
<P>Mach-O binary import has been greatly improved, including handling of relocation pointer chains, support for newer Objective-C
|
<P>Mach-O binary import has been greatly improved, including handling of relocation pointer chains, support for newer Objective-C
|
||||||
class structures with RelativePointers, many additional load commands such as encrypted blocks, and more recent dyld and kernel caches.</P>
|
class structures with RelativePointers, additional load commands, and support for more recent versions of dyld and kernel caches
|
||||||
|
including split-file dyld_shared_cache variants.</P>
|
||||||
|
|
||||||
<H2>Android</H2>
|
<H2>Android</H2>
|
||||||
<P>Added support for Android formats (ART, OAT, ODEX, DEX, CDEX, VDEX) and Dalvik VM Sleigh modules for each major Android release up to version 12.x.
|
<P>Import and analysis of the entire existing set (almost) of Android binaries up to version 12.x is now supported. The type of binaries supported
|
||||||
Support for the latest android release is in progress for a future release.</P>
|
include: Android Run-Time (ART), Ahead-of-Time (OAT)/ELF, Dalvik Executables (DEX), Compact DEX (CDEX), Verified DEX (VEX), Boot Image,
|
||||||
|
and Boot Loader formats. Also included are Sleigh modules for DEX files covering each major release of Android; the optimized instructions
|
||||||
|
vary across versions. Now when importing DEX files, you can select the Dalvik language appropriate to the Android release, which will result
|
||||||
|
in better analysis.</P>
|
||||||
|
|
||||||
<H2>Performance Improvements</H2>
|
<H2>Performance Improvements</H2>
|
||||||
<P>There have been many performance improvements to import, analysis, program data base access, many API calls, and the user interface.</P>
|
<P>There have been many performance improvements to import, analysis, program data base access, many API calls, and the user interface.</P>
|
||||||
|
@ -120,10 +140,6 @@
|
||||||
<H2>DWARF</H2>
|
<H2>DWARF</H2>
|
||||||
<P>Support for loading DWARF debug information from a separate file during import has been added. In addition data type information contained in the
|
<P>Support for loading DWARF debug information from a separate file during import has been added. In addition data type information contained in the
|
||||||
separate debug file can be loaded without application to a program, enabling the use of debug information from a related version of the binary.</P>
|
separate debug file can be loaded without application to a program, enabling the use of debug information from a related version of the binary.</P>
|
||||||
|
|
||||||
<P>
|
|
||||||
... WORK IN PROGRESS ... See release notes for more details.
|
|
||||||
</P>
|
|
||||||
|
|
||||||
<H2>Bug Fixes and Enhancements</H2>
|
<H2>Bug Fixes and Enhancements</H2>
|
||||||
<P> Numerous other bug fixes and improvements are fully listed in the <a href="ChangeHistory.html">ChangeHistory</a> file.</P>
|
<P> Numerous other bug fixes and improvements are fully listed in the <a href="ChangeHistory.html">ChangeHistory</a> file.</P>
|
||||||
|
|
|
@ -31,6 +31,7 @@ dependencies {
|
||||||
api project(':ProposedUtils')
|
api project(':ProposedUtils')
|
||||||
|
|
||||||
helpPath project(path: ':Base', configuration: 'helpPath')
|
helpPath project(path: ':Base', configuration: 'helpPath')
|
||||||
|
helpPath project(path: ':ProgramDiff', configuration: 'helpPath')
|
||||||
|
|
||||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||||
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
||||||
|
|
|
@ -120,6 +120,9 @@ src/main/help/help/topics/DebuggerThreadsPlugin/images/stepinto.png||GHIDRA||||E
|
||||||
src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTimeSelectionDialog.png||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTraceViewDiffPlugin.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END|
|
||||||
src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END|
|
src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END|
|
||||||
|
@ -185,6 +188,7 @@ src/main/resources/images/stop.png||GHIDRA||||END|
|
||||||
src/main/resources/images/sync_enabled.png||GHIDRA||||END|
|
src/main/resources/images/sync_enabled.png||GHIDRA||||END|
|
||||||
src/main/resources/images/system-switch-user.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
src/main/resources/images/system-switch-user.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||||
src/main/resources/images/table.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
src/main/resources/images/table.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||||
|
src/main/resources/images/table_relationship.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||||
src/main/resources/images/text-xml.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
src/main/resources/images/text-xml.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||||
src/main/resources/images/thread.png||GHIDRA||||END|
|
src/main/resources/images/thread.png||GHIDRA||||END|
|
||||||
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END|
|
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||||
|
|
|
@ -157,6 +157,10 @@
|
||||||
sortgroup="p"
|
sortgroup="p"
|
||||||
target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" />
|
target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" />
|
||||||
|
|
||||||
|
<tocdef id="DebuggerTraceDiffPlugin" text="Comparing Times"
|
||||||
|
sortgroup="p1"
|
||||||
|
target="help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
|
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
|
||||||
sortgroup="q"
|
sortgroup="q"
|
||||||
target="help/topics/DebuggerBots/DebuggerBots.html" />
|
target="help/topics/DebuggerBots/DebuggerBots.html" />
|
||||||
|
|
|
@ -56,15 +56,20 @@
|
||||||
|
|
||||||
<H2>Actions</H2>
|
<H2>Actions</H2>
|
||||||
|
|
||||||
<P>The time window provides the following action:</P>
|
<H3><A name="rename_snapshot"></A>Rename Snapshot</H3>
|
||||||
|
|
||||||
|
<P>This action is available in the <SPAN class="menu">Debugger</SPAN> menu whenever the focused
|
||||||
|
window has an associated snapshot. It will prompt for a new description for the current
|
||||||
|
snapshot. This is a shortcut to modifying the description in the time table, but can be
|
||||||
|
accessed outside of the time window.</P>
|
||||||
|
|
||||||
<H3><A name="hide_scratch"></A>Hide Scratch</H3>
|
<H3><A name="hide_scratch"></A>Hide Scratch</H3>
|
||||||
|
|
||||||
<P>This toggle action is always available. It is enabled by default. The emulation service,
|
<P>This toggle action is always available in the drop-down actions of the Time window. It is
|
||||||
which enables trace extrapolation and interpolation, writes emulated state into the trace's
|
enabled by default. The emulation service, which enables trace extrapolation and interpolation,
|
||||||
"scratch space," which comprises all negative snaps. When this toggle is enabled, those
|
writes emulated state into the trace's "scratch space," which comprises all negative snaps.
|
||||||
snapshots are hidden. They can be displayed by disabling this toggle. Note that navigating into
|
When this toggle is enabled, those snapshots are hidden. They can be displayed by disabling
|
||||||
scratch space may cause temporary undefined behavior in some windows, and may prevent
|
this toggle. Note that navigating into scratch space may cause temporary undefined behavior in
|
||||||
interaction with the target.</P>
|
some windows, and may prevent interaction with the target.</P>
|
||||||
</BODY>
|
</BODY>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||||
|
|
||||||
|
<HTML>
|
||||||
|
<HEAD>
|
||||||
|
<META name="generator" content=
|
||||||
|
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||||
|
|
||||||
|
<TITLE>Debugger: Comparing Times</TITLE>
|
||||||
|
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||||
|
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||||
|
</HEAD>
|
||||||
|
|
||||||
|
<BODY lang="EN-US">
|
||||||
|
<H1><A name="plugin"></A>Debugger: Comparing Times</H1>
|
||||||
|
|
||||||
|
<TABLE width="100%">
|
||||||
|
<TBODY>
|
||||||
|
<TR>
|
||||||
|
<TD align="center" width="100%"><IMG alt="" border="1" src=
|
||||||
|
"images/DebuggerTraceViewDiffPlugin.png"></TD>
|
||||||
|
</TR>
|
||||||
|
</TBODY>
|
||||||
|
</TABLE>
|
||||||
|
|
||||||
|
<P><A name="Toggle_Header"></A>A common strategy in dynamic analysis is to compare machine
|
||||||
|
state between two points in time. To this end, to support comparison of bytes in memory, the
|
||||||
|
"trace diff" plugin extends the <A href=
|
||||||
|
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A> to provide
|
||||||
|
side-by-side comparison of two different points in time. When active, listings for both points
|
||||||
|
in time are displayed and the byte value differences between them are highlighted. <B>NOTE:</B>
|
||||||
|
This does not compare annotations. It only compares raw byte values. Additionally, all stale
|
||||||
|
values are ignored, i.e., to show as a difference, the memory must be observed at <EM>both</EM>
|
||||||
|
points in time, and the values must differ.</P>
|
||||||
|
|
||||||
|
<P><B>NOTE:</B> This plugin only facilitates the comparison of memory displayed in listings. To
|
||||||
|
compare registers or SLEIGH expressions, use the respective windows: <A href=
|
||||||
|
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> and <A href=
|
||||||
|
"help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html">Watches</A>. By navigating back
|
||||||
|
and forth between two points in time, using the <A href=
|
||||||
|
"help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html">Time Window</A>, the differences are
|
||||||
|
displayed in <FONT color="red">red</FONT>.</P>
|
||||||
|
|
||||||
|
<H2>Actions</H2>
|
||||||
|
|
||||||
|
<P>The plugin adds actions to the main Dynamic Listing. When active, additional actions are
|
||||||
|
present.</P>
|
||||||
|
|
||||||
|
<H3><A name="compare"></A>Compare</H3>
|
||||||
|
|
||||||
|
<P>This action is available whenever a trace is active in the main listing. It prompts for an
|
||||||
|
alternative point in time:</P>
|
||||||
|
|
||||||
|
<TABLE width="100%">
|
||||||
|
<TBODY>
|
||||||
|
<TR>
|
||||||
|
<TD align="center" width="100%"><IMG alt="" src=
|
||||||
|
"images/DebuggerTimeSelectionDialog.png"></TD>
|
||||||
|
</TR>
|
||||||
|
</TBODY>
|
||||||
|
</TABLE>
|
||||||
|
|
||||||
|
<P>The snapshot table is exactly the same as that in the <A href=
|
||||||
|
"help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html">Time Window</A>. In most cases, simply
|
||||||
|
selecting a snapshot suffices.</P>
|
||||||
|
|
||||||
|
<P>Perhaps the most common use of this action is to identify where a given variable is stored
|
||||||
|
in memory. The trace saves a record of observed memory from the debugging session. Comparing
|
||||||
|
snapshots thus identifies changes over time; however, there is no guarantee that the desired
|
||||||
|
variable was ever observed. Assuming the general vicinity of the variable is known, e.g.,
|
||||||
|
"somewhere in the .data section," the <A href=
|
||||||
|
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html#read_memory">Read Selected
|
||||||
|
Memory</A> action can ensure its value is recorded. Of course, it can also read "all memory,"
|
||||||
|
but that operation and the follow-on comparison could take time. In general, the procedure to
|
||||||
|
locate a variable is to capture a baseline, execute the target until the variable has changed,
|
||||||
|
capture again, then compare:</P>
|
||||||
|
|
||||||
|
<OL>
|
||||||
|
<LI>Execute the target up to a baseline, and take note of the variable's value, as displayed
|
||||||
|
by the target program.</LI>
|
||||||
|
|
||||||
|
<LI>Consider naming the current snapshot for later reference, using the <A href=
|
||||||
|
"help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html#rename_snapshot">Rename Current
|
||||||
|
Snapshot</A> action. Ideally, the name should indicate the variable's value.</LI>
|
||||||
|
|
||||||
|
<LI>Select the range of memory believed to contain the variable. Consider using the <A href=
|
||||||
|
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html">Modules</A> or <A href=
|
||||||
|
"help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html">Regions</A> window to form the
|
||||||
|
selection.</LI>
|
||||||
|
|
||||||
|
<LI>Use the <A href=
|
||||||
|
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html#read_memory">Read Selected
|
||||||
|
Memory</A> action to ensure the variable's value is stored in the trace.</LI>
|
||||||
|
|
||||||
|
<LI>Allow the target to execute until the variable has changed. Ideally, execute as little as
|
||||||
|
necessary, so that few or no other variables change.</LI>
|
||||||
|
|
||||||
|
<LI>Execution will cause the trace to advance some number of snapshots. Once suspended, it's
|
||||||
|
a good idea to rename the current snapshot, again indicating the variable's new value and/or
|
||||||
|
the cause of its change.</LI>
|
||||||
|
|
||||||
|
<LI>Repeat the selection and capture steps to ensure the variable's new value is stored in
|
||||||
|
the trace.</LI>
|
||||||
|
|
||||||
|
<LI>Use this <B>Compare</B> action and select the baseline snapshot. It's easy to locate in
|
||||||
|
the table if named appropriately.</LI>
|
||||||
|
</OL>
|
||||||
|
|
||||||
|
<P>Assuming the variable is actually contained in the captured memory ranges, then it should be
|
||||||
|
among the differences shown. If too many differences appear, repeat the experiment. Consider
|
||||||
|
executing less code, establishing a new baseline, taking the intersection of the results, etc.
|
||||||
|
Remember, the variable's storage should encode its value.</P>
|
||||||
|
|
||||||
|
<P>Optionally, the specified time may also include emulation. See the <A href=
|
||||||
|
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A> action
|
||||||
|
for the syntax of the <B>Time Schedule</B> expression. For simple schedules, the step buttons
|
||||||
|
provide convenient forward and backward changes to the emulation schedule. Perhaps the most
|
||||||
|
common use of this is to see what changes from executing an isolated block of code. Ideally,
|
||||||
|
the baseline is a relatively complete capture or represents the present in a live session, so
|
||||||
|
that the emulator does not depend on un-recorded state:</P>
|
||||||
|
|
||||||
|
<OL>
|
||||||
|
<LI>Execute the target up to a baseline, probably using a breakpoint at the start of the
|
||||||
|
interesting block of code.</LI>
|
||||||
|
|
||||||
|
<LI>Keeping the target alive, use the <A href=
|
||||||
|
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#emu_trace_tick_forward">Emulate
|
||||||
|
Forward</A> and/or <A href=
|
||||||
|
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
|
||||||
|
actions to reach the end of the interesting block.</LI>
|
||||||
|
|
||||||
|
<LI>Use this <B>Compare</B> action and select the baseline snapshot.</LI>
|
||||||
|
</OL>
|
||||||
|
|
||||||
|
<P>Alternatively, if the number of steps to reach the end of the block is already known, just
|
||||||
|
use the emulation expression in the <B>Compare</B> action's dialog. <B>NOTE:</B> When used this
|
||||||
|
way, the baseline snapshot will be in the left pane, and the emulated snapshot in the right,
|
||||||
|
which is opposite the result from the steps above.</P>
|
||||||
|
|
||||||
|
<P>In either case, this will highlight any memory that was modified by the emulated code. Of
|
||||||
|
course, this could also be accomplished by setting a second breakpoint and allowing the target
|
||||||
|
to execute; however, emulation does not necessarily require large memory captures. It only
|
||||||
|
observes what it needs, and its internal state contains everything that changed. Furthermore,
|
||||||
|
if establishing the baseline is difficult, emulation allows the target to remain at that
|
||||||
|
baseline. Assuming sufficient state is captured, emulation can also be performed offline,
|
||||||
|
without a live target.</P>
|
||||||
|
|
||||||
|
<H3><A name="next_diff"></A><A name="prev_diff"></A>Previous / Next Difference</H3>
|
||||||
|
|
||||||
|
<P>These actions are only present when the comparison listing is visible. Each is available
|
||||||
|
when there exists a previous or next range from the main listing's cursor. Clicking the action
|
||||||
|
navigates to the nearest address in that range.</P>
|
||||||
|
|
||||||
|
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
||||||
|
|
||||||
|
<P>The difference highlight color is replicated from the <A href=
|
||||||
|
"help/topics/Diff/Diff.htm">Program Differences</A> plugin.</P>
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
|
@ -241,6 +241,10 @@ public class DebuggerCoordinates {
|
||||||
return all(trace, recorder, thread, view, newTime, frame);
|
return all(trace, recorder, thread, view, newTime, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates withView(TraceProgramView newView) {
|
||||||
|
return all(trace, recorder, thread, newView, time, frame);
|
||||||
|
}
|
||||||
|
|
||||||
public TraceSchedule getTime() {
|
public TraceSchedule getTime() {
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,6 +147,8 @@ public interface DebuggerResources {
|
||||||
ImageIcon ICON_READ_MEMORY = ICON_REGIONS;
|
ImageIcon ICON_READ_MEMORY = ICON_REGIONS;
|
||||||
//ResourceManager.loadImage("images/read-memory.png");
|
//ResourceManager.loadImage("images/read-memory.png");
|
||||||
|
|
||||||
|
ImageIcon ICON_RENAME_SNAPSHOT = ICON_TIME;
|
||||||
|
|
||||||
// TODO: Draw an icon
|
// TODO: Draw an icon
|
||||||
ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png");
|
ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png");
|
||||||
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png");
|
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png");
|
||||||
|
@ -176,6 +178,10 @@ public interface DebuggerResources {
|
||||||
ImageIcon ICON_CONFIG = ResourceManager.loadImage("images/conf.png");
|
ImageIcon ICON_CONFIG = ResourceManager.loadImage("images/conf.png");
|
||||||
ImageIcon ICON_TOGGLE = ResourceManager.loadImage("images/system-switch-user.png");
|
ImageIcon ICON_TOGGLE = ResourceManager.loadImage("images/system-switch-user.png");
|
||||||
|
|
||||||
|
ImageIcon ICON_DIFF = ResourceManager.loadImage("images/table_relationship.png");
|
||||||
|
ImageIcon ICON_DIFF_PREV = ResourceManager.loadImage("images/up.png");
|
||||||
|
ImageIcon ICON_DIFF_NEXT = ResourceManager.loadImage("images/down.png");
|
||||||
|
|
||||||
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
|
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
|
||||||
|
|
||||||
String HELP_ANCHOR_PLUGIN = "plugin";
|
String HELP_ANCHOR_PLUGIN = "plugin";
|
||||||
|
@ -368,6 +374,7 @@ public interface DebuggerResources {
|
||||||
String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close";
|
String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close";
|
||||||
String GROUP_MAINTENANCE = "Dbg8. Maintenance";
|
String GROUP_MAINTENANCE = "Dbg8. Maintenance";
|
||||||
String GROUP_MAPPING = "Dbg9. Map Modules/Sections";
|
String GROUP_MAPPING = "Dbg9. Map Modules/Sections";
|
||||||
|
String GROUP_DIFF_NAV = "DiffNavigate";
|
||||||
|
|
||||||
static void tableRowActivationAction(GTable table, Runnable runnable) {
|
static void tableRowActivationAction(GTable table, Runnable runnable) {
|
||||||
table.addMouseListener(new MouseAdapter() {
|
table.addMouseListener(new MouseAdapter() {
|
||||||
|
@ -1639,6 +1646,26 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Perhaps to reduce overloading of "snapshot" we should use "event" instead?
|
||||||
|
interface RenameSnapshotAction {
|
||||||
|
String NAME = "Rename Current Snapshot";
|
||||||
|
String DESCRIPTION =
|
||||||
|
"Modify the description of the snapshot (event) in the current view";
|
||||||
|
String GROUP = GROUP_TRACE;
|
||||||
|
Icon ICON = ICON_RENAME_SNAPSHOT;
|
||||||
|
String HELP_ANCHOR = "rename_snapshot";
|
||||||
|
|
||||||
|
static ActionBuilder builder(Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ActionBuilder(NAME, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.menuPath(DebuggerPluginPackage.NAME, NAME)
|
||||||
|
.menuGroup(GROUP, "zzz")
|
||||||
|
.keyBinding("CTRL SHIFT N")
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface SynchronizeFocusAction {
|
interface SynchronizeFocusAction {
|
||||||
String NAME = "Synchronize Focus";
|
String NAME = "Synchronize Focus";
|
||||||
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
|
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
|
||||||
|
@ -1876,6 +1903,57 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CompareTimesAction {
|
||||||
|
String NAME = "Compare";
|
||||||
|
String DESCRIPTION = "Compare this point in time to another";
|
||||||
|
String GROUP = "zzz"; // Same as for "Diff" action
|
||||||
|
Icon ICON = ICON_DIFF;
|
||||||
|
String HELP_ANCHOR = "compare";
|
||||||
|
|
||||||
|
static ToggleActionBuilder builder(Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ToggleActionBuilder(NAME, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.toolBarGroup(GROUP)
|
||||||
|
.toolBarIcon(ICON)
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PrevDifferenceAction {
|
||||||
|
String NAME = "Previous Difference";
|
||||||
|
String DESCRIPTION = "Go to the previous highlighted difference";
|
||||||
|
String GROUP = GROUP_DIFF_NAV;
|
||||||
|
Icon ICON = ICON_DIFF_PREV;
|
||||||
|
String HELP_ANCHOR = "prev_diff";
|
||||||
|
|
||||||
|
static ActionBuilder builder(Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ActionBuilder(NAME, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.toolBarGroup(GROUP)
|
||||||
|
.toolBarIcon(ICON)
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NextDifferenceAction {
|
||||||
|
String NAME = "Next Difference";
|
||||||
|
String DESCRIPTION = "Go to the next highlighted difference";
|
||||||
|
String GROUP = GROUP_DIFF_NAV;
|
||||||
|
Icon ICON = ICON_DIFF_NEXT;
|
||||||
|
String HELP_ANCHOR = "next_diff";
|
||||||
|
|
||||||
|
static ActionBuilder builder(Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ActionBuilder(NAME, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.toolBarGroup(GROUP)
|
||||||
|
.toolBarIcon(ICON)
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
|
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|
|
@ -16,16 +16,22 @@
|
||||||
package ghidra.app.plugin.core.debug.gui;
|
package ghidra.app.plugin.core.debug.gui;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
|
||||||
public class DebuggerSnapActionContext extends ActionContext {
|
public class DebuggerSnapActionContext extends ActionContext {
|
||||||
private final long tick;
|
private final Trace trace;
|
||||||
|
private final long snap;
|
||||||
|
|
||||||
public DebuggerSnapActionContext(long tick) {
|
public DebuggerSnapActionContext(Trace trace, long snap) {
|
||||||
// TODO: Also require track object?
|
this.trace = trace;
|
||||||
this.tick = tick;
|
this.snap = snap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTick() {
|
public Trace getTrace() {
|
||||||
return tick;
|
return trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSnap() {
|
||||||
|
return snap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,6 +285,11 @@ public abstract class DebuggerReadsMemoryTrait {
|
||||||
return autoSpec;
|
return autoSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* testing */
|
||||||
|
public AddressSetView getVisible() {
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract AddressSetView getSelection();
|
protected abstract AddressSetView getSelection();
|
||||||
|
|
||||||
protected abstract void repaintPanel();
|
protected abstract void repaintPanel();
|
||||||
|
|
|
@ -227,7 +227,7 @@ public class DebuggerTrackLocationTrait {
|
||||||
protected void doSetSpec(LocationTrackingSpec spec) {
|
protected void doSetSpec(LocationTrackingSpec spec) {
|
||||||
if (this.spec != spec) {
|
if (this.spec != spec) {
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
specChanged();
|
specChanged(spec);
|
||||||
}
|
}
|
||||||
doTrack();
|
doTrack();
|
||||||
}
|
}
|
||||||
|
@ -299,7 +299,7 @@ public class DebuggerTrackLocationTrait {
|
||||||
// Listener method
|
// Listener method
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void specChanged() {
|
protected void specChanged(LocationTrackingSpec spec) {
|
||||||
// Listener method
|
// Listener method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,630 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.diff;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.swing.event.ChangeEvent;
|
||||||
|
import javax.swing.event.ChangeListener;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
|
import docking.action.DockingAction;
|
||||||
|
import docking.action.ToggleDockingAction;
|
||||||
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
|
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
|
||||||
|
import ghidra.app.plugin.core.debug.*;
|
||||||
|
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.action.DebuggerTrackLocationTrait;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.MultiBlendedListingBackgroundColorModel;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog;
|
||||||
|
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService;
|
||||||
|
import ghidra.app.services.*;
|
||||||
|
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
|
||||||
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
|
import ghidra.async.AsyncUtils;
|
||||||
|
import ghidra.framework.options.AutoOptions;
|
||||||
|
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
||||||
|
import ghidra.framework.plugintool.*;
|
||||||
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryManager;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryState;
|
||||||
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
@PluginInfo(
|
||||||
|
shortDescription = "Compare memory state between times in a trace",
|
||||||
|
description = "Provides a side-by-side diff view between snapshots (points in time) in a " +
|
||||||
|
"trace. The comparison is limited to raw bytes.",
|
||||||
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
|
status = PluginStatus.RELEASED,
|
||||||
|
eventsConsumed = {
|
||||||
|
TraceClosedPluginEvent.class,
|
||||||
|
},
|
||||||
|
eventsProduced = {},
|
||||||
|
servicesRequired = {
|
||||||
|
DebuggerListingService.class,
|
||||||
|
},
|
||||||
|
servicesProvided = {})
|
||||||
|
public class DebuggerTraceViewDiffPlugin extends AbstractDebuggerPlugin {
|
||||||
|
protected static final String MARKER_NAME = "Trace Diff";
|
||||||
|
protected static final String MARKER_DESCRIPTION = "Difference between snapshots in this trace";
|
||||||
|
|
||||||
|
public static final String DIFF_COLOR_CATEGORY = "Listing Fields";
|
||||||
|
public static final String DIFF_COLOR_NAME = "Selection Colors.Difference Color";
|
||||||
|
public static final Color DEFAULT_DIFF_COLOR = new Color(255, 230, 180); // light orange
|
||||||
|
|
||||||
|
protected class ListingCoordinationListener implements CoordinatedListingPanelListener {
|
||||||
|
@Override
|
||||||
|
public boolean listingClosed() {
|
||||||
|
return endComparison();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void activeProgramChanged(Program activeProgram) {
|
||||||
|
endComparison();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ForAltListingTrackingTrait extends DebuggerTrackLocationTrait {
|
||||||
|
public ForAltListingTrackingTrait() {
|
||||||
|
super(DebuggerTraceViewDiffPlugin.this.getTool(), DebuggerTraceViewDiffPlugin.this,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void locationTracked() {
|
||||||
|
if (altListingPanel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// NB. Don't goTo here. The left listing controls navigation
|
||||||
|
altListingPanel.getFieldPanel().repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class SyncAltListingTrackingSpecChangeListener
|
||||||
|
implements LocationTrackingSpecChangeListener {
|
||||||
|
@Override
|
||||||
|
public void locationTrackingSpecChanged(LocationTrackingSpec spec) {
|
||||||
|
trackingTrait.setSpec(spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class MarkerSetChangeListener implements ChangeListener {
|
||||||
|
@Override
|
||||||
|
public void stateChanged(ChangeEvent e) {
|
||||||
|
if (altListingPanel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
altListingPanel.getFieldPanel().repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @AutoServiceConsumed via method
|
||||||
|
private DebuggerListingService listingService;
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private DebuggerTraceManagerService traceManager;
|
||||||
|
//@AutoServiceConsumed via method
|
||||||
|
private MarkerService markerService;
|
||||||
|
|
||||||
|
@AutoOptionConsumed(category = DIFF_COLOR_CATEGORY, name = DIFF_COLOR_NAME)
|
||||||
|
private Color diffColor = DEFAULT_DIFF_COLOR;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final AutoOptions.Wiring autoOptions;
|
||||||
|
|
||||||
|
protected final DebuggerTimeSelectionDialog timeDialog;
|
||||||
|
|
||||||
|
protected ToggleDockingAction actionCompare;
|
||||||
|
protected DockingAction actionPrevDiff;
|
||||||
|
protected DockingAction actionNextDiff;
|
||||||
|
|
||||||
|
protected ListingPanel altListingPanel;
|
||||||
|
protected final ForAltListingTrackingTrait trackingTrait;
|
||||||
|
protected boolean sessionActive;
|
||||||
|
|
||||||
|
protected final ListingCoordinationListener coordinationListener =
|
||||||
|
new ListingCoordinationListener();
|
||||||
|
protected final SyncAltListingTrackingSpecChangeListener syncTrackingSpecListener =
|
||||||
|
new SyncAltListingTrackingSpecChangeListener();
|
||||||
|
|
||||||
|
protected MultiBlendedListingBackgroundColorModel colorModel;
|
||||||
|
protected final MarkerSetChangeListener markerChangeListener = new MarkerSetChangeListener();
|
||||||
|
protected MarkerServiceBackgroundColorModel markerServiceColorModel;
|
||||||
|
|
||||||
|
protected MarkerSet diffMarkersL;
|
||||||
|
protected MarkerSet diffMarkersR;
|
||||||
|
|
||||||
|
public DebuggerTraceViewDiffPlugin(PluginTool tool) {
|
||||||
|
super(tool);
|
||||||
|
autoOptions = AutoOptions.wireOptions(this);
|
||||||
|
|
||||||
|
timeDialog = new DebuggerTimeSelectionDialog(tool);
|
||||||
|
trackingTrait = new ForAltListingTrackingTrait();
|
||||||
|
createActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createActions() {
|
||||||
|
actionCompare = CompareTimesAction.builder(this)
|
||||||
|
.enabled(false)
|
||||||
|
.enabledWhen(ctx -> traceManager != null && traceManager.getCurrentTrace() != null)
|
||||||
|
.onAction(this::activatedCompare)
|
||||||
|
.build();
|
||||||
|
actionPrevDiff = PrevDifferenceAction.builder(this)
|
||||||
|
.enabled(false)
|
||||||
|
.enabledWhen(ctx -> hasPrevDiff())
|
||||||
|
.onAction(ctx -> gotoPrevDiff())
|
||||||
|
.build();
|
||||||
|
actionNextDiff = NextDifferenceAction.builder(this)
|
||||||
|
.enabled(false)
|
||||||
|
.enabledWhen(ctx -> hasNextDiff())
|
||||||
|
.onAction(ctx -> gotoNextDiff())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void activatedCompare(ActionContext ctx) {
|
||||||
|
if (!actionCompare.isSelected()) {
|
||||||
|
endComparison();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sessionActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DebuggerCoordinates current = traceManager.getCurrent();
|
||||||
|
TraceSchedule time = timeDialog.promptTime(current.getTrace(), current.getTime());
|
||||||
|
if (time == null) {
|
||||||
|
// Cancelled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (traceManager == null) {
|
||||||
|
// Can happen if tool is closed while dialog was up
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (traceManager.getCurrentTrace() != current.getTrace()) {
|
||||||
|
Msg.warn(this, "Trace changed during time prompt. Aborting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// NB. startComparison will handle failure
|
||||||
|
startComparison(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin a snapshot/time comparison session
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* NOTE: This method handles asynchronous errors by popping an error dialog. Callers need not
|
||||||
|
* handle exceptional completion.
|
||||||
|
*
|
||||||
|
* @param time the alternative time
|
||||||
|
* @return a future which completes when the alternative listing and difference is presented
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Void> startComparison(TraceSchedule time) {
|
||||||
|
sessionActive = true; // prevents the action from performing anything
|
||||||
|
actionCompare.setSelected(true);
|
||||||
|
|
||||||
|
DebuggerCoordinates current = traceManager.getCurrent();
|
||||||
|
DebuggerCoordinates alternate =
|
||||||
|
traceManager.resolveCoordinates(DebuggerCoordinates.time(time));
|
||||||
|
PluginToolExecutorService toolExecutorService =
|
||||||
|
new PluginToolExecutorService(tool, "Computing diff", true, true, false, 500);
|
||||||
|
return traceManager.materialize(alternate).thenApplyAsync(snap -> {
|
||||||
|
clearMarkers();
|
||||||
|
TraceProgramView altView = alternate.getTrace().getFixedProgramView(snap);
|
||||||
|
altListingPanel.setProgram(altView);
|
||||||
|
trackingTrait.goToCoordinates(alternate.withView(altView));
|
||||||
|
listingService.setListingPanel(altListingPanel);
|
||||||
|
return altView;
|
||||||
|
}, AsyncUtils.SWING_EXECUTOR).thenApplyAsync(altView -> {
|
||||||
|
return computeDiff(current.getView(), altView);
|
||||||
|
}, toolExecutorService).thenAcceptAsync(diffSet -> {
|
||||||
|
addMarkers(diffSet);
|
||||||
|
listingService.addLocalAction(actionNextDiff);
|
||||||
|
listingService.addLocalAction(actionPrevDiff);
|
||||||
|
updateActions();
|
||||||
|
}, AsyncUtils.SWING_EXECUTOR).exceptionally(ex -> {
|
||||||
|
Msg.showError(this, null, "Compare", "Could not compare trace snapshots/times", ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateActions() {
|
||||||
|
// May not be necessary often, since contextChanged in ListingProvider should do it
|
||||||
|
actionNextDiff.setEnabled(actionNextDiff.isEnabledForContext(null));
|
||||||
|
actionPrevDiff.setEnabled(actionPrevDiff.isEnabledForContext(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean endComparison() {
|
||||||
|
sessionActive = false;
|
||||||
|
actionCompare.setSelected(false);
|
||||||
|
clearMarkers();
|
||||||
|
if (altListingPanel.getProgram() != null) {
|
||||||
|
listingService.removeListingPanel(altListingPanel);
|
||||||
|
altListingPanel.setProgram(null);
|
||||||
|
|
||||||
|
listingService.removeLocalAction(actionNextDiff);
|
||||||
|
listingService.removeLocalAction(actionPrevDiff);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Address getCurrentAddress() {
|
||||||
|
if (listingService == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ProgramLocation loc = listingService.getCurrentLocation();
|
||||||
|
if (loc == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return loc.getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressSetView getDiffs() {
|
||||||
|
if (diffMarkersL == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return diffMarkersL.getAddressSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasSeqDiff(Function<AddressSetView, AddressRange> getExtremeRange,
|
||||||
|
BiPredicate<AddressRange, Address> checkRange) {
|
||||||
|
Address cur = getCurrentAddress();
|
||||||
|
if (cur == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AddressSetView set = getDiffs();
|
||||||
|
if (set == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AddressRange extreme = getExtremeRange.apply(set);
|
||||||
|
if (extreme == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return checkRange.test(extreme, cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPrevDiff() {
|
||||||
|
return hasSeqDiff(AddressSetView::getFirstRange,
|
||||||
|
(first, cur) -> first.getMaxAddress().compareTo(cur) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNextDiff() {
|
||||||
|
return hasSeqDiff(AddressSetView::getLastRange,
|
||||||
|
(last, cur) -> cur.compareTo(last.getMinAddress()) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Address getSeqDiff(boolean forward,
|
||||||
|
Function<AddressRange, Address> getFarthestAddress,
|
||||||
|
Function<Address, Address> getStepped) {
|
||||||
|
Address cur = getCurrentAddress();
|
||||||
|
if (cur == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AddressSetView set = getDiffs();
|
||||||
|
if (set == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AddressRange range = set.getRangeContaining(cur);
|
||||||
|
if (range != null) {
|
||||||
|
cur = getFarthestAddress.apply(range);
|
||||||
|
}
|
||||||
|
cur = getStepped.apply(cur);
|
||||||
|
if (cur == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
AddressIterator it = set.getAddresses(cur, forward);
|
||||||
|
if (!it.hasNext()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address getPrevDiff() {
|
||||||
|
return getSeqDiff(false, AddressRange::getMinAddress, Address::previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address getNextDiff() {
|
||||||
|
return getSeqDiff(true, AddressRange::getMaxAddress, Address::next);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean gotoPrevDiff() {
|
||||||
|
Address prevDiff = getPrevDiff();
|
||||||
|
if (prevDiff == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return listingService.goTo(prevDiff, true) && altListingPanel.goTo(prevDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean gotoNextDiff() {
|
||||||
|
Address nextDiff = getNextDiff();
|
||||||
|
if (nextDiff == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return listingService.goTo(nextDiff, true) && altListingPanel.goTo(nextDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void injectOnListingService() {
|
||||||
|
if (listingService != null) {
|
||||||
|
listingService.addLocalAction(actionCompare);
|
||||||
|
altListingPanel = new ListingPanel(listingService.getFormatManager());
|
||||||
|
listingService.setCoordinatedListingPanelListener(coordinationListener);
|
||||||
|
listingService.addTrackingSpecChangeListener(syncTrackingSpecListener);
|
||||||
|
|
||||||
|
colorModel = listingService.createListingBackgroundColorModel(altListingPanel);
|
||||||
|
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(altListingPanel));
|
||||||
|
altListingPanel.setBackgroundColorModel(colorModel);
|
||||||
|
updateMarkerServiceColorModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ejectFromListingService() {
|
||||||
|
if (altListingPanel != null) {
|
||||||
|
altListingPanel.dispose();
|
||||||
|
altListingPanel = null;
|
||||||
|
}
|
||||||
|
colorModel = null;
|
||||||
|
if (listingService != null) {
|
||||||
|
listingService.removeLocalAction(actionCompare);
|
||||||
|
listingService.setCoordinatedListingPanelListener(null);
|
||||||
|
listingService.removeTrackingSpecChangeListener(syncTrackingSpecListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private void setListingService(DebuggerListingService listingService) {
|
||||||
|
ejectFromListingService();
|
||||||
|
this.listingService = listingService;
|
||||||
|
injectOnListingService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateMarkerServiceColorModel() {
|
||||||
|
if (colorModel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
colorModel.removeModel(markerServiceColorModel);
|
||||||
|
if (markerService != null && altListingPanel != null) {
|
||||||
|
colorModel.addModel(markerServiceColorModel = new MarkerServiceBackgroundColorModel(
|
||||||
|
markerService, altListingPanel.getProgram(), altListingPanel.getAddressIndexMap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createMarkers() {
|
||||||
|
if (diffMarkersL != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (markerService == null) {
|
||||||
|
diffMarkersL = null;
|
||||||
|
diffMarkersR = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (altListingPanel == null) {
|
||||||
|
diffMarkersL = null;
|
||||||
|
diffMarkersR = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Program viewR = altListingPanel.getProgram();
|
||||||
|
if (viewR == null) {
|
||||||
|
diffMarkersR = null;
|
||||||
|
diffMarkersL = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Color diffColor = this.diffColor == null ? DEFAULT_DIFF_COLOR : this.diffColor;
|
||||||
|
TraceProgramView viewL = traceManager.getCurrentView();
|
||||||
|
diffMarkersL = markerService.createAreaMarker(MARKER_NAME, MARKER_DESCRIPTION, viewL, 0,
|
||||||
|
true, true, true, diffColor, true);
|
||||||
|
diffMarkersR = markerService.createAreaMarker(MARKER_NAME, MARKER_DESCRIPTION, viewR, 0,
|
||||||
|
true, true, true, diffColor, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addMarkers(AddressSetView diffSet) {
|
||||||
|
createMarkers();
|
||||||
|
if (diffMarkersL != null) {
|
||||||
|
diffMarkersL.add(diffSet);
|
||||||
|
}
|
||||||
|
if (diffMarkersR != null) {
|
||||||
|
diffMarkersR.add(diffSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void clearMarkers() {
|
||||||
|
if (diffMarkersL != null) {
|
||||||
|
diffMarkersL.clearAll();
|
||||||
|
}
|
||||||
|
if (diffMarkersR != null) {
|
||||||
|
diffMarkersR.clearAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void deleteMarkers() {
|
||||||
|
if (diffMarkersL == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (markerService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (altListingPanel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Program altView = altListingPanel.getProgram();
|
||||||
|
if (altView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
markerService.removeMarker(diffMarkersL, altView);
|
||||||
|
markerService.removeMarker(diffMarkersR, altView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private void setMarkerService(MarkerService markerService) {
|
||||||
|
if (this.markerService != null) {
|
||||||
|
this.markerService.removeChangeListener(markerChangeListener);
|
||||||
|
deleteMarkers();
|
||||||
|
}
|
||||||
|
this.markerService = markerService;
|
||||||
|
updateMarkerServiceColorModel();
|
||||||
|
|
||||||
|
if (this.markerService != null) {
|
||||||
|
this.markerService.addChangeListener(markerChangeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoOptionConsumed(category = DIFF_COLOR_CATEGORY, name = DIFF_COLOR_NAME)
|
||||||
|
private void setDiffColor(Color diffColor) {
|
||||||
|
if (diffMarkersL != null) {
|
||||||
|
diffMarkersL.setMarkerColor(diffColor);
|
||||||
|
}
|
||||||
|
if (diffMarkersR != null) {
|
||||||
|
diffMarkersR.setMarkerColor(diffColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processEvent(PluginEvent event) {
|
||||||
|
super.processEvent(event);
|
||||||
|
if (event instanceof TraceClosedPluginEvent) {
|
||||||
|
TraceClosedPluginEvent evt = (TraceClosedPluginEvent) event;
|
||||||
|
if (timeDialog.getTrace() == evt.getTrace()) {
|
||||||
|
timeDialog.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int lenRemainsBlock(int blockSize, long off) {
|
||||||
|
return blockSize - (int) (off % blockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long minOfBlock(int blockSize, long off) {
|
||||||
|
return off / blockSize * blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long maxOfBlock(int blockSize, long off) {
|
||||||
|
return (off + blockSize - 1) / blockSize * blockSize - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Address maxOfBlock(int blockSize, Address address) {
|
||||||
|
long off = address.getOffset();
|
||||||
|
long max = maxOfBlock(blockSize, off);
|
||||||
|
AddressSpace space = address.getAddressSpace();
|
||||||
|
return space.getAddress(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AddressRange blockFor(int blockSize, Address address) {
|
||||||
|
long off = address.getOffset();
|
||||||
|
// TODO: Require powers of 2?
|
||||||
|
long min = minOfBlock(blockSize, off);
|
||||||
|
long max = maxOfBlock(blockSize, off);
|
||||||
|
AddressSpace space = address.getAddressSpace();
|
||||||
|
return new AddressRangeImpl(space.getAddress(min), space.getAddress(max));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AddressSetView computeDiff(TraceProgramView view1, TraceProgramView view2) {
|
||||||
|
Trace trace = view1.getTrace();
|
||||||
|
assert trace == view2.getTrace();
|
||||||
|
long snap1 = view1.getSnap();
|
||||||
|
long snap2 = view2.getSnap();
|
||||||
|
|
||||||
|
if (snap1 == snap2) {
|
||||||
|
// Punt on the degenerate case
|
||||||
|
return new AddressSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceMemoryManager mm = trace.getMemoryManager();
|
||||||
|
|
||||||
|
AddressSetView known1 = mm.getAddressesWithState(snap1, s -> s == TraceMemoryState.KNOWN);
|
||||||
|
AddressSetView known2 = mm.getAddressesWithState(snap2, s -> s == TraceMemoryState.KNOWN);
|
||||||
|
|
||||||
|
//AddressSet knownEither = known1.union(known2);
|
||||||
|
AddressSet knownBoth = known1.intersect(known2); // Will need byte-by-byte examination
|
||||||
|
|
||||||
|
// Symmetric difference in state counts as difference?
|
||||||
|
// TODO: Should that be togglable?
|
||||||
|
|
||||||
|
AddressSet diff = new AddressSet(); //knownEither;
|
||||||
|
//knownEither = null; // Don't need knownEither anymore. Avoid accidental use
|
||||||
|
//diff.delete(knownBoth);
|
||||||
|
|
||||||
|
int blockSize = mm.getBlockSize();
|
||||||
|
if (blockSize == 0) {
|
||||||
|
throw new UnsupportedOperationException("TODO: Unoptimized byte diff");
|
||||||
|
}
|
||||||
|
ByteBuffer buf1 = ByteBuffer.allocate(blockSize);
|
||||||
|
ByteBuffer buf2 = ByteBuffer.allocate(blockSize);
|
||||||
|
|
||||||
|
while (!knownBoth.isEmpty()) {
|
||||||
|
Address next = knownBoth.getMinAddress();
|
||||||
|
Long mrs1 = mm.getSnapOfMostRecentChangeToBlock(snap1, next);
|
||||||
|
Long mrs2 = mm.getSnapOfMostRecentChangeToBlock(snap2, next);
|
||||||
|
if (Objects.equals(mrs1, mrs2)) {
|
||||||
|
knownBoth.delete(blockFor(blockSize, next));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = lenRemainsBlock(blockSize, next.getOffset());
|
||||||
|
buf1.clear();
|
||||||
|
buf1.limit(len);
|
||||||
|
if (len != mm.getBytes(snap1, next, buf1)) {
|
||||||
|
throw new AssertionError("Read failed");
|
||||||
|
}
|
||||||
|
buf2.clear();
|
||||||
|
buf2.limit(len);
|
||||||
|
if (len != mm.getBytes(snap2, next, buf2)) {
|
||||||
|
throw new AssertionError("Read failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
compareBytes(diff, next, buf1, buf2);
|
||||||
|
knownBoth.delete(blockFor(blockSize, next));
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void compareBytes(AddressSet diff, Address addr, ByteBuffer buf1, ByteBuffer buf2) {
|
||||||
|
int len = buf1.limit();
|
||||||
|
byte[] arr1 = buf1.array();
|
||||||
|
byte[] arr2 = buf2.array();
|
||||||
|
Address rngStart = null;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (arr1[i] != arr2[i]) {
|
||||||
|
if (rngStart == null) {
|
||||||
|
rngStart = addr.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (rngStart != null) {
|
||||||
|
diff.add(rngStart, addr.add(i - 1));
|
||||||
|
rngStart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rngStart != null) {
|
||||||
|
diff.add(rngStart, addr.add(len - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import ghidra.app.util.viewer.util.AddressIndexMap;
|
||||||
import ghidra.framework.options.AutoOptions;
|
import ghidra.framework.options.AutoOptions;
|
||||||
import ghidra.framework.options.AutoOptions.Wiring;
|
import ghidra.framework.options.AutoOptions.Wiring;
|
||||||
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
||||||
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ class CursorBackgroundColorModel implements ListingBackgroundColorModel {
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final Wiring autoOptionsWiring;
|
private final Wiring autoOptionsWiring;
|
||||||
|
|
||||||
public CursorBackgroundColorModel(DebuggerListingPlugin plugin, ListingPanel listingPanel) {
|
public CursorBackgroundColorModel(Plugin plugin, ListingPanel listingPanel) {
|
||||||
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
||||||
modelDataChanged(listingPanel);
|
modelDataChanged(listingPanel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
|
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.viewer.format.FormatManager;
|
import ghidra.app.util.viewer.format.FormatManager;
|
||||||
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.framework.options.AutoOptions;
|
import ghidra.framework.options.AutoOptions;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.options.annotation.AutoOptionDefined;
|
import ghidra.framework.options.annotation.AutoOptionDefined;
|
||||||
|
@ -162,6 +163,16 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
|
||||||
createActions();
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultiBlendedListingBackgroundColorModel createListingBackgroundColorModel(
|
||||||
|
ListingPanel listingPanel) {
|
||||||
|
MultiBlendedListingBackgroundColorModel colorModel =
|
||||||
|
new MultiBlendedListingBackgroundColorModel();
|
||||||
|
colorModel.addModel(new MemoryStateListingBackgroundColorModel(this, listingPanel));
|
||||||
|
colorModel.addModel(new CursorBackgroundColorModel(this, listingPanel));
|
||||||
|
return colorModel;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DebuggerListingProvider createProvider(FormatManager formatManager,
|
protected DebuggerListingProvider createProvider(FormatManager formatManager,
|
||||||
boolean isConnected) {
|
boolean isConnected) {
|
||||||
|
@ -322,6 +333,21 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
|
||||||
connectedProvider.setTrackingSpec(spec);
|
connectedProvider.setTrackingSpec(spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocationTrackingSpec getTrackingSpec() {
|
||||||
|
return connectedProvider.getTrackingSpec();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
|
||||||
|
connectedProvider.addTrackingSpecChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
|
||||||
|
connectedProvider.removeTrackingSpecChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCurrentSelection(ProgramSelection selection) {
|
public void setCurrentSelection(ProgramSelection selection) {
|
||||||
connectedProvider.setSelection(selection);
|
connectedProvider.setSelection(selection);
|
||||||
|
|
|
@ -53,6 +53,7 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionConte
|
||||||
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
|
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
|
||||||
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
|
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
|
||||||
import ghidra.app.util.viewer.format.FormatManager;
|
import ghidra.app.util.viewer.format.FormatManager;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
|
@ -71,8 +72,9 @@ import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.modules.*;
|
import ghidra.trace.model.modules.*;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.*;
|
||||||
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.VersionException;
|
import ghidra.util.exception.VersionException;
|
||||||
import ghidra.util.task.*;
|
import ghidra.util.task.*;
|
||||||
|
@ -182,6 +184,11 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
DebuggerListingProvider.this);
|
DebuggerListingProvider.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void specChanged(LocationTrackingSpec spec) {
|
||||||
|
trackingSpecChangeListeners.fire.locationTrackingSpecChanged(spec);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void locationTracked() {
|
protected void locationTracked() {
|
||||||
doGoToTracked();
|
doGoToTracked();
|
||||||
|
@ -247,9 +254,12 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
protected boolean followsCurrentThread = true;
|
protected boolean followsCurrentThread = true;
|
||||||
// TODO: followsCurrentSnap?
|
// TODO: followsCurrentSnap?
|
||||||
|
|
||||||
protected DebuggerGoToTrait goToTrait;
|
protected final DebuggerGoToTrait goToTrait;
|
||||||
protected ForListingTrackingTrait trackingTrait;
|
protected final ForListingTrackingTrait trackingTrait;
|
||||||
protected ForListingReadsMemoryTrait readsMemTrait;
|
protected final ForListingReadsMemoryTrait readsMemTrait;
|
||||||
|
|
||||||
|
protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners =
|
||||||
|
new ListenerSet<>(LocationTrackingSpecChangeListener.class);
|
||||||
|
|
||||||
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
|
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
|
||||||
|
|
||||||
|
@ -275,10 +285,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
readsMemTrait = new ForListingReadsMemoryTrait();
|
readsMemTrait = new ForListingReadsMemoryTrait();
|
||||||
|
|
||||||
ListingPanel listingPanel = getListingPanel();
|
ListingPanel listingPanel = getListingPanel();
|
||||||
colorModel = new MultiBlendedListingBackgroundColorModel();
|
colorModel = plugin.createListingBackgroundColorModel(listingPanel);
|
||||||
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel));
|
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel));
|
||||||
colorModel.addModel(new MemoryStateListingBackgroundColorModel(plugin, listingPanel));
|
|
||||||
colorModel.addModel(new CursorBackgroundColorModel(plugin, listingPanel));
|
|
||||||
listingPanel.setBackgroundColorModel(colorModel);
|
listingPanel.setBackgroundColorModel(colorModel);
|
||||||
|
|
||||||
autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
@ -493,12 +501,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
if (this.markerService != null) {
|
if (this.markerService != null) {
|
||||||
this.markerService.removeChangeListener(markerChangeListener);
|
this.markerService.removeChangeListener(markerChangeListener);
|
||||||
}
|
}
|
||||||
this.markerService = markerService;
|
|
||||||
updateMarkerServiceColorModel();
|
|
||||||
|
|
||||||
removeOldStaticTrackingMarker();
|
removeOldStaticTrackingMarker();
|
||||||
this.markerService = markerService;
|
this.markerService = markerService;
|
||||||
createNewStaticTrackingMarker();
|
createNewStaticTrackingMarker();
|
||||||
|
updateMarkerServiceColorModel();
|
||||||
|
|
||||||
if (this.markerService != null && !isMainListing()) {
|
if (this.markerService != null && !isMainListing()) {
|
||||||
// NOTE: Connected provider marker listener is taken care of by CodeBrowserPlugin
|
// NOTE: Connected provider marker listener is taken care of by CodeBrowserPlugin
|
||||||
|
@ -594,6 +600,38 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
setSubTitle(computeSubTitle());
|
setSubTitle(computeSubTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String computePanelTitle(Program panelProgram) {
|
||||||
|
if (!(panelProgram instanceof TraceProgramView)) {
|
||||||
|
// really shouldn't happen anyway...
|
||||||
|
return super.computePanelTitle(panelProgram);
|
||||||
|
}
|
||||||
|
TraceProgramView view = (TraceProgramView) panelProgram;
|
||||||
|
TraceSnapshot snapshot =
|
||||||
|
view.getTrace().getTimeManager().getSnapshot(view.getSnap(), false);
|
||||||
|
if (snapshot == null) {
|
||||||
|
return Long.toString(view.getSnap());
|
||||||
|
}
|
||||||
|
String description = snapshot.getDescription();
|
||||||
|
String schedule = snapshot.getScheduleString();
|
||||||
|
if (description == null) {
|
||||||
|
description = "";
|
||||||
|
}
|
||||||
|
if (schedule == null) {
|
||||||
|
schedule = "";
|
||||||
|
}
|
||||||
|
if (!description.isBlank() && !schedule.isBlank()) {
|
||||||
|
return description + " (" + schedule + ")";
|
||||||
|
}
|
||||||
|
if (!description.isBlank()) {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
if (!schedule.isBlank()) {
|
||||||
|
return schedule;
|
||||||
|
}
|
||||||
|
return DateUtils.formatDateTimestamp(new Date(snapshot.getRealTime()));
|
||||||
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
if (isMainListing()) {
|
if (isMainListing()) {
|
||||||
actionSyncToStaticListing = new SyncToStaticListingAction();
|
actionSyncToStaticListing = new SyncToStaticListingAction();
|
||||||
|
@ -842,6 +880,14 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
return trackingTrait.getSpec();
|
return trackingTrait.getSpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
|
||||||
|
trackingSpecChangeListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
|
||||||
|
trackingSpecChangeListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public void setSyncToStaticListing(boolean sync) {
|
public void setSyncToStaticListing(boolean sync) {
|
||||||
if (!isMainListing()) {
|
if (!isMainListing()) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
|
|
@ -25,6 +25,7 @@ import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.app.util.viewer.util.AddressIndexMap;
|
import ghidra.app.util.viewer.util.AddressIndexMap;
|
||||||
import ghidra.framework.options.AutoOptions;
|
import ghidra.framework.options.AutoOptions;
|
||||||
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
||||||
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.trace.model.TraceAddressSnapRange;
|
import ghidra.trace.model.TraceAddressSnapRange;
|
||||||
|
@ -47,7 +48,7 @@ public class MemoryStateListingBackgroundColorModel implements ListingBackground
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoOptions.Wiring autoOptionsWiring;
|
private final AutoOptions.Wiring autoOptionsWiring;
|
||||||
|
|
||||||
public MemoryStateListingBackgroundColorModel(DebuggerListingPlugin plugin,
|
public MemoryStateListingBackgroundColorModel(Plugin plugin,
|
||||||
ListingPanel listingPanel) {
|
ListingPanel listingPanel) {
|
||||||
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
||||||
modelDataChanged(listingPanel);
|
modelDataChanged(listingPanel);
|
||||||
|
|
|
@ -379,7 +379,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
// TODO: Should I receive clicks on that renderer to seek to a given snap?
|
// TODO: Should I receive clicks on that renderer to seek to a given snap?
|
||||||
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
||||||
|
|
||||||
myActionContext = new DebuggerSnapActionContext(0);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
||||||
createActions();
|
createActions();
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
|
||||||
|
@ -617,7 +617,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
snap = 0;
|
snap = 0;
|
||||||
}
|
}
|
||||||
traceManager.activateSnap(snap);
|
traceManager.activateSnap(snap);
|
||||||
myActionContext = new DebuggerSnapActionContext(snap);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
||||||
contextChanged();
|
contextChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,262 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.time;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.TableColumn;
|
||||||
|
import javax.swing.table.TableColumnModel;
|
||||||
|
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
|
||||||
|
import docking.widgets.table.*;
|
||||||
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
|
import ghidra.framework.model.DomainObject;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||||
|
import ghidra.trace.model.TraceDomainObjectListener;
|
||||||
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
|
||||||
|
public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
|
|
||||||
|
protected enum SnapshotTableColumns
|
||||||
|
implements EnumeratedTableColumn<SnapshotTableColumns, SnapshotRow> {
|
||||||
|
SNAP("Snap", Long.class, SnapshotRow::getSnap),
|
||||||
|
TIMESTAMP("Timestamp", String.class, SnapshotRow::getTimeStamp), // TODO: Use Date type here
|
||||||
|
EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName),
|
||||||
|
SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule),
|
||||||
|
DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription);
|
||||||
|
|
||||||
|
private final String header;
|
||||||
|
private final Function<SnapshotRow, ?> getter;
|
||||||
|
private final BiConsumer<SnapshotRow, Object> setter;
|
||||||
|
private final Class<?> cls;
|
||||||
|
|
||||||
|
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter) {
|
||||||
|
this(header, cls, getter, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter,
|
||||||
|
BiConsumer<SnapshotRow, T> setter) {
|
||||||
|
this.header = header;
|
||||||
|
this.cls = cls;
|
||||||
|
this.getter = getter;
|
||||||
|
this.setter = (BiConsumer<SnapshotRow, Object>) setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getValueClass() {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueOf(SnapshotRow row) {
|
||||||
|
return getter.apply(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader() {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEditable(SnapshotRow row) {
|
||||||
|
return setter != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueOf(SnapshotRow row, Object value) {
|
||||||
|
setter.accept(row, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SnapshotListener extends TraceDomainObjectListener {
|
||||||
|
public SnapshotListener() {
|
||||||
|
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
||||||
|
|
||||||
|
listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded);
|
||||||
|
listenFor(TraceSnapshotChangeType.CHANGED, this::snapChanged);
|
||||||
|
listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void objectRestored() {
|
||||||
|
loadSnapshots();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void snapAdded(TraceSnapshot snapshot) {
|
||||||
|
if (snapshot.getKey() < 0 && hideScratch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SnapshotRow row = new SnapshotRow(currentTrace, snapshot);
|
||||||
|
snapshotTableModel.add(row);
|
||||||
|
if (currentSnap == snapshot.getKey()) {
|
||||||
|
snapshotFilterPanel.setSelectedItem(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void snapChanged(TraceSnapshot snapshot) {
|
||||||
|
if (snapshot.getKey() < 0 && hideScratch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
snapshotTableModel.notifyUpdatedWith(row -> row.getSnapshot() == snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void snapDeleted(TraceSnapshot snapshot) {
|
||||||
|
if (snapshot.getKey() < 0 && hideScratch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel =
|
||||||
|
new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class);
|
||||||
|
protected final GTable snapshotTable;
|
||||||
|
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
|
||||||
|
protected boolean hideScratch = true;
|
||||||
|
|
||||||
|
private Trace currentTrace;
|
||||||
|
private Long currentSnap;
|
||||||
|
|
||||||
|
protected final SnapshotListener listener = new SnapshotListener();
|
||||||
|
|
||||||
|
public DebuggerSnapshotTablePanel() {
|
||||||
|
super(new BorderLayout());
|
||||||
|
snapshotTable = new GTable(snapshotTableModel);
|
||||||
|
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
add(new JScrollPane(snapshotTable));
|
||||||
|
|
||||||
|
snapshotFilterPanel = new GhidraTableFilterPanel<>(snapshotTable, snapshotTableModel);
|
||||||
|
add(snapshotFilterPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
TableColumnModel columnModel = snapshotTable.getColumnModel();
|
||||||
|
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
|
||||||
|
snapCol.setPreferredWidth(40);
|
||||||
|
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
|
||||||
|
timeCol.setPreferredWidth(200);
|
||||||
|
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
|
||||||
|
etCol.setPreferredWidth(40);
|
||||||
|
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
|
||||||
|
schdCol.setPreferredWidth(60);
|
||||||
|
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
|
||||||
|
descCol.setPreferredWidth(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNewListeners() {
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentTrace.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeOldListeners() {
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentTrace.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTrace(Trace trace) {
|
||||||
|
if (currentTrace == trace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeOldListeners();
|
||||||
|
currentTrace = trace;
|
||||||
|
addNewListeners();
|
||||||
|
loadSnapshots();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Trace getTrace() {
|
||||||
|
return currentTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHideScratchSnapshots(boolean hideScratch) {
|
||||||
|
if (this.hideScratch == hideScratch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hideScratch = hideScratch;
|
||||||
|
if (hideScratch) {
|
||||||
|
deleteScratchSnapshots();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadScratchSnapshots();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadSnapshots() {
|
||||||
|
snapshotTableModel.clear();
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceTimeManager manager = currentTrace.getTimeManager();
|
||||||
|
Collection<? extends TraceSnapshot> snapshots = hideScratch
|
||||||
|
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
|
||||||
|
: manager.getAllSnapshots();
|
||||||
|
snapshotTableModel.addAll(Collections2.transform(snapshots,
|
||||||
|
s -> new SnapshotRow(currentTrace, s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void deleteScratchSnapshots() {
|
||||||
|
snapshotTableModel.deleteWith(s -> s.getSnap() < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadScratchSnapshots() {
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceTimeManager manager = currentTrace.getTimeManager();
|
||||||
|
snapshotTableModel.addAll(Collections2.transform(
|
||||||
|
manager.getSnapshots(Long.MIN_VALUE, true, 0, false),
|
||||||
|
s -> new SnapshotRow(currentTrace, s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListSelectionModel getSelectionModel() {
|
||||||
|
return snapshotTable.getSelectionModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSelectedSnapshot() {
|
||||||
|
SnapshotRow row = snapshotFilterPanel.getSelectedItem();
|
||||||
|
return row == null ? null : row.getSnap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedSnapshot(Long snap) {
|
||||||
|
currentSnap = snap;
|
||||||
|
if (snap == null) {
|
||||||
|
snapshotTable.clearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnapshotRow sel = snapshotFilterPanel.getSelectedItem();
|
||||||
|
Long curSnap = sel == null ? null : sel.getSnap();
|
||||||
|
if (Objects.equals(curSnap, snap)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SnapshotRow row = snapshotTableModel.findFirst(r -> r.getSnap() == snap);
|
||||||
|
if (row == null) {
|
||||||
|
snapshotTable.clearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
snapshotFilterPanel.setSelectedItem(row);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,14 +15,29 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.time;
|
package ghidra.app.plugin.core.debug.gui.time;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
|
import docking.action.DockingAction;
|
||||||
|
import docking.widgets.dialogs.InputDialog;
|
||||||
|
import ghidra.app.context.ProgramLocationActionContext;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.RenameSnapshotAction;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
shortDescription = "Lists recorded snapshots in a trace",
|
shortDescription = "Lists recorded snapshots in a trace",
|
||||||
|
@ -39,8 +54,12 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
||||||
protected DebuggerTimeProvider provider;
|
protected DebuggerTimeProvider provider;
|
||||||
|
|
||||||
|
protected DockingAction actionRenameSnapshot;
|
||||||
|
|
||||||
public DebuggerTimePlugin(PluginTool tool) {
|
public DebuggerTimePlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
|
||||||
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -49,6 +68,58 @@ public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
||||||
super.init();
|
super.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void createActions() {
|
||||||
|
actionRenameSnapshot = RenameSnapshotAction.builder(this)
|
||||||
|
.enabled(false)
|
||||||
|
.enabledWhen(ctx -> contextGetTraceSnap(ctx) != null)
|
||||||
|
.onAction(this::activatedRenameSnapshot)
|
||||||
|
.buildAndInstall(tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Entry<Trace, Long> contextGetTraceSnap(ActionContext context) {
|
||||||
|
if (context instanceof ProgramLocationActionContext) {
|
||||||
|
ProgramLocationActionContext ctx = (ProgramLocationActionContext) context;
|
||||||
|
Program program = ctx.getProgram();
|
||||||
|
if (program instanceof TraceProgramView) {
|
||||||
|
TraceProgramView view = (TraceProgramView) program;
|
||||||
|
return Map.entry(view.getTrace(), view.getSnap());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (context instanceof DebuggerSnapActionContext) {
|
||||||
|
DebuggerSnapActionContext ctx = (DebuggerSnapActionContext) context;
|
||||||
|
if (ctx.getTrace() != null) {
|
||||||
|
return Map.entry(ctx.getTrace(), ctx.getSnap());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void activatedRenameSnapshot(ActionContext context) {
|
||||||
|
Entry<Trace, Long> traceSnap = contextGetTraceSnap(context);
|
||||||
|
if (traceSnap == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Trace trace = traceSnap.getKey();
|
||||||
|
long snap = traceSnap.getValue();
|
||||||
|
TraceTimeManager manager = trace.getTimeManager();
|
||||||
|
TraceSnapshot snapshot = manager.getSnapshot(snap, false);
|
||||||
|
|
||||||
|
InputDialog dialog = new InputDialog("Rename Snapshot", "Description",
|
||||||
|
snapshot == null ? "" : snapshot.getDescription());
|
||||||
|
tool.showDialog(dialog);
|
||||||
|
if (dialog.isCanceled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Rename Snapshot", true)) {
|
||||||
|
if (snapshot == null) {
|
||||||
|
snapshot = manager.getSnapshot(snap, true);
|
||||||
|
}
|
||||||
|
snapshot.setDescription(dialog.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dispose() {
|
protected void dispose() {
|
||||||
tool.removeComponentProvider(provider);
|
tool.removeComponentProvider(provider);
|
||||||
|
|
|
@ -17,138 +17,30 @@ package ghidra.app.plugin.core.debug.gui.time;
|
||||||
|
|
||||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.table.TableColumn;
|
|
||||||
import javax.swing.table.TableColumnModel;
|
|
||||||
|
|
||||||
import com.google.common.collect.Collections2;
|
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.action.ToggleDockingAction;
|
import docking.action.ToggleDockingAction;
|
||||||
import docking.widgets.table.*;
|
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.framework.model.DomainObject;
|
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.trace.model.Trace;
|
|
||||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
|
||||||
import ghidra.trace.model.TraceDomainObjectListener;
|
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
|
||||||
import ghidra.trace.model.time.TraceTimeManager;
|
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
|
||||||
|
|
||||||
public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
private static final AutoConfigState.ClassHandler<DebuggerTimeProvider> CONFIG_STATE_HANDLER =
|
private static final AutoConfigState.ClassHandler<DebuggerTimeProvider> CONFIG_STATE_HANDLER =
|
||||||
AutoConfigState.wireHandler(DebuggerTimeProvider.class, MethodHandles.lookup());
|
AutoConfigState.wireHandler(DebuggerTimeProvider.class, MethodHandles.lookup());
|
||||||
|
|
||||||
protected enum SnapshotTableColumns
|
|
||||||
implements EnumeratedTableColumn<SnapshotTableColumns, SnapshotRow> {
|
|
||||||
SNAP("Snap", Long.class, SnapshotRow::getSnap),
|
|
||||||
TIMESTAMP("Timestamp", String.class, SnapshotRow::getTimeStamp), // TODO: Use Date type here
|
|
||||||
EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName),
|
|
||||||
SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule),
|
|
||||||
DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription);
|
|
||||||
|
|
||||||
private final String header;
|
|
||||||
private final Function<SnapshotRow, ?> getter;
|
|
||||||
private final BiConsumer<SnapshotRow, Object> setter;
|
|
||||||
private final Class<?> cls;
|
|
||||||
|
|
||||||
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter) {
|
|
||||||
this(header, cls, getter, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter,
|
|
||||||
BiConsumer<SnapshotRow, T> setter) {
|
|
||||||
this.header = header;
|
|
||||||
this.cls = cls;
|
|
||||||
this.getter = getter;
|
|
||||||
this.setter = (BiConsumer<SnapshotRow, Object>) setter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<?> getValueClass() {
|
|
||||||
return cls;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getValueOf(SnapshotRow row) {
|
|
||||||
return getter.apply(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader() {
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEditable(SnapshotRow row) {
|
|
||||||
return setter != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setValueOf(SnapshotRow row, Object value) {
|
|
||||||
setter.accept(row, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SnapshotListener extends TraceDomainObjectListener {
|
|
||||||
public SnapshotListener() {
|
|
||||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
|
||||||
|
|
||||||
listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded);
|
|
||||||
listenFor(TraceSnapshotChangeType.CHANGED, this::snapChanged);
|
|
||||||
listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void objectRestored() {
|
|
||||||
loadSnapshots();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void snapAdded(TraceSnapshot snapshot) {
|
|
||||||
if (snapshot.getKey() < 0 && hideScratch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SnapshotRow row = new SnapshotRow(current.getTrace(), snapshot);
|
|
||||||
snapshotTableModel.add(row);
|
|
||||||
if (current.getSnap() == snapshot.getKey()) {
|
|
||||||
snapshotFilterPanel.setSelectedItem(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void snapChanged(TraceSnapshot snapshot) {
|
|
||||||
if (snapshot.getKey() < 0 && hideScratch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
snapshotTableModel.notifyUpdatedWith(row -> row.getSnapshot() == snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void snapDeleted(TraceSnapshot snapshot) {
|
|
||||||
if (snapshot.getKey() < 0 && hideScratch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||||
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -162,28 +54,20 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
protected final DebuggerTimePlugin plugin;
|
protected final DebuggerTimePlugin plugin;
|
||||||
|
|
||||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
private Trace currentTrace; // copy for transition
|
|
||||||
|
|
||||||
protected final SnapshotListener listener = new SnapshotListener();
|
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
protected DebuggerTraceManagerService viewManager;
|
protected DebuggerTraceManagerService viewManager;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final Wiring autoServiceWiring;
|
private final Wiring autoServiceWiring;
|
||||||
|
|
||||||
private final JPanel mainPanel = new JPanel(new BorderLayout());
|
/*testing*/ final DebuggerSnapshotTablePanel mainPanel = new DebuggerSnapshotTablePanel();
|
||||||
|
|
||||||
/* testing */ final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel =
|
|
||||||
new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class);
|
|
||||||
/* testing */ GTable snapshotTable;
|
|
||||||
/* testing */ GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
|
|
||||||
|
|
||||||
private DebuggerSnapActionContext myActionContext;
|
private DebuggerSnapActionContext myActionContext;
|
||||||
|
|
||||||
ToggleDockingAction actionHideScratch;
|
ToggleDockingAction actionHideScratch;
|
||||||
|
|
||||||
@AutoConfigStateField
|
@AutoConfigStateField
|
||||||
/* testing */ boolean hideScratch = true;
|
/*testing*/ boolean hideScratch = true;
|
||||||
|
|
||||||
public DebuggerTimeProvider(DebuggerTimePlugin plugin) {
|
public DebuggerTimeProvider(DebuggerTimePlugin plugin) {
|
||||||
super(plugin.getTool(), TITLE_PROVIDER_TIME, plugin.getName());
|
super(plugin.getTool(), TITLE_PROVIDER_TIME, plugin.getName());
|
||||||
|
@ -198,7 +82,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
buildMainPanel();
|
buildMainPanel();
|
||||||
|
|
||||||
myActionContext = new DebuggerSnapActionContext(0);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap());
|
||||||
createActions();
|
createActions();
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
|
||||||
|
@ -224,42 +108,22 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void buildMainPanel() {
|
protected void buildMainPanel() {
|
||||||
snapshotTable = new GTable(snapshotTableModel);
|
mainPanel.getSelectionModel().addListSelectionListener(evt -> {
|
||||||
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|
||||||
mainPanel.add(new JScrollPane(snapshotTable));
|
|
||||||
|
|
||||||
snapshotFilterPanel = new GhidraTableFilterPanel<>(snapshotTable, snapshotTableModel);
|
|
||||||
mainPanel.add(snapshotFilterPanel, BorderLayout.SOUTH);
|
|
||||||
|
|
||||||
snapshotTable.getSelectionModel().addListSelectionListener(evt -> {
|
|
||||||
if (evt.getValueIsAdjusting()) {
|
if (evt.getValueIsAdjusting()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SnapshotRow row = snapshotFilterPanel.getSelectedItem();
|
Long snap = mainPanel.getSelectedSnapshot();
|
||||||
if (row == null) {
|
if (snap == null) {
|
||||||
myActionContext = null;
|
myActionContext = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long snap = row.getSnap();
|
if (snap.longValue() == current.getSnap().longValue()) {
|
||||||
if (snap == current.getSnap().longValue()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
myActionContext = new DebuggerSnapActionContext(snap);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
||||||
viewManager.activateSnap(snap);
|
viewManager.activateSnap(snap);
|
||||||
contextChanged();
|
contextChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
TableColumnModel columnModel = snapshotTable.getColumnModel();
|
|
||||||
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
|
|
||||||
snapCol.setPreferredWidth(40);
|
|
||||||
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
|
|
||||||
timeCol.setPreferredWidth(200);
|
|
||||||
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
|
|
||||||
etCol.setPreferredWidth(40);
|
|
||||||
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
|
|
||||||
schdCol.setPreferredWidth(60);
|
|
||||||
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
|
|
||||||
descCol.setPreferredWidth(200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
|
@ -271,51 +135,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
private void activatedHideScratch(ActionContext ctx) {
|
private void activatedHideScratch(ActionContext ctx) {
|
||||||
hideScratch = !hideScratch;
|
hideScratch = !hideScratch;
|
||||||
if (hideScratch) {
|
mainPanel.setHideScratchSnapshots(hideScratch);
|
||||||
deleteScratchSnapshots();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
loadScratchSnapshots();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNewListeners() {
|
|
||||||
if (currentTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTrace.addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeOldListeners() {
|
|
||||||
if (currentTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTrace.removeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doSetTrace(Trace trace) {
|
|
||||||
if (currentTrace == trace) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
removeOldListeners();
|
|
||||||
currentTrace = trace;
|
|
||||||
addNewListeners();
|
|
||||||
loadSnapshots();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doSetSnap(long snap) {
|
|
||||||
SnapshotRow sel = snapshotFilterPanel.getSelectedItem();
|
|
||||||
Long curSnap = sel == null ? null : sel.getSnap();
|
|
||||||
if (curSnap != null && curSnap.longValue() == snap) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SnapshotRow row = snapshotTableModel.findFirst(r -> r.getSnap() == snap);
|
|
||||||
if (row == null) {
|
|
||||||
snapshotTable.clearSelection();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
snapshotFilterPanel.setSelectedItem(row);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
@ -325,37 +145,8 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
|
|
||||||
doSetTrace(current.getTrace());
|
mainPanel.setTrace(current.getTrace());
|
||||||
doSetSnap(current.getSnap());
|
mainPanel.setSelectedSnapshot(current.getSnap());
|
||||||
}
|
|
||||||
|
|
||||||
protected void loadSnapshots() {
|
|
||||||
snapshotTableModel.clear();
|
|
||||||
Trace curTrace = current.getTrace();
|
|
||||||
if (curTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceTimeManager manager = curTrace.getTimeManager();
|
|
||||||
Collection<? extends TraceSnapshot> snapshots = hideScratch
|
|
||||||
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
|
|
||||||
: manager.getAllSnapshots();
|
|
||||||
snapshotTableModel.addAll(Collections2.transform(snapshots,
|
|
||||||
s -> new SnapshotRow(curTrace, s)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void deleteScratchSnapshots() {
|
|
||||||
snapshotTableModel.deleteWith(s -> s.getSnap() < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void loadScratchSnapshots() {
|
|
||||||
Trace curTrace = current.getTrace();
|
|
||||||
if (curTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceTimeManager manager = curTrace.getTimeManager();
|
|
||||||
snapshotTableModel.addAll(Collections2.transform(
|
|
||||||
manager.getSnapshots(Long.MIN_VALUE, true, 0, false),
|
|
||||||
s -> new SnapshotRow(curTrace, s)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeConfigState(SaveState saveState) {
|
public void writeConfigState(SaveState saveState) {
|
||||||
|
@ -366,5 +157,6 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
|
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
|
||||||
|
|
||||||
actionHideScratch.setSelected(hideScratch);
|
actionHideScratch.setSelected(hideScratch);
|
||||||
|
mainPanel.setHideScratchSnapshots(hideScratch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.time;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
|
||||||
|
import docking.DialogComponentProvider;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.util.MessageType;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
|
private final PluginTool tool;
|
||||||
|
|
||||||
|
DebuggerSnapshotTablePanel snapshotPanel;
|
||||||
|
JTextField scheduleText;
|
||||||
|
TraceSchedule schedule;
|
||||||
|
|
||||||
|
JButton tickStep;
|
||||||
|
JButton tickBack;
|
||||||
|
JButton opStep;
|
||||||
|
JButton opBack;
|
||||||
|
|
||||||
|
public DebuggerTimeSelectionDialog(PluginTool tool) {
|
||||||
|
super("Select Time", true, true, true, false);
|
||||||
|
this.tool = tool;
|
||||||
|
populateComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doStep(Function<TraceSchedule, TraceSchedule> stepper) {
|
||||||
|
try {
|
||||||
|
TraceSchedule stepped = stepper.apply(schedule);
|
||||||
|
if (stepped == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setScheduleText(stepped.toString());
|
||||||
|
}
|
||||||
|
catch (Throwable e) {
|
||||||
|
Msg.warn(this, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void populateComponents() {
|
||||||
|
JPanel workPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
{
|
||||||
|
Box hbox = Box.createHorizontalBox();
|
||||||
|
hbox.setBorder(BorderFactory.createTitledBorder("Schedule"));
|
||||||
|
hbox.add(new JLabel("Expression: "));
|
||||||
|
scheduleText = new JTextField();
|
||||||
|
hbox.add(scheduleText);
|
||||||
|
hbox.add(new JLabel("Ticks: "));
|
||||||
|
hbox.add(tickBack = new JButton(DebuggerResources.ICON_STEP_BACK));
|
||||||
|
hbox.add(tickStep = new JButton(DebuggerResources.ICON_STEP_INTO));
|
||||||
|
hbox.add(new JLabel("Ops: "));
|
||||||
|
hbox.add(opBack = new JButton(DebuggerResources.ICON_STEP_BACK));
|
||||||
|
hbox.add(opStep = new JButton(DebuggerResources.ICON_STEP_INTO));
|
||||||
|
workPanel.add(hbox, BorderLayout.NORTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
tickBack.addActionListener(evt -> doStep(s -> s.steppedBackward(getTrace(), 1)));
|
||||||
|
tickStep.addActionListener(evt -> doStep(s -> s.steppedForward(null, 1)));
|
||||||
|
opBack.addActionListener(evt -> doStep(s -> s.steppedPcodeBackward(1)));
|
||||||
|
opStep.addActionListener(evt -> doStep(s -> s.steppedPcodeForward(null, 1)));
|
||||||
|
|
||||||
|
{
|
||||||
|
snapshotPanel = new DebuggerSnapshotTablePanel();
|
||||||
|
workPanel.add(snapshotPanel, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotPanel.getSelectionModel().addListSelectionListener(evt -> {
|
||||||
|
Long snap = snapshotPanel.getSelectedSnapshot();
|
||||||
|
if (snap == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (schedule.getSnap() == snap.longValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduleText.setText(snap.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
scheduleText.getDocument().addDocumentListener(new DocumentListener() {
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(DocumentEvent e) {
|
||||||
|
scheduleTextChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e) {
|
||||||
|
scheduleTextChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e) {
|
||||||
|
scheduleTextChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addWorkPanel(workPanel);
|
||||||
|
addOKButton();
|
||||||
|
addCancelButton();
|
||||||
|
|
||||||
|
setMinimumSize(600, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void scheduleTextChanged() {
|
||||||
|
schedule = null;
|
||||||
|
try {
|
||||||
|
schedule = TraceSchedule.parse(scheduleText.getText());
|
||||||
|
snapshotPanel.setSelectedSnapshot(schedule.getSnap());
|
||||||
|
schedule.validate(getTrace());
|
||||||
|
setStatusText("");
|
||||||
|
setOkEnabled(true);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
setStatusText(e.getMessage(), MessageType.ERROR);
|
||||||
|
setOkEnabled(false);
|
||||||
|
}
|
||||||
|
enableStepButtons(schedule != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void enableStepButtons(boolean enabled) {
|
||||||
|
tickBack.setEnabled(enabled);
|
||||||
|
tickStep.setEnabled(enabled);
|
||||||
|
opBack.setEnabled(enabled);
|
||||||
|
opStep.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // Public for test access
|
||||||
|
public void okCallback() {
|
||||||
|
assert schedule != null;
|
||||||
|
super.okCallback();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // Public for test access
|
||||||
|
public void cancelCallback() {
|
||||||
|
this.schedule = null;
|
||||||
|
super.cancelCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
snapshotPanel.setTrace(null);
|
||||||
|
snapshotPanel.setSelectedSnapshot(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts the user to select a snapshot and optionally specify a full schedule
|
||||||
|
*
|
||||||
|
* @param trace the trace from whose snapshots to select
|
||||||
|
* @param defaultTime, optionally the time to select initially
|
||||||
|
* @return the schedule, likely specifying just the snapshot selection
|
||||||
|
*/
|
||||||
|
public TraceSchedule promptTime(Trace trace, TraceSchedule defaultTime) {
|
||||||
|
snapshotPanel.setTrace(trace);
|
||||||
|
schedule = defaultTime;
|
||||||
|
scheduleText.setText(defaultTime.toString());
|
||||||
|
tool.showDialog(this);
|
||||||
|
return schedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Trace getTrace() {
|
||||||
|
return snapshotPanel.getTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduleText(String text) {
|
||||||
|
scheduleText.setText(text);
|
||||||
|
}
|
||||||
|
}
|
|
@ -661,44 +661,39 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return current.getFrame();
|
return current.getFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Long> materialize(DebuggerCoordinates coordinates) {
|
||||||
|
if (coordinates.getTime().isSnapOnly()) {
|
||||||
|
return CompletableFuture.completedFuture(coordinates.getSnap());
|
||||||
|
}
|
||||||
|
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
|
||||||
|
.getTimeManager()
|
||||||
|
.getSnapshotsWithSchedule(coordinates.getTime());
|
||||||
|
if (!suitable.isEmpty()) {
|
||||||
|
TraceSnapshot found = suitable.iterator().next();
|
||||||
|
return CompletableFuture.completedFuture(found.getKey());
|
||||||
|
}
|
||||||
|
if (emulationService == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Cannot navigate to coordinates with execution schedules, " +
|
||||||
|
"because the emulation service is not available.");
|
||||||
|
}
|
||||||
|
return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
|
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
|
||||||
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
|
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
|
||||||
if (varView == null) { // Should only happen with NOWHERE
|
if (varView == null) { // Should only happen with NOWHERE
|
||||||
fireLocationEvent(coordinates);
|
fireLocationEvent(coordinates);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (coordinates.getTime().isSnapOnly()) {
|
materialize(coordinates).thenAcceptAsync(snap -> {
|
||||||
varView.setSnap(coordinates.getSnap());
|
if (!coordinates.equals(current)) {
|
||||||
|
return; // We navigated elsewhere before emulation completed
|
||||||
|
}
|
||||||
|
varView.setSnap(snap);
|
||||||
fireLocationEvent(coordinates);
|
fireLocationEvent(coordinates);
|
||||||
}
|
}, AsyncUtils.SWING_EXECUTOR);
|
||||||
else {
|
|
||||||
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
|
|
||||||
.getTimeManager()
|
|
||||||
.getSnapshotsWithSchedule(coordinates.getTime());
|
|
||||||
if (!suitable.isEmpty()) {
|
|
||||||
TraceSnapshot found = suitable.iterator().next();
|
|
||||||
varView.setSnap(found.getKey());
|
|
||||||
fireLocationEvent(coordinates);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (emulationService == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Cannot navigate to coordinates with execution schedules, " +
|
|
||||||
"because the emulation service is not available.");
|
|
||||||
}
|
|
||||||
CompletableFuture<Long> bg =
|
|
||||||
emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
|
|
||||||
bg.thenAccept(emuSnap -> Swing.runLater(() -> {
|
|
||||||
if (!coordinates.equals(current)) {
|
|
||||||
return; // We navigated elsewhere before emulation completed
|
|
||||||
}
|
|
||||||
varView.setSnap(emuSnap);
|
|
||||||
fireLocationEvent(coordinates);
|
|
||||||
})).exceptionally(ex -> {
|
|
||||||
Msg.showError(this, null, "Emulate", "Could not navigate to emulated coordinates",
|
|
||||||
ex);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
|
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
|
||||||
|
@ -892,6 +887,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
future.completeExceptionally(e);
|
future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return future;
|
return future;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.utils;
|
package ghidra.app.plugin.core.debug.utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
@ -24,8 +25,8 @@ import ghidra.framework.cmd.BackgroundCommand;
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
import ghidra.framework.model.UndoableDomainObject;
|
import ghidra.framework.model.UndoableDomainObject;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.util.task.CancelledListener;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.*;
|
||||||
|
|
||||||
public enum BackgroundUtils {
|
public enum BackgroundUtils {
|
||||||
;
|
;
|
||||||
|
@ -82,4 +83,59 @@ public enum BackgroundUtils {
|
||||||
tool.executeBackgroundCommand(cmd, obj);
|
tool.executeBackgroundCommand(cmd, obj);
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class PluginToolExecutorService extends AbstractExecutorService {
|
||||||
|
private final PluginTool tool;
|
||||||
|
private String name;
|
||||||
|
private boolean canCancel;
|
||||||
|
private boolean hasProgress;
|
||||||
|
private boolean isModal;
|
||||||
|
private final int delay;
|
||||||
|
|
||||||
|
public PluginToolExecutorService(PluginTool tool, String name, boolean canCancel,
|
||||||
|
boolean hasProgress, boolean isModal, int delay) {
|
||||||
|
this.tool = tool;
|
||||||
|
this.name = name;
|
||||||
|
this.canCancel = canCancel;
|
||||||
|
this.hasProgress = hasProgress;
|
||||||
|
this.isModal = isModal;
|
||||||
|
this.delay = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Runnable> shutdownNow() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable command) {
|
||||||
|
Task task = new Task(name, canCancel, hasProgress, isModal) {
|
||||||
|
@Override
|
||||||
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
|
command.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tool.execute(task, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,19 +17,89 @@ package ghidra.app.services;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
|
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.MultiBlendedListingBackgroundColorModel;
|
||||||
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service providing access to the main listing panel
|
||||||
|
*/
|
||||||
@ServiceInfo( //
|
@ServiceInfo( //
|
||||||
defaultProvider = DebuggerListingPlugin.class, //
|
defaultProvider = DebuggerListingPlugin.class, //
|
||||||
description = "Replacement CodeViewerService for Debugger" //
|
description = "Replacement CodeViewerService for Debugger" //
|
||||||
)
|
)
|
||||||
public interface DebuggerListingService extends CodeViewerService {
|
public interface DebuggerListingService extends CodeViewerService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for changes in location tracking specification
|
||||||
|
*/
|
||||||
|
interface LocationTrackingSpecChangeListener {
|
||||||
|
/**
|
||||||
|
* The specification has changed
|
||||||
|
*
|
||||||
|
* @param spec the new specification
|
||||||
|
*/
|
||||||
|
void locationTrackingSpecChanged(LocationTrackingSpec spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the tracking specification of the listing. Navigates immediately.
|
||||||
|
*
|
||||||
|
* @param spec the desired specification
|
||||||
|
*/
|
||||||
void setTrackingSpec(LocationTrackingSpec spec);
|
void setTrackingSpec(LocationTrackingSpec spec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tracking specification of the listing.
|
||||||
|
*
|
||||||
|
* @return the current specification
|
||||||
|
*/
|
||||||
|
LocationTrackingSpec getTrackingSpec();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to the tracking specification.
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
|
void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to the tracking specification.
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
|
void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the selection of addresses in this listing.
|
||||||
|
*
|
||||||
|
* @param selection the desired selection
|
||||||
|
*/
|
||||||
void setCurrentSelection(ProgramSelection selection);
|
void setCurrentSelection(ProgramSelection selection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the given address
|
||||||
|
*
|
||||||
|
* @param address the desired address
|
||||||
|
* @param centerOnScreen true to center the cursor in the listing
|
||||||
|
* @return true if the request was effective
|
||||||
|
*/
|
||||||
boolean goTo(Address address, boolean centerOnScreen);
|
boolean goTo(Address address, boolean centerOnScreen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain a coloring background model suitable for the given listing
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This may be used, e.g., to style an alternative view in the same manner as listings managed
|
||||||
|
* by this service. Namely, this provides coloring for memory state and the user's cursor.
|
||||||
|
* Coloring for tracked locations and the marker service in general must still be added
|
||||||
|
* separately, since they incorporate additional dependencies.
|
||||||
|
*
|
||||||
|
* @param listingPanel the panel to be colored
|
||||||
|
* @return a blended background color model implementing the common debugger listing style
|
||||||
|
*/
|
||||||
|
MultiBlendedListingBackgroundColorModel createListingBackgroundColorModel(
|
||||||
|
ListingPanel listingPanel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,39 +20,117 @@ import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||||
|
import ghidra.async.AsyncReference;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.TriConsumer;
|
import ghidra.util.TriConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface for managing open traces and navigating among them and their contents
|
||||||
|
*/
|
||||||
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
|
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
|
||||||
public interface DebuggerTraceManagerService {
|
public interface DebuggerTraceManagerService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An adapter that works nicely with an {@link AsyncReference}
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* TODO: Seems this is still leaking an implementation detail
|
||||||
|
*/
|
||||||
public interface BooleanChangeAdapter extends TriConsumer<Boolean, Boolean, Void> {
|
public interface BooleanChangeAdapter extends TriConsumer<Boolean, Boolean, Void> {
|
||||||
@Override
|
@Override
|
||||||
default void accept(Boolean oldVal, Boolean newVal, Void cause) {
|
default void accept(Boolean oldVal, Boolean newVal, Void cause) {
|
||||||
changed(newVal);
|
changed(newVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value has changed
|
||||||
|
*
|
||||||
|
* @param value the new value
|
||||||
|
*/
|
||||||
void changed(Boolean value);
|
void changed(Boolean value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the open traces
|
||||||
|
*
|
||||||
|
* @return all open traces
|
||||||
|
*/
|
||||||
Collection<Trace> getOpenTraces();
|
Collection<Trace> getOpenTraces();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current coordinates
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This entails everything except the current address
|
||||||
|
*
|
||||||
|
* @return the current coordinates
|
||||||
|
*/
|
||||||
DebuggerCoordinates getCurrent();
|
DebuggerCoordinates getCurrent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active trace
|
||||||
|
*
|
||||||
|
* @return the active trace, or null
|
||||||
|
*/
|
||||||
Trace getCurrentTrace();
|
Trace getCurrentTrace();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active view
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Every trace has an associated variable-snap view. When the manager navigates to a new point
|
||||||
|
* in time, it is accomplished by changing the snap of this view. This view is suitable for use
|
||||||
|
* in most places where a {@link Program} is ordinarily required.
|
||||||
|
*
|
||||||
|
* @return the active view, or null
|
||||||
|
*/
|
||||||
TraceProgramView getCurrentView();
|
TraceProgramView getCurrentView();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active thread
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* It is possible to have an active trace, but no active thread.
|
||||||
|
*
|
||||||
|
* @return the active thread, or null
|
||||||
|
*/
|
||||||
TraceThread getCurrentThread();
|
TraceThread getCurrentThread();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active thread for a given trace
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The manager remembers the last active thread for every open trace. If the trace has never
|
||||||
|
* been active, then the last active thread is null. If trace is the active trace, then this
|
||||||
|
* will return the currently active thread.
|
||||||
|
*
|
||||||
|
* @param trace the trace
|
||||||
|
* @return the thread, or null
|
||||||
|
*/
|
||||||
TraceThread getCurrentThreadFor(Trace trace);
|
TraceThread getCurrentThreadFor(Trace trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active snap
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that if emulation was used to materialize the current coordinates, then the current snap
|
||||||
|
* will differ from the view's snap.
|
||||||
|
*
|
||||||
|
* @return the active snap, or 0
|
||||||
|
*/
|
||||||
long getCurrentSnap();
|
long getCurrentSnap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active frame
|
||||||
|
*
|
||||||
|
* @return the active frame, or 0
|
||||||
|
*/
|
||||||
int getCurrentFrame();
|
int getCurrentFrame();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,10 +183,23 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
CompletableFuture<Void> saveTrace(Trace trace);
|
CompletableFuture<Void> saveTrace(Trace trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the given trace
|
||||||
|
*
|
||||||
|
* @param trace the trace to close
|
||||||
|
*/
|
||||||
void closeTrace(Trace trace);
|
void closeTrace(Trace trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all traces
|
||||||
|
*/
|
||||||
void closeAllTraces();
|
void closeAllTraces();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all traces except the given one
|
||||||
|
*
|
||||||
|
* @param keep the trace to keep open
|
||||||
|
*/
|
||||||
void closeOtherTraces(Trace keep);
|
void closeOtherTraces(Trace keep);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,24 +211,84 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
void closeDeadTraces();
|
void closeDeadTraces();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given coordinates
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This operation may be completed asynchronously, esp., if emulation is required to materialize
|
||||||
|
* the coordinates. The coordinates are "resolved" as a means of filling in missing parts. For
|
||||||
|
* example, if the thread is not specified, the manager may activate the last-active thread for
|
||||||
|
* the desired trace.
|
||||||
|
*
|
||||||
|
* @param coordinates the desired coordinates
|
||||||
|
*/
|
||||||
void activate(DebuggerCoordinates coordinates);
|
void activate(DebuggerCoordinates coordinates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given trace
|
||||||
|
*
|
||||||
|
* @param trace the desired trace
|
||||||
|
*/
|
||||||
void activateTrace(Trace trace);
|
void activateTrace(Trace trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given thread
|
||||||
|
*
|
||||||
|
* @param thread the desired thread
|
||||||
|
*/
|
||||||
void activateThread(TraceThread thread);
|
void activateThread(TraceThread thread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given snapshot key
|
||||||
|
*
|
||||||
|
* @param snap the desired snapshot key
|
||||||
|
*/
|
||||||
void activateSnap(long snap);
|
void activateSnap(long snap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given point in time, possibly invoking emulation
|
||||||
|
*
|
||||||
|
* @param time the desired schedule
|
||||||
|
*/
|
||||||
void activateTime(TraceSchedule time);
|
void activateTime(TraceSchedule time);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given stack frame
|
||||||
|
*
|
||||||
|
* @param frameLevel the level of the desired frame, 0 being innermost
|
||||||
|
*/
|
||||||
void activateFrame(int frameLevel);
|
void activateFrame(int frameLevel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control whether the trace manager automatically activates the "present snapshot"
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Auto activation only applies when the current trace advances. It never changes to another
|
||||||
|
* trace.
|
||||||
|
*
|
||||||
|
* @param enabled true to enable auto activation
|
||||||
|
*/
|
||||||
void setAutoActivatePresent(boolean enabled);
|
void setAutoActivatePresent(boolean enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the trace manager automatically activate the "present snapshot"
|
||||||
|
*
|
||||||
|
* @return true if auto activation is enabled
|
||||||
|
*/
|
||||||
boolean isAutoActivatePresent();
|
boolean isAutoActivatePresent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to auto activation enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
|
void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to auto activation enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
|
void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,8 +305,18 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
boolean isSynchronizeFocus();
|
boolean isSynchronizeFocus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to focus synchronization enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to focus synchronization enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -172,8 +333,18 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
boolean isSaveTracesByDefault();
|
boolean isSaveTracesByDefault();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to save-by-default enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
void addSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
|
void addSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to save-by-default enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
void removeSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
|
void removeSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,15 +361,40 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
boolean isAutoCloseOnTerminate();
|
boolean isAutoCloseOnTerminate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to close-on-terminate enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
void addAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
void addAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to close-on-terminate enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill in an incomplete coordinate specification, using the manager's "best judgement"
|
* Fill in an incomplete coordinate specification, using the manager's "best judgment"
|
||||||
*
|
*
|
||||||
* @param coords the possibly-incomplete coordinates
|
* @param coords the possibly-incomplete coordinates
|
||||||
* @return the complete resolved coordinates
|
* @return the complete resolved coordinates
|
||||||
*/
|
*/
|
||||||
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coords);
|
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coordinates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Materialize the given coordinates to a snapshot in the same trace
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the given coordinates do not require emulation, then this must complete immediately with
|
||||||
|
* the snapshot key given by the coordinates. If the given schedule is already materialized in
|
||||||
|
* the trace, then this may complete immediately with the previously-materialized snapshot key.
|
||||||
|
* Otherwise, this must invoke emulation, store the result into a chosen snapshot, and complete
|
||||||
|
* with its key.
|
||||||
|
*
|
||||||
|
* @param coordinates the coordinates to materialize
|
||||||
|
* @return a future that completes with the snapshot key of the materialized coordinates
|
||||||
|
*/
|
||||||
|
CompletableFuture<Long> materialize(DebuggerCoordinates coordinates);
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 663 B |
|
@ -0,0 +1,128 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.diff;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog;
|
||||||
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||||
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
|
import ghidra.async.AsyncTestUtils;
|
||||||
|
import ghidra.test.ToyProgramBuilder;
|
||||||
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
|
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||||
|
import ghidra.trace.database.thread.DBTraceThread;
|
||||||
|
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.util.Swing;
|
||||||
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
import help.screenshot.GhidraScreenShotGenerator;
|
||||||
|
|
||||||
|
public class DebuggerTraceViewDiffPluginScreenShots extends GhidraScreenShotGenerator
|
||||||
|
implements AsyncTestUtils {
|
||||||
|
|
||||||
|
DebuggerTraceManagerService traceManager;
|
||||||
|
DebuggerTraceViewDiffPlugin diffPlugin;
|
||||||
|
DebuggerListingPlugin listingPlugin;
|
||||||
|
DebuggerListingProvider listingProvider;
|
||||||
|
ToyDBTraceBuilder tb;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpMine() throws Throwable {
|
||||||
|
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
|
||||||
|
diffPlugin = addPlugin(tool, DebuggerTraceViewDiffPlugin.class);
|
||||||
|
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
|
||||||
|
tb = new ToyDBTraceBuilder("tictactoe", ToyProgramBuilder._X64);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDownMine() {
|
||||||
|
tb.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCaptureDebuggerTraceViewDiffPlugin() throws Throwable {
|
||||||
|
long snap1, snap2;
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
DBTraceTimeManager tm = tb.trace.getTimeManager();
|
||||||
|
snap1 = tm.createSnapshot("Baseline").getKey();
|
||||||
|
snap2 = tm.createSnapshot("X's first move").getKey();
|
||||||
|
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
mm.createRegion(".data", snap1, tb.range(0x00600000, 0x0060ffff),
|
||||||
|
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
|
||||||
|
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(0x1000).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buf.put((byte) 'X');
|
||||||
|
buf.putInt(3);
|
||||||
|
buf.putInt(3);
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
buf.put((byte) ' ');
|
||||||
|
}
|
||||||
|
buf.flip();
|
||||||
|
buf.limit(0x1000);
|
||||||
|
mm.putBytes(snap1, tb.addr(0x00600000), buf);
|
||||||
|
|
||||||
|
buf.put(0, (byte) 'O');
|
||||||
|
buf.put(13, (byte) 'X');
|
||||||
|
buf.position(0);
|
||||||
|
buf.limit(0x1000);
|
||||||
|
mm.putBytes(snap2, tb.addr(0x00600000), buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
traceManager.activateSnap(snap1);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
waitOn(diffPlugin.startComparison(TraceSchedule.snap(snap2)));
|
||||||
|
assertTrue(diffPlugin.gotoNextDiff());
|
||||||
|
|
||||||
|
captureIsolatedProvider(DebuggerListingProvider.class, 900, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCaptureDebuggerTimeSelectionDialog() throws Throwable {
|
||||||
|
DBTraceThread thread;
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
DBTraceTimeManager tm = tb.trace.getTimeManager();
|
||||||
|
thread = tb.getOrAddThread("main", 0);
|
||||||
|
tm.createSnapshot("Break on main").setEventThread(thread);
|
||||||
|
tm.createSnapshot("Game started").setEventThread(thread);
|
||||||
|
tm.createSnapshot("X's moved").setEventThread(thread);
|
||||||
|
tm.createSnapshot("O's moved").setEventThread(thread);
|
||||||
|
}
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
traceManager.activateThread(thread);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
performAction(diffPlugin.actionCompare, false);
|
||||||
|
DebuggerTimeSelectionDialog dialog =
|
||||||
|
waitForDialogComponent(DebuggerTimeSelectionDialog.class);
|
||||||
|
Swing.runNow(() -> dialog.setScheduleText("2"));
|
||||||
|
|
||||||
|
captureDialog(dialog);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
@ -40,11 +41,13 @@ import org.junit.runner.Description;
|
||||||
import docking.widgets.tree.GTree;
|
import docking.widgets.tree.GTree;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.action.*;
|
||||||
import ghidra.app.plugin.core.debug.mapping.*;
|
import ghidra.app.plugin.core.debug.mapping.*;
|
||||||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal;
|
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal;
|
||||||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
|
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.dbg.model.AbstractTestTargetRegisterBank;
|
import ghidra.dbg.model.AbstractTestTargetRegisterBank;
|
||||||
import ghidra.dbg.model.TestDebuggerModelBuilder;
|
import ghidra.dbg.model.TestDebuggerModelBuilder;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
|
@ -57,6 +60,7 @@ import ghidra.program.model.data.DataType;
|
||||||
import ghidra.program.model.lang.*;
|
import ghidra.program.model.lang.*;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.util.DefaultLanguageService;
|
import ghidra.program.util.DefaultLanguageService;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
|
@ -409,6 +413,64 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
||||||
clickMouse(button, m);
|
clickMouse(button, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void assertListingBackgroundAt(Color expected, ListingPanel panel,
|
||||||
|
Address addr, int yAdjust) throws AWTException, InterruptedException {
|
||||||
|
ProgramLocation oneBack = new ProgramLocation(panel.getProgram(), addr.previous());
|
||||||
|
runSwing(() -> panel.goTo(addr));
|
||||||
|
runSwing(() -> panel.goTo(oneBack, false));
|
||||||
|
waitForPass(() -> {
|
||||||
|
Rectangle r = panel.getBounds();
|
||||||
|
// Capture off screen, so that focus/stacking doesn't matter
|
||||||
|
BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics g = image.getGraphics();
|
||||||
|
try {
|
||||||
|
runSwing(() -> panel.paint(g));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
g.dispose();
|
||||||
|
}
|
||||||
|
Point locP = panel.getLocationOnScreen();
|
||||||
|
Point locFP = panel.getLocationOnScreen();
|
||||||
|
locFP.translate(-locP.x, -locP.y);
|
||||||
|
Rectangle cursor = panel.getCursorBounds();
|
||||||
|
assertNotNull("Cannot get cursor bounds", cursor);
|
||||||
|
Color actual = new Color(image.getRGB(locFP.x + cursor.x - 1,
|
||||||
|
locFP.y + cursor.y + cursor.height * 3 / 2 + yAdjust));
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void goTo(ListingPanel listingPanel, ProgramLocation location) {
|
||||||
|
waitForPass(() -> {
|
||||||
|
runSwing(() -> listingPanel.goTo(location));
|
||||||
|
ProgramLocation confirm = listingPanel.getCursorLocation();
|
||||||
|
assertNotNull(confirm);
|
||||||
|
assertEquals(location.getAddress(), confirm.getAddress());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
||||||
|
return LocationTrackingSpec.fromConfigName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
||||||
|
return AutoReadMemorySpec.fromConfigName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final LocationTrackingSpec trackNone =
|
||||||
|
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
|
||||||
|
protected final LocationTrackingSpec trackPc =
|
||||||
|
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
|
||||||
|
protected final LocationTrackingSpec trackSp =
|
||||||
|
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
|
||||||
|
|
||||||
|
protected final AutoReadMemorySpec readNone =
|
||||||
|
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
|
||||||
|
protected final AutoReadMemorySpec readVisible =
|
||||||
|
getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME);
|
||||||
|
protected final AutoReadMemorySpec readVisROOnce =
|
||||||
|
getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
|
||||||
|
|
||||||
protected TestEnv env;
|
protected TestEnv env;
|
||||||
protected PluginTool tool;
|
protected PluginTool tool;
|
||||||
|
|
||||||
|
@ -468,8 +530,12 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
||||||
|
|
||||||
if (mb != null) {
|
if (mb != null) {
|
||||||
if (mb.testModel != null) {
|
if (mb.testModel != null) {
|
||||||
// TODO: Stop recordings, too?
|
|
||||||
modelService.removeModel(mb.testModel);
|
modelService.removeModel(mb.testModel);
|
||||||
|
|
||||||
|
runSwing(() -> traceManager.setSaveTracesByDefault(false));
|
||||||
|
for (TraceRecorder recorder : modelService.getTraceRecorders()) {
|
||||||
|
recorder.stopRecording();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,233 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.diff;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog;
|
||||||
|
import ghidra.async.AsyncTestUtils;
|
||||||
|
import ghidra.program.model.address.AddressSetView;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.util.Swing;
|
||||||
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
|
public class DebuggerTraceViewDiffPluginTest extends AbstractGhidraHeadedDebuggerGUITest
|
||||||
|
implements AsyncTestUtils {
|
||||||
|
|
||||||
|
protected DebuggerTraceViewDiffPlugin traceDiffPlugin;
|
||||||
|
protected DebuggerListingPlugin listingPlugin;
|
||||||
|
|
||||||
|
protected DebuggerListingProvider listingProvider;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpTraceViewDiffPluginTest() throws Exception {
|
||||||
|
traceDiffPlugin = addPlugin(tool, DebuggerTraceViewDiffPlugin.class);
|
||||||
|
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
|
||||||
|
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionCompareConfirm() throws Exception {
|
||||||
|
assertFalse(traceDiffPlugin.actionCompare.isEnabled());
|
||||||
|
assertNull(listingPlugin.getProvider().getOtherPanel());
|
||||||
|
|
||||||
|
createAndOpenTrace();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertTrue(traceDiffPlugin.actionCompare.isEnabled());
|
||||||
|
performAction(traceDiffPlugin.actionCompare, false);
|
||||||
|
|
||||||
|
DebuggerTimeSelectionDialog dialog =
|
||||||
|
waitForDialogComponent(DebuggerTimeSelectionDialog.class);
|
||||||
|
Swing.runNow(() -> {
|
||||||
|
dialog.setScheduleText("0");
|
||||||
|
dialog.okCallback();
|
||||||
|
});
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertNotNull(listingPlugin.getProvider().getOtherPanel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionCompareCancel() throws Exception {
|
||||||
|
assertFalse(traceDiffPlugin.actionCompare.isEnabled());
|
||||||
|
assertNull(listingPlugin.getProvider().getOtherPanel());
|
||||||
|
|
||||||
|
createAndOpenTrace();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertTrue(traceDiffPlugin.actionCompare.isEnabled());
|
||||||
|
performAction(traceDiffPlugin.actionCompare, false);
|
||||||
|
|
||||||
|
DebuggerTimeSelectionDialog dialog =
|
||||||
|
waitForDialogComponent(DebuggerTimeSelectionDialog.class);
|
||||||
|
Swing.runNow(() -> {
|
||||||
|
dialog.setScheduleText("0");
|
||||||
|
dialog.cancelCallback();
|
||||||
|
});
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertNull(listingPlugin.getProvider().getOtherPanel());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test schedule input validation?
|
||||||
|
// TODO: Test stepping buttons?
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionCompareClosesWhenAlreadyActive() throws Exception {
|
||||||
|
assertFalse(traceDiffPlugin.actionCompare.isEnabled());
|
||||||
|
assertNull(listingPlugin.getProvider().getOtherPanel());
|
||||||
|
|
||||||
|
createAndOpenTrace();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertTrue(traceDiffPlugin.actionCompare.isEnabled());
|
||||||
|
performAction(traceDiffPlugin.actionCompare, false);
|
||||||
|
|
||||||
|
DebuggerTimeSelectionDialog dialog =
|
||||||
|
waitForDialogComponent(DebuggerTimeSelectionDialog.class);
|
||||||
|
Swing.runNow(() -> {
|
||||||
|
dialog.setScheduleText("0");
|
||||||
|
dialog.okCallback();
|
||||||
|
});
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertNotNull(listingPlugin.getProvider().getOtherPanel());
|
||||||
|
|
||||||
|
assertTrue(traceDiffPlugin.actionCompare.isEnabled());
|
||||||
|
performAction(traceDiffPlugin.actionCompare, false);
|
||||||
|
assertNull(listingPlugin.getProvider().getOtherPanel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testColorsDiffBytes() throws Throwable {
|
||||||
|
createAndOpenTrace();
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff),
|
||||||
|
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(0x1000); // Yes, smaller than .text
|
||||||
|
buf.limit(0x1000);
|
||||||
|
mm.putBytes(0, tb.addr(0x00400000), buf);
|
||||||
|
buf.position(0);
|
||||||
|
buf.putLong(0x0123, 0x1122334455667788L);
|
||||||
|
mm.putBytes(1, tb.addr(0x00400000), buf);
|
||||||
|
}
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
waitOn(traceDiffPlugin.startComparison(TraceSchedule.snap(1)));
|
||||||
|
|
||||||
|
assertListingBackgroundAt(DebuggerTraceViewDiffPlugin.DEFAULT_DIFF_COLOR,
|
||||||
|
traceDiffPlugin.altListingPanel, tb.addr(0x00400123), 0);
|
||||||
|
assertListingBackgroundAt(DebuggerTraceViewDiffPlugin.DEFAULT_DIFF_COLOR,
|
||||||
|
listingProvider.getListingPanel(), tb.addr(0x00400123), 0);
|
||||||
|
|
||||||
|
AddressSetView expected = tb.set(tb.range(0x00400123, 0x0040012a));
|
||||||
|
assertEquals(expected, Swing.runNow(() -> traceDiffPlugin.diffMarkersL.getAddressSet()));
|
||||||
|
assertEquals(expected, Swing.runNow(() -> traceDiffPlugin.diffMarkersR.getAddressSet()));
|
||||||
|
|
||||||
|
Swing.runNow(() -> traceDiffPlugin.endComparison());
|
||||||
|
|
||||||
|
assertTrue(Swing.runNow(() -> traceDiffPlugin.diffMarkersL.getAddressSet()).isEmpty());
|
||||||
|
assertTrue(Swing.runNow(() -> traceDiffPlugin.diffMarkersR.getAddressSet()).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionPrevDiff() throws Throwable {
|
||||||
|
createAndOpenTrace();
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff),
|
||||||
|
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(0x1000); // Yes, smaller than .text
|
||||||
|
buf.limit(0x1000);
|
||||||
|
mm.putBytes(0, tb.addr(0x00400000), buf);
|
||||||
|
buf.position(0);
|
||||||
|
buf.putLong(0x0123, 0x1122334455667788L);
|
||||||
|
buf.putLong(0x0321, 0x1122334455667788L);
|
||||||
|
mm.putBytes(1, tb.addr(0x00400000), buf);
|
||||||
|
}
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
waitOn(traceDiffPlugin.startComparison(TraceSchedule.snap(1)));
|
||||||
|
|
||||||
|
assertFalse(traceDiffPlugin.actionPrevDiff.isEnabled());
|
||||||
|
goTo(listingProvider.getListingPanel(),
|
||||||
|
new ProgramLocation(tb.trace.getProgramView(), tb.addr(0x00401000)));
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertTrue(traceDiffPlugin.actionPrevDiff.isEnabled());
|
||||||
|
performAction(traceDiffPlugin.actionPrevDiff);
|
||||||
|
assertEquals(tb.addr(0x00400328), traceDiffPlugin.getCurrentAddress());
|
||||||
|
|
||||||
|
assertTrue(traceDiffPlugin.actionPrevDiff.isEnabled());
|
||||||
|
performAction(traceDiffPlugin.actionPrevDiff);
|
||||||
|
assertEquals(tb.addr(0x0040012a), traceDiffPlugin.getCurrentAddress());
|
||||||
|
|
||||||
|
assertFalse(traceDiffPlugin.actionPrevDiff.isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionNextDiff() throws Throwable {
|
||||||
|
createAndOpenTrace();
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff),
|
||||||
|
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(0x1000); // Yes, smaller than .text
|
||||||
|
buf.limit(0x1000);
|
||||||
|
mm.putBytes(0, tb.addr(0x00400000), buf);
|
||||||
|
buf.position(0);
|
||||||
|
buf.putLong(0x0123, 0x1122334455667788L);
|
||||||
|
buf.putLong(0x0321, 0x1122334455667788L);
|
||||||
|
mm.putBytes(1, tb.addr(0x00400000), buf);
|
||||||
|
}
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
waitOn(traceDiffPlugin.startComparison(TraceSchedule.snap(1)));
|
||||||
|
|
||||||
|
assertTrue(traceDiffPlugin.actionNextDiff.isEnabled());
|
||||||
|
performAction(traceDiffPlugin.actionNextDiff);
|
||||||
|
waitForPass(() -> assertEquals(tb.addr(0x00400123), traceDiffPlugin.getCurrentAddress()));
|
||||||
|
|
||||||
|
assertTrue(traceDiffPlugin.actionNextDiff.isEnabled());
|
||||||
|
performAction(traceDiffPlugin.actionNextDiff);
|
||||||
|
assertEquals(tb.addr(0x00400321), traceDiffPlugin.getCurrentAddress());
|
||||||
|
|
||||||
|
assertFalse(traceDiffPlugin.actionNextDiff.isEnabled());
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.gui.listing;
|
||||||
import static ghidra.lifecycle.Unfinished.TODO;
|
import static ghidra.lifecycle.Unfinished.TODO;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.Color;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -37,14 +36,13 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.*;
|
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
|
||||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
|
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
|
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
|
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
|
||||||
import ghidra.async.SwingExecutorService;
|
import ghidra.async.SwingExecutorService;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.plugin.importer.ImporterPlugin;
|
import ghidra.plugin.importer.ImporterPlugin;
|
||||||
|
@ -68,27 +66,6 @@ import ghidra.util.exception.VersionException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
|
||||||
return LocationTrackingSpec.fromConfigName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
|
||||||
return AutoReadMemorySpec.fromConfigName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
final LocationTrackingSpec trackNone =
|
|
||||||
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
final LocationTrackingSpec trackPc =
|
|
||||||
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
final LocationTrackingSpec trackSp =
|
|
||||||
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
|
|
||||||
final AutoReadMemorySpec readNone =
|
|
||||||
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
final AutoReadMemorySpec readVisible =
|
|
||||||
getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
final AutoReadMemorySpec readVisROOnce =
|
|
||||||
getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
|
|
||||||
protected DebuggerListingPlugin listingPlugin;
|
protected DebuggerListingPlugin listingPlugin;
|
||||||
protected DebuggerListingProvider listingProvider;
|
protected DebuggerListingProvider listingProvider;
|
||||||
|
@ -110,12 +87,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void goToDyn(ProgramLocation location) {
|
protected void goToDyn(ProgramLocation location) {
|
||||||
waitForPass(() -> {
|
goTo(listingProvider.getListingPanel(), location);
|
||||||
runSwing(() -> listingProvider.goTo(location.getProgram(), location));
|
|
||||||
ProgramLocation confirm = listingProvider.getLocation();
|
|
||||||
assertNotNull(confirm);
|
|
||||||
assertEquals(location.getAddress(), confirm.getAddress());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static byte[] incBlock() {
|
protected static byte[] incBlock() {
|
||||||
|
@ -572,32 +544,6 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertEquals(ss.getAddress(0x00601234), loc.getAddress());
|
assertEquals(ss.getAddress(0x00601234), loc.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertListingBackgroundAt(Color expected, ListingPanel panel,
|
|
||||||
Address addr, int yAdjust) throws AWTException, InterruptedException {
|
|
||||||
ProgramLocation oneBack = new ProgramLocation(panel.getProgram(), addr.previous());
|
|
||||||
runSwing(() -> panel.goTo(addr));
|
|
||||||
runSwing(() -> panel.goTo(oneBack, false));
|
|
||||||
waitForPass(() -> {
|
|
||||||
Rectangle r = panel.getBounds();
|
|
||||||
// Capture off screen, so that focus/stacking doesn't matter
|
|
||||||
BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
Graphics g = image.getGraphics();
|
|
||||||
try {
|
|
||||||
runSwing(() -> panel.paint(g));
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
g.dispose();
|
|
||||||
}
|
|
||||||
Point locP = panel.getLocationOnScreen();
|
|
||||||
Point locFP = panel.getLocationOnScreen();
|
|
||||||
locFP.translate(-locP.x, -locP.y);
|
|
||||||
Rectangle cursor = panel.getCursorBounds();
|
|
||||||
Color actual = new Color(image.getRGB(locFP.x + cursor.x - 1,
|
|
||||||
locFP.y + cursor.y + cursor.height * 3 / 2 + yAdjust));
|
|
||||||
assertEquals(expected, actual);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDynamicListingMarksTrackedRegister() throws Exception {
|
public void testDynamicListingMarksTrackedRegister() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
|
|
@ -66,26 +66,6 @@ import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
@Category(NightlyCategory.class)
|
@Category(NightlyCategory.class)
|
||||||
public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
|
||||||
return LocationTrackingSpec.fromConfigName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
|
||||||
return AutoReadMemorySpec.fromConfigName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
final LocationTrackingSpec trackNone =
|
|
||||||
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
final LocationTrackingSpec trackPc =
|
|
||||||
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
final LocationTrackingSpec trackSp =
|
|
||||||
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
|
|
||||||
final AutoReadMemorySpec readNone = getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
final AutoReadMemorySpec readVisible =
|
|
||||||
getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
final AutoReadMemorySpec readVisROOnce =
|
|
||||||
getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
|
|
||||||
protected DebuggerMemoryBytesPlugin memBytesPlugin;
|
protected DebuggerMemoryBytesPlugin memBytesPlugin;
|
||||||
protected DebuggerMemoryBytesProvider memBytesProvider;
|
protected DebuggerMemoryBytesProvider memBytesProvider;
|
||||||
|
@ -104,7 +84,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
protected void goToDyn(ProgramLocation location) {
|
protected void goToDyn(ProgramLocation location) {
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
runSwing(() -> memBytesProvider.goTo(location.getProgram(), location));
|
runSwing(() -> memBytesProvider.goTo(location.getProgram(), location));
|
||||||
ProgramLocation confirm = memBytesProvider.getLocation();
|
ProgramLocation confirm = runSwing(() -> memBytesProvider.getLocation());
|
||||||
assertNotNull(confirm);
|
assertNotNull(confirm);
|
||||||
assertEquals(location.getAddress(), confirm.getAddress());
|
assertEquals(location.getAddress(), confirm.getAddress());
|
||||||
});
|
});
|
||||||
|
@ -316,8 +296,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
//Pre-check
|
//Pre-check
|
||||||
assertNull(memBytesProvider.getLocation());
|
assertNull(memBytesProvider.getLocation());
|
||||||
|
|
||||||
memBytesProvider.setTrackingSpec(trackSp);
|
runSwing(() -> memBytesProvider.setTrackingSpec(trackSp));
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
ProgramLocation loc = memBytesProvider.getLocation();
|
ProgramLocation loc = memBytesProvider.getLocation();
|
||||||
assertEquals(tb.trace.getProgramView(), loc.getProgram());
|
assertEquals(tb.trace.getProgramView(), loc.getProgram());
|
||||||
|
@ -326,7 +305,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
|
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
|
||||||
memBytesProvider.setTrackingSpec(trackNone);
|
runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
|
||||||
try ( //
|
try ( //
|
||||||
ToyDBTraceBuilder b1 =
|
ToyDBTraceBuilder b1 =
|
||||||
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
||||||
|
@ -377,7 +356,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
|
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
|
||||||
memBytesProvider.setTrackingSpec(trackNone);
|
runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
|
||||||
try ( //
|
try ( //
|
||||||
ToyDBTraceBuilder b1 =
|
ToyDBTraceBuilder b1 =
|
||||||
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
||||||
|
@ -483,7 +462,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
byte[] zero = new byte[data.length];
|
byte[] zero = new byte[data.length];
|
||||||
ByteBuffer buf = ByteBuffer.allocate(data.length);
|
ByteBuffer buf = ByteBuffer.allocate(data.length);
|
||||||
assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec());
|
assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec());
|
||||||
memBytesProvider.setAutoReadMemorySpec(readNone);
|
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
|
||||||
|
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
@ -517,10 +496,19 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
assertArrayEquals(zero, buf.array());
|
assertArrayEquals(zero, buf.array());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTE: Should read immediately upon setting auto-read, but we're not focused on the
|
* Assure ourselves the block under test is not on screen
|
||||||
|
*/
|
||||||
|
waitForPass(() -> {
|
||||||
|
AddressSetView visible = memBytesProvider.readsMemTrait.getVisible();
|
||||||
|
assertFalse(visible.isEmpty());
|
||||||
|
assertFalse(visible.contains(addr(trace, 0x55550000)));
|
||||||
|
assertFalse(visible.contains(addr(trace, 0x55550fff)));
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* NOTE: Should read immediately upon setting auto-read, but we're not looking at the
|
||||||
* written block
|
* written block
|
||||||
*/
|
*/
|
||||||
memBytesProvider.setAutoReadMemorySpec(readVisROOnce);
|
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readVisROOnce));
|
||||||
waitForDomainObject(trace);
|
waitForDomainObject(trace);
|
||||||
buf.clear();
|
buf.clear();
|
||||||
assertEquals(data.length,
|
assertEquals(data.length,
|
||||||
|
@ -595,13 +583,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
public static <T> void setActionStateWithTrigger(MultiStateDockingAction<T> action, T userData,
|
public static <T> void setActionStateWithTrigger(MultiStateDockingAction<T> action, T userData,
|
||||||
EventTrigger trigger) {
|
EventTrigger trigger) {
|
||||||
for (ActionState<T> actionState : action.getAllActionStates()) {
|
runSwing(() -> {
|
||||||
if (actionState.getUserData() == userData) {
|
for (ActionState<T> actionState : action.getAllActionStates()) {
|
||||||
action.setCurrentActionStateWithTrigger(actionState, trigger);
|
if (actionState.getUserData() == userData) {
|
||||||
return;
|
action.setCurrentActionStateWithTrigger(actionState, trigger);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
fail("Invalid action state user data");
|
||||||
fail("Invalid action state user data");
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -682,8 +672,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertEquals(tb.addr(0x1fff8765), memBytesProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x1fff8765), memBytesProvider.getLocation().getAddress());
|
||||||
|
|
||||||
memBytesProvider.setTrackingSpec(trackNone);
|
runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
|
||||||
waitForSwing();
|
|
||||||
assertEquals(trackNone, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(trackNone, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -747,7 +736,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
byte[] zero = new byte[data.length];
|
byte[] zero = new byte[data.length];
|
||||||
ByteBuffer buf = ByteBuffer.allocate(data.length);
|
ByteBuffer buf = ByteBuffer.allocate(data.length);
|
||||||
assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled());
|
assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled());
|
||||||
memBytesProvider.setAutoReadMemorySpec(readNone);
|
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
|
||||||
|
|
||||||
// To verify enabled requires live target
|
// To verify enabled requires live target
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
@ -845,18 +834,16 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec());
|
assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec());
|
||||||
assertEquals(readVisROOnce, memBytesProvider.actionAutoReadMemory.getCurrentUserData());
|
assertEquals(readVisROOnce, memBytesProvider.actionAutoReadMemory.getCurrentUserData());
|
||||||
|
|
||||||
memBytesProvider.actionAutoReadMemory.setCurrentActionStateByUserData(readNone);
|
runSwing(
|
||||||
waitForSwing();
|
() -> memBytesProvider.actionAutoReadMemory.setCurrentActionStateByUserData(readNone));
|
||||||
assertEquals(readNone, memBytesProvider.getAutoReadMemorySpec());
|
assertEquals(readNone, memBytesProvider.getAutoReadMemorySpec());
|
||||||
assertEquals(readNone, memBytesProvider.actionAutoReadMemory.getCurrentUserData());
|
assertEquals(readNone, memBytesProvider.actionAutoReadMemory.getCurrentUserData());
|
||||||
|
|
||||||
memBytesProvider.setAutoReadMemorySpec(readVisROOnce);
|
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readVisROOnce));
|
||||||
waitForSwing();
|
|
||||||
assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec());
|
assertEquals(readVisROOnce, memBytesProvider.getAutoReadMemorySpec());
|
||||||
assertEquals(readVisROOnce, memBytesProvider.actionAutoReadMemory.getCurrentUserData());
|
assertEquals(readVisROOnce, memBytesProvider.actionAutoReadMemory.getCurrentUserData());
|
||||||
|
|
||||||
memBytesProvider.setAutoReadMemorySpec(readNone);
|
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
|
||||||
waitForSwing();
|
|
||||||
assertEquals(readNone, memBytesProvider.getAutoReadMemorySpec());
|
assertEquals(readNone, memBytesProvider.getAutoReadMemorySpec());
|
||||||
assertEquals(readNone, memBytesProvider.actionAutoReadMemory.getCurrentUserData());
|
assertEquals(readNone, memBytesProvider.actionAutoReadMemory.getCurrentUserData());
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,14 @@ import java.util.List;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
|
import docking.action.ActionContextProvider;
|
||||||
|
import docking.action.DockingActionIf;
|
||||||
|
import docking.widgets.dialogs.InputDialog;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
||||||
|
import ghidra.trace.database.time.DBTraceSnapshot;
|
||||||
import ghidra.trace.database.time.DBTraceTimeManager;
|
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
@ -64,11 +71,11 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertProviderEmpty() {
|
protected void assertProviderEmpty() {
|
||||||
assertTrue(timeProvider.snapshotTableModel.getModelData().isEmpty());
|
assertTrue(timeProvider.mainPanel.snapshotTableModel.getModelData().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertProviderPopulated() {
|
protected void assertProviderPopulated() {
|
||||||
List<SnapshotRow> snapsDisplayed = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> snapsDisplayed = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
// I should be able to assume this is sorted by key
|
// I should be able to assume this is sorted by key
|
||||||
assertEquals(2, snapsDisplayed.size());
|
assertEquals(2, snapsDisplayed.size());
|
||||||
|
|
||||||
|
@ -85,6 +92,59 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
// Timestamp is left unchecked, since default is current time
|
// Timestamp is left unchecked, since default is current time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void assertDisabled(ActionContextProvider provider, DockingActionIf action) {
|
||||||
|
ActionContext context = provider.getActionContext(null);
|
||||||
|
assertFalse(action.isEnabledForContext(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void assertEnabled(ActionContextProvider provider, DockingActionIf action) {
|
||||||
|
ActionContext context = provider.getActionContext(null);
|
||||||
|
assertTrue(action.isEnabledForContext(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void performEnabledAction(ActionContextProvider provider,
|
||||||
|
DockingActionIf action, boolean wait) {
|
||||||
|
ActionContext context = provider.getActionContext(null);
|
||||||
|
waitForCondition(() -> action.isEnabledForContext(context));
|
||||||
|
performAction(action, context, wait);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // TODO: Technically, this is a plugin action.... Different test case?
|
||||||
|
public void testActionRenameSnapshot() throws Exception {
|
||||||
|
// More often than not, this action will be used from the dynamic listing
|
||||||
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
DebuggerListingProvider listingProvider =
|
||||||
|
waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
assertDisabled(listingProvider, timePlugin.actionRenameSnapshot);
|
||||||
|
|
||||||
|
createSnaplessTrace();
|
||||||
|
addSnapshots();
|
||||||
|
assertDisabled(listingProvider, timePlugin.actionRenameSnapshot);
|
||||||
|
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertDisabled(listingProvider, timePlugin.actionRenameSnapshot);
|
||||||
|
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertEnabled(listingProvider, timePlugin.actionRenameSnapshot);
|
||||||
|
|
||||||
|
traceManager.activateSnap(10);
|
||||||
|
waitForSwing();
|
||||||
|
performEnabledAction(listingProvider, timePlugin.actionRenameSnapshot, false);
|
||||||
|
InputDialog dialog = waitForDialogComponent(InputDialog.class);
|
||||||
|
assertEquals("Snap 10", dialog.getValue());
|
||||||
|
|
||||||
|
dialog.setValue("My Snapshot");
|
||||||
|
dialog.close(); // isCancelled (private) defaults to false
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
DBTraceSnapshot snapshot = tb.trace.getTimeManager().getSnapshot(10, false);
|
||||||
|
assertEquals("My Snapshot", snapshot.getDescription());
|
||||||
|
|
||||||
|
// TODO: Test cancelled has no effect
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmpty() {
|
public void testEmpty() {
|
||||||
assertProviderEmpty();
|
assertProviderEmpty();
|
||||||
|
@ -158,7 +218,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
assertEquals(1, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(1, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -238,7 +298,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
SnapshotRow row = timeProvider.snapshotTableModel.getModelData().get(0);
|
SnapshotRow row = timeProvider.mainPanel.snapshotTableModel.getModelData().get(0);
|
||||||
runSwing(() -> row.setDescription("Custom Description"));
|
runSwing(() -> row.setDescription("Custom Description"));
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
@ -258,14 +318,14 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
|
|
||||||
timeProvider.snapshotFilterPanel.setSelectedItem(data.get(0));
|
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(0));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(0, traceManager.getCurrentSnap());
|
assertEquals(0, traceManager.getCurrentSnap());
|
||||||
|
|
||||||
timeProvider.snapshotFilterPanel.setSelectedItem(data.get(1));
|
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(1));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(10, traceManager.getCurrentSnap());
|
assertEquals(10, traceManager.getCurrentSnap());
|
||||||
|
@ -283,22 +343,22 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
|
|
||||||
traceManager.activateSnap(0);
|
traceManager.activateSnap(0);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(data.get(0), timeProvider.snapshotFilterPanel.getSelectedItem());
|
assertEquals(data.get(0), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
|
||||||
|
|
||||||
traceManager.activateSnap(10);
|
traceManager.activateSnap(10);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(data.get(1), timeProvider.snapshotFilterPanel.getSelectedItem());
|
assertEquals(data.get(1), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
|
||||||
|
|
||||||
traceManager.activateSnap(5);
|
traceManager.activateSnap(5);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertNull(timeProvider.snapshotFilterPanel.getSelectedItem());
|
assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -312,7 +372,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
assertEquals(2, data.size());
|
assertEquals(2, data.size());
|
||||||
for (SnapshotRow row : data) {
|
for (SnapshotRow row : data) {
|
||||||
assertTrue(row.getSnap() >= 0);
|
assertTrue(row.getSnap() >= 0);
|
||||||
|
@ -329,12 +389,12 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
|
|
||||||
addScratchSnapshot();
|
addScratchSnapshot();
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
assertEquals(2, data.size());
|
assertEquals(2, data.size());
|
||||||
for (SnapshotRow row : data) {
|
for (SnapshotRow row : data) {
|
||||||
assertTrue(row.getSnap() >= 0);
|
assertTrue(row.getSnap() >= 0);
|
||||||
|
@ -353,17 +413,17 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(true, timeProvider.hideScratch);
|
assertEquals(true, timeProvider.hideScratch);
|
||||||
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
|
|
||||||
performAction(timeProvider.actionHideScratch);
|
performAction(timeProvider.actionHideScratch);
|
||||||
|
|
||||||
assertEquals(false, timeProvider.hideScratch);
|
assertEquals(false, timeProvider.hideScratch);
|
||||||
assertEquals(3, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
|
|
||||||
performAction(timeProvider.actionHideScratch);
|
performAction(timeProvider.actionHideScratch);
|
||||||
|
|
||||||
assertEquals(true, timeProvider.hideScratch);
|
assertEquals(true, timeProvider.hideScratch);
|
||||||
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -380,6 +440,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(false, timeProvider.hideScratch);
|
assertEquals(false, timeProvider.hideScratch);
|
||||||
assertEquals(3, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -327,6 +327,17 @@ public class DBTraceMemoryManager
|
||||||
return delegateRead(start.getAddressSpace(), m -> m.getBufferAt(snap, start, byteOrder));
|
return delegateRead(start.getAddressSpace(), m -> m.getBufferAt(snap, start, byteOrder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getSnapOfMostRecentChangeToBlock(long snap, Address address) {
|
||||||
|
return delegateRead(address.getAddressSpace(),
|
||||||
|
m -> m.getSnapOfMostRecentChangeToBlock(snap, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBlockSize() {
|
||||||
|
return DBTraceMemorySpace.BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pack() {
|
public void pack() {
|
||||||
delegateWriteAll(getActiveSpaces(), m -> m.pack());
|
delegateWriteAll(getActiveSpaces(), m -> m.pack());
|
||||||
|
|
|
@ -884,6 +884,26 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getSnapOfMostRecentChangeToBlock(long snap, Address address) {
|
||||||
|
assertInSpace(address);
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
long offset = address.getOffset();
|
||||||
|
long roundOffset = offset & BLOCK_MASK;
|
||||||
|
OffsetSnap loc = new OffsetSnap(roundOffset, snap);
|
||||||
|
DBTraceMemoryBlockEntry ent = findMostRecentBlockEntry(loc, true);
|
||||||
|
if (ent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ent.getSnap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBlockSize() {
|
||||||
|
return BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
public long getFirstChange(Range<Long> span, AddressRange range) {
|
public long getFirstChange(Range<Long> span, AddressRange range) {
|
||||||
assertInSpace(range);
|
assertInSpace(range);
|
||||||
long lower = DBTraceUtils.lowerEndpoint(span);
|
long lower = DBTraceUtils.lowerEndpoint(span);
|
||||||
|
|
|
@ -472,6 +472,32 @@ public interface TraceMemoryOperations {
|
||||||
: ByteOrder.LITTLE_ENDIAN);
|
: ByteOrder.LITTLE_ENDIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the internal storage block that most-recently defines the value at the given snap and
|
||||||
|
* address, and return the block's snap.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method reveals portions of the internal storage so that clients can optimize difference
|
||||||
|
* computations by eliminating corresponding ranges defined by the same block. If the underlying
|
||||||
|
* implementation cannot answer this question, this returns the given snap.
|
||||||
|
*
|
||||||
|
* @param snap the time
|
||||||
|
* @param address the location
|
||||||
|
* @return the most snap for the most recent containing block
|
||||||
|
*/
|
||||||
|
Long getSnapOfMostRecentChangeToBlock(long snap, Address address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the block size used by internal storage.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method reveals portions of the internal storage so that clients can optimize searches.
|
||||||
|
* If the underlying implementation cannot answer this question, this returns 0.
|
||||||
|
*
|
||||||
|
* @return the block size
|
||||||
|
*/
|
||||||
|
int getBlockSize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optimize storage space
|
* Optimize storage space
|
||||||
*
|
*
|
||||||
|
|
|
@ -368,7 +368,7 @@ public class Sequence implements Comparable<Sequence> {
|
||||||
*
|
*
|
||||||
* @param trace the trace to which the machine is bound
|
* @param trace the trace to which the machine is bound
|
||||||
* @param eventThread the thread for the first step, if it applies to the "last thread"
|
* @param eventThread the thread for the first step, if it applies to the "last thread"
|
||||||
* @param machine the machine to step
|
* @param machine the machine to step, or null to validate the sequence
|
||||||
* @param action the action to step each thread
|
* @param action the action to step each thread
|
||||||
* @param monitor a monitor for cancellation and progress reports
|
* @param monitor a monitor for cancellation and progress reports
|
||||||
* @return the last trace thread stepped during execution
|
* @return the last trace thread stepped during execution
|
||||||
|
@ -384,6 +384,22 @@ public class Sequence implements Comparable<Sequence> {
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate this sequence for the given trace
|
||||||
|
*
|
||||||
|
* @param trace the trace
|
||||||
|
* @param eventThread the thread for the first step, if it applies to the "last thread"
|
||||||
|
* @return the last trace thread that would be stepped by this sequence
|
||||||
|
*/
|
||||||
|
public TraceThread validate(Trace trace, TraceThread eventThread) {
|
||||||
|
try {
|
||||||
|
return execute(trace, eventThread, null, null, null);
|
||||||
|
}
|
||||||
|
catch (CancelledException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the key of the last thread stepped
|
* Get the key of the last thread stepped
|
||||||
*
|
*
|
||||||
|
|
|
@ -91,8 +91,8 @@ public interface Step extends Comparable<Step> {
|
||||||
TraceThread thread = isEventThread() ? eventThread : tm.getThread(getThreadKey());
|
TraceThread thread = isEventThread() ? eventThread : tm.getThread(getThreadKey());
|
||||||
if (thread == null) {
|
if (thread == null) {
|
||||||
if (isEventThread()) {
|
if (isEventThread()) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException("Thread must be given, e.g., 0:t1-3, " +
|
||||||
"Thread key -1 can only be used if last/event thread is given");
|
"since the last thread or snapshot event thread is not given.");
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Thread with key " + getThreadKey() + " does not exist in given trace");
|
"Thread with key " + getThreadKey() + " does not exist in given trace");
|
||||||
|
@ -160,6 +160,10 @@ public interface Step extends Comparable<Step> {
|
||||||
PcodeMachine<T> machine, Consumer<PcodeThread<T>> stepAction, TaskMonitor monitor)
|
PcodeMachine<T> machine, Consumer<PcodeThread<T>> stepAction, TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
TraceThread thread = getThread(tm, eventThread);
|
TraceThread thread = getThread(tm, eventThread);
|
||||||
|
if (machine == null) {
|
||||||
|
// Just performing validation (specifically thread parts)
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
|
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
|
||||||
execute(emuThread, stepAction, monitor);
|
execute(emuThread, stepAction, monitor);
|
||||||
return thread;
|
return thread;
|
||||||
|
|
|
@ -344,6 +344,22 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate this schedule for the given trace
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This performs a dry run of the sequence on the given trace. If the schedule starts on the
|
||||||
|
* "last thread," it verifies the snapshot gives the event thread. It also checks that every
|
||||||
|
* thread key in the sequence exists in the trace.
|
||||||
|
*
|
||||||
|
* @param trace the trace against which to validate this schedule
|
||||||
|
*/
|
||||||
|
public void validate(Trace trace) {
|
||||||
|
TraceThread lastThread = getEventThread(trace);
|
||||||
|
lastThread = steps.validate(trace, lastThread);
|
||||||
|
lastThread = pSteps.validate(trace, lastThread);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Realize the machine state for this schedule using the given trace and pre-positioned machine
|
* Realize the machine state for this schedule using the given trace and pre-positioned machine
|
||||||
*
|
*
|
||||||
|
@ -385,13 +401,13 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
* This schedule is left unmodified. If it had any p-code steps, those steps are dropped in the
|
* This schedule is left unmodified. If it had any p-code steps, those steps are dropped in the
|
||||||
* resulting schedule.
|
* resulting schedule.
|
||||||
*
|
*
|
||||||
* @param thread the thread to step
|
* @param thread the thread to step, or null for the "last thread"
|
||||||
* @param tickCount the number of ticks to take the thread forward
|
* @param tickCount the number of ticks to take the thread forward
|
||||||
* @return the resulting schedule
|
* @return the resulting schedule
|
||||||
*/
|
*/
|
||||||
public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
|
public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
|
||||||
Sequence steps = this.steps.clone();
|
Sequence steps = this.steps.clone();
|
||||||
steps.advance(new TickStep(thread.getKey(), tickCount));
|
steps.advance(new TickStep(thread == null ? -1 : thread.getKey(), tickCount));
|
||||||
return new TraceSchedule(snap, steps, new Sequence());
|
return new TraceSchedule(snap, steps, new Sequence());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,13 +457,13 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
* Returns the equivalent of executing the schedule followed by stepping the given thread
|
* Returns the equivalent of executing the schedule followed by stepping the given thread
|
||||||
* {@code pTickCount} more p-code operations
|
* {@code pTickCount} more p-code operations
|
||||||
*
|
*
|
||||||
* @param thread the thread to step
|
* @param thread the thread to step, or null for the "last thread"
|
||||||
* @param pTickCount the number of p-code ticks to take the thread forward
|
* @param pTickCount the number of p-code ticks to take the thread forward
|
||||||
* @return the resulting schedule
|
* @return the resulting schedule
|
||||||
*/
|
*/
|
||||||
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
|
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
|
||||||
Sequence pTicks = this.pSteps.clone();
|
Sequence pTicks = this.pSteps.clone();
|
||||||
pTicks.advance(new TickStep(thread.getKey(), pTickCount));
|
pTicks.advance(new TickStep(thread == null ? -1 : thread.getKey(), pTickCount));
|
||||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,14 +75,16 @@ public interface AutoService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) {
|
public static Wiring wireServicesConsumed(PluginTool tool, Object receiver) {
|
||||||
// TODO: Validate against PluginInfo?
|
|
||||||
|
|
||||||
AutoServiceListener<Object> listener = new AutoServiceListener<>(receiver);
|
AutoServiceListener<Object> listener = new AutoServiceListener<>(receiver);
|
||||||
PluginTool tool = plugin.getTool();
|
|
||||||
tool.addServiceListener(listener);
|
tool.addServiceListener(listener);
|
||||||
listener.notifyCurrentServices(tool);
|
listener.notifyCurrentServices(tool);
|
||||||
|
|
||||||
return new WiringImpl(listener);
|
return new WiringImpl(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) {
|
||||||
|
// TODO: Validate against PluginInfo?
|
||||||
|
return wireServicesConsumed(plugin.getTool(), receiver);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,14 +41,14 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
protected static final String OPT2_NAME = "Test Option 2";
|
protected static final String OPT2_NAME = "Test Option 2";
|
||||||
protected static final String OPT2_DEFAULT = "Default value";
|
protected static final String OPT2_DEFAULT = "Default value";
|
||||||
protected static final String OPT2_DESC = "Another test option";
|
protected static final String OPT2_DESC = "Another test option";
|
||||||
|
protected static final String OPT2_NEW_VALUE = "A new value";
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class",//
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsPlugin extends Plugin {
|
public static class AnnotatedWithOptionsPlugin extends Plugin {
|
||||||
@AutoOptionDefined(name = OPT1_NAME, description = OPT1_DESC)
|
@AutoOptionDefined(name = OPT1_NAME, description = OPT1_DESC)
|
||||||
private int myIntOption = OPT1_DEFAULT;
|
private int myIntOption = OPT1_DEFAULT;
|
||||||
|
@ -65,13 +65,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNoParamPlugin extends AnnotatedWithOptionsPlugin {
|
public static class AnnotatedWithOptionsNoParamPlugin extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNoParamCount;
|
protected int updateNoParamCount;
|
||||||
|
|
||||||
|
@ -85,13 +84,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOnlyParamDefaultPlugin
|
public static class AnnotatedWithOptionsNewOnlyParamDefaultPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOnlyParamDefaultNew;
|
protected int updateNewOnlyParamDefaultNew;
|
||||||
|
@ -107,13 +105,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOnlyParamAnnotatedPlugin
|
public static class AnnotatedWithOptionsNewOnlyParamAnnotatedPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOnlyParamAnnotatedNew;
|
protected int updateNewOnlyParamAnnotatedNew;
|
||||||
|
@ -128,13 +125,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsOldOnlyParamAnnotatedPlugin
|
public static class AnnotatedWithOptionsOldOnlyParamAnnotatedPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateOldOnlyParamAnnotatedOld;
|
protected int updateOldOnlyParamAnnotatedOld;
|
||||||
|
@ -149,13 +145,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOldParamDefaultPlugin
|
public static class AnnotatedWithOptionsNewOldParamDefaultPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOldParamDefaultNew;
|
protected int updateNewOldParamDefaultNew;
|
||||||
|
@ -172,13 +167,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOldParamNewAnnotPlugin
|
public static class AnnotatedWithOptionsNewOldParamNewAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOldParamNewAnnotNew;
|
protected int updateNewOldParamNewAnnotNew;
|
||||||
|
@ -195,13 +189,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOldParamOldAnnotPlugin
|
public static class AnnotatedWithOptionsNewOldParamOldAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOldParamOldAnnotNew;
|
protected int updateNewOldParamOldAnnotNew;
|
||||||
|
@ -218,13 +211,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOldParamNewOldAnnotPlugin
|
public static class AnnotatedWithOptionsNewOldParamNewOldAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOldParamNewOldAnnotNew;
|
protected int updateNewOldParamNewOldAnnotNew;
|
||||||
|
@ -242,13 +234,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsOldNewParamNewAnnotPlugin
|
public static class AnnotatedWithOptionsOldNewParamNewAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateOldNewParamNewAnnotNew;
|
protected int updateOldNewParamNewAnnotNew;
|
||||||
|
@ -265,13 +256,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsOldNewParamOldAnnotPlugin
|
public static class AnnotatedWithOptionsOldNewParamOldAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateOldNewParamOldAnnotNew;
|
protected int updateOldNewParamOldAnnotNew;
|
||||||
|
@ -288,13 +278,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsOldNewParamOldNewAnnotPlugin
|
public static class AnnotatedWithOptionsOldNewParamOldNewAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateOldNewParamOldNewAnnotNew;
|
protected int updateOldNewParamOldNewAnnotNew;
|
||||||
|
@ -312,13 +301,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "Consumer-only plugin class with auto option annotations", //
|
description = "Consumer-only plugin class with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "A consumer-only plugin class", //
|
shortDescription = "A consumer-only plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedConsumerOnlyPlugin extends Plugin {
|
public static class AnnotatedConsumerOnlyPlugin extends Plugin {
|
||||||
@AutoOptionConsumed(name = OPT1_NAME)
|
@AutoOptionConsumed(name = OPT1_NAME)
|
||||||
private int othersIntOption;
|
private int othersIntOption;
|
||||||
|
@ -387,7 +375,18 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertEquals(6, plugin.myIntOption);
|
assertEquals(6, plugin.myIntOption);
|
||||||
options.setInt(OPT1_NAME, OPT1_NEW_VALUE);
|
options.setInt(OPT1_NAME, OPT1_NEW_VALUE);
|
||||||
|
|
||||||
assertEquals(10, plugin.myIntOption);
|
assertEquals(OPT1_NEW_VALUE, plugin.myIntOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOptionsUpdatedExplicitCategory() throws PluginException {
|
||||||
|
AnnotatedWithOptionsPlugin plugin = addPlugin(tool, AnnotatedWithOptionsPlugin.class);
|
||||||
|
|
||||||
|
ToolOptions options = tool.getOptions(OPT2_CATEGORY);
|
||||||
|
assertEquals(1, options.getOptionNames().size());
|
||||||
|
options.setString(OPT2_NAME, OPT2_NEW_VALUE);
|
||||||
|
|
||||||
|
assertEquals(OPT2_NEW_VALUE, plugin.myStringOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,97 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
//
|
||||||
|
// Synchronize the category path name case from the first archive into the second
|
||||||
|
// archive.
|
||||||
|
//
|
||||||
|
//@category Data Types
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.program.model.data.Category;
|
||||||
|
import ghidra.program.model.data.FileDataTypeManager;
|
||||||
|
import ghidra.util.InvalidNameException;
|
||||||
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
|
|
||||||
|
public class SynchronizeGDTCategoryPaths extends GhidraScript {
|
||||||
|
|
||||||
|
private File firstFile;
|
||||||
|
private File secondFile;
|
||||||
|
private FileDataTypeManager firstArchive;
|
||||||
|
private FileDataTypeManager secondArchive;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void run() throws Exception {
|
||||||
|
firstFile = askFile("Select First GDT File", "Select 1st");
|
||||||
|
secondFile = askFile("Select Second GDT File", "Select 2nd");
|
||||||
|
|
||||||
|
try {
|
||||||
|
firstArchive = FileDataTypeManager.openFileArchive(firstFile, false);
|
||||||
|
secondArchive = FileDataTypeManager.openFileArchive(secondFile, true);
|
||||||
|
|
||||||
|
int transactionID = secondArchive.startTransaction("Synchronize Category Path Names");
|
||||||
|
Category firstCategory = firstArchive.getRootCategory();
|
||||||
|
Category secondCategory = secondArchive.getRootCategory();
|
||||||
|
|
||||||
|
synchronizeCategory(firstCategory, secondCategory);
|
||||||
|
secondArchive.endTransaction(transactionID, true);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (firstArchive != null) {
|
||||||
|
firstArchive.close();
|
||||||
|
}
|
||||||
|
secondArchive.save();
|
||||||
|
if (secondArchive != null) {
|
||||||
|
secondArchive.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void synchronizeCategory(Category firstCategory, Category secondCategory) {
|
||||||
|
Category[] firstCategories = firstCategory.getCategories();
|
||||||
|
for (Category categoryA : firstCategories) {
|
||||||
|
|
||||||
|
// loop through categories looking for a case agnostic path match
|
||||||
|
Category[] secondCategories = secondCategory.getCategories();
|
||||||
|
boolean foundIt = false;
|
||||||
|
for (Category categoryB : secondCategories) {
|
||||||
|
if (categoryA.getName().equalsIgnoreCase(categoryB.getName())) {
|
||||||
|
// if not the exact same name, rename it
|
||||||
|
if (!categoryA.getName().equals(categoryB.getName())) {
|
||||||
|
try {
|
||||||
|
println(
|
||||||
|
"Renamed " + categoryB.getName() + " to " + categoryA.getName());
|
||||||
|
categoryB.setName(categoryA.getName());
|
||||||
|
foundIt = true;
|
||||||
|
}
|
||||||
|
catch (DuplicateNameException | InvalidNameException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
foundIt = true;
|
||||||
|
}
|
||||||
|
synchronizeCategory(categoryA, categoryB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!foundIt) {
|
||||||
|
println("Couldn't find matching category for " + categoryA.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -705,6 +705,16 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension point to specify titles when dual panels are active
|
||||||
|
*
|
||||||
|
* @param panelProgram the program assigned to the panel whose title is requested
|
||||||
|
* @return the title of the panel for the given program
|
||||||
|
*/
|
||||||
|
protected String computePanelTitle(Program panelProgram) {
|
||||||
|
return panelProgram.getDomainFile().toString();
|
||||||
|
}
|
||||||
|
|
||||||
public void setPanel(ListingPanel lp) {
|
public void setPanel(ListingPanel lp) {
|
||||||
Program myProgram = listingPanel.getListingModel().getProgram();
|
Program myProgram = listingPanel.getListingModel().getProgram();
|
||||||
Program otherProgram = lp.getListingModel().getProgram();
|
Program otherProgram = lp.getListingModel().getProgram();
|
||||||
|
@ -712,10 +722,10 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
|
||||||
String otherName = myName;
|
String otherName = myName;
|
||||||
|
|
||||||
if (myProgram != null) {
|
if (myProgram != null) {
|
||||||
myName = myProgram.getDomainFile().toString();
|
myName = computePanelTitle(myProgram);
|
||||||
}
|
}
|
||||||
if (otherProgram != null) {
|
if (otherProgram != null) {
|
||||||
otherName = otherProgram.getDomainFile().toString();
|
otherName = computePanelTitle(otherProgram);
|
||||||
}
|
}
|
||||||
if (otherPanel != null) {
|
if (otherPanel != null) {
|
||||||
removeHoverServices(otherPanel);
|
removeHoverServices(otherPanel);
|
||||||
|
|
|
@ -22,7 +22,6 @@ import ghidra.app.util.cparser.CPP.PreProcessor.PPToken;
|
||||||
import ghidra.program.model.data.*;
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.program.util.AddressEvaluator;
|
import ghidra.program.util.AddressEvaluator;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.NumericUtilities;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -113,7 +112,8 @@ public class DefineTable {
|
||||||
node = new Hashtable();
|
node = new Hashtable();
|
||||||
findTable.put(chObj, node);
|
findTable.put(chObj, node);
|
||||||
findTable = node;
|
findTable = node;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
findTable = node;
|
findTable = node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,14 +271,14 @@ public class DefineTable {
|
||||||
*/
|
*/
|
||||||
private String macroSub(String image, int pos, ArrayList<String> sublist) {
|
private String macroSub(String image, int pos, ArrayList<String> sublist) {
|
||||||
int replaceCount = 0;
|
int replaceCount = 0;
|
||||||
|
|
||||||
StringBuffer buf = new StringBuffer(image);
|
StringBuffer buf = new StringBuffer(image);
|
||||||
|
|
||||||
// don't replace an infinite number of times.
|
// don't replace an infinite number of times.
|
||||||
//HashMap<String,Integer> lastReplStrings = new HashMap<String,Integer>();
|
//HashMap<String,Integer> lastReplStrings = new HashMap<String,Integer>();
|
||||||
while (pos < buf.length() && replaceCount < 900000) {
|
while (pos < buf.length() && replaceCount < 900000) {
|
||||||
String defName = getDefineAt(buf, pos);
|
String defName = getDefineAt(buf, pos);
|
||||||
if (shouldReplace(buf,defName,pos)) {
|
if (shouldReplace(buf, defName, pos)) {
|
||||||
// stop recursion on the same replacement string
|
// stop recursion on the same replacement string
|
||||||
// if (lastReplStrings.containsKey(defName)) {
|
// if (lastReplStrings.containsKey(defName)) {
|
||||||
// int lastpos = lastReplStrings.get(defName);
|
// int lastpos = lastReplStrings.get(defName);
|
||||||
|
@ -296,13 +296,15 @@ public class DefineTable {
|
||||||
// is there a replacement string
|
// is there a replacement string
|
||||||
if (newpos == -1) {
|
if (newpos == -1) {
|
||||||
pos++;
|
pos++;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
//System.err.println(" replace " + defName + " with " + buf.substring(pos,newpos));
|
//System.err.println(" replace " + defName + " with " + buf.substring(pos,newpos));
|
||||||
//lastReplStrings.put(defName,pos + defName.length());
|
//lastReplStrings.put(defName,pos + defName.length());
|
||||||
pos = newpos;
|
pos = newpos;
|
||||||
replaceCount++;
|
replaceCount++;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -312,36 +314,34 @@ public class DefineTable {
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean shouldReplace(StringBuffer buf, String defName, int pos) {
|
private boolean shouldReplace(StringBuffer buf, String defName, int pos) {
|
||||||
if (defName == null) {
|
if (defName == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//String nextRepl = "";
|
//String nextRepl = "";
|
||||||
int currIndex = buf.indexOf(defName, pos);
|
int currIndex = buf.indexOf(defName, pos);
|
||||||
if (currIndex < 0)
|
if (currIndex < 0) {
|
||||||
return false; // nothing to replace
|
return false; // nothing to replace
|
||||||
|
}
|
||||||
|
|
||||||
// this match is not exact so skip it (borrowing from JavaCharacter)
|
// this match is not exact so skip it (borrowing from JavaCharacter)
|
||||||
if (currIndex > 0
|
if (currIndex > 0 && (Character.isJavaIdentifierStart(buf.charAt(currIndex - 1)) ||
|
||||||
&& (Character
|
Character.isJavaIdentifierPart(buf.charAt(currIndex - 1)))) {
|
||||||
.isJavaIdentifierStart(buf.charAt(currIndex - 1)) || Character
|
|
||||||
.isJavaIdentifierPart(buf.charAt(currIndex - 1)))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int afterIndex = currIndex + defName.length();
|
int afterIndex = currIndex + defName.length();
|
||||||
if (afterIndex < buf.length()
|
if (afterIndex < buf.length() && (Character.isJavaIdentifierStart(buf.charAt(afterIndex)) ||
|
||||||
&& (Character.isJavaIdentifierStart(buf.charAt(afterIndex)) || Character
|
Character.isJavaIdentifierPart(buf.charAt(afterIndex)))) {
|
||||||
.isJavaIdentifierPart(buf.charAt(afterIndex)))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//nextRepl = image.substring(0, currIndex); // shift to location
|
//nextRepl = image.substring(0, currIndex); // shift to location
|
||||||
String replacementString = defs.get(defName).image; // get replacement text
|
String replacementString = defs.get(defName).image; // get replacement text
|
||||||
if (replacementString.equals(defName))
|
if (replacementString.equals(defName)) {
|
||||||
return false; // no need to replace
|
return false; // no need to replace
|
||||||
|
}
|
||||||
|
|
||||||
// // check that macro argv arguments match
|
// // check that macro argv arguments match
|
||||||
// Vector<PPToken> argv = getArgs(defName);
|
// Vector<PPToken> argv = getArgs(defName);
|
||||||
// if (argv != null && argv.size() > 0) {
|
// if (argv != null && argv.size() > 0) {
|
||||||
|
@ -360,41 +360,40 @@ public class DefineTable {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int replace(StringBuffer buf, String currKey, int fromIndex, ArrayList<String> sublist) {
|
int replace(StringBuffer buf, String currKey, int fromIndex, ArrayList<String> sublist) {
|
||||||
String replacementString = null;
|
String replacementString = null;
|
||||||
|
|
||||||
if (sublist == null) {
|
if (sublist == null) {
|
||||||
sublist = new ArrayList<String>();
|
sublist = new ArrayList<String>();
|
||||||
}
|
}
|
||||||
|
|
||||||
//String nextRepl = "";
|
//String nextRepl = "";
|
||||||
int currIndex = buf.indexOf(currKey, fromIndex);
|
int currIndex = buf.indexOf(currKey, fromIndex);
|
||||||
if (currIndex < 0)
|
if (currIndex < 0) {
|
||||||
return -1; // nothing to replace
|
return -1; // nothing to replace
|
||||||
|
}
|
||||||
|
|
||||||
// this match is not exact so skip it (borrowing from JavaCharacter)
|
// this match is not exact so skip it (borrowing from JavaCharacter)
|
||||||
if (currIndex > 0
|
if (currIndex > 0 && (Character.isJavaIdentifierStart(buf.charAt(currIndex - 1)) ||
|
||||||
&& (Character
|
Character.isJavaIdentifierPart(buf.charAt(currIndex - 1)))) {
|
||||||
.isJavaIdentifierStart(buf.charAt(currIndex - 1)) || Character
|
|
||||||
.isJavaIdentifierPart(buf.charAt(currIndex - 1)))) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int afterIndex = currIndex + currKey.length();
|
int afterIndex = currIndex + currKey.length();
|
||||||
if (afterIndex < buf.length()
|
if (afterIndex < buf.length() && (Character.isJavaIdentifierStart(buf.charAt(afterIndex)) ||
|
||||||
&& (Character.isJavaIdentifierStart(buf.charAt(afterIndex)) || Character
|
Character.isJavaIdentifierPart(buf.charAt(afterIndex)))) {
|
||||||
.isJavaIdentifierPart(buf.charAt(afterIndex)))) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//nextRepl = image.substring(0, currIndex); // shift to location
|
//nextRepl = image.substring(0, currIndex); // shift to location
|
||||||
replacementString = defs.get(currKey).image; // get replacement text
|
replacementString = defs.get(currKey).image; // get replacement text
|
||||||
if (replacementString.equals(currKey))
|
if (replacementString.equals(currKey)) {
|
||||||
return -1; // no need to replace
|
return -1; // no need to replace
|
||||||
|
}
|
||||||
|
|
||||||
// if current def has args, take care of the replacement of them
|
// if current def has args, take care of the replacement of them
|
||||||
Vector<PPToken> argv = getArgs(currKey);
|
Vector<PPToken> argv = getArgs(currKey);
|
||||||
int replacedSubpieceLen = currKey.length();
|
int replacedSubpieceLen = currKey.length();
|
||||||
|
@ -406,8 +405,7 @@ public class DefineTable {
|
||||||
// need to scan carefully, and recursively
|
// need to scan carefully, and recursively
|
||||||
// there shouldn't be so many globals...
|
// there shouldn't be so many globals...
|
||||||
// could be screwed up by so many things
|
// could be screwed up by so many things
|
||||||
String parms = getParams(buf, currIndex + currKey.length(),
|
String parms = getParams(buf, currIndex + currKey.length(), (char) 0);
|
||||||
(char) 0);
|
|
||||||
|
|
||||||
int parmslen = parms.length();
|
int parmslen = parms.length();
|
||||||
if (parmslen < 2) {
|
if (parmslen < 2) {
|
||||||
|
@ -417,30 +415,30 @@ public class DefineTable {
|
||||||
if (!parms.startsWith("(") || !parms.endsWith(")")) {
|
if (!parms.startsWith("(") || !parms.endsWith(")")) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
parms = parms.substring(1, parms.length() - 1);
|
parms = parms.substring(1, parms.length() - 1);
|
||||||
replacementString = subParams(replacementString, currKey, parms, argv);
|
replacementString = subParams(replacementString, currKey, parms, argv);
|
||||||
|
|
||||||
replacementString = joinPdPd(replacementString);
|
replacementString = joinPdPd(replacementString);
|
||||||
|
|
||||||
replacedSubpieceLen += parmslen;
|
replacedSubpieceLen += parmslen;
|
||||||
}
|
}
|
||||||
// you may add an else if{} block to warn of malformed macros
|
// you may add an else if{} block to warn of malformed macros
|
||||||
// but the actual culprit may be the Define() non-terminal
|
// but the actual culprit may be the Define() non-terminal
|
||||||
//if (replString != null)
|
//if (replString != null)
|
||||||
// nextRepl += replString;
|
// nextRepl += replString;
|
||||||
|
|
||||||
sublist = new ArrayList<String>(sublist);
|
sublist = new ArrayList<String>(sublist);
|
||||||
sublist.add(currKey);
|
sublist.add(currKey);
|
||||||
String newReplString = macroSub(replacementString,0, sublist);
|
String newReplString = macroSub(replacementString, 0, sublist);
|
||||||
if (newReplString != null) {
|
if (newReplString != null) {
|
||||||
replacementString = newReplString;
|
replacementString = newReplString;
|
||||||
}
|
}
|
||||||
buf.replace(currIndex, currIndex+replacedSubpieceLen, replacementString);
|
buf.replace(currIndex, currIndex + replacedSubpieceLen, replacementString);
|
||||||
//nextRepl += image.substring(currIndex + currKey.length());
|
//nextRepl += image.substring(currIndex + currKey.length());
|
||||||
return currIndex+replacementString.length();
|
return currIndex + replacementString.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* expand a define with arguments
|
* expand a define with arguments
|
||||||
*
|
*
|
||||||
|
@ -462,13 +460,10 @@ public class DefineTable {
|
||||||
}
|
}
|
||||||
pos += argValue.length() + 1;
|
pos += argValue.length() + 1;
|
||||||
if (index >= argv.size()) {
|
if (index >= argv.size()) {
|
||||||
Msg.error(
|
Msg.error(this,
|
||||||
this,
|
"Define parameter mismatch for macro " + defName + "(" + parms + ")" +
|
||||||
"Define parameter mismatch for macro " + defName
|
" Expected " + argv.size() + " arguments. " + " badarg(" + index + ") " +
|
||||||
+ "(" + parms + ")" + " Expected "
|
argValue + " args processed : " + argsfound);
|
||||||
+ argv.size() + " arguments. "
|
|
||||||
+ " badarg(" + index + ") " + argValue
|
|
||||||
+ " args processed : " + argsfound);
|
|
||||||
return replString;
|
return replString;
|
||||||
}
|
}
|
||||||
String curArgName = argv.elementAt(index).image;
|
String curArgName = argv.elementAt(index).image;
|
||||||
|
@ -489,20 +484,16 @@ public class DefineTable {
|
||||||
|
|
||||||
// this match is not exact so skip it (borrowing from
|
// this match is not exact so skip it (borrowing from
|
||||||
// JavaCharacter)
|
// JavaCharacter)
|
||||||
if (curpos > 0
|
if (curpos > 0 &&
|
||||||
&& (Character.isJavaIdentifierStart(substString
|
(Character.isJavaIdentifierStart(substString.charAt(curpos - 1)) ||
|
||||||
.charAt(curpos - 1)) || Character
|
Character.isJavaIdentifierPart(substString.charAt(curpos - 1)))) {
|
||||||
.isJavaIdentifierPart(substString
|
|
||||||
.charAt(curpos - 1)))) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int afterIndex = curpos + curArgName.length();
|
int afterIndex = curpos + curArgName.length();
|
||||||
if (afterIndex < substString.length()
|
if (afterIndex < substString.length() &&
|
||||||
&& (Character.isJavaIdentifierStart(substString
|
(Character.isJavaIdentifierStart(substString.charAt(afterIndex)) ||
|
||||||
.charAt(afterIndex)) || Character
|
Character.isJavaIdentifierPart(substString.charAt(afterIndex)))) {
|
||||||
.isJavaIdentifierPart(substString
|
|
||||||
.charAt(afterIndex)))) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,10 +507,10 @@ public class DefineTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
beginPos.add(insertLoc, begin);
|
beginPos.add(insertLoc, begin);
|
||||||
endPos.add(insertLoc,
|
endPos.add(insertLoc, new Integer(curpos + curArgName.length()));
|
||||||
new Integer(curpos + curArgName.length()));
|
|
||||||
subValue.add(insertLoc, argValue);
|
subValue.add(insertLoc, argValue);
|
||||||
} while (curpos >= 0);
|
}
|
||||||
|
while (curpos >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuffer buf = new StringBuffer();
|
StringBuffer buf = new StringBuffer();
|
||||||
|
@ -566,8 +557,9 @@ public class DefineTable {
|
||||||
}
|
}
|
||||||
if (!hitQuote && ch == ')') {
|
if (!hitQuote && ch == ')') {
|
||||||
depth--;
|
depth--;
|
||||||
if (depth == 0 && endChar == 0)
|
if (depth == 0 && endChar == 0) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
// hit a paren above depth, back up
|
// hit a paren above depth, back up
|
||||||
if (depth < 0) {
|
if (depth < 0) {
|
||||||
pos--;
|
pos--;
|
||||||
|
@ -617,35 +609,37 @@ public class DefineTable {
|
||||||
inString = !inString;
|
inString = !inString;
|
||||||
}
|
}
|
||||||
quotePos--;
|
quotePos--;
|
||||||
} while (quotePos > currIndex);
|
}
|
||||||
|
while (quotePos > currIndex);
|
||||||
|
|
||||||
int afterIndex = currIndex + 2;
|
int afterIndex = currIndex + 2;
|
||||||
while (currIndex > 0 && image.charAt(currIndex - 1) == ' ') {
|
while (currIndex > 0 && image.charAt(currIndex - 1) == ' ') {
|
||||||
currIndex--; // scan back for first non-blank before ##
|
currIndex--; // scan back for first non-blank before ##
|
||||||
}
|
}
|
||||||
|
|
||||||
while (afterIndex < image.length()
|
while (afterIndex < image.length() && image.charAt(afterIndex) == ' ') {
|
||||||
&& image.charAt(afterIndex) == ' ') {
|
|
||||||
afterIndex++; // scan back for first non-blank before ##
|
afterIndex++; // scan back for first non-blank before ##
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inString) {
|
if (!inString) {
|
||||||
buf.replace(currIndex, afterIndex, "");
|
buf.replace(currIndex, afterIndex, "");
|
||||||
currIndex--;
|
currIndex--;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
currIndex -= 2;
|
currIndex -= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (currIndex > 0);
|
}
|
||||||
|
while (currIndex > 0);
|
||||||
image = buf.toString();
|
image = buf.toString();
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a data type manager, populate defines with constant values as Enums
|
* Given a data type manager, populate defines with constant values as Enums
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public void populateDefineEquates(DataTypeManager dtMgr) {
|
public void populateDefineEquates(DataTypeManager dtMgr) {
|
||||||
int transactionID = dtMgr.startTransaction("Add Equates");
|
int transactionID = dtMgr.startTransaction("Add Equates");
|
||||||
|
|
||||||
|
@ -663,15 +657,20 @@ public class DefineTable {
|
||||||
String strValue = getValue(defName);
|
String strValue = getValue(defName);
|
||||||
String strExpanded = expand(strValue, true);
|
String strExpanded = expand(strValue, true);
|
||||||
strValue = strExpanded;
|
strValue = strExpanded;
|
||||||
|
|
||||||
// strip off any casting/parentheses
|
// strip off any casting/parentheses
|
||||||
strValue = stripCast(strValue);
|
strValue = stripCast(strValue);
|
||||||
|
|
||||||
long value = 0;
|
long value = 0;
|
||||||
Long lvalue = getCValue(strValue);
|
Long lvalue = getCValue(strValue);
|
||||||
|
|
||||||
if (lvalue == null) {
|
if (lvalue == null) {
|
||||||
lvalue = AddressEvaluator.evaluateToLong(strValue);
|
try {
|
||||||
|
lvalue = AddressEvaluator.evaluateToLong(strValue);
|
||||||
|
}
|
||||||
|
catch (Exception exc) {
|
||||||
|
// ignore didn't parse well
|
||||||
|
}
|
||||||
if (lvalue == null) {
|
if (lvalue == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -710,9 +709,10 @@ public class DefineTable {
|
||||||
if (strValue.startsWith("0x")) {
|
if (strValue.startsWith("0x")) {
|
||||||
start = 2;
|
start = 2;
|
||||||
radix = 16;
|
radix = 16;
|
||||||
} else if (strValue.startsWith("0")) {
|
}
|
||||||
|
else if (strValue.startsWith("0")) {
|
||||||
start = 1;
|
start = 1;
|
||||||
radix = 8;
|
radix = 8;
|
||||||
}
|
}
|
||||||
if (strValue.endsWith("ul") || strValue.endsWith("ll")) {
|
if (strValue.endsWith("ul") || strValue.endsWith("ll")) {
|
||||||
strValue = strValue.substring(0, strValue.length() - 2);
|
strValue = strValue.substring(0, strValue.length() - 2);
|
||||||
|
@ -720,11 +720,11 @@ public class DefineTable {
|
||||||
else if (strValue.endsWith("l") || strValue.endsWith("u")) {
|
else if (strValue.endsWith("l") || strValue.endsWith("u")) {
|
||||||
strValue = strValue.substring(0, strValue.length() - 1);
|
strValue = strValue.substring(0, strValue.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start != 0) {
|
if (start != 0) {
|
||||||
strValue = strValue.substring(start);
|
strValue = strValue.substring(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Long.parseLong(strValue, radix);
|
return Long.parseLong(strValue, radix);
|
||||||
}
|
}
|
||||||
catch (RuntimeException e) {
|
catch (RuntimeException e) {
|
||||||
|
@ -732,7 +732,7 @@ public class DefineTable {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* create a category path based on a name, or the root category with no name
|
* create a category path based on a name, or the root category with no name
|
||||||
*/
|
*/
|
||||||
|
@ -757,41 +757,42 @@ public class DefineTable {
|
||||||
}
|
}
|
||||||
return path.substring(slashpos + 1);
|
return path.substring(slashpos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Strip off any casts
|
* Strip off any casts
|
||||||
*/
|
*/
|
||||||
private static String stripCast(String strValue) {
|
private static String stripCast(String strValue) {
|
||||||
strValue = strValue.trim();
|
strValue = strValue.trim();
|
||||||
|
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
while (pos < strValue.length()) {
|
while (pos < strValue.length()) {
|
||||||
int procLen = 1;
|
int procLen = 1;
|
||||||
int startPos = strValue.indexOf('(',pos);
|
int startPos = strValue.indexOf('(', pos);
|
||||||
if (startPos == -1) {
|
if (startPos == -1) {
|
||||||
return strValue; // done, no more open parens
|
return strValue; // done, no more open parens
|
||||||
}
|
}
|
||||||
pos = startPos;
|
pos = startPos;
|
||||||
int endParen = strValue.indexOf(')', pos+1);
|
int endParen = strValue.indexOf(')', pos + 1);
|
||||||
if (endParen != -1) {
|
if (endParen != -1) {
|
||||||
String subStr = strValue.substring(pos+1, endParen);
|
String subStr = strValue.substring(pos + 1, endParen);
|
||||||
if (subStr.length() > 0) {
|
if (subStr.length() > 0) {
|
||||||
int subPos = 0;
|
int subPos = 0;
|
||||||
subStr = subStr.trim();
|
subStr = subStr.trim();
|
||||||
boolean isValid = Character.isJavaIdentifierStart(subStr.charAt(0));
|
boolean isValid = Character.isJavaIdentifierStart(subStr.charAt(0));
|
||||||
while (isValid && subPos < subStr.length()) {
|
while (isValid && subPos < subStr.length()) {
|
||||||
char ch = subStr.charAt(subPos++);
|
char ch = subStr.charAt(subPos++);
|
||||||
isValid |= Character.isJavaIdentifierPart(ch);
|
isValid |= Character.isJavaIdentifierPart(ch);
|
||||||
}
|
}
|
||||||
// if looks like a cast, throw it away
|
// if looks like a cast, throw it away
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
strValue = strValue.substring(0, pos) + strValue.substring(endParen+1);
|
strValue = strValue.substring(0, pos) + strValue.substring(endParen + 1);
|
||||||
procLen = 0;
|
procLen = 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return strValue; // no more end parens, just finish
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return strValue; // no more end parens, just finish
|
||||||
|
}
|
||||||
pos = pos + procLen;
|
pos = pos + procLen;
|
||||||
}
|
}
|
||||||
return strValue;
|
return strValue;
|
||||||
|
|
|
@ -2596,7 +2596,7 @@ Object CastExpression() : {
|
||||||
(
|
(
|
||||||
LOOKAHEAD("(" TypeName() ")")
|
LOOKAHEAD("(" TypeName() ")")
|
||||||
(
|
(
|
||||||
"(" TypeName() ")" ( CastExpression() |
|
"(" TypeName() ")" ( obj = CastExpression() |
|
||||||
( "{" InitializerList() [ "," ] "}" ) )
|
( "{" InitializerList() [ "," ] "}" ) )
|
||||||
)
|
)
|
||||||
|
|
|
|
||||||
|
@ -2633,13 +2633,12 @@ Object UnaryExpression() : {
|
||||||
|
|
|
|
||||||
<SIZEOF>
|
<SIZEOF>
|
||||||
(
|
(
|
||||||
LOOKAHEAD(UnaryExpression() )
|
"(" (dt = TypeName() | <IDENTIFIER> | obj = ConstantExpression() ) ")"
|
||||||
UnaryExpression()
|
|
||||||
|
|
|
||||||
"(" dt = TypeName() ")"
|
|
||||||
{
|
{
|
||||||
if (dt != null) {
|
if (dt != null) {
|
||||||
obj = new Integer(dt.getLength());
|
obj = Long.valueOf(dt.getLength());
|
||||||
|
} else if (obj != null && obj instanceof String) {
|
||||||
|
obj = Long.valueOf(((String) obj).length() - 1); // will include "" plus \0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -2690,7 +2689,7 @@ Object PrimaryExpression() : {
|
||||||
Object obj = null;
|
Object obj = null;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
( <IDENTIFIER> |
|
( obj = <IDENTIFIER> |
|
||||||
obj = Constant() |
|
obj = Constant() |
|
||||||
"(" obj = Expression() ")" )
|
"(" obj = Expression() ")" )
|
||||||
{
|
{
|
||||||
|
@ -2729,11 +2728,11 @@ Object Constant() : {
|
||||||
}
|
}
|
||||||
if (sval.startsWith("0x") || sval.startsWith("0X")) {
|
if (sval.startsWith("0x") || sval.startsWith("0X")) {
|
||||||
BigInteger bigConst = new BigInteger(sval.substring(2), 16);
|
BigInteger bigConst = new BigInteger(sval.substring(2), 16);
|
||||||
obj = new Long(bigConst.longValue());
|
obj = Long.valueOf(bigConst.longValue());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
BigInteger bigConst = new BigInteger(sval);
|
BigInteger bigConst = new BigInteger(sval);
|
||||||
obj = new Long(bigConst.longValue());
|
obj = Long.valueOf(bigConst.longValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
|
||||||
|
@ -2748,7 +2747,7 @@ Object Constant() : {
|
||||||
obj = new Character(t.image.charAt(0));
|
obj = new Character(t.image.charAt(0));
|
||||||
} else if (t.image.length() == 4) {
|
} else if (t.image.length() == 4) {
|
||||||
long cval = ((long)t.image.charAt(0) << 24) + ((long)t.image.charAt(1) << 16) + ((long)t.image.charAt(2) << 8) + ((long) t.image.charAt(3));
|
long cval = ((long)t.image.charAt(0) << 24) + ((long)t.image.charAt(1) << 16) + ((long)t.image.charAt(2) << 8) + ((long) t.image.charAt(3));
|
||||||
obj = new Long(cval);
|
obj = Long.valueOf(cval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
|
||||||
|
|
|
@ -789,11 +789,28 @@ public class PreProcessor {
|
||||||
iFile = new File(parent + File.separator + filename);
|
iFile = new File(parent + File.separator + filename);
|
||||||
if (iFile.exists())
|
if (iFile.exists())
|
||||||
return iFile;
|
return iFile;
|
||||||
|
|
||||||
// try just in this directory
|
// try just in this directory
|
||||||
File sameiFile = new File(parent + File.separator
|
File sameiFile = new File(parent + File.separator
|
||||||
+ (new File(filename)).getName());
|
+ (new File(filename)).getName());
|
||||||
if (sameiFile.exists())
|
if (sameiFile.exists())
|
||||||
return sameiFile;
|
return sameiFile;
|
||||||
|
|
||||||
|
// try all files in this directory doing to-lower on both input file and output file
|
||||||
|
// if match return it
|
||||||
|
File folder = new File(parent);
|
||||||
|
if (folder.isDirectory()) {
|
||||||
|
File[] listOfFiles = folder.listFiles();
|
||||||
|
|
||||||
|
if (listOfFiles != null) {
|
||||||
|
for (File file : listOfFiles) {
|
||||||
|
if (file.isFile() && filename.compareToIgnoreCase(file.getName()) == 0) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (possibleXsym) {
|
if (possibleXsym) {
|
||||||
String child = "";
|
String child = "";
|
||||||
// look up parent chain, looking for a file that has an Xsym in it
|
// look up parent chain, looking for a file that has an Xsym in it
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.datamgr;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import generic.jar.ResourceFile;
|
||||||
|
import generic.test.AbstractGenericTest;
|
||||||
|
import ghidra.framework.Application;
|
||||||
|
import ghidra.program.model.data.*;
|
||||||
|
|
||||||
|
public class DataTypeArchiveIDTest extends AbstractGenericTest {
|
||||||
|
|
||||||
|
private static final String WIN_VS12_32_GDT_PATH = "typeinfo/win32/windows_vs12_32.gdt";
|
||||||
|
private static final String WIN_VS12_64_GDT_PATH = "typeinfo/win32/windows_vs12_64.gdt";
|
||||||
|
private static final String GENERIC_CLIB_32_GDT_PATH = "typeinfo/generic/generic_clib.gdt";
|
||||||
|
private static final String GENERIC_CLIB_64_GDT_PATH = "typeinfo/generic/generic_clib_64.gdt";
|
||||||
|
private static final String MAC_OS_10_9_GDT_PATH = "typeinfo/mac_10.9/mac_osx.gdt";
|
||||||
|
|
||||||
|
private static final HashMap<String, String> archiveIdMap = new HashMap<>();
|
||||||
|
static {
|
||||||
|
archiveIdMap.put(WIN_VS12_32_GDT_PATH, "2644092282468053077");
|
||||||
|
archiveIdMap.put(WIN_VS12_64_GDT_PATH, "3193696833254024484");
|
||||||
|
archiveIdMap.put(GENERIC_CLIB_32_GDT_PATH, "2644097909188870631");
|
||||||
|
archiveIdMap.put(GENERIC_CLIB_64_GDT_PATH, "3193699959493190971");
|
||||||
|
archiveIdMap.put(MAC_OS_10_9_GDT_PATH, "2650667045259492112");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testArchiveIDMatch() throws IOException {
|
||||||
|
|
||||||
|
HashSet<String> notFound = new HashSet<>(archiveIdMap.keySet());
|
||||||
|
|
||||||
|
for (ResourceFile gdtFile : Application.findFilesByExtensionInApplication(".gdt")) {
|
||||||
|
|
||||||
|
String path = gdtFile.getAbsolutePath();
|
||||||
|
if (!path.contains("/data/typeinfo/")) {
|
||||||
|
continue; // only verify standard archives
|
||||||
|
}
|
||||||
|
|
||||||
|
int ix = path.indexOf("/typeinfo/");
|
||||||
|
path = path.substring(ix + 1); // path starts with typeinfo/...
|
||||||
|
|
||||||
|
String oldID = archiveIdMap.get(path);
|
||||||
|
if (oldID == null) {
|
||||||
|
fail("New archive added, test must be updated: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
notFound.remove(path);
|
||||||
|
|
||||||
|
FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false);
|
||||||
|
try {
|
||||||
|
assertEquals("Archive UniversalID mismatch: " + path, oldID,
|
||||||
|
dtm.getUniversalID().toString());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
dtm.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!notFound.isEmpty()) {
|
||||||
|
System.out.println("The following standard archives were not found:");
|
||||||
|
for (String p : notFound) {
|
||||||
|
System.out.println("missing archive: " + p);
|
||||||
|
}
|
||||||
|
fail("One or more standard archives are missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyArchive(DataType dt, String gdtPath) {
|
||||||
|
SourceArchive sourceArchive = dt.getSourceArchive();
|
||||||
|
assertEquals(archiveIdMap.get(gdtPath), sourceArchive.getSourceArchiveID().toString());
|
||||||
|
int ix = gdtPath.lastIndexOf('/');
|
||||||
|
String gdtName = gdtPath.substring(ix + 1);
|
||||||
|
ix = gdtName.indexOf(".gdt");
|
||||||
|
gdtName = gdtName.substring(0, ix); // strip-off file extension
|
||||||
|
assertEquals(gdtName, sourceArchive.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void spotCheckWindowsVS12_32() throws IOException {
|
||||||
|
ResourceFile gdtFile = Application.getModuleDataFile(WIN_VS12_32_GDT_PATH);
|
||||||
|
FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false);
|
||||||
|
try {
|
||||||
|
DataType dt = dtm.getDataType("/winsock.h/fd_set");
|
||||||
|
assertNotNull(dt);
|
||||||
|
assertEquals("2592696207400888580", dt.getUniversalID().toString());
|
||||||
|
verifyArchive(dt, WIN_VS12_32_GDT_PATH);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
dtm.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void spotCheckWindowsVS12_64() throws IOException {
|
||||||
|
ResourceFile gdtFile = Application.getModuleDataFile(WIN_VS12_64_GDT_PATH);
|
||||||
|
FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false);
|
||||||
|
try {
|
||||||
|
DataType dt = dtm.getDataType("/winsock.h/fd_set");
|
||||||
|
assertNotNull(dt);
|
||||||
|
assertEquals("3193696894570554681", dt.getUniversalID().toString());
|
||||||
|
verifyArchive(dt, WIN_VS12_64_GDT_PATH);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
dtm.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void spotCheckGenericCLib32() throws IOException {
|
||||||
|
ResourceFile gdtFile = Application.getModuleDataFile(GENERIC_CLIB_32_GDT_PATH);
|
||||||
|
FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false);
|
||||||
|
try {
|
||||||
|
DataType dt = dtm.getDataType("/select.h/fd_set");
|
||||||
|
assertNotNull(dt);
|
||||||
|
assertEquals("2592696207400888580", dt.getUniversalID().toString());
|
||||||
|
verifyArchive(dt, GENERIC_CLIB_32_GDT_PATH);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
dtm.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void spotCheckGenericCLib64() throws IOException {
|
||||||
|
ResourceFile gdtFile = Application.getModuleDataFile(GENERIC_CLIB_64_GDT_PATH);
|
||||||
|
FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false);
|
||||||
|
try {
|
||||||
|
DataType dt = dtm.getDataType("/select.h/fd_set");
|
||||||
|
assertNotNull(dt);
|
||||||
|
assertEquals("3193700096632251689", dt.getUniversalID().toString());
|
||||||
|
verifyArchive(dt, GENERIC_CLIB_64_GDT_PATH);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
dtm.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void spotCheckMacOS10_9() throws IOException {
|
||||||
|
ResourceFile gdtFile = Application.getModuleDataFile(MAC_OS_10_9_GDT_PATH);
|
||||||
|
FileDataTypeManager dtm = FileDataTypeManager.openFileArchive(gdtFile, false);
|
||||||
|
try {
|
||||||
|
DataType dt = dtm.getDataType("/_fd_def.h/fd_set");
|
||||||
|
assertNotNull(dt);
|
||||||
|
assertEquals("3015963966244190568", dt.getUniversalID().toString());
|
||||||
|
verifyArchive(dt, MAC_OS_10_9_GDT_PATH);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
dtm.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -429,5 +429,23 @@ public class CParserTest extends AbstractGenericTest {
|
||||||
assertTrue(a.isZeroLength());
|
assertTrue(a.isZeroLength());
|
||||||
assertTrue(a.getDataType() instanceof UnsignedLongDataType);
|
assertTrue(a.getDataType() instanceof UnsignedLongDataType);
|
||||||
assertEquals(4, a.getElementLength());
|
assertEquals(4, a.getElementLength());
|
||||||
|
|
||||||
|
dt = dtMgr.getDataType(new CategoryPath("/"), "sizeof_t");
|
||||||
|
assertTrue(dt instanceof Structure);
|
||||||
|
sdt = (Structure) dt;
|
||||||
|
DataTypeComponent cdt = sdt.getComponent(0);
|
||||||
|
assertTrue(cdt.getDataType() instanceof Array);
|
||||||
|
assertEquals("Array field defined with sizeof typedef", 128, cdt.getLength());
|
||||||
|
|
||||||
|
dt = dtMgr.getDataType(new CategoryPath("/"), "cpu_set_t");
|
||||||
|
assertTrue(dt instanceof Structure);
|
||||||
|
sdt = (Structure) dt;
|
||||||
|
cdt = sdt.getComponent(0);
|
||||||
|
assertTrue(cdt.getDataType() instanceof Array);
|
||||||
|
assertEquals("Array field defined with sizeof typedef", 128, cdt.getLength());
|
||||||
|
cdt = sdt.getComponent(1);
|
||||||
|
assertTrue(cdt.getDataType() instanceof Array);
|
||||||
|
assertEquals("Array field defined with sizeof typedef", 2084, cdt.getLength());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* EXCLUDE: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,6 +17,7 @@
|
||||||
/** Test parsing header file for CParser. Most of the file is just checked to make sure it gets through parsing.
|
/** Test parsing header file for CParser. Most of the file is just checked to make sure it gets through parsing.
|
||||||
** Some data types are checked. More checking of the parsed information would be beneficial at some point.
|
** Some data types are checked. More checking of the parsed information would be beneficial at some point.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** use of long as an attribute
|
** use of long as an attribute
|
||||||
|
@ -229,6 +229,8 @@ __checkint(int val, int* err) {
|
||||||
if (val < (-2147483647-1) || val > 2147483647) {
|
if (val < (-2147483647-1) || val > 2147483647) {
|
||||||
*err |= OVR_ERR;
|
*err |= OVR_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val = 8 * sizeof(val);
|
||||||
|
|
||||||
return (int32_t) val;
|
return (int32_t) val;
|
||||||
}
|
}
|
||||||
|
@ -339,6 +341,22 @@ typedef struct sigevent
|
||||||
} sigevent_t;
|
} sigevent_t;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Complicated sizeof array size
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned long int val[(1024 / (8 * sizeof (unsigned long int)))];
|
||||||
|
} sizeof_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef unsigned long int __cpu_mask;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
__cpu_mask __bits[1024 / (8 * (int) sizeof (__cpu_mask))];
|
||||||
|
char szUrl[(2048 + 32 + sizeof("://"))] ;
|
||||||
|
} cpu_set_t;
|
||||||
|
|
||||||
|
|
||||||
void __mem_func (void *, char **, int ***, long (*) (size_t),
|
void __mem_func (void *, char **, int ***, long (*) (size_t),
|
||||||
|
@ -389,7 +407,7 @@ struct sockaddr_in
|
||||||
struct in_addr sin_addr;
|
struct in_addr sin_addr;
|
||||||
|
|
||||||
|
|
||||||
unsigned char sin_zero[sizeof (struct bob) -
|
unsigned char sin_zero[3 * sizeof (struct bob) -
|
||||||
(sizeof (unsigned short int)) -
|
(sizeof (unsigned short int)) -
|
||||||
sizeof (int) -
|
sizeof (int) -
|
||||||
sizeof (struct bob)];
|
sizeof (struct bob)];
|
||||||
|
|
|
@ -43,12 +43,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
private InputDialogListener listener;
|
private InputDialogListener listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a provider for a generic input dialog with the specified title, a text field,
|
* Creates a provider for a generic input dialog with the specified title, a text field, labeled
|
||||||
* labeled by the specified label. The user should check the value of
|
* by the specified label. The user should check the value of "isCanceled()" to know whether or
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* not the user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* the value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param label value to use for the label of the text field
|
* @param label value to use for the label of the text field
|
||||||
*/
|
*/
|
||||||
|
@ -57,12 +57,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param label value to use for the label of the text field
|
* @param label value to use for the label of the text field
|
||||||
* @param initialValue initial value to use for the text field
|
* @param initialValue initial value to use for the text field
|
||||||
|
@ -72,12 +72,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param label value to use for the label of the text field
|
* @param label value to use for the label of the text field
|
||||||
* @param initialValue initial value to use for the text field
|
* @param initialValue initial value to use for the text field
|
||||||
|
@ -89,12 +89,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param label value to use for the label of the text field
|
* @param label value to use for the label of the text field
|
||||||
* @param initialValue initial value to use for the text field
|
* @param initialValue initial value to use for the text field
|
||||||
|
@ -105,12 +105,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param labels values to use for the labels of the text fields
|
* @param labels values to use for the labels of the text fields
|
||||||
* @param initialValues initial values to use for the text fields
|
* @param initialValues initial values to use for the text fields
|
||||||
|
@ -120,12 +120,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param labels values to use for the labels of the text fields
|
* @param labels values to use for the labels of the text fields
|
||||||
* @param initialValues initial values to use for the text fields
|
* @param initialValues initial values to use for the text fields
|
||||||
|
@ -194,6 +194,7 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
textFields = new MyTextField[inputLabels.length];
|
textFields = new MyTextField[inputLabels.length];
|
||||||
for (int i = 0; i < inputValues.length; i++) {
|
for (int i = 0; i < inputValues.length; i++) {
|
||||||
textFields[i] = new MyTextField(initialValues[i]);
|
textFields[i] = new MyTextField(initialValues[i]);
|
||||||
|
inputValues[i] = initialValues[i];
|
||||||
textFields[i].addKeyListener(keyListener);
|
textFields[i].addKeyListener(keyListener);
|
||||||
textFields[i].setName("input.dialog.text.field." + i);
|
textFields[i].setName("input.dialog.text.field." + i);
|
||||||
panel.add(new GLabel(inputLabels[i], SwingConstants.RIGHT));
|
panel.add(new GLabel(inputLabels[i], SwingConstants.RIGHT));
|
||||||
|
@ -221,11 +222,16 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
@Override
|
@Override
|
||||||
protected void cancelCallback() {
|
protected void cancelCallback() {
|
||||||
isCanceled = true;
|
isCanceled = true;
|
||||||
|
for (int v = 0; v < inputValues.length; v++) {
|
||||||
|
inputValues[v] = null;
|
||||||
|
}
|
||||||
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if this dialog is cancelled
|
* Returns if this dialog is cancelled
|
||||||
|
*
|
||||||
* @return true if cancelled
|
* @return true if cancelled
|
||||||
*/
|
*/
|
||||||
public boolean isCanceled() {
|
public boolean isCanceled() {
|
||||||
|
@ -234,6 +240,7 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the value of the first (and maybe only) text field
|
* Return the value of the first (and maybe only) text field
|
||||||
|
*
|
||||||
* @return the text field value
|
* @return the text field value
|
||||||
*/
|
*/
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
|
@ -242,6 +249,7 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the text of the primary text field
|
* Sets the text of the primary text field
|
||||||
|
*
|
||||||
* @param text the text
|
* @param text the text
|
||||||
*/
|
*/
|
||||||
public void setValue(String text) {
|
public void setValue(String text) {
|
||||||
|
@ -250,15 +258,18 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the text of the text field at the given index
|
* Sets the text of the text field at the given index
|
||||||
|
*
|
||||||
* @param text the text
|
* @param text the text
|
||||||
* @param index the index of the text field
|
* @param index the index of the text field
|
||||||
*/
|
*/
|
||||||
public void setValue(String text, int index) {
|
public void setValue(String text, int index) {
|
||||||
textFields[index].setText(text);
|
textFields[index].setText(text);
|
||||||
|
inputValues[index] = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the values for all the text field(s)
|
* Return the values for all the text field(s)
|
||||||
|
*
|
||||||
* @return the text field values
|
* @return the text field values
|
||||||
*/
|
*/
|
||||||
public String[] getValues() {
|
public String[] getValues() {
|
||||||
|
@ -285,7 +296,8 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see javax.swing.text.Document#insertString(int, java.lang.String, javax.swing.text.AttributeSet)
|
* @see javax.swing.text.Document#insertString(int, java.lang.String,
|
||||||
|
* javax.swing.text.AttributeSet)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void insertString(int offs, String str, AttributeSet a)
|
public void insertString(int offs, String str, AttributeSet a)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
MODULE FILE LICENSE: lib/cglib-nodep-2.2.jar Apache License 2.0
|
MODULE FILE LICENSE: lib/cglib-nodep-2.2.jar Apache License 2.0
|
||||||
MODULE FILE LICENSE: lib/guava-19.0.jar Apache License 2.0
|
MODULE FILE LICENSE: lib/guava-19.0.jar Apache License 2.0
|
||||||
MODULE FILE LICENSE: lib/jdom-legacy-1.1.3.jar JDOM License
|
MODULE FILE LICENSE: lib/jdom-legacy-1.1.3.jar JDOM License
|
||||||
MODULE FILE LICENSE: lib/log4j-api-2.12.1.jar Apache License 2.0
|
MODULE FILE LICENSE: lib/log4j-api-2.15.0.jar Apache License 2.0
|
||||||
MODULE FILE LICENSE: lib/log4j-core-2.12.1.jar Apache License 2.0
|
MODULE FILE LICENSE: lib/log4j-core-2.15.0.jar Apache License 2.0
|
||||||
MODULE FILE LICENSE: lib/commons-collections4-4.1.jar Apache License 2.0
|
MODULE FILE LICENSE: lib/commons-collections4-4.1.jar Apache License 2.0
|
||||||
MODULE FILE LICENSE: lib/commons-lang3-3.9.jar Apache License 2.0
|
MODULE FILE LICENSE: lib/commons-lang3-3.9.jar Apache License 2.0
|
||||||
MODULE FILE LICENSE: lib/commons-io-2.6.jar Apache License 2.0
|
MODULE FILE LICENSE: lib/commons-io-2.6.jar Apache License 2.0
|
||||||
|
|
|
@ -29,8 +29,8 @@ dependencies {
|
||||||
api "cglib:cglib-nodep:2.2"
|
api "cglib:cglib-nodep:2.2"
|
||||||
api "com.google.guava:guava:19.0"
|
api "com.google.guava:guava:19.0"
|
||||||
api "org.jdom:jdom-legacy:1.1.3"
|
api "org.jdom:jdom-legacy:1.1.3"
|
||||||
api "org.apache.logging.log4j:log4j-api:2.12.1"
|
api "org.apache.logging.log4j:log4j-api:2.15.0"
|
||||||
api "org.apache.logging.log4j:log4j-core:2.12.1"
|
api "org.apache.logging.log4j:log4j-core:2.15.0"
|
||||||
api "org.apache.commons:commons-collections4:4.1"
|
api "org.apache.commons:commons-collections4:4.1"
|
||||||
api "org.apache.commons:commons-lang3:3.9"
|
api "org.apache.commons:commons-lang3:3.9"
|
||||||
api "org.apache.commons:commons-text:1.6"
|
api "org.apache.commons:commons-text:1.6"
|
||||||
|
|
|
@ -105,6 +105,7 @@ public class DataTypeArchiveTransformerPanel extends JPanel {
|
||||||
gbc.gridx = 3;
|
gbc.gridx = 3;
|
||||||
gbc.gridwidth = 1;
|
gbc.gridwidth = 1;
|
||||||
useOldFileIDCheckBox = new GCheckBox(" Use Old File ID");
|
useOldFileIDCheckBox = new GCheckBox(" Use Old File ID");
|
||||||
|
useOldFileIDCheckBox.setSelected(true);
|
||||||
filePanel.add(useOldFileIDCheckBox, gbc);
|
filePanel.add(useOldFileIDCheckBox, gbc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue