diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java index f6d85ab822..0928b75fbb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java @@ -44,6 +44,7 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer { public AbstractDemanglerAnalyzer(String name, String description) { super(name, description, AnalyzerType.BYTE_ANALYZER); setPriority(AnalysisPriority.DATA_TYPE_PROPOGATION.before().before().before()); + setSupportsOneTimeAnalysis(); } @Override @@ -58,7 +59,7 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer { DemanglerOptions options = getOptions(); if (!validateOptions(options, log)) { - log.error(getName(), "Invalid demangler options--cannot demangle"); + log.appendMsg(getName(), "Invalid demangler options--cannot demangle"); return false; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java index 0bae1cf2b0..388291400e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java @@ -849,9 +849,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl for (AutoAnalysisManagerListener listener : listeners) { listener.analysisEnded(this); } - if (log.getMsgCount() > 0) { - Msg.info(AutoAnalysisManager.class, log.toString()); - } log.clear(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java index 41be2b9839..bcb7542928 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java @@ -108,7 +108,7 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis // use this index to make sure that the following actions are ordered in the way that // they are inserted int subGroupIndex = 0; - + autoAnalyzeAction = new ActionBuilder("Auto Analyze", getName()) .menuPath("&Analysis", "&Auto Analyze...") .menuGroup(ANALYZE_GROUP_NAME, "" + subGroupIndex++) @@ -319,9 +319,16 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis @Override public void analysisEnded(AutoAnalysisManager manager) { MessageLog log = manager.getMessageLog(); - if (log.getMsgCount() > 0) { + if (log.hasMessages()) { + + log.write(AutoAnalysisManager.class, "Analysis Log Messages"); + + String shortMessage = "There were warnings/errors issued during analysis."; + String detailedMessage = + "(These messages are also written to the application log file)\n\n" + + log.toString(); MultiLineMessageDialog dialog = new MultiLineMessageDialog("Auto Analysis Summary", - "There were warnings/errors issued during analysis.", log.toString(), + shortMessage, detailedMessage, MultiLineMessageDialog.WARNING_MESSAGE, false);//modal? DockingWindowManager.showDialog(null, dialog); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java index 601aefd94c..bd4f612642 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java @@ -499,9 +499,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa resultsBuffer.append("Format: " + exporter.getName() + "\n\n"); MessageLog log = exporter.getMessageLog(); - if (log != null) { - resultsBuffer.append(log.toString()); - } + resultsBuffer.append(log.toString()); HelpLocation helpLocation = new HelpLocation(GenericHelpTopics.ABOUT, "About_Program"); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java index b2faad48ce..47f5b45baa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java @@ -145,10 +145,12 @@ public class ImportBatchTask extends Task { Object consumer = new Object(); try { MessageLog messageLog = new MessageLog(); - List importedObjects = loadSpec.getLoader().load(byteProvider, - fixupProjectFilename(destInfo.second), destInfo.first, loadSpec, - getOptionsFor(batchLoadConfig, loadSpec, byteProvider), messageLog, consumer, - monitor); + List importedObjects = loadSpec.getLoader() + .load(byteProvider, + fixupProjectFilename(destInfo.second), destInfo.first, loadSpec, + getOptionsFor(batchLoadConfig, loadSpec, byteProvider), messageLog, + consumer, + monitor); // TODO: accumulate batch results if (importedObjects != null) { @@ -163,7 +165,7 @@ public class ImportBatchTask extends Task { Msg.info(this, "Imported " + destInfo.first + "/ " + destInfo.second + ", " + totalAppsImported + " of " + totalEnabledApps); - if (messageLog.getMsgCount() > 0) { + if (messageLog.hasMessages()) { Msg.info(this, "Additional info:\n" + messageLog.toString()); } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/eclipse/AndroidProjectCreator.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/eclipse/AndroidProjectCreator.java index eda58360a2..0deda2cec5 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/eclipse/AndroidProjectCreator.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/eclipse/AndroidProjectCreator.java @@ -74,8 +74,9 @@ public class AndroidProjectCreator { public void create(TaskMonitor monitor) throws IOException, CancelledException { createEclipseProjectDirectories(); - try (ZipFileSystem fs = FileSystemService.getInstance().mountSpecificFileSystem( - apkFile.getFSRL(), ZipFileSystem.class, monitor)) { + try (ZipFileSystem fs = FileSystemService.getInstance() + .mountSpecificFileSystem( + apkFile.getFSRL(), ZipFileSystem.class, monitor)) { List listing = fs.getListing(null); processListing(eclipseProjectDirectory, listing, monitor); } @@ -163,8 +164,9 @@ public class AndroidProjectCreator { private void processDex(File outputDirectory, GFile dexFile, TaskMonitor monitor) throws IOException, CancelledException { - try (DexToJarFileSystem fs = FileSystemService.getInstance().mountSpecificFileSystem( - dexFile.getFSRL(), DexToJarFileSystem.class, monitor)) { + try (DexToJarFileSystem fs = FileSystemService.getInstance() + .mountSpecificFileSystem( + dexFile.getFSRL(), DexToJarFileSystem.class, monitor)) { GFile jarFile = fs.getJarFile(); processJar(srcDirectory, jarFile.getFSRL(), monitor); } @@ -176,7 +178,7 @@ public class AndroidProjectCreator { JarDecompiler decompiler = new JarDecompiler(jarFile, outputDirectory); decompiler.decompile(monitor); - if (decompiler.getLog().getMsgCount() > 0) { + if (decompiler.getLog().hasMessages()) { log.copyFrom(decompiler.getLog()); } } @@ -199,8 +201,9 @@ public class AndroidProjectCreator { private void processXML(File outputDirectory, GFile containerFile, TaskMonitor monitor) throws CancelledException { - try (AndroidXmlFileSystem fs = FileSystemService.getInstance().mountSpecificFileSystem( - containerFile.getFSRL(), AndroidXmlFileSystem.class, monitor)) { + try (AndroidXmlFileSystem fs = FileSystemService.getInstance() + .mountSpecificFileSystem( + containerFile.getFSRL(), AndroidXmlFileSystem.class, monitor)) { GFile xmlFile = fs.getPayloadFile(); copyStream(fs.getInputStream(xmlFile, monitor), outputDirectory, containerFile.getName(), monitor); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/plugins/fileformats/FileFormatsPlugin.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/plugins/fileformats/FileFormatsPlugin.java index 5c1e29e0ed..0297ce60f8 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/plugins/fileformats/FileFormatsPlugin.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/plugins/fileformats/FileFormatsPlugin.java @@ -128,7 +128,7 @@ public class FileFormatsPlugin extends Plugin implements FrontEndable { new AndroidProjectCreator(refdFile.file, outputDirectory); creator.create(monitor); - if (creator.getLog().getMsgCount() > 0) { + if (creator.getLog().hasMessages()) { Msg.showInfo(this, getTool().getActiveWindow(), "Export to Eclipse Project", creator.getLog().toString()); } @@ -194,7 +194,7 @@ public class FileFormatsPlugin extends Plugin implements FrontEndable { new JarDecompiler(jarFSRL, outputDirectory); decompiler.decompile(monitor); - if (decompiler.getLog().getMsgCount() > 0) { + if (decompiler.getLog().hasMessages()) { Msg.showInfo(this, gTree, "Decompiling Jar " + jarFSRL.getName(), decompiler.getLog().toString()); diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java index 3ce48fcf15..27993bd2fb 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java @@ -133,7 +133,7 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer { return true; } catch (IOException e) { - log.error(getName(), "Invalid options for GNU dangler '" + demanglerName + + log.appendMsg(getName(), "Invalid options for GNU dangler '" + demanglerName + "': " + applicationArguments); log.appendException(e); } @@ -148,7 +148,7 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer { return true; } catch (IOException e) { - log.error(getName(), + log.appendMsg(getName(), "Invalid options for GNU dangler '" + deprecatedName + "': " + applicationArguments); log.appendException(e); diff --git a/Ghidra/Features/PDB/src/main/java/pdb/LoadPdbTask.java b/Ghidra/Features/PDB/src/main/java/pdb/LoadPdbTask.java index a27430be46..21ed709833 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/LoadPdbTask.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/LoadPdbTask.java @@ -108,7 +108,7 @@ class LoadPdbTask extends Task { Msg.showError(getClass(), null, "Load PDB Failed", message, t); } - if (log.getMsgCount() > 0) { + if (log.hasMessages()) { MultiLineMessageDialog dialog = new MultiLineMessageDialog("Load PDB File", "There were warnings/errors loading the PDB file.", log.toString(), MultiLineMessageDialog.WARNING_MESSAGE, false); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/MultiLineMessageDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/MultiLineMessageDialog.java index a03d288805..248e69a6f3 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/MultiLineMessageDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/MultiLineMessageDialog.java @@ -104,34 +104,41 @@ public class MultiLineMessageDialog extends DialogComponentProvider { // In this case, we are also inserting a that specifies the font-family // to get us back to the same font the rest of the GUI is using. - JTextPane textpane = new JTextPane(); - String fontfamily = textpane.getFont().getFamily(); + JTextPane textPane = new JTextPane(); + String fontfamily = textPane.getFont().getFamily(); detailedMessage = "" + detailedMessage.substring(6); // Set the textpane to not auto-scroll to bottom when adding text - DefaultCaret caret = (DefaultCaret) textpane.getCaret(); + DefaultCaret caret = (DefaultCaret) textPane.getCaret(); caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); - textpane.setContentType("text/html"); - textpane.setText(detailedMessage); - textpane.setEditable(false); + textPane.setContentType("text/html"); + textPane.setText(detailedMessage); + textPane.setEditable(false); - DockingUtils.setTransparent(textpane); - JScrollPane scrollPane = new JScrollPane(textpane); + DockingUtils.setTransparent(textPane); + JScrollPane scrollPane = new JScrollPane(textPane); DockingUtils.setTransparent(scrollPane); scrollPane.setBorder(BorderFactory.createEmptyBorder()); workPanel.add(scrollPane, BorderLayout.CENTER); + + // note: this must be done after adding the text component to the scroll pane + // (seems like the scroll pane is changing the border) + textPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); } else { - JTextArea textarea = new JTextArea(detailedMessage); - textarea.setEditable(false); + JTextArea textArea = new JTextArea(detailedMessage); + textArea.setEditable(false); - DockingUtils.setTransparent(textarea); - JScrollPane scrollPane = new JScrollPane(textarea); + DockingUtils.setTransparent(textArea); + JScrollPane scrollPane = new JScrollPane(textArea); DockingUtils.setTransparent(scrollPane); - scrollPane.setBorder(BorderFactory.createEmptyBorder()); workPanel.add(scrollPane, BorderLayout.CENTER); + + // note: this must be done after adding the text component to the scroll pane + // (seems like the scroll pane is changing the border) + textArea.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); } Icon icon = OptionDialog.getIconForMessageType(messageType); @@ -147,10 +154,14 @@ public class MultiLineMessageDialog extends DialogComponentProvider { setFocusComponent(okButton); setDefaultButton(okButton); setRememberSize(false); + + // A somewhat arbitrary number to prevent the dialog from stretching across the screen + setPreferredSize(600, 300); } @Override protected void okCallback() { close(); } + } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/app/util/importer/MessageLog.java b/Ghidra/Framework/Generic/src/main/java/ghidra/app/util/importer/MessageLog.java index 41199b6b5e..fa9717a3b7 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/app/util/importer/MessageLog.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/app/util/importer/MessageLog.java @@ -15,51 +15,41 @@ */ package ghidra.app.util.importer; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + import ghidra.util.Msg; -import ghidra.util.exception.AssertException; +import utilities.util.reflection.ReflectionUtilities; /** * A simple class to handle logging messages and exceptions. A maximum message count size * constraint can be set to clip messages after a certain number, but still keep incrementing * a running total. + * + *

In addition to logging messages, clients can also set a status message. This message may + * later used as the primary error message when reporting to the user. */ public class MessageLog { /** * The default number of messages to store before clipping */ - public final static int MAX_COUNT = 500; + private final static int MAX_COUNT = 500; - private StringBuffer buffer = new StringBuffer(); - private int maxSize; + private List messages = new ArrayList<>(); + private int maxSize = MAX_COUNT; private int count; - private int pos = -1; - private String statusMsg; - - /** - * Constructs a new message log using the default message count - */ - public MessageLog() { - this(MAX_COUNT); - } - - /** - * Constructs a new message log using the specified message count - * @param maxSize the maximum number of messages - */ - public MessageLog(int maxSize) { - this.maxSize = maxSize; - clearStatus(); - } + private String statusMsg = StringUtils.EMPTY; /** * Copies the contents of one message log into this one * @param log the log to copy from */ public void copyFrom(MessageLog log) { - this.buffer = new StringBuffer(log.buffer); - this.maxSize = log.maxSize; - this.count = log.count; - this.pos = log.pos; + for (String otherMessage : log.messages) { + add(otherMessage); + } } /** @@ -67,7 +57,7 @@ public class MessageLog { * @param message the message */ public void appendMsg(String message) { - msg(message); + add(message); } /** @@ -78,10 +68,10 @@ public class MessageLog { */ public void appendMsg(String originator, String message) { if (originator == null) { - msg(message); + add(message); } else { - msg(originator + "> " + message); + add(originator + "> " + message); } } @@ -91,7 +81,7 @@ public class MessageLog { * @param message the message */ public void appendMsg(int lineNum, String message) { - msg("Line #" + lineNum + " - " + message); + add("Line #" + lineNum + " - " + message); } /** @@ -99,31 +89,39 @@ public class MessageLog { * @param t the exception to append to the log */ public void appendException(Throwable t) { - if (t instanceof NullPointerException || t instanceof AssertException) { - Msg.error(this, "Exception appended to MessageLog", t); - } - else { - Msg.debug(this, "Exception appended to MessageLog", t); - } - String msg = t.toString(); - msg(msg); + String asString = ReflectionUtilities.stackTraceToString(t); + add(asString); } /** - * Returns the message count - * @return the message count + * Readable method for appending error messages to the log. + * + *

Currently does nothing different than {@link #appendMsg(String, String)}. + * + * + * @param originator the originator of the message + * @param message the message + * @deprecated use {@link #appendMsg(String)} */ - public int getMsgCount() { - return count; + @Deprecated + public void error(String originator, String message) { + appendMsg(originator, message); + } + + /** + * Returns true if this log has messages + * @return true if this log has messages + */ + public boolean hasMessages() { + return count > 0; } /** * Clears all messages from this log and resets the count */ public void clear() { - buffer = new StringBuffer(); + messages = new ArrayList<>(); count = 0; - pos = -1; } /** @@ -138,7 +136,7 @@ public class MessageLog { * Clear status message */ public void clearStatus() { - statusMsg = ""; + statusMsg = StringUtils.EMPTY; } /** @@ -151,39 +149,41 @@ public class MessageLog { @Override public String toString() { - if (count > maxSize) { - if (pos > -1) { - buffer.delete(pos, buffer.length()); - } - pos = buffer.length(); - buffer.append("\n \n"); - buffer.append("There were too many messages to display.\n"); - buffer.append("" + (count - maxSize) + " messages have been truncated."); - buffer.append("\n \n"); - } - return buffer.toString(); - } - - private void msg(String msg) { - if (msg == null || msg.length() == 0) {//discard if null... - return; - } - if (count++ < maxSize) { - buffer.append(msg); - buffer.append("\n"); - } + return toStringWithWarning(); } /** - * Readable method for appending error messages to the log. - * - *

Currently does nothing different than {@link #appendMsg(String, String)}. - * - * - * @param originator the originator of the message - * @param message the message + * Writes this log's contents to the application log + * @param owner the owning class whose name will appear in the log message + * @param messageHeader the message header that will appear before the log messages */ - public void error(String originator, String message) { - appendMsg(originator, message); + public void write(Class owner, String messageHeader) { + String header = StringUtils.defaultIfBlank(messageHeader, "Log Messages"); + Msg.info(owner, header + '\n' + toStringWithWarning()); + } + + private String toStringWithWarning() { + StringBuilder output = new StringBuilder(); + if (count > maxSize) { + output.append('\n').append('\n'); + output.append("There were too many messages to display.\n"); + output.append((count - maxSize)).append(" messages have been truncated."); + output.append('\n').append('\n'); + } + + for (String s : messages) { + output.append(s).append('\n'); + } + return output.toString(); + } + + private void add(String msg) { + if (StringUtils.isBlank(msg)) { + return; + } + + if (count++ < maxSize) { + messages.add(msg); + } } }