mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-10 14:11:59 +00:00
Merge remote-tracking branch 'origin/GP-126_ghidra1_StoreCumulativeAnalaysisTimes'
This commit is contained in:
commit
8af6deefb5
@ -802,6 +802,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
|
||||
notifyAnalysisEnded();
|
||||
if (printTaskTimes) {
|
||||
printTimedTasks();
|
||||
saveTaskTimes();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1289,6 +1290,19 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
|
||||
Msg.info(this, taskTimeString);
|
||||
}
|
||||
|
||||
private void saveTaskTimes() {
|
||||
|
||||
StoredAnalyzerTimes times = StoredAnalyzerTimes.getStoredAnalyzerTimes(program);
|
||||
|
||||
String taskNames[] = getTimedTasks();
|
||||
for (String element : taskNames) {
|
||||
long taskTimeMSec = getTaskTime(timedTasks, element);
|
||||
times.addTime(element, taskTimeMSec);
|
||||
}
|
||||
|
||||
StoredAnalyzerTimes.setStoredAnalyzerTimes(program, times);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an analysis worker to run while auto analysis is suspended. Invocation will block
|
||||
* until callback is completed or cancelled. If an analysis task is busy, it will be allowed to
|
||||
|
@ -32,6 +32,7 @@ import ghidra.app.events.*;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.services.Analyzer;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.framework.options.OptionType;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
@ -275,6 +276,11 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis
|
||||
}
|
||||
|
||||
private void programActivated(final Program program) {
|
||||
|
||||
program.getOptions(StoredAnalyzerTimes.OPTIONS_LIST)
|
||||
.registerOption(StoredAnalyzerTimes.OPTION_NAME, OptionType.CUSTOM_TYPE,
|
||||
null, null, "Cumulative analysis task times",
|
||||
new StoredAnalyzerTimesPropertyEditor());
|
||||
|
||||
// invokeLater() to ensure that all other plugins have been notified of the program
|
||||
// activated. This makes sure plugins like the Listing have opened and painted the
|
||||
|
@ -0,0 +1,170 @@
|
||||
/* ###
|
||||
* 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.util.*;
|
||||
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* <code>StoredAnalyzerTimes</code> provides a custom option container for
|
||||
* accumulated analysis times for named tasks.
|
||||
*/
|
||||
public class StoredAnalyzerTimes implements CustomOption {
|
||||
|
||||
public static final String OPTIONS_LIST = Program.PROGRAM_INFO + ".Analysis Times";
|
||||
public static final String OPTION_NAME = "Times";
|
||||
|
||||
private Map<String, Long> taskTimes = new HashMap<>();
|
||||
private Long totalTime;
|
||||
private String[] names;
|
||||
|
||||
@Override
|
||||
public void readState(SaveState saveState) {
|
||||
taskTimes.clear();
|
||||
for (String taskName : saveState.getNames()) {
|
||||
taskTimes.put(taskName, saveState.getLong(taskName, 0));
|
||||
}
|
||||
names = null;
|
||||
totalTime = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeState(SaveState saveState) {
|
||||
for (String taskName : taskTimes.keySet()) {
|
||||
saveState.putLong(taskName, taskTimes.get(taskName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all task entries and times
|
||||
*/
|
||||
public void clear() {
|
||||
taskTimes.clear();
|
||||
names = null;
|
||||
totalTime = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any task times exist
|
||||
* @return true if no task times available, else false
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return taskTimes.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear time entry corresponding to specified taskName
|
||||
* @param taskName analysis task name
|
||||
*/
|
||||
public void clear(String taskName) {
|
||||
taskTimes.remove(taskName);
|
||||
names = null;
|
||||
totalTime = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified time corresponding to the specified analysis taskName
|
||||
* @param taskName analysis task name
|
||||
* @param t time increment
|
||||
*/
|
||||
public void addTime(String taskName, long t) {
|
||||
long cumulativeTime = taskTimes.getOrDefault(taskName, 0L) + t;
|
||||
taskTimes.put(taskName, cumulativeTime);
|
||||
names = null;
|
||||
totalTime = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the accumulated time for the specified analysis taskName
|
||||
* @param taskName analysis task name
|
||||
* @return accumulated task time or null if entry not found
|
||||
*/
|
||||
public Long getTime(String taskName) {
|
||||
return taskTimes.get(taskName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total accumulated task time for all task entries
|
||||
* @return total accumuated task time
|
||||
*/
|
||||
public long getTotalTime() {
|
||||
if (totalTime == null) {
|
||||
long sum = 0;
|
||||
for (long t : taskTimes.values()) {
|
||||
sum += t;
|
||||
}
|
||||
totalTime = sum;
|
||||
}
|
||||
return totalTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all task names for which time entries exist
|
||||
* @return array of task names
|
||||
*/
|
||||
public String[] getTaskNames() {
|
||||
if (names == null) {
|
||||
names = taskTimes.keySet().toArray(new String[taskTimes.size()]);
|
||||
Arrays.sort(names);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return taskTimes.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof StoredAnalyzerTimes)) {
|
||||
return false;
|
||||
}
|
||||
return taskTimes.equals(((StoredAnalyzerTimes) obj).taskTimes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredAnalyzerTimes clone() {
|
||||
StoredAnalyzerTimes newInstance = new StoredAnalyzerTimes();
|
||||
newInstance.taskTimes = new HashMap<>(taskTimes);
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the StoredAnalyzerTimes options data from the specified program
|
||||
* @param program program
|
||||
* @return StoredAnalyzerTimes option data
|
||||
*/
|
||||
public static StoredAnalyzerTimes getStoredAnalyzerTimes(Program program) {
|
||||
Options options = program.getOptions(OPTIONS_LIST);
|
||||
StoredAnalyzerTimes times = (StoredAnalyzerTimes) options
|
||||
.getCustomOption(StoredAnalyzerTimes.OPTION_NAME, new StoredAnalyzerTimes());
|
||||
return times;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the updated StoredAnalyzerTimes option data on the specified program
|
||||
* @param program program
|
||||
* @param times StoredAnalyzerTimes option data
|
||||
*/
|
||||
public static void setStoredAnalyzerTimes(Program program, StoredAnalyzerTimes times) {
|
||||
Options options = program.getOptions(OPTIONS_LIST);
|
||||
options.putObject(StoredAnalyzerTimes.OPTION_NAME, times);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/* ###
|
||||
* 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.*;
|
||||
import java.beans.PropertyEditorSupport;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.widgets.label.GDLabel;
|
||||
import ghidra.framework.options.CustomOptionsEditor;
|
||||
import ghidra.util.StringUtilities;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
|
||||
/**
|
||||
* <code>StoredAnalyzerTimesPropertyEditor</code> implements a custom option
|
||||
* editor panel for {@link StoredAnalyzerTimes}. Ability to edit values
|
||||
* is disabled with panel intended for display purpose only.
|
||||
*/
|
||||
public class StoredAnalyzerTimesPropertyEditor extends PropertyEditorSupport
|
||||
implements CustomOptionsEditor {
|
||||
|
||||
private StoredAnalyzerTimes times;
|
||||
|
||||
@Override
|
||||
public boolean supportsCustomEditor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOptionNames() {
|
||||
if (times == null) {
|
||||
return new String[0];
|
||||
}
|
||||
return times.getTaskNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOptionDescriptions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Object value) {
|
||||
if (!(value instanceof StoredAnalyzerTimes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
times = (StoredAnalyzerTimes) value;
|
||||
firePropertyChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return times.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getCustomEditor() {
|
||||
return buildEditor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build analysis time panel showing all task names and corresponding
|
||||
* cumulative times in seconds. Edit ability is disabled.
|
||||
* @return options panel
|
||||
*/
|
||||
private Component buildEditor() {
|
||||
|
||||
if (times == null || times.isEmpty()) {
|
||||
JPanel panel = new JPanel(new FlowLayout());
|
||||
panel.add(new JLabel("No Data Available"));
|
||||
return panel;
|
||||
}
|
||||
|
||||
JPanel panel = new JPanel(new PairLayout(6, 10));
|
||||
|
||||
panel.add(new GDLabel(""));
|
||||
GDLabel label = new GDLabel("seconds", SwingConstants.RIGHT);
|
||||
panel.add(label);
|
||||
|
||||
for (String taskName : getOptionNames()) {
|
||||
label = new GDLabel(taskName, SwingConstants.RIGHT);
|
||||
label.setToolTipText(taskName);
|
||||
panel.add(label);
|
||||
|
||||
Long timeMS = times.getTime(taskName);
|
||||
if (timeMS == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JTextField valueField = new JTextField(formatTimeMS(timeMS));
|
||||
valueField.setEditable(false);
|
||||
valueField.setHorizontalAlignment(SwingConstants.RIGHT);
|
||||
panel.add(valueField);
|
||||
}
|
||||
|
||||
label = new GDLabel("TOTAL", SwingConstants.RIGHT);
|
||||
label.setFont(label.getFont().deriveFont(Font.BOLD));
|
||||
panel.add(label);
|
||||
|
||||
JTextField valueField = new JTextField(formatTimeMS(times.getTotalTime()));
|
||||
valueField.setEditable(false);
|
||||
valueField.setHorizontalAlignment(SwingConstants.RIGHT);
|
||||
valueField.setBorder(BorderFactory.createLineBorder(Color.black, 2));
|
||||
panel.add(valueField);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private String formatTimeMS(long timeMS) {
|
||||
String str = Long.toUnsignedString(timeMS / 1000L);
|
||||
str += ".";
|
||||
str += StringUtilities.pad(Long.toUnsignedString(timeMS % 1000L), '0', 3);
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
@ -29,6 +29,7 @@ import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
|
||||
public abstract class AbstractOptions implements Options {
|
||||
@ -143,64 +144,51 @@ public abstract class AbstractOptions implements Options {
|
||||
ReflectionUtilities.createJavaFilteredThrowable());
|
||||
}
|
||||
|
||||
Option currentOption = valueMap.get(optionName);
|
||||
if (currentOption == null) {
|
||||
Option option =
|
||||
createRegisteredOption(optionName, type, description, help, defaultValue, editor);
|
||||
valueMap.put(optionName, option);
|
||||
Option currentOption = getExistingComptibleOption(optionName, type, defaultValue);
|
||||
if (currentOption != null) {
|
||||
currentOption.updateRegistration(description, help, defaultValue, editor);
|
||||
return;
|
||||
}
|
||||
|
||||
Option newOption = null;
|
||||
if (currentOption.isRegistered()) {
|
||||
// Registered again
|
||||
newOption =
|
||||
copyRegisteredOption(currentOption, type, description, defaultValue, help, editor);
|
||||
}
|
||||
else {
|
||||
// option was accessed, but not registered
|
||||
newOption =
|
||||
createRegisteredOption(optionName, type, description, help, defaultValue, editor);
|
||||
}
|
||||
Option option =
|
||||
createRegisteredOption(optionName, type, description, help, defaultValue, editor);
|
||||
|
||||
copyCurrentValue(currentOption, newOption);
|
||||
valueMap.put(optionName, newOption);
|
||||
valueMap.put(optionName, option);
|
||||
}
|
||||
|
||||
protected void copyCurrentValue(Option currentOption, Option newOption) {
|
||||
if (currentOption.isDefault()) {
|
||||
return; // don't copy the current value if it is just the old default.
|
||||
private Option getExistingComptibleOption(String optionName, OptionType type,
|
||||
Object defaultValue) {
|
||||
|
||||
// There are several cases where an existing option may exist when registering an option
|
||||
// 1) the option was accessed before it was registered
|
||||
// 2) the option was loaded from a store (database or toolstate)
|
||||
// 3) the option was registered more than once.
|
||||
//
|
||||
// The only time this is a problem is if the exiting option type is not compatible with
|
||||
// the type being registered. If we encounter an incompatible option, we just log a
|
||||
// warning and return null so that the new option will replace it. Otherwise, we return
|
||||
// the existing option so it can be updated with the data from the registration.
|
||||
|
||||
Option option = valueMap.get(optionName);
|
||||
if (option == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object currentValue = currentOption.getCurrentValue();
|
||||
OptionType type = currentOption.getOptionType();
|
||||
if (!isNullable(type) && currentValue == null) {
|
||||
return; // not allowed to be null
|
||||
if (!isCompatibleOption(option, type, defaultValue)) {
|
||||
Msg.error(this, "Registered option incompatible with existing option: " + optionName,
|
||||
new AssertException());
|
||||
return null;
|
||||
}
|
||||
|
||||
// null is allowed; null can represent a valid 'cleared' state
|
||||
newOption.setCurrentValue(currentValue);
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
private Option copyRegisteredOption(Option currentOption, OptionType type,
|
||||
String description, Object defaultValue, HelpLocation help, PropertyEditor editor) {
|
||||
|
||||
// We probably don't need to do anything special if we are re-registering an option,
|
||||
// which is what the below code handles
|
||||
String oldDescription = currentOption.getDescription();
|
||||
HelpLocation oldHelp = currentOption.getHelpLocation();
|
||||
Object oldDefaultValue = currentOption.getDefaultValue();
|
||||
PropertyEditor oldEditor = currentOption.getPropertyEditor();
|
||||
|
||||
String newDescripiton = oldDescription == null ? description : oldDescription;
|
||||
HelpLocation newHelpLocation = oldHelp == null ? help : oldHelp;
|
||||
Object newDefaultValue = oldDefaultValue == null ? defaultValue : oldDefaultValue;
|
||||
PropertyEditor newEditor = oldEditor == null ? editor : oldEditor;
|
||||
|
||||
String optionName = currentOption.getName();
|
||||
return createRegisteredOption(optionName, type, newDescripiton, newHelpLocation,
|
||||
newDefaultValue, newEditor);
|
||||
private boolean isCompatibleOption(Option option, OptionType type, Object defaultValue) {
|
||||
if (option.getOptionType() != type) {
|
||||
return false;
|
||||
}
|
||||
Object optionValue = option.getValue(null);
|
||||
return optionValue == null || defaultValue == null ||
|
||||
optionValue.getClass().equals(defaultValue.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,6 +20,7 @@ import java.beans.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
public class EditorState implements PropertyChangeListener {
|
||||
@ -119,10 +120,16 @@ public class EditorState implements PropertyChangeListener {
|
||||
* directly, as opposed to using the generic framework.
|
||||
*/
|
||||
public boolean supportsCustomOptionsEditor() {
|
||||
return (editor instanceof CustomOptionsEditor);
|
||||
return editor == null || (editor instanceof CustomOptionsEditor);
|
||||
}
|
||||
|
||||
public Component getEditorComponent() {
|
||||
if (editor == null) {
|
||||
// can occur if support has been dropped for custom state/option
|
||||
editor = new ErrorPropertyEditor(
|
||||
"Ghidra does not know how to render state: " + name, null);
|
||||
return editor.getCustomEditor();
|
||||
}
|
||||
if (editor.supportsCustomEditor()) {
|
||||
return editor.getCustomEditor();
|
||||
}
|
||||
@ -146,7 +153,9 @@ public class EditorState implements PropertyChangeListener {
|
||||
|
||||
editor.removePropertyChangeListener(this);
|
||||
editor = new ErrorPropertyEditor(
|
||||
"Ghidra does not know how to use PropertyEditor: " + editor.getClass().getName(), null);
|
||||
Application.getName() + " does not know how to use PropertyEditor: " +
|
||||
editor.getClass().getName(),
|
||||
null);
|
||||
return editor.getCustomEditor();
|
||||
}
|
||||
|
||||
|
@ -24,13 +24,13 @@ import utilities.util.reflection.ReflectionUtilities;
|
||||
|
||||
public abstract class Option {
|
||||
private final String name;
|
||||
private final Object defaultValue;
|
||||
private Object defaultValue;
|
||||
private boolean isRegistered;
|
||||
private final String description;
|
||||
private final HelpLocation helpLocation;
|
||||
private final OptionType optionType;
|
||||
private String description;
|
||||
private HelpLocation helpLocation;
|
||||
private OptionType optionType;
|
||||
|
||||
private final PropertyEditor propertyEditor;
|
||||
private PropertyEditor propertyEditor;
|
||||
private String inceptionInformation;
|
||||
|
||||
protected Option(String name, OptionType optionType, String description,
|
||||
@ -48,6 +48,25 @@ public abstract class Option {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update any null registration information using the specified updated information.
|
||||
* This assumption is that multiple registrations for the same option may be partial
|
||||
* but will be compatible. No compatibility checks are performed between existing
|
||||
* registration and update registration info.
|
||||
* @param updatedDescription updated option description (will be ignored if description previously set)
|
||||
* @param updatedHelp updated option help location (will be ignored if help location previously set)
|
||||
* @param updatedDefaultValue updated option default-value (will be ignored if default-value previously set)
|
||||
* @param updatedEditor updated option editor (will be ignored if editor previously set)
|
||||
*/
|
||||
final void updateRegistration(String updatedDescription, HelpLocation updatedHelp,
|
||||
Object updatedDefaultValue, PropertyEditor updatedEditor) {
|
||||
description = description != null ? description : updatedDescription;
|
||||
helpLocation = helpLocation != null ? helpLocation : updatedHelp;
|
||||
defaultValue = defaultValue != null ? defaultValue : updatedDefaultValue;
|
||||
propertyEditor = propertyEditor != null ? propertyEditor : updatedEditor;
|
||||
isRegistered = true;
|
||||
}
|
||||
|
||||
public abstract Object getCurrentValue();
|
||||
|
||||
public abstract void doSetCurrentValue(Object value);
|
||||
|
@ -223,8 +223,13 @@ public enum OptionType {
|
||||
option.readState(saveState);
|
||||
return option;
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
Msg.warn(this,
|
||||
"Ignoring unsupported customOption instance for: " + customOptionClassName);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Can't create customOption instance for: " + customOptionClassName,
|
||||
Msg.error(this,
|
||||
"Can't create customOption instance for: " + customOptionClassName +
|
||||
e);
|
||||
}
|
||||
return null;
|
||||
|
Loading…
Reference in New Issue
Block a user