Merge remote-tracking branch 'origin/master' into debugger

This commit is contained in:
Dan 2021-04-12 10:09:49 -04:00
commit 62bd317380
32 changed files with 1511 additions and 996 deletions

2
.gitignore vendored
View file

@ -3,7 +3,7 @@ ghidra.repos.config
# Misc files produced while executing application # Misc files produced while executing application
repositories/ repositories/
flatRepo/ dependencies/
Ghidra/.ghidraSvrKeys Ghidra/.ghidraSvrKeys
wrapper.log* wrapper.log*

View file

@ -34,7 +34,7 @@ You may not need all of these, depending on which portions you are building or d
- https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot - https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot
- Amazon Corretto - Amazon Corretto
- https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html - https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html
* Gradle 5.0 or later - We use version 5.0, and tested with up to 5.6.3. * Gradle 5.0 or later - We use version 5.0, and tested with up to 6.8.3.
- https://gradle.org/next-steps/?version=5.0&format=bin - https://gradle.org/next-steps/?version=5.0&format=bin
* A C/C++ compiler - We use GCC on Linux, Xcode (Clang) on macOS, and Visual Studio (2017 or later) on Windows. * A C/C++ compiler - We use GCC on Linux, Xcode (Clang) on macOS, and Visual Studio (2017 or later) on Windows.
- https://gcc.gnu.org/ - https://gcc.gnu.org/
@ -62,9 +62,9 @@ You may not need all of these, depending on which portions you are building or d
- https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-12.12/ - https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-12.12/
* Eclipse PDE - Environment for developing the GhidraDev plugin. * Eclipse PDE - Environment for developing the GhidraDev plugin.
- https://www.eclipse.org/pde/ - https://www.eclipse.org/pde/
* Eclipse CDT. We use version 8.6.0 - Build dependency for the GhidraDev plugin. * Eclipse CDT. We build against version 8.6.0 - Build dependency for the GhidraDev plugin.
- https://www.eclipse.org/cdt/ - https://www.eclipse.org/cdt/
* PyDev. We use version 6.3.1 - Build dependency for the GhidraDev plugin. * PyDev. We build against version 6.3.1 - Build dependency for the GhidraDev plugin.
- https://sourceforge.net/projects/pydev/files/pydev/ - https://sourceforge.net/projects/pydev/files/pydev/
There are many, many others automatically downloaded by Gradle from Maven Central and Bintray JCenter when building and/or setting up the development environment. There are many, many others automatically downloaded by Gradle from Maven Central and Bintray JCenter when building and/or setting up the development environment.
@ -111,64 +111,64 @@ or manually by downloading the required dependencies. Choose one of the two fol
The flat directory-style repository can be setup automatically by running a simple Gradle script. The flat directory-style repository can be setup automatically by running a simple Gradle script.
Navigate to `~/git/ghidra` and run the following: Navigate to `~/git/ghidra` and run the following:
``` ```
gradle --init-script gradle/support/fetchDependencies.gradle init gradle -I gradle/support/fetchDependencies.gradle init
``` ```
The Gradle task to be executed, in this case _init_, is unimportant. The point is to have Gradle execute The Gradle task to be executed, in this case _init_, is unimportant. The point is to have Gradle execute
the `fetchDependencies.gradle` script. If it ran correctly you will have a new `~/git/ghidra/flatRepo/` the `fetchDependencies.gradle` script. If it ran correctly you will have a new `~/git/ghidra/dependencies/`
directory populated with the following jar files: directory populated with the following files:
* AXMLPrinter2 * flatRepo/AXMLPrinter2.jar
* csframework * flatRepo/csframework.jar
* dex-ir-2.0 * flatRepo/dex-ir-2.0.jar
* dex-reader-2.0 * flatRepo/dex-reader-2.0.jar
* dex-reader-api-2.0 * flatRepo/dex-reader-api-2.0.jar
* dex-tools-2.0 * flatRepo/dex-tools-2.0.jar
* dex-translator-2.0 * flatRepo/dex-translator-2.0.jar
* dex-writer-2.0 * flatRepo/dex-writer-2.0.jar
* hfsx * flatRepo/hfsx.jar
* hfsx_dmglib * flatRepo/hfsx_dmglib.jar
* iharder-base64 * flatRepo/iharder-base64.jar
* cdt-8.6.0.zip
There will also be a new archive files at: * PyDev 6.3.1.zip
* ~/git/ghidra/Ghidra/Features/GhidraServer/build/`yajsw-stable-12.12.zip` * yajsw-stable-12.12.zip
* ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/`PyDev 6.3.1.zip` * fid/*.fidb
* ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/`cdt-8.6.0.zip`
If you see these, congrats! Skip to [building](#building-ghidra) or [developing](#developing-ghidra). If not, continue with manual download If you see these, congrats! Skip to [building](#building-ghidra) or [developing](#developing-ghidra). If not, continue with manual download
instructions below... instructions below...
### Manual Download Instructions ### Manual Download Instructions
Create the `~/git/ghidra/flatRepo/` directory to hold the manually-downloaded dependencies: Create the `~/git/ghidra/dependencies/` and `~/git/ghidra/dependencies/flatRepo` directories to hold the manually-downloaded dependencies:
```bash ```bash
mkdir ~/git/ghidra/flatRepo mkdir ~/git/ghidra/dependencies
mkdir ~/git/ghidra/dependencies/flatRepo
``` ```
#### Get Dependencies for FileFormats: #### Get Dependencies for FileFormats:
Download `dex-tools-2.0.zip` from the dex2jar project's releases page on GitHub. Download `dex-tools-2.0.zip` from the dex2jar project's releases page on GitHub.
Unpack the `dex-*.jar` files from the `lib` directory to `~/git/ghidra/flatRepo`: Unpack the `dex-*.jar` files from the `lib` directory to `~/git/ghidra/dependencies/flatRepo`:
```bash ```bash
cd ~/Downloads # Or wherever cd ~/Downloads # Or wherever
curl -OL https://github.com/pxb1988/dex2jar/releases/download/2.0/dex-tools-2.0.zip curl -OL https://github.com/pxb1988/dex2jar/releases/download/2.0/dex-tools-2.0.zip
unzip dex-tools-2.0.zip unzip dex-tools-2.0.zip
cp dex2jar-2.0/lib/dex-*.jar ~/git/ghidra/flatRepo/ cp dex2jar-2.0/lib/dex-*.jar ~/git/ghidra/dependencies/flatRepo/
``` ```
Download `AXMLPrinter2.jar` from the "android4me" archive on code.google.com. Download `AXMLPrinter2.jar` from the "android4me" archive on code.google.com.
Place it in `~/git/ghidra/flatRepo`: Place it in `~/git/ghidra/dependencies/flatRepo`:
```bash ```bash
cd ~/git/ghidra/flatRepo cd ~/git/ghidra/dependencies/flatRepo
curl -OL https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/android4me/AXMLPrinter2.jar curl -OL https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/android4me/AXMLPrinter2.jar
``` ```
#### Get Dependencies for DMG: #### Get Dependencies for DMG:
Download `hfsexplorer-0_21-bin.zip` from www.catacombae.org. Download `hfsexplorer-0_21-bin.zip` from www.catacombae.org.
Unpack the `lib` directory to `~/git/ghidra/flatRepo`: Unpack the `lib` directory to `~/git/ghidra/dependencies/flatRepo`:
```bash ```bash
cd ~/Downloads # Or wherever cd ~/Downloads # Or wherever
@ -177,35 +177,33 @@ mkdir hfsx
cd hfsx cd hfsx
unzip ../hfsexplorer-0_21-bin.zip unzip ../hfsexplorer-0_21-bin.zip
cd lib cd lib
cp csframework.jar hfsx_dmglib.jar hfsx.jar iharder-base64.jar ~/git/ghidra/flatRepo/ cp csframework.jar hfsx_dmglib.jar hfsx.jar iharder-base64.jar ~/git/ghidra/dependencies/flatRepo/
``` ```
#### Get Dependencies for GhidraServer #### Get Dependencies for GhidraServer
Building the GhidraServer requires "Yet another Java service wrapper" (yajsw) version 12.12. Building the GhidraServer requires "Yet another Java service wrapper" (yajsw) version 12.12.
Download `yajsw-stable-12.12.zip` from their project on www.sourceforge.net, and place it in: Download `yajsw-stable-12.12.zip` from their project on www.sourceforge.net, and place it in:
`~/git/ghidra/Ghidra/Features/GhidraServer/build`: `~/git/ghidra/dependencies/`:
```bash ```bash
cd ~/Downloads # Or wherever cd ~/Downloads # Or wherever
curl -OL https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-12.12/yajsw-stable-12.12.zip curl -OL https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-12.12/yajsw-stable-12.12.zip
mkdir -p ~/git/ghidra/Ghidra/Features/GhidraServer/build/ cp ~/Downloads/yajsw-stable-12.12.zip ~/git/ghidra/dependencies/
cp ~/Downloads/yajsw-stable-12.12.zip ~/git/ghidra/Ghidra/Features/GhidraServer/build/
``` ```
#### Get Dependencies for GhidraDev #### Get Dependencies for GhidraDev
Building the GhidraDev plugin for Eclipse requires the CDT and PyDev plugins for Eclipse. Building the GhidraDev plugin for Eclipse requires the CDT and PyDev plugins for Eclipse.
Download `cdt-8.6.0.zip` from The Eclipse Foundation, and place it in: Download `cdt-8.6.0.zip` from The Eclipse Foundation, and place it in:
`~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/`: `~/git/ghidra/dependencies/`:
```bash ```bash
cd ~/Downloads # Or wherever cd ~/Downloads # Or wherever
curl -OL 'https://archive.eclipse.org/tools/cdt/releases/8.6/cdt-8.6.0.zip' curl -OL 'https://archive.eclipse.org/tools/cdt/releases/8.6/cdt-8.6.0.zip'
curl -o 'cdt-8.6.0.zip.sha512' -L --retry 3 'https://www.eclipse.org/downloads/sums.php?type=sha512&file=/tools/cdt/releases/8.6/cdt-8.6.0.zip' curl -o 'cdt-8.6.0.zip.sha512' -L --retry 3 'https://www.eclipse.org/downloads/sums.php?type=sha512&file=/tools/cdt/releases/8.6/cdt-8.6.0.zip'
shasum -a 512 -c 'cdt-8.6.0.zip.sha512' shasum -a 512 -c 'cdt-8.6.0.zip.sha512'
mkdir -p ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/ cp ~/Downloads/cdt-8.6.0.zip ~/git/ghidra/dependencies/
cp ~/Downloads/cdt-8.6.0.zip ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/
``` ```
Download `PyDev 6.3.1.zip` from www.pydev.org, and place it in the same directory: Download `PyDev 6.3.1.zip` from www.pydev.org, and place it in the same directory:
@ -213,7 +211,7 @@ Download `PyDev 6.3.1.zip` from www.pydev.org, and place it in the same director
```bash ```bash
cd ~/Downloads # Or wherever cd ~/Downloads # Or wherever
curl -L -o 'PyDev 6.3.1.zip' https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip curl -L -o 'PyDev 6.3.1.zip' https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip
cp ~/Downloads/'PyDev 6.3.1.zip' ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/ cp ~/Downloads/'PyDev 6.3.1.zip' ~/git/ghidra/dependencies/
``` ```
## Building Ghidra ## Building Ghidra

View file

@ -71,6 +71,12 @@
option, OR</li> option, OR</li>
<li>Select the <img src="Icons.REFRESH_ICON">button on the tool bar.</li> <li>Select the <img src="Icons.REFRESH_ICON">button on the tool bar.</li>
</ul> </ul>
<p><img border="0" src="../../shared/note.png">The refresh icon on the toolbar will
appear grayed-out by default. If potential changes to string data are detected,
the icon will become green in color. The toolbar button can be pressed in either state
for a full table reload.
</blockquote> </blockquote>
<h3><a name="Settings___"></a><a name="Default_Settings___"></a>Settings... and Default Settings...</h3> <h3><a name="Settings___"></a><a name="Default_Settings___"></a>Settings... and Default Settings...</h3>

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.core.analysis;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.demangler.*; import ghidra.app.util.demangler.*;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address; import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -41,6 +40,9 @@ import ghidra.util.task.TaskMonitor;
*/ */
public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer { public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer {
private static final AddressSetView EXTERNAL_SET = new AddressSet(
AddressSpace.EXTERNAL_SPACE.getMinAddress(), AddressSpace.EXTERNAL_SPACE.getMaxAddress());
public AbstractDemanglerAnalyzer(String name, String description) { public AbstractDemanglerAnalyzer(String name, String description) {
super(name, description, AnalyzerType.BYTE_ANALYZER); super(name, description, AnalyzerType.BYTE_ANALYZER);
setPriority(AnalysisPriority.DATA_TYPE_PROPOGATION.before().before().before()); setPriority(AnalysisPriority.DATA_TYPE_PROPOGATION.before().before().before());
@ -59,6 +61,8 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer {
try { try {
monitor.setIndeterminate(true); monitor.setIndeterminate(true);
// NOTE: demangling of Externals may lose mangled name if original
// imported name has already been assigned to the External symbol (e.g., ordinal based name)
return doAdded(program, set, monitor, log); return doAdded(program, set, monitor, log);
} }
finally { finally {
@ -76,17 +80,41 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer {
return false; return false;
} }
int count = 0; // Demangle external symbols after memory symbols.
// This is done to compensate for cases where the mangled name on externals may be lost
// after demangling when an alternate Ordinal symbol exists. The external mangled
// name is helpful in preserving thunk relationships when a mangled symbols have been
// placed on a thunk. It is assumed that analyzer is presented with entire
// EXTERNAL space in set (all or none).
boolean demangleExternals = set.contains(EXTERNAL_SET.getMinAddress());
if (demangleExternals) {
set = set.subtract(EXTERNAL_SET);
}
String defaultMessage = monitor.getMessage(); String baseMonitorMessage = monitor.getMessage();
int memorySymbolCount =
demangleSymbols(program, set, 0, baseMonitorMessage, options, log, monitor);
if (demangleExternals) {
// process external symbols last
demangleSymbols(program, EXTERNAL_SET, memorySymbolCount, baseMonitorMessage, options,
log, monitor);
}
return true;
}
private int demangleSymbols(Program program, AddressSetView set, int initialCount,
String baseMonitorMessage, DemanglerOptions options, MessageLog log,
TaskMonitor monitor) throws CancelledException {
int count = initialCount;
SymbolTable symbolTable = program.getSymbolTable(); SymbolTable symbolTable = program.getSymbolTable();
SymbolIterator it = symbolTable.getPrimarySymbolIterator(set, true); SymbolIterator it = symbolTable.getPrimarySymbolIterator(set, true);
while (it.hasNext()) { while (it.hasNext()) {
monitor.checkCanceled(); monitor.checkCanceled();
if (++count % 100 == 0) { if (++count % 100 == 0) {
monitor.setMessage(defaultMessage + " - " + count + " symbols"); monitor.setMessage(baseMonitorMessage + " - " + count + " symbols");
} }
Symbol symbol = it.next(); Symbol symbol = it.next();
@ -101,8 +129,7 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer {
apply(program, address, demangled, options, log, monitor); apply(program, address, demangled, options, log, monitor);
} }
} }
return count;
return true;
} }
/** /**
@ -152,10 +179,13 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer {
return true; return true;
} }
// Someone has already added arguments or return to the function signature // Someone has already added arguments or return to the function signature.
// Treatment of thunks must be handled later since thunk relationship may
// need to be broken
if (symbol.getSymbolType() == SymbolType.FUNCTION) { if (symbol.getSymbolType() == SymbolType.FUNCTION) {
Function function = (Function) symbol.getObject(); Function function = (Function) symbol.getObject();
if (function.getSignatureSource().isHigherPriorityThan(SourceType.ANALYSIS)) { if (!function.isThunk() &&
function.getSignatureSource().isHigherPriorityThan(SourceType.ANALYSIS)) {
return true; return true;
} }
} }

View file

@ -18,25 +18,21 @@ package ghidra.app.plugin.core.datamgr.editor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.ComboBoxModel;
import javax.swing.JPanel;
import docking.ComponentProvider; import docking.ComponentProvider;
import docking.actions.DockingToolActions; import docking.actions.DockingToolActions;
import docking.actions.SharedDockingActionPlaceholder; import docking.actions.SharedDockingActionPlaceholder;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GLabel;
import ghidra.app.plugin.core.compositeeditor.*; import ghidra.app.plugin.core.compositeeditor.*;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.function.EditFunctionSignatureDialog; import ghidra.app.plugin.core.function.AbstractEditFunctionSignatureDialog;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum; import ghidra.program.model.data.Enum;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.FunctionSignature;
import ghidra.program.model.listing.Program;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.*; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
/** /**
* Manages program and archive data type editors. * Manages program and archive data type editors.
@ -238,8 +234,8 @@ public class DataTypeEditorManager
list.add(editor); list.add(editor);
} }
} }
for (int i = 0; i < list.size(); i++) { for (EditorProvider element : list) {
dismissEditor(list.get(i)); dismissEditor(element);
} }
} }
@ -519,55 +515,10 @@ public class DataTypeEditorManager
editFunctionSignature(category, functionDefinition); editFunctionSignature(category, functionDefinition);
} }
private void editFunctionSignature(final Category category, private void editFunctionSignature(Category category, FunctionDefinition functionDefinition) {
final FunctionDefinition functionDefinition) {
Function function =
new UndefinedFunction(plugin.getProgram(), plugin.getProgram().getMinAddress()) {
@Override
public String getCallingConventionName() {
if (functionDefinition == null) {
return super.getCallingConventionName();
}
return functionDefinition.getGenericCallingConvention().toString();
}
@Override
public void setCallingConvention(String name) throws InvalidInputException {
// no-op; we handle this in the editor dialog
}
@Override
public void setInline(boolean isInline) {
// can't edit this from the DataTypeManager
}
@Override
public void setNoReturn(boolean hasNoReturn) {
// can't edit this from the DataTypeManager
}
@Override
public FunctionSignature getSignature() {
if (functionDefinition != null) {
return functionDefinition;
}
return super.getSignature();
}
@Override
public String getName() {
if (functionDefinition != null) {
return functionDefinition.getName();
}
return "newFunction";
}
};
// DT how do I do the same as the other creates.
PluginTool tool = plugin.getTool(); PluginTool tool = plugin.getTool();
DTMEditFunctionSignatureDialog editSigDialog = new DTMEditFunctionSignatureDialog( DTMEditFunctionSignatureDialog editSigDialog = new DTMEditFunctionSignatureDialog(
plugin.getTool(), "Edit Function Signature", function, category, functionDefinition); plugin.getTool(), "Edit Function Signature", category, functionDefinition);
editSigDialog.setHelpLocation( editSigDialog.setHelpLocation(
new HelpLocation("DataTypeManagerPlugin", "Function_Definition")); new HelpLocation("DataTypeManagerPlugin", "Function_Definition"));
tool.showDialog(editSigDialog); tool.showDialog(editSigDialog);
@ -577,64 +528,73 @@ public class DataTypeEditorManager
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private class DTMEditFunctionSignatureDialog extends EditFunctionSignatureDialog { /**
* <code>DTMEditFunctionSignatureDialog</code> provides the ability to edit the
* function signature associated with a specific {@link FunctionDefinition}.
* Use of this editor requires the presence of the tool-based datatype manager service.
*/
private class DTMEditFunctionSignatureDialog extends AbstractEditFunctionSignatureDialog {
private final FunctionDefinition functionDefinition;
private final FunctionSignature oldSignature;
private final Category category; private final Category category;
private final FunctionDefinition functionDefinitionDataType;
DTMEditFunctionSignatureDialog(PluginTool pluginTool, String title, Function function, DTMEditFunctionSignatureDialog(PluginTool pluginTool, String title, Category category,
Category category, FunctionDefinition functionDefinition) { FunctionDefinition functionDefinition) {
super(pluginTool, title, function); super(pluginTool, title, false, false, false);
this.functionDefinition = functionDefinition;
this.category = category; this.category = category;
this.functionDefinitionDataType = functionDefinition; this.oldSignature = buildSignature();
if (functionDefinitionDataType != null) {
setCallingConvention(
functionDefinitionDataType.getGenericCallingConvention().toString());
} }
private FunctionSignature buildSignature() {
if (functionDefinition != null) {
if (category.getDataTypeManager() != functionDefinition.getDataTypeManager()) {
throw new IllegalArgumentException(
"functionDefinition and category must have same Datatypemanager");
}
return functionDefinition;
}
return new FunctionDefinitionDataType("newFunction");
} }
@Override @Override
protected void installCallingConventionWidget(JPanel parentPanel) { protected String[] getSupportedCallFixupNames() {
callingConventionComboBox = new GhidraComboBox<>(); return null; // Call fixup not supported on FunctionDefinition
}
@Override
protected String getCallFixupName() {
return null; // Call fixup not supported on FunctionDefinition
}
@Override
protected FunctionSignature getFunctionSignature() {
return oldSignature;
}
@Override
protected String getPrototypeString() {
return getFunctionSignature().getPrototypeString();
}
@Override
protected String getCallingConventionName() {
return getFunctionSignature().getGenericCallingConvention().toString();
}
@Override
protected List<String> getCallingConventionNames() {
GenericCallingConvention[] values = GenericCallingConvention.values(); GenericCallingConvention[] values = GenericCallingConvention.values();
String[] choices = new String[values.length]; List<String> choices = new ArrayList<>();
for (int i = 0; i < values.length; i++) { for (GenericCallingConvention value : values) {
choices[i] = values[i].toString(); choices.add(value.toString());
} }
return choices;
setCallingConventionChoices(choices);
parentPanel.add(new GLabel("Calling Convention:"));
parentPanel.add(callingConventionComboBox);
} }
@Override @Override
protected void installInlineWidget(JPanel parentPanel) { protected DataTypeManager getDataTypeManager() {
inlineCheckBox = new GCheckBox("Inline"); return category.getDataTypeManager();
}
@Override
protected void installNoReturnWidget(JPanel parentPanel) {
noReturnCheckBox = new GCheckBox("No Return");
}
@Override
protected void installCallFixupWidget(JPanel parentPanel) {
// don't add this panel
}
@Override
protected void setCallingConvention(String callingConvention) {
ComboBoxModel<?> model = callingConventionComboBox.getModel();
int size = model.getSize();
for (int i = 0; i < size; i++) {
Object item = model.getElementAt(i);
if (item.equals(callingConvention)) {
callingConventionComboBox.setSelectedItem(callingConvention);
return;
}
}
callingConventionComboBox.setSelectedItem(GenericCallingConvention.unknown);
} }
@Override @Override
@ -657,9 +617,9 @@ public class DataTypeEditorManager
GenericCallingConvention.getGenericCallingConvention(getCallingConvention()); GenericCallingConvention.getGenericCallingConvention(getCallingConvention());
newDefinition.setGenericCallingConvention(callingConvention); newDefinition.setGenericCallingConvention(callingConvention);
DataTypeManager manager = category.getDataTypeManager(); DataTypeManager manager = getDataTypeManager();
SourceArchive sourceArchive = manager.getLocalSourceArchive(); SourceArchive sourceArchive = manager.getLocalSourceArchive();
if (functionDefinitionDataType == null) { if (functionDefinition == null) {
newDefinition.setSourceArchive(sourceArchive); newDefinition.setSourceArchive(sourceArchive);
newDefinition.setCategoryPath(category.getCategoryPath()); newDefinition.setCategoryPath(category.getCategoryPath());
int id = manager.startTransaction("Create Function Definition"); int id = manager.startTransaction("Create Function Definition");
@ -669,14 +629,14 @@ public class DataTypeEditorManager
else { else {
int id = manager.startTransaction("Edit Function Definition"); int id = manager.startTransaction("Edit Function Definition");
try { try {
if (!functionDefinitionDataType.getName().equals(newDefinition.getName())) { if (!functionDefinition.getName().equals(newDefinition.getName())) {
functionDefinitionDataType.setName(newDefinition.getName()); functionDefinition.setName(newDefinition.getName());
} }
functionDefinitionDataType.setArguments(newDefinition.getArguments()); functionDefinition.setArguments(newDefinition.getArguments());
functionDefinitionDataType.setGenericCallingConvention( functionDefinition.setGenericCallingConvention(
newDefinition.getGenericCallingConvention()); newDefinition.getGenericCallingConvention());
functionDefinitionDataType.setReturnType(newDefinition.getReturnType()); functionDefinition.setReturnType(newDefinition.getReturnType());
functionDefinitionDataType.setVarArgs(newDefinition.hasVarArgs()); functionDefinition.setVarArgs(newDefinition.hasVarArgs());
} }
catch (InvalidNameException | DuplicateNameException e) { catch (InvalidNameException | DuplicateNameException e) {
// not sure why we are squashing this? ...assuming this can't happen // not sure why we are squashing this? ...assuming this can't happen

View file

@ -0,0 +1,435 @@
/* ###
* 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.function;
import java.awt.Component;
import java.awt.event.ItemEvent;
import java.util.List;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.cparser.C.ParseException;
import ghidra.app.util.parser.FunctionSignatureParser;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FunctionDefinitionDataType;
import ghidra.program.model.listing.FunctionSignature;
import ghidra.util.exception.CancelledException;
/**
* <code>EditFunctionSignatureDialog</code> provides an abstract implementation
* a function signature editor. Use of this editor requires the presence of the tool-based
* datatype manager service.
*/
public abstract class AbstractEditFunctionSignatureDialog extends DialogComponentProvider {
private static final String NONE_CHOICE = "-NONE-";
private static int SIGNATURE_COLUMNS = 60;
protected JLabel signatureLabel;
protected JTextField signatureField;
protected JComboBox<String> callingConventionComboBox;
protected JComboBox<String> callFixupComboBox;
protected JCheckBox inlineCheckBox;
protected JCheckBox noReturnCheckBox;
protected boolean allowInLine;
protected boolean allowNoReturn;
protected boolean allowCallFixup;
protected PluginTool tool;
// Due to delayed initialization and tests not actually displaying dialog
// we will track function info initialization
boolean initialized = false;
/**
* Abstract function signature editor
*
* @param tool A reference to the active tool.
* @param title The title of the dialog.
* @param allowInLine true if in-line attribute control should be included
* @param allowNoReturn true if no-return attribute control should be added
* @param allowCallFixup true if call-fixup choice should be added
*/
public AbstractEditFunctionSignatureDialog(PluginTool tool, String title, boolean allowInLine,
boolean allowNoReturn, boolean allowCallFixup) {
super(title, true, true, true, false);
this.tool = tool;
this.allowInLine = allowInLine;
this.allowNoReturn = allowNoReturn;
this.allowCallFixup = allowCallFixup;
addWorkPanel(buildMainPanel());
addOKButton();
addCancelButton();
setDefaultButton(okButton);
setRememberSize(true);
}
@Override
public JComponent getComponent() {
setFunctionInfo(); //delay update for after construction
return super.getComponent();
}
/**
* @return DataTypeManager associated with function or function definition
*/
protected abstract DataTypeManager getDataTypeManager();
/**
* @return optional initial function signature which can assist parse with
* identifying referenced datatypes within signature
*/
protected abstract FunctionSignature getFunctionSignature();
/**
* @return the initial signature string for the dialog
*/
protected abstract String getPrototypeString();
/**
* @return initial calling convention name
*/
protected abstract String getCallingConventionName();
/**
* @return list of acceptable calling convention names
*/
protected abstract List<String> getCallingConventionNames();
/**
* @return initial in-line attribute value
*/
protected boolean isInline() {
return false;
}
/**
* @return initial no-return attribute value
*/
protected boolean hasNoReturn() {
return false;
}
/**
* @return initial call-fixup name or null if n/a
*/
protected abstract String getCallFixupName();
/**
* @return array of allowed call fixup names or null
*/
protected abstract String[] getSupportedCallFixupNames();
/**
* Method must be invoked following construction to fetch function info
* and update components.
*/
private void setFunctionInfo() {
if (initialized) {
return;
}
initialized = true;
signatureField.setText(getPrototypeString());
setCallingConventionChoices();
callingConventionComboBox.setSelectedItem(getCallingConventionName());
if (allowInLine) {
inlineCheckBox.setSelected(isInline());
}
if (allowNoReturn) {
noReturnCheckBox.setSelected(hasNoReturn());
}
if (allowCallFixup) {
setCallFixupChoices();
String callFixupName = getCallFixupName();
if (callFixupName != null) {
callFixupComboBox.setSelectedItem(callFixupName);
}
}
}
private JPanel buildMainPanel() {
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 2));
mainPanel.add(buildSignaturePanel());
mainPanel.add(buildAttributePanel());
if (allowCallFixup) {
installCallFixupWidget(mainPanel);
}
return mainPanel;
}
private void installCallFixupWidget(JPanel parentPanel) {
JPanel callFixupPanel = buildCallFixupPanel();
parentPanel.add(callFixupPanel != null ? callFixupPanel : buildSpacerPanel());
}
private JPanel buildSignaturePanel() {
JPanel signaturePanel = new JPanel();
signaturePanel.setLayout(new BoxLayout(signaturePanel, BoxLayout.X_AXIS));
signatureField = new JTextField(SIGNATURE_COLUMNS);
signatureLabel = new GDLabel("Signature:");
signaturePanel.add(signatureLabel);
signaturePanel.add(signatureField);
signaturePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
return signaturePanel;
}
private Component buildSpacerPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
panel.add(Box.createVerticalStrut(20));
return panel;
}
private JPanel buildAttributePanel() {
JPanel attributePanel = new JPanel();
attributePanel.setLayout(new BoxLayout(attributePanel, BoxLayout.X_AXIS));
attributePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
installCallingConventionWidget(attributePanel);
if (allowInLine) {
installInlineWidget(attributePanel);
}
if (allowNoReturn) {
installNoReturnWidget(attributePanel);
}
attributePanel.add(Box.createGlue());
return attributePanel;
}
private void installCallingConventionWidget(JPanel parentPanel) {
callingConventionComboBox = new GhidraComboBox<>();
parentPanel.add(new GLabel("Calling Convention:"));
parentPanel.add(callingConventionComboBox);
}
private void installInlineWidget(JPanel parentPanel) {
inlineCheckBox = new GCheckBox("Inline");
inlineCheckBox.addChangeListener(e -> {
if (inlineCheckBox.isSelected() && callFixupComboBox != null) {
callFixupComboBox.setSelectedItem(NONE_CHOICE);
}
});
parentPanel.add(inlineCheckBox);
}
private void installNoReturnWidget(JPanel parentPanel) {
noReturnCheckBox = new GCheckBox("No Return");
parentPanel.add(noReturnCheckBox);
}
private JPanel buildCallFixupPanel() {
if (allowCallFixup) {
return null;
}
JPanel callFixupPanel = new JPanel();
callFixupPanel.setLayout(new BoxLayout(callFixupPanel, BoxLayout.X_AXIS));
callFixupComboBox = new GhidraComboBox<>();
callFixupComboBox.addItemListener(e -> {
if (e.getStateChange() == ItemEvent.DESELECTED) {
return;
}
if (!NONE_CHOICE.equals(e.getItem())) {
inlineCheckBox.setSelected(false);
}
});
callFixupPanel.add(new GLabel("Call-Fixup:"));
callFixupPanel.add(callFixupComboBox);
callFixupPanel.add(Box.createGlue());
callFixupPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
return callFixupPanel;
}
/**
* @return plugin tool for which dialog was constructed
*/
protected PluginTool getTool() {
return tool;
}
private String getSignature() {
return signatureField.getText();
}
private void setCallingConventionChoices() {
callingConventionComboBox.removeAllItems();
for (String element : getCallingConventionNames()) {
callingConventionComboBox.addItem(element);
}
}
/**
* @return current calling convention selection from dialog
*/
protected String getCallingConvention() {
return (String) callingConventionComboBox.getSelectedItem();
}
/**
* @return current in-line attribute value from dialog
*/
protected boolean isInlineSelected() {
return inlineCheckBox != null ? inlineCheckBox.isSelected() : false;
}
/**
* @return current no-return attribute value from dialog
*/
protected boolean hasNoReturnSelected() {
return noReturnCheckBox != null ? noReturnCheckBox.isSelected() : false;
}
private void setCallFixupChoices() {
String[] callFixupNames = getSupportedCallFixupNames();
callFixupComboBox.addItem(NONE_CHOICE);
if (callFixupNames != null) {
for (String element : callFixupNames) {
callFixupComboBox.addItem(element);
}
}
}
/**
* @return current call fixup selection from dialog or null
*/
protected String getCallFixupSelection() {
if (callFixupComboBox != null) {
String callFixup = (String) callFixupComboBox.getSelectedItem();
if (callFixup != null && !NONE_CHOICE.equals(callFixup)) {
return callFixup;
}
}
return null;
}
/**
* This method gets called when the user clicks on the OK Button. The base
* class calls this method. This method will invoke {@link #applyChanges()}
* and close dialog if that method returns true. If false is returned, the
* {@link #applyChanges()} method should display a status message to indicate
* the failure.
*/
@Override
protected void okCallback() {
// only close the dialog if the user made valid changes
try {
if (applyChanges()) {
close();
}
}
catch (CancelledException e) {
// ignore - do not close
}
}
@Override
protected void cancelCallback() {
setStatusText("");
close();
}
/**
* Called when the user initiates changes that need to be applied to the
* underlying function or function definition
*
* @return true if applied successfully, otherwise false which will keep
* dialog displayed (a status message should bet set)
* @throws CancelledException if operation cancelled by user
*/
protected abstract boolean applyChanges() throws CancelledException;
/**
* Perform parse of current user-specified function signature (see {@link #getSignature()})
* and return valid {@link FunctionDefinitionDataType} if parse successful.
* @return function definition data type if parse successful, otherwise null
* @throws CancelledException if function signature entry cancelled
*/
protected final FunctionDefinitionDataType parseSignature() throws CancelledException {
setFunctionInfo(); // needed for testing which never shows dialog
FunctionSignatureParser parser = new FunctionSignatureParser(
getDataTypeManager(), tool.getService(DataTypeManagerService.class));
try {
// FIXME: Parser returns FunctionDefinition which only supports GenericCallingConventions
return parser.parse(getFunctionSignature(), getSignature());
}
catch (ParseException e) {
setStatusText("Invalid Signature: " + e.getMessage());
}
return null;
}
/**
* Determine if user-specified function signature has been modified from original
* @return true if modified signature has been entered, else false
*/
protected final boolean isSignatureChanged() {
return !getSignature().equals(getPrototypeString());
}
/**
* Determine if user has changed the selected calling convention from the original
* @return true if a change in the selected calling convention has been made
*/
protected final boolean isCallingConventionChanged() {
String current = getCallingConventionName();
if (current == null && this.getCallingConvention() == null) {
return false;
}
if (current == null && this.getCallingConvention().equals("default")) {
return false;
}
if (current == null && this.getCallingConvention().equals("unknown")) {
return false;
}
if (current == null) {
return true;
}
if (current.equals(getCallingConvention())) {
return false;
}
return true;
}
@Override
protected void dialogShown() {
signatureField.selectAll();
}
}

View file

@ -15,300 +15,122 @@
*/ */
package ghidra.app.plugin.core.function; package ghidra.app.plugin.core.function;
import java.awt.Component;
import java.awt.event.ItemEvent;
import java.util.List; import java.util.List;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import ghidra.app.cmd.function.ApplyFunctionSignatureCmd; import ghidra.app.cmd.function.ApplyFunctionSignatureCmd;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.cparser.C.ParseException;
import ghidra.app.util.parser.FunctionSignatureParser;
import ghidra.framework.cmd.Command; import ghidra.framework.cmd.Command;
import ghidra.framework.cmd.CompoundCmd; import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FunctionDefinitionDataType; import ghidra.program.model.data.FunctionDefinitionDataType;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.FunctionSignature;
import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.SourceType;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException; import ghidra.util.exception.InvalidInputException;
/** /**
* <code>EditFunctionSignatureDialog</code> provides the ability to edit function * <code>EditFunctionSignatureDialog</code> provides the ability to edit the
* signatures. Use of this editor requires the presence of the tool-based * function signature associated with a specific {@link Function}.
* datatype manager service. * Use of this editor requires the presence of the tool-based datatype manager service.
*/ */
public class EditFunctionSignatureDialog extends DialogComponentProvider { public class EditFunctionSignatureDialog extends AbstractEditFunctionSignatureDialog {
private static final String NONE_CHOICE = "-NONE-"; protected final Function function;
protected final String oldFunctionSignature;
protected JLabel signatureLabel;
protected JTextField signatureField;
protected JComboBox<String> callingConventionComboBox;
protected JComboBox<String> callFixupComboBox;
protected JCheckBox inlineCheckBox;
protected JCheckBox noReturnCheckBox;
protected PluginTool tool;
protected Function function;
protected String oldFunctionName;
protected String oldFunctionSignature;
/** /**
* This class is not meant to be instantiated directly, but rather by * Edit function signature for a specified Function
* subclasses. * @param tool A reference to the active tool.
*
* @param plugin A reference to the FunctionPlugin.
* @param title The title of the dialog. * @param title The title of the dialog.
* @param function the function which is having its signature edited. * @param function the function which is having its signature edited.
*/ */
public EditFunctionSignatureDialog(PluginTool tool, String title, final Function function) { public EditFunctionSignatureDialog(PluginTool tool, String title, final Function function) {
super(tool, title, allowInLine(function), true, allowCallFixup(function));
super(title, true, true, true, false);
this.tool = tool;
this.function = function; this.function = function;
this.oldFunctionName = function.getName();
this.oldFunctionSignature = function.getSignature().getPrototypeString(); this.oldFunctionSignature = function.getSignature().getPrototypeString();
addWorkPanel(buildMainPanel());
addOKButton();
addCancelButton();
setDefaultButton(okButton);
setFunctionInfo();
setRememberSize(true);
} }
protected void setFunctionInfo() { protected EditFunctionSignatureDialog(PluginTool tool, String title, final Function function,
setSignature(function.getSignature().getPrototypeString()); boolean allowInLine, boolean allowNoReturn, boolean allowCallFixup) {
setCallingConvention(function.getCallingConventionName()); super(tool, title, allowInLine, allowNoReturn, allowCallFixup);
setInlineSelected(function.isInline()); this.function = function;
inlineCheckBox.setEnabled(!getAffectiveFunction(function).isExternal()); this.oldFunctionSignature = function.getSignature().getPrototypeString();
setNoReturnSelected(function.hasNoReturn()); }
@Override
protected FunctionSignature getFunctionSignature() {
return function.getSignature();
}
@Override
protected String getPrototypeString() {
return oldFunctionSignature;
}
@Override
protected String getCallingConventionName() {
return function.getCallingConventionName();
}
@Override
protected List<String> getCallingConventionNames() {
return function.getProgram().getFunctionManager().getCallingConventionNames();
}
@Override
protected boolean isInline() {
return function.isInline();
}
@Override
protected boolean hasNoReturn() {
return function.hasNoReturn();
}
@Override
protected String getCallFixupName() {
return function.getCallFixup();
}
private static String[] getCallFixupNames(Function function) {
String[] callFixupNames =
function.getProgram().getCompilerSpec().getPcodeInjectLibrary().getCallFixupNames();
if (callFixupNames.length == 0) {
return null;
}
return callFixupNames;
}
@Override
protected String[] getSupportedCallFixupNames() {
return getCallFixupNames(function);
}
@Override
protected DataTypeManager getDataTypeManager() {
return function.getProgram().getDataTypeManager();
} }
/** /**
* Get the effective function to which changes will be made. This * Get the effective function to which changes will be made. This
* will be the same as function unless it is a thunk in which case * will be the same as function unless it is a thunk in which case
* the returned function will be the ultimate non-thunk function. * the returned function will be the ultimate non-thunk function.
* @param f * @param f function
* @return non-thunk function * @return non-thunk function
*/ */
protected Function getAffectiveFunction(Function f) { private static Function getEffectiveFunction(Function f) {
return f.isThunk() ? f.getThunkedFunction(true) : f; return f.isThunk() ? f.getThunkedFunction(true) : f;
} }
private JPanel buildMainPanel() { private static boolean allowInLine(Function function) {
JPanel mainPanel = new JPanel(); return !getEffectiveFunction(function).isExternal();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 2));
mainPanel.add(buildSignaturePanel());
mainPanel.add(buildAttributePanel());
installCallFixupWidget(mainPanel);
return mainPanel;
} }
protected void installCallFixupWidget(JPanel parentPanel) { private static boolean allowCallFixup(Function function) {
JPanel callFixupPanel = buildCallFixupPanel(); return getCallFixupNames(function) != null;
parentPanel.add(callFixupPanel != null ? callFixupPanel : buildSpacerPanel());
}
private JPanel buildSignaturePanel() {
JPanel signaturePanel = new JPanel();
signaturePanel.setLayout(new BoxLayout(signaturePanel, BoxLayout.X_AXIS));
String signature = function.getPrototypeString(false, false);
signatureField = new JTextField(signature.length()); // add some extra room to edit
signatureField.setText(signature);
signatureLabel = new GDLabel("Signature:");
signaturePanel.add(signatureLabel);
signaturePanel.add(signatureField);
signaturePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
return signaturePanel;
}
private Component buildSpacerPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
panel.add(Box.createVerticalStrut(20));
return panel;
}
private JPanel buildAttributePanel() {
JPanel attributePanel = new JPanel();
attributePanel.setLayout(new BoxLayout(attributePanel, BoxLayout.X_AXIS));
attributePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
installCallingConventionWidget(attributePanel);
installInlineWidget(attributePanel);
installNoReturnWidget(attributePanel);
attributePanel.add(Box.createGlue());
return attributePanel;
}
protected void installCallingConventionWidget(JPanel parentPanel) {
callingConventionComboBox = new GhidraComboBox<>();
List<String> callingConventions =
function.getProgram().getFunctionManager().getCallingConventionNames();
String[] choices = callingConventions.toArray(new String[callingConventions.size()]);
setCallingConventionChoices(choices);
parentPanel.add(new GLabel("Calling Convention:"));
parentPanel.add(callingConventionComboBox);
}
protected void installInlineWidget(JPanel parentPanel) {
inlineCheckBox = new GCheckBox("Inline");
inlineCheckBox.addChangeListener(e -> {
if (inlineCheckBox.isSelected() && callFixupComboBox != null) {
callFixupComboBox.setSelectedItem(NONE_CHOICE);
}
});
parentPanel.add(inlineCheckBox);
}
protected void installNoReturnWidget(JPanel parentPanel) {
noReturnCheckBox = new GCheckBox("No Return");
parentPanel.add(noReturnCheckBox);
}
private JPanel buildCallFixupPanel() {
String[] callFixupNames =
function.getProgram().getCompilerSpec().getPcodeInjectLibrary().getCallFixupNames();
if (callFixupNames.length == 0) {
return null;
}
JPanel callFixupPanel = new JPanel();
callFixupPanel.setLayout(new BoxLayout(callFixupPanel, BoxLayout.X_AXIS));
callFixupComboBox = new GhidraComboBox<>();
callFixupComboBox.addItem(NONE_CHOICE);
for (String element : callFixupNames) {
callFixupComboBox.addItem(element);
}
callFixupComboBox.addItemListener(e -> {
if (e.getStateChange() == ItemEvent.DESELECTED) {
return;
}
if (!NONE_CHOICE.equals(e.getItem())) {
inlineCheckBox.setSelected(false);
}
});
String callFixupName = function.getCallFixup();
if (callFixupName != null) {
callFixupComboBox.setSelectedItem(callFixupName);
}
callFixupPanel.add(new GLabel("Call-Fixup:"));
callFixupPanel.add(callFixupComboBox);
callFixupPanel.add(Box.createGlue());
callFixupPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
return callFixupPanel;
}
protected PluginTool getTool() {
return tool;
}
protected Program getProgram() {
return function.getProgram();
}
protected Function getFunction() {
return function;
}
public String getSignature() {
return signatureField.getText();
}
protected void setSignature(String signature) {
signatureField.setText(signature);
}
protected void setCallingConventionChoices(String[] callingConventions) {
callingConventionComboBox.removeAllItems();
for (String element : callingConventions) {
callingConventionComboBox.addItem(element);
}
}
protected String getCallingConvention() {
return (String) callingConventionComboBox.getSelectedItem();
}
protected void setCallingConvention(String callingConvention) {
callingConventionComboBox.setSelectedItem(callingConvention);
}
protected boolean isInlineSelected() {
return inlineCheckBox.isSelected();
}
protected void setInlineSelected(boolean selected) {
inlineCheckBox.setSelected(selected);
}
protected boolean hasNoReturnSelected() {
return noReturnCheckBox.isSelected();
}
protected void setNoReturnSelected(boolean selected) {
noReturnCheckBox.setSelected(selected);
}
protected String getCallFixupSelection() {
if (callFixupComboBox != null) {
String callFixup = (String) callFixupComboBox.getSelectedItem();
if (callFixup != null && !NONE_CHOICE.equals(callFixup)) {
return callFixup;
}
}
return null;
}
/**
* This method gets called when the user clicks on the OK Button. The base
* class calls this method.
*/
@Override
protected void okCallback() {
// only close the dialog if the user made valid changes
try {
if (applyChanges()) {
close();
}
}
catch (CancelledException e) {
// ignore - do not close
}
}
@Override
protected void cancelCallback() {
setStatusText("");
close();
} }
/** /**
@ -318,6 +140,7 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider {
* @return true if the command was successfully created. * @return true if the command was successfully created.
* @throws CancelledException if operation cancelled by user * @throws CancelledException if operation cancelled by user
*/ */
@Override
protected boolean applyChanges() throws CancelledException { protected boolean applyChanges() throws CancelledException {
// create the command // create the command
Command command = createCommand(); Command command = createCommand();
@ -327,7 +150,7 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider {
} }
// run the command // run the command
if (!getTool().execute(command, getProgram())) { if (!getTool().execute(command, function.getProgram())) {
setStatusText(command.getStatusMsg()); setStatusText(command.getStatusMsg());
return false; return false;
} }
@ -336,25 +159,16 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider {
return true; return true;
} }
protected FunctionDefinitionDataType parseSignature() throws CancelledException {
FunctionSignatureParser parser = new FunctionSignatureParser(
getProgram().getDataTypeManager(), tool.getService(DataTypeManagerService.class));
try {
return parser.parse(getFunction().getSignature(), getSignature());
}
catch (ParseException e) {
setStatusText("Invalid Signature: " + e.getMessage());
}
return null;
}
private Command createCommand() throws CancelledException { private Command createCommand() throws CancelledException {
Command cmd = null; Command cmd = null;
if (!getSignature().equals(this.oldFunctionSignature) || !isSameCallingConvention() || if (isSignatureChanged() || isCallingConventionChanged() ||
(function.getSignatureSource() == SourceType.DEFAULT)) { (function.getSignatureSource() == SourceType.DEFAULT)) {
FunctionDefinitionDataType definition = parseSignature(); FunctionDefinitionDataType definition = parseSignature();
if (definition == null) {
return null;
}
cmd = new ApplyFunctionSignatureCmd(function.getEntryPoint(), definition, cmd = new ApplyFunctionSignatureCmd(function.getEntryPoint(), definition,
SourceType.USER_DEFINED, true, true); SourceType.USER_DEFINED, true, true);
} }
@ -394,6 +208,7 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider {
return errMsg; return errMsg;
} }
}); });
if (allowInLine) {
compoundCommand.add(new Command() { compoundCommand.add(new Command() {
@Override @Override
public boolean applyTo(DomainObject obj) { public boolean applyTo(DomainObject obj) {
@ -411,6 +226,8 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider {
return null; return null;
} }
}); });
}
if (allowNoReturn) {
compoundCommand.add(new Command() { compoundCommand.add(new Command() {
@Override @Override
public boolean applyTo(DomainObject obj) { public boolean applyTo(DomainObject obj) {
@ -428,6 +245,8 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider {
return null; return null;
} }
}); });
}
if (allowCallFixup) {
compoundCommand.add(new Command() { compoundCommand.add(new Command() {
@Override @Override
public boolean applyTo(DomainObject obj) { public boolean applyTo(DomainObject obj) {
@ -445,34 +264,11 @@ public class EditFunctionSignatureDialog extends DialogComponentProvider {
return null; return null;
} }
}); });
}
if (cmd != null) { if (cmd != null) {
compoundCommand.add(cmd); compoundCommand.add(cmd);
} }
return compoundCommand; return compoundCommand;
} }
private boolean isSameCallingConvention() {
PrototypeModel conv = function.getCallingConvention();
if (conv == null && this.getCallingConvention() == null) {
return true;
}
if (conv == null && this.getCallingConvention().equals("default")) {
return true;
}
if (conv == null && this.getCallingConvention().equals("unknown")) {
return true;
}
if (conv == null) {
return false;
}
if (conv.getName().equals(this.getCallingConvention())) {
return true;
}
return false;
}
@Override
protected void dialogShown() {
signatureField.selectAll();
}
} }

View file

@ -15,12 +15,11 @@
*/ */
package ghidra.app.plugin.core.strings; package ghidra.app.plugin.core.strings;
import javax.swing.ImageIcon; import javax.swing.Icon;
import docking.ActionContext; import docking.ActionContext;
import docking.action.*; import docking.action.*;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.data.DataSettingsDialog; import ghidra.app.plugin.core.data.DataSettingsDialog;
@ -38,6 +37,7 @@ import ghidra.util.table.SelectionNavigationAction;
import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.table.actions.MakeProgramSelectionAction;
import ghidra.util.task.SwingUpdateManager; import ghidra.util.task.SwingUpdateManager;
import resources.Icons; import resources.Icons;
import resources.ResourceManager;
/** /**
* Plugin that provides the "Defined Strings" table, where all the currently defined * Plugin that provides the "Defined Strings" table, where all the currently defined
@ -57,7 +57,11 @@ import resources.Icons;
//@formatter:on //@formatter:on
public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectListener { public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectListener {
private DockingAction selectAction; private static Icon REFRESH_ICON = Icons.REFRESH_ICON;
private static Icon REFRESH_NOT_NEEDED_ICON =
ResourceManager.getDisabledIcon(Icons.REFRESH_ICON, 60);
private DockingAction refreshAction;
private DockingAction showSettingsAction; private DockingAction showSettingsAction;
private DockingAction showDefaultSettingsAction; private DockingAction showDefaultSettingsAction;
private SelectionNavigationAction linkNavigationAction; private SelectionNavigationAction linkNavigationAction;
@ -82,7 +86,7 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList
} }
private void createActions() { private void createActions() {
DockingAction refreshAction = new DockingAction("Refresh Strings", getName()) { refreshAction = new DockingAction("Refresh Strings", getName()) {
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
@ -91,12 +95,14 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
getToolBarData().setIcon(REFRESH_NOT_NEEDED_ICON);
reload(); reload();
} }
}; };
ImageIcon refreshIcon = Icons.REFRESH_ICON; refreshAction.setToolBarData(new ToolBarData(REFRESH_NOT_NEEDED_ICON));
refreshAction.setDescription("Reloads all string data from the program"); refreshAction.setDescription(
refreshAction.setToolBarData(new ToolBarData(refreshIcon)); "<html>Push at any time to refresh the current table of strings.<br>" +
"This button is highlighted when the data <i>may</i> be stale.<br>");
refreshAction.setHelpLocation(new HelpLocation("ViewStringsPlugin", "Refresh")); refreshAction.setHelpLocation(new HelpLocation("ViewStringsPlugin", "Refresh"));
tool.addLocalAction(provider, refreshAction); tool.addLocalAction(provider, refreshAction);
@ -152,13 +158,6 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList
} }
private void selectData(ProgramSelection selection) {
ProgramSelectionPluginEvent pspe =
new ProgramSelectionPluginEvent("Selection", selection, currentProgram);
firePluginEvent(pspe);
processEvent(pspe);
}
@Override @Override
public void dispose() { public void dispose() {
reloadUpdateMgr.dispose(); reloadUpdateMgr.dispose();
@ -186,23 +185,28 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList
} }
} }
private void markDataAsStale() {
provider.getComponent().repaint();
refreshAction.getToolBarData().setIcon(REFRESH_ICON);
}
@Override @Override
public void domainObjectChanged(DomainObjectChangedEvent ev) { public void domainObjectChanged(DomainObjectChangedEvent ev) {
if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) || if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) ||
ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_MOVED) || ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_MOVED) ||
ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED) || ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED) ||
ev.containsEvent(ChangeManager.DOCR_CODE_REMOVED) ||
ev.containsEvent(ChangeManager.DOCR_DATA_TYPE_CHANGED)) { ev.containsEvent(ChangeManager.DOCR_DATA_TYPE_CHANGED)) {
reload(); markDataAsStale();
return;
} }
else if (ev.containsEvent(ChangeManager.DOCR_CODE_ADDED)) {
for (int i = 0; i < ev.numRecords(); ++i) { for (int i = 0; i < ev.numRecords(); ++i) {
DomainObjectChangeRecord doRecord = ev.getChangeRecord(i); DomainObjectChangeRecord doRecord = ev.getChangeRecord(i);
Object newValue = doRecord.getNewValue(); Object newValue = doRecord.getNewValue();
switch (doRecord.getEventType()) { switch (doRecord.getEventType()) {
case ChangeManager.DOCR_CODE_REMOVED: case ChangeManager.DOCR_CODE_REMOVED:
case ChangeManager.DOCR_COMPOSITE_ADDED:
ProgramChangeRecord pcRec = (ProgramChangeRecord) doRecord; ProgramChangeRecord pcRec = (ProgramChangeRecord) doRecord;
provider.remove(pcRec.getStart(), pcRec.getEnd()); provider.remove(pcRec.getStart(), pcRec.getEnd());
break; break;
@ -216,15 +220,15 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList
break; break;
} }
} }
}
else if (ev.containsEvent(ChangeManager.DOCR_DATA_TYPE_SETTING_CHANGED)) { if (ev.containsEvent(ChangeManager.DOCR_DATA_TYPE_SETTING_CHANGED)) {
// Unusual code: because the table model goes directly to the settings values // Unusual code: because the table model goes directly to the settings values
// during each repaint, we don't need to figure out which row was changed. // during each repaint, we don't need to figure out which row was changed.
provider.getComponent().repaint(); provider.getComponent().repaint();
} }
} }
void reload() { private void reload() {
reloadUpdateMgr.update(); reloadUpdateMgr.update();
} }
} }

View file

@ -0,0 +1,85 @@
/* ###
* 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.symtable;
import java.util.*;
import docking.widgets.table.AddRemoveListItem;
import docking.widgets.table.threaded.TableAddRemoveStrategy;
import docking.widgets.table.threaded.TableData;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* This strategy attempts to optimize removal of db objects that have been deleted. The issue with
* deleted db objects is that they may no longer have their attributes, which means we cannot
* use any of those attributes that may have been used as the basis for sorting. We use the
* table's sort to perform a binary search of existing symbols for removal. If the binary search
* does not work, then removal operations will require slow list traversal. Additionally,
* some clients use proxy objects in add/remove list to signal which object needs to be removed,
* since the original object is no longer available to the client. Using these proxy objects
* in a binary search may lead to exceptions if the proxy has unsupported methods called when
* searching.
*
* <P>This strategy will has guilty knowledge of client proxy object usage. The proxy objects
* are coded such that the {@code hashCode()} and {@code equals()} methods will match those
* methods of the data's real objects.
*
* @param <T> the row type
*/
public class SymbolTableAddRemoveStrategy<T> implements TableAddRemoveStrategy<T> {
@Override
public void process(List<AddRemoveListItem<T>> addRemoveList, TableData<T> tableData,
TaskMonitor monitor) throws CancelledException {
//
// Hash map the existing values so that we can use any object inside the add/remove list
// as a key into this map to get the matching existing value.
//
Map<T, T> hashed = new HashMap<>();
for (T t : tableData) {
hashed.put(t, t);
}
int n = addRemoveList.size();
monitor.setMessage("Adding/Removing " + n + " items...");
monitor.initialize(n);
for (int i = 0; i < n; i++) {
AddRemoveListItem<T> item = addRemoveList.get(i);
T value = item.getValue();
if (item.isChange()) {
T toRemove = hashed.get(value);
if (toRemove != null) {
tableData.remove(toRemove);
}
tableData.insert(value);
}
else if (item.isRemove()) {
T toRemove = hashed.get(value);
if (toRemove != null) {
tableData.remove(toRemove);
}
}
else if (item.isAdd()) {
tableData.insert(value);
}
monitor.checkCanceled();
monitor.setProgress(i);
}
monitor.setMessage("Done adding/removing");
}
}

View file

@ -18,6 +18,7 @@ package ghidra.app.plugin.core.symtable;
import java.util.*; import java.util.*;
import docking.widgets.table.*; import docking.widgets.table.*;
import docking.widgets.table.threaded.TableAddRemoveStrategy;
import ghidra.app.cmd.function.DeleteFunctionCmd; import ghidra.app.cmd.function.DeleteFunctionCmd;
import ghidra.app.cmd.label.DeleteLabelCmd; import ghidra.app.cmd.label.DeleteLabelCmd;
import ghidra.app.cmd.label.RenameLabelCmd; import ghidra.app.cmd.label.RenameLabelCmd;
@ -60,6 +61,8 @@ class SymbolTableModel extends AddressBasedTableModel<Symbol> {
private ReferenceManager refMgr; private ReferenceManager refMgr;
private Symbol lastSymbol; private Symbol lastSymbol;
private SymbolFilter filter; private SymbolFilter filter;
private TableAddRemoveStrategy<Symbol> deletedDbObjectAddRemoveStrategy =
new SymbolTableAddRemoveStrategy<>();
SymbolTableModel(SymbolProvider provider, PluginTool tool) { SymbolTableModel(SymbolProvider provider, PluginTool tool) {
super("Symbols", tool, null, null); super("Symbols", tool, null, null);
@ -88,6 +91,11 @@ class SymbolTableModel extends AddressBasedTableModel<Symbol> {
return descriptor; return descriptor;
} }
@Override
protected TableAddRemoveStrategy<Symbol> getAddRemoveStrategy() {
return deletedDbObjectAddRemoveStrategy;
}
void setFilter(SymbolFilter filter) { void setFilter(SymbolFilter filter) {
this.filter = filter; this.filter = filter;
reload(); reload();

View file

@ -29,6 +29,7 @@ import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.util.Swing;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
/** /**
@ -64,7 +65,9 @@ public class GhidraState {
this.currentHighlight = highlight; this.currentHighlight = highlight;
this.isGlobalState = true; this.isGlobalState = true;
if (!SystemUtilities.isInHeadlessMode()) { if (!SystemUtilities.isInHeadlessMode()) {
Swing.runNow(() -> {
gatherParamPanel = new GatherParamPanel(this); gatherParamPanel = new GatherParamPanel(this);
});
} }
} }

View file

@ -349,14 +349,26 @@ public class DemangledFunction extends DemangledObject {
Function function = createFunction(program, address, options.doDisassembly(), monitor); Function function = createFunction(program, address, options.doDisassembly(), monitor);
if (function == null) { if (function == null) {
// no function whose signature we need to update // No function whose signature we need to update
// NOTE: this does not make much sense
// renameExistingSymbol(program, address, symbolTable);
// maybeCreateUndefined(program, address);
return false; return false;
} }
//if existing function signature is user defined - add demangled label only if (function.isThunk()) {
// If thunked function has same mangled name we can discard our
// symbol if no other symbols at this address (i.e., rely entirely on
// thunked function).
// NOTE: mangled name on external may be lost once it is demangled.
if (shouldThunkBePreserved(function)) {
// Preserve thunk and remove mangled symbol. Allow to proceed normally by returning true.
function.getSymbol().setName(null, SourceType.DEFAULT);
return true;
}
// Break thunk relationship and continue applying demangle function below
function.setThunkedFunction(null);
}
// If existing function signature is user defined - add demangled label only
boolean makePrimary = (function.getSignatureSource() != SourceType.USER_DEFINED); boolean makePrimary = (function.getSignatureSource() != SourceType.USER_DEFINED);
Symbol demangledSymbol = Symbol demangledSymbol =
@ -395,6 +407,65 @@ public class DemangledFunction extends DemangledObject {
return true; return true;
} }
/**
* Determine if existing thunk relationship should be preserved and mangled symbol
* discarded. This is the case when the thunk function mangled name matches
* the thunked function since we want to avoid duplicate symbol names.
* @param thunkFunction thunk function with a mangled symbol which is currently
* being demangled.
* @return true if thunk should be preserved and mangled symbol discarded, otherwise
* false if thunk relationship should be eliminated and demangled function information
* should be applied as normal.
*/
private boolean shouldThunkBePreserved(Function thunkFunction) {
Program program = thunkFunction.getProgram();
SymbolTable symbolTable = program.getSymbolTable();
if (thunkFunction.getSymbol().isExternalEntryPoint()) {
return false; // entry point should retain its own symbol
}
Symbol[] symbols = symbolTable.getSymbols(thunkFunction.getEntryPoint());
if (symbols.length > 1) {
return false; // too many symbols present to preserve thunk
}
// NOTE: order of demangling unknown - thunked function may, or may not, have
// already been demangled
Function thunkedFunction = thunkFunction.getThunkedFunction(true);
if (mangled.equals(thunkedFunction.getName())) {
// thunked function has matching mangled name
return true;
}
if (thunkedFunction.isExternal()) {
if (thunkedFunction.getParentNamespace() instanceof Library) {
// Thunked function does not have mangled name, if it did it would have
// matched name check above or now reside in a different namespace
return false;
}
// assume external contained with specific namespace
ExternalLocation externalLocation =
program.getExternalManager().getExternalLocation(thunkedFunction.getSymbol());
String originalImportedName = externalLocation.getOriginalImportedName();
if (originalImportedName == null) {
// assume external manually manipulated without use of mangled name
return false;
}
if (mangled.equals(externalLocation.getOriginalImportedName())) {
// matching mangled name also resides at thunked function location
return true;
}
// TODO: carefully compare signature in absense of matching mangled name
return false;
}
if (symbolTable.getSymbol(mangled, thunkedFunction.getEntryPoint(),
program.getGlobalNamespace()) != null) {
// matching mangled name also resides at thunked function location
return true;
}
return false;
}
private boolean hasVarArgs() { private boolean hasVarArgs() {
if (parameters.isEmpty()) { if (parameters.isEmpty()) {
return false; return false;

View file

@ -88,7 +88,7 @@ public class FunctionDataTypeHTMLRepresentation extends HTMLDataTypeRepresentati
GenericCallingConvention genericCallingConvention = GenericCallingConvention genericCallingConvention =
functionDefinition.getGenericCallingConvention(); functionDefinition.getGenericCallingConvention();
String modifier = genericCallingConvention != GenericCallingConvention.unknown String modifier = genericCallingConvention != GenericCallingConvention.unknown
? (" " + genericCallingConvention.name()) ? (" " + genericCallingConvention.getDeclarationName())
: ""; : "";
return new TextLine( return new TextLine(
HTMLUtilities.friendlyEncodeHTML(returnDataType.getDisplayName()) + modifier); HTMLUtilities.friendlyEncodeHTML(returnDataType.getDisplayName()) + modifier);

View file

@ -50,7 +50,7 @@ import ghidra.app.plugin.core.datamgr.actions.CreateTypeDefDialog;
import ghidra.app.plugin.core.datamgr.archive.Archive; import ghidra.app.plugin.core.datamgr.archive.Archive;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler; import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.*; import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.app.plugin.core.function.EditFunctionSignatureDialog; import ghidra.app.plugin.core.function.AbstractEditFunctionSignatureDialog;
import ghidra.app.plugin.core.programtree.ProgramTreePlugin; import ghidra.app.plugin.core.programtree.ProgramTreePlugin;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.app.util.datatype.DataTypeSelectionEditor; import ghidra.app.util.datatype.DataTypeSelectionEditor;
@ -695,11 +695,9 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
DataType dt = iter.next(); DataType dt = iter.next();
listTwo.add(dt); listTwo.add(dt);
} }
for (int i = 0; i < listOne.size(); i++) { for (DataType dt : listOne) {
DataType dt = listOne.get(i);
boolean found = false; boolean found = false;
for (int j = 0; j < listTwo.size(); j++) { for (DataType dt2 : listTwo) {
DataType dt2 = listTwo.get(j);
if (dt.isEquivalent(dt2)) { if (dt.isEquivalent(dt2)) {
found = true; found = true;
break; break;
@ -807,8 +805,8 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
assertTrue(action.isEnabledForContext(treeContext)); assertTrue(action.isEnabledForContext(treeContext));
performAction(action, treeContext, false); performAction(action, treeContext, false);
EditFunctionSignatureDialog dialog = AbstractEditFunctionSignatureDialog dialog =
waitForDialogComponent(EditFunctionSignatureDialog.class); waitForDialogComponent(AbstractEditFunctionSignatureDialog.class);
JTextField textField = (JTextField) getInstanceField("signatureField", dialog); JTextField textField = (JTextField) getInstanceField("signatureField", dialog);
setText(textField, newSignature); setText(textField, newSignature);

View file

@ -84,9 +84,11 @@ public class GhidraScriptAskMethodsTest extends AbstractGhidraHeadedIntegrationT
} }
private void clearScriptCachedValues() { private void clearScriptCachedValues() {
if (script != null) {
Map<?, ?> map = (Map<?, ?>) TestUtils.getInstanceField("askMap", script); Map<?, ?> map = (Map<?, ?>) TestUtils.getInstanceField("askMap", script);
map.clear(); map.clear();
} }
}
@Test @Test
public void testAskBytes_NoPreviousValue() throws Exception { public void testAskBytes_NoPreviousValue() throws Exception {

View file

@ -29,59 +29,12 @@ import ghidra.program.model.data.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.*; import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
public class OverridePrototypeAction extends AbstractDecompilerAction { public class OverridePrototypeAction extends AbstractDecompilerAction {
public class ProtoOverrideDialog extends EditFunctionSignatureDialog {
private FunctionDefinition functionDefinition;
public FunctionDefinition getFunctionDefinition() {
return functionDefinition;
}
public ProtoOverrideDialog(PluginTool tool, Function func, String signature, String conv) {
super(tool, "Override Signature", func);
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionOverrideSignature"));
setSignature(signature);
setCallingConvention(conv);
}
/**
* This method gets called when the user clicks on the OK Button. The base
* class calls this method.
*/
@Override
protected void okCallback() {
// only close the dialog if the user made valid changes
if (parseFunctionDefinition()) {
close();
}
}
private boolean parseFunctionDefinition() {
functionDefinition = null;
try {
functionDefinition = parseSignature();
}
catch (CancelledException e) {
// ignore
}
if (functionDefinition == null) {
return false;
}
GenericCallingConvention convention =
GenericCallingConvention.guessFromName(getCallingConvention());
functionDefinition.setGenericCallingConvention(convention);
return true;
}
}
public OverridePrototypeAction() { public OverridePrototypeAction() {
super("Override Signature"); super("Override Signature");
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionOverrideSignature")); setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionOverrideSignature"));
@ -183,11 +136,30 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
return null; return null;
} }
private String generateSignature(PcodeOp op, String name) { private String generateSignature(PcodeOp op, String name, Function calledfunc) {
// TODO: If an override has already be placed-down it should probably be used
// for the initial signature. HighFunction does not make it easy to grab
// existing override prototype
if (calledfunc != null) {
SourceType signatureSource = calledfunc.getSignatureSource();
if (signatureSource == SourceType.DEFAULT || signatureSource == SourceType.ANALYSIS) {
calledfunc = null; // ignore
}
}
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
Varnode vn = op.getOutput(); Varnode vn = op.getOutput();
DataType dt = null; DataType dt = null;
if (vn != null) { if (calledfunc != null) {
dt = calledfunc.getReturnType();
if (Undefined.isUndefined(dt)) {
dt = null;
}
}
if (dt == null && vn != null) {
dt = vn.getHigh().getDataType(); dt = vn.getHigh().getDataType();
} }
if (dt != null) { if (dt != null) {
@ -198,24 +170,46 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
} }
buf.append(' ').append(name).append('('); buf.append(' ').append(name).append('(');
for (int i = 1; i < op.getNumInputs(); ++i) {
vn = op.getInput(i); int index = 1;
dt = null; if (calledfunc != null) {
for (Parameter p : calledfunc.getParameters()) {
String dtName = getInputDataTypeName(op, index, p.getDataType());
if (index++ != 1) {
buf.append(", ");
}
buf.append(dtName);
if (p.getSource() != SourceType.DEFAULT) {
buf.append(' ');
buf.append(p.getName());
}
}
}
for (int i = index; i < op.getNumInputs(); ++i) {
if (i != 1) {
buf.append(", ");
}
buf.append(getInputDataTypeName(op, i, null));
}
buf.append(')');
return buf.toString();
}
private String getInputDataTypeName(PcodeOp op, int inIndex, DataType preferredDt) {
if (preferredDt != null && !Undefined.isUndefined(preferredDt)) {
return preferredDt.getDisplayName();
}
Varnode vn = op.getInput(inIndex);
DataType dt = null;
if (vn != null) { if (vn != null) {
dt = vn.getHigh().getDataType(); dt = vn.getHigh().getDataType();
} }
if (dt != null) { if (dt != null) {
buf.append(dt.getDisplayName()); return dt.getDisplayName();
} }
else { return "BAD";
buf.append("BAD");
}
if (i != op.getNumInputs() - 1) {
buf.append(',');
}
}
buf.append(')');
return buf.toString();
} }
@Override @Override
@ -257,9 +251,10 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
conv = calledfunc.getCallingConventionName(); conv = calledfunc.getCallingConventionName();
} }
String signature = generateSignature(op, name); String signature = generateSignature(op, name, calledfunc);
PluginTool tool = context.getTool(); PluginTool tool = context.getTool();
ProtoOverrideDialog dialog = new ProtoOverrideDialog(tool, func, signature, conv); ProtoOverrideDialog dialog =
new ProtoOverrideDialog(tool, calledfunc != null ? calledfunc : func, signature, conv);
tool.showDialog(dialog); tool.showDialog(dialog);
FunctionDefinition fdef = dialog.getFunctionDefinition(); FunctionDefinition fdef = dialog.getFunctionDefinition();
if (fdef == null) { if (fdef == null) {
@ -279,4 +274,71 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
program.endTransaction(transaction, commit); program.endTransaction(transaction, commit);
} }
} }
/**
* <code>ProtoOverrideDialog</code> provides the ability to edit the
* function signature associated with a specific function definition override
* at a sub-function callsite.
* Use of this editor requires the presence of the tool-based datatype manager service.
*/
private class ProtoOverrideDialog extends EditFunctionSignatureDialog {
private FunctionDefinition functionDefinition;
private final String initialSignature;
private final String initialConvention;
/**
* Construct signature override for called function
* @param tool active tool
* @param func function from which program access is achieved and supply of preferred
* datatypes when parsing signature
* @param signature initial prototype signature to be used
* @param conv initial calling convention
*/
public ProtoOverrideDialog(PluginTool tool, Function func, String signature, String conv) {
super(tool, "Override Signature", func, false, false, false);
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionOverrideSignature"));
this.initialSignature = signature;
this.initialConvention = conv;
}
@Override
protected String getPrototypeString() {
return initialSignature;
}
@Override
protected String getCallingConventionName() {
return initialConvention;
}
@Override
protected boolean applyChanges() throws CancelledException {
return parseFunctionDefinition();
}
private boolean parseFunctionDefinition() {
functionDefinition = null;
try {
functionDefinition = parseSignature();
}
catch (CancelledException e) {
// ignore
}
if (functionDefinition == null) {
return false;
}
GenericCallingConvention convention =
GenericCallingConvention.guessFromName(getCallingConvention());
functionDefinition.setGenericCallingConvention(convention);
return true;
}
public FunctionDefinition getFunctionDefinition() {
return functionDefinition;
}
}
} }

View file

@ -31,10 +31,11 @@ dependencies {
} }
// All *.fidb files located in the BIN repo under src/main/fidb will be unpacked // All *.fidb files located in the dependencies/fid directory OR the
def fidbSrcDir = "${getProjectLocationInBinRepo(project)}/src/main/fidb" // BIN repo under src/main/fidb will be unpacked
def depsDir = file("${DEPS_DIR}/fidb")
def fidDbFiles = fileTree(fidbSrcDir) { def binRepoDir = "${getProjectLocationInBinRepo(project)}/src/main/fidb"
def fidDbFiles = fileTree(depsDir.exists() ? depsDir : binRepoDir) {
include '**/*.fidb' include '**/*.fidb'
} }

View file

@ -40,12 +40,11 @@ addExports([
]) ])
CopySpec yajswCopySpec = copySpec { CopySpec yajswCopySpec = copySpec {
File localFile = file("build/${yajswRelease}.zip") File depsFile = file("${DEPS_DIR}/GhidraServer/${yajswRelease}.zip")
File binFile = file("${BIN_REPO}/Ghidra/Features/GhidraServer/${yajswRelease}.zip") File binRepoFile = file("${BIN_REPO}/Ghidra/Features/GhidraServer/${yajswRelease}.zip")
// First check if the file was downloaded and dropped in locally. If not, check in the bin // First check if the file is in the dependencies repo. If not, check in the bin repo.
// repo. def yajswZipTree = depsFile.exists() ? zipTree(depsFile) : zipTree(binRepoFile)
def yajswZipTree = localFile.exists() ? zipTree(localFile) : zipTree(binFile)
from(yajswZipTree) { from(yajswZipTree) {
include "${yajswRelease}/lib/core/**" include "${yajswRelease}/lib/core/**"

View file

@ -0,0 +1,61 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.table.threaded;
import java.util.List;
import docking.widgets.table.AddRemoveListItem;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A strategy that uses the table's sort state to perform a binary search of items to be added
* and removed.
*
* @param <T> the row type
*/
public class DefaultAddRemoveStrategy<T> implements TableAddRemoveStrategy<T> {
@Override
public void process(List<AddRemoveListItem<T>> addRemoveList, TableData<T> updatedData,
TaskMonitor monitor) throws CancelledException {
int n = addRemoveList.size();
monitor.setMessage("Adding/Removing " + n + " items...");
monitor.initialize(n);
// Note: this class does not directly perform a binary such, but instead relies on that
// work to be done by the call to TableData.remove()
for (int i = 0; i < n; i++) {
AddRemoveListItem<T> item = addRemoveList.get(i);
T value = item.getValue();
if (item.isChange()) {
updatedData.remove(value);
updatedData.insert(value);
}
else if (item.isRemove()) {
updatedData.remove(value);
}
else if (item.isAdd()) {
updatedData.insert(value);
}
monitor.checkCanceled();
monitor.setProgress(i);
}
monitor.setMessage("Done adding/removing");
}
}

View file

@ -0,0 +1,40 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.table.threaded;
import java.util.List;
import docking.widgets.table.AddRemoveListItem;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A strategy to perform table add and remove updates
*
* @param <T> the row type
*/
public interface TableAddRemoveStrategy<T> {
/**
* Adds to and removes from the table data those items in the given add/remove list
* @param addRemoveList the items to add/remove
* @param tableData the table's data
* @param monitor the monitor
* @throws CancelledException if the monitor is cancelled
*/
public void process(List<AddRemoveListItem<T>> addRemoveList, TableData<T> tableData,
TaskMonitor monitor) throws CancelledException;
}

View file

@ -137,7 +137,7 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
* @param t the item * @param t the item
* @return the index * @return the index
*/ */
int indexOf(ROW_OBJECT t) { public int indexOf(ROW_OBJECT t) {
if (!sortContext.isUnsorted()) { if (!sortContext.isUnsorted()) {
Comparator<ROW_OBJECT> comparator = sortContext.getComparator(); Comparator<ROW_OBJECT> comparator = sortContext.getComparator();
return Collections.binarySearch(data, t, comparator); return Collections.binarySearch(data, t, comparator);
@ -153,7 +153,7 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
return -1; return -1;
} }
boolean remove(ROW_OBJECT t) { public boolean remove(ROW_OBJECT t) {
if (source != null) { if (source != null) {
source.remove(t); source.remove(t);
} }
@ -186,7 +186,7 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
* *
* @param value the row Object to insert * @param value the row Object to insert
*/ */
void insert(ROW_OBJECT value) { public void insert(ROW_OBJECT value) {
if (source != null) { if (source != null) {
// always update the master data // always update the master data

View file

@ -553,32 +553,15 @@ public class TableUpdateJob<T> {
*/ */
private void doProcessAddRemoves() throws CancelledException { private void doProcessAddRemoves() throws CancelledException {
int n = addRemoveList.size();
monitor.setMessage("Adding/Removing " + n + " items...");
monitor.initialize(n);
initializeSortCache(); initializeSortCache();
try {
for (int i = 0; i < n; i++) { TableAddRemoveStrategy<T> strategy = model.getAddRemoveStrategy();
AddRemoveListItem<T> item = addRemoveList.get(i); strategy.process(addRemoveList, updatedData, monitor);
T value = item.getValue();
if (item.isChange()) {
updatedData.remove(value);
updatedData.insert(value);
} }
else if (item.isRemove()) { finally {
updatedData.remove(value);
}
else if (item.isAdd()) {
updatedData.insert(value);
}
monitor.checkCanceled();
monitor.setProgress(i);
}
monitor.setMessage("Done adding/removing");
clearSortCache(); clearSortCache();
} }
}
/** When sorting we cache column value lookups to increase speed. */ /** When sorting we cache column value lookups to increase speed. */
private void initializeSortCache() { private void initializeSortCache() {

View file

@ -90,6 +90,8 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
private volatile Worker worker; // only created as needed (if we are incremental) private volatile Worker worker; // only created as needed (if we are incremental)
private int minUpdateDelayMillis; private int minUpdateDelayMillis;
private int maxUpdateDelayMillis; private int maxUpdateDelayMillis;
private TableAddRemoveStrategy<ROW_OBJECT> binarySearchAddRemoveStrategy =
new DefaultAddRemoveStrategy<>();
protected ThreadedTableModel(String modelName, ServiceProvider serviceProvider) { protected ThreadedTableModel(String modelName, ServiceProvider serviceProvider) {
this(modelName, serviceProvider, null); this(modelName, serviceProvider, null);
@ -510,6 +512,17 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
/** /**
* Removes the specified object from this model and schedules an update. * Removes the specified object from this model and schedules an update.
*
* <P>Note: for this method to function correctly, the given object must compare as
* {@link #equals(Object)} and have the same {@link #hashCode()} as the object to be removed
* from the table data. This allows clients to create proxy objects to pass into this method,
* as long as they honor those requirements.
*
* <P>If this model's data is sorted, then a binary search will be used to locate the item
* to be removed. However, for this to work, all field used to sort the data must still be
* available from the original object and must be the same values. If this is not true, then
* the binary search will not work and a brute force search will be used.
*
* @param obj the object to remove * @param obj the object to remove
*/ */
public void removeObject(ROW_OBJECT obj) { public void removeObject(ROW_OBJECT obj) {
@ -786,6 +799,23 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
updateManager.setTaskMonitor(monitor); updateManager.setTaskMonitor(monitor);
} }
/**
* Returns the strategy to use for performing adds and removes to this table. Subclasses can
* override this method to customize this process for their particular type of data. See
* the implementations of {@link TableAddRemoveStrategy} for details.
*
* <P>Note: The default add/remove strategy assumes that objects to be removed will be the
* same instance that is in the list of this model. This allows the {@link #equals(Object)}
* and {@link #hashCode()} to be used when removing the object from the list. If you model
* does not pass the same instance into {@link #removeObject(Object)}, then you will need to
* update your add/remove strategy accordingly.
*
* @return the strategy
*/
protected TableAddRemoveStrategy<ROW_OBJECT> getAddRemoveStrategy() {
return binarySearchAddRemoveStrategy;
}
public void setIncrementalTaskMonitor(TaskMonitor monitor) { public void setIncrementalTaskMonitor(TaskMonitor monitor) {
SystemUtilities.assertTrue(loadIncrementally, "Cannot set an incremental task monitor " + SystemUtilities.assertTrue(loadIncrementally, "Cannot set an incremental task monitor " +
"on a table that was not constructed to load incrementally"); "on a table that was not constructed to load incrementally");

View file

@ -160,7 +160,7 @@ public class AddressMapImpl {
} }
void checkAddressSpace(AddressSpace addrSpace) { void checkAddressSpace(AddressSpace addrSpace) {
String name = addrSpace.getName().toUpperCase(); String name = addrSpace.getName();
AddressSpace existingSpace = spaceMap.get(name); AddressSpace existingSpace = spaceMap.get(name);
if (existingSpace == null) { if (existingSpace == null) {
spaceMap.put(name, addrSpace); spaceMap.put(name, addrSpace);
@ -248,10 +248,12 @@ public class AddressMapImpl {
private void addKeyRanges(List<KeyRange> keyRangeList, Address start, Address end) { private void addKeyRanges(List<KeyRange> keyRangeList, Address start, Address end) {
int index = Arrays.binarySearch(sortedBaseStartAddrs, start); int index = Arrays.binarySearch(sortedBaseStartAddrs, start);
if (index < 0) if (index < 0) {
index = -index - 2; index = -index - 2;
if (index < 0) }
if (index < 0) {
index++; index++;
}
while (index < sortedBaseStartAddrs.length && while (index < sortedBaseStartAddrs.length &&
end.compareTo(sortedBaseStartAddrs[index]) >= 0) { end.compareTo(sortedBaseStartAddrs[index]) >= 0) {
Address addr1 = max(start, sortedBaseStartAddrs[index]); Address addr1 = max(start, sortedBaseStartAddrs[index]);
@ -312,7 +314,7 @@ public class AddressMapImpl {
} }
for (AddressSpace space : remapSpaces.values()) { for (AddressSpace space : remapSpaces.values()) {
spaceMap.put(space.getName().toUpperCase(), space); spaceMap.put(space.getName(), space);
} }
for (int i = 0; i < baseAddrs.length; i++) { for (int i = 0; i < baseAddrs.length; i++) {

View file

@ -162,7 +162,7 @@ public class StringDataInstance {
return ((AbstractStringDataType) dt).getStringDataInstance(data, data, return ((AbstractStringDataType) dt).getStringDataInstance(data, data,
data.getLength()); data.getLength());
} }
if (dt instanceof Array && !data.isInitializedMemory()) { if (dt instanceof Array && data.isInitializedMemory()) {
ArrayStringable arrayStringable = ArrayStringable arrayStringable =
ArrayStringable.getArrayStringable(((Array) dt).getDataType()); ArrayStringable.getArrayStringable(((Array) dt).getDataType());
if (arrayStringable != null && arrayStringable.hasStringValue(data)) { if (arrayStringable != null && arrayStringable.hasStringValue(data)) {
@ -918,7 +918,8 @@ public class StringDataInstance {
if (byteOffset + charSize > stringBytes.length) { if (byteOffset + charSize > stringBytes.length) {
return false; return false;
} }
long origCodePointValue = DataConverter.getInstance(buf.isBigEndian()).getValue(stringBytes, long origCodePointValue = DataConverter.getInstance(buf.isBigEndian())
.getValue(stringBytes,
byteOffset, charSize); byteOffset, charSize);
return origCodePointValue == StringUtilities.UNICODE_REPLACEMENT; return origCodePointValue == StringUtilities.UNICODE_REPLACEMENT;
} }

View file

@ -15,7 +15,7 @@
*/ */
package ghidra.program.model.address; package ghidra.program.model.address;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.*;
import org.junit.*; import org.junit.*;
@ -26,6 +26,7 @@ public class AddressMapImplTest extends AbstractGenericTest {
AddressSpace sp16; AddressSpace sp16;
AddressSpace sp32; AddressSpace sp32;
AddressSpace sp64; AddressSpace sp64;
AddressSpace ov64;
AddressSpace regSpace; AddressSpace regSpace;
AddressSpace stackSpace; AddressSpace stackSpace;
SegmentedAddressSpace segSpace1; SegmentedAddressSpace segSpace1;
@ -42,6 +43,8 @@ public class AddressMapImplTest extends AbstractGenericTest {
sp32 = new GenericAddressSpace("THREE", 32, AddressSpace.TYPE_RAM, 2); sp32 = new GenericAddressSpace("THREE", 32, AddressSpace.TYPE_RAM, 2);
sp64 = new GenericAddressSpace("FOUR", 64, AddressSpace.TYPE_RAM, 2); sp64 = new GenericAddressSpace("FOUR", 64, AddressSpace.TYPE_RAM, 2);
ov64 = new OverlayAddressSpace("four", sp64, 100, 0x1000, 0x1fff);
segSpace1 = new SegmentedAddressSpace("SegSpaceOne", 3); segSpace1 = new SegmentedAddressSpace("SegSpaceOne", 3);
segSpace2 = new SegmentedAddressSpace("SegSpaceTwo", 4); segSpace2 = new SegmentedAddressSpace("SegSpaceTwo", 4);
@ -50,7 +53,7 @@ public class AddressMapImplTest extends AbstractGenericTest {
map = new AddressMapImpl(); map = new AddressMapImpl();
addrs = new Address[29]; addrs = new Address[31];
addrs[0] = sp8.getAddress(0); addrs[0] = sp8.getAddress(0);
addrs[1] = sp8.getAddress(0x0ff); addrs[1] = sp8.getAddress(0x0ff);
addrs[2] = sp16.getAddress(0); addrs[2] = sp16.getAddress(0);
@ -84,6 +87,9 @@ public class AddressMapImplTest extends AbstractGenericTest {
addrs[27] = stackSpace.getAddress(0); addrs[27] = stackSpace.getAddress(0);
addrs[28] = stackSpace.getAddress(0x80000000); addrs[28] = stackSpace.getAddress(0x80000000);
addrs[29] = ov64.getAddress(0x1100);
addrs[30] = ov64.getAddress(0x2000);
} }
@Test @Test

View file

@ -80,12 +80,11 @@ task pyDevUnpack(type:Copy) {
!pyDevDestDir.exists() !pyDevDestDir.exists()
} }
File localFile = file("build/PyDev 6.3.1.zip") File depsFile = file("${DEPS_DIR}/GhidraDev/PyDev 6.3.1.zip")
File binFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/PyDev 6.3.1.zip") File binRepoFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/PyDev 6.3.1.zip")
// First check if the file was downloaded and dropped in locally. If not, check in the bin // First check if the file is in the dependencies repo. If not, check in the bin repo.
// repo. def pyDevZipTree = depsFile.exists() ? zipTree(depsFile) : zipTree(binRepoFile)
def pyDevZipTree = localFile.exists() ? zipTree(localFile) : zipTree(binFile)
from pyDevZipTree from pyDevZipTree
exclude "**/.project", "**/.pydevproject" exclude "**/.project", "**/.pydevproject"
@ -104,12 +103,11 @@ task cdtUnpack(type:Copy) {
!cdtDestDir.exists() !cdtDestDir.exists()
} }
File localFile = file("build/cdt-8.6.0.zip") File depsFile = file("${DEPS_DIR}/GhidraDev/cdt-8.6.0.zip")
File binFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/cdt-8.6.0.zip") File binRepoFile = file("${BIN_REPO}/GhidraBuild/EclipsePlugins/GhidraDev/buildDependencies/cdt-8.6.0.zip")
// First check if the file was downloaded and dropped in locally. If not, check in the bin // First check if the file is in the dependencies repo. If not, check in the bin repo.
// repo. def cdtZipTree = depsFile.exists() ? zipTree(depsFile) : zipTree(binRepoFile)
def cdtZipTree = localFile.exists() ? zipTree(localFile) : zipTree(binFile)
from cdtZipTree from cdtZipTree

View file

@ -51,9 +51,9 @@ if ("32".equals(System.getProperty("sun.arch.data.model"))) {
* Define the location of bin repo * Define the location of bin repo
*********************************************************************************/ *********************************************************************************/
project.ext.GHIDRA_GROUP = "Z Ghidra" project.ext.GHIDRA_GROUP = "Z Ghidra"
project.ext.BIN_REPO = file("${projectDir}/../ghidra.bin").absolutePath
project.ext.ROOT_PROJECT_DIR = projectDir.absolutePath project.ext.ROOT_PROJECT_DIR = projectDir.absolutePath
project.ext.BIN_REPO_PATH = BIN_REPO // TODO make path names consistent project.ext.BIN_REPO = file("${projectDir}/../ghidra.bin").absolutePath
project.ext.DEPS_DIR = file("${projectDir}/dependencies")
/********************************************************************************* /*********************************************************************************
* Prevent forked Java processes from stealing focus * Prevent forked Java processes from stealing focus
@ -67,13 +67,14 @@ allprojects {
/********************************************************************************* /*********************************************************************************
* Use flat directory-style repository if flatRepo directory is present. * Use flat directory-style repository if flatRepo directory is present.
*********************************************************************************/ *********************************************************************************/
if (file("flatRepo").isDirectory()) { def flatRepo = file("${DEPS_DIR}/flatRepo")
if (flatRepo.isDirectory()) {
allprojects { allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
jcenter() jcenter()
flatDir name: "flat", dirs:["$rootProject.projectDir/flatRepo"] flatDir name: "flat", dirs:["$flatRepo"]
} }
} }
} }

View file

@ -178,10 +178,14 @@ def initTestJVM(Task task, String rootDirName) {
'-Duser.language=en', '-Duser.language=en',
'-Djdk.attach.allowAttachSelf', '-Djdk.attach.allowAttachSelf',
'-javaagent:' + jmockitPath, '-javaagent:' + jmockitPath,
'-DLock.DEBUG=true', '-noverify',
'-XX:TieredStopAtLevel=1',
'-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=' + debugPort '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=' + debugPort
// Note: this args are used to speed-up the tests, but are not safe for production code
// -noverify and -XX:TieredStopAtLevel=1
// Note: modern remote debug invocation; // Note: modern remote debug invocation;
// -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 // -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
@ -191,6 +195,16 @@ def initTestJVM(Task task, String rootDirName) {
// -Xnoagent // -Xnoagent
// -Djava.compiler=NONE // -Djava.compiler=NONE
// -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 // -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
//
// TODO Future Updates:
// The test configuration should be updated to support all known modes of operation:
// command-line test execution, CI test execution of a branch upon request, and full
// CI test execution (this is slow and may need to run overnight). We do not currently
// support well running tests via the command-line. See discussion at github 2854.
// For better command-line usage we will need to update tests such that they can
// share a VM, enabling us to elimnate the use of 'forEver 1' in this file.
//
} }
} }
/********************************************************************************* /*********************************************************************************

View file

@ -43,7 +43,6 @@ task rasterizeSvg(type: JavaExec) {
// added these in the individual projects which use this task (eg: to the 'compile' // added these in the individual projects which use this task (eg: to the 'compile'
// configuration) but since this is the only task which requires them, it seemed // configuration) but since this is the only task which requires them, it seemed
// appropriate to just add them here. // appropriate to just add them here.
def BIN_REPO = rootProject.file(BIN_REPO_PATH).toString()
classpath = files ( BIN_REPO + "/ExternalLibraries/libsforBuild/batik-all-1.7.jar", classpath = files ( BIN_REPO + "/ExternalLibraries/libsforBuild/batik-all-1.7.jar",
BIN_REPO + "/ExternalLibraries/libsforBuild/xml-apis-ext.jar") BIN_REPO + "/ExternalLibraries/libsforBuild/xml-apis-ext.jar")

View file

@ -346,9 +346,12 @@ def initTestJVM(Task task, String rootDirName) {
'-Duser.language=en', '-Duser.language=en',
'-Djdk.attach.allowAttachSelf', '-Djdk.attach.allowAttachSelf',
'-javaagent:' + jmockitPath, '-javaagent:' + jmockitPath,
'-DLock.DEBUG=true', '-noverify',
'-XX:TieredStopAtLevel=1',
'-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=' + debugPort '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=' + debugPort
// Note: this args are used to speed-up the tests, but are not safe for production code
// -noverify and -XX:TieredStopAtLevel=1
// Note: modern remote debug invocation; // Note: modern remote debug invocation;
// -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 // -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000

View file

@ -22,29 +22,13 @@
* immediately after cloning the Ghidra repository before any other gradle * * immediately after cloning the Ghidra repository before any other gradle *
* tasks are run. * * tasks are run. *
* * * *
* Specifically, this task: * * usage: from the command line in the main ghidra repository directory, run *
* the following: *
* * * *
* 1. Downloads various dependencies required by the ghidra build and * * gradle -I gradle/support/fetchDependencies.gradle init *
* puts them in <ghidra repo>/build/downloads/. From here they are *
* unzipped and/or copied to their final locations. The files to be *
* downloaded: *
* - dex-tools-2.0.zip *
* - AXMLPrinter2.jar *
* - hfsexplorer-0_21-bin.zip *
* - yajsw-stable-12.12.zip *
* - cdt-8.6.0.zip *
* - PyDev 6.3.1.zip *
* *
* 2. Creates a directory at <ghidra repo>/flatRepo which is used as a *
* flat directory-style respository for the files extracted above. *
* *
* usage: from the command line in the main ghidra repository *
* directory, run the following: *
* *
* gradle --init-script gradle/support/fetchDependencies.gradle init *
* * * *
* Note: When running the script, files will only be downloaded if * * Note: When running the script, files will only be downloaded if *
* necessary (eg: they are not already in the build/downloads/ * * necessary (eg: they are not already in the dependencies/downloads/ *
* directory). * * directory). *
* * * *
*******************************************************************************/ *******************************************************************************/
@ -52,87 +36,175 @@
import java.util.zip.*; import java.util.zip.*;
import java.nio.file.*; import java.nio.file.*;
import java.security.MessageDigest; import java.security.MessageDigest;
import org.apache.commons.io.*; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.*; import org.apache.commons.io.filefilter.WildcardFileFilter;
ext.HOME_DIR = System.getProperty('user.home')
ext.REPO_DIR = ((Script)this).buildscript.getSourceFile().getParentFile().getParentFile().getParentFile()
ext.FLAT_REPO_DIR = new File(REPO_DIR, "flatRepo")
ext.DOWNLOADS_DIR = new File(REPO_DIR, "build/downloads")
// Stores the size of the file being downloaded (for formatting print statements)
ext.FILE_SIZE = 0;
// The URLs for each of the dependencies
ext.DEX_ZIP = 'https://github.com/pxb1988/dex2jar/releases/download/2.0/dex-tools-2.0.zip'
ext.AXML_ZIP = 'https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/android4me/AXMLPrinter2.jar'
ext.HFS_ZIP = 'https://sourceforge.net/projects/catacombae/files/HFSExplorer/0.21/hfsexplorer-0_21-bin.zip'
ext.YAJSW_ZIP = 'https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-12.12/yajsw-stable-12.12.zip'
ext.PYDEV_ZIP = 'https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip'
ext.CDT_ZIP = 'https://archive.eclipse.org/tools/cdt/releases/8.6/cdt-8.6.0.zip'
// The SHA-256s for each of the dependencies
ext.DEX_SHA_256 = '7907eb4d6e9280b6e17ddce7ee0507eae2ef161ee29f70a10dbc6944fdca75bc'
ext.AXML_SHA_256 = '00ed038eb6abaf6ddec8d202a3ed7a81b521458f4cd459948115cfd02ff59d6d'
ext.HFS_SHA_256 = '90c9b54798abca5b12f4a678db7d0a4c970f4702cb153c11919536d0014dedbf'
ext.YAJSW_SHA_256 = '1398fcb1e93abb19992c4fa06d7fe5758aabb4c45781d7ef306c6f57ca7a7321'
ext.PYDEV_SHA_256 = '4d81fe9d8afe7665b8ea20844d3f5107f446742927c59973eade4f29809b0699'
ext.CDT_SHA_256 = '81b7d19d57c4a3009f4761699a72e8d642b5e1d9251d2bb98df438b1e28f8ba9'
// Number of times to try and establish a connection when downloading files before
// failing
ext.NUM_RETRIES = 2
// Set up a maven repository configuration so we can get access to Apache FileUtils for
// copying/deleting files.
initscript { initscript {
repositories { repositories { mavenCentral() }
mavenCentral() dependencies { classpath 'commons-io:commons-io:2.8.0' }
}
dependencies {
classpath 'commons-io:commons-io:2.5'
}
} }
// This is where the real flow of the script starts... ext.NUM_RETRIES = 3 // # of times to try to download a file before failing
try { ext.REPO_DIR = ((Script)this).buildscript.getSourceFile().getParentFile().getParentFile().getParentFile()
createDirs() ext.DEPS_DIR = file("${REPO_DIR}/dependencies")
populateFlatRepo() ext.DOWNLOADS_DIR = file("${DEPS_DIR}/downloads")
} ext.FID_DIR = file("${DEPS_DIR}/fidb")
finally { ext.FLAT_REPO_DIR = file("${DEPS_DIR}/flatRepo")
cleanup()
}
/** ext.deps = [
* Creates the directories where the dependencies will be downloaded and stored [
*/ name: 'dex-tools-2.0.zip',
def createDirs() { url: 'https://github.com/pxb1988/dex2jar/releases/download/2.0/dex-tools-2.0.zip',
if (!DOWNLOADS_DIR.exists()) { sha256: '7907eb4d6e9280b6e17ddce7ee0507eae2ef161ee29f70a10dbc6944fdca75bc',
destination: {
unzip(DOWNLOADS_DIR, DOWNLOADS_DIR, 'dex-tools-2.0.zip')
FileUtils.copyDirectory(new File(DOWNLOADS_DIR, 'dex2jar-2.0/lib/'), FLAT_REPO_DIR, new WildcardFileFilter("dex-*"));
}
],
[
name: 'hfsexplorer-0_21-bin.zip',
url: 'https://sourceforge.net/projects/catacombae/files/HFSExplorer/0.21/hfsexplorer-0_21-bin.zip',
sha256: '90c9b54798abca5b12f4a678db7d0a4c970f4702cb153c11919536d0014dedbf',
destination: {
def hfsxdir = new File (DOWNLOADS_DIR, "hfsx")
hfsxdir.mkdir()
unzip (DOWNLOADS_DIR, hfsxdir, 'hfsexplorer-0_21-bin.zip')
FileUtils.copyFileToDirectory(new File(DOWNLOADS_DIR, "hfsx/lib/csframework.jar"), FLAT_REPO_DIR);
FileUtils.copyFileToDirectory(new File(DOWNLOADS_DIR, "hfsx/lib/hfsx_dmglib.jar"), FLAT_REPO_DIR);
FileUtils.copyFileToDirectory(new File(DOWNLOADS_DIR, "hfsx/lib/hfsx.jar"), FLAT_REPO_DIR);
FileUtils.copyFileToDirectory(new File(DOWNLOADS_DIR, "hfsx/lib/iharder-base64.jar"), FLAT_REPO_DIR);
}
],
[
name: 'AXMLPrinter2.jar',
url: 'https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/android4me/AXMLPrinter2.jar',
sha256: '00ed038eb6abaf6ddec8d202a3ed7a81b521458f4cd459948115cfd02ff59d6d',
destination: FLAT_REPO_DIR
],
[
name: 'yajsw-stable-12.12.zip',
url: 'https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-12.12/yajsw-stable-12.12.zip',
sha256: '1398fcb1e93abb19992c4fa06d7fe5758aabb4c45781d7ef306c6f57ca7a7321',
destination: file("${DEPS_DIR}/GhidraServer")
],
[
name: 'PyDev 6.3.1.zip',
url: 'https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip',
sha256: '4d81fe9d8afe7665b8ea20844d3f5107f446742927c59973eade4f29809b0699',
destination: file("${DEPS_DIR}/GhidraDev")
],
[
name: 'cdt-8.6.0.zip',
url: 'https://archive.eclipse.org/tools/cdt/releases/8.6/cdt-8.6.0.zip',
sha256: '81b7d19d57c4a3009f4761699a72e8d642b5e1d9251d2bb98df438b1e28f8ba9',
destination: file("${DEPS_DIR}/GhidraDev")
],
[
name: 'vs2012_x64.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2012_x64.fidb',
sha256: 'f26548a6df6b6963a418d8c83ac216d9e196b180d944a52b8123c457d472b7c9',
destination: FID_DIR
],
[
name: 'vs2012_x86.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2012_x86.fidb',
sha256: '0a8962cf3699d5b8d4b3a79400382462519edc26570a46b2085200e38534f900',
destination: FID_DIR
],
[
name: 'vs2015_x64.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2015_x64.fidb',
sha256: '187248f87fc1deb695bc3051b2d92f9b7482023a356821154db22478eed13088',
destination: FID_DIR
],
[
name: 'vs2015_x86.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2015_x86.fidb',
sha256: '1d05afa070e9c09b83ee15d544c8559ed0d2b53d7eac476f8f5f8849543b3812',
destination: FID_DIR
],
[
name: 'vs2017_x64.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2017_x64.fidb',
sha256: '1784ad6b25571177ff8212871867559998c6b8256bb1dbaeee864b580c1b2d6a',
destination: FID_DIR
],
[
name: 'vs2017_x86.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2017_x86.fidb',
sha256: 'bc9bf30621190e0eb56c4db5ec30ad0401ca7be0311f5a2ce3d894178eafd19c',
destination: FID_DIR
],
[
name: 'vs2019_x64.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2019_x64.fidb',
sha256: 'aab04eefd1142f7b3c3f86c8d766abe361b167b4fe4157c36fad18777b2a6fbd',
destination: FID_DIR
],
[
name: 'vs2019_x86.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2019_x86.fidb',
sha256: '0a2282ac3479ffc022e6cdb4e32e057bc10f0394cfb0f8016d7145be0167f5f7',
destination: FID_DIR
],
[
name: 'vsOlder_x64.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vsOlder_x64.fidb',
sha256: 'fe1856c0acad297d9ba4fb6a2df1d32ba34df766d9f1a2a16da0ca2b375e23dd',
destination: FID_DIR
],
[
name: 'vsOlder_x86.fidb',
url: 'https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vsOlder_x86.fidb',
sha256: '46e56bc82ba68ad4e9a3c6a2e4ecd3428e2c390c7de0a379fa0165a58d46e115',
destination: FID_DIR
]
]
// Download dependencies (if necessary) and verify their hashes
DOWNLOADS_DIR.mkdirs() DOWNLOADS_DIR.mkdirs()
} deps.each {
if (!FLAT_REPO_DIR.exists()) { File file = new File(DOWNLOADS_DIR, it.name)
FLAT_REPO_DIR.mkdirs() if (!it.sha256.equals(generateHash(file))) {
download(it.url, file.path)
assert(it.sha256.equals(generateHash(file)));
} }
} }
// Copies the downloaded dependencies to their required destination.
// Some downloads require pre-processing before their relevant pieces can be copied.
deps.each {
if (it.destination instanceof File) {
println("Copying " + it.name + " to " + it.destination)
it.destination.mkdirs()
FileUtils.copyFile(new File(DOWNLOADS_DIR, it.name), new File(it.destination, it.name));
}
else if (it.destination instanceof Closure) {
println("Processing " + it.name)
it.destination()
}
else {
throw new GradleException("Unexpected destination type: " + it.destination)
}
}
//-------------------------------------Helper methods----------------------------------------------
/** /**
* Downloads a file from a URL. If there is a problem connecting to the given * Downloads a file from a URL. The download attempt will be tried NUM_RETRIES times before failing.
* URL the attempt will be retried NUM_RETRIES times before failing.
* *
* Progress is shown on the command line in the form of the number of bytes * Progress is shown on the command line in the form of the number of bytes downloaded and a
* downloaded and a percentage of the total. * percentage of the total.
* *
* Note: We do not validate that the number of bytes downloaded matches the * Note: We do not validate that the number of bytes downloaded matches the expected total here; any
* expected total here; any discrepencies will be caught when checking * discrepencies will be caught when checking the SHA-256s later on.
* the SHA-256s later on.
* *
* @param url the file to download * @param url the file to download
* @param filename the local file to create for the download * @param filename the local file to create for the download
*/ */
def download(url, filename) { def download(url, filename) {
println("File: " + url) println("URL: " + url)
BufferedInputStream istream = establishConnection(url, NUM_RETRIES); def(InputStream istream, size) = establishConnection(url, NUM_RETRIES);
assert istream != null : " ***CONNECTION FAILURE***\n max attempts exceeded; exiting\n" assert istream != null : " ***CONNECTION FAILURE***\n max attempts exceeded; exiting\n"
FileOutputStream ostream = new FileOutputStream(filename); FileOutputStream ostream = new FileOutputStream(filename);
@ -140,45 +212,45 @@ def download(url, filename) {
int bytesRead; int bytesRead;
int totalRead; int totalRead;
while ((bytesRead = istream.read(dataBuffer, 0, 1024)) != -1) { while ((bytesRead = istream.read(dataBuffer, 0, 1024)) != -1) {
ostream.write(dataBuffer, 0, bytesRead); ostream.write(dataBuffer, 0, bytesRead);
totalRead += bytesRead totalRead += bytesRead
print("\r") print("\r")
if (FILE_SIZE.equals("unknown")) { print(" Downloading: " + totalRead + " of " + size)
print(" Downloading: " + totalRead + " of " + FILE_SIZE) if (!size.equals("???")) {
} int pctComplete = (totalRead / size) * 100
else { print(" (" + pctComplete + "%)")
int pctComplete = (totalRead / FILE_SIZE) * 100
print(" Downloading: " + totalRead + " of " + FILE_SIZE + " (" + pctComplete + "%)")
} }
print(" ") // overwrite gradle timer output
System.out.flush() System.out.flush()
} }
println("") println()
istream.close(); istream.close();
ostream.close(); ostream.close();
} }
/** /**
* Attemps to establish a connection to the given URL. * Attempts to establish a connection to the given URL
* *
* @param url the site to connect to * @param url the URL to connect to
* @param retries the number of times to attempt to reconnect if there is a failure * @param retries the number of times to attempt to connect if there are failures
* @return the InputStream for the URL * @return the InputStream for the URL, and the size of the download in bytes as a string
*/ */
def establishConnection(url, retries) { def establishConnection(url, retries) {
for (int i = 0; i < retries; i++) { for (int i = 0; i < retries; i++) {
try { try {
println(" Connect attempt " + (i+1) + " of " + retries) if (i == 0) {
URLConnection conn = new URL(url).openConnection(); println(" Connecting...")
FILE_SIZE = conn.getContentLength();
if (FILE_SIZE == -1) {
// This can happen if there is a problem retrieving the size; we've seen it happen
// in testing.
FILE_SIZE = "unknown"
} }
return new BufferedInputStream(new URL(url).openStream()); else {
println(" Connecting (" + (i+1) + "/" + retries + ")...")
}
URLConnection conn = new URL(url).openConnection();
conn.setRequestMethod("HEAD");
def size = conn.getContentLengthLong();
if (size == -1) {
size = "???"
}
return [new BufferedInputStream(new URL(url).openStream()), size];
} }
catch (Exception e) { catch (Exception e) {
println(" Connection error! " + e) println(" Connection error! " + e)
@ -195,7 +267,6 @@ def establishConnection(url, retries) {
*/ */
def unzip(sourceDir, targetDir, zipFileName) { def unzip(sourceDir, targetDir, zipFileName) {
def zip = new ZipFile(new File(sourceDir, zipFileName)) def zip = new ZipFile(new File(sourceDir, zipFileName))
zip.entries().findAll { !it.directory }.each { e -> zip.entries().findAll { !it.directory }.each { e ->
(e.name as File).with { f -> (e.name as File).with { f ->
if (f.parentFile != null) { if (f.parentFile != null) {
@ -208,75 +279,16 @@ def unzip(sourceDir, targetDir, zipFileName) {
} }
} }
} }
zip.close()
} }
/** /**
* Downloads and stores the necessary dependencies in the local flat repository. * Generates the SHA-256 hash for the given file
* *
* If the dependency already exists in the downloads folder (DOWNLOADS_DIR) and has the * @param file the file to generate the SHA-256 hash for
* proper checksum, it will NOT be re-downloaded. * @return the generated SHA-256 hash, or null if the file does not exist
*/ */
def populateFlatRepo() { def generateHash(file) {
// 1. Download all the dependencies and verify their checksums. If the dependency has already
// been download, do NOT download again.
File file = new File(DOWNLOADS_DIR, 'dex-tools-2.0.zip')
if (!DEX_SHA_256.equals(generateChecksum(file))) {
download (DEX_ZIP, file.path)
validateChecksum(generateChecksum(file), DEX_SHA_256);
}
file = new File(DOWNLOADS_DIR, 'AXMLPrinter2.jar')
if (!AXML_SHA_256.equals(generateChecksum(file))) {
download (AXML_ZIP, file.path)
validateChecksum(generateChecksum(file), AXML_SHA_256);
}
file = new File(DOWNLOADS_DIR, 'hfsexplorer-0_21-bin.zip')
if (!HFS_SHA_256.equals(generateChecksum(file))) {
download (HFS_ZIP, file.path)
validateChecksum(generateChecksum(file), HFS_SHA_256);
}
file = new File(DOWNLOADS_DIR, 'yajsw-stable-12.12.zip')
if (!YAJSW_SHA_256.equals(generateChecksum(file))) {
download (YAJSW_ZIP, file.path)
validateChecksum(generateChecksum(file), YAJSW_SHA_256);
}
file = new File(DOWNLOADS_DIR, 'PyDev 6.3.1.zip')
if (!PYDEV_SHA_256.equals(generateChecksum(file))) {
download (PYDEV_ZIP, file.path)
validateChecksum(generateChecksum(file), PYDEV_SHA_256);
}
file = new File(DOWNLOADS_DIR, 'cdt-8.6.0.zip')
if (!CDT_SHA_256.equals(generateChecksum(file))) {
download (CDT_ZIP, file.path)
validateChecksum(generateChecksum(file), CDT_SHA_256);
}
// 2. Unzip the dependencies
unzip(DOWNLOADS_DIR, DOWNLOADS_DIR, "dex-tools-2.0.zip")
unzipHfsx()
// 3. Copy the necessary jars to the flatRepo directory. Yajsw, CDT, and PyDev go directly into
// the source repository.
copyDexTools()
copyAXML()
copyHfsx()
copyYajsw()
copyPyDev()
copyCdt()
}
/**
* Generates the SHA-256 for the given file
*
* @param file the file to generate the checksum for
* @return the generated checksum
*/
def generateChecksum(file) {
if (!file.exists()) { if (!file.exists()) {
return null return null
} }
@ -287,98 +299,5 @@ def generateChecksum(file) {
for (byte b : digest) { for (byte b : digest) {
sb.append(String.format("%02x", b)); sb.append(String.format("%02x", b));
} }
return sb.toString(); return sb.toString();
} }
/**
* Compares two checksums and generates an assert failure if they do not match
*
* @param sourceSha256 the checksum to validate
* @param expectedSha256 the expected checksum
*/
def validateChecksum(sourceSha256, expectedSha256) {
assert(sourceSha256.equals(expectedSha256));
}
/**
* Unzips the hfsx zip file
*/
def unzipHfsx() {
def hfsxdir = getOrCreateTempHfsxDir()
unzip (DOWNLOADS_DIR, hfsxdir, "hfsexplorer-0_21-bin.zip")
}
/**
* Copies the dex-tools jars to the flat repository
*
* Note: This will only copy files beginning with "dex-"
*/
def copyDexTools() {
FileUtils.copyDirectory(new File(DOWNLOADS_DIR, 'dex2jar-2.0/lib/'), FLAT_REPO_DIR, new WildcardFileFilter("dex-*"));
}
/**
* Copies the AXMLPrinter2 jar to the flat repository
*/
def copyAXML() {
FileUtils.copyFile(new File(DOWNLOADS_DIR, 'AXMLPrinter2.jar'), new File(FLAT_REPO_DIR, "AXMLPrinter2.jar"));
}
/**
* Copies the necessary hfsx jars to the flat repository
*/
def copyHfsx() {
FileUtils.copyFile(new File(DOWNLOADS_DIR, "hfsx/lib/csframework.jar"), new File(FLAT_REPO_DIR, "csframework.jar"));
FileUtils.copyFile(new File(DOWNLOADS_DIR, "hfsx/lib/hfsx_dmglib.jar"), new File(FLAT_REPO_DIR, "hfsx_dmglib.jar"));
FileUtils.copyFile(new File(DOWNLOADS_DIR, "hfsx/lib/hfsx.jar"), new File(FLAT_REPO_DIR, "hfsx.jar"));
FileUtils.copyFile(new File(DOWNLOADS_DIR, "hfsx/lib/iharder-base64.jar"), new File(FLAT_REPO_DIR, "iharder-base64.jar"));
}
/**
* Copies the yajswdir zip to its location in the GhidraServer project.
*/
def copyYajsw() {
FileUtils.copyFile(new File(DOWNLOADS_DIR, "yajsw-stable-12.12.zip"), new File(REPO_DIR, "Ghidra/Features/GhidraServer/build/yajsw-stable-12.12.zip"));
}
/**
* Copies the pydev zip to its bin repository location
*/
def copyPyDev() {
FileUtils.copyFile(new File(DOWNLOADS_DIR, 'PyDev 6.3.1.zip'), new File(REPO_DIR, "GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/PyDev 6.3.1.zip"));
}
/**
* Copies the cdt zip to its bin repository location
*/
def copyCdt() {
FileUtils.copyFile(new File(DOWNLOADS_DIR, 'cdt-8.6.0.zip'), new File(REPO_DIR, "GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/cdt-8.6.0.zip"));
}
/**
* Creates a temporary folder to house the hfsx zip contents
*
* @return the newly-created hfsx directory object
*/
def getOrCreateTempHfsxDir() {
def hfsxdir = new File (DOWNLOADS_DIR, "hfsx")
if (!hfsxdir.exists()) {
hfsxdir.mkdir()
}
return hfsxdir;
}
/**
* Performs any cleanup operations that need to be performed after the flat repo has
* been populated.
*/
def cleanup() {
// Uncomment this if we want to delete the downloads folder. For now, leave this and
// depend on a gradle clean to wipe it out.
//
//if (DOWNLOADS_DIR.exists()) {
// FileUtils.deleteDirectory(DOWNLOADS_DIR)
//}
}