Merge remote-tracking branch 'origin/GP-3826_ryanmkurtz_DefLoader' into

Ghidra_10.4 (Closes #5676)
This commit is contained in:
Ryan Kurtz 2023-09-11 12:38:43 -04:00
commit 7bffc47b81
3 changed files with 307 additions and 90 deletions

View File

@ -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 <a href="https://learn.microsoft.com/en-us/cpp/build/reference/exports?view=msvc-170">EXPORTS</a>
*
*/
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);
}
}
}

View File

@ -88,10 +88,14 @@ public class DefLoader extends AbstractProgramWrapperLoader {
}
SymbolTable symtab = prog.getSymbolTable();
Consumer<String> errorConsumer = err -> log.error("DefLoader", err);
Consumer<String> 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;
}
}

View File

@ -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
}
}