From de9947336c89ec08b2677aab37a8ce9c5a50b774 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sun, 10 Mar 2024 11:21:51 +0100 Subject: [PATCH 001/356] Create proper memory references for scalars. The "Create Memory Reference" action did not take segmented memory address spaces into account when creating memory references out of scalar operands. --- .../CreateDefaultReferenceAction.java | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/CreateDefaultReferenceAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/CreateDefaultReferenceAction.java index 55ae504418..4595586403 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/CreateDefaultReferenceAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/CreateDefaultReferenceAction.java @@ -233,27 +233,50 @@ public class CreateDefaultReferenceAction extends ListingContextAction { updatePopupMenuPath(actionOK); return actionOK; } - - private boolean initMemoryAddress(AddressFactory addrFactory, long offset) { - AddressSpace contextAddrSpace = context.getAddress().getAddressSpace(); + + private Address buildDestinationAddress(AddressSpace addrSpace, + Address sourceAddress, long offset) { try { - memAddr = contextAddrSpace.getAddress(offset, true); - return true; + if ((addrSpace instanceof SegmentedAddressSpace) && + (sourceAddress instanceof SegmentedAddress)) { + return ((SegmentedAddressSpace) addrSpace).getAddress( + ((SegmentedAddress) sourceAddress).getSegment(), + (int) (offset & 0xFFFF)); + } + + return addrSpace.getAddress(offset, true); } catch (AddressOutOfBoundsException ei) { - // try the default space! } - AddressSpace defaultSpace = addrFactory.getDefaultAddressSpace(); - if (contextAddrSpace != defaultSpace) { - try { - memAddr = defaultSpace.getAddress(offset, true); - return true; - } - catch (AddressOutOfBoundsException ei) { - // ignore - } + return null; + } + + private boolean initMemoryAddress(AddressFactory addrFactory, long offset) { + // Use the context's address space. + + AddressSpace contextAddrSpace = context.getAddress().getAddressSpace(); + Address address = buildDestinationAddress(contextAddrSpace, + context.getAddress(), offset); + if (address != null) { + memAddr = address; + return true; } + + // Try the default space. + + AddressSpace defaultSpace = addrFactory.getDefaultAddressSpace(); + if (contextAddrSpace != defaultSpace) { + address = buildDestinationAddress(defaultSpace, context.getAddress(), + offset); + } + if (address != null) { + memAddr = address; + return true; + } + + // Ignore. + return false; } From a10c00911ae1b59b87157cee333b679bc78f26cd Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 20 Mar 2024 18:27:34 +0100 Subject: [PATCH 002/356] Add embedded MIDI data type with its media player. --- .../program/model/data/MIDIDataType.java | 173 ++++++++++++++++++ .../program/model/data/ScorePlayer.java | 101 ++++++++++ 2 files changed, 274 insertions(+) create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java new file mode 100644 index 0000000000..4a93231918 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java @@ -0,0 +1,173 @@ +/* ### + * 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.program.model.data; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import ghidra.docking.settings.Settings; +import ghidra.program.model.mem.MemBuffer; +import ghidra.program.model.mem.Memory; +import ghidra.util.Msg; + +public class MIDIDataType extends BuiltIn implements Dynamic { + public MIDIDataType() { + this(null); + } + + public MIDIDataType(DataTypeManager dtm) { + super(null, "MIDI-Score", dtm); + } + + @Override + public int getLength() { + return -1; + } + + @Override + public int getLength(MemBuffer buf, int maxLength) { + try { + return computeLength(buf, maxLength); + } + catch (Exception e) { + Msg.debug(this, "Invalid MIDI data at " + buf.getAddress()); + } + return -1; + } + + private long readUnsignedInteger(InputStream stream) throws IOException { + long value = 0; + for (int index = 0; index < 4; index++) { + int currentByte = stream.read(); + if (currentByte == -1) { + throw new EOFException(); + } + value = (value << 8) | currentByte; + } + return value; + } + + private int readUnsignedShort(InputStream stream) throws IOException { + int value = 0; + for (int index = 0; index < 2; index++) { + int currentByte = stream.read(); + if (currentByte == -1) { + throw new EOFException(); + } + value = (value << 8) | currentByte; + } + return value; + } + + private int computeLength(MemBuffer buf, int maxLength) throws IOException, InvalidDataTypeException { + int computedLength = -1; + + try (InputStream stream = buf.getInputStream(0, maxLength > 0 ? maxLength : Integer.MAX_VALUE)) { + byte[] chunkType = new byte[4]; + if (stream.read(chunkType) < chunkType.length) { + throw new EOFException(); + } + if (chunkType[0] != (byte)'M' || + chunkType[1] != (byte)'T' || + chunkType[2] != (byte)'h' || + chunkType[3] != (byte)'d') { + return -1; + } + long chunkLength = readUnsignedInteger(stream); + if (chunkLength != 6) { + throw new InvalidDataTypeException("Unexpected header length."); + } + stream.skip(2); + int tracks = readUnsignedShort(stream); + stream.skip(2); + computedLength = 14; + while (tracks > 0) { + if (stream.read(chunkType) < chunkType.length) { + throw new EOFException(); + } + chunkLength = readUnsignedInteger(stream); + stream.skip(chunkLength); + computedLength += 8 + chunkLength; + if (chunkType[0] != (byte)'M' || + chunkType[1] != (byte)'T' || + chunkType[2] != (byte)'r' || + chunkType[3] != (byte)'k') { + continue; + } + tracks--; + } + } finally { + } + + return computedLength; + } + + @Override + public boolean canSpecifyLength() { + return false; + } + + @Override + public DataType clone(DataTypeManager dtm) { + if (dtm == getDataTypeManager()) { + return this; + } + return new MIDIDataType(dtm); + } + + @Override + public String getDescription() { + return "MIDI score stored within program"; + } + + @Override + public String getMnemonic(Settings settings) { + return "MIDI"; + } + + @Override + public String getRepresentation(MemBuffer buf, Settings settings, int length) { + return ""; + } + + @Override + public Object getValue(MemBuffer buf, Settings settings, int length) { + byte[] data = new byte[length]; + if (buf.getBytes(data, 0) != length) { + Msg.error(this, "MIDI-Score error: Not enough bytes!"); + return null; + } + return new ScorePlayer(data); + } + + @Override + public Class getValueClass(Settings settings) { + return ScorePlayer.class; + } + + @Override + public String getDefaultLabelPrefix(MemBuffer buf, Settings settings, int len, + DataTypeDisplayOptions options) { + return "MIDI"; + } + + @Override + public DataType getReplacementBaseType() { + return ByteDataType.dataType; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java new file mode 100644 index 0000000000..d8c7dd33c4 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java @@ -0,0 +1,101 @@ +/* ### + * 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.program.model.data; + +import java.awt.event.MouseEvent; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MetaEventListener; +import javax.sound.midi.MetaMessage; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Sequence; +import javax.sound.midi.Sequencer; +import javax.swing.Icon; + +import generic.theme.GIcon; +import ghidra.util.Msg; +import ghidra.util.Swing; + +public class ScorePlayer implements Playable, MetaEventListener { + + private static final int END_OF_TRACK_MESSAGE = 47; + + private static final Icon AUDIO_ICON = new GIcon("icon.data.type.audio.player"); + + // This currently only allows one sequence to be played for the entire application, + // which seems good enough. The MIDI instance variables are currently synchronized + // by the Swing thread. + private static volatile Sequence currentSequence; + private static volatile Sequencer currentSequencer; + + private byte[] bytes; + + public ScorePlayer(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public Icon getImageIcon() { + return AUDIO_ICON; + } + + @Override + public void clicked(MouseEvent event) { + try { + // any new request should stop any previous sequence being played + if (currentSequence != null && currentSequencer != null) { + assert currentSequencer.isOpen(); + currentSequencer.stop(); + currentSequence = null; // this field is also updated when the sound thread calls back + currentSequencer = null; // same as above + return; + } + + Sequencer sequencer = MidiSystem.getSequencer(true); + sequencer.addMetaEventListener(this); + sequencer.setLoopCount(0); + Sequence sequence = MidiSystem.getSequence(new ByteArrayInputStream(bytes)); + if (!sequencer.isOpen()) { + sequencer.open(); + } + sequencer.setSequence(sequence); + currentSequence = sequence; + currentSequencer = sequencer; + currentSequencer.start(); + } + catch (MidiUnavailableException | InvalidMidiDataException | IOException e) { + Msg.debug(this, "Unable to play score", e); + } + } + + @Override + public void meta(MetaMessage message) { + if (message.getType() != END_OF_TRACK_MESSAGE) { + return; + } + + assert currentSequencer != null && currentSequence != null; + currentSequencer.removeMetaEventListener(this); + currentSequencer.stop(); + Swing.runNow(() -> { + currentSequence = null; + currentSequencer = null; + }); + } +} From b355f9b36415d1c1e844433b01abd2f5ea1b6be2 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:12:56 +0000 Subject: [PATCH 003/356] Test for both forward and backward slashes --- .../src/decompile/cpp/filemanage.cc | 36 ++++++++++++++----- .../src/decompile/cpp/filemanage.hh | 2 ++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/filemanage.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/filemanage.cc index aa1d06cafc..d6ead436ac 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/filemanage.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/filemanage.cc @@ -34,8 +34,10 @@ namespace ghidra { // Path name separator #ifdef _WINDOWS char FileManage::separator = '\\'; +char FileManage::separatorClass[] = "/\\"; #else char FileManage::separator = '/'; +char FileManage::separatorClass[] = "/"; #endif void FileManage::addDir2Path(const string &path) @@ -43,7 +45,7 @@ void FileManage::addDir2Path(const string &path) { if (path.size()>0) { pathlist.push_back(path); - if (path[path.size()-1] != separator) + if (!isSeparator(path[path.size()-1])) pathlist.back() += separator; } } @@ -53,7 +55,7 @@ void FileManage::findFile(string &res,const string &name) const { // Search through paths to find file with given name vector::const_iterator iter; - if (name[0] == separator) { + if (isSeparator(name[0])) { res = name; ifstream s(res.c_str()); if (s) { @@ -122,6 +124,22 @@ bool FileManage::isDirectory(const string &path) #endif +#ifdef _WINDOWS +bool FileManage::isSeparator(char c) + +{ + return (c == '/' || c == '\\'); +} + +#else +bool FileManage::isSeparator(char c) + +{ + return c == separator; +} + +#endif + #ifdef _WINDOWS void FileManage::matchListDir(vector &res,const string &match,bool isSuffix,const string &dirname,bool allowdot) @@ -131,7 +149,7 @@ void FileManage::matchListDir(vector &res,const string &match,bool isSuf string dirfinal; dirfinal = dirname; - if (dirfinal[dirfinal.size()-1] != separator) + if (!isSeparator(dirfinal[dirfinal.size()-1])) dirfinal += separator; string regex = dirfinal + '*'; @@ -162,7 +180,7 @@ void FileManage::matchListDir(vector &res,const string &match,bool isSuf DIR *dir; struct dirent *entry; string dirfinal = dirname; - if (dirfinal[dirfinal.size()-1] != separator) + if (!isSeparator(dirfinal[dirfinal.size()-1])) dirfinal += separator; dir = opendir(dirfinal.c_str()); @@ -205,7 +223,7 @@ void FileManage::directoryList(vector &res,const string &dirname,bool al WIN32_FIND_DATAA FindFileData; HANDLE hFind; string dirfinal = dirname; - if (dirfinal[dirfinal.size()-1] != separator) + if (!isSeparator(dirfinal[dirfinal.size()-1])) dirfinal += separator; string regex = dirfinal + "*"; const char *s = regex.c_str(); @@ -232,7 +250,7 @@ void FileManage::directoryList(vector &res,const string &dirname,bool al string dirfinal; dirfinal = dirname; - if (dirfinal[dirfinal.size()-1] != separator) + if (!isSeparator(dirfinal[dirfinal.size()-1])) dirfinal += separator; dir = opendir(dirfinal.c_str()); @@ -262,7 +280,7 @@ void FileManage::scanDirectoryRecursive(vector &res,const string &matchn vector::const_iterator iter; for(iter = subdir.begin();iter!=subdir.end();++iter) { const string &curpath( *iter ); - string::size_type pos = curpath.rfind(separator); + string::size_type pos = curpath.find_last_of(separatorClass); if (pos == string::npos) pos = 0; else @@ -280,9 +298,9 @@ void FileManage::splitPath(const string &full,string &path,string &base) // If there is no path, i.e. only a basename in full, then -path- will return as an empty string // otherwise -path- will be non-empty and end in a separator character string::size_type end = full.size()-1; - if (full[full.size()-1] == separator) // Take into account terminating separator + if (isSeparator(full[full.size()-1])) // Take into account terminating separator end = full.size()-2; - string::size_type pos = full.rfind(separator,end); + string::size_type pos = full.find_last_of(separatorClass,end); if (pos == string::npos) { // Didn't find any separator base = full; path.clear(); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/filemanage.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/filemanage.hh index defa54e7e3..9c2ab1b604 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/filemanage.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/filemanage.hh @@ -34,6 +34,7 @@ using std::ostringstream; class FileManage { vector pathlist; // List of paths to search for files static char separator; + static char separatorClass[]; // Characters that can be accepted as a separator static string buildPath(const vector &pathels,int level); static bool testDevelopmentPath(const vector &pathels,int level,string &root); static bool testInstallPath(const vector &pathels,int level,string &root); @@ -42,6 +43,7 @@ public: void addCurrentDir(void); void findFile(string &res,const string &name) const; // Resolve full pathname void matchList(vector &res,const string &match,bool isSuffix) const; // List of files with suffix + static bool isSeparator(char c); static bool isDirectory(const string &path); static void matchListDir(vector &res,const string &match,bool isSuffix,const string &dir,bool allowdot); static void directoryList(vector &res,const string &dirname,bool allowdot=false); From f59f9fbd14a638701a30a620028cd4b1b8605df3 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Fri, 3 May 2024 18:37:02 -0400 Subject: [PATCH 004/356] GP-0 Updated version for master to 11.2 --- Ghidra/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ghidra/application.properties b/Ghidra/application.properties index 3453f5597c..4f8b5f2ee4 100644 --- a/Ghidra/application.properties +++ b/Ghidra/application.properties @@ -1,5 +1,5 @@ application.name=Ghidra -application.version=11.1 +application.version=11.2 application.release.name=DEV application.layout.version=2 application.gradle.min=7.3 From 6e255143fb6e3a4f7846acd9027f57d7342ecb3e Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Sat, 4 May 2024 10:01:40 -0400 Subject: [PATCH 005/356] GP-3849 - Symbol Tree - Added snapshot feature --- Ghidra/Features/Base/certification.manifest | 1 - .../topics/SymbolTreePlugin/SymbolTree.htm | 42 ++- .../app/cmd/refs/SetExternalNameCmd.java | 4 +- .../actions/FindDataTypesBySizeAction.java | 23 +- .../actions/FindStructuresByOffsetAction.java | 27 +- .../actions/SecondaryTreeFilterProvider.java | 51 ++++ .../app/plugin/core/gotoquery/GoToHelper.java | 2 +- .../DisconnectedSymbolTreeProvider.java | 181 ++++++++++++ .../plugin/core/symboltree/SymbolGTree.java | 27 +- .../symboltree/SymbolTreeActionContext.java | 53 ++++ .../core/symboltree/SymbolTreePlugin.java | 64 ++++- .../core/symboltree/SymbolTreeProvider.java | 138 ++++++---- .../actions/CloneSymbolTreeAction.java | 47 ++++ .../core/symboltree/actions/RenameAction.java | 21 +- .../symboltree/actions/SelectionAction.java | 2 +- .../actions/ShowSymbolReferencesAction.java | 2 +- .../actions/SymbolTreeContextAction.java | 2 + .../symboltree/nodes/ClassCategoryNode.java | 2 +- .../nodes/ConfigurableSymbolTreeRootNode.java | 74 +++++ .../symboltree/nodes/ExportsCategoryNode.java | 7 +- .../nodes/FunctionCategoryNode.java | 2 +- .../nodes/NamespaceCategoryNode.java | 2 +- .../symboltree/nodes/SymbolCategoryNode.java | 41 ++- .../core/symboltree/nodes/SymbolTreeNode.java | 4 +- .../symboltree/nodes/SymbolTreeRootNode.java | 66 ++--- .../plugin/core/symtable/SymbolRowObject.java | 4 +- .../core/symtable/SymbolTableModel.java | 13 +- .../docking/widgets/tree/GTreeFilterTest.java | 30 +- .../symboltree/SymbolTreePlugin4Test.java | 258 +++++++++++++++++- .../core/symboltree/SymbolTreeTestUtils.java | 57 +++- .../core/functiongraph/FGActionManager.java | 2 +- .../widgets/filter/FilterTextField.java | 37 ++- .../tree/DefaultGTreeFilterProvider.java | 49 +++- .../main/java/docking/widgets/tree/GTree.java | 25 +- .../widgets/tree/GTreeFilterProvider.java | 20 +- .../java/docking/widgets/tree/GTreeNode.java | 1 - .../widgets/tree/support/GTreeRenderer.java | 6 +- .../tree/tasks/GTreeExpandPathsTask.java | 9 +- 38 files changed, 1140 insertions(+), 256 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SecondaryTreeFilterProvider.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/DisconnectedSymbolTreeProvider.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CloneSymbolTreeAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ConfigurableSymbolTreeRootNode.java diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 51ca6cb420..8238c745f2 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -775,7 +775,6 @@ src/main/resources/images/eclipse.png||GHIDRA||||END| src/main/resources/images/edit-bomb.png||Oxygen Icons - LGPL 3.0||||END| src/main/resources/images/editbytes.gif||GHIDRA||||END| src/main/resources/images/emblem-favorite.png||Tango Icons - Public Domain|||tango|END| -src/main/resources/images/empty8x16.png||GHIDRA||||END| src/main/resources/images/emptyFragment.gif||GHIDRA||||END| src/main/resources/images/emptyFragmentInView.gif||GHIDRA||||END| src/main/resources/images/enum.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm index 479c4d1115..749b0e0473 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm @@ -348,7 +348,7 @@

View Qualified Names in Code Browser

- +

To include namespace names in the display of labels and names within the Code Browser, select Edit Tool Options... from the @@ -359,6 +359,46 @@ "help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm">CodeBrowser Options).

+ + + +

Symbol Tree Snapshots

+ +
+

Pressing the action will create a copy of the current + Symbol Tree in a new window. The new disconnected Symbol Tree will not respond to program + activation events. This allows users to keep the new window open while working with various + programs, without affecting the contents. +

+ +

Symbol Tree Clone Action

+ +
+

Creates a new disconnected snapshot (cloned) view of the Symbol Tree. This action is on + the primary Symbol Tree, as well as any cloned symbol trees.

+
+ +

Disable Category

+ +
+

When working in a snapshot Symbol Tree, you can choose to disable a root-level folder + by right-clicking and selected Disable Category. Once disabled, the node will remain + in the tree with a disabled icon. A disabled node will no longer show any children. +

+
+ +

Enable Category

+ +
+

This action is used to re-enable categories that were previously disabled. +

+
+ +
+ + + +

Provided By: SymbolTreePlugin

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java index 8f7b8f3915..43055175c3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java @@ -21,8 +21,6 @@ import ghidra.util.exception.InvalidInputException; /** * Command for setting the external program name and path. - * - * */ public class SetExternalNameCmd implements Command { @@ -34,7 +32,7 @@ public class SetExternalNameCmd implements Command { /** * Constructs a new command for setting the external program name and path. * @param externalName the name of the link. - * @param externalPath the path of the file to assocate with this link. + * @param externalPath the path of the file to associate with this link. */ public SetExternalNameCmd(String externalName, String externalPath) { this.externalName = externalName; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java index 699b2c99aa..43fb93b079 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java @@ -19,8 +19,7 @@ import docking.ActionContext; import docking.action.DockingAction; import docking.action.MenuData; import docking.widgets.dialogs.NumberRangeInputDialog; -import docking.widgets.tree.*; -import docking.widgets.tree.support.CombinedGTreeFilter; +import docking.widgets.tree.GTreeNode; import docking.widgets.tree.support.GTreeFilter; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.app.plugin.core.datamgr.DataTypesProvider; @@ -64,7 +63,7 @@ public class FindDataTypesBySizeAction extends DockingAction { newProvider.setTitle(getName()); DataTypeArchiveGTree tree = newProvider.getGTree(); GTreeFilter filter = createFilter(values); - tree.setFilterProvider(new MyTreeFilterProvider(tree, filter)); + tree.setFilterProvider(new SecondaryTreeFilterProvider(tree, filter)); newProvider.setVisible(true); } @@ -72,24 +71,6 @@ public class FindDataTypesBySizeAction extends DockingAction { return new SizeGTreeFilter(values); } - private class MyTreeFilterProvider extends DefaultGTreeFilterProvider { - private GTreeFilter secondaryFilter; - - MyTreeFilterProvider(GTree tree, GTreeFilter secondaryFilter) { - super(tree); - this.secondaryFilter = secondaryFilter; - } - - @Override - public GTreeFilter getFilter() { - GTreeFilter filter = super.getFilter(); - if (filter == null) { - return secondaryFilter; - } - return new CombinedGTreeFilter(filter, secondaryFilter); - } - } - private class SizeGTreeFilter implements GTreeFilter { private final SortedRangeList sizes; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java index f3763bdf57..18181e2efd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java @@ -21,8 +21,7 @@ import docking.ActionContext; import docking.action.DockingAction; import docking.action.MenuData; import docking.widgets.dialogs.NumberRangeInputDialog; -import docking.widgets.tree.*; -import docking.widgets.tree.support.CombinedGTreeFilter; +import docking.widgets.tree.GTreeNode; import docking.widgets.tree.support.GTreeFilter; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.app.plugin.core.datamgr.DataTypesProvider; @@ -56,8 +55,7 @@ public class FindStructuresByOffsetAction extends DockingAction { @Override public void actionPerformed(ActionContext context) { - NumberRangeInputDialog inputDialog = - new NumberRangeInputDialog(NAME, "Offset(s)"); + NumberRangeInputDialog inputDialog = new NumberRangeInputDialog(NAME, "Offset(s)"); if (!inputDialog.show()) { return; } @@ -66,28 +64,11 @@ public class FindStructuresByOffsetAction extends DockingAction { DataTypesProvider newProvider = plugin.createProvider(); newProvider.setTitle(NAME); DataTypeArchiveGTree tree = newProvider.getGTree(); - tree.setFilterProvider(new MyTreeFilterProvider(tree, new OffsetGTreeFilter(values))); + tree.setFilterProvider( + new SecondaryTreeFilterProvider(tree, new OffsetGTreeFilter(values))); newProvider.setVisible(true); } - private class MyTreeFilterProvider extends DefaultGTreeFilterProvider { - private GTreeFilter secondaryFilter; - - MyTreeFilterProvider(GTree tree, GTreeFilter secondaryFilter) { - super(tree); - this.secondaryFilter = secondaryFilter; - } - - @Override - public GTreeFilter getFilter() { - GTreeFilter filter = super.getFilter(); - if (filter == null) { - return secondaryFilter; - } - return new CombinedGTreeFilter(filter, secondaryFilter); - } - } - private class OffsetGTreeFilter implements GTreeFilter { private final SortedRangeList offsets; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SecondaryTreeFilterProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SecondaryTreeFilterProvider.java new file mode 100644 index 0000000000..43969e8f74 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SecondaryTreeFilterProvider.java @@ -0,0 +1,51 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.datamgr.actions; + +import docking.widgets.tree.*; +import docking.widgets.tree.support.CombinedGTreeFilter; +import docking.widgets.tree.support.GTreeFilter; + +/** + * A filter that allows for an additional second filter. + */ +public class SecondaryTreeFilterProvider extends DefaultGTreeFilterProvider { + + private GTreeFilter secondaryFilter; + + SecondaryTreeFilterProvider(GTree tree, GTreeFilter secondaryFilter) { + super(tree); + this.secondaryFilter = secondaryFilter; + } + + @Override + public GTreeFilter getFilter() { + GTreeFilter filter = super.getFilter(); + if (filter == null) { + return secondaryFilter; + } + return new CombinedGTreeFilter(filter, secondaryFilter); + } + + @Override + public GTreeFilterProvider copy(GTree newTree) { + // For now, we shouldn't need to copy the secondary filter. It's current uses are to not + // change the filter once it has been created. + SecondaryTreeFilterProvider newProvider = + new SecondaryTreeFilterProvider(newTree, secondaryFilter); + return newProvider; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java index e5a54beb51..b5f6d074ff 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java @@ -449,7 +449,7 @@ public class GoToHelper { ExternalManager externalManager = program.getExternalManager(); String externalLibraryPath = externalManager.getExternalLibraryPath(extProgName); if (!pathName.equals(externalLibraryPath)) { - Command cmd = new SetExternalNameCmd(extProgName, domainFile.getPathname()); + Command cmd = new SetExternalNameCmd(extProgName, domainFile.getPathname()); tool.execute(cmd, program); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/DisconnectedSymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/DisconnectedSymbolTreeProvider.java new file mode 100644 index 0000000000..41e99f893e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/DisconnectedSymbolTreeProvider.java @@ -0,0 +1,181 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.symboltree; + +import javax.swing.JComponent; +import javax.swing.JPanel; + +import docking.WindowPosition; +import docking.action.KeyBindingData; +import docking.action.builder.ActionBuilder; +import ghidra.app.nav.DecoratorPanel; +import ghidra.app.plugin.core.symboltree.nodes.*; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.util.HelpLocation; + +/** + * A disconnected symbol tree is a snapshot of the primary symbol tree. + */ +public class DisconnectedSymbolTreeProvider extends SymbolTreeProvider { + + private static final String WINDOW_GROUP = "Disconnected Symbol Tree"; + + public DisconnectedSymbolTreeProvider(PluginTool tool, SymbolTreePlugin plugin, + Program program) { + super(tool, plugin); + + setDefaultWindowPosition(WindowPosition.WINDOW); + + createActions(); + + // Snapshots do not usually track events. Turn this off now, but leave the action so + // clients can turn the action on as desired. + goToToggleAction.setEnabled(false); + + setHelpLocation(new HelpLocation("SymbolTreePlugin", "Disconnected_Symbol_Tree")); + + this.program = program; + program.addListener(domainObjectListener); + + rebuildTree(); + } + + @Override + public String getWindowGroup() { + return WINDOW_GROUP; + } + + @Override + public WindowPosition getDefaultWindowPosition() { + return WindowPosition.WINDOW; + } + + @Override + public boolean isTransient() { + return true; + } + + @Override + public boolean isSnapshot() { + return true; + } + + @Override + protected void addToToolbar() { + // do not add the disconnected provider to the toolbar + } + + @Override + protected void setKeyBinding(KeyBindingData kbData) { + // no keybinding for the disconnected provider + } + + @Override + void setProgram(Program newProgram) { + // nothing to do; we maintain our state as the user changes programs + } + + @Override + void programDeactivated(Program deactivatedProgram) { + // nothing to do; we maintain our state as the user changes programs + } + + @Override + void programClosed(Program closedProgram) { + tree.cancelWork(); + + closedProgram.removeListener(domainObjectListener); + + program = null; + rebuildTree(); + + closeComponent(); + } + + @Override + protected JPanel createMainPanel(JComponent contentComponent) { + return new DecoratorPanel(contentComponent, false); + } + + @Override + protected SymbolTreeRootNode createRootNode() { + return new ConfigurableSymbolTreeRootNode(program); + } + + @Override + public void closeComponent() { + plugin.closeDisconnectedProvider(this); + } + + @Override + protected void transferSettings(DisconnectedSymbolTreeProvider newProvider) { + + // transfer disabled node settings + ConfigurableSymbolTreeRootNode myModelRoot = + (ConfigurableSymbolTreeRootNode) tree.getModelRoot(); + + ConfigurableSymbolTreeRootNode newModelRoot = + (ConfigurableSymbolTreeRootNode) newProvider.tree.getModelRoot(); + myModelRoot.transferSettings(newModelRoot); + + super.transferSettings(newProvider); + } + + @Override + void writeConfigState(SaveState saveState) { + // we have no state we are interested in saving + } + + @Override + void readConfigState(SaveState saveState) { + // we have no state we are interested in loading + } + + private void createActions() { + + //@formatter:off + new ActionBuilder("Enable Category", plugin.getName()) + .popupMenuPath("Enable Category") + .withContext(SymbolTreeActionContext.class) + .enabledWhen(c -> { + SymbolTreeNode node = c.getSelectedNode(); + return node instanceof SymbolCategoryNode; + }) + .onAction(c -> { + SymbolCategoryNode node = (SymbolCategoryNode) c.getSelectedNode(); + node.setEnabled(true); + }) + .buildAndInstallLocal(this); + //@formatter:on + + //@formatter:off + new ActionBuilder("Disable Category", plugin.getName()) + .popupMenuPath("Disable Category") + .withContext(SymbolTreeActionContext.class) + .enabledWhen(c -> { + SymbolTreeNode node = c.getSelectedNode(); + return node instanceof SymbolCategoryNode; + }) + .onAction(c -> { + SymbolCategoryNode node = (SymbolCategoryNode) c.getSelectedNode(); + node.setEnabled(false); + }) + .buildAndInstallLocal(this); + //@formatter:on + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTree.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTree.java index b23a94bcb1..6ae4991678 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTree.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTree.java @@ -21,14 +21,15 @@ import java.awt.Component; import javax.swing.*; import javax.swing.tree.TreePath; -import docking.widgets.tree.GTree; -import docking.widgets.tree.GTreeNode; +import docking.widgets.tree.*; import docking.widgets.tree.support.GTreeRenderer; import generic.theme.GIcon; +import ghidra.app.plugin.core.symboltree.nodes.SymbolCategoryNode; import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; import ghidra.app.util.SymbolInspector; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; +import resources.ResourceManager; public class SymbolGTree extends GTree { @@ -44,6 +45,14 @@ public class SymbolGTree extends GTree { setDragNDropHandler(new SymbolGTreeDragNDropHandler(plugin)); setAccessibleNamePrefix("Symbol"); + + setRootNodeAllowedToCollapse(false); + } + + // open access + @Override + protected void setFilterRestoreState(GTreeState state) { + super.setFilterRestoreState(state); } @Override @@ -95,6 +104,20 @@ public class SymbolGTree extends GTree { return label; } + + @Override + protected Icon getNodeIcon(GTreeNode node, boolean expanded) { + + Icon icon = super.getNodeIcon(node, expanded); + + if (node instanceof SymbolCategoryNode symbolNode) { + if (!symbolNode.isEnabled()) { + return ResourceManager.getDisabledIcon(icon); + } + } + + return icon; + } } public void setProgram(Program program) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java index ed009ebac2..ad004fae9e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java @@ -22,6 +22,7 @@ import javax.swing.tree.TreePath; import ghidra.app.context.ProgramSymbolActionContext; import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; +import ghidra.app.plugin.core.symboltree.nodes.SymbolTreeNode; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; @@ -54,6 +55,58 @@ public class SymbolTreeActionContext extends ProgramSymbolActionContext { return null; } + /** + * Returns a symbol tree node if there is a single node selected and it is a symbol tree node. + * Otherwise, null is returned. + * @return the selected node or null + */ + public SymbolTreeNode getSelectedNode() { + if (selectionPaths != null && selectionPaths.length == 1) { + Object object = selectionPaths[0].getLastPathComponent(); + if (object instanceof SymbolTreeNode node) { + return node; + } + } + return null; + } + + /** + * Returns true if the tree's current selection contains at least one {@link SymbolNode}. + * @return true if the tree's current selection contains at least one {@link SymbolNode}. + */ + public boolean hasSymbolsSelected() { + if (selectionPaths == null) { + return false; + } + + for (TreePath treePath : selectionPaths) { + Object object = treePath.getLastPathComponent(); + if (object instanceof SymbolNode) { + return true; + } + } + return false; + } + + /** + * Returns all selected {@link SymbolNode}s or an empty list. + * @return all selected {@link SymbolNode}s or an empty list. + */ + public List getSelectedSymbolNodes() { + if (selectionPaths == null) { + return List.of(); + } + + List symbols = new ArrayList<>(); + for (TreePath treePath : selectionPaths) { + Object object = treePath.getLastPathComponent(); + if (object instanceof SymbolNode) { + symbols.add((SymbolNode) object); + } + } + return symbols; + } + private static List getSymbols(TreePath[] selectionPaths) { if (selectionPaths == null) { return null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java index 52cb02c549..a55242bbbe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java @@ -15,6 +15,9 @@ */ package ghidra.app.plugin.core.symboltree; +import java.util.ArrayList; +import java.util.List; + import ghidra.app.CorePluginPackage; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; @@ -43,14 +46,15 @@ public class SymbolTreePlugin extends Plugin { public static final String PLUGIN_NAME = "SymbolTreePlugin"; - private SymbolTreeProvider provider; + private SymbolTreeProvider connectedProvider; + private List disconnectedProviders = new ArrayList<>(); private Program program; private GoToService goToService; private boolean processingGoTo; public SymbolTreePlugin(PluginTool tool) { super(tool); - provider = new SymbolTreeProvider(tool, this); + connectedProvider = new SymbolTreeProvider(tool, this); } @Override @@ -60,14 +64,13 @@ public class SymbolTreePlugin extends Plugin { Program oldProgram = program; program = ev.getActiveProgram(); if (oldProgram != null) { - provider.programDeactivated(oldProgram); - } - if (program != null) { - provider.programActivated(program); + connectedProvider.programDeactivated(oldProgram); } + + connectedProvider.setProgram(program); } else if (event instanceof ProgramClosedPluginEvent) { - provider.programClosed(((ProgramClosedPluginEvent) event).getProgram()); + programClosed(((ProgramClosedPluginEvent) event).getProgram()); } else if (event instanceof ProgramLocationPluginEvent) { if (processingGoTo) { @@ -75,10 +78,32 @@ public class SymbolTreePlugin extends Plugin { } ProgramLocation loc = ((ProgramLocationPluginEvent) event).getLocation(); - provider.locationChanged(loc); + connectedProvider.locationChanged(loc); + + for (SymbolTreeProvider provider : disconnectedProviders) { + provider.locationChanged(loc); + } } } + private void programClosed(Program p) { + + connectedProvider.programClosed(p); + + List copy = new ArrayList<>(disconnectedProviders); + for (SymbolTreeProvider provider : copy) { + if (provider.getProgram() == p) { + closeDisconnectedProvider(provider); + } + } + } + + void closeDisconnectedProvider(SymbolTreeProvider provider) { + disconnectedProviders.remove(provider); + tool.removeComponentProvider(provider); + provider.dispose(); + } + @Override protected void init() { goToService = tool.getService(GoToService.class); @@ -86,19 +111,24 @@ public class SymbolTreePlugin extends Plugin { @Override protected void dispose() { - tool.removeComponentProvider(provider); - provider.dispose(); + tool.removeComponentProvider(connectedProvider); + connectedProvider.dispose(); program = null; + + List copy = new ArrayList<>(disconnectedProviders); + for (SymbolTreeProvider provider : copy) { + closeDisconnectedProvider(provider); + } } @Override public void readConfigState(SaveState saveState) { - provider.readConfigState(saveState); + connectedProvider.readConfigState(saveState); } @Override public void writeConfigState(SaveState saveState) { - provider.writeConfigState(saveState); + connectedProvider.writeConfigState(saveState); } public void goTo(Symbol symbol) { @@ -145,6 +175,14 @@ public class SymbolTreePlugin extends Plugin { } SymbolTreeProvider getProvider() { - return provider; + return connectedProvider; + } + + public DisconnectedSymbolTreeProvider createNewDisconnectedProvider(Program p) { + DisconnectedSymbolTreeProvider newProvider = + new DisconnectedSymbolTreeProvider(tool, this, p); + disconnectedProviders.add(newProvider); + tool.showComponentProvider(newProvider, true); + return newProvider; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java index a4a9b530f5..2175ed71d2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java @@ -60,15 +60,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { private ClipboardOwner clipboardOwner; private Clipboard localClipboard;// temporary clipboard used for the "cut" operation - private DomainObjectListener domainObjectListener; - private Program program; + protected DomainObjectListener domainObjectListener; + protected Program program; - private final SymbolTreePlugin plugin; - private SymbolGTree tree; - private JPanel mainPanel; - private JComponent component; + protected SymbolTreePlugin plugin; + protected SymbolGTree tree; + protected JPanel mainPanel; + protected JComponent component; - private GoToToggleAction goToToggleAction; + protected GoToToggleAction goToToggleAction; /** * A list into which tasks to be run will accumulated until we put them into the GTree's @@ -108,6 +108,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { super(tool, NAME, plugin.getName()); this.plugin = plugin; + setWindowMenuGroup(NAME); + setIcon(ICON); addToToolbar(); @@ -127,16 +129,27 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { // Setup Methods //================================================================================================== - private JComponent buildProvider() { - mainPanel = new JPanel(new BorderLayout()); + protected JPanel createMainPanel(JComponent contentComponent) { + JPanel panel = new JPanel(new BorderLayout()); - tree = createTree(new SymbolTreeRootNode()); - mainPanel.add(tree, BorderLayout.CENTER); + panel.add(contentComponent, BorderLayout.CENTER); + + return panel; + } + + protected SymbolTreeRootNode createRootNode() { + return new SymbolTreeRootNode(program); + } + + private JComponent buildProvider() { + + tree = createTree(createRootNode()); // There's no reason to see the root node in this window. The name (GLOBAL) is // unimportant and the tree is never collapsed at this level. tree.setRootVisible(false); + mainPanel = createMainPanel(tree); return mainPanel; } @@ -219,8 +232,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { SymbolNode node = (SymbolNode) object; Symbol symbol = node.getSymbol(); SymbolType type = symbol.getSymbolType(); - if (!type.isNamespace() || - type == SymbolType.FUNCTION) { + if (!type.isNamespace() || type == SymbolType.FUNCTION) { plugin.goTo(symbol); } } @@ -247,8 +259,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { deleteAction.setEnabled(false); DockingAction referencesAction = - new ShowSymbolReferencesAction(plugin.getTool(), - plugin.getName()); + new ShowSymbolReferencesAction(plugin.getTool(), plugin.getName()); DockingAction selectionAction = new SelectionAction(plugin); selectionAction.setEnabled(false); @@ -257,6 +268,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { DockingAction goToExternalAction = new GoToExternalLocationAction(plugin); goToExternalAction.setEnabled(false); + CloneSymbolTreeAction cloneAction = new CloneSymbolTreeAction(plugin, this); + tool.addLocalAction(this, createImportAction); tool.addLocalAction(this, setExternalProgramAction); tool.addLocalAction(this, createExternalLocationAction); @@ -272,6 +285,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { tool.addLocalAction(this, goToToggleAction); tool.addLocalAction(this, selectionAction); tool.addLocalAction(this, goToExternalAction); + tool.addLocalAction(this, cloneAction); } //================================================================================================== @@ -300,24 +314,56 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { // Class Methods //================================================================================================== - void programActivated(Program openedProgram) { - this.program = openedProgram; - if (tool.isVisible(this)) { - setProgram(openedProgram); - } + GTree getTree() { + return tree; } - private void setProgram(Program program) { + public void cloneWindow() { + + DisconnectedSymbolTreeProvider newProvider = plugin.createNewDisconnectedProvider(program); + + Swing.runLater(() -> { + newProvider.setProgram(program); + transferSettings(newProvider); + }); + } + + /** + * Called to have this symbol tree provider copy settings into the given provider. + * @param newProvider the new provider + */ + protected void transferSettings(DisconnectedSymbolTreeProvider newProvider) { + // + // Unusual Code: We want to copy the current tree state to the new tree. Since we are + // also applying the filter state below, the tree will use the 'filter restore state' + // after the filter has been applied. Thus, we need to set the filter restore state + // instead of using the GTree's restoreTreeState() method. + // + GTreeState treeState = tree.getTreeState(); + newProvider.tree.setFilterRestoreState(treeState); + + GTreeFilterProvider filterProvider = tree.getFilterProvider(); + GTreeFilterProvider newFilterProvider = filterProvider.copy(newProvider.tree); + newProvider.tree.setFilterProvider(newFilterProvider); + } + + public Program getProgram() { + return program; + } + + void setProgram(Program program) { + this.program = program; + if (!isVisible()) { + return; + } + if (program == null) { return; } program.addListener(domainObjectListener); - mainPanel.remove(tree); - tree = createTree(new SymbolTreeRootNode(program)); - mainPanel.add(tree); - component.validate(); + rebuildTree(); // restore any state that may be saved GTreeState treeState = treeStateMap.get(program); @@ -335,11 +381,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { GTreeState treeState = tree.getTreeState(); treeStateMap.put(program, treeState); + rebuildTree(); + this.program = null; + } + + protected void rebuildTree() { mainPanel.remove(tree); - tree = createTree(new SymbolTreeRootNode()); + tree = createTree(createRootNode()); mainPanel.add(tree); component.validate(); - this.program = null; } void programClosed(Program closedProgram) { @@ -371,14 +421,11 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { } catch (DuplicateNameException e) { sb.append("Parent namespace " + namespace.getName() + - " contains namespace named " + symbol.getName() + - "\n"); + " contains namespace named " + symbol.getName() + "\n"); } catch (InvalidInputException | CircularDependencyException e) { - sb.append("Could not change parent namespace for " + symbol.getName() + - ": " + - e.getMessage() + - "\n"); + sb.append("Could not change parent namespace for " + symbol.getName() + ": " + + e.getMessage() + "\n"); } } } @@ -405,8 +452,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { return true; } // the symbol to move does not allow dups, so make sure all existing symbols do allow dups. - List symbols = symbolTable.getSymbols(symbol.getName(), - destinationNamespace); + List symbols = symbolTable.getSymbols(symbol.getName(), destinationNamespace); for (Symbol s : symbols) { if (!s.getSymbolType().allowsDuplicates()) { return false; @@ -421,7 +467,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { (symbolType == SymbolType.NAMESPACE) || (symbolType == SymbolType.CLASS); } - private void rebuildTree() { + private void reloadTree() { // If we do not cancel the edit here, then an open edits will instead be committed. It // seems safer to cancel an edit rather than to commit it without asking. @@ -458,18 +504,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { domainChangeUpdateManager.update(); } - void showComponent(Program currentProgram) { - if (!tool.isVisible(this)) { - setProgram(currentProgram); - } - tool.showComponentProvider(this, true); - } - public void locationChanged(ProgramLocation loc) { if (!goToToggleAction.isSelected()) { return; } + if (program != loc.getProgram()) { + return; + } + Symbol symbol = null; Address addr = loc.getAddress(); if (loc instanceof VariableLocation) { @@ -547,7 +590,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { // @formatter:off return new DomainObjectListenerBuilder(this) .ignoreWhen(this::ignoreEvents) - .any(RESTORED).terminate(this::rebuildTree) + .any(RESTORED).terminate(this::reloadTree) .with(ProgramChangeRecord.class) .each(SYMBOL_RENAMED).call(this::processSymbolRenamed) .each(SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED).call(this::processSymbolChanged) @@ -665,8 +708,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { @Override public String toString() { - return getClass().getSimpleName() + - " " + symbol; + return getClass().getSimpleName() + " " + symbol; } } @@ -721,7 +763,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { @Override void doRun(TaskMonitor monitor) throws CancelledException { SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot(); - root.symbolRemoved(symbol, monitor); + root.symbolRemoved(symbol, symbol.getName(), monitor); tree.refilterLater(); } } @@ -743,7 +785,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { public void runBulk(TaskMonitor monitor) throws CancelledException { if (tasks.size() > MAX_TASK_COUNT) { - Swing.runLater(() -> rebuildTree()); + Swing.runLater(() -> reloadTree()); return; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CloneSymbolTreeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CloneSymbolTreeAction.java new file mode 100644 index 0000000000..e87fa3ed5a --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CloneSymbolTreeAction.java @@ -0,0 +1,47 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.symboltree.actions; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.ToolBarData; +import generic.theme.GIcon; +import ghidra.app.plugin.core.symboltree.SymbolTreePlugin; +import ghidra.app.plugin.core.symboltree.SymbolTreeProvider; + +public class CloneSymbolTreeAction extends DockingAction { + + private SymbolTreeProvider provider; + + public CloneSymbolTreeAction(SymbolTreePlugin plugin, SymbolTreeProvider provider) { + super("Symbol Tree Clone", plugin.getName()); + this.provider = provider; + + setToolBarData(new ToolBarData(new GIcon("icon.provider.clone"))); + setDescription("Create a snapshot (disconnected) copy of this Symbol Tree window"); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return provider.getProgram() != null; + } + + @Override + public void actionPerformed(ActionContext context) { + provider.cloneWindow(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java index fd142c52a3..21adb37ee1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java @@ -15,37 +15,28 @@ */ package ghidra.app.plugin.core.symboltree.actions; -import javax.swing.tree.TreePath; - import docking.action.MenuData; -import docking.widgets.tree.GTreeNode; import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext; import ghidra.app.plugin.core.symboltree.SymbolTreePlugin; -import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; +import ghidra.app.plugin.core.symboltree.nodes.SymbolTreeNode; public class RenameAction extends SymbolTreeContextAction { public RenameAction(SymbolTreePlugin plugin) { super("Rename Symbol", plugin.getName()); - setPopupMenuData(new MenuData(new String[] { "Rename" }, null, "xxx", MenuData.NO_MNEMONIC, - "1")); + setPopupMenuData( + new MenuData(new String[] { "Rename" }, null, "xxx", MenuData.NO_MNEMONIC, "1")); } @Override public boolean isEnabledForContext(SymbolTreeActionContext context) { - TreePath[] selectionPaths = context.getSelectedSymbolTreePaths(); - if (selectionPaths.length == 1) { - Object object = selectionPaths[0].getLastPathComponent(); - return (object instanceof SymbolNode); - } - return false; + SymbolTreeNode node = context.getSelectedNode(); + return node != null; } @Override public void actionPerformed(SymbolTreeActionContext context) { - TreePath[] selectionPaths = context.getSelectedSymbolTreePaths(); - GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent(); - context.getSymbolTree().startEditing(node); + context.getSymbolTree().startEditing(context.getSelectedNode()); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java index 15a031df59..2f6a7a8f43 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java @@ -34,7 +34,7 @@ public class SelectionAction extends SymbolTreeContextAction { public SelectionAction(Plugin plugin) { super("Make Selection", plugin.getName()); this.plugin = plugin; - setPopupMenuData(new MenuData(new String[] { "Make Selection" }, "0Middle")); + setPopupMenuData(new MenuData(new String[] { "Make Selection" }, MIDDLE_MENU_GROUP)); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java index 8154b12910..09aa3dad0c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java @@ -59,7 +59,7 @@ public class ShowSymbolReferencesAction extends SymbolTreeContextAction { super(AbstractFindReferencesDataTypeAction.NAME, owner, KeyBindingType.SHARED); this.tool = tool; - setPopupMenuData(new MenuData(new String[] { "Show References to" }, "0Middle")); + setPopupMenuData(new MenuData(new String[] { "Show References to" }, MIDDLE_MENU_GROUP)); installHelpLocation(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java index 4cd532a4c4..0c21d71047 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java @@ -24,6 +24,8 @@ import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext; public abstract class SymbolTreeContextAction extends DockingAction { + protected static final String MIDDLE_MENU_GROUP = "0Middle"; + public SymbolTreeContextAction(String name, String owner) { super(name, owner); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ClassCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ClassCategoryNode.java index 2803fa7b88..708b50d3e0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ClassCategoryNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ClassCategoryNode.java @@ -36,7 +36,7 @@ public class ClassCategoryNode extends SymbolCategoryNode { public static final Icon CLOSED_FOLDER_CLASSES_ICON = new GIcon("icon.plugin.symboltree.node.category.classes.closed"); - ClassCategoryNode(Program program) { + public ClassCategoryNode(Program program) { super(SymbolCategory.CLASS_CATEGORY, program); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ConfigurableSymbolTreeRootNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ConfigurableSymbolTreeRootNode.java new file mode 100644 index 0000000000..529c253e8c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ConfigurableSymbolTreeRootNode.java @@ -0,0 +1,74 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.symboltree.nodes; + +import java.util.List; + +import docking.widgets.tree.GTree; +import docking.widgets.tree.GTreeNode; +import ghidra.app.plugin.core.symboltree.DisconnectedSymbolTreeProvider; +import ghidra.program.model.listing.Program; + +/** + * A version of the Symbol Tree's root node that allows users to disable categories. The categories + * themselves track their enabled state. This class supports the cloning of a + * {@link DisconnectedSymbolTreeProvider} by copying the categories' enable state. + */ +public class ConfigurableSymbolTreeRootNode extends SymbolTreeRootNode { + + public ConfigurableSymbolTreeRootNode(Program program) { + super(program); + } + + public void transferSettings(ConfigurableSymbolTreeRootNode otherRoot) { + + if (!isLoaded()) { + return; + } + + List myChildren = getChildren(); + List otherChildren = otherRoot.getChildren(); + for (GTreeNode node : myChildren) { + SymbolCategoryNode myCategoryNode = getModelNode((SymbolCategoryNode) node); + SymbolCategoryNode otherCategoryNode = getMatchingNode(otherChildren, myCategoryNode); + otherCategoryNode.setEnabled(myCategoryNode.isEnabled()); + } + } + + private SymbolCategoryNode getMatchingNode(List nodes, + SymbolCategoryNode nodeToMatch) { + + for (GTreeNode node : nodes) { + if (nodeToMatch.equals(node)) { + return getModelNode((SymbolCategoryNode) node); + } + } + + return null; + } + + private SymbolCategoryNode getModelNode(SymbolCategoryNode node) { + GTree gTree = node.getTree(); + if (gTree != null) { + SymbolCategoryNode modelNode = (SymbolCategoryNode) gTree.getModelNode(node); + if (node != modelNode) { + return modelNode; + } + } + return node; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ExportsCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ExportsCategoryNode.java index 656d64d0cf..31eaaa34ce 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ExportsCategoryNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ExportsCategoryNode.java @@ -33,14 +33,17 @@ class ExportsCategoryNode extends SymbolCategoryNode { private static final Icon CLOSED_FOLDER = new GIcon("icon.plugin.symboltree.node.category.exports.closed"); - ExportsCategoryNode(Program program) { + public ExportsCategoryNode(Program program) { super(SymbolCategory.EXPORTS_CATEGORY, program); } @Override public List generateChildren(TaskMonitor monitor) { - List list = new ArrayList<>(); + if (!isEnabled) { + return Collections.emptyList(); + } + List list = new ArrayList<>(); List functionSymbolList = getExportSymbols(); for (Symbol symbol : functionSymbolList) { list.add(SymbolNode.createNode(symbol, program)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/FunctionCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/FunctionCategoryNode.java index 336dba25c6..e013a176ac 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/FunctionCategoryNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/FunctionCategoryNode.java @@ -36,7 +36,7 @@ class FunctionCategoryNode extends SymbolCategoryNode { public static final Icon CLOSED_FOLDER_FUNCTIONS_ICON = new GIcon("icon.plugin.symboltree.node.category.function.closed"); - FunctionCategoryNode(Program program) { + public FunctionCategoryNode(Program program) { super(SymbolCategory.FUNCTION_CATEGORY, program); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/NamespaceCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/NamespaceCategoryNode.java index 3a6259efd5..f0527dbdb8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/NamespaceCategoryNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/NamespaceCategoryNode.java @@ -32,7 +32,7 @@ public class NamespaceCategoryNode extends SymbolCategoryNode { public static final Icon CLOSED_FOLDER_NAMESPACES_ICON = new GIcon("icon.plugin.symboltree.node.category.namespace.closed"); - NamespaceCategoryNode(Program program) { + public NamespaceCategoryNode(Program program) { super(SymbolCategory.NAMESPACE_CATEGORY, program); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolCategoryNode.java index d44db795c5..62b63affdd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolCategoryNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolCategoryNode.java @@ -37,23 +37,42 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode { protected GlobalNamespace globalNamespace; protected Program program; - // dummy constructor for no program - protected SymbolCategoryNode() { - symbolCategory = null; - symbolTable = null; - globalNamespace = null; - program = null; + protected boolean isEnabled = true; + + public SymbolCategoryNode(SymbolCategory symbolCategory, Program p) { + this.symbolCategory = symbolCategory; + this.program = p; + this.symbolTable = p == null ? null : p.getSymbolTable(); + this.globalNamespace = p == null ? null : (GlobalNamespace) p.getGlobalNamespace(); } - public SymbolCategoryNode(SymbolCategory symbolCategory, Program program) { - this.symbolCategory = symbolCategory; - this.program = program; - this.symbolTable = program.getSymbolTable(); - this.globalNamespace = (GlobalNamespace) program.getGlobalNamespace(); + public void setEnabled(boolean enabled) { + if (isEnabled == enabled) { + return; + } + + isEnabled = enabled; + unloadChildren(); + + GTree gTree = getTree(); + if (gTree != null) { + SymbolCategoryNode modelNode = (SymbolCategoryNode) gTree.getModelNode(this); + if (this != modelNode) { + modelNode.setEnabled(enabled); + } + } + } + + public boolean isEnabled() { + return isEnabled; } @Override public List generateChildren(TaskMonitor monitor) throws CancelledException { + if (!isEnabled) { + return Collections.emptyList(); + } + SymbolType symbolType = symbolCategory.getSymbolType(); List list = getSymbols(symbolType, monitor); monitor.checkCancelled(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeNode.java index d92c3b5695..6e103ebbd7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeNode.java @@ -97,6 +97,7 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode { /** * Returns true if this nodes handles paste operations + * @param pastedNodes the nodes to be pasted * @return true if this nodes handles paste operations */ public abstract boolean canPaste(List pastedNodes); @@ -172,8 +173,7 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode { * @param monitor the task monitor * @return the node that contains the given symbol. */ - public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, - TaskMonitor monitor) { + public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) { // if we don't have to loadChildren and we are not loaded get out. if (!loadChildren && !isLoaded()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeRootNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeRootNode.java index 245d5520b5..aaadc294b0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeRootNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeRootNode.java @@ -17,7 +17,6 @@ package ghidra.app.plugin.core.symboltree.nodes; import static ghidra.program.model.symbol.SymbolType.*; -import java.awt.datatransfer.DataFlavor; import java.util.*; import javax.swing.Icon; @@ -30,21 +29,31 @@ import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.SymbolType; import ghidra.util.task.TaskMonitor; -public class SymbolTreeRootNode extends SymbolCategoryNode { +public class SymbolTreeRootNode extends GTreeNode { private static Icon GLOBAL_ICON = new GIcon("icon.plugin.symboltree.node.root"); private final String name; - public SymbolTreeRootNode() { - name = "No Symbol Tree"; - } + protected SymbolCategory symbolCategory; + protected Program program; public SymbolTreeRootNode(Program program) { - super(SymbolCategory.ROOT_CATEGORY, program); - name = "Global"; + this.symbolCategory = SymbolCategory.ROOT_CATEGORY; + this.program = program; + + if (program == null) { + name = "No Symbol Tree"; + } + else { + name = "Global"; + } + } + + public Program getProgram() { + return program; } @Override - public List generateChildren(TaskMonitor monitor) { + public List generateChildren() { if (program == null) { return Collections.emptyList(); } @@ -61,7 +70,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode { return list; } - @Override public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) { // @@ -93,7 +101,7 @@ public class SymbolTreeRootNode extends SymbolCategoryNode { } //else { GLOBAL, GLOBAL_VAR } // not sure where these end up - return super.findSymbolTreeNode(key, loadChildren, monitor); + return null; } private GTreeNode findCodeSymbol(SymbolNode key, boolean loadChildren, TaskMonitor monitor) { @@ -230,7 +238,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode { return null; // must be filtered out } - @Override public SymbolNode symbolAdded(Symbol symbol) { SymbolNode returnNode = null; List allChildren = getChildren(); @@ -244,7 +251,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode { return returnNode; } - @Override public void symbolRemoved(Symbol symbol, String oldName, TaskMonitor monitor) { // we have to loop--the symbol may exist in more than one category @@ -280,32 +286,14 @@ public class SymbolTreeRootNode extends SymbolCategoryNode { } @Override - public boolean canCut() { - return false; - } - - @Override - public boolean canPaste(List pastedNodes) { - return false; - } - - @Override - public DataFlavor getNodeDataFlavor() { - return null; - } - - @Override - public boolean isCut() { - return false; - } - - @Override - public boolean isModifiable() { - return false; - } - - @Override - public void setNodeCut(boolean isCut) { - throw new UnsupportedOperationException("Cannot cut the symbol tree root node"); + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SymbolTreeRootNode)) { + return false; + } + SymbolTreeRootNode node = (SymbolTreeRootNode) o; + return getName().equals(node.getName()); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java index c1b2c279cf..75e14e3f9a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java @@ -22,7 +22,7 @@ import ghidra.program.model.symbol.Symbol; /** * SymbolRowObject provides a lightweight {@link Symbol} - * table row object which may be used to reacquire an associated symbol. + * table row object which may be used to acquire an associated symbol. */ public class SymbolRowObject implements Comparable { @@ -50,7 +50,7 @@ public class SymbolRowObject implements Comparable { } /** - * Get symbol id used to reacquire symbol from program + * Get symbol id used to acquire symbol from program * @return symbol id */ public long getID() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java index 1554f0bdad..02d3dca934 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java @@ -278,27 +278,26 @@ class SymbolTableModel extends AddressBasedTableModel { tool.setStatusInfo(""); List deleteList = new LinkedList<>(); - CompoundCmd cmd = new CompoundCmd("Delete symbol(s)"); + CompoundCmd cmd = new CompoundCmd<>("Delete symbol(s)"); for (Symbol symbol : rowObjects) { if (symbol.isDynamic()) { - continue;//can't delete dynamic symbols... + continue; // can't delete dynamic symbols... } deleteList.add(symbol); String label = symbol.getName(); + Address address = symbol.getAddress(); if (symbol.getSymbolType() == SymbolType.FUNCTION) { Function function = (Function) symbol.getObject(); boolean ignoreMissingFunction = function.isThunk(); - cmd.add(new DeleteFunctionCmd(symbol.getAddress(), ignoreMissingFunction)); + cmd.add(new DeleteFunctionCmd(address, ignoreMissingFunction)); if (symbol.getSource() != SourceType.DEFAULT) { // remove label which gets created when non-default function is removed - cmd.add(new DeleteLabelCmd(symbol.getAddress(), label, - symbol.getParentNamespace())); + cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace())); } } else { - cmd.add( - new DeleteLabelCmd(symbol.getAddress(), label, symbol.getParentNamespace())); + cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace())); } } if (cmd.size() == 0) { diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java index c66e399158..48611f1352 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java @@ -62,8 +62,7 @@ public class GTreeFilterTest extends AbstractDockingTest { assertEquals(5, viewRoot().getChildCount()); setFilterText("ABC"); - assertEquals("Expected 4 of nodes to be in filtered tree!", 4, - viewRoot().getChildCount()); + assertEquals("Expected 4 of nodes to be in filtered tree!", 4, viewRoot().getChildCount()); checkContainsNode("ABC"); checkContainsNode("XABC"); @@ -441,13 +440,13 @@ public class GTreeFilterTest extends AbstractDockingTest { assertEquals(1, viewRoot().getChildCount()); Object originalValue = getInstanceField("uniquePreferenceKey", gTree); - setInstanceField("preferenceKey", gTree.getFilterProvider(), "XYZ"); + setInstanceField("uniquePreferenceKey", gTree, "XYZ"); setFilterOptions(TextFilterStrategy.STARTS_WITH, false); checkContainsNode("ABC"); checkContainsNode("ABCX"); assertEquals(2, viewRoot().getChildCount()); - setInstanceField("preferenceKey", gTree.getFilterProvider(), originalValue); + setInstanceField("uniquePreferenceKey", gTree, originalValue); setInstanceField("optionsSet", gTree.getFilterProvider(), false); restorePreferences(); checkContainsNode("ABC"); @@ -588,11 +587,11 @@ public class GTreeFilterTest extends AbstractDockingTest { private void setFilterOnPath(boolean usePath) { runSwing(() -> { - FilterOptions filterOptions = new FilterOptions(TextFilterStrategy.CONTAINS, - true, false, false, usePath, false, FilterOptions.DEFAULT_DELIMITER, - MultitermEvaluationMode.AND); - ((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions( - filterOptions); + FilterOptions filterOptions = + new FilterOptions(TextFilterStrategy.CONTAINS, true, false, false, usePath, false, + FilterOptions.DEFAULT_DELIMITER, MultitermEvaluationMode.AND); + ((DefaultGTreeFilterProvider) gTree.getFilterProvider()) + .setFilterOptions(filterOptions); }); waitForTree(); } @@ -600,9 +599,8 @@ public class GTreeFilterTest extends AbstractDockingTest { private void restorePreferences() { runSwing(() -> { GTreeFilterProvider filterProvider = gTree.getFilterProvider(); - String key = (String) getInstanceField("uniquePreferenceKey", gTree); - Class[] classes = new Class[] { DockingWindowManager.class, String.class }; - Object[] objs = new Object[] { winMgr, key }; + Class[] classes = new Class[] { DockingWindowManager.class }; + Object[] objs = new Object[] { winMgr }; invokeInstanceMethod("loadFilterPreference", filterProvider, classes, objs); }); waitForTree(); @@ -639,8 +637,8 @@ public class GTreeFilterTest extends AbstractDockingTest { runSwing(() -> { FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted); - ((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions( - filterOptions); + ((DefaultGTreeFilterProvider) gTree.getFilterProvider()) + .setFilterOptions(filterOptions); }); waitForTree(); } @@ -650,8 +648,8 @@ public class GTreeFilterTest extends AbstractDockingTest { runSwing(() -> { FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted, false, multiTerm, splitCharacter, evalMode); - ((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions( - filterOptions); + ((DefaultGTreeFilterProvider) gTree.getFilterProvider()) + .setFilterOptions(filterOptions); }); waitForTree(); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin4Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin4Test.java index 99a0cf8942..a2324c24aa 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin4Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin4Test.java @@ -17,10 +17,17 @@ package ghidra.app.plugin.core.symboltree; import static org.junit.Assert.*; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.tree.TreePath; + import org.junit.*; import docking.action.DockingActionIf; -import docking.widgets.tree.GTreeNode; +import docking.widgets.filter.*; +import docking.widgets.tree.*; +import docking.widgets.tree.support.DepthFirstIterator; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; import ghidra.program.model.address.Address; @@ -259,10 +266,259 @@ public class SymbolTreePlugin4Test extends AbstractGhidraHeadedIntegrationTest { addr("0x1002cf9"), location.getAddress()); } + @Test + public void testClone() throws Exception { + + GTreeNode fNode = rootNode.getChild(2); + util.expandNode(fNode); + + GTreeNode gNode = fNode.getChild(1); + util.expandNode(gNode); + util.selectNode(gNode); + + DockingActionIf clone = getAction(plugin, "Symbol Tree Clone"); + performAction(clone); + + DisconnectedSymbolTreeProvider disconnectedProvider = + waitForComponentProvider(DisconnectedSymbolTreeProvider.class); + + GTree gTree = disconnectedProvider.getTree(); + TreePath selectedPath = gTree.getSelectionPath(); + GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent(); + assertEquals(gNode, selectedNode); + } + + @Test + public void testClone_WithFilter() throws Exception { + + GTreeNode fNode = rootNode.getChild(2); + util.expandNode(fNode); + + GTreeNode gNode = fNode.getChild(1); + util.expandNode(gNode); + util.selectNode(gNode); + + SymbolGTree gTree = util.getTree(); + filter(gTree, "param_1"); + + assertEquals(6, countNodes(gTree)); + assertNodes(gTree, "ghidra", "doStuff"); + + DockingActionIf clone = getAction(plugin, "Symbol Tree Clone"); + performAction(clone); + + DisconnectedSymbolTreeProvider disconnectedProvider = + waitForComponentProvider(DisconnectedSymbolTreeProvider.class); + + GTree disconnectedGTree = disconnectedProvider.getTree(); + waitForTree(disconnectedGTree); + + TreePath selectedPath = disconnectedGTree.getSelectionPath(); + GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent(); + assertEquals(gNode, selectedNode); + + assertFilterText(disconnectedGTree, "param_1"); + assertEquals(6, countNodes(disconnectedGTree)); + assertNodes(disconnectedGTree, "ghidra", "doStuff"); + } + + @Test + public void testClone_WithFilter_ChangedSettings() throws Exception { + + SymbolGTree gTree = util.getTree(); + setFilterOptions(gTree, TextFilterStrategy.MATCHES_EXACTLY, false); + filter(gTree, "param_1"); + + GTreeNode fNode = rootNode.getChild(2); + util.expandNode(fNode); + + GTreeNode gNode = fNode.getChild(1); + util.expandNode(gNode); + util.selectNode(gNode); + + assertEquals(6, countNodes(gTree)); + assertNodes(gTree, "ghidra", "doStuff"); + + DockingActionIf clone = getAction(plugin, "Symbol Tree Clone"); + performAction(clone); + + DisconnectedSymbolTreeProvider disconnectedProvider = + waitForComponentProvider(DisconnectedSymbolTreeProvider.class); + + GTree disconnectedGTree = disconnectedProvider.getTree(); + waitForTree(disconnectedGTree); + + TreePath selectedPath = disconnectedGTree.getSelectionPath(); + GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent(); + assertEquals(gNode, selectedNode); + + assertFilterText(disconnectedGTree, "param_1"); + assertFilterSetting(disconnectedGTree, TextFilterStrategy.MATCHES_EXACTLY); + assertEquals(6, countNodes(disconnectedGTree)); + assertNodes(disconnectedGTree, "ghidra", "doStuff"); + } + + @Test + public void testClone_IgnoresProgramActivation() throws Exception { + + GTreeNode fNode = rootNode.getChild(2); + util.expandNode(fNode); + + GTreeNode gNode = fNode.getChild(1); + util.expandNode(gNode); + util.selectNode(gNode); + + DockingActionIf clone = getAction(plugin, "Symbol Tree Clone"); + performAction(clone); + + DisconnectedSymbolTreeProvider disconnectedProvider = + waitForComponentProvider(DisconnectedSymbolTreeProvider.class); + + GTree gTree = disconnectedProvider.getTree(); + TreePath selectedPath = gTree.getSelectionPath(); + GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent(); + assertEquals(gNode, selectedNode); + + SymbolTreeProvider primaryProvider = util.getProvider(); + assertEquals(program, primaryProvider.getProgram()); + assertEquals(program, disconnectedProvider.getProgram()); + + Program program2 = util.openProgram2(); + + assertEquals(program2, primaryProvider.getProgram()); + assertEquals(program, disconnectedProvider.getProgram()); + } + + @Test + public void testClone_ClosingProgramClosesClonedProvider() throws Exception { + + DockingActionIf clone = getAction(plugin, "Symbol Tree Clone"); + performAction(clone); + + DisconnectedSymbolTreeProvider disconnectedProvider = + waitForComponentProvider(DisconnectedSymbolTreeProvider.class); + + SymbolTreeProvider primaryProvider = util.getProvider(); + assertEquals(program, primaryProvider.getProgram()); + assertEquals(program, disconnectedProvider.getProgram()); + + Program program2 = util.openProgram2(); + + assertEquals(program2, primaryProvider.getProgram()); + assertEquals(program, disconnectedProvider.getProgram()); + + util.closeProgram(); + + assertTrue(primaryProvider.isVisible()); + assertFalse(disconnectedProvider.isVisible()); + + assertEquals(program2, primaryProvider.getProgram()); + assertNull(disconnectedProvider.getProgram()); + } + //================================================================================================== // Private Methods //================================================================================================== + private void setFilterOptions(GTree gTree, TextFilterStrategy filterStrategy, + boolean inverted) { + + runSwing(() -> { + FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted); + ((DefaultGTreeFilterProvider) gTree.getFilterProvider()) + .setFilterOptions(filterOptions); + }); + waitForTree(gTree); + } + + private void assertFilterSetting(GTree gTree, TextFilterStrategy expectedStrategy) { + + FilterOptions filterOptions = runSwing(() -> { + DefaultGTreeFilterProvider provider = + ((DefaultGTreeFilterProvider) gTree.getFilterProvider()); + return provider.getFilterOptions(); + }); + + assertEquals(expectedStrategy, filterOptions.getTextFilterStrategy()); + } + + private int countNodes(GTree gTree) { + int n = 0; + DepthFirstIterator it = new DepthFirstIterator(gTree.getViewRoot()); + while (it.hasNext()) { + n++; + it.next(); + } + return n; + } + + private void filter(GTree gTree, String text) { + + FilterTextField filterField = (FilterTextField) gTree.getFilterField(); + runSwing(() -> { + filterField.setText(text); + }); + waitForTree(gTree); + } + + private void assertFilterText(GTree gTree, String expectedText) { + FilterTextField filterField = (FilterTextField) gTree.getFilterField(); + String filterText = runSwing(() -> filterField.getText()); + assertEquals(expectedText, filterText); + } + + private void assertNodes(GTree gTree, String... nodeNames) { + + List nodes = new ArrayList<>(); + for (String name : nodeNames) { + GTreeNode node = node(name); + assertNotNull(node); + nodes.add(node); + } + + int count = 0; + int rows = gTree.getRowCount(); + for (int i = 0; i < rows; i++) { + TreePath path = gTree.getPathForRow(i); + GTreeNode node = (GTreeNode) path.getLastPathComponent(); + if (node.isLeaf()) { + if (node.isLeaf()) { + count++; + } + } + } + + assertEquals(nodes.size(), count); + for (GTreeNode node : nodes) { + TreePath path = node.getTreePath(); + assertTrue("Could not find row for path: " + path, gTree.getRowForPath(path) != -1); + } + } + + private GTreeNode node(String name) { + return findNodeInTree(rootNode, name); + } + + private GTreeNode findNodeInTree(GTreeNode node, String name) { + if (node.getName().equals(name)) { + return node; + } + + List children = node.getChildren(); + for (GTreeNode child : children) { + if (child.getName().startsWith(name)) { + return child; + } + + GTreeNode grandChild = findNodeInTree(child, name); + if (grandChild != null) { + return grandChild; + } + } + + return null; + } + private Address addr(String address) { return program.getAddressFactory().getAddress(address); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java index f903b985a8..7f2a135198 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.symboltree; import static generic.test.AbstractGTest.*; import static generic.test.AbstractGenericTest.*; +import static generic.test.AbstractGuiTest.*; import static ghidra.test.AbstractGhidraHeadedIntegrationTest.*; import static org.junit.Assert.*; @@ -102,7 +103,7 @@ class SymbolTreeTestUtils { public static Program buildProgram() throws Exception { - ToyProgramBuilder builder = new ToyProgramBuilder("notepad", true); + ToyProgramBuilder builder = new ToyProgramBuilder("sample1", true); Program program = builder.getProgram(); builder.createMemory("test", "0x1001000", 0x5500); @@ -169,6 +170,49 @@ class SymbolTreeTestUtils { return program; } + public static Program buildProgram2() throws Exception { + + // Note: the contents of this program are arbitrary and loosely based off of the program + // in buildProgram(). + + ToyProgramBuilder builder = new ToyProgramBuilder("sample2", true); + Program program = builder.getProgram(); + + builder.createMemory("test", "0x1001000", 0x5500); + + // create an 'Exports' node + builder.createEntryPoint("0x1006420", "entry"); + builder.createLabel("0x1006420", "entry"); + + // imports symbol tree node + builder.createExternalLibraries("ADVAPI32.dll", "comdlg32.dll", "GDI32.dll", "KERNEL32.dll", + "MSVCRT.dll", "SHELL32.dll", "USER32.dll", "WINSPOOL.DRV"); + builder.createExternalReference("0x1001000", "ADVAPI32.dll", "IsTextUnicode", 0); + builder.createLabel("0x1001000", "ADVAPI32.dll_IsTextUnicode"); + builder.createExternalReference("0x1001004", "ADVAPI32.dll", "RegCreateKeyW", 0); + + ExternalManager externalManager = builder.getProgram().getExternalManager(); + int tx = program.startTransaction("Test Transaction"); + externalManager.setExternalPath("ADVAPI32.dll", "/path/to/ADVAPI32.DLL", true); + program.endTransaction(tx, true); + + // functions + builder.createEmptyFunction("doStuff2", null, "0x10048a3", 19, new Undefined1DataType(), + new ParameterImpl("param_1", new IntegerDataType(), program), + new ParameterImpl("param_2", new IntegerDataType(), program)); + + //@formatter:off + ParameterImpl p = new ParameterImpl(null /*auto name*/, new IntegerDataType(), program); + builder.createEmptyFunction("ghidra2", null, "0x1002cf5", 121, new Undefined1DataType(), + p, p, p, p, p, p, p, p, p); + //@formatter:on + + builder.createLabel("0x1002d2b", "AnotherLoca2l", "ghidra"); + builder.createLabel("0x1002d1f", "MyLocal2", "ghidra"); + + return program; + } + SymbolTreeRootNode getRootNode() { return (SymbolTreeRootNode) rootGTreeNode; } @@ -359,8 +403,8 @@ class SymbolTreeTestUtils { } void closeProgram() throws Exception { - final ProgramManager pm = plugin.getTool().getService(ProgramManager.class); - runSwing(() -> pm.closeProgram()); + ProgramManager pm = plugin.getTool().getService(ProgramManager.class); + runSwing(() -> pm.closeProgram(program, true)); } Program getProgram() { @@ -372,6 +416,13 @@ class SymbolTreeTestUtils { pm.openProgram(program.getDomainFile()); } + Program openProgram2() throws Exception { + Program p2 = buildProgram2(); + ProgramManager pm = plugin.getTool().getService(ProgramManager.class); + pm.openProgram(p2.getDomainFile()); + return p2; + } + void clearClipboard() { Clipboard clipboard = (Clipboard) getInstanceField("localClipboard", provider); ClipboardOwner owner = (ClipboardOwner) getInstanceField("clipboardOwner", provider); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java index cc3c2214cd..91cb84ee0c 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java @@ -662,7 +662,7 @@ class FGActionManager { Icon image = new GIcon("icon.plugin.functiongraph.action.viewer.clone"); cloneAction.setToolBarData(new ToolBarData(image, toolbarEndGroup)); cloneAction.setDescription( - "Create a snapshot (disconnected) copy of this Function Graph window "); + "Create a snapshot (disconnected) copy of this Function Graph window"); cloneAction.setHelpLocation(new HelpLocation("Snapshots", "Snapshots_Start")); cloneAction.setHelpLocation( new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Snapshot")); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java index 73bc18f722..172f3937b2 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java @@ -68,6 +68,8 @@ public class FilterTextField extends JPanel { private WeakSet listeners = WeakDataStructureFactory.createCopyOnWriteWeakSet(); private WeakSet enterListeners = WeakDataStructureFactory.createCopyOnWriteWeakSet(); + private String accessibleNamePrefix; + /** * Constructs this text field with the given component. component may be null, but * then this field will be unable to flash in response to focus events (see the header @@ -302,6 +304,27 @@ public class FilterTextField extends JPanel { } } + /** + * Sets the accessible name prefix for for the focusable components in the filter panel. + * @param prefix the base name for these components. A suffix will be added to further + * describe the sub component. + */ + public void setAccessibleNamePrefix(String prefix) { + this.accessibleNamePrefix = prefix; + String name = prefix + " filter text field"; + textField.setName(name); + textField.getAccessibleContext().setAccessibleName(name); + } + + /** + * Returns the accessible name prefix set by a previous call to + * {@link #setAccessibleNamePrefix(String)}. This will be null if not set. + * @return the prefix + */ + public String getAccessibleNamePrefix() { + return accessibleNamePrefix; + } + //================================================================================================== // Package Methods (these make testing easier) //================================================================================================== @@ -374,6 +397,7 @@ public class FilterTextField extends JPanel { }); } + //================================================================================================== // Inner Classes //================================================================================================== @@ -463,17 +487,4 @@ public class FilterTextField extends JPanel { flashCount = 0; } } - - /** - * Sets the accessible name prefix for for the focusable components in the filter panel. - * @param prefix the base name for these components. A suffix will be added to further - * describe the sub component. - */ - public void setAccessibleNamePrefix(String prefix) { - String name = prefix + " filter text field"; - textField.setName(name); - textField.getAccessibleContext().setAccessibleName(name); - - } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/DefaultGTreeFilterProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/DefaultGTreeFilterProvider.java index b1ac603fb2..bddba92fb6 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/DefaultGTreeFilterProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/DefaultGTreeFilterProvider.java @@ -46,7 +46,6 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider { private JPanel filterPanel; private FilterTransformer dataTransformer = new DefaultGTreeDataTransformer(); - private String preferenceKey; private boolean optionsSet; public DefaultGTreeFilterProvider(GTree gTree) { @@ -55,6 +54,28 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider { filterPanel = createFilterPanel(); } + @Override + public GTreeFilterProvider copy(GTree newTree) { + DefaultGTreeFilterProvider newProvider = new DefaultGTreeFilterProvider(newTree); + + FilterOptions existingOptions = filterFactory.getFilterOptions(); + newProvider.setFilterOptions(existingOptions); + + String existingText = filterField.getText(); + newProvider.setFilterText(existingText); + + if (!filterField.isEnabled()) { + newProvider.setEnabled(false); + } + + String accessibleNamePrefix = filterField.getAccessibleNamePrefix(); + if (accessibleNamePrefix != null) { + newProvider.setAccessibleNamePrefix(accessibleNamePrefix); + } + + return newProvider; + } + @Override public JComponent getFilterComponent() { return filterPanel; @@ -90,10 +111,14 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider { // tooltips which seem excessive to read to the user every time they get focus. We may need // to revisit this decision. context.setAccessibleDescription(""); - } private void updateModelFilter() { + + FilterOptions filterOptions = filterFactory.getFilterOptions(); + filterStateButton.setIcon(filterOptions.getFilterStateIcon()); + filterStateButton.setToolTipText(filterOptions.getFilterDescription()); + gTree.filterChanged(); } @@ -103,7 +128,7 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider { DockingWindowManager dwm = DockingWindowManager.getInstance(gTree.getJTree()); if (dwm != null) { - dwm.putPreferenceState(preferenceKey, preferenceState); + dwm.putPreferenceState(gTree.getPreferenceKey(), preferenceState); } } @@ -114,10 +139,12 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider { updateModelFilter(); } + public FilterOptions getFilterOptions() { + return filterFactory.getFilterOptions(); + } + @Override - public void loadFilterPreference(DockingWindowManager windowManager, - String uniquePreferenceKey) { - preferenceKey = uniquePreferenceKey; + public void loadFilterPreference(DockingWindowManager windowManager) { if (optionsSet) { // if the options were specifically set, don't restore saved values return; } @@ -126,12 +153,12 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider { return; } - PreferenceState preferenceState = windowManager.getPreferenceState(preferenceKey); - if (preferenceState == null) { + PreferenceState state = windowManager.getPreferenceState(gTree.getPreferenceKey()); + if (state == null) { return; } - Element xmlElement = preferenceState.getXmlElement(FILTER_STATE); + Element xmlElement = state.getXmlElement(FILTER_STATE); if (xmlElement != null) { FilterOptions filterOptions = FilterOptions.restoreFromXML(xmlElement); filterFactory = new GTreeFilterFactory(filterOptions); @@ -157,10 +184,6 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider { FilterOptions newFilterOptions = dialog.getResultFilterOptions(); if (newFilterOptions != null) { filterFactory = new GTreeFilterFactory(newFilterOptions); - - filterStateButton.setIcon(newFilterOptions.getFilterStateIcon()); - filterStateButton.setToolTipText(newFilterOptions.getFilterDescription()); - saveFilterState(); updateModelFilter(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java index 3e80443f06..818a13ab0e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java @@ -139,8 +139,7 @@ public class GTree extends JPanel implements BusyListener { init(); DockingWindowManager.registerComponentLoadedListener(this, - (windowManager, provider) -> filterProvider.loadFilterPreference(windowManager, - uniquePreferenceKey)); + (windowManager, provider) -> filterProvider.loadFilterPreference(windowManager)); filterUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateModelFilter()); Gui.addThemeListener(themeListener); @@ -385,6 +384,17 @@ public class GTree extends JPanel implements BusyListener { runTask(new GTreeRestoreTreeStateTask(this, state)); } + /** + * Sets the filter restore state. This method is a way to override the tree's filtering + * behavior, which is usually set by a call to {@link #saveFilterRestoreState()}. Most clients + * will never need to call this method. + * + * @param state the state to set + */ + protected void setFilterRestoreState(GTreeState state) { + this.filterRestoreTreeState = state; + } + /** * Signal to the tree that it should record its expanded and selected state when a new filter is * applied @@ -404,6 +414,14 @@ public class GTree extends JPanel implements BusyListener { filterRestoreTreeState = null; } + /** + * Returns the key that this tree uses to store preferences. + * @return the key that this tree uses to store preferences. + */ + public String getPreferenceKey() { + return uniquePreferenceKey; + } + /** * A method that subclasses can use to be notified when tree state has been restored. This * method is called after a major structural tree change has happened and the paths that @@ -689,7 +707,8 @@ public class GTree extends JPanel implements BusyListener { return node; // this node is a valid child of the given root } - GTreeNode parentNode = getNodeForPath(root, path.getParentPath()); + TreePath parentPath = path.getParentPath(); + GTreeNode parentNode = getNodeForPath(root, parentPath); if (parentNode == null) { return null; // must be a path we don't have } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterProvider.java index 8eb01aea1b..1d2bb74e74 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterProvider.java @@ -26,7 +26,7 @@ import ghidra.util.FilterTransformer; */ public interface GTreeFilterProvider { /** - * Returns the component to place at the bottom of a GTree to provider filtering capabilites. + * Returns the component to place at the bottom of a GTree to provider filtering capabilities. * @return the filter component */ public JComponent getFilterComponent(); @@ -65,10 +65,8 @@ public interface GTreeFilterProvider { /** * Loads any filter preferences that have been saved. * @param windowManager the {@link DockingWindowManager} to load preferences from - * @param uniquePreferenceKey the preference key */ - public void loadFilterPreference(DockingWindowManager windowManager, - String uniquePreferenceKey); + public void loadFilterPreference(DockingWindowManager windowManager); /** * Sets an accessible name on the filter component. This prefix will be used to assign @@ -82,4 +80,18 @@ public interface GTreeFilterProvider { * example if the tree contains fruits, then "Fruits" would be an appropriate prefix name. */ public void setAccessibleNamePrefix(String namePrefix); + + /** + * Creates a copy of this filter with all current filter settings. + *

+ * This is meant to be used for GTrees that support creating a new copy. + *

+ * Note: Filter providers that do not support copying will return null from this method. + * + * @param gTree the new tree for the new filter + * @return the copy + */ + public default GTreeFilterProvider copy(GTree gTree) { + return null; + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java index e0d7641e9f..86ab550c52 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java @@ -388,7 +388,6 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable list = new ArrayList<>(); - if (isLoaded()) { for (GTreeNode child : children()) { monitor.checkCancelled(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java index 0b85eca17b..67d2942d6e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java @@ -72,7 +72,7 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent setText(text); setToolTipText(node.getToolTip()); - Icon icon = node.getIcon(expanded); + Icon icon = getNodeIcon(node, expanded); if (icon == null) { icon = getIcon(); } @@ -90,6 +90,10 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent return this; } + protected Icon getNodeIcon(GTreeNode node, boolean expanded) { + return node.getIcon(expanded); + } + /** * Overrides this method to ensure that the new background selection color is not * a {@link GColorUIResource}. Some Look and Feels will ignore color values that extend diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandPathsTask.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandPathsTask.java index c34365bf90..34e5f536f9 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandPathsTask.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandPathsTask.java @@ -52,17 +52,18 @@ public class GTreeExpandPathsTask extends GTreeTask { if (nodeList.length < 2) { return; // only the root is in the path } + List allChildren = parent.getChildren(); for (int i = 1; i < nodeList.length; i++) { if (monitor.isCancelled()) { return; } - GTreeNode node = findNode(allChildren, (GTreeNode) nodeList[i]); - if (node == null) { + + GTreeNode nextParent = findNode(allChildren, (GTreeNode) nodeList[i]); + if (nextParent == null) { return; } - allChildren = node.getChildren(); - parent = node; + allChildren = nextParent.getChildren(); } } From 30600863cf12d8a4db42007f44ae8fd2e3d7928b Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Thu, 2 May 2024 12:41:22 -0400 Subject: [PATCH 006/356] GP-4516: Improvements --- .../FindAudioInProgramScript.java | 19 ++-- .../core/analysis/EmbeddedMediaAnalyzer.java | 3 + .../bin/format/pe/ResourceDataDirectory.java | 13 +++ .../program/model/data/MIDIDataType.java | 86 +++++++------------ .../program/model/data/ScorePlayer.java | 50 +++++------ 5 files changed, 75 insertions(+), 96 deletions(-) diff --git a/Ghidra/Features/Base/ghidra_scripts/FindAudioInProgramScript.java b/Ghidra/Features/Base/ghidra_scripts/FindAudioInProgramScript.java index bc49371a36..3b04838e24 100644 --- a/Ghidra/Features/Base/ghidra_scripts/FindAudioInProgramScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/FindAudioInProgramScript.java @@ -15,17 +15,16 @@ */ //Finds programs containing various audio resources such as WAV's //@category Resources +import java.util.ArrayList; +import java.util.List; + import ghidra.app.script.GhidraScript; import ghidra.program.model.address.Address; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.WAVEDataType; +import ghidra.program.model.data.*; import ghidra.program.model.listing.Data; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryBlock; -import java.util.ArrayList; -import java.util.List; - public class FindAudioInProgramScript extends GhidraScript { @Override @@ -36,8 +35,10 @@ public class FindAudioInProgramScript extends GhidraScript { //look for WAV data types WAVEDataType wdt = new WAVEDataType(); + MIDIDataType mdt = new MIDIDataType(); totalFound += findAudioData("WAV", wdt, WAVEDataType.MAGIC, WAVEDataType.MAGIC_MASK); + totalFound += findAudioData("MIDI", mdt, MIDIDataType.MAGIC, MIDIDataType.MAGIC_MASK); if (totalFound == 0) { println("No Audio data found in " + currentProgram.getName()); @@ -54,12 +55,12 @@ public class FindAudioInProgramScript extends GhidraScript { int numDataFound = 0; List

foundList = scanForAudioData(pattern, mask); - //Loop over all potential found WAVs + //Loop over all potential found audio for (int i = 0; i < foundList.size(); i++) { boolean foundData = false; - //See if already applied WAV + //See if already applied data type Data data = getDataAt(foundList.get(i)); - //If not already applied, try to apply WAV data type + //If not already applied, try to apply audio data type if (data == null) { println("Trying to apply " + dataName + " datatype at " + foundList.get(i).toString()); @@ -67,7 +68,7 @@ public class FindAudioInProgramScript extends GhidraScript { try { Data newData = createData(foundList.get(i), dt); if (newData != null) { - println("Applied WAV at " + newData.getAddressString(false, true)); + printf("Applied %s at %s", dataName, newData.getAddressString(false, true)); foundData = true; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java index 141404a300..334acc3a1f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java @@ -81,6 +81,9 @@ public class EmbeddedMediaAnalyzer extends AbstractAnalyzer { addByteSearchPattern(searcher, program, foundMedia, new WAVEDataType(), "WAVE", WAVEDataType.MAGIC, WAVEDataType.MAGIC_MASK); + addByteSearchPattern(searcher, program, foundMedia, new MIDIDataType(), "MIDI", + MIDIDataType.MAGIC, MIDIDataType.MAGIC_MASK); + addByteSearchPattern(searcher, program, foundMedia, new AUDataType(), "AU", AUDataType.MAGIC, AUDataType.MAGIC_MASK); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ResourceDataDirectory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ResourceDataDirectory.java index b6aa8db00b..688211a313 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ResourceDataDirectory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ResourceDataDirectory.java @@ -269,6 +269,19 @@ public class ResourceDataDirectory extends DataDirectory { } PeUtils.createData(program, addr, dataType, log); } + else if (info.getName().startsWith("Rsrc_MIDI")) { + DataType dataType = null; + // Check for MIDI magic number + try { + if (program.getMemory().getInt(addr) == 0x6468544d) { + dataType = new MIDIDataType(); + } + } + catch (MemoryAccessException e) { + // ignore - let createData produce error + } + PeUtils.createData(program, addr, dataType, log); + } else if (info.getName().startsWith("Rsrc_WEVT")) { DataType dataType = null; // Check for WEVT magic number "CRIM" diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java index 4a93231918..4b2ab9eb6a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java @@ -4,9 +4,9 @@ * 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. @@ -15,16 +15,25 @@ */ package ghidra.program.model.data; +import java.io.DataInputStream; import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; import ghidra.docking.settings.Settings; import ghidra.program.model.mem.MemBuffer; -import ghidra.program.model.mem.Memory; import ghidra.util.Msg; public class MIDIDataType extends BuiltIn implements Dynamic { + + public static byte[] MAGIC = + new byte[] { (byte) 'M', (byte) 'T', (byte) 'h', (byte) 'd', (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 'M', (byte) 'T', (byte) 'r', (byte) 'k' }; + + public static byte[] MAGIC_MASK = + new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; + public MIDIDataType() { this(null); } @@ -40,80 +49,43 @@ public class MIDIDataType extends BuiltIn implements Dynamic { @Override public int getLength(MemBuffer buf, int maxLength) { - try { - return computeLength(buf, maxLength); - } - catch (Exception e) { - Msg.debug(this, "Invalid MIDI data at " + buf.getAddress()); - } - return -1; - } - - private long readUnsignedInteger(InputStream stream) throws IOException { - long value = 0; - for (int index = 0; index < 4; index++) { - int currentByte = stream.read(); - if (currentByte == -1) { - throw new EOFException(); - } - value = (value << 8) | currentByte; - } - return value; - } - - private int readUnsignedShort(InputStream stream) throws IOException { - int value = 0; - for (int index = 0; index < 2; index++) { - int currentByte = stream.read(); - if (currentByte == -1) { - throw new EOFException(); - } - value = (value << 8) | currentByte; - } - return value; - } - - private int computeLength(MemBuffer buf, int maxLength) throws IOException, InvalidDataTypeException { - int computedLength = -1; - - try (InputStream stream = buf.getInputStream(0, maxLength > 0 ? maxLength : Integer.MAX_VALUE)) { + try (DataInputStream stream = new DataInputStream( + buf.getInputStream(0, maxLength > 0 ? maxLength : Integer.MAX_VALUE))) { byte[] chunkType = new byte[4]; if (stream.read(chunkType) < chunkType.length) { throw new EOFException(); } - if (chunkType[0] != (byte)'M' || - chunkType[1] != (byte)'T' || - chunkType[2] != (byte)'h' || - chunkType[3] != (byte)'d') { + if (chunkType[0] != (byte) 'M' || chunkType[1] != (byte) 'T' || + chunkType[2] != (byte) 'h' || chunkType[3] != (byte) 'd') { return -1; } - long chunkLength = readUnsignedInteger(stream); + long chunkLength = Integer.toUnsignedLong(stream.readInt()); if (chunkLength != 6) { throw new InvalidDataTypeException("Unexpected header length."); } stream.skip(2); - int tracks = readUnsignedShort(stream); + int tracks = Short.toUnsignedInt(stream.readShort()); stream.skip(2); - computedLength = 14; + int computedLength = 14; while (tracks > 0) { if (stream.read(chunkType) < chunkType.length) { throw new EOFException(); } - chunkLength = readUnsignedInteger(stream); + chunkLength = Integer.toUnsignedLong(stream.readInt()); stream.skip(chunkLength); computedLength += 8 + chunkLength; - if (chunkType[0] != (byte)'M' || - chunkType[1] != (byte)'T' || - chunkType[2] != (byte)'r' || - chunkType[3] != (byte)'k') { + if (chunkType[0] != (byte) 'M' || chunkType[1] != (byte) 'T' || + chunkType[2] != (byte) 'r' || chunkType[3] != (byte) 'k') { continue; } tracks--; } - } finally { + return computedLength; + } + catch (Exception e) { + Msg.debug(this, "Invalid MIDI data at " + buf.getAddress()); + return -1; } - - return computedLength; } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java index d8c7dd33c4..48251df628 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java @@ -19,29 +19,24 @@ import java.awt.event.MouseEvent; import java.io.ByteArrayInputStream; import java.io.IOException; -import javax.sound.midi.InvalidMidiDataException; -import javax.sound.midi.MetaEventListener; -import javax.sound.midi.MetaMessage; -import javax.sound.midi.MidiSystem; -import javax.sound.midi.MidiUnavailableException; -import javax.sound.midi.Sequence; -import javax.sound.midi.Sequencer; +import javax.sound.midi.*; import javax.swing.Icon; import generic.theme.GIcon; import ghidra.util.Msg; import ghidra.util.Swing; +/** + * Plays a MIDI score + */ public class ScorePlayer implements Playable, MetaEventListener { + private static final Icon MIDI_ICON = new GIcon("icon.data.type.audio.player"); private static final int END_OF_TRACK_MESSAGE = 47; - private static final Icon AUDIO_ICON = new GIcon("icon.data.type.audio.player"); - // This currently only allows one sequence to be played for the entire application, // which seems good enough. The MIDI instance variables are currently synchronized // by the Swing thread. - private static volatile Sequence currentSequence; private static volatile Sequencer currentSequencer; private byte[] bytes; @@ -52,30 +47,23 @@ public class ScorePlayer implements Playable, MetaEventListener { @Override public Icon getImageIcon() { - return AUDIO_ICON; + return MIDI_ICON; } @Override public void clicked(MouseEvent event) { try { - // any new request should stop any previous sequence being played - if (currentSequence != null && currentSequencer != null) { - assert currentSequencer.isOpen(); - currentSequencer.stop(); - currentSequence = null; // this field is also updated when the sound thread calls back - currentSequencer = null; // same as above + // Any new request should stop any previous sequence being played + if (currentSequencer != null) { + stop(); return; } Sequencer sequencer = MidiSystem.getSequencer(true); sequencer.addMetaEventListener(this); sequencer.setLoopCount(0); - Sequence sequence = MidiSystem.getSequence(new ByteArrayInputStream(bytes)); - if (!sequencer.isOpen()) { - sequencer.open(); - } - sequencer.setSequence(sequence); - currentSequence = sequence; + sequencer.setSequence(MidiSystem.getSequence(new ByteArrayInputStream(bytes))); + sequencer.open(); currentSequencer = sequencer; currentSequencer.start(); } @@ -86,16 +74,18 @@ public class ScorePlayer implements Playable, MetaEventListener { @Override public void meta(MetaMessage message) { - if (message.getType() != END_OF_TRACK_MESSAGE) { + if (message.getType() == END_OF_TRACK_MESSAGE) { + Swing.runNow(() -> stop()); + } + } + + private void stop() { + if (currentSequencer == null) { return; } - - assert currentSequencer != null && currentSequence != null; currentSequencer.removeMetaEventListener(this); currentSequencer.stop(); - Swing.runNow(() -> { - currentSequence = null; - currentSequencer = null; - }); + currentSequencer.close(); + currentSequencer = null; } } From 7c37564d967a81544812bcae1fbbcdeacb18ddaf Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 7 May 2024 06:24:18 -0400 Subject: [PATCH 007/356] GP-4516: Review --- .../src/main/java/ghidra/program/model/data/AudioPlayer.java | 2 +- .../src/main/java/ghidra/program/model/data/ScorePlayer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AudioPlayer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AudioPlayer.java index ab1c2a8241..7f8146753f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AudioPlayer.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AudioPlayer.java @@ -65,7 +65,7 @@ public class AudioPlayer implements Playable, LineListener { clip.start(); } catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) { - Msg.debug(this, "Unable to play audio", e); + Msg.error(this, "Unable to play audio", e); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java index 48251df628..b9dfa674b9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java @@ -68,7 +68,7 @@ public class ScorePlayer implements Playable, MetaEventListener { currentSequencer.start(); } catch (MidiUnavailableException | InvalidMidiDataException | IOException e) { - Msg.debug(this, "Unable to play score", e); + Msg.error(this, "Unable to play score", e); } } From ba5575a0d40354b6c4893d1804da76b227d7ccba Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 7 May 2024 09:24:16 -0400 Subject: [PATCH 008/356] GP-4581: Making OSGi-related dialogs modal --- .../app/plugin/core/osgi/BundleHost.java | 2 +- .../osgi/BundleStatusComponentProvider.java | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java index 597ef58b04..b7f6df0631 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java @@ -891,7 +891,7 @@ public class BundleHost { } if (!bundlesToActivate.isEmpty()) { - TaskLauncher.launchNonModal("Restoring bundle state", + TaskLauncher.launchModal("Restoring Bundle State", (monitor) -> activateInStages(bundlesToActivate, monitor, new NullPrintWriter())); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java index 2e66e0a9ab..c6f857e2f7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java @@ -219,7 +219,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { } // then activate them all - new TaskLauncher(new EnableAndActivateBundlesTask("activating", statuses, true), + new TaskLauncher(new EnableAndActivateBundlesTask("Activating Bundles", statuses, true), getComponent(), 1000); } @@ -243,7 +243,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { if (selectedModelRows == null || selectedModelRows.length == 0) { return; } - new TaskLauncher(new RemoveBundlesTask("removing bundles", getSelectedStatuses()), + new TaskLauncher(new RemoveBundlesTask("Removing Bundles", getSelectedStatuses()), getComponent(), 1000); } @@ -281,7 +281,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { files.stream().map(ResourceFile::new).collect(Collectors.toUnmodifiableList()); Collection bundles = bundleHost.add(resourceFiles, true, false); - TaskLauncher.launchNonModal("Activating new bundles", (monitor) -> { + TaskLauncher.launchModal("Activating New Bundles", (monitor) -> { try { bundleHost.activateAll(bundles, monitor, getTool().getService(ConsoleService.class).getStdErr()); @@ -303,12 +303,14 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { } protected void doEnableBundles() { - new TaskLauncher(new EnableAndActivateBundlesTask("enabling", getSelectedStatuses(), false), + new TaskLauncher( + new EnableAndActivateBundlesTask("Enabling Bundles", getSelectedStatuses(), false), getComponent(), 1000); } protected void doDisableBundles() { - new TaskLauncher(new DeactivateAndDisableBundlesTask("disabling", getSelectedStatuses()), + new TaskLauncher( + new DeactivateAndDisableBundlesTask("Disabling Bundles", getSelectedStatuses()), getComponent(), 1000); } @@ -317,7 +319,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { notifyTableRowChanged(status); new TaskLauncher( new ActivateDeactivateBundleTask( - (activate ? "Activating" : "Deactivating ") + " bundle...", status, activate), + (activate ? "Activating" : "Deactivating ") + " Bundle", status, activate), null, 1000); } @@ -373,7 +375,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { private final List statuses; private RemoveBundlesTask(String title, List statuses) { - super(title); + super(title, true, false, true); this.deactivateBundlesTask = new DeactivateAndDisableBundlesTask("deactivating", statuses); this.statuses = statuses; @@ -415,7 +417,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { */ private EnableAndActivateBundlesTask(String title, List statuses, boolean inStages) { - super(title, true, true, false); + super(title, true, true, true); this.statuses = statuses; this.inStages = inStages; } @@ -473,7 +475,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { final List statuses; private DeactivateAndDisableBundlesTask(String title, List statuses) { - super(title, true, true, false); + super(title, true, true, true); this.statuses = statuses; } @@ -511,7 +513,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { private final boolean activate; private ActivateDeactivateBundleTask(String title, BundleStatus status, boolean activate) { - super(title); + super(title, true, false, true); this.status = status; this.activate = activate; } From 05f8745c28270d36eaecc1d7b2c20f96bc62837f Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 7 May 2024 10:06:53 -0400 Subject: [PATCH 009/356] Test fixes --- .../core/symboltree/actions/RenameAction.java | 3 +- .../SymbolTreeLocationReferencesTest.java | 12 +-- .../symboltree/SymbolTreePlugin1Test.java | 79 ++++++++----------- 3 files changed, 40 insertions(+), 54 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java index 21adb37ee1..25496afa95 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java @@ -18,6 +18,7 @@ package ghidra.app.plugin.core.symboltree.actions; import docking.action.MenuData; import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext; import ghidra.app.plugin.core.symboltree.SymbolTreePlugin; +import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; import ghidra.app.plugin.core.symboltree.nodes.SymbolTreeNode; public class RenameAction extends SymbolTreeContextAction { @@ -31,7 +32,7 @@ public class RenameAction extends SymbolTreeContextAction { @Override public boolean isEnabledForContext(SymbolTreeActionContext context) { SymbolTreeNode node = context.getSelectedNode(); - return node != null; + return node instanceof SymbolNode; } @Override diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/locationreferences/SymbolTreeLocationReferencesTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/locationreferences/SymbolTreeLocationReferencesTest.java index 22bb67db8e..30e63c970c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/locationreferences/SymbolTreeLocationReferencesTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/locationreferences/SymbolTreeLocationReferencesTest.java @@ -42,7 +42,7 @@ public class SymbolTreeLocationReferencesTest extends AbstractLocationReferences super.setUp(); symbolTreePlugin = getPlugin(tool, SymbolTreePlugin.class); - treeProvider = (SymbolTreeProvider) getInstanceField("provider", symbolTreePlugin); + treeProvider = (SymbolTreeProvider) getInstanceField("connectedProvider", symbolTreePlugin); showProvider(tool, treeProvider.getName()); symbolTree = (SymbolGTree) getInstanceField("tree", treeProvider); showSymbolReferencesAction = @@ -118,8 +118,9 @@ public class SymbolTreeLocationReferencesTest extends AbstractLocationReferences externalLocation); int txId = program.startTransaction("Add Ext Ref"); try { - program.getReferenceManager().addExternalReference(refAddr, 1, externalLocation, - SourceType.USER_DEFINED, refType); + program.getReferenceManager() + .addExternalReference(refAddr, 1, externalLocation, SourceType.USER_DEFINED, + refType); return externalLocation.createFunction(); } finally { @@ -130,8 +131,9 @@ public class SymbolTreeLocationReferencesTest extends AbstractLocationReferences private void addThunk(Address thunkAddr, Function thunkedFunction) throws Exception { int txId = program.startTransaction("Add Thunk"); try { - Function f = program.getFunctionManager().createFunction(null, thunkAddr, - new AddressSet(thunkAddr), SourceType.USER_DEFINED); + Function f = program.getFunctionManager() + .createFunction(null, thunkAddr, new AddressSet(thunkAddr), + SourceType.USER_DEFINED); f.setThunkedFunction(thunkedFunction); } finally { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin1Test.java index a12ae95f6b..37c70d4c93 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin1Test.java @@ -257,15 +257,15 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { util.selectNode(rootNode); ActionContext context = util.getSymbolTreeContext(); assertTrue(createLibraryAction.isEnabledForContext(context)); - assertTrue(!createClassAction.isEnabledForContext(context)); - assertTrue(!createNamespaceAction.isEnabledForContext(context)); - assertTrue(!renameAction.isEnabledForContext(context)); - assertTrue(!cutAction.isEnabledForContext(context)); - assertTrue(!pasteAction.isEnabledForContext(context)); - assertTrue(!deleteAction.isEnabledForContext(context)); - assertTrue(!selectionAction.isEnabledForContext(context)); - assertTrue(!goToExtLocAction.isEnabledForContext(context)); - assertTrue(!goToExtLocAction.isEnabledForContext(context)); + assertFalse(createClassAction.isEnabledForContext(context)); + assertFalse(createNamespaceAction.isEnabledForContext(context)); + assertFalse(renameAction.isEnabledForContext(context)); + assertFalse(cutAction.isEnabledForContext(context)); + assertFalse(pasteAction.isEnabledForContext(context)); + assertFalse(deleteAction.isEnabledForContext(context)); + assertFalse(selectionAction.isEnabledForContext(context)); + assertFalse(goToExtLocAction.isEnabledForContext(context)); + assertFalse(goToExtLocAction.isEnabledForContext(context)); } @Test @@ -281,9 +281,6 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { util.selectNode(node); performAction(cutAction, util.getSymbolTreeContext(), true); - util.selectNode(rootNode); - assertTrue(pasteAction.isEnabledForContext(util.getSymbolTreeContext())); - // move a function to a namespace // cut a function; select global; paste should be enabled GTreeNode nsParentNode = rootNode.getChild(5); @@ -312,10 +309,6 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { util.selectNode(gNode); assertTrue(cutAction.isEnabledForContext(util.getSymbolTreeContext())); performAction(cutAction, util.getSymbolTreeContext(), true); - - // select the root node - util.selectNode(rootNode); - assertTrue(pasteAction.isEnabledForContext(util.getSymbolTreeContext())); } @Test @@ -354,7 +347,7 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { GTreeNode dNode = fNode.getChild(0); util.selectNode(dNode); - assertTrue(!pasteAction.isEnabledForContext(util.getSymbolTreeContext())); + assertFalse(pasteAction.isEnabledForContext(util.getSymbolTreeContext())); } @Test @@ -415,35 +408,25 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { else { assertFalse(createLibraryIsEnabled); } - assertTrue(!createClassAction.isEnabledForContext(context)); - assertTrue(!createNamespaceAction.isEnabledForContext(context)); - assertTrue(!renameAction.isEnabledForContext(context)); - assertTrue(!renameAction.isEnabledForContext(context)); - assertTrue(!cutAction.isEnabledForContext(context)); - assertTrue(!cutAction.isEnabledForContext(context)); - assertTrue(!pasteAction.isEnabledForContext(context)); - assertTrue(!pasteAction.isEnabledForContext(context)); - assertTrue(!deleteAction.isEnabledForContext(context)); - assertTrue(!deleteAction.isEnabledForContext(context)); - assertTrue(!selectionAction.isEnabledForContext(context)); - assertTrue(!selectionAction.isEnabledForContext(context)); + assertFalse(createClassAction.isEnabledForContext(context)); + assertFalse(createNamespaceAction.isEnabledForContext(context)); + assertFalse(renameAction.isEnabledForContext(context)); + assertFalse(cutAction.isEnabledForContext(context)); + assertFalse(pasteAction.isEnabledForContext(context)); + assertFalse(deleteAction.isEnabledForContext(context)); + assertFalse(selectionAction.isEnabledForContext(context)); GTreeNode lNode = rootNode.getChild(1); util.selectNode(lNode); context = util.getSymbolTreeContext(); - assertTrue(!createLibraryAction.isEnabledForContext(context)); - assertTrue(!createClassAction.isEnabledForContext(context)); - assertTrue(!createNamespaceAction.isEnabledForContext(context)); - assertTrue(!renameAction.isEnabledForContext(context)); - assertTrue(!renameAction.isEnabledForContext(context)); - assertTrue(!cutAction.isEnabledForContext(context)); - assertTrue(!cutAction.isEnabledForContext(context)); - assertTrue(!pasteAction.isEnabledForContext(context)); - assertTrue(!pasteAction.isEnabledForContext(context)); - assertTrue(!deleteAction.isEnabledForContext(context)); - assertTrue(!deleteAction.isEnabledForContext(context)); - assertTrue(!selectionAction.isEnabledForContext(context)); - assertTrue(!selectionAction.isEnabledForContext(context)); + assertFalse(createLibraryAction.isEnabledForContext(context)); + assertFalse(createClassAction.isEnabledForContext(context)); + assertFalse(createNamespaceAction.isEnabledForContext(context)); + assertFalse(renameAction.isEnabledForContext(context)); + assertFalse(cutAction.isEnabledForContext(context)); + assertFalse(pasteAction.isEnabledForContext(context)); + assertFalse(deleteAction.isEnabledForContext(context)); + assertFalse(selectionAction.isEnabledForContext(context)); } @Test @@ -457,8 +440,8 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { GTreeNode pNode = gNode.getChild(0); util.selectNode(pNode); ActionContext context = util.getSymbolTreeContext(); - assertTrue(!cutAction.isEnabledForContext(context)); - assertTrue(!pasteAction.isEnabledForContext(context)); + assertFalse(cutAction.isEnabledForContext(context)); + assertFalse(pasteAction.isEnabledForContext(context)); assertTrue(renameAction.isEnabledForContext(context)); assertTrue(renameAction.isEnabledForContext(context)); assertTrue(selectionAction.isEnabledForContext(context)); @@ -476,7 +459,7 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { util.selectNode(gNode); ActionContext context = util.getSymbolTreeContext(); assertTrue(cutAction.isEnabledForContext(context)); - assertTrue(!pasteAction.isEnabledForContext(context)); + assertFalse(pasteAction.isEnabledForContext(context)); assertTrue(renameAction.isEnabledForContext(context)); assertTrue(renameAction.isEnabledForContext(context)); assertTrue(selectionAction.isEnabledForContext(context)); @@ -495,7 +478,7 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { util.selectNode(pNode); ActionContext context = util.getSymbolTreeContext(); assertTrue(cutAction.isEnabledForContext(context)); - assertTrue(!pasteAction.isEnabledForContext(context)); + assertFalse(pasteAction.isEnabledForContext(context)); assertTrue(renameAction.isEnabledForContext(context)); assertTrue(renameAction.isEnabledForContext(context)); assertTrue(selectionAction.isEnabledForContext(context)); @@ -866,8 +849,8 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { } private void renameSelectedNode() throws Exception { - SwingUtilities.invokeAndWait( - () -> renameAction.actionPerformed(util.getSymbolTreeContext())); + SwingUtilities + .invokeAndWait(() -> renameAction.actionPerformed(util.getSymbolTreeContext())); waitForEditing(); } From 78425f441dcedb55e8b66c2542c040a4dd4be097 Mon Sep 17 00:00:00 2001 From: ghidra007 Date: Wed, 8 May 2024 22:39:41 +0000 Subject: [PATCH 010/356] GP-4586 corrected and simplified method to generate class description to fix order of parents. --- .../classrecovery/RecoveredClassHelper.java | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassHelper.java b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassHelper.java index 5371244860..5d02e260a7 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassHelper.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassHelper.java @@ -3172,26 +3172,18 @@ public class RecoveredClassHelper { if (recoveredClass.hasParentClass()) { - // use this to get direct parents - Map> classHierarchyMap = - recoveredClass.getClassHierarchyMap(); - Set directParents = classHierarchyMap.keySet(); - - // use this to get correct parent order and to get the type of parent Map parentToBaseTypeMap = recoveredClass.getParentToBaseTypeMap(); - Set ancestors = parentToBaseTypeMap.keySet(); - for (RecoveredClass ancestor : ancestors) { - monitor.checkCancelled(); - if (directParents.contains(ancestor)) { - Boolean isVirtualParent = parentToBaseTypeMap.get(ancestor); - if (isVirtualParent != null && isVirtualParent) { - classString = classString.concat(" : virtual " + ancestor.getName()); - } - else { - classString = classString.concat(" : " + ancestor.getName()); - } + List parentList = recoveredClass.getParentList(); + for (RecoveredClass parent : parentList) { + monitor.checkCancelled(); + Boolean isVirtualParent = parentToBaseTypeMap.get(parent); + if (isVirtualParent != null && isVirtualParent) { + classString = classString.concat(" : virtual " + parent.getName()); + } + else { + classString = classString.concat(" : " + parent.getName()); } } } From 88952d8309f3b401fc0ae3494e5650603f3f4409 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 14 May 2024 14:47:41 -0400 Subject: [PATCH 011/356] Minor test tweaks --- .../app/util/viewer/field/FieldMouseHandler.java | 14 +++++++------- .../AbstractGhidraHeadlessIntegrationTest.java | 7 ++++++- .../src/main/resources/generic.log4jtest.xml | 3 ++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FieldMouseHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FieldMouseHandler.java index 3327d88915..80050dfa3f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FieldMouseHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FieldMouseHandler.java @@ -15,13 +15,12 @@ */ package ghidra.app.util.viewer.field; -import ghidra.app.nav.Navigatable; -import ghidra.framework.plugintool.ServiceProvider; -import ghidra.program.util.ProgramLocation; - import java.awt.event.MouseEvent; import docking.widgets.fieldpanel.field.Field; +import ghidra.app.nav.Navigatable; +import ghidra.framework.plugintool.ServiceProvider; +import ghidra.program.util.ProgramLocation; public interface FieldMouseHandler { /** @@ -32,16 +31,17 @@ public interface FieldMouseHandler { * @param clickedObject The object that was clicked * @param sourceNavigatable The source navigatable that was clicked upon. * @param programLocation The location at the time the click was made. Due to swing delay, this - * location may not be the same as you would get if you asked the navagatable for the current + * location may not be the same as you would get if you asked the navigatable for the current * location.SC * @param mouseEvent The mouse event that triggered the click * @param serviceProvider A service provider used to access system resources. * @return true if this handler wishes to have exclusive handling rights to processing the * clickedObject - * @see ListingField#getClickedObject(ghidra.util.bean.field.FieldLocation) + * @see ListingField#getClickedObject(docking.widgets.fieldpanel.support.FieldLocation) */ public boolean fieldElementClicked(Object clickedObject, Navigatable sourceNavigatable, - ProgramLocation programLocation, MouseEvent mouseEvent, ServiceProvider serviceProvider); + ProgramLocation programLocation, MouseEvent mouseEvent, + ServiceProvider serviceProvider); /** * Returns an array of types that this handler wishes to handle. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java index c3a9b33fdc..215787b6e3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java @@ -180,7 +180,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock * @return result of command applyTo method * @throws RollbackException thrown if thrown by command applyTo method */ - public static boolean applyCmd(Program program, Command cmd) throws RollbackException { + public static boolean applyCmd(Program program, Command cmd) throws RollbackException { int txId = program.startTransaction(cmd.getName()); boolean commit = true; try { @@ -498,6 +498,11 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock waitForSwing(); } + public void clearSelection(PluginTool tool, Program p) { + AddressSet set = new AddressSet(); + makeSelection(tool, p, set); + } + /** * Returns the global symbol with the given name if and only if it is the only global symbol * with that name. diff --git a/Ghidra/Framework/Generic/src/main/resources/generic.log4jtest.xml b/Ghidra/Framework/Generic/src/main/resources/generic.log4jtest.xml index 4ee5f636f5..c0ebeecd45 100644 --- a/Ghidra/Framework/Generic/src/main/resources/generic.log4jtest.xml +++ b/Ghidra/Framework/Generic/src/main/resources/generic.log4jtest.xml @@ -70,7 +70,8 @@ - + + From 45a67b6d80cf0e90107c2a8dc62cc623dda05c1d Mon Sep 17 00:00:00 2001 From: Anton Fedorov Date: Tue, 14 May 2024 21:51:51 +0200 Subject: [PATCH 012/356] Add "hex" into "Loadable files" filter to highlight support for loading hex filex. --- .../src/main/java/ghidra/plugin/importer/ImporterUtilities.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java index c05cbcd61e..67901e57bc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java @@ -60,7 +60,7 @@ public class ImporterUtilities { * TODO: will be refactored to use file_extension_icon.xml file info. */ public static final GhidraFileFilter LOADABLE_FILES_FILTER = ExtensionFileFilter.forExtensions( - "Loadable files", "exe", "dll", "obj", "drv", "bin", "o", "a", "so", "class", "lib"); + "Loadable files", "exe", "dll", "obj", "drv", "bin", "hex", "o", "a", "so", "class", "lib"); /** * File extension filter for well known 'container' files for GhidraFileChoosers. From 4d83de9f062a062763f0a3fb852bcf89873187a6 Mon Sep 17 00:00:00 2001 From: Sleigh-InSPECtor Date: Wed, 15 May 2024 17:37:42 +0930 Subject: [PATCH 013/356] x86: Add missing float-to-integer cast operation to CVTSD2SI/CVTSD2SI --- Ghidra/Processors/x86/data/languages/ia.sinc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Ghidra/Processors/x86/data/languages/ia.sinc b/Ghidra/Processors/x86/data/languages/ia.sinc index a9936769db..51485ac218 100644 --- a/Ghidra/Processors/x86/data/languages/ia.sinc +++ b/Ghidra/Processors/x86/data/languages/ia.sinc @@ -5207,12 +5207,12 @@ CMPSS_OPERAND: ", "^imm8 is imm8 { } @ifdef IA64 :CVTSD2SI Reg64, m64 is $(LONGMODE_ON) & vexMode=0 & opsize=2 & $(PRE_F2) & byte=0x0F; byte=0x2D; Reg64 ... & m64 { - Reg64 = round(m64); + Reg64 = trunc(round(m64)); } :CVTSD2SI Reg64, XmmReg2 is vexMode=0 & opsize=2 & $(PRE_F2) & byte=0x0F; byte=0x2D; xmmmod=3 & Reg64 & XmmReg2 { - Reg64 = round(XmmReg2[0,64]); + Reg64 = trunc(round(XmmReg2[0,64])); } @endif @@ -5262,12 +5262,12 @@ CMPSS_OPERAND: ", "^imm8 is imm8 { } :CVTSS2SI Reg32, m32 is vexMode=0 & $(PRE_F3) & byte=0x0F; byte=0x2D; Reg32 ... & m32 { - Reg32 = round(m32); + Reg32 = trunc(round(m32)); } :CVTSS2SI Reg32, XmmReg2 is vexMode=0 & $(PRE_F3) & byte=0x0F; byte=0x2D; xmmmod=3 & Reg32 & XmmReg2 { - Reg32 = round(XmmReg2[0,32]); + Reg32 = trunc(round(XmmReg2[0,32])); } @ifdef IA64 From 614d50fcfa5ddd3f089d9023ec91d19d0d989c57 Mon Sep 17 00:00:00 2001 From: Sleigh-InSPECtor Date: Thu, 16 May 2024 12:08:49 +0930 Subject: [PATCH 014/356] AArch32: fixed sha1su0.32 * fixed destructive 64-bit left shift on a 64-bit value before zext --- Ghidra/Processors/ARM/data/languages/ARMneon.sinc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Ghidra/Processors/ARM/data/languages/ARMneon.sinc b/Ghidra/Processors/ARM/data/languages/ARMneon.sinc index eb0da7014a..f558f7f921 100644 --- a/Ghidra/Processors/ARM/data/languages/ARMneon.sinc +++ b/Ghidra/Processors/ARM/data/languages/ARMneon.sinc @@ -619,9 +619,9 @@ define pcodeop SHA1HashUpdateParity; local op1 = Qd; local op2 = Qn; local op3 = Qm; - local op2lo:8 = op2(0); - local op1hi:8 = op1(8); - op2 = zext(op2lo << 64) | zext(op1hi); + local op2LowerHalf = zext(op2[0,64]) << 64; + local op1UpperHalf = zext(op1[64,64]); + op2 = op2LowerHalf | op1UpperHalf; Qd = op1 ^ op2 ^ op3; } From 45781680c457c9ba071184c352d42418273cc84a Mon Sep 17 00:00:00 2001 From: Sleigh-InSPECtor Date: Thu, 16 May 2024 13:37:25 +0930 Subject: [PATCH 015/356] AArch32: fixed sha1su1.32 * fixed destructive left shifts on 32-bit values before zext --- Ghidra/Processors/ARM/data/languages/ARMneon.sinc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Ghidra/Processors/ARM/data/languages/ARMneon.sinc b/Ghidra/Processors/ARM/data/languages/ARMneon.sinc index eb0da7014a..206df85d64 100644 --- a/Ghidra/Processors/ARM/data/languages/ARMneon.sinc +++ b/Ghidra/Processors/ARM/data/languages/ARMneon.sinc @@ -637,15 +637,15 @@ define pcodeop SHA1HashUpdateParity; local X = Qd; local Y = Qm; local Tm = X ^ (Y >> 32); - local t0:4 = Tm(0); - local t1:4 = Tm(4); - local t2:4 = Tm(8); - local t3:4 = Tm(12); + local t0:4 = Tm[0, 32]; + local t1:4 = Tm[32, 32]; + local t2:4 = Tm[64, 32]; + local t3:4 = Tm[96, 32]; local W0:4 = (t0 << 1 | t0 >> 31); local W1:4 = (t1 << 1 | t1 >> 31); local W2:4 = (t2 << 1 | t2 >> 31); local W3:4 = (t3 << 1 | t3 >> 31) ^ (t0 << 2 | t0 >> 30); - Qd = zext(W3 << 96) | zext(W2 << 64) | zext(W1 << 32) | zext(W0); + Qd = (zext(W3) << 96) | (zext(W2) << 64) | (zext(W1) << 32) | zext(W0); } ####### From 62c623cbcded05b069e40c6bb5c090eaddb0c0d4 Mon Sep 17 00:00:00 2001 From: Sleigh-InSPECtor Date: Thu, 16 May 2024 18:09:04 +0930 Subject: [PATCH 016/356] AArch32: fixed ldaexd * fixed addr src register aliased as first dest register of a double word load causing second word load at the value of first load --- .../Processors/ARM/data/languages/ARMv8.sinc | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Ghidra/Processors/ARM/data/languages/ARMv8.sinc b/Ghidra/Processors/ARM/data/languages/ARMv8.sinc index 96ebdae44a..79ff865b16 100644 --- a/Ghidra/Processors/ARM/data/languages/ARMv8.sinc +++ b/Ghidra/Processors/ARM/data/languages/ARMv8.sinc @@ -119,13 +119,14 @@ dcps_lev:3 is TMode=1 & thv_c0001=0b11 { export 3:1; } :ldaexd^COND Rd,Rd2,[Rn] is TMode=0 & ARMcond=1 & COND & c2027=0x1b & Rn & Rd & Rd2 & c0011=0xe9f { + local addr:4 = Rn; build COND; @if ENDIAN == "big" - Rd = *(Rn + 4); - Rd2 = *(Rn); + Rd = *(addr + 4); + Rd2 = *(addr); @else # ENDIAN == "little" - Rd = *(Rn); - Rd2 = *(Rn + 4); + Rd = *(addr); + Rd2 = *(addr + 4); @endif # ENDIAN == "little" } @@ -134,13 +135,14 @@ dcps_lev:3 is TMode=1 & thv_c0001=0b11 { export 3:1; } is TMode=1 & thv_c2031=0b111010001101 & thv_c0407=0b1111 & ItCond & thv_Rt & thv_Rt2 & thv_Rn { + local addr:4 = thv_Rn; build ItCond; @if ENDIAN == "big" - thv_Rt = *(thv_Rn + 4); - thv_Rt2 = *(thv_Rn); + thv_Rt = *(addr + 4); + thv_Rt2 = *(addr); @else # ENDIAN == "little" - thv_Rt = *(thv_Rn); - thv_Rt2 = *(thv_Rn + 4); + thv_Rt = *(addr); + thv_Rt2 = *(addr + 4); @endif # ENDIAN == "little" } From 9f883022a43058eb4b51ebf880c0dfd6dccc856c Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Thu, 16 May 2024 06:43:27 -0400 Subject: [PATCH 017/356] GP-4563: Support for searching for libraries inside a GFileSystem --- .../Base/data/base.icons.theme.properties | 1 + .../FileSystemBrowserPlugin.html | 13 +++ .../importer/LibrarySearchPathManager.java | 43 +++++++- .../opinion/AbstractLibrarySupportLoader.java | 101 ++++++++---------- .../plugins/fsbrowser/FSBActionManager.java | 37 ++++++- .../plugins/fsbrowser/ImageManager.java | 1 + .../pathmanager/PathnameTablePanel.java | 6 +- 7 files changed, 140 insertions(+), 62 deletions(-) diff --git a/Ghidra/Features/Base/data/base.icons.theme.properties b/Ghidra/Features/Base/data/base.icons.theme.properties index 19323dd601..648f2fdb88 100644 --- a/Ghidra/Features/Base/data/base.icons.theme.properties +++ b/Ghidra/Features/Base/data/base.icons.theme.properties @@ -366,6 +366,7 @@ icon.plugin.fsbrowser.import = images/famfamfam_silk_icons_v013/application_get. icon.plugin.fsbrowser.ios = images/famfamfam_silk_icons_v013/phone.png icon.plugin.fsbrowser.open.all = images/famfamfam_silk_icons_v013/application_cascade.png icon.plugin.fsbrowser.list.mounted = downArrow.png +icon.plugin.fsbrowser.library = images/imported_bookmark.gif icon.base.util.fixed.bit.size.field = icon.pulldown diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FileSystemBrowserPlugin/FileSystemBrowserPlugin.html b/Ghidra/Features/Base/src/main/help/help/topics/FileSystemBrowserPlugin/FileSystemBrowserPlugin.html index 6c49462fa8..91ce4338b4 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FileSystemBrowserPlugin/FileSystemBrowserPlugin.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/FileSystemBrowserPlugin/FileSystemBrowserPlugin.html @@ -90,6 +90,12 @@ using the Batch Import dialog.

+

Add To Program

+ +
+

Adds the selected file into the currently active program.

+
+

Export

@@ -103,6 +109,13 @@

Recursively copies the contents of a selected folder to a directory you select on your local computer.

+ +

Add Library Search Path

+ +
+

Adds the currently selected file or folder to the list of library search paths which + is used during program import.

+

View As Image

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathManager.java index 0b0ab10c81..739e70ca65 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathManager.java @@ -15,9 +15,16 @@ */ package ghidra.app.util.importer; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; import java.util.*; +import ghidra.formats.gfilesystem.FSRL; +import ghidra.formats.gfilesystem.FileSystemService; import ghidra.framework.Platform; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; /** * A simple class for managing the library search path @@ -61,11 +68,39 @@ public class LibrarySearchPathManager { } /** - * Returns an array of directories to search for libraries - * @return a list of directories to search for libraries + * Returns a {@link List} of {@link FSRL}s to search for libraries + * @param log The log + * @param monitor A cancellable monitor + * @return a {@link List} of {@link FSRL}s to search for libraries + * @throws CancelledException if the user cancelled the operation */ - public static List getLibraryPathsList() { - return new ArrayList<>(pathList); + public static List getLibraryFsrlList(MessageLog log, TaskMonitor monitor) + throws CancelledException { + FileSystemService fsService = FileSystemService.getInstance(); + List fsrlList = new ArrayList<>(); + for (String path : pathList) { + monitor.checkCancelled(); + path = path.trim(); + FSRL fsrl = null; + try { + fsrl = FSRL.fromString(path); + } + catch (MalformedURLException e) { + try { + File f = new File(path); + if (f.exists() && f.isAbsolute()) { + fsrl = fsService.getLocalFSRL(f.getCanonicalFile()); + } + } + catch (IOException e2) { + log.appendException(e2); + } + } + if (fsrl != null) { + fsrlList.add(fsrl); + } + } + return fsrlList; } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java index 6e08264c11..482f68a82a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java @@ -17,9 +17,11 @@ package ghidra.app.util.opinion; import java.io.File; import java.io.IOException; -import java.nio.file.*; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.ObjectUtils; @@ -584,16 +586,13 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader if (!success) { release(loadedPrograms, consumer); } - for (FileSystemSearchPath fsSearchPath : localSearchPaths) { - if (!fsSearchPath.fsRef().isClosed()) { - fsSearchPath.fsRef().close(); - } - } - for (FileSystemSearchPath fsSearchPath : systemSearchPaths) { - if (!fsSearchPath.fsRef().isClosed()) { - fsSearchPath.fsRef().close(); - } - } + Stream.of(customSearchPaths, localSearchPaths, systemSearchPaths) + .flatMap(Collection::stream) + .forEach(fsSearchPath -> { + if (!fsSearchPath.fsRef().isClosed()) { + fsSearchPath.fsRef().close(); + } + }); } } @@ -1061,9 +1060,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader * @param monitor A cancelable task monitor * @return A {@link List} of priority-ordered custom {@link FileSystemSearchPath}s used to * search for libraries + * @throws CancelledException if the user cancelled the load */ protected List getCustomLibrarySearchPaths(ByteProvider provider, - List