From 32e2ef2ee75d0ea6cac5bfd414ae9dfc01053a71 Mon Sep 17 00:00:00 2001 From: jmlagor <73651947+jmlagor@users.noreply.github.com> Date: Wed, 10 Mar 2021 08:34:29 -0500 Subject: [PATCH] GP-729 Decode RUNTIME_INFO and UNWIND_INFO structures in the PE .pdata section --- .github/ISSUE_TEMPLATE/bug_report.md | 3 +- .../src/global/docs/ChangeHistory.html | 211 ++++--- .../Base/data/PEFunctionsThatDoNotReturn | 8 + .../navigation/GoToAddressLabelPlugin.java | 44 +- .../app/util/bin/format/pe/FileHeader.java | 553 ++++++++++-------- .../pe/ImageRuntimeFunctionEntries.java | 486 +++++++++++++++ .../app/util/bin/format/pe/NTHeader.java | 19 +- .../ghidra/app/util/opinion/PeLoader.java | 81 ++- .../gfilesystem/FileSystemService.java | 2 +- .../ghidra/util/state/VarnodeOperation.java | 3 +- .../datamgr/DataTypeManagerPluginTest.java | 28 +- .../Decompiler/ghidra_scripts/GraphAST.java | 14 +- .../Decompiler/src/main/doc/pcoderef.xml | 2 +- .../core/decompile/actions/ASTGraphTask.java | 9 +- .../formats/sevenzip/SevenZipFileSystem.java | 6 +- .../demangler/gnu/GnuDemanglerParser.java | 15 +- Ghidra/Features/GraphServices/Module.manifest | 4 +- Ghidra/Features/GraphServices/build.gradle | 6 +- .../visualization/DefaultGraphDisplay.java | 96 ++- .../graph/visualization/LayoutFunction.java | 61 +- .../LayoutTransitionManager.java | 62 +- .../visualization/mouse/JgtGraphMouse.java | 8 +- .../mouse/JgtSatelliteGraphMouse.java | 10 +- .../mouse/JgtSelectingGraphMousePlugin.java | 10 +- .../src/main/resources/jungrapht.properties | 12 +- .../util/pdb/pdbapplicator/PdbApplicator.java | 5 +- .../main/java/docking/ActionToGuiMapper.java | 5 +- .../main/java/docking/DockableComponent.java | 27 +- .../java/docking/DockingErrorDisplay.java | 37 +- .../java/docking/DockingWindowManager.java | 50 +- .../src/main/java/docking/ErrLogDialog.java | 15 +- .../main/java/docking/PopupActionManager.java | 22 +- .../main/java/docking/PopupMenuContext.java | 61 ++ .../docking/action/KeyBindingsManager.java | 4 +- .../docking/action/ShowContextMenuAction.java | 52 ++ .../java/docking/actions/ToolActions.java | 4 + .../menu/DockingCheckboxMenuItemUI.java | 4 +- .../java/docking/menu/DockingMenuItemUI.java | 3 +- .../main/java/docking/menu/DockingMenuUI.java | 4 +- .../docking/test/AbstractDockingTest.java | 2 +- .../tree/tasks/GTreeStartEditingTask.java | 15 +- .../util/DockingWindowsLookAndFeelUtils.java | 5 +- .../java/ghidra/util/ReservedKeyBindings.java | 9 +- .../util/task/TaskMonitorComponent.java | 18 +- .../docking/framework/SplashScreenTest.java | 107 ++-- .../graph/viewer/popup/PopupRegulator.java | 8 + .../program/model/pcode/PcodeFactory.java | 2 +- .../program/model/pcode/PcodeSyntaxTree.java | 4 +- .../ghidra/program/model/pcode/Varnode.java | 6 +- .../program/model/pcode/VarnodeAST.java | 12 +- .../ghidra/service/graph/GraphDisplay.java | 46 ++ .../ghidra.xtext.sleigh.feature/category.xml | 6 +- .../ghidra.xtext.sleigh.feature/feature.xml | 2 +- .../META-INF/MANIFEST.MF | 2 +- .../META-INF/MANIFEST.MF | 3 +- .../META-INF/MANIFEST.MF | 2 +- .../ghidra.xtext.sleigh/META-INF/MANIFEST.MF | 2 +- .../ghidra.xtext.sleigh/plugin.xml | 6 +- .../src/ghidra/xtext/sleigh/Sleigh.xtext | 3 +- .../languages/html/pcodedescription.html | 2 +- 60 files changed, 1630 insertions(+), 678 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ImageRuntimeFunctionEntries.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/action/ShowContextMenuAction.java diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 385782a424..2882356cb6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -29,7 +29,8 @@ If applicable, please attach any files that caused problems or log files generat **Environment (please complete the following information):** - OS: [e.g. macOS 10.14.2] - Java Version: [e.g. 11.0] - - Ghidra Version: [e.g. 9.0] + - Ghidra Version: [e.g. 9.1.2] + - Ghidra Origin: [e.g. official ghidra-sre.org distro, third party distro, locally built] **Additional context** Add any other context about the problem here. diff --git a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html index aabe465742..dc38d983fe 100644 --- a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html +++ b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.html @@ -6,14 +6,61 @@ +

Ghidra 9.2.3 Change History (March 2021)

+

Improvements

+ +
+

Bugs

+ +
+

Ghidra 9.2.2 Change History (December 2020)

Bugs

@@ -24,6 +71,7 @@
  • Analysis. Updated RTTI analyzer to find type_info vftable when it cannot be found with its mangled name. This will enable many more Windows programs to have their RTTI structures created that were unable to be parsed in previous Ghidra versions. (GP-141)
  • API. Relaxed memory block naming restrictions and restored ability to have spaces in memory block names. However, if a memory block is flagged as an overlay, the associated overlay space name may be modified to ensure validity and uniqueness. The DuplicateNameException has been removed from all memory block API methods since this was entirely an overlay space concern. Memory block GUI has also been changed eliminate the duplicate block name restriction. (GP-420, Issue #2465)
  • Build. Eliminated the need for installation of bison and flex when performing source-based gradle build of Ghidra or the Decompiler module. The generated files are now included with source files and maintained in source control. A separate gradle Decompiler:generateParsers task, which still requires bison and flex, must be used, explicitly, when changes are made to lex/yacc source files. (GP-467)
  • +
  • Graphing. Fixed issue with exporting graphs to DOT format due to invalid vertex IDs. (GP-280)
  • Graphing. Improved graphing where it did not navigate when clicking on external function nodes. Now it will navigate to the fake function location in the program, which is the location of the pointer to the external function. (GP-493)
  • Listing:Symbols. Removed restriction for naming labels that resemble default label names. (GT-3185, Issue #1057)
  • PDB. Crafted PDB type ID records 0x1608 and 0x1609 with presumed class and struct types and follow-on application of these types. Also fixed up some fall-back data type logic and improved some warning messages to reflect the cause of the conditions. (GP-474, Issue #2523)
  • @@ -37,7 +85,7 @@
  • Decompiler. Fixed issue with the Auto Create/Fill Structure command that caused it to silently miss some pointer accesses. (GP-344)
  • Decompiler. Jump table recovery now takes into account encoded bits, like ARM/THUMB mode transition, that may be present in address tables. (GP-387, Issue #2420)
  • Decompiler. Fixed a bug in the Decompiler renaming action when applied to function references. (GP-477, Issue #2415)
  • -
  • Decompiler. Corrected 8-byte return value storage specification in compiler-spec affecting longlong and double return values. Endianess ordering of r0/r1 was incorrect. (GP-512, Issue #2547)
  • +
  • Decompiler. Corrected 8-byte return value storage specification in compiler-spec affecting longlong and double return values. Endianess ordering of r0/r1 was incorrect. (GP-512, Issue #2547)
  • Graphing. Fixed the Function Graph's drag-to-select-nodes feature. (GP-430)
  • Graphing. Fixed issue where the graph in the satellite view is sometimes truncated. (GP-469)
  • Graphing. Fixed a stack trace issue caused by reusing a graph display window to show a graph that is larger than is allowed. (GP-492)
  • @@ -126,7 +174,7 @@
  • Importer:ELF. Added support for processing Android packed ELF Relocation Tables. (GT-3320, Issue #1192)
  • Importer:ELF. Added ELF import opinion for ARM BE8. (GT-3642, Issue #1187)
  • Importer:ELF. Added support for ELF RELR relocations, such as those produced for Android. (GP-348)
  • -
  • Importer:MachO. DYLD Loader can now load x86_64 DYLD from macOS. (GT-3611, Issue #1566)
  • +
  • Importer:Mach-O. DYLD Loader can now load x86_64 DYLD from macOS. (GT-3611, Issue #1566)
  • Importer:PE. Improved parsing of Microsoft ordinal map files produced with DUMPBIN /EXPORTS (see Ghidra/Features/Base/data/symbols/README.txt). (GT-3235)
  • Jython. Upgraded Jython to version 2.7.2. (GP-109)
  • Listing. In the PCode field of the Listing, accesses of varnodes in the unique space are now always shown with the size of the access. Fixed bug which would cause the PCode emulator to reject valid pcode in rare instances. (GP-196)
  • @@ -282,8 +330,8 @@
  • Disassembly. Corrected potential infinite loop with disassembler caused by branch to self with invalid delay slot instruction. (GT-3511, Issue #1486)
  • GUI. Corrected processor manual display for Microsoft Windows users, which was not displaying processor manual and was, instead, rendering a blank page in web browser. (GT-3444)
  • GUI:Bitfield Editor. Added field comment support to composite bitfield editor. (GT-3410)
  • -
  • Importer:MachO. A MachO loader regression, in Ghidra 9.1.1, when laying down symbols at the correct location, has been fixed. (GT-3487, Issue #1446)
  • -
  • Multi-User:Ghidra Server. Corrected Ghidra Server remote interface errors that occur when running with Java 11.0.6 (and later) release, which would throw RemoteException "Method is not Remote" errors. (GT-3521, Issue #1440)
  • +
  • Importer:Mach-O. A Mach-O loader regression, in Ghidra 9.1.1, when laying down symbols at the correct location, has been fixed. (GT-3487, Issue #1446)
  • +
  • Multi-User:Ghidra Server. Corrected Ghidra Server remote interface errors that occur when running with Java 11.0.6 (and later) release, which would throw RemoteException Method is not Remote errors. (GT-3521, Issue #1440)
  • PDB. Corrected PDB XML generation for zero-length classes and structures and resolved various datatype dependency issues encountered during PDB Analysis. Changed line numbers from hex to decimal. (GT-3462, Issue #1410)
  • Processors. Corrected mnemonic for ARM thumb RSB.w instruction. (GT-3420, Issue #1365)
  • Processors. Corrected issue in M68000 with some move instructions not creating correct array assignments. (GT-3429, Issue #1394)
  • @@ -294,7 +342,7 @@

    Ghidra 9.1.1 Change History (December 2019)

    Improvements

    @@ -331,7 +379,7 @@
  • Eclipse Integration. Added new GhidraSleighEditor Eclipse plugin in the installation directory under Extensions/Eclipse. (GT-113)
  • GUI. Added method for turning off table sorting by control-clicking the only sorted table column. (GT-2763, Issue #87)
  • GUI. Hovering on an address will now show where the byte at that address came from in the imported file. (GT-3016, Issue #154)
  • -
  • Importer:MachO. Added new importer/loader for DYLD-shared cache files. (GT-2343)
  • +
  • Importer:Mach-O. Added new importer/loader for DYLD-shared cache files. (GT-2343)
  • Memory. Added new API to preserve imported program's original bytes and how they map to memory blocks. (GT-2845)
  • Processors. Implemented Intel MCS-96 processor module. (GT-2350)
  • Processors. Added SH1/2/2a sleigh processor specification. (GT-3029, Issue #715)
  • @@ -488,7 +536,7 @@
  • Listing. Cursor in the listing now stays in the proper column after editing a field. (GT-3045, Issue #702)
  • Listing. Fixed a problem with register highlighting that could occur on certain register/sub-register combinations. (GT-3071, Issue #810)
  • Multi-User. Corrected terminate checkout from viewed checkout list which was always terminating first row range based upon number of selected rows and not the actual selected rows. (GT-2903)
  • -
  • Multi-user. Corrected ability for user to cancel checkin/checkout to Ghidra Server. (GT-3208)
  • +
  • Multi-User. Corrected ability for user to cancel checkin/checkout to Ghidra Server. (GT-3208)
  • Multi-User:Ghidra Server. Added proper Ghidra Server interface binding with new -i option. Corrected -ip option to strictly convey remote access hostname to clients. The updated server will only accept connections from Ghidra 9.1 and later clients due to the registry port now employing TLS. (GT-2685, Issue #101, #645)
  • Multi-User:Ghidra Server. Fixed argument-passing bug in svrAdmin script. (GT-3082, Issue #907)
  • Multi-User:Merge. Corrected merge problem affecting modified Function Definition datatypes which could result in a NullPointerException. (GT-2922)
  • @@ -518,7 +566,7 @@
  • Program API. Corrected parameter storage which failed to properly refresh after undo/redo. (GT-3130, Issue #960)
  • Program API. Corrected function parameter ordinal numbering when more than one auto-parameter is present. (GT-3214)
  • Project Manager. Fixed a problem with creating Ghidra projects in Windows root directories (e.g., Z:\). (GT-2585)
  • -
  • Project Manager. Fixed a path traversal vulnerability that could occur when restoring a malicious project archive. (GT-3001, Issue #789)
  • +
  • Project Manager. Fixed a path-traversal vulnerability that could occur when restoring a malicious project archive. (GT-3001, Issue #789)
  • Scripting. GhidraScript.askDomainFile() now correctly throws a CancelledException when the cancel button is clicked. (GT-2841)
  • Scripting. Removed deprecated scripting methods older than 5 releases. (GT-2949)
  • Security. Removed use of nonsecure XMLEncoder/XMLDecoder from Ghidra code base. (GT-3198, Issue #1090)
  • @@ -531,128 +579,123 @@

    Ghidra 9.0.4 Change History (May 2019)

    Bugs

    Ghidra 9.0.3 Change History (April 2019)

    New Features

    Improvements

    Bugs

    Ghidra 9.0.2 Change History (April 2019)

    Bugs

    Security

    Ghidra 9.0.1 Change History (March 2019)

    New Features

    Improvements

    Bugs

    Security

    diff --git a/Ghidra/Features/Base/data/PEFunctionsThatDoNotReturn b/Ghidra/Features/Base/data/PEFunctionsThatDoNotReturn index e8ed7404f4..51c5089410 100644 --- a/Ghidra/Features/Base/data/PEFunctionsThatDoNotReturn +++ b/Ghidra/Features/Base/data/PEFunctionsThatDoNotReturn @@ -1,7 +1,15 @@ +abort CxxThrowException CxxThrowException@8 CxxFrameHandler3 crtExitProcess ExitProcess +ExitThread exit +FreeLibraryAndExitThread +invalid_parameter_noinfo_noreturn +invoke_watson longjmp +quick_exit +RpcRaiseException +terminate diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPlugin.java index eef0b65f3c..b77dafd9f5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPlugin.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.navigation; import java.awt.event.KeyEvent; +import docking.ActionContext; import docking.action.*; import docking.tool.ToolConstants; import ghidra.GhidraOptions; @@ -29,7 +30,7 @@ import ghidra.app.util.HelpTopics; import ghidra.app.util.navigation.GoToAddressLabelDialog; import ghidra.framework.options.*; import ghidra.framework.plugintool.*; -import ghidra.framework.plugintool.util.*; +import ghidra.framework.plugintool.util.PluginStatus; import ghidra.util.HelpLocation; import ghidra.util.bean.opteditor.OptionsVetoException; @@ -91,11 +92,21 @@ public class GoToAddressLabelPlugin extends Plugin implements OptionsChangeListe public void actionPerformed(NavigatableActionContext context) { goToDialog.show(context.getNavigatable(), context.getAddress(), tool); } + + @Override + protected boolean isEnabledForContext(NavigatableActionContext context) { + return context.getProgram() != null; + } + + @Override + public boolean isAddToPopup(ActionContext context) { + return true; + } }; action.setHelpLocation(new HelpLocation(HelpTopics.NAVIGATION, action.getName())); - action.setMenuBarData(new MenuData( - new String[] { ToolConstants.MENU_NAVIGATION, "Go To..." }, null, "GoTo", - MenuData.NO_MNEMONIC, "2")); // second item in the menu + action.setMenuBarData( + new MenuData(new String[] { ToolConstants.MENU_NAVIGATION, "Go To..." }, null, "GoTo", + MenuData.NO_MNEMONIC, "2")); // second item in the menu action.setKeyBindingData(new KeyBindingData(KeyEvent.VK_G, 0)); @@ -144,10 +155,11 @@ public class GoToAddressLabelPlugin extends Plugin implements OptionsChangeListe * @param newValue new value of the option */ @Override - public void optionsChanged(ToolOptions options, String opName, Object oldValue, Object newValue) { + public void optionsChanged(ToolOptions options, String opName, Object oldValue, + Object newValue) { if (opName.equals(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES)) { maximumGotoEntries = - options.getInt(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES); + options.getInt(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES); if (maximumGotoEntries <= 0) { throw new OptionsVetoException("Search limit must be greater than 0"); } @@ -155,7 +167,7 @@ public class GoToAddressLabelPlugin extends Plugin implements OptionsChangeListe } else if (opName.equals(GhidraOptions.OPTION_NUMERIC_FORMATTING)) { cStyleInput = - options.getBoolean(GhidraOptions.OPTION_NUMERIC_FORMATTING, DEFAULT_C_STYLE); + options.getBoolean(GhidraOptions.OPTION_NUMERIC_FORMATTING, DEFAULT_C_STYLE); goToDialog.setCStyleInput(cStyleInput); } else if (opName.equals(GO_TO_MEMORY)) { @@ -182,20 +194,20 @@ public class GoToAddressLabelPlugin extends Plugin implements OptionsChangeListe ToolOptions opt = tool.getOptions(ToolConstants.TOOL_OPTIONS); // descriptions opt.registerOption(GhidraOptions.OPTION_NUMERIC_FORMATTING, DEFAULT_C_STYLE, null, - "Interpret value entered in the Go To dialog as either hex, " - + "octal, or binary number."); + "Interpret value entered in the Go To dialog as either hex, " + + "octal, or binary number."); opt.registerOption(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES, null, - "Max number of entries remembered in the go to list."); + "Max number of entries remembered in the go to list."); opt.registerOption(GO_TO_MEMORY, DEFAULT_MEMORY, null, - "Remember the last successful go to input in the " - + "Go To dialog. If this option is enabled, then the " - + "Go To dialog will leave the last " - + "successful go to input in the combo box of the Go " - + "To dialog and will select the " + "value for easy paste replacement."); + "Remember the last successful go to input in the " + + "Go To dialog. If this option is enabled, then the " + + "Go To dialog will leave the last " + + "successful go to input in the combo box of the Go " + + "To dialog and will select the " + "value for easy paste replacement."); // options maximumGotoEntries = - opt.getInt(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES); + opt.getInt(GhidraOptions.OPTION_MAX_GO_TO_ENTRIES, DEFAULT_MAX_GOTO_ENTRIES); cStyleInput = opt.getBoolean(GhidraOptions.OPTION_NUMERIC_FORMATTING, DEFAULT_C_STYLE); goToDialog.setCStyleInput(cStyleInput); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/FileHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/FileHeader.java index 2a9cfebdd2..7d1dd90681 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/FileHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/FileHeader.java @@ -22,6 +22,7 @@ import java.util.List; import ghidra.app.util.bin.StructConverter; import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader; +import ghidra.app.util.bin.format.pe.ImageRuntimeFunctionEntries._IMAGE_RUNTIME_FUNCTION_ENTRY; import ghidra.app.util.bin.format.pe.debug.DebugCOFFSymbol; import ghidra.app.util.bin.format.pe.debug.DebugCOFFSymbolAux; import ghidra.program.model.data.*; @@ -45,7 +46,7 @@ import ghidra.util.exception.DuplicateNameException; * WORD Characteristics; // MANDATORY * } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; * - * + * */ public class FileHeader implements StructConverter { /** @@ -55,128 +56,131 @@ public class FileHeader implements StructConverter { /** * The size of the IMAGE_FILE_HEADER in bytes. */ - public final static int IMAGE_SIZEOF_FILE_HEADER = 20; + public final static int IMAGE_SIZEOF_FILE_HEADER = 20; /** * Relocation info stripped from file. */ - public final static int IMAGE_FILE_RELOCS_STRIPPED = 0x0001; - /** - * File is executable (no unresolved externel references). - */ - public final static int IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002; - /** - * Line nunbers stripped from file. - */ - public final static int IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004; - /** - * Local symbols stripped from file. - */ - public final static int IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008; - /** - * Agressively trim working set - */ - public final static int IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010; - /** - * App can handle >2gb addresses - */ - public final static int IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020; - /** - * Bytes of machine word are reversed. - */ - public final static int IMAGE_FILE_BYTES_REVERSED_LO = 0x0080; - /** - * 32 bit word machine. - */ - public final static int IMAGE_FILE_32BIT_MACHINE = 0x0100; - /** - * Debugging info stripped from file in .DBG file - */ - public final static int IMAGE_FILE_DEBUG_STRIPPED = 0x0200; - /** - * If Image is on removable media, copy and run from the swap file. - */ - public final static int IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400; - /** - * If Image is on Net, copy and run from the swap file. - */ - public final static int IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800; - /** - * System File. - */ - public final static int IMAGE_FILE_SYSTEM = 0x1000; - /** - * File is a DLL. - */ - public final static int IMAGE_FILE_DLL = 0x2000; - /** - * File should only be run on a UP machine - */ - public final static int IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000; - /** - * Bytes of machine word are reversed. - */ - public final static int IMAGE_FILE_BYTES_REVERSED_HI = 0x8000; + public final static int IMAGE_FILE_RELOCS_STRIPPED = 0x0001; + /** + * File is executable (no unresolved externel references). + */ + public final static int IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002; + /** + * Line nunbers stripped from file. + */ + public final static int IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004; + /** + * Local symbols stripped from file. + */ + public final static int IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008; + /** + * Agressively trim working set + */ + public final static int IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010; + /** + * App can handle >2gb addresses + */ + public final static int IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020; + /** + * Bytes of machine word are reversed. + */ + public final static int IMAGE_FILE_BYTES_REVERSED_LO = 0x0080; + /** + * 32 bit word machine. + */ + public final static int IMAGE_FILE_32BIT_MACHINE = 0x0100; + /** + * Debugging info stripped from file in .DBG file + */ + public final static int IMAGE_FILE_DEBUG_STRIPPED = 0x0200; + /** + * If Image is on removable media, copy and run from the swap file. + */ + public final static int IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400; + /** + * If Image is on Net, copy and run from the swap file. + */ + public final static int IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800; + /** + * System File. + */ + public final static int IMAGE_FILE_SYSTEM = 0x1000; + /** + * File is a DLL. + */ + public final static int IMAGE_FILE_DLL = 0x2000; + /** + * File should only be run on a UP machine. + */ + public final static int IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000; + /** + * Bytes of machine word are reversed. + */ + public final static int IMAGE_FILE_BYTES_REVERSED_HI = 0x8000; - public final static String [] CHARACTERISTICS = { - "Relocation info stripped from file", - "File is executable (i.e. no unresolved externel references)", - "Line nunbers stripped from file", - "Local symbols stripped from file", - "Agressively trim working set", - "App can handle >2gb addresses", - "Bytes of machine word are reversed", - "32 bit word machine", - "Debugging info stripped from file in .DBG file", - "If Image is on removable media, copy and run from the swap file", - "If Image is on Net, copy and run from the swap file", - "System file", - "File is a DLL", - "File should only be run on a UP machine", - "Bytes of machine word are reversed" - }; + /** + * Magic value in LordPE's Symbol Table pointer field. + */ + private final static int LORDPE_SYMBOL_TABLE = 0x726F4C5B; + /** + * Magic value in LordPE's Number of Symbols field. + */ + private final static int LORDPE_NUMBER_OF_SYMBOLS = 0x5D455064; - private short machine; - private short numberOfSections; - private int timeDateStamp; - private int pointerToSymbolTable; - private int numberOfSymbols; - private short sizeOfOptionalHeader; // delta between start of OptionalHeader and start of section table - private short characteristics; + public final static String[] CHARACTERISTICS = { "Relocation info stripped from file", + "File is executable (i.e. no unresolved externel references)", + "Line nunbers stripped from file", "Local symbols stripped from file", + "Agressively trim working set", "App can handle >2gb addresses", + "Bytes of machine word are reversed", "32 bit word machine", + "Debugging info stripped from file in .DBG file", + "If Image is on removable media, copy and run from the swap file", + "If Image is on Net, copy and run from the swap file", "System file", "File is a DLL", + "File should only be run on a UP machine", "Bytes of machine word are reversed" }; - private SectionHeader [] sectionHeaders; - private Listsymbols = new ArrayList<>(); + private short machine; + private short numberOfSections; + private int timeDateStamp; + private int pointerToSymbolTable; + private int numberOfSymbols; + private short sizeOfOptionalHeader; // delta between start of OptionalHeader and start of section table + private short characteristics; - private FactoryBundledWithBinaryReader reader; - private int startIndex; - private NTHeader ntHeader; + private SectionHeader[] sectionHeaders; + private List symbols = new ArrayList<>(); + private List<_IMAGE_RUNTIME_FUNCTION_ENTRY> irfes = new ArrayList<>(); - static FileHeader createFileHeader( - FactoryBundledWithBinaryReader reader, int startIndex, - NTHeader ntHeader) throws IOException { - FileHeader fileHeader = (FileHeader) reader.getFactory().create(FileHeader.class); - fileHeader.initFileHeader(reader, startIndex, ntHeader); - return fileHeader; - } + private FactoryBundledWithBinaryReader reader; + private int startIndex; + private NTHeader ntHeader; - /** - * DO NOT USE THIS CONSTRUCTOR, USE create*(GenericFactory ...) FACTORY METHODS INSTEAD. - */ - public FileHeader() {} + static FileHeader createFileHeader(FactoryBundledWithBinaryReader reader, int startIndex, + NTHeader ntHeader) throws IOException { + FileHeader fileHeader = (FileHeader) reader.getFactory().create(FileHeader.class); + fileHeader.initFileHeader(reader, startIndex, ntHeader); + return fileHeader; + } - private void initFileHeader(FactoryBundledWithBinaryReader reader, int startIndex, NTHeader ntHeader) throws IOException { - this.reader = reader; - this.startIndex = startIndex; - this.ntHeader = ntHeader; + /** + * DO NOT USE THIS CONSTRUCTOR, USE create*(GenericFactory ...) FACTORY METHODS INSTEAD. + */ + public FileHeader() { + } + + private void initFileHeader(FactoryBundledWithBinaryReader reader, int startIndex, + NTHeader ntHeader) throws IOException { + this.reader = reader; + this.startIndex = startIndex; + this.ntHeader = ntHeader; + + parse(); + } - parse(); - } - /** * Returns the architecture type of the computer. * @return the architecture type of the computer */ - public short getMachine() { + public short getMachine() { return machine; } @@ -184,131 +188,137 @@ public class FileHeader implements StructConverter { * Returns a string representation of the architecture type of the computer. * @return a string representation of the architecture type of the computer */ - public String getMachineName() { - return MachineName.getName(machine); - } - + public String getMachineName() { + return MachineName.getName(machine); + } + /** - * Returns the number of sections. + * Returns the number of sections. * Sections equate to Ghidra memory blocks. * @return the number of sections */ - public int getNumberOfSections() { - return numberOfSections; - } + public int getNumberOfSections() { + return numberOfSections; + } /** * Returns the array of section headers. * @return the array of section headers */ - public SectionHeader [] getSectionHeaders() { - if (sectionHeaders == null) { - return new SectionHeader[0]; - } - return sectionHeaders; - } + public SectionHeader[] getSectionHeaders() { + if (sectionHeaders == null) { + return new SectionHeader[0]; + } + return sectionHeaders; + } /** * Returns the array of symbols. * @return the array of symbols */ - public List getSymbols() { + public List getSymbols() { return symbols; } + public List<_IMAGE_RUNTIME_FUNCTION_ENTRY> getImageRuntimeFunctionEntries() { + return irfes; + } + /** * Returns the section header that contains the specified virtual address. * @param virtualAddr the virtual address * @return the section header that contains the specified virtual address */ - public SectionHeader getSectionHeaderContaining(int virtualAddr) { - for (SectionHeader sectionHeader : sectionHeaders) { - int start = sectionHeader.getVirtualAddress(); - int end = sectionHeader.getVirtualAddress()+sectionHeader.getVirtualSize()-1; - if (virtualAddr >= start && virtualAddr <= end) { - return sectionHeader; - } - } - return null; - } + public SectionHeader getSectionHeaderContaining(int virtualAddr) { + for (SectionHeader sectionHeader : sectionHeaders) { + int start = sectionHeader.getVirtualAddress(); + int end = sectionHeader.getVirtualAddress() + sectionHeader.getVirtualSize() - 1; + if (virtualAddr >= start && virtualAddr <= end) { + return sectionHeader; + } + } + return null; + } /** * Returns the section header at the specified position in the array. * @param index index of section header to return * @return the section header at the specified position in the array, or null if invalid */ - public SectionHeader getSectionHeader(int index) { + public SectionHeader getSectionHeader(int index) { if (index >= 0 && index < sectionHeaders.length) { - return sectionHeaders[index]; - } - return null; - } + return sectionHeaders[index]; + } + return null; + } /** * Returns the time stamp of the image. * @return the time stamp of the image */ - public int getTimeDateStamp() { - return timeDateStamp; - } + public int getTimeDateStamp() { + return timeDateStamp; + } /** * Returns the file offset of the COFF symbol table * @return the file offset of the COFF symbol table */ - public int getPointerToSymbolTable() { - return pointerToSymbolTable; - } + public int getPointerToSymbolTable() { + return pointerToSymbolTable; + } /** * Returns the number of symbols in the COFF symbol table * @return the number of symbols in the COFF symbol table */ - public int getNumberOfSymbols() { - return numberOfSymbols; - } + public int getNumberOfSymbols() { + return numberOfSymbols; + } /** * Returns the size of the optional header data * @return the size of the optional header, in bytes */ - public int getSizeOfOptionalHeader() { + public int getSizeOfOptionalHeader() { return sizeOfOptionalHeader; } /** - * Returns a set of bit flags indicating attributes of the file. + * Returns a set of bit flags indicating attributes of the file. * @return a set of bit flags indicating attributes */ - public int getCharacteristics() { - return characteristics; - } + public int getCharacteristics() { + return characteristics; + } /** * Returns the file pointer to the section headers. * @return the file pointer to the section headers */ - public int getPointerToSections() { - short sizeOptHdr = ntHeader.getFileHeader().sizeOfOptionalHeader; + public int getPointerToSections() { + short sizeOptHdr = ntHeader.getFileHeader().sizeOfOptionalHeader; int ptrToSections = startIndex + IMAGE_SIZEOF_FILE_HEADER + sizeOptHdr; - int testSize = ntHeader.getOptionalHeader().is64bit() - ? Constants.IMAGE_SIZEOF_NT_OPTIONAL64_HEADER - : Constants.IMAGE_SIZEOF_NT_OPTIONAL32_HEADER; - if (sizeOptHdr != testSize) { + int testSize = + ntHeader.getOptionalHeader().is64bit() ? Constants.IMAGE_SIZEOF_NT_OPTIONAL64_HEADER + : Constants.IMAGE_SIZEOF_NT_OPTIONAL32_HEADER; + if (sizeOptHdr != testSize) { Msg.warn(this, "Non-standard optional header size: " + sizeOptHdr + " bytes"); - } + } return ptrToSections; - } + } - void processSections(OptionalHeader optHeader) throws IOException { - long oldIndex = reader.getPointerIndex(); + void processSections(OptionalHeader optHeader) throws IOException { + long oldIndex = reader.getPointerIndex(); - int tmpIndex = getPointerToSections(); - if (numberOfSections < 0) { - Msg.error(this, "Number of sections = "+numberOfSections); - } else if (optHeader.getFileAlignment() == 0) { - Msg.error(this, "File alignment == 0: section processing skipped"); - } else { + int tmpIndex = getPointerToSections(); + if (numberOfSections < 0) { + Msg.error(this, "Number of sections = " + numberOfSections); + } + else if (optHeader.getFileAlignment() == 0) { + Msg.error(this, "File alignment == 0: section processing skipped"); + } + else { sectionHeaders = new SectionHeader[numberOfSections]; for (int i = 0; i < numberOfSections; ++i) { sectionHeaders[i] = SectionHeader.createSectionHeader(reader, tmpIndex); @@ -330,8 +340,8 @@ public class FileHeader implements StructConverter { optHeader.getSectionAlignment()); if (virtualAddress == alignedVirtualAddress) { if (sizeOfRawData > virtualSize) { - sectionHeaders[i].setVirtualSize( - Math.min(sizeOfRawData, alignedVirtualSize)); + sectionHeaders[i] + .setVirtualSize(Math.min(sizeOfRawData, alignedVirtualSize)); } } else { @@ -341,68 +351,101 @@ public class FileHeader implements StructConverter { } } - reader.setPointerIndex(oldIndex); - } + reader.setPointerIndex(oldIndex); + } - void processSymbols() throws IOException { - if (isLordPE()) { - return; - } + void processImageRuntimeFunctionEntries() throws IOException { + FileHeader fh = ntHeader.getFileHeader(); + SectionHeader[] sections = fh.getSectionHeaders(); - long oldIndex = reader.getPointerIndex(); + // Look for an exception handler section for an array of + // RUNTIME_FUNCTION structures, bail if one isn't found + SectionHeader irfeHeader = null; + for (SectionHeader header : sections) { + if (header.getName().equals(".pdata")) { + irfeHeader = header; + break; + } + } - int tmpIndex = getPointerToSymbolTable(); - if (!ntHeader.checkRVA(tmpIndex)) { - Msg.error(this, "Invalid file index "+Integer.toHexString(tmpIndex)); - return; - } + if (irfeHeader == null) { + return; + } - if ( numberOfSymbols < 0 || numberOfSymbols > reader.length()) { - Msg.error(this, "Invalid symbol count "+Integer.toHexString(numberOfSymbols)); - return; - } + long oldIndex = reader.getPointerIndex(); - int stringTableIndex = tmpIndex + DebugCOFFSymbol.IMAGE_SIZEOF_SYMBOL * numberOfSymbols; - - for (int i = 0; i < numberOfSymbols; ++i) { - if (!ntHeader.checkRVA(tmpIndex)) { - Msg.error(this, "Invalid file index "+Integer.toHexString(tmpIndex)); - break; - } + int start = irfeHeader.getPointerToRawData(); + reader.setPointerIndex(start); - DebugCOFFSymbol symbol = DebugCOFFSymbol.createDebugCOFFSymbol(reader, tmpIndex, stringTableIndex); + ImageRuntimeFunctionEntries entries = + ImageRuntimeFunctionEntries.createImageRuntimeFunctionEntries(reader, start, ntHeader); + irfes = entries.getRuntimeFunctionEntries(); - tmpIndex += DebugCOFFSymbol.IMAGE_SIZEOF_SYMBOL; + reader.setPointerIndex(oldIndex); + } - tmpIndex += (DebugCOFFSymbolAux.IMAGE_SIZEOF_AUX_SYMBOL * symbol.getNumberOfAuxSymbols()); + void processSymbols() throws IOException { + if (isLordPE()) { + return; + } - int numberOfAuxSymbols = symbol.getNumberOfAuxSymbols(); + long oldIndex = reader.getPointerIndex(); + + int tmpIndex = getPointerToSymbolTable(); + if (!ntHeader.checkRVA(tmpIndex)) { + Msg.error(this, "Invalid file index " + Integer.toHexString(tmpIndex)); + return; + } + + if (numberOfSymbols < 0 || numberOfSymbols > reader.length()) { + Msg.error(this, "Invalid symbol count " + Integer.toHexString(numberOfSymbols)); + return; + } + + int stringTableIndex = tmpIndex + DebugCOFFSymbol.IMAGE_SIZEOF_SYMBOL * numberOfSymbols; + + for (int i = 0; i < numberOfSymbols; ++i) { + if (!ntHeader.checkRVA(tmpIndex)) { + Msg.error(this, "Invalid file index " + Integer.toHexString(tmpIndex)); + break; + } + + DebugCOFFSymbol symbol = + DebugCOFFSymbol.createDebugCOFFSymbol(reader, tmpIndex, stringTableIndex); + + tmpIndex += DebugCOFFSymbol.IMAGE_SIZEOF_SYMBOL; + + tmpIndex += + (DebugCOFFSymbolAux.IMAGE_SIZEOF_AUX_SYMBOL * symbol.getNumberOfAuxSymbols()); + + int numberOfAuxSymbols = symbol.getNumberOfAuxSymbols(); i += numberOfAuxSymbols > 0 ? numberOfAuxSymbols : 0; - symbols.add( symbol ); - } + symbols.add(symbol); + } - reader.setPointerIndex(oldIndex); - } + reader.setPointerIndex(oldIndex); + } - public boolean isLordPE() { - if (getPointerToSymbolTable() == 0x726F4C5B && getNumberOfSymbols() == 0x5D455064) { - return true; - } - return false; - } + public boolean isLordPE() { + if (getPointerToSymbolTable() == LORDPE_SYMBOL_TABLE && + getNumberOfSymbols() == LORDPE_NUMBER_OF_SYMBOLS) { + return true; + } + return false; + } - private void parse() throws IOException { - reader.setPointerIndex(startIndex); + private void parse() throws IOException { + reader.setPointerIndex(startIndex); - machine = reader.readNextShort(); - numberOfSections = reader.readNextShort(); - timeDateStamp = reader.readNextInt (); - pointerToSymbolTable = reader.readNextInt (); - numberOfSymbols = reader.readNextInt (); - sizeOfOptionalHeader = reader.readNextShort(); - characteristics = reader.readNextShort(); - } + machine = reader.readNextShort(); + numberOfSections = reader.readNextShort(); + timeDateStamp = reader.readNextInt(); + pointerToSymbolTable = reader.readNextInt(); + numberOfSymbols = reader.readNextInt(); + sizeOfOptionalHeader = reader.readNextShort(); + characteristics = reader.readNextShort(); + } /** * @see ghidra.app.util.bin.StructConverter#toDataType() @@ -411,22 +454,22 @@ public class FileHeader implements StructConverter { public DataType toDataType() throws DuplicateNameException { StructureDataType struct = new StructureDataType(NAME, 0); - struct.add(WORD,2,"Machine",getMachineName()); - struct.add(WORD,2,"NumberOfSections",null); - struct.add(DWORD,4,"TimeDateStamp",null); - struct.add(DWORD,4,"PointerToSymbolTable",null); - struct.add(DWORD,4,"NumberOfSymbols",null); - struct.add(WORD,2,"SizeOfOptionalHeader",null); - struct.add(WORD,2,"Characteristics",null); + struct.add(WORD, 2, "Machine", getMachineName()); + struct.add(WORD, 2, "NumberOfSections", null); + struct.add(DWORD, 4, "TimeDateStamp", null); + struct.add(DWORD, 4, "PointerToSymbolTable", null); + struct.add(DWORD, 4, "NumberOfSymbols", null); + struct.add(WORD, 2, "SizeOfOptionalHeader", null); + struct.add(WORD, 2, "Characteristics", null); struct.setCategoryPath(new CategoryPath("/PE")); return struct; } - private void setSectionHeaders(SectionHeader [] sectionHeaders) { + private void setSectionHeaders(SectionHeader[] sectionHeaders) { this.sectionHeaders = sectionHeaders; - numberOfSections = (short)sectionHeaders.length; + numberOfSections = (short) sectionHeaders.length; } void writeHeader(RandomAccessFile raf, DataConverter dc) throws IOException { @@ -436,7 +479,7 @@ public class FileHeader implements StructConverter { raf.write(dc.getBytes(pointerToSymbolTable)); raf.write(dc.getBytes(numberOfSymbols)); raf.write(dc.getBytes(sizeOfOptionalHeader)); - raf.write(dc.getBytes(characteristics)); + raf.write(dc.getBytes(characteristics)); } /** @@ -449,61 +492,61 @@ public class FileHeader implements StructConverter { * @throws RuntimeException if the memory block is uninitialized */ public void addSection(MemoryBlock block, OptionalHeader optionalHeader) { - DataDirectory [] directories = optionalHeader.getDataDirectories(); - - DataDirectory [] dataDirectories = optionalHeader.getDataDirectories(); + DataDirectory[] directories = optionalHeader.getDataDirectories(); + DataDirectory[] dataDirectories = optionalHeader.getDataDirectories(); SecurityDataDirectory sdd = null; if (dataDirectories.length > OptionalHeader.IMAGE_DIRECTORY_ENTRY_SECURITY) { - sdd = (SecurityDataDirectory)dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_SECURITY]; + sdd = + (SecurityDataDirectory) dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_SECURITY]; if (sdd != null && sdd.getSize() > 0) { - sdd.updatePointers( PortableExecutable.computeAlignment( (int)block.getSize( ), optionalHeader.getFileAlignment( ) ) ); + sdd.updatePointers(PortableExecutable.computeAlignment((int) block.getSize(), + optionalHeader.getFileAlignment())); } } - - int lastPos = computeAlignedNewPosition( optionalHeader, directories ); + int lastPos = computeAlignedNewPosition(optionalHeader, directories); SectionHeader newSection = new SectionHeader(block, optionalHeader, lastPos); - SectionHeader [] newSectionHeaders = new SectionHeader[sectionHeaders.length + 1]; - System.arraycopy(sectionHeaders, 0, newSectionHeaders, 0, sectionHeaders.length); + SectionHeader[] newSectionHeaders = new SectionHeader[sectionHeaders.length + 1]; + System.arraycopy(sectionHeaders, 0, newSectionHeaders, 0, sectionHeaders.length); newSectionHeaders[sectionHeaders.length] = newSection; setSectionHeaders(newSectionHeaders); int firstSectionStart = sectionHeaders[0].getPointerToRawData(); - int lastSectionEnd = sectionHeaders[sectionHeaders.length-1].getPointerToRawData() - +sectionHeaders[sectionHeaders.length-1].getSizeOfRawData(); + int lastSectionEnd = sectionHeaders[sectionHeaders.length - 1].getPointerToRawData() + + sectionHeaders[sectionHeaders.length - 1].getSizeOfRawData(); - for (int i = 0 ; i < directories.length ; i++) { - if (directories[i] == null || - directories[i].getSize() == 0 || + for (int i = 0; i < directories.length; i++) { + if (directories[i] == null || directories[i].getSize() == 0 || directories[i].isContainedInSection()) { continue; } if (directories[i].getVirtualAddress() < firstSectionStart) { if (i != OptionalHeader.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT) { - throw new RuntimeException("PE - Unexpected directory before sections: "+i); + throw new RuntimeException("PE - Unexpected directory before sections: " + i); } } if (directories[i].getVirtualAddress() > lastSectionEnd) { if (i != OptionalHeader.IMAGE_DIRECTORY_ENTRY_SECURITY) { - throw new RuntimeException("PE - Unexpected directory after sections: "+i); + throw new RuntimeException("PE - Unexpected directory after sections: " + i); } } } int offset = 0; - if (dataDirectories.length > OptionalHeader.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT) { - BoundImportDataDirectory bidd = (BoundImportDataDirectory)dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]; + BoundImportDataDirectory bidd = + (BoundImportDataDirectory) dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]; if (bidd != null && bidd.getSize() > 0) { bidd.updatePointers(SectionHeader.IMAGE_SIZEOF_SECTION_HEADER); int endptr = bidd.getVirtualAddress() + bidd.getSize() - 1; if (endptr >= sectionHeaders[0].getPointerToRawData()) { - int alignedPtr = PortableExecutable.computeAlignment(endptr, optionalHeader.getFileAlignment()); + int alignedPtr = PortableExecutable.computeAlignment(endptr, + optionalHeader.getFileAlignment()); offset = alignedPtr - sectionHeaders[0].getPointerToRawData(); for (SectionHeader sectionHeader : sectionHeaders) { sectionHeader.updatePointers(offset); @@ -514,9 +557,9 @@ public class FileHeader implements StructConverter { } } - if (dataDirectories.length > OptionalHeader.IMAGE_DIRECTORY_ENTRY_DEBUG) { - DebugDataDirectory ddd = (DebugDataDirectory)dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_DEBUG]; + DebugDataDirectory ddd = + (DebugDataDirectory) dataDirectories[OptionalHeader.IMAGE_DIRECTORY_ENTRY_DEBUG]; if (ddd != null && ddd.getSize() > 0) { if (ddd.getVirtualAddress() > newSection.getVirtualAddress()) { if (sdd != null && sdd.getSize() > 0) { @@ -530,12 +573,12 @@ public class FileHeader implements StructConverter { } if (block.isExecute()) { - optionalHeader.setSizeOfCode(optionalHeader.getSizeOfCode() + - newSection.getSizeOfRawData()); + optionalHeader + .setSizeOfCode(optionalHeader.getSizeOfCode() + newSection.getSizeOfRawData()); } else { - optionalHeader.setSizeOfInitializedData(optionalHeader.getSizeOfInitializedData() + - newSection.getSizeOfRawData()); + optionalHeader.setSizeOfInitializedData( + optionalHeader.getSizeOfInitializedData() + newSection.getSizeOfRawData()); } int soi = newSection.getVirtualAddress() + newSection.getSizeOfRawData(); @@ -543,7 +586,8 @@ public class FileHeader implements StructConverter { optionalHeader.setSizeOfImage(soi); } - private int computeAlignedNewPosition( OptionalHeader optionalHeader, DataDirectory [] directories ) { + private int computeAlignedNewPosition(OptionalHeader optionalHeader, + DataDirectory[] directories) { int lastPos = 0; for (SectionHeader sectionHeader : sectionHeaders) { if (sectionHeader.getPointerToRawData() + sectionHeader.getSizeOfRawData() > lastPos) { @@ -551,14 +595,13 @@ public class FileHeader implements StructConverter { } } for (DataDirectory directorie : directories) { - if (directorie == null || - directorie.getSize() == 0) { + if (directorie == null || directorie.getSize() == 0) { continue; } if (directorie.rvaToPointer() + directorie.getSize() > lastPos) { lastPos = directorie.rvaToPointer() + directorie.getSize(); } } - return PortableExecutable.computeAlignment( lastPos, optionalHeader.getFileAlignment( ) ); + return PortableExecutable.computeAlignment(lastPos, optionalHeader.getFileAlignment()); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ImageRuntimeFunctionEntries.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ImageRuntimeFunctionEntries.java new file mode 100644 index 0000000000..7a372d9d19 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ImageRuntimeFunctionEntries.java @@ -0,0 +1,486 @@ +/* ### + * 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.util.bin.format.pe; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.StructConverter; +import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + +/** + * typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { + * DWORD BeginAddress; + * DWORD EndAddress; + * union { + * DWORD UnwindInfoAddress; + * DWORD UnwindData; + * } DUMMYUNIONNAME; + * } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION, _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY; + * + * #define UNW_FLAG_NHANDLER 0x0 + * #define UNW_FLAG_EHANDLER 0x1 + * #define UNW_FLAG_UHANDLER 0x2 + * #define UNW_FLAG_CHAININFO 0x4 + * + * typedef struct _UNWIND_INFO { + * UCHAR Version : 3; + * UCHAR Flags : 5; + * UCHAR SizeOfProlog; + * UCHAR CountOfUnwindCodes; + * UCHAR FrameRegister : 4; + * UCHAR FrameOffset : 4; + * UNWIND_CODE UnwindCode[1]; + * + * // + * // The unwind codes are followed by an optional DWORD aligned field that + * // contains the exception handler address or the address of chained unwind + * // information. If an exception handler address is specified, then it is + * // followed by the language specified exception handler data. + * // + * // union { + * // ULONG ExceptionHandler; + * // ULONG FunctionEntry; + * // }; + * // + * // ULONG ExceptionData[]; + * // + * } UNWIND_INFO, *PUNWIND_INFO; + */ +public class ImageRuntimeFunctionEntries { + private final static int UNWIND_INFO_VERSION_BITMASK = 0x07; + private final static int UNWIND_INFO_FLAGS_SHIFT = 0x03; + private final static int UNWIND_INFO_FRAME_REGISTER_MASK = 0x0F; + private final static int UNWIND_INFO_FRAME_OFFSET_SHIFT = 0x04; + private final static int UNWIND_INFO_OPCODE_MASK = 0x0F; + private final static int UNWIND_INFO_OPCODE_INFO_SHIFT = 0x04; + private final static int UNWIND_INFO_SIZE = 0x0C; + + List<_IMAGE_RUNTIME_FUNCTION_ENTRY> functionEntries = new ArrayList<>(); + + static ImageRuntimeFunctionEntries createImageRuntimeFunctionEntries( + FactoryBundledWithBinaryReader reader, long index, NTHeader ntHeader) + throws IOException { + ImageRuntimeFunctionEntries imageRuntimeFunctionEntriesSection = + (ImageRuntimeFunctionEntries) reader.getFactory() + .create(ImageRuntimeFunctionEntries.class); + imageRuntimeFunctionEntriesSection.initImageRuntimeFunctionEntries(reader, index, ntHeader); + return imageRuntimeFunctionEntriesSection; + } + + /** + * DO NOT USE THIS CONSTRUCTOR, USE create*(GenericFactory ...) FACTORY METHODS INSTEAD. + */ + public ImageRuntimeFunctionEntries() { + } + + private void initImageRuntimeFunctionEntries(FactoryBundledWithBinaryReader reader, long index, + NTHeader ntHeader) throws IOException { + + int entryCount = 0; + + // Find the exception handler data section. This is an unbounded array of + // RUNTIME_INFO structures one after another and there's no count field + // to tell us how many there are, so get the maximum number there could be + // based on the size of the section. + FileHeader fh = ntHeader.getFileHeader(); + for (SectionHeader section : fh.getSectionHeaders()) { + if (section.getName().contentEquals(".pdata")) { + entryCount = section.getSizeOfRawData() / UNWIND_INFO_SIZE; + break; + } + } + + if (entryCount == 0) { + return; + } + + long origIndex = reader.getPointerIndex(); + + reader.setPointerIndex(index); + + for (int i = 0; i < entryCount; i++) { + _IMAGE_RUNTIME_FUNCTION_ENTRY entry = new _IMAGE_RUNTIME_FUNCTION_ENTRY(); + entry.beginAddress = reader.readNextUnsignedInt(); + entry.endAddress = reader.readNextUnsignedInt(); + entry.unwindInfoAddressOrData = reader.readNextUnsignedInt(); + + // When the size of the section is bigger than the number of structures + // the structure data fields will all be null, signaling the end of the + // array of structures. Break out here. + if (entry.beginAddress == 0 && entry.endAddress == 0 && + entry.unwindInfoAddressOrData == 0) { + break; + } + + // Read and process the UNWIND_INFO structures the RUNTIME_INFO + // structures point to + entry.unwindInfo = readUnwindInfo(reader, entry.unwindInfoAddressOrData, ntHeader); + + functionEntries.add(entry); + } + + reader.setPointerIndex(origIndex); + } + + private UNWIND_INFO readUnwindInfo(FactoryBundledWithBinaryReader reader, long offset, + NTHeader ntHeader) throws IOException { + long origIndex = reader.getPointerIndex(); + + long pointer = ntHeader.rvaToPointer(offset); + UNWIND_INFO unwindInfo = new UNWIND_INFO(pointer); + + if (pointer < 0) { + return unwindInfo; + } + + reader.setPointerIndex(pointer); + byte splitByte = reader.readNextByte(); + unwindInfo.version = (byte) (splitByte & UNWIND_INFO_VERSION_BITMASK); + unwindInfo.flags = (byte) (splitByte >> UNWIND_INFO_FLAGS_SHIFT); + + unwindInfo.sizeOfProlog = reader.readNextByte(); + unwindInfo.countOfUnwindCodes = reader.readNextByte(); + + splitByte = reader.readNextByte(); + unwindInfo.frameRegister = (byte) (splitByte & UNWIND_INFO_FRAME_REGISTER_MASK); + unwindInfo.frameOffset = (byte) (splitByte >> UNWIND_INFO_FRAME_OFFSET_SHIFT); + + unwindInfo.unwindCodes = new UNWIND_CODE[unwindInfo.countOfUnwindCodes]; + for (int i = 0; i < unwindInfo.countOfUnwindCodes; i++) { + UNWIND_CODE code = new UNWIND_CODE(); + code.offsetInProlog = reader.readNextByte(); + + int opCodeData = reader.readNextUnsignedByte(); + code.opCode = UNWIND_CODE_OPCODE.fromInt((opCodeData & UNWIND_INFO_OPCODE_MASK)); + code.opInfoRegister = + UNWIND_CODE_OPINFO_REGISTER.fromInt(opCodeData >> UNWIND_INFO_OPCODE_INFO_SHIFT); + + unwindInfo.unwindCodes[i] = code; + } + + // You can have an exception handler and/or an unwind handler, or you + // can have chained exception handling info only. + if (unwindInfo.hasExceptionHandler() || unwindInfo.hasUnwindHandler()) { + if (unwindInfo.hasExceptionHandler()) { + unwindInfo.exceptionHandlerFunction = reader.readNextInt(); + } + if (unwindInfo.hasUnwindHandler()) { + unwindInfo.unwindHandlerFunction = reader.readNextInt(); + } + } + else if (unwindInfo.hasChainedUnwindInfo()) { + unwindInfo.unwindHandlerChainInfo = new _IMAGE_RUNTIME_FUNCTION_ENTRY(); + unwindInfo.unwindHandlerChainInfo.beginAddress = reader.readNextInt(); + unwindInfo.unwindHandlerChainInfo.endAddress = reader.readNextInt(); + unwindInfo.unwindHandlerChainInfo.unwindInfoAddressOrData = reader.readNextInt(); + + // Follow the chain to the referenced UNWIND_INFO structure until we + // get to the end + unwindInfo.unwindHandlerChainInfo.unwindInfo = readUnwindInfo(reader, + unwindInfo.unwindHandlerChainInfo.unwindInfoAddressOrData, ntHeader); + } + + reader.setPointerIndex(origIndex); + + return unwindInfo; + } + + public List<_IMAGE_RUNTIME_FUNCTION_ENTRY> getRuntimeFunctionEntries() { + return functionEntries; + } + + public class _IMAGE_RUNTIME_FUNCTION_ENTRY { + public long beginAddress; + public long endAddress; + public long unwindInfoAddressOrData; + public UNWIND_INFO unwindInfo; + } + + public enum UNWIND_CODE_OPCODE { + UWOP_PUSH_NONVOL(0x00), + UWOP_ALLOC_LARGE(0x01), + UWOP_ALLOC_SMALL(0x02), + UWOP_SET_FPREG(0x03), + UWOP_SAVE_NONVOL(0x04), + UWOP_SAVE_NONVOL_FAR(0x05), + UWOP_SAVE_XMM(0x06), + UWOP_SAVE_XMM_FAR(0x07), + UWOP_SAVE_XMM128(0x08), + UWOP_SAVE_XMM128_FAR(0x09), + UWOP_PUSH_MACHFRAME(0x0A); + + private final int id; + + UNWIND_CODE_OPCODE(int value) { + id = value; + } + + public int id() { + return id; + } + + public static UNWIND_CODE_OPCODE fromInt(int id) { + UNWIND_CODE_OPCODE[] values = UNWIND_CODE_OPCODE.values(); + for (UNWIND_CODE_OPCODE value : values) { + if (value.id == id) { + return value; + } + } + return null; + } + } + + public enum UNWIND_CODE_OPINFO_REGISTER { + UNWIND_OPINFO_REGISTER_RAX(0x00), + UNWIND_OPINFO_REGISTER_RCX(0x01), + UNWIND_OPINFO_REGISTER_RDX(0x02), + UNWIND_OPINFO_REGISTER_RBX(0x03), + UNWIND_OPINFO_REGISTER_RSP(0x04), + UNWIND_OPINFO_REGISTER_RBP(0x05), + UNWIND_OPINFO_REGISTER_RSI(0x06), + UNWIND_OPINFO_REGISTER_RDI(0x07), + UNWIND_OPINFO_REGISTER_R8(0x08), + UNWIND_OPINFO_REGISTER_R9(0x09), + UNWIND_OPINFO_REGISTER_R10(0x0A), + UNWIND_OPINFO_REGISTER_R11(0x0B), + UNWIND_OPINFO_REGISTER_R12(0x0C), + UNWIND_OPINFO_REGISTER_R13(0x0D), + UNWIND_OPINFO_REGISTER_R14(0x0E), + UNWIND_OPINFO_REGISTER_R15(0x0F); + + private final int id; + + UNWIND_CODE_OPINFO_REGISTER(int value) { + id = value; + } + + public int id() { + return id; + } + + public static UNWIND_CODE_OPINFO_REGISTER fromInt(int id) { + UNWIND_CODE_OPINFO_REGISTER[] values = UNWIND_CODE_OPINFO_REGISTER.values(); + for (UNWIND_CODE_OPINFO_REGISTER value : values) { + if (value.id == id) { + return value; + } + } + return null; + } + } + + public class UNWIND_CODE { + public byte offsetInProlog; + public UNWIND_CODE_OPCODE opCode; + public UNWIND_CODE_OPINFO_REGISTER opInfoRegister; + } + + public class UNWIND_INFO implements StructConverter { + private static final String NAME = "UNWIND_INFO"; + + private final static int UNW_FLAG_NHANDLER = 0x0; + private final static int UNW_FLAG_EHANDLER = 0x1; + private final static int UNW_FLAG_UHANDLER = 0x2; + private final static int UNW_FLAG_CHAININFO = 0x4; + + private final static int UNWIND_VERSION_FIELD_LENGTH = 0x03; + private final static int UNWIND_FLAGS_FIELD_LENGTH = 0x05; + private final static int UNWIND_FRAME_REGISTER_LENGTH = 0x04; + private final static int UNWIND_OP_FIELD_LENGTH = 0x04; + + byte version; + byte flags; + byte sizeOfProlog; + byte countOfUnwindCodes; + byte frameRegister; + byte frameOffset; + UNWIND_CODE[] unwindCodes; + int exceptionHandlerFunction; + int unwindHandlerFunction; + _IMAGE_RUNTIME_FUNCTION_ENTRY unwindHandlerChainInfo; + + long startOffset; + + public UNWIND_INFO(long offset) { + startOffset = offset; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + StructureDataType struct = new StructureDataType(NAME + "_" + startOffset, 0); + try { + StructureDataType vf = new StructureDataType("VersionFlags", 0); + vf.insertBitField(0, 1, 0, BYTE, UNWIND_VERSION_FIELD_LENGTH, "Version", null); + vf.insertBitField(0, 1, UNWIND_VERSION_FIELD_LENGTH, defineFlagsField(), + UNWIND_FLAGS_FIELD_LENGTH, "Flags", null); + + struct.add(vf, "Version + Flags", null); + } + catch (InvalidDataTypeException e) { + struct.add(BYTE, "Version + Flags", null); + } + + struct.add(BYTE, "SizeOfProlog", null); + struct.add(BYTE, "CountOfUnwindCodes", null); + + try { + StructureDataType fr = new StructureDataType("FrameRegisterAndOffset", 0); + fr.insertBitField(0, 1, 0, BYTE, UNWIND_FRAME_REGISTER_LENGTH, "FrameRegister", + null); + fr.insertBitField(0, 1, UNWIND_FRAME_REGISTER_LENGTH, BYTE, + UNWIND_FRAME_REGISTER_LENGTH, "FrameOffset", null); + struct.add(fr, "FrameRegister + FrameOffset", null); + } + catch (InvalidDataTypeException e) { + struct.add(BYTE, "FrameRegister + FrameOffset", null); + } + + for (int i = 0; i < countOfUnwindCodes; i++) { + StructureDataType unwindCode = new StructureDataType("UnwindCode", 0); + unwindCode.add(BYTE, "OffsetInProlog", null); + + StructureDataType unwindCodeInfo = new StructureDataType("UnwindCodeInfo", 0); + try { + if (unwindCodes[i].opCode != null) { + unwindCodeInfo.insertBitField(0, 1, 0, defineUnwindOpCodeField(), + UNWIND_OP_FIELD_LENGTH, "UnwindOpCode", null); + } + else { + unwindCodeInfo.insertBitField(0, 1, 0, BYTE, UNWIND_OP_FIELD_LENGTH, + "UnwindOpCode", null); + } + + if (unwindCodes[i].opInfoRegister != null) { + unwindCodeInfo.insertBitField(0, 1, UNWIND_OP_FIELD_LENGTH, + defineUnwindCodeRegisterField(), UNWIND_OP_FIELD_LENGTH, "OpInfo", + null); + } + else { + unwindCodeInfo.insertBitField(0, 1, UNWIND_OP_FIELD_LENGTH, BYTE, + UNWIND_OP_FIELD_LENGTH, "OpInfo", null); + } + } + catch (InvalidDataTypeException e) { + } + unwindCode.add(unwindCodeInfo, "UnwindCodeInfo", null); + + struct.add(unwindCode, "UnwindCode", null); + } + + if (hasExceptionHandler() || hasUnwindHandler()) { + if (hasExceptionHandler()) { + struct.add(IBO32, "ExceptionHandler", null); + } + if (hasUnwindHandler()) { + struct.add(IBO32, "UnwindHandler", null); + } + } + else { + if (hasChainedUnwindInfo()) { + struct.add(IBO32, "FunctionStartAddress", null); + struct.add(IBO32, "FunctionEndAddress", null); + struct.add(IBO32, "FunctionUnwindInfoAddress", null); + } + } + + return struct; + } + + public boolean hasExceptionHandler() { + return (flags & UNW_FLAG_EHANDLER) == UNW_FLAG_EHANDLER; + } + + public boolean hasUnwindHandler() { + return (flags & UNW_FLAG_UHANDLER) == UNW_FLAG_UHANDLER; + } + + public boolean hasChainedUnwindInfo() { + return (flags & UNW_FLAG_CHAININFO) == UNW_FLAG_CHAININFO; + } + + private EnumDataType defineFlagsField() { + EnumDataType flagsField = new EnumDataType("Flags", 5); + flagsField.add("UNW_FLAG_NHANDLER", UNW_FLAG_NHANDLER); + flagsField.add("UNW_FLAG_EHANDLER", UNW_FLAG_EHANDLER); + flagsField.add("UNW_FLAG_UHANDLER", UNW_FLAG_UHANDLER); + flagsField.add("UNW_FLAG_CHAININFO", UNW_FLAG_CHAININFO); + + return flagsField; + } + + private EnumDataType defineUnwindOpCodeField() { + EnumDataType unwindOpCodeField = new EnumDataType("UNWIND_CODE_OPCODE", 4); + unwindOpCodeField.add("UWOP_PUSH_NONVOL", UNWIND_CODE_OPCODE.UWOP_PUSH_NONVOL.id); + unwindOpCodeField.add("UWOP_ALLOC_LARGE", UNWIND_CODE_OPCODE.UWOP_ALLOC_LARGE.id); + unwindOpCodeField.add("UWOP_ALLOC_SMALL", UNWIND_CODE_OPCODE.UWOP_ALLOC_SMALL.id); + unwindOpCodeField.add("UWOP_SET_FPREG", UNWIND_CODE_OPCODE.UWOP_SET_FPREG.id); + unwindOpCodeField.add("UWOP_SAVE_NONVOL", UNWIND_CODE_OPCODE.UWOP_SAVE_NONVOL.id); + unwindOpCodeField.add("UWOP_SAVE_NONVOL_FAR", + UNWIND_CODE_OPCODE.UWOP_SAVE_NONVOL_FAR.id); + unwindOpCodeField.add("UWOP_SAVE_XMM", UNWIND_CODE_OPCODE.UWOP_SAVE_XMM.id); + unwindOpCodeField.add("UWOP_SAVE_XMM_FAR", UNWIND_CODE_OPCODE.UWOP_SAVE_XMM_FAR.id); + unwindOpCodeField.add("UWOP_SAVE_XMM128", UNWIND_CODE_OPCODE.UWOP_SAVE_XMM128.id); + unwindOpCodeField.add("UWOP_SAVE_XMM128_FAR", + UNWIND_CODE_OPCODE.UWOP_SAVE_XMM128_FAR.id); + unwindOpCodeField.add("UWOP_PUSH_MACHFRAME", UNWIND_CODE_OPCODE.UWOP_PUSH_MACHFRAME.id); + + return unwindOpCodeField; + } + + private EnumDataType defineUnwindCodeRegisterField() { + EnumDataType unwindCodeRegisterField = + new EnumDataType("UNWIND_CODE_OPINFO_REGISTER", 4); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RAX", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RAX.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RCX", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RCX.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RDX", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RDX.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RBX", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RBX.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RSP", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RSP.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RBP", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RBP.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RSI", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RSI.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_RDI", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_RDI.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R8", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R8.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R9", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R9.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R10", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R10.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R11", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R11.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R12", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R12.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R13", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R13.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R14", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R14.id); + unwindCodeRegisterField.add("UNWIND_OPINFO_REGISTER_R15", + UNWIND_CODE_OPINFO_REGISTER.UNWIND_OPINFO_REGISTER_R15.id); + + return unwindCodeRegisterField; + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/NTHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/NTHeader.java index 0912d75484..e88d1fe976 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/NTHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/NTHeader.java @@ -30,7 +30,7 @@ import ghidra.util.task.TaskMonitorAdapter; /** * A class to represent the IMAGE_NT_HEADERS32 and - * IMAGE_NT_HEADERS64 structs as defined in + * IMAGE_NT_HEADERS64 structs as defined in * winnt.h. *
      * typedef struct _IMAGE_NT_HEADERS {
    @@ -39,8 +39,8 @@ import ghidra.util.task.TaskMonitorAdapter;
      *    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
      * };
      * 
    - * - * + * + * */ public class NTHeader implements StructConverter, OffsetValidator { /** @@ -82,8 +82,8 @@ public class NTHeader implements StructConverter, OffsetValidator { public NTHeader() { } - private void initNTHeader(FactoryBundledWithBinaryReader reader, int index, SectionLayout layout, - boolean advancedProcess, boolean parseCliHeaders) + private void initNTHeader(FactoryBundledWithBinaryReader reader, int index, + SectionLayout layout, boolean advancedProcess, boolean parseCliHeaders) throws InvalidNTHeaderException, IOException { this.reader = reader; this.index = index; @@ -172,9 +172,9 @@ public class NTHeader implements StructConverter, OffsetValidator { //low alignment mode? // if (optionalHeader != null) { - if (optionalHeader.getFileAlignment() == optionalHeader.getSectionAlignment() - && optionalHeader.getSectionAlignment() < 800 - && optionalHeader.getFileAlignment() > 1) { + if (optionalHeader.getFileAlignment() == optionalHeader.getSectionAlignment() && + optionalHeader.getSectionAlignment() < 800 && + optionalHeader.getFileAlignment() > 1) { return rva; } } @@ -270,6 +270,7 @@ public class NTHeader implements StructConverter, OffsetValidator { fileHeader.processSections(optionalHeader); fileHeader.processSymbols(); + fileHeader.processImageRuntimeFunctionEntries(); if (advancedProcess) { optionalHeader.processDataDirectories(TaskMonitorAdapter.DUMMY_MONITOR); @@ -278,7 +279,7 @@ public class NTHeader implements StructConverter, OffsetValidator { void writeHeader(RandomAccessFile raf, DataConverter dc) throws IOException { - raf.seek( index ); + raf.seek(index); raf.write(dc.getBytes(signature)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java index f7f1141135..ad4317876c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java @@ -28,6 +28,7 @@ import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.format.mz.DOSHeader; import ghidra.app.util.bin.format.pe.*; import ghidra.app.util.bin.format.pe.ImageCor20Header.ImageCor20Flags; +import ghidra.app.util.bin.format.pe.ImageRuntimeFunctionEntries._IMAGE_RUNTIME_FUNCTION_ENTRY; import ghidra.app.util.bin.format.pe.PortableExecutable.SectionLayout; import ghidra.app.util.bin.format.pe.debug.DebugCOFFSymbol; import ghidra.app.util.bin.format.pe.debug.DebugDirectoryParser; @@ -148,6 +149,7 @@ public class PeLoader extends AbstractPeDebugLoader { processProperties(optionalHeader, program, monitor); processComments(program.getListing(), monitor); processSymbols(fileHeader, sectionToAddress, program, monitor, log); + processImageRuntimeFunctionEntries(fileHeader, program, monitor, log); processEntryPoints(ntHeader, program, monitor); String compiler = CompilerOpinion.getOpinion(pe, provider).toString(); @@ -248,24 +250,74 @@ public class PeLoader extends AbstractPeDebugLoader { setComment(CodeUnit.EOL_COMMENT, start, section.getName()); start = start.add(dt.getLength()); } - -// for (int i = 0; i < datadirs.length; ++i) { -// if (datadirs[i] == null || datadirs[i].getSize() == 0) { -// continue; -// } -// -// if (datadirs[i].hasParsedCorrectly()) { -// start = datadirs[i].getMarkupAddress(program, true); -// dt = datadirs[i].toDataType(); -// DataUtilities.createData(program, start, dt, true, DataUtilities.ClearDataMode.CHECK_FOR_SPACE); -// } -// } } catch (Exception e1) { Msg.error(this, "Error laying down header structures " + e1); } } + private void processImageRuntimeFunctionEntries(FileHeader fileHeader, Program program, + TaskMonitor monitor, MessageLog log) { + + // Check to see that we have exception data to process + SectionHeader irfeHeader = null; + for (SectionHeader header : fileHeader.getSectionHeaders()) { + if (header.getName().contains(".pdata")) { + irfeHeader = header; + break; + } + } + + if (irfeHeader == null) { + return; + } + + Address start = program.getImageBase().add(irfeHeader.getVirtualAddress()); + + List<_IMAGE_RUNTIME_FUNCTION_ENTRY> irfes = fileHeader.getImageRuntimeFunctionEntries(); + if (irfes == null) { + return; + } + + StructureDataType dt = new StructureDataType(".PDATA", 0); + dt.setCategoryPath(new CategoryPath("/PE")); + + // Lay an array of RUNTIME_INFO structure out over the data + StructureDataType irfeStruct = new StructureDataType("_IMAGE_RUNTIME_FUNCTION_ENTRY", 0); + irfeStruct.add(ghidra.app.util.bin.StructConverter.IBO32, "BeginAddress", null); + irfeStruct.add(ghidra.app.util.bin.StructConverter.IBO32, "EndAddress", null); + irfeStruct.add(ghidra.app.util.bin.StructConverter.IBO32, "UnwindInfoAddressOrData", null); + + ArrayDataType irfeArray = + new ArrayDataType(irfeStruct, irfes.size(), irfeStruct.getLength()); + + try { + DataUtilities.createData(program, start, irfeArray, irfeArray.getLength(), true, + DataUtilities.ClearDataMode.CHECK_FOR_SPACE); + } + catch (CodeUnitInsertionException e) { + return; + } + + // Each RUNTIME_INFO contains an address to an UNWIND_INFO structure + // which also needs to be laid out. When they contain chaining data + // they're recursive but the toDataType() function handles that. + for (_IMAGE_RUNTIME_FUNCTION_ENTRY entry : irfes) { + if (entry.unwindInfoAddressOrData > 0) { + try { + dt = (StructureDataType) entry.unwindInfo.toDataType(); + start = program.getImageBase().add(entry.unwindInfoAddressOrData); + + DataUtilities.createData(program, start, dt, dt.getLength(), true, + DataUtilities.ClearDataMode.CHECK_FOR_SPACE); + } + catch (CodeUnitInsertionException | DuplicateNameException | IOException e) { + continue; + } + } + } + } + private void processSymbols(FileHeader fileHeader, Map sectionToAddress, Program program, TaskMonitor monitor, MessageLog log) { List symbols = fileHeader.getSymbols(); @@ -493,7 +545,7 @@ public class PeLoader extends AbstractPeDebugLoader { break; } - // Get address of current position in the import address table + // Get address of current position in the import address table Address iatAddr = iatBaseAddr.add(offset); Data iatData = listing.getDataAt(iatAddr); if (iatData == null || !(iatData.getValue() instanceof Address)) { @@ -506,8 +558,7 @@ public class PeLoader extends AbstractPeDebugLoader { importInfo.getName(), null, SourceType.IMPORTED, 0, RefType.DATA); } catch (DuplicateNameException | InvalidInputException e) { - log.appendMsg( - "Failed to create Delay Load external function at: " + iatAddr); + log.appendMsg("Failed to create Delay Load external function at: " + iatAddr); } // Create delay load proxy function diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java index b2711c6493..1ada2cecae 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java @@ -56,7 +56,7 @@ import ghidra.util.timer.GTimer; * * Refactor fileInfo -> needs dialog to show properties * Refactor GFile.getInfo() to return Map<> instead of String. - * Persistant filesystem - when reopen tool, filesystems should auto-reopen. + * Persistent filesystem - when reopen tool, filesystems should auto-reopen. * Unify GhidraFileChooser with GFileSystem. * Add "Mounted Filesystems" button to show currently opened GFilesystems? * Dockable filesystem browser in FrontEnd. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/state/VarnodeOperation.java b/Ghidra/Features/Base/src/main/java/ghidra/util/state/VarnodeOperation.java index 0d098c9bc1..33872f6f17 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/state/VarnodeOperation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/state/VarnodeOperation.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -170,7 +169,7 @@ public class VarnodeOperation extends Varnode { } @Override - public boolean isPersistant() { + public boolean isPersistent() { return false; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java index 9ed0d7ada6..e7adb329df 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java @@ -205,7 +205,33 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe waitForSwing(); waitForTree(); - runSwing(() -> jTree.stopEditing()); + + // verify that the tree opens a new node with the default + // category name is "New Category" + assertEquals(childCount + 1, miscNode.getChildCount()); + GTreeNode node = miscNode.getChild("New Category"); + assertNotNull(node); + } + + @Test + public void testCreateCategory_WhileFiltered() throws Exception { + // select a category + GTreeNode miscNode = programNode.getChild("MISC"); + assertNotNull(miscNode); + expandNode(miscNode); + + int childCount = miscNode.getChildCount(); + selectNode(miscNode); + + filterTree(miscNode.getName()); + + DockingActionIf action = getAction(plugin, "New Category"); + assertTrue(action.isEnabledForContext(treeContext)); + + // select "New Category" action + DataTypeTestUtils.performAction(action, tree, false); + + waitForDialogComponent("Cannot Edit Tree Node"); // verify that the tree opens a new node with the default // category name is "New Category" diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java b/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java index ed2f6f1df1..aa610ac733 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java @@ -31,6 +31,7 @@ import ghidra.program.model.pcode.*; import ghidra.service.graph.*; import ghidra.util.Msg; import java.util.*; +import static ghidra.service.graph.GraphDisplay.*; public class GraphAST extends GhidraScript { protected static final String COLOR_ATTRIBUTE = "Color"; @@ -66,11 +67,12 @@ public class GraphAST extends GhidraScript { buildGraph(); Map properties = new HashMap<>(); - properties.put("selectedVertexColor", "0xFF1493"); - properties.put("selectedEdgeColor", "0xFF1493"); - properties.put("initialLayoutAlgorithm", "Hierarchical MinCross Coffman Graham"); - properties.put("displayVerticesAsIcons", "false"); - properties.put("vertexLabelPosition", "S"); + properties.put(SELECTED_VERTEX_COLOR, "0xFF1493"); + properties.put(SELECTED_EDGE_COLOR, "0xFF1493"); + properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham"); + properties.put(DISPLAY_VERTICES_AS_ICONS, "false"); + properties.put(VERTEX_LABEL_POSITION, "S"); + properties.put(ENABLE_EDGE_SELECTION, "true"); GraphDisplay graphDisplay = graphDisplayBroker.getDefaultGraphDisplay(false, properties, monitor); // graphDisplay.defineVertexAttribute(CODE_ATTRIBUTE); // @@ -137,7 +139,7 @@ public class GraphAST extends GhidraScript { else if (vn.isUnique()) { colorattrib = "Black"; } - else if (vn.isPersistant()) { + else if (vn.isPersistent()) { colorattrib = "DarkOrange"; } else if (vn.isAddrTied()) { diff --git a/Ghidra/Features/Decompiler/src/main/doc/pcoderef.xml b/Ghidra/Features/Decompiler/src/main/doc/pcoderef.xml index 70f219d2f6..4921d8d051 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/pcoderef.xml +++ b/Ghidra/Features/Decompiler/src/main/doc/pcoderef.xml @@ -926,7 +926,7 @@ This is a truncation operator that understands the endianess of the data. Input1 indicates the number of least significant bytes of input0 to be thrown away. Output is then filled with any remaining bytes of input0 up to the size of output. If the size of -output is smaller than the size of input0 plus the constant input1, +output is smaller than the size of input0 minus the constant input1, then the additional most significant bytes of input0 will also be truncated. diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java index ebe02584e0..0a6a4e7db3 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java @@ -34,6 +34,8 @@ import ghidra.util.exception.GraphException; import ghidra.util.task.Task; import ghidra.util.task.TaskMonitor; +import static ghidra.service.graph.GraphDisplay.*; + public class ASTGraphTask extends Task { enum GraphType { CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow"); @@ -104,9 +106,10 @@ public class ASTGraphTask extends Task { createControlFlowGraph(graph, monitor); } Map properties = new HashMap<>(); - properties.put("selectedVertexColor", "0xFF1493"); - properties.put("selectedEdgeColor", "0xFF1493"); - properties.put("initialLayoutAlgorithm", "Hierarchical MinCross Coffman Graham"); + properties.put(SELECTED_VERTEX_COLOR, "0xFF1493"); + properties.put(SELECTED_EDGE_COLOR, "0xFF1493"); + properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham"); + properties.put(ENABLE_EDGE_SELECTION, "true"); GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, properties, monitor); ASTGraphDisplayListener displayListener = new ASTGraphDisplayListener(tool, display, hfunction, graphType); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystem.java index 912f6b3088..ae4d47fdc9 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystem.java @@ -15,9 +15,10 @@ */ package ghidra.file.formats.sevenzip; -import java.io.*; import java.util.*; +import java.io.*; + import org.apache.commons.io.FilenameUtils; import ghidra.formats.gfilesystem.*; @@ -261,6 +262,9 @@ public class SevenZipFileSystem implements GFileSystem { case UNKNOWN_OPERATION_RESULT: { throw new IOException("Unexpected: 7-Zip returned unknown operation result"); } + case WRONG_PASSWORD: { + throw new IOException("7-Zip wrong password"); + } case OK: default: { // it's all ok! diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java index 3417362362..6b64a95ae4 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java @@ -239,16 +239,13 @@ public class GnuDemanglerParser { * Pattern: text for|to text * * Parts: - * -required text (capture group 2) - * --note: this uses '++', a possessive quantifier, to help keep the - * backtracking to a minimum - * -a space - * -'for' or 'to' (capture group 3) - * -a space + * -required text (capture group 2) -+ + * -'for' or 'to' (capture group 3) | (capture group 1) + * -a space -+ * -optional text (capture group 4) - * - * Note: capture group 1 is the combination of groups 2 and 3 - * + * + * Note: capture group 1 is the combination of groups 2 and 3 with trailing space + * * Examples: * construction vtable for * vtable for diff --git a/Ghidra/Features/GraphServices/Module.manifest b/Ghidra/Features/GraphServices/Module.manifest index 361687fc49..ad8b9ffc53 100644 --- a/Ghidra/Features/GraphServices/Module.manifest +++ b/Ghidra/Features/GraphServices/Module.manifest @@ -1,7 +1,7 @@ EXCLUDE FROM GHIDRA JAR: true -MODULE FILE LICENSE: lib/jungrapht-visualization-1.1.jar BSD -MODULE FILE LICENSE: lib/jungrapht-layout-1.1.jar BSD +MODULE FILE LICENSE: lib/jungrapht-visualization-1.2.jar BSD +MODULE FILE LICENSE: lib/jungrapht-layout-1.2.jar BSD MODULE FILE LICENSE: lib/jgrapht-core-1.5.0.jar LGPL 2.1 MODULE FILE LICENSE: lib/jgrapht-io-1.5.0.jar LGPL 2.1 MODULE FILE LICENSE: lib/jheaps-0.13.jar Apache License 2.0 diff --git a/Ghidra/Features/GraphServices/build.gradle b/Ghidra/Features/GraphServices/build.gradle index 9d3c632bf9..2c53c410ca 100644 --- a/Ghidra/Features/GraphServices/build.gradle +++ b/Ghidra/Features/GraphServices/build.gradle @@ -12,9 +12,9 @@ dependencies { compile project(":Base") // jungrapht - exclude slf4j which produces a conflict with other uses with Ghidra - compile ("com.github.tomnelson:jungrapht-visualization:1.1") { exclude group: "org.slf4j", module: "slf4j-api" } - compile ("com.github.tomnelson:jungrapht-layout:1.1") { exclude group: "org.slf4j", module: "slf4j-api" } - + compile ("com.github.tomnelson:jungrapht-visualization:1.2") { exclude group: "org.slf4j", module: "slf4j-api" } + compile ("com.github.tomnelson:jungrapht-layout:1.2") { exclude group: "org.slf4j", module: "slf4j-api" } + compile "org.jgrapht:jgrapht-core:1.5.0" // not using jgrapht-io code that depends on antlr, so exclude antlr diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java index d2d345fcc9..de890a5582 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java @@ -103,28 +103,33 @@ public class DefaultGraphDisplay implements GraphDisplay { private static final String ACTION_OWNER = "GraphServices"; - /* - A handful of properties that can be set via the constructor - */ - private static final String SELECTED_VERTEX_COLOR = "selectedVertexColor"; - private static final String SELECTED_EDGE_COLOR = "selectedEdgeColor"; - private static final String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm"; - private static final String DISPLAY_VERTICES_AS_ICONS = "displayVerticesAsIcons"; - private static final String VERTEX_LABEL_POSITION = "vertexLabelPosition"; - private static final int MAX_NODES = Integer.getInteger("maxNodes", 10000); private static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000); private static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000); private Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName()); - private Map displayProperties = new HashMap<>(); + private Map displayProperties; private Set addedActions = new LinkedHashSet<>(); private GraphDisplayListener listener = new DummyGraphDisplayListener(); private String title; private AttributedGraph graph; + private static String DEFAULT_EDGE_TYPE_PRIORITY_LIST = + "Fall-Through,"+ + "Conditional-Return,"+ + "Unconditional-Jump,"+ + "Conditional-Jump,"+ + "Unconditional-Call,"+ + "Conditional-Call,"+ + "Terminator,"+ + "Computed,"+ + "Indirection,"+ + "Entry"; + + private static String DEFAULT_FAVORED_EDGES = "Fall-Through"; + /** * a unique id for this {@link GraphDisplay} */ @@ -219,7 +224,9 @@ public class DefaultGraphDisplay implements GraphDisplay { viewer.getComponent().add(satelliteViewer.getComponent()); } layoutTransitionManager = - new LayoutTransitionManager(viewer, this::isRoot); + new LayoutTransitionManager(viewer, this::isRoot, + getEdgeTypePriorityList(), + getFavoredEdgePredicate()); viewer.getComponent().addComponentListener(new ComponentAdapter() { @Override @@ -252,6 +259,19 @@ public class DefaultGraphDisplay implements GraphDisplay { return Colors.getHexColor(property); } + private List getEdgeTypePriorityList() { + return Arrays.asList(displayProperties + .getOrDefault(EDGE_TYPE_PRIORITY_LIST, DEFAULT_EDGE_TYPE_PRIORITY_LIST) + .split(",")); + } + + private Predicate getFavoredEdgePredicate() { + String[] favoredEdges = displayProperties.getOrDefault(FAVORED_EDGES, DEFAULT_FAVORED_EDGES) + .split(","); + return attributedEdge -> Arrays.stream(favoredEdges) + .anyMatch(s -> s.equals(attributedEdge.getAttribute("EdgeType"))); + } + JComponent getComponent() { JComponent component = viewer.getComponent(); component.setFocusable(true); @@ -486,7 +506,7 @@ public class DefaultGraphDisplay implements GraphDisplay { "Extends the current selection by including the target/source vertices " + "of all edges whose source/target is selected") .keyBinding("ctrl C") - .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) && + .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) || !isAllSelected(getTargetVerticesFromSelected())) .onAction(c -> growSelection(getAllComponentVerticesFromSelected())) .buildAndInstallLocal(componentProvider); @@ -610,6 +630,23 @@ public class DefaultGraphDisplay implements GraphDisplay { private void growSelection(Set vertices) { viewer.getSelectedVertexState().select(vertices); + selectEdgesConnecting(vertices); + } + + // select all the edges that connect the supplied vertices + private void selectEdgesConnecting(Collection vertices) { + viewer.getSelectedEdgeState().select( + graph.edgeSet() + .stream() + .filter( + e -> { + AttributedVertex source = graph.getEdgeSource(e); + AttributedVertex target = graph.getEdgeTarget(e); + return vertices.contains(source) + && vertices.contains(target); + }) + .collect(Collectors.toSet())); + } private boolean isAllSelected(Set vertices) { @@ -617,8 +654,8 @@ public class DefaultGraphDisplay implements GraphDisplay { } private Set getSourceVerticesFromSelected() { - Set sources = new HashSet<>(); Set selectedVertices = getSelectedVertices(); + Set sources = new HashSet<>(selectedVertices); for (AttributedVertex v : selectedVertices) { Set edges = graph.incomingEdgesOf(v); edges.forEach(e -> sources.add(graph.getEdgeSource(e))); @@ -635,8 +672,8 @@ public class DefaultGraphDisplay implements GraphDisplay { } private Set getTargetVerticesFromSelected() { - Set targets = new HashSet<>(); Set selectedVertices = getSelectedVertices(); + Set targets = new HashSet<>(selectedVertices); for (AttributedVertex v : selectedVertices) { Set edges = graph.outgoingEdgesOf(v); edges.forEach(e -> targets.add(graph.getEdgeTarget(e))); @@ -674,9 +711,20 @@ public class DefaultGraphDisplay implements GraphDisplay { return upstream; } + /** + * Gather all source and target vertices until there are no more available. + * @return all the vertices in the component(s) of the selected vertices + */ public Set getAllComponentVerticesFromSelected() { - Set componentVertices = getAllDownstreamVerticesFromSelected(); - componentVertices.addAll(getAllUpstreamVerticesFromSelected()); + Set componentVertices = new HashSet<>(viewer.getSelectedVertices()); + Set downstream = getAllDownstreamVerticesFromSelected(); + Set upstream = getAllUpstreamVerticesFromSelected(); + while (!downstream.isEmpty() || !upstream.isEmpty()) { + componentVertices.addAll(downstream); + componentVertices.addAll(upstream); + downstream = getAllDownstreamVerticesFromSelected(); + upstream = getAllUpstreamVerticesFromSelected(); + } return componentVertices; } @@ -955,13 +1003,14 @@ public class DefaultGraphDisplay implements GraphDisplay { /** * Determines if a vertex is a root. For our purpose, a root either has no incoming edges - * or has at least one outgoing "favored" edge and no incoming "favored" edge + * or if all edges of a vertex are 'loop' edges * @param vertex the vertex to test if it is a root * @return true if the vertex is a root */ private boolean isRoot(AttributedVertex vertex) { Set incomingEdgesOf = graph.incomingEdgesOf(vertex); - return incomingEdgesOf.isEmpty(); + return incomingEdgesOf.isEmpty() || + graph.incomingEdgesOf(vertex).equals(graph.outgoingEdgesOf(vertex)); } /** @@ -1230,12 +1279,6 @@ public class DefaultGraphDisplay implements GraphDisplay { setVertexPreferences(vv); - // the selectedEdgeState will be controlled by the vertices that are selected. - // if both endpoints of an edge are selected, select that edge. - vv.setSelectedEdgeState( - new VertexEndpointsSelectedEdgeSelectedState<>(vv.getVisualizationModel()::getGraph, - vv.getSelectedVertexState())); - // selected edges will be drawn with a wider stroke renderContext.setEdgeStrokeFunction( e -> isSelected(e) ? new BasicStroke(20.f) @@ -1259,7 +1302,6 @@ public class DefaultGraphDisplay implements GraphDisplay { // cause the lightweight (optimized) renderer to use the vertex shapes instead // of using default shapes. - if (vertexRenderer instanceof LightweightVertexRenderer) { Function vertexShapeFunction = renderContext.getVertexShapeFunction(); @@ -1282,7 +1324,9 @@ public class DefaultGraphDisplay implements GraphDisplay { vv.getComponent().removeMouseListener(mouseListener); } - graphMouse = new JgtGraphMouse(this); + graphMouse = new JgtGraphMouse(this, + Boolean.parseBoolean(displayProperties.getOrDefault(ENABLE_EDGE_SELECTION, + "false"))); vv.setGraphMouse(graphMouse); return vv; diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java index c248961f26..00690e8436 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java @@ -15,7 +15,9 @@ */ package ghidra.graph.visualization; +import java.util.Comparator; import java.util.function.Function; +import java.util.function.Predicate; import org.jungrapht.visualization.layout.algorithms.*; import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion; @@ -36,22 +38,38 @@ class LayoutFunction static final String KAMADA_KAWAI = "Force Balanced"; static final String FRUCTERMAN_REINGOLD = "Force Directed"; - static final String CIRCLE_MINCROSS = "Circle"; + static final String CIRCLE = "Circle"; static final String TIDIER_TREE = "Compact Hierarchical"; static final String TIDIER_RADIAL_TREE = "Compact Radial"; static final String MIN_CROSS_TOP_DOWN = "Hierarchical MinCross Top Down"; static final String MIN_CROSS_LONGEST_PATH = "Hierarchical MinCross Longest Path"; static final String MIN_CROSS_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex"; static final String MIN_CROSS_COFFMAN_GRAHAM = "Hierarchical MinCross Coffman Graham"; + static final String EXP_MIN_CROSS_TOP_DOWN = "Experimental Hierarchical MinCross Top Down"; + static final String EXP_MIN_CROSS_LONGEST_PATH = "Experimental Hierarchical MinCross Longest Path"; + static final String EXP_MIN_CROSS_NETWORK_SIMPLEX = "Experimental Hierarchical MinCross Network Simplex"; + static final String EXP_MIN_CROSS_COFFMAN_GRAHAM = "Experimental Hierarchical MinCross Coffman Graham"; static final String TREE = "Hierarchical"; static final String RADIAL = "Radial"; static final String BALLOON = "Balloon"; static final String GEM = "Gem (Graph Embedder)"; + Predicate favoredEdgePredicate; + Comparator edgeTypeComparator; + + LayoutFunction(Comparator edgeTypeComparator, Predicate favoredEdgePredicate) { + this.edgeTypeComparator = edgeTypeComparator; + this.favoredEdgePredicate = favoredEdgePredicate; + } + public String[] getNames() { return new String[] { TIDIER_TREE, TREE, TIDIER_RADIAL_TREE, MIN_CROSS_TOP_DOWN, MIN_CROSS_LONGEST_PATH, - MIN_CROSS_NETWORK_SIMPLEX, MIN_CROSS_COFFMAN_GRAHAM, CIRCLE_MINCROSS, + MIN_CROSS_NETWORK_SIMPLEX, MIN_CROSS_COFFMAN_GRAHAM, CIRCLE, + EXP_MIN_CROSS_TOP_DOWN, + EXP_MIN_CROSS_LONGEST_PATH, + EXP_MIN_CROSS_NETWORK_SIMPLEX, + EXP_MIN_CROSS_COFFMAN_GRAHAM, KAMADA_KAWAI, FRUCTERMAN_REINGOLD, RADIAL, BALLOON, GEM }; } @@ -67,27 +85,56 @@ class LayoutFunction case FRUCTERMAN_REINGOLD: return FRLayoutAlgorithm. builder() .repulsionContractBuilder(BarnesHutFRRepulsion.builder()); - case CIRCLE_MINCROSS: + case CIRCLE: return CircleLayoutAlgorithm. builder() - .reduceEdgeCrossing(true); + .reduceEdgeCrossing(false); case TIDIER_RADIAL_TREE: return TidierRadialTreeLayoutAlgorithm - . edgeAwareBuilder(); + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator); case MIN_CROSS_TOP_DOWN: return EiglspergerLayoutAlgorithm . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) .layering(Layering.TOP_DOWN); case MIN_CROSS_LONGEST_PATH: return EiglspergerLayoutAlgorithm . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) .layering(Layering.LONGEST_PATH); case MIN_CROSS_NETWORK_SIMPLEX: return EiglspergerLayoutAlgorithm . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) .layering(Layering.NETWORK_SIMPLEX); case MIN_CROSS_COFFMAN_GRAHAM: return EiglspergerLayoutAlgorithm . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .layering(Layering.COFFMAN_GRAHAM); + case EXP_MIN_CROSS_TOP_DOWN: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.TOP_DOWN); + case EXP_MIN_CROSS_LONGEST_PATH: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.LONGEST_PATH); + case EXP_MIN_CROSS_NETWORK_SIMPLEX: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.NETWORK_SIMPLEX); + case EXP_MIN_CROSS_COFFMAN_GRAHAM: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) .layering(Layering.COFFMAN_GRAHAM); case RADIAL: return RadialTreeLayoutAlgorithm @@ -103,7 +150,9 @@ class LayoutFunction case TIDIER_TREE: default: return TidierTreeLayoutAlgorithm - . edgeAwareBuilder(); + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator); + } } } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java index c500ba8f51..6849d81829 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java @@ -38,7 +38,7 @@ import ghidra.service.graph.AttributedVertex; */ class LayoutTransitionManager { - LayoutFunction layoutFunction = new LayoutFunction(); + LayoutFunction layoutFunction; /** * the {@link VisualizationServer} used to display graphs using the requested {@link LayoutAlgorithm} */ @@ -49,24 +49,6 @@ class LayoutTransitionManager { */ Predicate rootPredicate; - public static final List EDGE_PRIORITY_LIST = - List.of( - "Fall-Through", - "Conditional-Return", - "Unconditional-Jump", - "Conditional-Jump", - "Unconditional-Call", - "Conditional-Call", - "Terminator", - "Computed", - "Indirection", - "Entry"); - /** - * a {@link Comparator} to sort edges during layout graph traversal - * The default uses the {@code EDGE_PRIORTITY_LIST } - */ - Comparator edgeComparator = new EdgeComparator(EDGE_PRIORITY_LIST); - /** * a {@link Function} to provide {@link Rectangle} (and thus bounds} for vertices */ @@ -81,47 +63,54 @@ class LayoutTransitionManager { LayoutPaintable.RadialRings radialLayoutRings; - /** * Create an instance with passed parameters * @param visualizationServer displays the graph * @param rootPredicate selects root vertices + * @param edgeTypePriorityList a {@code List} of EdgeType names in priority order + * @param favoredEdgePredicate q {@code Predicate} that will cause certain EdgeTypes to be favored during layout */ public LayoutTransitionManager( VisualizationServer visualizationServer, - Predicate rootPredicate) { + Predicate rootPredicate, + List edgeTypePriorityList, + Predicate favoredEdgePredicate) { this.visualizationServer = visualizationServer; this.rootPredicate = rootPredicate; - this.renderContext = visualizationServer.getRenderContext(); this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction(); - } - - public void setEdgeComparator(Comparator edgeComparator) { - this.edgeComparator = edgeComparator; + this.layoutFunction = new LayoutFunction(new EdgeComparator(edgeTypePriorityList), + favoredEdgePredicate); } /** * set the layout in order to configure the requested {@link LayoutAlgorithm} * @param layoutName the name of the layout algorithm to use */ - @SuppressWarnings("unchecked") public void setLayout(String layoutName) { LayoutAlgorithm.Builder builder = layoutFunction.apply(layoutName); LayoutAlgorithm layoutAlgorithm = builder.build(); + // layout algorithm considers the size of vertices if (layoutAlgorithm instanceof VertexBoundsFunctionConsumer) { ((VertexBoundsFunctionConsumer) layoutAlgorithm) .setVertexBoundsFunction(vertexBoundsFunction); } + // mincross layouts are 'layered'. put some bounds on the number of + // iterations of the level cross function based on the size of the graph + // very large graphs do not improve enough to out-weigh the cost of + // repeated iterations if (layoutAlgorithm instanceof Layered) { ((Layered) layoutAlgorithm) .setMaxLevelCrossFunction(g -> Math.max(1, Math.min(10, 500 / g.vertexSet().size()))); } + // tree layouts need a way to determine which vertices are roots + // especially when the graph is not a DAG if (layoutAlgorithm instanceof TreeLayout) { ((TreeLayout) layoutAlgorithm).setRootPredicate(rootPredicate); } // remove any previously added layout paintables + // and apply paintables to these 2 algorithms removePaintable(radialLayoutRings); removePaintable(balloonLayoutRings); if (layoutAlgorithm instanceof BalloonLayoutAlgorithm) { @@ -138,9 +127,7 @@ class LayoutTransitionManager { visualizationServer.addPreRenderPaintable(radialLayoutRings); } - if (layoutAlgorithm instanceof EdgeSorting) { - ((EdgeSorting) layoutAlgorithm).setEdgeComparator(edgeComparator); - } + // apply the layout algorithm LayoutAlgorithmTransition.apply(visualizationServer, layoutAlgorithm); } @@ -151,7 +138,10 @@ class LayoutTransitionManager { } } - @SuppressWarnings("unchecked") + /** + * Supplies the {@code LayoutAlgorithm} to be used for the initial @{code Graph} visualization + * @return + */ public LayoutAlgorithm getInitialLayoutAlgorithm() { LayoutAlgorithm initialLayoutAlgorithm = layoutFunction.apply(TIDIER_TREE).build(); @@ -159,12 +149,6 @@ class LayoutTransitionManager { if (initialLayoutAlgorithm instanceof TreeLayout) { ((TreeLayout) initialLayoutAlgorithm) .setRootPredicate(rootPredicate); - ((TreeLayout) initialLayoutAlgorithm) - .setVertexBoundsFunction(vertexBoundsFunction); - } - if (initialLayoutAlgorithm instanceof EdgeSorting) { - ((EdgeSorting) initialLayoutAlgorithm) - .setEdgeComparator(edgeComparator); } if (initialLayoutAlgorithm instanceof VertexBoundsFunctionConsumer) { ((VertexBoundsFunctionConsumer) initialLayoutAlgorithm) @@ -173,6 +157,10 @@ class LayoutTransitionManager { return initialLayoutAlgorithm; } + /** + * Supplies a {@code String[]} array of the supported layout names + * @return + */ public String[] getLayoutNames() { return layoutFunction.getNames(); } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java index b198d513b1..fc77954316 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java @@ -29,12 +29,14 @@ import ghidra.service.graph.AttributedVertex; public class JgtGraphMouse extends DefaultGraphMouse { private DefaultGraphDisplay graphDisplay; + private boolean allowEdgeSelection; // TODO we should not need the graph display for any mouse plugins, but the API is net yet // robust enough to communicate fully without it - public JgtGraphMouse(DefaultGraphDisplay graphDisplay) { + public JgtGraphMouse(DefaultGraphDisplay graphDisplay, boolean allowEdgeSelection) { super(DefaultGraphMouse.builder()); this.graphDisplay = graphDisplay; + this.allowEdgeSelection = allowEdgeSelection; } @Override @@ -54,13 +56,13 @@ public class JgtGraphMouse extends DefaultGraphMouse()); // add(new SelectingGraphMousePlugin<>()); add(new RegionSelectingGraphMousePlugin<>()); // the grab/pan feature - add(new TranslatingGraphMousePlugin(InputEvent.BUTTON1_DOWN_MASK)); + add(TranslatingGraphMousePlugin.builder().translatingMask(InputEvent.BUTTON1_DOWN_MASK).build()); // scaling add(new ScalingGraphMousePlugin()); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java index da8f9dab09..668777ea9d 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSatelliteGraphMouse.java @@ -39,19 +39,21 @@ public class JgtSatelliteGraphMouse // // JUNGRAPHT CHANGE 3 // + // disable single selection in satellite view by setting masks to 0 SelectingGraphMousePlugin mySelectingPlugin = - new JgtSelectingGraphMousePlugin(singleSelectionMask, addSingleSelectionMask); + new JgtSelectingGraphMousePlugin(0, 0); mySelectingPlugin.setLocked(true); selectingPlugin = mySelectingPlugin; regionSelectingPlugin = RegionSelectingGraphMousePlugin.builder() .regionSelectionMask(regionSelectionMask) - .addRegionSelectionMask(addRegionSelectionMask) + .toggleRegionSelectionMask(toggleRegionSelectionMask) .regionSelectionCompleteMask(regionSelectionCompleteMask) - .addRegionSelectionCompleteMask(addRegionSelectionCompleteMask) + .toggleRegionSelectionCompleteMask(toggleRegionSelectionCompleteMask) .build(); - translatingPlugin = new SatelliteTranslatingGraphMousePlugin(translatingMask); + translatingPlugin = SatelliteTranslatingGraphMousePlugin.builder() + .translatingMask(translatingMask).build(); add(selectingPlugin); add(regionSelectingPlugin); add(translatingPlugin); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java index e2f15d4917..680a2c0199 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtSelectingGraphMousePlugin.java @@ -54,8 +54,10 @@ public class JgtSelectingGraphMousePlugin this.pickFootprintPaintable = dummyPickFootprintPaintable; } - public JgtSelectingGraphMousePlugin(int singleSelectionMask, int addSingleSelectionMask) { - super(singleSelectionMask, addSingleSelectionMask); + public JgtSelectingGraphMousePlugin(int singleSelectionMask, int toggleSingleSelectionMask) { + super(SelectingGraphMousePlugin.builder() + .singleSelectionMask(singleSelectionMask) + .toggleSingleSelectionMask(toggleSingleSelectionMask)); // // JUNGRAPHT CHANGE 1 @@ -89,7 +91,6 @@ public class JgtSelectingGraphMousePlugin selectedVertexState.clear(); } selectedVertexState.select(vertex); - deselectedVertex = null; } else { // If this vertex is still around in mouseReleased, it will be deselected @@ -99,9 +100,6 @@ public class JgtSelectingGraphMousePlugin // // JUNGRAPHT CHANGE 2 HERE // - if (addToSelection) { - deselectedVertex = vertex; - } } e.consume(); return true; diff --git a/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties b/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties index 707a0815ae..1adeb4b1d0 100644 --- a/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties +++ b/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties @@ -13,8 +13,8 @@ jungrapht.satelliteBackgroundTransparent=false jungrapht.satelliteLensColor= 0xFAFAFA jungrapht.pickedEdgeColor=0xFF0000 -jungrapht.edgeArrowLength=30 -jungrapht.edgeArrowWidth=20 +jungrapht.edgeArrowLength=50 +jungrapht.edgeArrowWidth=40 # default spacing for tree layouts jungrapht.treeLayoutHorizontalSpacing=2 @@ -34,7 +34,7 @@ jungrapht.lensStrokeWidth=10.0 # when scale is < .1, switch to lightweight rendering jungrapht.lightweightScaleThreshold=.1 # under 50 vertices will use heavyweight rendering all the time -jungrapht.lightweightCountThreshold=50 +jungrapht.lightweightCountThreshold=80 # default pixels spacings for vertices jungrapht.mincross.horizontalOffset=10 @@ -69,12 +69,12 @@ jungrapht.edgeSpatialSupport=RTREE # the mask for single vertex/edge selection jungrapht.singleSelectionMask=MB1 # the mask to augment the selection with a single vertex/edge -jungrapht.addSingleSelectionMask=MB1_MENU +jungrapht.toggleSingleSelectionMask=MB1_MENU # the mask to select vertices within a region jungrapht.regionSelectionMask=MB1_MENU # the mask to augment the selection with vertices in a region -jungrapht.addRegionSelectionMask=MB1_SHIFT_MENU +jungrapht.toggleRegionSelectionMask=MB1_SHIFT_MENU # the mask to indicate that the selection region is complete/closed and selection may commence jungrapht.regionSelectionCompleteMask=MENU # the mask to indicate that the selection region for augmentation is complete/closed and selection may commence -jungrapht.addRegionSelectionCompleteMask=SHIFT_MENU +jungrapht.toggleRegionSelectionCompleteMask=SHIFT_MENU diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/pdb/pdbapplicator/PdbApplicator.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/pdb/pdbapplicator/PdbApplicator.java index 2d10d2f3eb..5d2d894236 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/pdb/pdbapplicator/PdbApplicator.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/pdb/pdbapplicator/PdbApplicator.java @@ -15,9 +15,10 @@ */ package ghidra.app.util.pdb.pdbapplicator; -import java.math.BigInteger; import java.util.*; +import java.math.BigInteger; + import ghidra.app.cmd.label.SetLabelPrimaryCmd; import ghidra.app.util.NamespaceUtils; import ghidra.app.util.SymbolPath; @@ -204,7 +205,7 @@ public class PdbApplicator { throw new PdbException("Invalid Restriction"); } - if (program == null) { + if (program != null) { Options options = program.getOptions(Program.PROGRAM_INFO); options.setBoolean(PdbParserConstants.PDB_LOADED, true); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java index b048f0cf42..87d7fa763b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java @@ -15,7 +15,6 @@ */ package docking; -import java.awt.event.MouseEvent; import java.util.LinkedHashSet; import java.util.Set; @@ -134,7 +133,7 @@ public class ActionToGuiMapper { return menuGroupMap; } - public void showPopupMenu(ComponentPlaceholder componentInfo, MouseEvent e) { - popupActionManager.popupMenu(componentInfo, e); + public void showPopupMenu(ComponentPlaceholder componentInfo, PopupMenuContext popupContext) { + popupActionManager.popupMenu(componentInfo, popupContext); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java b/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java index 55538b0f9e..c0963c8883 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java @@ -67,17 +67,17 @@ public class DockableComponent extends JPanel implements ContainerListener { @Override public void mousePressed(MouseEvent e) { componentSelected((Component) e.getSource()); - processPopupMouseEvent(e); + showContextMenu(e); } @Override public void mouseReleased(MouseEvent e) { - processPopupMouseEvent(e); + showContextMenu(e); } @Override public void mouseClicked(MouseEvent e) { - processPopupMouseEvent(e); + showContextMenu(e); } }; @@ -146,24 +146,27 @@ public class DockableComponent extends JPanel implements ContainerListener { return focusedComponent; } - private void processPopupMouseEvent(final MouseEvent e) { + void showContextMenu(PopupMenuContext popupContext) { + actionMgr.showPopupMenu(placeholder, popupContext); + } + + private void showContextMenu(MouseEvent e) { Component component = e.getComponent(); if (component == null) { - return; + return; // not sure this can happen } // get the bounds to see if the clicked point is over the component - Rectangle bounds = component.getBounds(); // get bounds to get width and height - + Rectangle bounds = component.getBounds(); if (component instanceof JComponent) { ((JComponent) component).computeVisibleRect(bounds); } Point point = e.getPoint(); boolean withinBounds = bounds.contains(point); - if (e.isPopupTrigger() && withinBounds) { - actionMgr.showPopupMenu(placeholder, e); + PopupMenuContext popupContext = new PopupMenuContext(e); + actionMgr.showPopupMenu(placeholder, popupContext); } } @@ -476,17 +479,11 @@ public class DockableComponent extends JPanel implements ContainerListener { return null; } - /** - * @see java.awt.event.ContainerListener#componentAdded(java.awt.event.ContainerEvent) - */ @Override public void componentAdded(ContainerEvent e) { initializeComponents(e.getChild()); } - /** - * @see java.awt.event.ContainerListener#componentRemoved(java.awt.event.ContainerEvent) - */ @Override public void componentRemoved(ContainerEvent e) { deinitializeComponents(e.getChild()); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingErrorDisplay.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingErrorDisplay.java index 5647b31906..3fd195d9cc 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingErrorDisplay.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingErrorDisplay.java @@ -18,6 +18,8 @@ package docking; import java.awt.Component; import java.awt.Window; +import org.apache.commons.text.WordUtils; + import docking.widgets.OkDialog; import docking.widgets.OptionDialog; import ghidra.util.*; @@ -26,11 +28,11 @@ import ghidra.util.exception.MultipleCauses; public class DockingErrorDisplay implements ErrorDisplay { /** - * Error dialog used to append exceptions. - * + * Error dialog used to append exceptions. + * *

    While this dialog is showing all new exceptions will be added to the dialog. When - * this dialog is closed, this reference will be cleared. - * + * this dialog is closed, this reference will be cleared. + * *

    Note: all use of this variable must be on the Swing thread to avoid thread * visibility issues. */ @@ -62,23 +64,34 @@ public class DockingErrorDisplay implements ErrorDisplay { Component parent, String title, Object message, Throwable throwable) { int dialogType = OptionDialog.PLAIN_MESSAGE; + String messageString = message != null ? message.toString() : null; - String rawMessage = HTMLUtilities.fromHTML(messageString); + if (messageString != null) { + // prevent excessive message degenerate cases + int maxChars = 1000; + String safeMessage = StringUtilities.trimMiddle(messageString, maxChars); + + // wrap any poorly formatted text that gets displayed in the label; 80-100 chars is + // a reasonable line length based on historical print margins + messageString = WordUtils.wrap(safeMessage, 100, null, true); + } + + String unformattedMessage = HTMLUtilities.fromHTML(messageString); switch (messageType) { case INFO: dialogType = OptionDialog.INFORMATION_MESSAGE; consoleDisplay.displayInfoMessage(errorLogger, originator, parent, title, - rawMessage); + unformattedMessage); break; case WARNING: case ALERT: dialogType = OptionDialog.WARNING_MESSAGE; consoleDisplay.displayWarningMessage(errorLogger, originator, parent, title, - rawMessage, throwable); + unformattedMessage, throwable); break; case ERROR: consoleDisplay.displayErrorMessage(errorLogger, originator, parent, title, - rawMessage, throwable); + unformattedMessage, throwable); dialogType = OptionDialog.ERROR_MESSAGE; break; } @@ -93,8 +106,8 @@ public class DockingErrorDisplay implements ErrorDisplay { return component; } - private void showDialog(final String title, final Throwable throwable, - final int dialogType, final String messageString, final Component parent) { + private void showDialog(final String title, final Throwable throwable, final int dialogType, + final String messageString, final Component parent) { Swing.runIfSwingOrRunLater(() -> { @@ -108,8 +121,8 @@ public class DockingErrorDisplay implements ErrorDisplay { }); } - private void showDialogOnSwing(String title, Throwable throwable, - int dialogType, String messageString, Component parent) { + private void showDialogOnSwing(String title, Throwable throwable, int dialogType, + String messageString, Component parent) { if (activeDialog != null) { activeDialog.addException(messageString, throwable); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java index 4b7d9b4476..b85ab9ec69 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java @@ -1765,8 +1765,11 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder } if (bestParent == null) { - KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - bestParent = kfm.getActiveWindow(); + bestParent = getJavaActiveWindow(); + } + + if (bestParent != null && !bestParent.isShowing()) { + bestParent = null; // don't let non-showing windows be parents } return bestParent; @@ -1775,8 +1778,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder private static Window getBestNonModalParent(DialogComponentProvider newProvider, Window bestParent) { - KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - Window activeWindow = kfm.getActiveWindow(); + Window activeWindow = getJavaActiveWindow(); if (!(activeWindow instanceof DockingDialog)) { return bestParent; } @@ -1937,13 +1939,19 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder private static Window getJavaActiveWindow() { KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - return kfm.getActiveWindow(); + Window activeWindow = kfm.getActiveWindow(); + if (activeWindow == null) { + return null; + } + if (!activeWindow.isShowing()) { + return null; // don't let non-showing windows be considered active + } + return activeWindow; } private static Window getActiveNonTransientWindow() { - KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); - Window bestWindow = kfm.getActiveWindow(); + Window bestWindow = getJavaActiveWindow(); if (bestWindow instanceof DockingDialog) { // We do not want Task Dialogs becoming parents, as they will get closed when the // task is finished, closing any other child dialogs, which means that dialogs such @@ -2172,6 +2180,34 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder objectUnderMouse = null; } + /** + * Shows a popup menu over the given component. If this given component is not part of the + * docking windows hierarchy, then no action is taken. + * + * @param component the component + */ + public static void showContextMenu(Component component) { + + DockingWindowManager dwm = getInstance(component); + if (dwm == null) { + return; + } + + DockableComponent dockableComponent = dwm.getDockableComponent(component); + if (dockableComponent == null) { + return; + } + + Rectangle bounds = dockableComponent.getBounds(); + + bounds.x = 0; + bounds.y = 0; + int x = (int) bounds.getCenterX(); + int y = (int) bounds.getCenterY(); + PopupMenuContext popupContext = new PopupMenuContext(dockableComponent, new Point(x, y)); + dockableComponent.showContextMenu(popupContext); + } + public void contextChanged(ComponentProvider provider) { if (provider == null) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ErrLogDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/ErrLogDialog.java index 0f02fc7c40..3eaf3c950d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ErrLogDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ErrLogDialog.java @@ -43,7 +43,7 @@ import ghidra.util.table.column.GColumnRenderer; import utilities.util.reflection.ReflectionUtilities; /** - * A dialog that takes error text and displays it with an option details button. If there is + * A dialog that takes error text and displays it with an option details button. If there is * an {@link ErrorReporter}, then a button is provided to report the error. */ public class ErrLogDialog extends AbstractErrDialog { @@ -149,7 +149,8 @@ public class ErrLogDialog extends AbstractErrDialog { introPanel.add( new GIconLabel(UIManager.getIcon("OptionPane.errorIcon"), SwingConstants.RIGHT), BorderLayout.WEST); - introPanel.add(new GHtmlLabel(HTMLUtilities.toHTML(message)) { + String html = HTMLUtilities.toHTML(message); + introPanel.add(new GHtmlLabel(html) { @Override public Dimension getPreferredSize() { // rendering HTML the label can expand larger than the screen; keep it reasonable @@ -387,6 +388,16 @@ public class ErrLogDialog extends AbstractErrDialog { textDetails.scrollToBottom(); } + @Override + public Dimension getPreferredSize() { + Dimension size = super.getPreferredSize(); + + // Cap preferred width to something reasonable; most displays have more than 1000 width. + // Users can still resize as desired + size.width = Math.min(size.width, 1000); + return size; + } + void setError(ErrorEntry e) { error = e; setExceptionMessage(e.getDetailsText()); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java index af93a7677a..5a712f2b76 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java @@ -16,6 +16,7 @@ package docking; import java.awt.Component; +import java.awt.Point; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -67,28 +68,27 @@ public class PopupActionManager implements PropertyChangeListener { } } - void popupMenu(ComponentPlaceholder info, MouseEvent e) { + void popupMenu(ComponentPlaceholder placeholder, PopupMenuContext popupContext) { - if (e.isConsumed()) { - return; - } - ComponentProvider popupProvider = info.getProvider(); - ActionContext actionContext = popupProvider.getActionContext(e); + MouseEvent event = popupContext.getEvent(); + ComponentProvider popupProvider = placeholder.getProvider(); + ActionContext actionContext = popupProvider.getActionContext(event); if (actionContext == null) { actionContext = new ActionContext(); } - actionContext.setSourceObject(e.getSource()); - actionContext.setMouseEvent(e); + actionContext.setSourceObject(popupContext.getSource()); + actionContext.setMouseEvent(event); - Iterator localActions = info.getActions(); + Iterator localActions = placeholder.getActions(); JPopupMenu popupMenu = createPopupMenu(localActions, actionContext); if (popupMenu == null) { return; // no matching actions } - Component c = (Component) e.getSource(); - popupMenu.show(c, e.getX(), e.getY()); + Component c = popupContext.getComponent(); + Point p = popupContext.getPoint(); + popupMenu.show(c, p.x, p.y); } protected JPopupMenu createPopupMenu(Iterator localActions, diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java b/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java new file mode 100644 index 0000000000..16c87cbd7f --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java @@ -0,0 +1,61 @@ +/* ### + * 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 docking; + +import java.awt.Component; +import java.awt.Point; +import java.awt.event.MouseEvent; +import java.util.Objects; + +/** + * A class that holds information used to show a popup menu + */ +public class PopupMenuContext { + + private Component component; + private MouseEvent event; + private Point point; + + PopupMenuContext(MouseEvent event) { + this.event = event; + this.component = Objects.requireNonNull(event.getComponent()); + this.point = event.getPoint(); + } + + PopupMenuContext(Component component, Point point) { + this.component = Objects.requireNonNull(component); + this.point = point; + } + + public MouseEvent getEvent() { + return event; + } + + public Component getComponent() { + return component; + } + + public Point getPoint() { + return new Point(point); + } + + public Object getSource() { + if (event != null) { + return event.getSource(); + } + return component; + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java index f29071f815..e433c7705a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java @@ -17,8 +17,7 @@ package docking.action; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import javax.swing.Action; import javax.swing.KeyStroke; @@ -61,6 +60,7 @@ public class KeyBindingsManager implements PropertyChangeListener { public void addReservedAction(DockingActionIf action) { KeyStroke keyBinding = action.getKeyBinding(); + Objects.requireNonNull(keyBinding); addReservedKeyBinding(action, keyBinding); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/ShowContextMenuAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/ShowContextMenuAction.java new file mode 100644 index 0000000000..469e521057 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/ShowContextMenuAction.java @@ -0,0 +1,52 @@ +/* ### + * 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 docking.action; + +import java.awt.*; + +import javax.swing.KeyStroke; + +import docking.ActionContext; +import docking.DockingWindowManager; + +/** + * An action to trigger a context menu over the focus owner. This allows context menus to be + * triggered from the keyboard. + */ +public class ShowContextMenuAction extends DockingAction { + + public ShowContextMenuAction(KeyStroke keyStroke) { + super("Show Context Menu", DockingWindowManager.DOCKING_WINDOWS_OWNER); + setKeyBindingData(new KeyBindingData(keyStroke)); + } + + @Override + public void actionPerformed(ActionContext context) { + + KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Window window = kfm.getActiveWindow(); + if (window == null) { + return; + } + + // use the focused component to determine what should get the context menu + Component focusOwner = kfm.getFocusOwner(); + if (focusOwner != null) { + DockingWindowManager.showContextMenu(focusOwner); + } + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java index 592d545f81..bec2cc41cb 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java @@ -91,6 +91,10 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2)); keyBindingsManager.addReservedAction( new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY)); + keyBindingsManager.addReservedAction( + new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY1)); + keyBindingsManager.addReservedAction( + new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY2)); // these are diagnostic if (SystemUtilities.isInDevelopmentMode()) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingCheckboxMenuItemUI.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingCheckboxMenuItemUI.java index 6cfc44548b..11b8ed0883 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingCheckboxMenuItemUI.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingCheckboxMenuItemUI.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +25,8 @@ import javax.swing.plaf.MenuItemUI; public class DockingCheckboxMenuItemUI extends DockingMenuItemUI { public static ComponentUI createUI(JComponent c) { - LookAndFeel underlying = UIManager.getLookAndFeel(); DockingCheckboxMenuItemUI result = new DockingCheckboxMenuItemUI(); - result.ui = (MenuItemUI) underlying.getDefaults().getUI(c); + result.ui = (MenuItemUI) UIManager.getDefaults().getUI(c); return result; } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingMenuItemUI.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingMenuItemUI.java index e03acac259..c692839a0b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingMenuItemUI.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingMenuItemUI.java @@ -58,9 +58,8 @@ public class DockingMenuItemUI extends MenuItemUI { protected MenuItemUI ui; public static ComponentUI createUI(JComponent c) { - LookAndFeel underlying = UIManager.getLookAndFeel(); DockingMenuItemUI result = new DockingMenuItemUI(); - result.ui = (MenuItemUI) underlying.getDefaults().getUI(c); + result.ui = (MenuItemUI) UIManager.getDefaults().getUI(c); return result; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingMenuUI.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingMenuUI.java index 8b61ac3cb4..d53eacd34b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingMenuUI.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingMenuUI.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +21,8 @@ import javax.swing.plaf.MenuItemUI; public class DockingMenuUI extends DockingMenuItemUI { public static ComponentUI createUI(JComponent c) { - LookAndFeel underlying = UIManager.getLookAndFeel(); DockingMenuUI result = new DockingMenuUI(); - result.ui = (MenuItemUI) underlying.getDefaults().getUI(c); + result.ui = (MenuItemUI) UIManager.getDefaults().getUI(c); return result; } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index 775c4f8407..a732c3e492 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -2017,7 +2017,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { // ThreadedTableModelUpdateMgr Object updateManager = getInstanceField("updateManager", model); SwingUpdateManager sum = - (SwingUpdateManager) getInstanceField("updateManager", updateManager); + (SwingUpdateManager) getInstanceField("addRemoveUpdater", updateManager); Worker worker = (Worker) getInstanceField("worker", model); String workerState = worker == null ? "" : Boolean.toString(worker.isBusy()); return "Table model busy state - Swing Update Manager? " + sum.isBusy() + "; worker?" + diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeStartEditingTask.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeStartEditingTask.java index 8d8ba4e8aa..3132cce3bf 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeStartEditingTask.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeStartEditingTask.java @@ -60,16 +60,15 @@ public class GTreeStartEditingTask extends GTreeTask { private void edit() { + if (tree.isFiltered()) { + Msg.showWarn(getClass(), tree, "Cannot Edit Tree Node", + "Can't edit tree node \"" + childName + "\" while tree is filtered."); + return; + } + GTreeNode editNode = parent.getChild(childName); if (editNode == null) { - if (tree.isFiltered()) { - Msg.showWarn(getClass(), tree, "Cannot Edit Tree Node", - "Can't edit tree node \"" + childName + "\" while tree is filtered."); - } - else { - Msg.debug(this, - "Can't find node \"" + childName + "\" to edit."); - } + Msg.debug(this, "Can't find node \"" + childName + "\" to edit."); return; } diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/DockingWindowsLookAndFeelUtils.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/DockingWindowsLookAndFeelUtils.java index 1d9ef7bb95..4e49418168 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/DockingWindowsLookAndFeelUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/DockingWindowsLookAndFeelUtils.java @@ -204,7 +204,7 @@ public class DockingWindowsLookAndFeelUtils { case NIMBUS_LOOK_AND_FEEL: // fix scroll bar grabber disappearing. See https://bugs.openjdk.java.net/browse/JDK-8134828 // This fix looks like it should not cause harm even if the bug is fixed on the jdk side. - UIDefaults defaults = lookAndFeel.getDefaults(); + UIDefaults defaults = UIManager.getDefaults(); defaults.put("ScrollBar.minimumThumbSize", new Dimension(30, 30)); // (see NimbusDefaults for key values that can be changed here) @@ -277,8 +277,7 @@ public class DockingWindowsLookAndFeelUtils { /** Allows you to globally set the font size (don't use this method!) */ private static void setGlobalFontSizeOverride(int fontSize) { - LookAndFeel lookAndFeel = UIManager.getLookAndFeel(); - UIDefaults defaults = lookAndFeel.getDefaults(); + UIDefaults defaults = UIManager.getDefaults(); Set> set = defaults.entrySet(); Iterator> iterator = set.iterator(); diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/ReservedKeyBindings.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/ReservedKeyBindings.java index c081d5ff99..025c116c48 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/ReservedKeyBindings.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/ReservedKeyBindings.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +33,11 @@ public class ReservedKeyBindings { public static final KeyStroke HELP_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F1, DockingUtils.CONTROL_KEY_MODIFIER_MASK); + public static final KeyStroke CONTEXT_MENU_KEY1 = + KeyStroke.getKeyStroke(KeyEvent.VK_F10, InputEvent.SHIFT_DOWN_MASK); + public static final KeyStroke CONTEXT_MENU_KEY2 = + KeyStroke.getKeyStroke(KeyEvent.VK_CONTEXT_MENU, 0); + public static final KeyStroke FOCUS_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F2, DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); @@ -50,7 +54,8 @@ public class ReservedKeyBindings { code == KeyEvent.VK_CAPS_LOCK || code == KeyEvent.VK_TAB || HELP_KEY1.equals(keyStroke) || HELP_KEY2.equals(keyStroke) || HELP_INFO_KEY.equals(keyStroke) || UPDATE_KEY_BINDINGS_KEY.equals(keyStroke) || - FOCUS_INFO_KEY.equals(keyStroke) || FOCUS_CYCLE_INFO_KEY.equals(keyStroke)) { + FOCUS_INFO_KEY.equals(keyStroke) || FOCUS_CYCLE_INFO_KEY.equals(keyStroke) || + CONTEXT_MENU_KEY1.equals(keyStroke) || CONTEXT_MENU_KEY2.equals(keyStroke)) { return true; } diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskMonitorComponent.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskMonitorComponent.java index e18ec1a446..7fa2a23752 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskMonitorComponent.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskMonitorComponent.java @@ -92,9 +92,9 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor { /** * Constructor - * + * * @param includeTextField if true, the dialog can display a status progressMessage with progress details - * @param includeCancelButton if true, a cancel button will be displayed + * @param includeCancelButton if true, a cancel button will be displayed */ public TaskMonitorComponent(boolean includeTextField, boolean includeCancelButton) { updateProgressPanelRunnable = () -> updateProgressPanel(); @@ -106,7 +106,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor { shouldCancelRunnable = () -> { int currentTaskID = taskID.get(); - boolean userSaysYes = OptionDialog.showYesNoDialog(TaskMonitorComponent.this, "Cancel?", + boolean userSaysYes = OptionDialog.showYesNoDialog(null, "Cancel?", "Do you really want to cancel " + getTaskName() + "?") == OptionDialog.OPTION_ONE; if (userSaysYes && currentTaskID == taskID.get()) { @@ -206,7 +206,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor { // a chance to do so. In other words, the background thread will end up // blocking instead of working, which defeats our attempts to never show // a task dialog for fast background tasks. - // + // isIndeterminate.set(indeterminate); Swing.runIfSwingOrRunLater(() -> { boolean newValue = isIndeterminate.get(); @@ -269,7 +269,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor { /** * Returns true if {@link #setIndeterminate(boolean)} with a value of true has * been called. - * + * * @return true if {@link #setIndeterminate(boolean)} with a value of true has * been called. */ @@ -280,7 +280,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor { /** * Set whether the progress bar should be visible - * + * * @param show true if the progress bar should be visible */ public synchronized void showProgress(boolean show) { @@ -293,7 +293,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor { /** * Set the name of the task; the name shows up in the tool tip for * the cancel button. - * + * * @param name the name of the task */ public void setTaskName(String name) { @@ -303,7 +303,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor { /** * Set the visibility of the cancel button - * + * * @param visible if true, show the cancel button; false otherwise */ public void setCancelButtonVisibility(boolean visible) { @@ -325,7 +325,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor { /** * Sets the visibility of the progress icon - * + * * @param visible if true, display the progress icon */ public void showProgressIcon(boolean visible) { diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/framework/SplashScreenTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/framework/SplashScreenTest.java index c3ef15bca8..0dbdf2655b 100644 --- a/Ghidra/Framework/Docking/src/test.slow/java/docking/framework/SplashScreenTest.java +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/framework/SplashScreenTest.java @@ -30,71 +30,43 @@ import docking.*; import docking.test.AbstractDockingTest; import docking.widgets.PasswordDialog; import generic.test.category.NightlyCategory; -import ghidra.util.Msg; // The splash screen is sensitive to windows being activated/deactivated, so don't run // when other test windows may be open @Category(NightlyCategory.class) public class SplashScreenTest extends AbstractDockingTest { - private AboutDialog aboutDialog; - @After public void tearDown() { - Msg.debug(this, "tearDown() - open windows before closing"); - printOpenWindows(); - runSwing(() -> SplashScreen.disposeSplashScreen()); - - closeAllWindows(); - printOpenWindows(); - - Msg.debug(this, "tearDown() - open windows after closing"); + disposeAllWindows(); } - @Test - public void testShowInfoWindow() throws Exception { - // no parent - showModalInfoWindow(null); - - ensureInfoWindowVisible(); - hideInfoWindow(); - - // not visible parent - JFrame parentFrame = new JFrame("InfoWindowTest.testShowInfoWindow Frame"); - parentFrame.setBounds(-100, -100, 0, 0); - showModalInfoWindow(parentFrame); - - ensureInfoWindowVisible(); - hideInfoWindow(); - - // visible parent - parentFrame.setVisible(true); - showModalInfoWindow(parentFrame); - - ensureInfoWindowVisible(); - hideInfoWindow(); + private void disposeAllWindows() { + for (Window window : getAllWindows()) { + runSwing(window::dispose); + } } @Test public void testShowAndHideSplashScreen() { showSplashScreen(true); - ensureSpashScreenVisible(true); + assertSpashScreenVisible(true); showSplashScreen(false); - ensureSpashScreenVisible(false); + assertSpashScreenVisible(false); showSplashScreen(true); - ensureSpashScreenVisible(true); + assertSpashScreenVisible(true); showSplashScreen(false); - ensureSpashScreenVisible(false); + assertSpashScreenVisible(false); } @Test public void testUpdateSplashScreenStatus() { showSplashScreen(true); - ensureSpashScreenVisible(true); + assertSpashScreenVisible(true); JLabel statusLabel = (JLabel) getInstanceField("statusLabel", SplashScreen.class); @@ -120,24 +92,24 @@ public class SplashScreenTest extends AbstractDockingTest { public void testSplashScreenPasswordModality_SharedParent() throws Exception { showSplashScreen(true); - ensureSpashScreenVisible(true); + assertSpashScreenVisible(true); // show a modal dialog with no parent (this will use the Splash Screen's parent) showModalPasswordDialog(null); // When the splash screen and the dialog share a parent, then the dialog should NOT // cause the splash screen to go away - ensureSpashScreenVisible(true); + assertSpashScreenVisible(true); } @Test public void testSplashScreenPasswordModality_UnsharedParent() throws Exception { // show the splash screen showSplashScreen(true); - ensureSpashScreenVisible(true); + assertSpashScreenVisible(true); DockingFrame frame = new DockingFrame("Modal Parent Frame"); - frame.setVisible(true); + show(frame); showModalPasswordDialog(frame); ensureSplashScreenWillClose(); @@ -147,6 +119,10 @@ public class SplashScreenTest extends AbstractDockingTest { // Private Methods //================================================================================================== + private void show(JFrame frame) { + runSwing(() -> frame.setVisible(true)); + } + private void ensureSplashScreenWillClose() { waitForCondition(() -> { SplashScreen splash = getSplash(); @@ -155,42 +131,44 @@ public class SplashScreenTest extends AbstractDockingTest { } private DockingDialog showModalPasswordDialog(Frame parentFrame) throws Exception { + String dialogTitle = "InfoWindowTest.testSplashScreenPasswordModality() Dialog"; DialogComponentProvider passwordDialog = runSwing(() -> new PasswordDialog(dialogTitle, "Server Type", "Server Name", "Prompt", null, null)); + if (parentFrame == null) { + // null means to share the parent + Object splashParent = getInstanceField("hiddenFrame", SplashScreen.class); + parentFrame = (Frame) splashParent; + } + + Frame finalParent = parentFrame; executeOnSwingWithoutBlocking( - () -> DockingWindowManager.showDialog(parentFrame, passwordDialog)); + () -> { + DockingDialog dialog = + DockingDialog.createDialog(finalParent, passwordDialog, finalParent); + dialog.setVisible(true); + }); JDialog dialog = waitForJDialog(dialogTitle); assertNotNull(dialog); - Window dialogWindow = SwingUtilities.windowForComponent(dialog); - Msg.debug(this, "Created modal dialog with parent: " + getTitleForWindow(dialogWindow) + - " - id: " + System.identityHashCode(dialogWindow)); - return (DockingDialog) dialog; } - // handles showing the modal info window, which must be done from a thread outside of the - // test thread - private void showModalInfoWindow(final JFrame parentFrame) { - // create a thread to show the modal dialog so that the current thread doesn't block - aboutDialog = runSwing(() -> new AboutDialog()); - executeOnSwingWithoutBlocking(() -> DockingWindowManager.showDialog(null, aboutDialog)); - } - private void showSplashScreen(final boolean makeVisible) { if (makeVisible) { SplashScreen splash = runSwing(() -> SplashScreen.showSplashScreen()); assertNotNull("Failed showing splash screen", splash); + waitForSwing(); return; } SplashScreen.disposeSplashScreen(); + waitForSwing(); } - private void ensureSpashScreenVisible(boolean visible) { + private void assertSpashScreenVisible(boolean visible) { // get the 'splashWindow' and make sure that it is not null and that it is visible SplashScreen splashScreen = getSplash(); @@ -206,23 +184,6 @@ public class SplashScreenTest extends AbstractDockingTest { // timing issue debug waitForCondition(() -> splashScreen.isVisible()); - - if (!splashScreen.isVisible()) { - - // this can happen if other OS windows trigger the splash window to be hidden - printOpenWindows(); - fail("The splash screen is not visible when expected to be so - " + splashScreen); - } - } - - private void ensureInfoWindowVisible() { - // get the 'infoDialog' and make sure that it is not null and that it is visible - assertTrue("The info dialog is not visible after it was supposed to " + "have been shown.", - aboutDialog.isVisible()); - } - - private void hideInfoWindow() throws Exception { - runSwing(() -> aboutDialog.close()); } private SplashScreen getSplash() { diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/popup/PopupRegulator.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/popup/PopupRegulator.java index 50445c0f64..1a004061c8 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/popup/PopupRegulator.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/popup/PopupRegulator.java @@ -15,6 +15,7 @@ */ package ghidra.graph.viewer.popup; +import java.awt.Component; import java.awt.Window; import java.awt.event.*; @@ -129,6 +130,13 @@ public class PopupRegulator { return; } + Component c = event.getComponent(); + if (!c.isShowing()) { + // This method is called from a a timer. It is possible that the graph has been + // closed by the time this method is called. + return; + } + ToolTipInfo toolTipInfo = popupSource.getToolTipInfo(event); JComponent toolTipComponent = toolTipInfo.getToolTipComponent(); boolean isCustomJavaTooltip = !(toolTipComponent instanceof JToolTip); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeFactory.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeFactory.java index 11e248eec2..da39b07af5 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeFactory.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeFactory.java @@ -61,7 +61,7 @@ public interface PcodeFactory { public HighSymbol getSymbol(long symbolId); public Varnode setInput(Varnode vn,boolean val); public void setAddrTied(Varnode vn,boolean val); - public void setPersistant(Varnode vn,boolean val); + public void setPersistent(Varnode vn, boolean val); public void setUnaffected(Varnode vn,boolean val); public void setMergeGroup(Varnode vn,short val); public void setDataType(Varnode vn,DataType type); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeSyntaxTree.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeSyntaxTree.java index 48cd3429a6..4e1002e758 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeSyntaxTree.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeSyntaxTree.java @@ -392,9 +392,9 @@ public class PcodeSyntaxTree implements PcodeFactory { } @Override - public void setPersistant(Varnode vn, boolean val) { + public void setPersistent(Varnode vn, boolean val) { VarnodeAST vnast = (VarnodeAST) vn; - vnast.setPersistant(val); + vnast.setPersistent(val); } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java index 58a175c7a9..4e6f867f4b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java @@ -255,9 +255,9 @@ public class Varnode { } /** - * @return is persistant + * @return is persistent */ - public boolean isPersistant() { + public boolean isPersistent() { return false; // Not a valid query with a free varnode } @@ -476,7 +476,7 @@ public class Varnode { } attrstring = el.getAttribute("persists"); if ((attrstring != null) && (SpecXmlUtils.decodeBoolean(attrstring))) { - factory.setPersistant(vn, true); + factory.setPersistent(vn, true); } attrstring = el.getAttribute("addrtied"); if ((attrstring != null) && (SpecXmlUtils.decodeBoolean(attrstring))) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/VarnodeAST.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/VarnodeAST.java index 8a837809e4..a3c3241b6a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/VarnodeAST.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/VarnodeAST.java @@ -37,7 +37,7 @@ public class VarnodeAST extends Varnode { private boolean bInput; private boolean bAddrTied; - private boolean bPersistant; + private boolean bPersistent; private boolean bUnaffected; private boolean bFree; private int uniqId; // Unique Id for distinguishing otherwise identical varnodes @@ -50,7 +50,7 @@ public class VarnodeAST extends Varnode { super(a, sz); bInput = false; bAddrTied = false; - bPersistant = false; + bPersistent = false; bUnaffected = false; bFree = true; uniqId = id; @@ -70,8 +70,8 @@ public class VarnodeAST extends Varnode { } @Override - public boolean isPersistant() { - return bPersistant; + public boolean isPersistent() { + return bPersistent; } @Override @@ -132,8 +132,8 @@ public class VarnodeAST extends Varnode { def = null; } - public void setPersistant(boolean val) { - bPersistant = val; + public void setPersistent(boolean val) { + bPersistent = val; } public void setUnaffected(boolean val) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java index 696671614e..0f1358f53f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java @@ -34,6 +34,52 @@ public interface GraphDisplay { public static final int ALIGN_CENTER = 1; // aligns graph text to the center public static final int ALIGN_RIGHT = 2; // aligns graph text to the right + /** + * values are color names or rgb in hex '0xFF0000' is red + */ + String SELECTED_VERTEX_COLOR = "selectedVertexColor"; + /** + * values are color names or rgb in hex '0xFF0000' is red + */ + String SELECTED_EDGE_COLOR = "selectedEdgeColor"; + /** + * values are defined as String symbols in LayoutFunction class + * + * KAMADA_KAWAI,FRUCTERMAN_REINGOLD,CIRCLE_MINCROSS,TIDIER_TREE,TIDIER_RADIAL_TREE, + * MIN_CROSS_TOP_DOWN,MIN_CROSS_LONGEST_PATH,MIN_CROSS_NETWORK_SIMPLEX,MIN_CROSS_COFFMAN_GRAHAM, + * EXP_MIN_CROSS_TOP_DOWN,EXP_MIN_CROSS_LONGEST_PATH,EXP_MIN_CROSS_NETWORK_SIMPLEX, + * EXP_MIN_CROSS_COFFMAN_GRAHAM,TREE,RADIAL,BALLOON,GEM + * + * may have no meaning for a different graph visualization library + */ + String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm"; + /** + * true or false + * may have no meaning for a different graph visualization library + */ + String DISPLAY_VERTICES_AS_ICONS = "displayVerticesAsIcons"; + /** + * values are the strings N,NE,E,SE,S,SW,W,NW,AUTO,CNTR + * may have no meaning for a different graph visualization library + */ + String VERTEX_LABEL_POSITION = "vertexLabelPosition"; + /** + * true or false, whether edge selection via a mouse click is enabled. + * May not be supported by another graph visualization library + */ + String ENABLE_EDGE_SELECTION = "enableEdgeSelection"; + /** + * a comma-separated list of edge type names in priority order + */ + String EDGE_TYPE_PRIORITY_LIST = "edgeTypePriorityList"; + /** + * a comma-separated list of edge type names. + * any will be considered a favored edge for the min-cross layout + * algorithms. + * May have no meaning with a different graph visualization library + */ + String FAVORED_EDGES = "favoredEdges"; + /** * Sets a {@link GraphDisplayListener} to be notified when the user changes the vertex focus * or selects one or more nodes in a graph window diff --git a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.feature/category.xml b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.feature/category.xml index 21b6add2a1..5c9a401995 100644 --- a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.feature/category.xml +++ b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.feature/category.xml @@ -1,7 +1,7 @@ - - + + - + diff --git a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.feature/feature.xml b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.feature/feature.xml index 65831c9255..16ffc71a33 100644 --- a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.feature/feature.xml +++ b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.feature/feature.xml @@ -2,7 +2,7 @@ diff --git a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ide/META-INF/MANIFEST.MF b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ide/META-INF/MANIFEST.MF index fffdce6b63..7760ce9751 100644 --- a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ide/META-INF/MANIFEST.MF +++ b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ide/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Automatic-Module-Name: ghidra.xtext.sleigh.ide Bundle-ManifestVersion: 2 Bundle-Name: ghidra.xtext.sleigh.ide Bundle-Vendor: Ghidra -Bundle-Version: 1.0.0.qualifier +Bundle-Version: 1.0.1.qualifier Bundle-SymbolicName: ghidra.xtext.sleigh.ide; singleton:=true Bundle-ActivationPolicy: lazy Require-Bundle: ghidra.xtext.sleigh, diff --git a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ui.tests/META-INF/MANIFEST.MF b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ui.tests/META-INF/MANIFEST.MF index 94627e204e..d050e0d5b7 100644 --- a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ui.tests/META-INF/MANIFEST.MF +++ b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ui.tests/META-INF/MANIFEST.MF @@ -13,6 +13,7 @@ Require-Bundle: ghidra.xtext.sleigh.ui, org.eclipse.xtext.junit4, org.eclipse.xtext.xbase.junit, org.eclipse.core.runtime, - org.eclipse.ui.workbench;resolution:=optional + org.eclipse.ui.workbench;resolution:=optional, + org.eclipse.xtext.ui.testing Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Export-Package: ghidra.xtext.sleigh.ui.tests;x-internal=true diff --git a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ui/META-INF/MANIFEST.MF b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ui/META-INF/MANIFEST.MF index 075e0e71af..7d5b41fd0d 100644 --- a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ui/META-INF/MANIFEST.MF +++ b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh.ui/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Automatic-Module-Name: ghidra.xtext.sleigh.ui Bundle-ManifestVersion: 2 Bundle-Name: ghidra.xtext.sleigh.ui Bundle-Vendor: Ghidra -Bundle-Version: 1.0.0.qualifier +Bundle-Version: 1.0.1.qualifier Bundle-SymbolicName: ghidra.xtext.sleigh.ui; singleton:=true Bundle-ActivationPolicy: lazy Require-Bundle: ghidra.xtext.sleigh, diff --git a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh/META-INF/MANIFEST.MF b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh/META-INF/MANIFEST.MF index c982b203b5..4fa4632222 100644 --- a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh/META-INF/MANIFEST.MF +++ b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Automatic-Module-Name: ghidra.xtext.sleigh Bundle-ManifestVersion: 2 Bundle-Name: ghidra.xtext.sleigh Bundle-Vendor: Ghidra -Bundle-Version: 1.0.0.qualifier +Bundle-Version: 1.0.1.qualifier Bundle-SymbolicName: ghidra.xtext.sleigh; singleton:=true Bundle-ActivationPolicy: lazy Require-Bundle: org.eclipse.xtext, diff --git a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh/plugin.xml b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh/plugin.xml index 26c699fb0e..242ae876f4 100644 --- a/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh/plugin.xml +++ b/GhidraBuild/EclipsePlugins/GhidraSleighEditor/ghidra.xtext.sleigh/plugin.xml @@ -1,7 +1,11 @@ - + + + up to the size of output. If the size of -output is smaller than the size of input0 plus the constant input1, +output is smaller than the size of input0 minus the constant input1, then the additional most significant bytes of input0 will also be truncated.