Merge remote-tracking branch 'origin/GP-3344_dev747368_applydataarchives_choice--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-05-01 06:16:05 -04:00
commit 5396d68128
14 changed files with 828 additions and 247 deletions

View file

@ -15,18 +15,28 @@
*/
package ghidra.app.plugin.core.analysis;
import java.util.ArrayList;
import java.util.List;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import docking.options.editor.FileChooserEditor;
import docking.options.editor.StringWithChoicesEditor;
import generic.jar.ResourceFile;
import ghidra.app.cmd.function.ApplyFunctionDataTypesCmd;
import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility;
import ghidra.app.services.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.Application;
import ghidra.framework.model.*;
import ghidra.framework.options.OptionType;
import ghidra.framework.options.Options;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.Msg;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
@ -35,12 +45,36 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
private static final String DESCRIPTION =
"Apply known data type archives based on program information.";
protected static final String OPTION_NAME_CREATE_BOOKMARKS = "Create Analysis Bookmarks";
private static final String OPTION_DESCRIPTION_CREATE_BOOKMARKS =
"If checked, an analysis bookmark will be created at each symbol address " +
"where multiple function definitions were found and not applied.";
private static final String OPTION_NAME_CREATE_BOOKMARKS = "Create Analysis Bookmarks";
private static final String OPTION_DESCRIPTION_CREATE_BOOKMARKS = """
If checked, an analysis bookmark will be created at each symbol address \
where multiple function definitions were found and not applied.""";
private static final boolean OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED = true;
private static final String OPTION_NAME_ARCHIVE_CHOOSER = "Archive Chooser";
private static final String OPTION_DESCRIPTION_ARCHIVE_CHOOSER =
"Specifies the data type archive to apply";
private static final String OPTION_NAME_GDT_FILEPATH = "GDT User File Archive Path";
private static final String OPTION_DESCRIPTION_GDT_FILEPATH = """
Path to a user-supplied data type archive .gdt file, \
only valid when 'Archive Chooser' is '[User-File-Archive]'""";
private static final String OPTION_NAME_PROJECT_PATH = "User Project Archive Path";
private static final String OPTION_DESCRIPTION_PROJECT_PATH = """
Path to a user-supplied data type archive located in the project, \
only valid when 'Archive Chooser' is '[User-Project-Archive]'""";
private static final String CHOOSER_AUTO_DETECT = "[Auto-Detect]";
private static final String CHOOSER_USER_FILE_ARCHIVE = "[User-File-Archive]";
private static final String CHOOSER_USER_PROJECT_ARCHIVE = "[User-Project-Archive]";
private boolean createBookmarksEnabled = OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED;
private String archiveChooser = CHOOSER_AUTO_DETECT;
private File userGdtFileArchive;
private String userProjectArchive;
private Map<String, ResourceFile> builtinGDTs = getBuiltInGdts();
private DataTypeManagerService dtmService;
public ApplyDataArchiveAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER);
@ -50,52 +84,24 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) {
AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program);
DataTypeManagerService service = mgr.getDataTypeManagerService();
dtmService = AutoAnalysisManager.getAnalysisManager(program).getDataTypeManagerService();
// pick the archives to apply
List<String> archiveList = DataTypeArchiveUtility.getArchiveList(program);
List<DataTypeManager> managerList = new ArrayList<>();
monitor.initialize(archiveList.size());
// pick the archives to apply, typically 0 or 1
List<DataTypeManager> managerList = getDataTypeArchives(program, log, monitor);
// apply the archive restricted to the address set
for (String archiveName : archiveList) {
if (monitor.isCancelled()) {
break;
}
DataTypeManager dtm = null;
try {
dtm = service.openDataTypeArchive(archiveName);
if (dtm == null) {
log.appendMsg("Apply Data Archives",
"Failed to locate data type archive: " + archiveName);
}
else {
managerList.add(dtm);
}
}
catch (Exception e) {
Throwable cause = e.getCause();
if (cause instanceof VersionException) {
log.appendMsg("Apply Data Archives",
"Unable to open archive " + archiveName + ": " + cause.toString());
}
else {
String msg = e.getMessage();
if (msg == null) {
msg = e.toString();
}
log.appendMsg("Apply Data Archives",
"Unexpected Error opening archive " + archiveName + ": " + msg);
}
if (!managerList.isEmpty()) {
monitor.setMessage("Applying Function Signatures...");
// TODO: SourceType of imported is not exactly right here.
// This isn't imported. Need to add some other sourceType, like SecondaryInfo
ApplyFunctionDataTypesCmd cmd = new ApplyFunctionDataTypesCmd(managerList, set,
SourceType.IMPORTED, false, createBookmarksEnabled);
cmd.applyTo(program, monitor);
for (DataTypeManager dtm : managerList) {
Msg.info(this, "Applied data type archive: %s".formatted(dtm.getName()));
}
}
monitor.setMessage("Applying Function Signatures...");
// TODO: SourceType of imported is not exactly right here.
// This isn't imported. Need to add some other sourceType, like SecondaryInfo
ApplyFunctionDataTypesCmd cmd = new ApplyFunctionDataTypesCmd(managerList, set,
SourceType.IMPORTED, false, createBookmarksEnabled);
cmd.applyTo(program, monitor);
return true;
}
@ -103,12 +109,177 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
public void registerOptions(Options options, Program program) {
options.registerOption(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled, null,
OPTION_DESCRIPTION_CREATE_BOOKMARKS);
List<String> chooserList = new ArrayList<>();
chooserList.add(CHOOSER_AUTO_DETECT);
chooserList.add(CHOOSER_USER_FILE_ARCHIVE);
chooserList.add(CHOOSER_USER_PROJECT_ARCHIVE);
chooserList.addAll(builtinGDTs.keySet());
options.registerOption(OPTION_NAME_ARCHIVE_CHOOSER, OptionType.STRING_TYPE,
CHOOSER_AUTO_DETECT, null, OPTION_DESCRIPTION_ARCHIVE_CHOOSER,
new StringWithChoicesEditor(chooserList));
options.registerOption(OPTION_NAME_GDT_FILEPATH, OptionType.FILE_TYPE, null, null,
OPTION_DESCRIPTION_GDT_FILEPATH,
new FileChooserEditor(FileDataTypeManager.GDT_FILEFILTER));
options.registerOption(OPTION_NAME_PROJECT_PATH, OptionType.STRING_TYPE, null, null,
OPTION_DESCRIPTION_PROJECT_PATH, new ProjectPathChooserEditor(
"Choose Data Type Archive", DATATYPEARCHIVE_PROJECT_FILTER));
}
@Override
public void optionsChanged(Options options, Program program) {
createBookmarksEnabled =
options.getBoolean(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled);
archiveChooser = options.getString(OPTION_NAME_ARCHIVE_CHOOSER, archiveChooser);
userGdtFileArchive = options.getFile(OPTION_NAME_GDT_FILEPATH, userGdtFileArchive);
userProjectArchive = options.getString(OPTION_NAME_PROJECT_PATH, userProjectArchive);
}
private List<DataTypeManager> getDataTypeArchives(Program program, MessageLog log,
TaskMonitor monitor) {
switch (archiveChooser) {
case CHOOSER_AUTO_DETECT:
return getAutoDTMs(program, log, monitor);
case CHOOSER_USER_FILE_ARCHIVE:
return openUserFileArchive(userGdtFileArchive, log);
case CHOOSER_USER_PROJECT_ARCHIVE:
return openUserProjectArchive(userProjectArchive, program, log, monitor);
default:
return openBuiltinGDT(archiveChooser, log);
}
}
private List<DataTypeManager> getAutoDTMs(Program program, MessageLog log,
TaskMonitor monitor) {
List<String> archiveList = DataTypeArchiveUtility.getArchiveList(program);
List<DataTypeManager> result = new ArrayList<>();
monitor.initialize(archiveList.size());
for (String archiveName : archiveList) {
if (monitor.isCancelled()) {
break;
}
try {
DataTypeManager dtm = dtmService.openDataTypeArchive(archiveName);
if (dtm == null) {
log.appendMsg("Apply Data Archives",
"Failed to locate data type archive: " + archiveName);
}
else {
result.add(dtm);
}
}
catch (Exception e) {
Throwable cause = e.getCause();
if (cause instanceof VersionException) {
log.appendMsg("Apply Data Archives",
"Unable to open archive %s: %s".formatted(archiveName, cause.toString()));
}
else {
String msg = Objects.requireNonNullElse(e.getMessage(), e.toString());
log.appendMsg("Apply Data Archives",
"Unexpected Error opening archive %s: %s".formatted(archiveName, msg));
}
}
}
return result;
}
private List<DataTypeManager> openBuiltinGDT(String gdtName, MessageLog log) {
// opens a gdt that was included in the ghidra distro, not the 'built-in' dtm.
ResourceFile gdtFile = builtinGDTs.get(gdtName);
if (gdtFile == null) {
log.appendMsg("Unknown built-in archive: %s".formatted(gdtName));
return List.of();
}
try {
return List.of(dtmService.openArchive(gdtFile, false));
}
catch (Exception e) {
Throwable cause = e.getCause();
if (cause instanceof VersionException) {
log.appendMsg("Apply Data Archives",
"Unable to open archive %s: %s".formatted(gdtName, cause.toString()));
}
else {
String msg = Objects.requireNonNullElse(e.getMessage(), e.toString());
log.appendMsg("Apply Data Archives",
"Unexpected Error opening archive %s: %s".formatted(gdtName, msg));
}
}
return List.of();
}
private List<DataTypeManager> openUserFileArchive(File gdtFile, MessageLog log) {
if (gdtFile == null) {
return List.of();
}
if (!gdtFile.isFile()) {
log.appendMsg("Missing archive: %s".formatted(gdtFile));
return List.of();
}
try {
return List.of(dtmService.openArchive(new ResourceFile(gdtFile), false));
}
catch (Exception e) {
Throwable cause = e.getCause();
if (cause instanceof VersionException) {
log.appendMsg("Apply Data Archives",
"Unable to open archive %s: %s".formatted(gdtFile, cause.toString()));
}
else {
String msg = Objects.requireNonNullElse(e.getMessage(), e.toString());
log.appendMsg("Apply Data Archives",
"Unexpected Error opening archive %s: %s".formatted(gdtFile, msg));
}
}
return List.of();
}
private List<DataTypeManager> openUserProjectArchive(String filename, Program program,
MessageLog log, TaskMonitor monitor) {
if (filename == null || filename.isBlank()) {
return List.of();
}
ProjectData projectData = program.getDomainFile().getParent().getProjectData();
DomainFile gdtDomainFile = projectData.getFile(filename);
if (gdtDomainFile == null) {
log.appendMsg("Missing project archive: %s".formatted(filename));
return List.of();
}
if (!DataTypeArchive.class.isAssignableFrom(gdtDomainFile.getDomainObjectClass())) {
log.appendMsg("Bad project file type: %s".formatted(filename));
return List.of();
}
try {
return List.of(dtmService.openArchive(gdtDomainFile, monitor));
}
catch (Exception e) {
Throwable cause = e.getCause();
if (cause instanceof VersionException) {
log.appendMsg("Apply Data Archives",
"Unable to open project archive %s: %s".formatted(filename, cause.toString()));
}
else {
String msg = Objects.requireNonNullElse(e.getMessage(), e.toString());
log.appendMsg("Apply Data Archives",
"Unexpected Error opening project archive %s: %s".formatted(filename, msg));
}
}
return List.of();
}
//---------------------------------------------------------------------------------------------
private static Map<String, ResourceFile> getBuiltInGdts() {
return Application.findFilesByExtensionInApplication(".gdt")
.stream()
.collect(Collectors.toMap(f -> f.getName(), f -> f));
}
private static final DomainFileFilter DATATYPEARCHIVE_PROJECT_FILTER =
df -> DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
}

View file

@ -15,116 +15,27 @@
*/
package ghidra.app.plugin.core.analysis;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import java.util.List;
import java.util.Set;
import javax.swing.tree.TreePath;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility;
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.app.plugin.core.datamgr.archive.DefaultDataTypeArchiveService;
import ghidra.app.plugin.core.datamgr.util.DataTypeComparator;
import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManagerChangeListener;
import ghidra.util.HelpLocation;
import ghidra.util.UniversalID;
// FIXME!! TESTING
public class DefaultDataTypeManagerService implements DataTypeManagerService {
private Map<String, FileDataTypeManager> archiveMap = new HashMap<>();
private DataTypeManager builtInDataTypesManager = BuiltInDataTypeManager.getDataTypeManager();
void dispose() {
for (FileDataTypeManager dtfm : archiveMap.values()) {
dtfm.close();
}
archiveMap.clear();
}
public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
implements DataTypeManagerService {
// TODO: This implementation needs to be consolidated with the tool-based service in
// favor of a single static data type manager service used by both the tool and
// headless scenarios
private FileDataTypeManager findOpenFileArchiveWithID(UniversalID universalID) {
if (universalID == null) {
return null;
}
for (FileDataTypeManager dtm : archiveMap.values()) {
if (universalID.equals(dtm.getUniversalID()) && !dtm.isClosed()) {
return dtm;
}
}
return null;
}
@Override
public synchronized DataTypeManager openDataTypeArchive(String archiveName)
throws IOException, DuplicateIdException {
if (archiveMap.containsKey(archiveName)) {
FileDataTypeManager dtm = archiveMap.get(archiveName);
if (!dtm.isClosed()) {
return dtm;
}
archiveMap.remove(archiveName);
}
ResourceFile archiveFile = DataTypeArchiveUtility.findArchiveFile(archiveName);
if (archiveFile == null) {
return null;
}
FileDataTypeManager fileDtm = FileDataTypeManager.openFileArchive(archiveFile, false);
FileDataTypeManager existingDtm = findOpenFileArchiveWithID(fileDtm.getUniversalID());
if (existingDtm != null) {
fileDtm.close();
throw new DuplicateIdException(fileDtm.getName(), existingDtm.getName());
}
archiveMap.put(archiveName, fileDtm);
return fileDtm;
}
@Override
public void closeArchive(DataTypeManager dtm) {
String archiveName = null;
Set<Entry<String, FileDataTypeManager>> entries = archiveMap.entrySet();
for (Entry<String, FileDataTypeManager> entry : entries) {
FileDataTypeManager manager = entry.getValue();
if (manager.equals(dtm)) {
archiveName = entry.getKey();
break;
}
}
if (archiveName != null) {
FileDataTypeManager manager = archiveMap.get(archiveName);
archiveMap.remove(archiveName);
manager.close();
}
}
@Override
public DataTypeManager[] getDataTypeManagers() {
ArrayList<FileDataTypeManager> dtmList = new ArrayList<>();
for (FileDataTypeManager dtm : archiveMap.values()) {
if (!dtm.isClosed()) {
dtmList.add(dtm);
}
}
DataTypeManager[] managers = new DataTypeManager[dtmList.size()];
dtmList.toArray(managers);
return managers;
}
@Override
public HelpLocation getEditorHelpLocation(DataType dataType) {
throw new UnsupportedOperationException();
@ -145,11 +56,6 @@ public class DefaultDataTypeManagerService implements DataTypeManagerService {
throw new UnsupportedOperationException();
}
@Override
public DataTypeManager getBuiltInDataTypesManager() {
return builtInDataTypesManager;
}
@Override
public DataType getDataType(String filterText) {
throw new UnsupportedOperationException();
@ -206,14 +112,4 @@ public class DefaultDataTypeManagerService implements DataTypeManagerService {
public Set<String> getPossibleEquateNames(long value) {
throw new UnsupportedOperationException();
}
@Override
public Archive openArchive(File file, boolean acquireWriteLock) {
throw new UnsupportedOperationException();
}
@Override
public Archive openArchive(DataTypeArchive dataTypeArchive) {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,164 @@
/* ###
* 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.analysis;
import java.awt.Component;
import java.awt.event.MouseListener;
import java.beans.PropertyEditorSupport;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.button.BrowseButton;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFileFilter;
/**
* Bean editor to show a text field and a browse button to bring
* up a Domain File Chooser dialog. The path of the chosen domain file is returned as
* a String value.
*/
public class ProjectPathChooserEditor extends PropertyEditorSupport {
private final static int NUMBER_OF_COLUMNS = 20;
private JTextField textField = new JTextField(NUMBER_OF_COLUMNS);
private MouseListener otherMouseListener;
private String title;
private DomainFileFilter filter;
public ProjectPathChooserEditor() {
this(null, null);
}
public ProjectPathChooserEditor(String title, DomainFileFilter filter) {
this.title = title;
this.filter = filter;
}
@Override
public String getAsText() {
return textField.getText().trim();
}
@Override
public Object getValue() {
String text = getAsText();
if (StringUtils.isBlank(text)) {
return null;
}
return text;
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null || text.trim().isEmpty()) {
text = "";
}
textField.setText(text);
}
@Override
public void setValue(Object value) {
if (value == null) {
setAsText("");
}
else if (value instanceof String s) {
setAsText(s);
}
}
@Override
public boolean supportsCustomEditor() {
return true;
}
@Override
public Component getCustomEditor() {
return new ProjectFileChooserPanel();
}
void setMouseListener(MouseListener listener) {
this.otherMouseListener = listener;
}
private class ProjectFileChooserPanel extends JPanel {
private JButton browseButton;
private ProjectFileChooserPanel() {
BoxLayout bl = new BoxLayout(this, BoxLayout.X_AXIS);
setLayout(bl);
browseButton = new BrowseButton();
add(textField);
add(Box.createHorizontalStrut(5));
add(browseButton);
setBorder(BorderFactory.createEmptyBorder());
textField.addActionListener(e -> ProjectPathChooserEditor.this.firePropertyChange());
textField.getDocument().addDocumentListener(new TextListener());
browseButton.addActionListener(e -> displayFileChooser());
if (otherMouseListener != null) {
textField.addMouseListener(otherMouseListener);
browseButton.addMouseListener(otherMouseListener);
}
}
private void displayFileChooser() {
AtomicReference<String> result = new AtomicReference<>();
DataTreeDialog dataTreeDialog =
new DataTreeDialog(this, title, DataTreeDialog.OPEN, filter);
dataTreeDialog.addOkActionListener(e -> {
dataTreeDialog.close();
DomainFile df = dataTreeDialog.getDomainFile();
result.set(df != null ? df.getPathname() : null);
});
dataTreeDialog.showComponent();
String newPath = result.get();
if (newPath != null) {
textField.setText(newPath);
ProjectPathChooserEditor.this.firePropertyChange();
}
}
}
private class TextListener implements DocumentListener {
@Override
public void changedUpdate(DocumentEvent e) {
ProjectPathChooserEditor.this.firePropertyChange();
}
@Override
public void insertUpdate(DocumentEvent e) {
ProjectPathChooserEditor.this.firePropertyChange();
}
@Override
public void removeUpdate(DocumentEvent e) {
ProjectPathChooserEditor.this.firePropertyChange();
}
}
}

View file

@ -45,8 +45,7 @@ import ghidra.app.plugin.core.datamgr.editor.DataTypeEditorManager;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
import ghidra.app.plugin.core.datamgr.util.DataDropOnBrowserHandler;
import ghidra.app.plugin.core.datamgr.util.DataTypeChooserDialog;
import ghidra.app.services.CodeViewerService;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.services.*;
import ghidra.app.util.HelpTopics;
import ghidra.framework.Application;
import ghidra.framework.main.OpenVersionedFileDialog;
@ -62,7 +61,10 @@ import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.listing.Program;
import ghidra.util.*;
import ghidra.util.datastruct.LRUMap;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
/**
* Plugin to pop up the dialog to manage data types in the program
@ -78,11 +80,12 @@ import ghidra.util.task.TaskLauncher;
description = "Provides the window for managing and categorizing dataTypes. " +
"The datatype display shows all built-in datatypes, datatypes in the " +
"current program, and datatypes in all open archives.",
servicesProvided = { DataTypeManagerService.class }
servicesProvided = { DataTypeManagerService.class, DataTypeArchiveService.class }
)
//@formatter:on
public class DataTypeManagerPlugin extends ProgramPlugin
implements DomainObjectListener, DataTypeManagerService, PopupActionProvider {
implements DomainObjectListener, DataTypeManagerService, DataTypeArchiveService,
PopupActionProvider {
private static final String EXTENSIONS_PATH_PREFIX = Path.GHIDRA_HOME + "/Extensions";
@ -557,6 +560,20 @@ public class DataTypeManagerPlugin extends ProgramPlugin
return dataTypeManagerHandler.openArchive(archiveName);
}
@Override
public DataTypeManager openArchive(ResourceFile file, boolean acquireWriteLock)
throws IOException, DuplicateIdException {
Archive archive = openArchive(file.getFile(true), acquireWriteLock);
return archive.getDataTypeManager();
}
@Override
public DataTypeManager openArchive(DomainFile domainFile, TaskMonitor monitor)
throws VersionException, CancelledException, IOException, DuplicateIdException {
DataTypeArchive archive = openArchive(domainFile);
return archive.getDataTypeManager();
}
public List<Archive> getAllArchives() {
return dataTypeManagerHandler.getAllArchives();
}

View file

@ -36,7 +36,6 @@ import ghidra.framework.GenericRunInfo;
import ghidra.framework.preferences.Preferences;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.util.Msg;
import ghidra.util.filechooser.ExtensionFileFilter;
public class OpenArchiveAction extends DockingAction {
@ -59,8 +58,7 @@ public class OpenArchiveAction extends DockingAction {
GhidraFileChooser fileChooser = new GhidraFileChooser(tree);
File archiveDirectory = getArchiveDirectory();
fileChooser.setFileFilter(new ExtensionFileFilter(
new String[] { FileDataTypeManager.EXTENSION }, "Ghidra Data Type Files"));
fileChooser.setFileFilter(FileDataTypeManager.GDT_FILEFILTER);
fileChooser.setCurrentDirectory(archiveDirectory);
fileChooser.setApproveButtonText("Open DataType Archive File");
fileChooser.setApproveButtonToolTipText("Open DataType Archive File");

View file

@ -0,0 +1,231 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.archive;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility;
import ghidra.app.services.DataTypeArchiveService;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.util.Msg;
import ghidra.util.UniversalID;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Simple, non-ui implementation of the {@link DataTypeArchiveService} interface.
*/
public class DefaultDataTypeArchiveService implements DataTypeArchiveService {
protected record DataTypeManagerInfo(ResourceFile file, DomainObject domainObject, String name,
DataTypeManager dtm) {
public boolean isClosed() {
return dtm instanceof FileDataTypeManager fdtm
? fdtm.isClosed()
: false;
}
}
protected Map<UniversalID, DataTypeManagerInfo> openDTMs = new HashMap<>();
protected BuiltInDataTypeManager builtInDataTypesManager;
public DefaultDataTypeArchiveService() {
this.builtInDataTypesManager = BuiltInDataTypeManager.getDataTypeManager();
}
public synchronized void dispose() {
for (DataTypeManagerInfo dtmInfo : new ArrayList<>(openDTMs.values())) {
closeDTM(dtmInfo); // mutates openDTMs map
}
}
@Override
public DataTypeManager getBuiltInDataTypesManager() {
return builtInDataTypesManager;
}
@Override
public DataTypeManager[] getDataTypeManagers() {
List<DataTypeManager> dtmList = openDTMs.values()
.stream()
.filter(dtmInfo -> !dtmInfo.isClosed())
.map(dtmInfo -> dtmInfo.dtm())
.collect(Collectors.toList());
return dtmList.toArray(DataTypeManager[]::new);
}
@Override
public synchronized void closeArchive(DataTypeManager dtm) {
if (dtm instanceof BuiltInDataTypeManager) {
Msg.info(this, "Cannot close the built-in Data Type Manager");
return;
}
if (dtm instanceof ProgramDataTypeManager) {
Msg.info(this, "Cannot close the Program's Data Type Manager");
return;
}
DataTypeManagerInfo dtmInfo = openDTMs.get(dtm.getUniversalID());
if (dtmInfo == null) {
Msg.info(this, "Unable close archive; archive not open: '%s'".formatted(dtm.getName()));
return;
}
beforeCloseDataTypeManager(dtmInfo);
openDTMs.remove(dtm.getUniversalID());
if (dtmInfo.domainObject != null) {
dtmInfo.domainObject.release(this);
}
dtmInfo.dtm.close();
afterCloseDataTypeManager(dtmInfo);
}
@Override
public Archive openArchive(DataTypeArchive dataTypeArchive) {
throw new UnsupportedOperationException();
}
@Override
public Archive openArchive(File file, boolean acquireWriteLock)
throws IOException, DuplicateIdException {
throw new UnsupportedOperationException();
}
@Override
public DataTypeManager openDataTypeArchive(String archiveName)
throws IOException, DuplicateIdException {
ResourceFile file = DataTypeArchiveUtility.findArchiveFile(archiveName);
if (file != null) {
return openArchive(file, false);
}
return null;
}
@Override
public synchronized DataTypeManager openArchive(DomainFile domainFile, TaskMonitor monitor)
throws VersionException, CancelledException, IOException, DuplicateIdException {
if (!DataTypeArchive.class.isAssignableFrom(domainFile.getDomainObjectClass())) {
throw new IOException("Unable to open domain file: '%s', not a data type archive"
.formatted(domainFile.getName()));
}
DataTypeManagerInfo dtmInfo = getOpenDTMInfo(domainFile);
if (dtmInfo == null) {
DataTypeArchive dta = openDomainFile(domainFile, monitor);
DataTypeManager dtm = dta.getDataTypeManager();
dtmInfo = addDTM(new DataTypeManagerInfo(null, dta, domainFile.getPathname(), dtm));
}
return dtmInfo.dtm;
}
protected DataTypeArchive openDomainFile(DomainFile domainFile, TaskMonitor monitor)
throws VersionException, CancelledException, IOException {
DataTypeArchive dta =
(DataTypeArchive) domainFile.getDomainObject(this, false, false, monitor);
return dta;
}
@Override
public synchronized DataTypeManager openArchive(ResourceFile file, boolean acquireWriteLock)
throws IOException, DuplicateIdException {
file = file.getCanonicalFile();
DataTypeManagerInfo dtmInfo = getOpenDTMInfo(file);
if (dtmInfo == null) {
FileDataTypeManager fileDTM =
FileDataTypeManager.openFileArchive(file, acquireWriteLock);
dtmInfo = addDTM(new DataTypeManagerInfo(file, null, file.getName(), fileDTM));
}
return dtmInfo.dtm;
}
protected DataTypeManagerInfo addDTM(DataTypeManagerInfo dtmInfo) throws DuplicateIdException {
DataTypeManagerInfo existingDTM = openDTMs.get(dtmInfo.dtm.getUniversalID());
if (existingDTM != null) {
dtmInfo.dtm.close();
throw new DuplicateIdException(dtmInfo.name(), existingDTM.name());
}
openDTMs.put(dtmInfo.dtm.getUniversalID(), dtmInfo);
afterAddDataTypeManager(dtmInfo);
return dtmInfo;
}
protected void closeDTM(DataTypeManagerInfo dtmInfo) {
beforeCloseDataTypeManager(dtmInfo);
openDTMs.remove(dtmInfo.dtm.getUniversalID());
if (dtmInfo.domainObject != null) {
dtmInfo.domainObject.release(this);
}
dtmInfo.dtm.close();
afterCloseDataTypeManager(dtmInfo);
}
private DataTypeManagerInfo getOpenDTMInfo(ResourceFile file) {
for (DataTypeManagerInfo dtmInfo : openDTMs.values()) {
if (dtmInfo.file != null && dtmInfo.file.equals(file)) {
if (dtmInfo.isClosed()) {
openDTMs.remove(dtmInfo.dtm.getUniversalID());
return null;
}
return dtmInfo;
}
}
return null;
}
private DataTypeManagerInfo getOpenDTMInfo(DomainFile projectFile) {
for (DataTypeManagerInfo dtmInfo : openDTMs.values()) {
if (dtmInfo.domainObject != null &&
dtmInfo.domainObject.getDomainFile().equals(projectFile)) {
if (dtmInfo.isClosed()) {
openDTMs.remove(dtmInfo.dtm.getUniversalID());
return null;
}
return dtmInfo;
}
}
return null;
}
protected void afterAddDataTypeManager(DataTypeManagerInfo dtmInfo) {
// override as needed
}
protected void afterCloseDataTypeManager(DataTypeManagerInfo dtmInfo) {
// override as needed
}
protected void beforeCloseDataTypeManager(DataTypeManagerInfo dtmInfo) {
// override as needed
}
}

View file

@ -0,0 +1,132 @@
/* ###
* 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.services;
import java.io.File;
import java.io.IOException;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.archive.Archive;
import ghidra.app.plugin.core.datamgr.archive.DuplicateIdException;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* A service that manages a set of data type archives, allowing re-use of already open
* archives.
*/
//@formatter:off
@ServiceInfo(
defaultProvider = DataTypeManagerPlugin.class,
description = "Service to manipulate the set of active Data Type Managers"
)
//@formatter:on
public interface DataTypeArchiveService {
/**
* Get the data type manager that has all of the built in types.
* @return data type manager for built in data types
*/
public DataTypeManager getBuiltInDataTypesManager();
/**
* Gets the open data type managers.
*
* @return the open data type managers.
*/
public DataTypeManager[] getDataTypeManagers();
/**
* Closes the archive for the given {@link DataTypeManager}. This will ignore request to
* close the open Program's manager and the built-in manager.
*
* @param dtm the data type manager of the archive to close
*/
public void closeArchive(DataTypeManager dtm);
/**
* Opens a data type archive that was built into the Ghidra installation.
* <p>
* NOTE: This is predicated upon all archive files having a unique name within the installation.
* <p>
* Any path prefix specified may prevent the file from opening (or reopening) correctly.
*
* @param archiveName archive file name (i.e., "generic_C_lib")
* @return the data type archive or null if an archive with the specified name
* can not be found.
* @throws IOException if an i/o error occurs opening the data type archive
* @throws DuplicateIdException if another archive with the same ID is already open
*/
public DataTypeManager openDataTypeArchive(String archiveName)
throws IOException, DuplicateIdException;
/**
* Opens the specified gdt (file based) data type archive.
*
* @param file gdt file
* @param acquireWriteLock true if write lock should be acquired (i.e., open for update)
* @return the data type archive
* @throws IOException if an i/o error occurs opening the data type archive
* @throws DuplicateIdException if another archive with the same ID is already open
*/
public DataTypeManager openArchive(ResourceFile file, boolean acquireWriteLock)
throws IOException, DuplicateIdException;
/**
* Opens the specified project-located data type archive.
*
* @param domainFile archive file located in the current project
* @param monitor {@link TaskMonitor} to display progess during the opening
* @return the data type archive
* @throws IOException if an i/o error occurs opening the data type archive
* @throws DuplicateIdException if another archive with the same ID is already open
* @throws VersionException
* @throws CancelledException
*/
public DataTypeManager openArchive(DomainFile domainFile, TaskMonitor monitor)
throws VersionException, CancelledException, IOException, DuplicateIdException;
/**
* A method to open an Archive for the given, pre-existing DataTypeArchive (like one that
* was opened during the import process.
*
* @param dataTypeArchive the archive from which to create an Archive
* @return an Archive based upon the given DataTypeArchive
*/
@Deprecated
public Archive openArchive(DataTypeArchive dataTypeArchive);
/**
* A method to open an Archive for the given, pre-existing archive file (*.gdt)
*
* @param file data type archive file
* @param acquireWriteLock true if write lock should be acquired (i.e., open for update)
* @return an Archive based upon the given archive files
* @throws IOException if an i/o error occurs opening the data type archive
* @throws DuplicateIdException if another archive with the same ID is already open
*/
@Deprecated
public Archive openArchive(File file, boolean acquireWriteLock)
throws IOException, DuplicateIdException;
}

View file

@ -15,19 +15,15 @@
*/
package ghidra.app.services;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import javax.swing.tree.TreePath;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.archive.Archive;
import ghidra.app.plugin.core.datamgr.archive.DuplicateIdException;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManagerChangeListener;
import ghidra.util.HelpLocation;
/**
@ -35,14 +31,13 @@ import ghidra.util.HelpLocation;
* "favorites." Favorites will show up on the popup menu for creating
* data and defining function return types and parameters.
*/
@ServiceInfo(defaultProvider = DataTypeManagerPlugin.class, description = "Service to provide list of cycle groups and data types identified as 'Favorites.'")
public interface DataTypeManagerService extends DataTypeQueryService {
/**
* Get the data type manager that has all of the built in types.
* @return data type manager for built in data types
*/
public DataTypeManager getBuiltInDataTypesManager();
//@formatter:off
@ServiceInfo(
defaultProvider = DataTypeManagerPlugin.class,
description = "Service to provide list of cycle groups and data types identified as 'Favorites.'"
)
//@formatter:on
public interface DataTypeManagerService extends DataTypeQueryService, DataTypeArchiveService {
/**
* Get the data types marked as favorites that will show up on
@ -101,48 +96,6 @@ public interface DataTypeManagerService extends DataTypeQueryService {
*/
public void edit(DataType dt);
/**
* Closes the archive for the given {@link DataTypeManager}. This will ignore request to
* close the open Program's manager and the built-in manager.
*
* @param dtm the data type manager of the archive to close
*/
public void closeArchive(DataTypeManager dtm);
/**
* Opens the specified data type archive contained within the Ghidra installation.
* NOTE: This is predicated upon all archive files having a unique name within the installation.
* Any path prefix specified may prevent the file from opening (or reopening) correctly.
* @param archiveName archive file name (i.e., "generic_C_lib")
* @return the data type archive or null if an archive with the specified name
* can not be found.
* @throws IOException if an i/o error occurs opening the data type archive
* @throws DuplicateIdException if another archive with the same ID is already open
*/
public DataTypeManager openDataTypeArchive(String archiveName)
throws IOException, DuplicateIdException;
/**
* A method to open an Archive for the given, pre-existing DataTypeArchive (like one that
* was opened during the import process.
*
* @param dataTypeArchive the archive from which to create an Archive
* @return an Archive based upon the given DataTypeArchive
*/
public Archive openArchive(DataTypeArchive dataTypeArchive);
/**
* A method to open an Archive for the given, pre-existing archive file (*.gdt)
*
* @param file data type archive file
* @param acquireWriteLock true if write lock should be acquired (i.e., open for update)
* @return an Archive based upon the given archive files
* @throws IOException if an i/o error occurs opening the data type archive
* @throws DuplicateIdException if another archive with the same ID is already open
*/
public Archive openArchive(File file, boolean acquireWriteLock)
throws IOException, DuplicateIdException;
/**
* Selects the given data type in the display of data types. A null <code>dataType</code>
* value will clear the current selection.

View file

@ -18,7 +18,6 @@ package ghidra.app.services;
import java.util.List;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
/**
* Simplified datatype service interface to provide query capabilities
@ -26,13 +25,6 @@ import ghidra.program.model.data.DataTypeManager;
*/
public interface DataTypeQueryService {
/**
* Gets the open data type managers.
*
* @return the open data type managers.
*/
public DataTypeManager[] getDataTypeManagers();
/**
* Gets the sorted list of all datatypes known by this service via it's owned DataTypeManagers.
* This method can be called frequently, as the underlying data is indexed and only updated

View file

@ -18,8 +18,6 @@ package ghidra.app.util.parser;
import java.util.*;
import java.util.regex.Pattern;
import javax.help.UnsupportedOperationException;
import org.apache.commons.lang3.StringUtils;
import ghidra.app.services.DataTypeQueryService;
@ -348,11 +346,6 @@ public class FunctionSignatureParser {
dtCache.clear();
}
@Override
public DataTypeManager[] getDataTypeManagers() {
throw new UnsupportedOperationException();
}
@Override
public List<DataType> getSortedDataTypeList() {
return service.getSortedDataTypeList();

View file

@ -15,7 +15,8 @@
*/
package help.screenshot;
import static org.junit.Assert.*;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.awt.*;
import java.awt.geom.GeneralPath;
@ -45,7 +46,6 @@ import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.table.*;
import docking.widgets.table.threaded.ThreadedTableModel;
import docking.widgets.tree.GTree;
import generic.jar.ResourceFile;
import generic.test.AbstractGenericTest;
import generic.theme.GThemeDefaults.Colors;
import generic.theme.GThemeDefaults.Colors.Java;
@ -138,9 +138,8 @@ public abstract class AbstractScreenShotGenerator extends AbstractGhidraHeadedIn
public void loadProgram() throws Exception {
loadProgram("WinHelloCPP.exe");
ResourceFile file = TestEnv.findProvidedDataTypeArchive("windows_vs12_32.gdt");
DataTypeManagerService dtm = tool.getService(DataTypeManagerService.class);
dtm.openArchive(file.getFile(false), false);
DataTypeManagerService dtms = tool.getService(DataTypeManagerService.class);
dtms.openDataTypeArchive("windows_vs12_32.gdt");
}
public void closeNonProgramArchives() {

View file

@ -22,11 +22,16 @@ import java.util.Set;
import javax.swing.tree.TreePath;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.datamgr.archive.Archive;
import ghidra.app.plugin.core.datamgr.archive.DuplicateIdException;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* A stub of the {@link DataTypeManagerService} interface. This can be used to supply a test values
@ -135,4 +140,16 @@ public class TestDoubleDataTypeManagerService implements DataTypeManagerService
public Set<String> getPossibleEquateNames(long value) {
throw new UnsupportedOperationException();
}
@Override
public DataTypeManager openArchive(ResourceFile file, boolean acquireWriteLock)
throws IOException, DuplicateIdException {
throw new UnsupportedOperationException();
}
@Override
public DataTypeManager openArchive(DomainFile domainFile, TaskMonitor monitor)
throws VersionException, CancelledException, IOException, DuplicateIdException {
throw new UnsupportedOperationException();
}
}

View file

@ -29,6 +29,7 @@ import org.apache.commons.lang3.StringUtils;
import docking.widgets.button.BrowseButton;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import ghidra.util.filechooser.GhidraFileFilter;
/**
* Bean editor to show a text field and a browse button to bring
@ -42,8 +43,17 @@ public class FileChooserEditor extends PropertyEditorSupport {
private JTextField textField = new JTextField(NUMBER_OF_COLUMNS);
private File currentDir;
private GhidraFileChooser fileChooser;
private GhidraFileFilter fileFilter;
private MouseListener otherMouseListener;
public FileChooserEditor() {
this(null);
}
public FileChooserEditor(GhidraFileFilter fileFilter) {
this.fileFilter = fileFilter;
}
@Override
public String getAsText() {
return textField.getText().trim();
@ -74,14 +84,14 @@ public class FileChooserEditor extends PropertyEditorSupport {
public void setValue(Object value) {
if (value == null) {
currentFileValue = null;
textField.setText("");
}
if (value instanceof File) {
else if (value instanceof File) {
currentFileValue = (File) value;
textField.setText(currentFileValue.getAbsolutePath());
}
else if (value instanceof String) {
setAsText((String) value);
else if (value instanceof String s) {
setAsText(s);
}
}
@ -162,6 +172,9 @@ public class FileChooserEditor extends PropertyEditorSupport {
fileChooser.setApproveButtonText("Choose Path");
fileChooser.setTitle("Choose Path");
fileChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES);
if (fileFilter != null) {
fileChooser.setFileFilter(fileFilter);
}
if (currentFileValue != null) {
fileChooser.setSelectedFile(currentFileValue);
}

View file

@ -27,6 +27,8 @@ import ghidra.program.model.lang.Language;
import ghidra.util.InvalidNameException;
import ghidra.util.UniversalID;
import ghidra.util.exception.*;
import ghidra.util.filechooser.ExtensionFileFilter;
import ghidra.util.filechooser.GhidraFileFilter;
import ghidra.util.task.TaskMonitor;
/**
@ -37,6 +39,9 @@ public class FileDataTypeManager extends StandAloneDataTypeManager
implements FileArchiveBasedDataTypeManager {
public final static String EXTENSION = "gdt"; // Ghidra Data Types
public static final GhidraFileFilter GDT_FILEFILTER =
ExtensionFileFilter.forExtensions("Ghidra Data Type Files", EXTENSION);
/**
* Suffix for an archive file.
*/