From f4eb9f2082740939f39c21b6a5e0f4ecbebdb325 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Thu, 7 Sep 2023 18:24:26 -0400 Subject: [PATCH] GP-3826: DefLoader fixes --- .../app/util/opinion/DefExportLine.java | 185 ++++++++++++---- .../ghidra/app/util/opinion/DefLoader.java | 13 +- .../app/util/opinion/DefExportLineTest.java | 199 ++++++++++++++---- 3 files changed, 307 insertions(+), 90 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefExportLine.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefExportLine.java index 483745999b..2dd847cb01 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefExportLine.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefExportLine.java @@ -15,67 +15,162 @@ */ package ghidra.app.util.opinion; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import ghidra.util.exception.AssertException; +import java.io.IOException; +import java.util.StringTokenizer; /** - * An object to parse a line from a ".def" file. + * An object to parse an EXPORTS line from a ".def" file. + * + * @see EXPORTS + * */ class DefExportLine { - private Pattern EXPORT_LINE_PATTERN = Pattern.compile("\\s*(\\w+)(\\s@\\d+)?(\\s\\w+)?"); - private String name; - private int ordinal; - private String type; + private String internalName; + private String otherModuleName; + private String otherModuleExportedName; + private Integer otherModuleOrdinal; + private Integer ordinal; + private boolean isNoName; + private boolean isPrivate; + private boolean isData; - DefExportLine(String exportLine) { - - // - // Format: FunctionName [@1] [PRIVATE] - // - - Matcher matcher = EXPORT_LINE_PATTERN.matcher(exportLine); - if (!matcher.matches()) { - throw new AssertException("Unexpected '.def' file line format. " + - "Expected 'Name [@number] [PRIVATE]';" + " found " + exportLine); + /** + * Parses the given export line into a new {@link DefExportLine} + * + * @param exportLine The export line + * @throws IOException if there was a problem parsing + */ + DefExportLine(String exportLine) throws IOException { + StringTokenizer st = new StringTokenizer(exportLine); + if (!st.hasMoreTokens()) { + throw new IOException("Line is empty"); + } + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (name == null) { + String[] equalsParts = token.split("=", 2); + name = equalsParts[0]; + if (equalsParts.length > 1) { + String[] dotParts = equalsParts[1].split("\\.", 2); + if (dotParts.length == 1) { + internalName = equalsParts[1]; + } + else { + otherModuleName = dotParts[0]; + if (dotParts[1].startsWith("#")) { + otherModuleOrdinal = parseInt(dotParts[1].substring(1)); + } + else { + otherModuleExportedName = dotParts[1]; + } + } + } + } + else if (ordinal == null && token.startsWith("@")) { + if (!token.equals("@")) { + ordinal = parseInt(token.substring(1)); + } + else if (st.hasMoreTokens()) { + ordinal = parseInt(st.nextToken()); + } + } + else { + switch (token) { + case "NONAME": + isNoName = true; + break; + case "PRIVATE": + isPrivate = true; + break; + case "DATA": + isData = true; + break; + default: + throw new IOException("Invalid type: " + token); + } + } } - name = matcher.group(1); - String ordinalString = matcher.group(2); - if (ordinalString != null) { // this is optional - ordinalString = ordinalString.trim().substring(1); // strip off '@' - ordinal = Integer.parseInt(ordinalString); - } - - String privateString = matcher.group(3); - if (privateString != null) { - type = privateString.trim(); - } - } - - int getOrdinal() { - return ordinal; } + /** + * {@return the name} + */ String getName() { return name; } - String getType() { - return type; + /** + * {@return the internal name, or null if there is no internal name} + */ + String getInternalName() { + return internalName; } - @Override - public String toString() { - //@formatter:off - return "{\n" + - "\tname: " + name + ",\n" + - "\tordinal: " + ordinal + ",\n" + - "\ttype: " + type + "\n" + - "}"; - //@formatter:on + /** + * {@return the other module name, or null if there is no other module} + */ + String getOtherModuleName() { + return otherModuleName; + } + + /** + * {@return the other module exported name, or null if there is no other module exported name} + */ + String getOtherModuleExportedName() { + return otherModuleExportedName; + } + + /** + * {@return the other module ordinal, or null if there is no other module ordinal} + */ + Integer getOtherModuleOrdinal() { + return otherModuleOrdinal; + } + + /** + * {@return the ordinal value, or null if there is no ordinal} + */ + Integer getOrdinal() { + return ordinal; + } + + /** + * {@return true if the export has no name; otherwise, false} + */ + boolean isNoName() { + return isNoName; + } + + /** + * {@return true if the export is private; otherwise, false} + */ + boolean isPrivate() { + return isPrivate; + } + + /** + * {@return true if the export is data; otherwise, false} + */ + boolean isData() { + return isData; + } + + /** + * Parses the {@link String} argument as a signed decimal integer + * + * @param str The {@link String} to parse + * @return The integer value represented by the argument in decimal + * @throws IOException if the {@link String} does not contain a parseable integer + */ + private int parseInt(String str) throws IOException { + try { + return Integer.parseInt(str); + } + catch (NumberFormatException e) { + throw new IOException(e); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefLoader.java index 3d8699d1d2..01dcf7e6e4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefLoader.java @@ -88,10 +88,14 @@ public class DefLoader extends AbstractProgramWrapperLoader { } SymbolTable symtab = prog.getSymbolTable(); - Consumer errorConsumer = err -> log.error("DefLoader", err); + Consumer errorConsumer = err -> log.appendMsg("DefLoader", err); for (DefExportLine def : parseExports(provider)) { + Integer ordinal = def.getOrdinal(); + if (ordinal == null) { + continue; + } Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(prog, - SymbolUtilities.ORDINAL_PREFIX + def.getOrdinal(), errorConsumer); + SymbolUtilities.ORDINAL_PREFIX + ordinal, errorConsumer); if (symbol == null) { continue; } @@ -110,4 +114,9 @@ public class DefLoader extends AbstractProgramWrapperLoader { public String getName() { return DEF_NAME; } + + @Override + public boolean supportsLoadIntoProgram(Program program) { + return true; + } } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DefExportLineTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DefExportLineTest.java index 095f97f050..1103bbd914 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DefExportLineTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DefExportLineTest.java @@ -15,70 +15,183 @@ */ package ghidra.app.util.opinion; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; + +import java.io.IOException; import org.junit.Test; -import ghidra.util.exception.AssertException; - public class DefExportLineTest { @Test - public void testExportLineWithOrdinal() { - - // - // Format: FunctionName @1 PRIVATE - // - DefExportLine line = new DefExportLine("BobsHouse @1 PRIVATE"); - assertEquals("BobsHouse", line.getName()); - assertEquals(1, line.getOrdinal()); - assertEquals("PRIVATE", line.getType()); + public void testExportLineNameOnly() throws IOException { + DefExportLine export = new DefExportLine("func"); + assertEquals("func", export.getName()); + assertEquals(null, export.getInternalName()); + assertEquals(null, export.getOtherModuleName()); + assertEquals(null, export.getOtherModuleExportedName()); + assertEquals(null, export.getOtherModuleOrdinal()); + assertEquals(null, export.getOrdinal()); + assertEquals(false, export.isNoName()); + assertEquals(false, export.isPrivate()); + assertEquals(false, export.isData()); } @Test - public void testExportLineWithoutOrdinal() { - - // - // Format: FunctionName PRIVATE - // - DefExportLine line = new DefExportLine("BobsHouse PRIVATE"); - assertEquals("BobsHouse", line.getName()); - assertEquals(0, line.getOrdinal()); - assertEquals("PRIVATE", line.getType()); + public void testExportLineInternalName() throws IOException { + DefExportLine export = new DefExportLine("func2=func1"); + assertEquals("func2", export.getName()); + assertEquals("func1", export.getInternalName()); + assertEquals(null, export.getOtherModuleName()); + assertEquals(null, export.getOtherModuleExportedName()); + assertEquals(null, export.getOtherModuleOrdinal()); + assertEquals(null, export.getOrdinal()); + assertEquals(false, export.isNoName()); + assertEquals(false, export.isPrivate()); + assertEquals(false, export.isData()); } @Test - public void testExportLineWithoutPrivateKeyword() { - - // - // Format: FunctionName PRIVATE - // - DefExportLine line = new DefExportLine("BobsHouse @1"); - assertEquals("BobsHouse", line.getName()); - assertEquals(1, line.getOrdinal()); - assertEquals(null, line.getType()); + public void testExportLineOtherModuleExportedName() throws IOException { + DefExportLine export = new DefExportLine("func2=other_module.func1"); + assertEquals("func2", export.getName()); + assertEquals(null, export.getInternalName()); + assertEquals("other_module", export.getOtherModuleName()); + assertEquals("func1", export.getOtherModuleExportedName()); + assertEquals(null, export.getOtherModuleOrdinal()); + assertEquals(null, export.getOrdinal()); + assertEquals(false, export.isNoName()); + assertEquals(false, export.isPrivate()); + assertEquals(false, export.isData()); } @Test - public void testExportLineWithoutOrdinalOrPrivateKeyword() { - - // - // Format: FunctionName PRIVATE - // - DefExportLine line = new DefExportLine("BobsHouse"); - assertEquals("BobsHouse", line.getName()); - assertEquals(0, line.getOrdinal()); - assertEquals(null, line.getType()); + public void testExportLineOtherModuleOrdinal() throws IOException { + DefExportLine export = new DefExportLine("func2=other_module.#42"); + assertEquals("func2", export.getName()); + assertEquals(null, export.getInternalName()); + assertEquals("other_module", export.getOtherModuleName()); + assertEquals(null, export.getOtherModuleExportedName()); + assertEquals(42, (int) export.getOtherModuleOrdinal()); + assertEquals(null, export.getOrdinal()); + assertEquals(false, export.isNoName()); + assertEquals(false, export.isPrivate()); + assertEquals(false, export.isData()); } @Test - public void testExportLineWithInvalidFormat() { + public void testExportLineOrdinal() throws IOException { + DefExportLine export = new DefExportLine("func @1"); + assertEquals("func", export.getName()); + assertEquals(null, export.getInternalName()); + assertEquals(null, export.getOtherModuleName()); + assertEquals(null, export.getOtherModuleExportedName()); + assertEquals(null, export.getOtherModuleOrdinal()); + assertEquals(1, (int) export.getOrdinal()); + assertEquals(false, export.isNoName()); + assertEquals(false, export.isPrivate()); + assertEquals(false, export.isData()); + } + + @Test + public void testExportLineOrdinalSpaces() throws IOException { + DefExportLine export = new DefExportLine("func @ 1"); + assertEquals("func", export.getName()); + assertEquals(null, export.getInternalName()); + assertEquals(null, export.getOtherModuleName()); + assertEquals(null, export.getOtherModuleExportedName()); + assertEquals(null, export.getOtherModuleOrdinal()); + assertEquals(1, (int) export.getOrdinal()); + assertEquals(false, export.isNoName()); + assertEquals(false, export.isPrivate()); + assertEquals(false, export.isData()); + } + + @Test + public void testExportLineOrdinalNoName() throws IOException { + DefExportLine export = new DefExportLine("func @1 NONAME"); + assertEquals("func", export.getName()); + assertEquals(null, export.getInternalName()); + assertEquals(null, export.getOtherModuleName()); + assertEquals(null, export.getOtherModuleExportedName()); + assertEquals(null, export.getOtherModuleOrdinal()); + assertEquals(1, (int) export.getOrdinal()); + assertEquals(true, export.isNoName()); + assertEquals(false, export.isPrivate()); + assertEquals(false, export.isData()); + } + + @Test + public void testExportLineData() throws IOException { + DefExportLine export = new DefExportLine("exported_global DATA"); + assertEquals("exported_global", export.getName()); + assertEquals(null, export.getInternalName()); + assertEquals(null, export.getOtherModuleName()); + assertEquals(null, export.getOtherModuleExportedName()); + assertEquals(null, export.getOtherModuleOrdinal()); + assertEquals(null, export.getOrdinal()); + assertEquals(false, export.isNoName()); + assertEquals(false, export.isPrivate()); + assertEquals(true, export.isData()); + } + + @Test + public void testExportLinePrivate() throws IOException { + DefExportLine export = new DefExportLine("func PRIVATE"); + assertEquals("func", export.getName()); + assertEquals(null, export.getInternalName()); + assertEquals(null, export.getOtherModuleName()); + assertEquals(null, export.getOtherModuleExportedName()); + assertEquals(null, export.getOtherModuleOrdinal()); + assertEquals(null, export.getOrdinal()); + assertEquals(false, export.isNoName()); + assertEquals(true, export.isPrivate()); + assertEquals(false, export.isData()); + } + + @Test + public void testExportLineAll() throws IOException { + DefExportLine export = new DefExportLine("func2=other_module.#42 @ 1 NONAME PRIVATE"); + assertEquals("func2", export.getName()); + assertEquals(null, export.getInternalName()); + assertEquals("other_module", export.getOtherModuleName()); + assertEquals(null, export.getOtherModuleExportedName()); + assertEquals(42, (int) export.getOtherModuleOrdinal()); + assertEquals(1, (int) export.getOrdinal()); + assertEquals(true, export.isNoName()); + assertEquals(true, export.isPrivate()); + assertEquals(false, export.isData()); + } + + @Test + public void testExportLineWithNoName() { try { - new DefExportLine("one two three four"); + new DefExportLine(" "); fail("Did not get a parsing exception with an invalid format"); } - catch (AssertException e) { + catch (IOException e) { + // expected + } + } + + @Test + public void testExportLineWithInvalidOrdinal() { + try { + new DefExportLine("func @ff"); + fail("Did not get a parsing exception with an invalid format"); + } + catch (IOException e) { + // expected + } + } + + @Test + public void testExportLineWithInvalidType() { + try { + new DefExportLine("func @ 1 INVALID_TYPE"); + fail("Did not get a parsing exception with an invalid format"); + } + catch (IOException e) { // expected } }