GT-3481 - Gnu Demangler - Checkpoint 3 - Ready for review

This commit is contained in:
dragonmacher 2020-02-13 11:23:09 -05:00
parent b4ce3012d7
commit a21a644a68
4 changed files with 279 additions and 82 deletions

View file

@ -212,6 +212,7 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer {
protected void apply(Program program, Address address, DemangledObject demangled, protected void apply(Program program, Address address, DemangledObject demangled,
DemanglerOptions options, MessageLog log, TaskMonitor monitor) { DemanglerOptions options, MessageLog log, TaskMonitor monitor) {
String errorMessage = null;
try { try {
if (demangled.applyTo(program, address, options, monitor)) { if (demangled.applyTo(program, address, options, monitor)) {
return; return;
@ -219,16 +220,20 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer {
} }
catch (Exception e) { catch (Exception e) {
String message = e.getMessage(); String message = e.getMessage();
message = message == null ? "" : ". Message: " + message; if (message == null) {
log.appendMsg(getName(), message = "";
"Unable to demangle symbol at " + address + "; name: " + }
demangled.getMangledName() + message); errorMessage = "\n" + e.getClass().getSimpleName() + ' ' + message;
}
String failMessage = " (" + getName() + "/" + demangled.getClass().getName() + ")";
if (errorMessage != null) {
failMessage += errorMessage;
} }
log.appendMsg(getName(), log.appendMsg(getName(),
"Failed to apply mangled symbol at " + address + "; name: " + "Failed to apply mangled symbol at " + address + "; name: " +
demangled.getMangledName() + " (" + demangled.getMangledName() + failMessage);
getName() + "/" + demangled.getClass().getName() + ")");
} }
protected String cleanSymbol(Address address, String name) { protected String cleanSymbol(Address address, String name) {

View file

@ -51,7 +51,7 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer {
"Signals to use the deprecated demangler when the modern demangler cannot demangle a " + "Signals to use the deprecated demangler when the modern demangler cannot demangle a " +
"given string"; "given string";
private static final String OPTION_NAME_DEMANGLER_PARAMETERS = static final String OPTION_NAME_DEMANGLER_PARAMETERS =
"Use External Demangler Options"; "Use External Demangler Options";
private static final String OPTION_DESCRIPTION_DEMANGLER_PARAMETERS = private static final String OPTION_DESCRIPTION_DEMANGLER_PARAMETERS =
"Signals to use pass the given parameters to the demangler program"; "Signals to use pass the given parameters to the demangler program";

View file

@ -17,27 +17,38 @@ package ghidra.app.plugin.core.analysis;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import ghidra.app.cmd.label.AddLabelCmd; import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.util.demangler.gnu.GnuDemanglerOptions;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.*;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.test.ToyProgramBuilder; import ghidra.test.ToyProgramBuilder;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.RollbackException; import ghidra.util.StringUtilities;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class GnuDemanglerAnalyzerTest extends AbstractGhidraHeadlessIntegrationTest { public class GnuDemanglerAnalyzerTest extends AbstractGhidraHeadlessIntegrationTest {
private GnuDemanglerAnalyzer analyzer = new GnuDemanglerAnalyzer();
private ProgramDB program; private ProgramDB program;
private GnuDemanglerAnalyzer analyzer = new GnuDemanglerAnalyzer();
// overridden to prevent stack traces from appearing in the console
private MessageLog log = new MessageLog() {
@Override
public void appendException(Throwable t) {
appendMsg(t.toString());
}
};
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@ -48,8 +59,34 @@ public class GnuDemanglerAnalyzerTest extends AbstractGhidraHeadlessIntegrationT
registerOptions(); registerOptions();
} }
@Override
protected void testFailed(Throwable e) {
Msg.error(this, "Test failed - analysis log:\n" + log);
}
@Test @Test
public void testDeprectedDemangledString() throws Exception { public void testDeprectedMangledString_WithoutDeprecatedDemangler() throws Exception {
//
// The below demangles to MsoDAL::VertFrame::__dt( (void))
// note the (void) syntax
//
// from program Microsoft Entourage
//
String mangled = "__dt__Q26MsoDAL9VertFrameFv";
Address addr = addr("0x110");
createSymbol(addr, mangled);
setOption(GnuDemanglerAnalyzer.OPTION_NAME_USE_DEPRECATED_DEMANGLER, false);
analyze();
assertNotDemangled(addr, "__dt");
}
@Test
public void testDeprectedMangledString_WithDeprecatedDemangler() throws Exception {
// //
// The below demangles to MsoDAL::VertFrame::__dt( (void)) // The below demangles to MsoDAL::VertFrame::__dt( (void))
@ -64,57 +101,214 @@ public class GnuDemanglerAnalyzerTest extends AbstractGhidraHeadlessIntegrationT
setOption(GnuDemanglerAnalyzer.OPTION_NAME_USE_DEPRECATED_DEMANGLER, true); setOption(GnuDemanglerAnalyzer.OPTION_NAME_USE_DEPRECATED_DEMANGLER, true);
MessageLog log = new MessageLog(); analyze();
analyzer.added(program, program.getMemory(), TaskMonitor.DUMMY, log);
assertDemangled(addr, "__dt");
} }
private void setOption(String optionNameUseDeprecatedDemangler, boolean b) { @Test
public void testMangledString_WithArguments_Valid() {
//
// The below demangles to std::io::Read::read_to_end
//
String mangled = "_ZN3std2io4Read11read_to_end17hb85a0f6802e14499E";
Address addr = addr("0x110");
createSymbol(addr, mangled);
setOption(GnuDemanglerAnalyzer.OPTION_NAME_DEMANGLER_PARAMETERS, "-s rust");
analyze();
assertDemangled(addr, "read_to_end");
}
@Test
public void testMangledString_WithArguments_ValidButWrongFormat() {
//
// The below demangles to std::io::Read::read_to_end
//
String mangled = "_ZN3std2io4Read11read_to_end17hb85a0f6802e14499E";
Address addr = addr("0x110");
createSymbol(addr, mangled);
setOption(GnuDemanglerAnalyzer.OPTION_NAME_DEMANGLER_PARAMETERS, "-s dlang");
analyze();
assertNotDemangled(addr, "read_to_end");
}
@Test
public void testMangledString_WithArguments_Invalid() {
//
// The below demangles to std::io::Read::read_to_end
//
String mangled = "_ZN3std2io4Read11read_to_end17hb85a0f6802e14499E";
Address addr = addr("0x110");
createSymbol(addr, mangled);
setOption(GnuDemanglerAnalyzer.OPTION_NAME_DEMANGLER_PARAMETERS, "-s badformatname");
analyze();
assertNotDemangled(addr, "read_to_end");
assertMessageLogLine("java.io.IOException: Error starting demangler with command");
assertMessageLogLine("Invalid options", GnuDemanglerOptions.GNU_DEMANGLER_V2_33_1);
}
@Test
public void testDeprecatedMangledString_WithArguments_Invalid() {
//
// The below demangles to std::io::Read::read_to_end
//
String mangled = "_ZN3std2io4Read11read_to_end17hb85a0f6802e14499E";
Address addr = addr("0x110");
createSymbol(addr, mangled);
setOption(GnuDemanglerAnalyzer.OPTION_NAME_USE_DEPRECATED_DEMANGLER, true);
setOption(GnuDemanglerAnalyzer.OPTION_NAME_DEMANGLER_PARAMETERS, "-s badformatname");
analyze();
assertNotDemangled(addr, "read_to_end");
assertMessageLogLine("java.io.IOException: Error starting demangler with command");
assertMessageLogLine("Invalid options", GnuDemanglerOptions.GNU_DEMANGLER_V2_33_1);
assertMessageLogLine("Invalid options", GnuDemanglerOptions.GNU_DEMANGLER_V2_24);
}
@Test
public void testDeprecatedMangledString_WithArguments_InvalidModernArguments_ValidDeprecatedArguments() {
//
// The below demangles to std::io::Read::read_to_end
//
String mangled = "_ZN3std2io4Read11read_to_end17hb85a0f6802e14499E";
Address addr = addr("0x110");
createSymbol(addr, mangled);
setOption(GnuDemanglerAnalyzer.OPTION_NAME_USE_DEPRECATED_DEMANGLER, true);
setOption(GnuDemanglerAnalyzer.OPTION_NAME_DEMANGLER_PARAMETERS, "-s arm");
analyze();
assertNotDemangled(addr, "read_to_end");
assertMessageLogLine("java.io.IOException: Error starting demangler with command");
assertMessageLogLine("Invalid options", GnuDemanglerOptions.GNU_DEMANGLER_V2_33_1);
assertMessageNotInLogLine("Invalid options", GnuDemanglerOptions.GNU_DEMANGLER_V2_24);
}
// things missed:
// -demangle error case in base class...this is OK
// -error case in applyTo method in base class
// -use deprecated demangler case in validateOptions
//==================================================================================================
// Private Methods
//==================================================================================================
private void assertMessageLogLine(String... expected) {
String allMessages = log.toString();
String[] logLines = allMessages.split("\n");
for (String line : logLines) {
if (StringUtilities.containsAllIgnoreCase(line, expected)) {
return;
}
}
fail("The folllowing source text did not have a line containing:\n" +
Arrays.toString(expected) + "\n\nActual Text:\n" + allMessages);
}
private void assertMessageNotInLogLine(String... expected) {
String allMessages = log.toString();
String[] logLines = allMessages.split("\n");
for (String line : logLines) {
if (StringUtilities.containsAllIgnoreCase(line, expected)) {
fail("The folllowing source text unexpectedly has a line containing:\n" +
Arrays.toString(expected) + "\n\nActual Text:\n" + allMessages);
}
}
}
private void analyze() {
tx(program, () -> analyzer.added(program, program.getMemory(), TaskMonitor.DUMMY, log));
}
private void assertNotDemangled(Address addr, String name) {
SymbolTable st = program.getSymbolTable();
Symbol[] symbols = st.getSymbols(addr);
for (Symbol s : symbols) {
if (s.getName().equals(name)) {
fail("Symbol should not have been demangled '" + name + "'");
}
}
}
private void assertDemangled(Address addr, String name) {
SymbolTable st = program.getSymbolTable();
Symbol[] symbols = st.getSymbols(addr);
for (Symbol s : symbols) {
if (s.getName().equals(name)) {
return;
}
}
fail("Unable to find demangled symbol '" + name + "'");
}
private void setOption(String optionName, boolean doUse) {
String fullOptionName = analyzer.getName() + Options.DELIMITER_STRING + optionName;
Options options = program.getOptions("Analyzers"); Options options = program.getOptions("Analyzers");
for (String name : options.getOptionNames()) { for (String name : options.getOptionNames()) {
if (name.equals(fullOptionName)) {
tx(program, () -> options.setBoolean(optionName, doUse));
if (name.contains("Demangler GNU")) { // we must call this manually, since we are not using a tool
Msg.out("found it: " + name); analyzer.optionsChanged(options, program);
} return;
else {
Msg.out("no it: " + name);
} }
} }
fail("Could not find option '" + optionName + "'");
}
private void setOption(String optionName, String value) {
String fullOptionName = analyzer.getName() + Options.DELIMITER_STRING + optionName;
Options options = program.getOptions("Analyzers");
for (String name : options.getOptionNames()) {
if (name.equals(fullOptionName)) {
tx(program, () -> options.setString(optionName, value));
// we must call this manually, since we are not using a tool
analyzer.optionsChanged(options, program);
return;
}
}
fail("Could not find option '" + optionName + "'");
} }
private void createSymbol(Address addr, String mangled) { private void createSymbol(Address addr, String mangled) {
AddLabelCmd cmd = new AddLabelCmd(addr, mangled, SourceType.ANALYSIS); AddLabelCmd cmd = new AddLabelCmd(addr, mangled, SourceType.ANALYSIS);
int txId = program.startTransaction(cmd.getName()); applyCmd(program, cmd);
boolean commit = true;
try {
boolean status = cmd.applyTo(program);
program.flushEvents();
if (!status) {
fail("Could not apply command: " + cmd.getStatusMsg());
}
}
catch (RollbackException e) {
commit = false;
throw e;
}
finally {
program.endTransaction(txId, commit);
}
}
@Test
public void testDeprectedDemangledString_WithArguments_Valid() {
fail();
}
@Test
public void testDeprectedDemangledString_WithArguments_Invalid() {
fail();
} }
private Address addr(String addr) { private Address addr(String addr) {
@ -123,7 +317,6 @@ public class GnuDemanglerAnalyzerTest extends AbstractGhidraHeadlessIntegrationT
private void registerOptions() { private void registerOptions() {
Options options = program.getOptions(Program.ANALYSIS_PROPERTIES); Options options = program.getOptions(Program.ANALYSIS_PROPERTIES);
Options analyzerOptions = options.getOptions(analyzer.getName()); Options analyzerOptions = options.getOptions(analyzer.getName());
analyzer.registerOptions(analyzerOptions, program); analyzer.registerOptions(analyzerOptions, program);
} }

View file

@ -19,15 +19,13 @@ import ghidra.util.Msg;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
/** /**
* A simple class to handle logging messages and exceptions. * A simple class to handle logging messages and exceptions. A maximum message count size
* A maximum message count size constraint can be set to clip * constraint can be set to clip messages after a certain number, but still keep incrementing
* messages after a certain number, but still keep incrementing
* a running total. * a running total.
*
*/ */
public class MessageLog { public class MessageLog {
/** /**
* The default number of messages to store before clipping. * The default number of messages to store before clipping
*/ */
public final static int MAX_COUNT = 500; public final static int MAX_COUNT = 500;
@ -38,14 +36,14 @@ public class MessageLog {
private String statusMsg; private String statusMsg;
/** /**
* Constructs a new message log using the default message count. * Constructs a new message log using the default message count
*/ */
public MessageLog() { public MessageLog() {
this(MAX_COUNT); this(MAX_COUNT);
} }
/** /**
* Constructs a new message log using the sepcified message count. * Constructs a new message log using the specified message count
* @param maxSize the maximum number of messages * @param maxSize the maximum number of messages
*/ */
public MessageLog(int maxSize) { public MessageLog(int maxSize) {
@ -54,7 +52,7 @@ public class MessageLog {
} }
/** /**
* Copies the contents of one message log into this one. * Copies the contents of one message log into this one
* @param log the log to copy from * @param log the log to copy from
*/ */
public void copyFrom(MessageLog log) { public void copyFrom(MessageLog log) {
@ -65,34 +63,40 @@ public class MessageLog {
} }
/** /**
* Appends the message to the log. * Appends the message to the log
* @param msg the message * @param message the message
*/ */
public void appendMsg(String msg) { public void appendMsg(String message) {
msg(msg); msg(message);
} }
public void appendMsg(String originator, String msg) { /**
* Appends the message to the log
*
* @param originator the originator of the message
* @param message the message
*/
public void appendMsg(String originator, String message) {
if (originator == null) { if (originator == null) {
msg(msg); msg(message);
} }
else { else {
msg(originator + "> " + msg); msg(originator + "> " + message);
} }
} }
/** /**
* Appends the message and line number to the log. * Appends the message and line number to the log
* @param lineNum the line number that generated the message * @param lineNum the line number that generated the message
* @param msg the message * @param message the message
*/ */
public void appendMsg(int lineNum, String msg) { public void appendMsg(int lineNum, String message) {
msg("Line #" + lineNum + " - " + msg); msg("Line #" + lineNum + " - " + message);
} }
/** /**
* Appends the exception to the log. * Appends the exception to the log
* @param t the exeception to append to the log * @param t the exception to append to the log
*/ */
public void appendException(Throwable t) { public void appendException(Throwable t) {
if (t instanceof NullPointerException || t instanceof AssertException) { if (t instanceof NullPointerException || t instanceof AssertException) {
@ -106,7 +110,7 @@ public class MessageLog {
} }
/** /**
* Returns the message count. * Returns the message count
* @return the message count * @return the message count
*/ */
public int getMsgCount() { public int getMsgCount() {
@ -114,8 +118,7 @@ public class MessageLog {
} }
/** /**
* Clears all messages from this log * Clears all messages from this log and resets the count
* and resets the count.
*/ */
public void clear() { public void clear() {
buffer = new StringBuffer(); buffer = new StringBuffer();
@ -125,7 +128,7 @@ public class MessageLog {
/** /**
* Stores a status message that can be used elsewhere (i.e., populate warning dialogs) * Stores a status message that can be used elsewhere (i.e., populate warning dialogs)
* @param status * @param status the status message
*/ */
public void setStatus(String status) { public void setStatus(String status) {
statusMsg = status; statusMsg = status;
@ -146,9 +149,6 @@ public class MessageLog {
return statusMsg; return statusMsg;
} }
/**
* @see java.lang.Object#toString()
*/
@Override @Override
public String toString() { public String toString() {
if (count > maxSize) { if (count > maxSize) {
@ -177,12 +177,11 @@ public class MessageLog {
/** /**
* Readable method for appending error messages to the log. * Readable method for appending error messages to the log.
* *
* Currently does nothing different that {@link #appendMsg(String, String)} * <p>Currently does nothing different than {@link #appendMsg(String, String)}.
* *
* *
* @param originator * @param originator the originator of the message
* @param message * @param message the message
*
*/ */
public void error(String originator, String message) { public void error(String originator, String message) {
appendMsg(originator, message); appendMsg(originator, message);