GP-1556 - Added support for searching for structure fields by offset

This commit is contained in:
dragonmacher 2022-03-25 09:43:50 -04:00
parent 883f5a687a
commit 812ea4fe1e
45 changed files with 1461 additions and 840 deletions

View file

@ -20,30 +20,30 @@ import java.util.List;
import ghidra.app.decompiler.ClangFieldToken;
import ghidra.app.decompiler.ClangLine;
import ghidra.app.services.DataTypeReference;
import ghidra.app.services.FieldMatcher;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataType;
/**
* This class represents the use of a field of a {@link Composite} data type <b>where there is
* no variable in the Decompiler</b> for that data type. A normal variable access in the
* Decompiler may look like so:
* This class represents the use of a field of a {@link Composite} data type <b>where there is no
* variable in the Decompiler</b> for that data type. A normal variable access in the Decompiler
* may look like so:
* <pre>
* Foo f;
* ...
* return f.some_field;
* </pre>
*
*
* Alternatively, an anonymous variable access would look like this:
* <pre>
* Bar b;
* ...
* return b-><b>foo_array[1].some_field</b>;
* </pre>
*
* In this case, <code><b>foo_array[1]</b></code> is a <code>Foo</code>, whose
* <code><b>some_field</b></code> is
* being accessed anonymously, since there is no variable of <code>Foo</code> declared
* in the current function.
*
* In this case, <code><b>foo_array[1]</b></code> is a <code>Foo</code>, whose
* <code><b>some_field</b></code> is being accessed anonymously, since there is no variable of
* <code>Foo</code> declared in the current function.
*/
public class AnonymousVariableAccessDR extends VariableAccessDR {
@ -52,42 +52,57 @@ public class AnonymousVariableAccessDR extends VariableAccessDR {
}
@Override
public void accumulateMatches(DataType dt, String fieldName, List<DataTypeReference> results) {
public void accumulateMatches(DataType dt, FieldMatcher fieldMatcher,
List<DataTypeReference> results) {
//
// This class is backed by a ClangFieldToken. That class's data type is the composite
// that contains the field being accessed. A variable being accessed has 2 types being
// This class is backed by a ClangFieldToken. That class's data type is the composite that
// contains the field being accessed. A variable being accessed has 2 types being
// touched: the aforementioned composite and the type of the field itself.
//
// This can match in one of two cases:
// 1) the client seeks to match a given field inside of the containing composite, or
// 2) the client seeks to match only the type, which means that the field type itself must match
// 1) the passed in type must match the field type and not the parent type, or
// 2) the passed in type must match the parent type, along with supplied field name/offset.
//
ClangFieldToken field = (ClangFieldToken) sourceToken;
DataType compositeType = field.getDataType();
DataType fieldDt = DecompilerReference.getFieldDataType(field);
boolean matchesComposite = isEqual(dt, compositeType);
boolean matchesField = isEqual(dt, fieldDt);
boolean noMatch = !(matchesComposite || matchesField);
boolean matchesCompositeType = isEqual(dt, compositeType);
boolean matchesFieldType = isEqual(dt, fieldDt);
boolean noMatch = !(matchesCompositeType || matchesFieldType);
if (noMatch) {
return;
}
if (fieldName == null) {
// case 2; no field name to check
if (matchesField) {
//
// Case 1
//
// If the client did not specify a field to match, then we only want to match on the type
// of this reference's field type and NOT the composite type, since this reference is
// referring to the field and not the composite.
//
if (fieldMatcher.isIgnored()) {
if (matchesFieldType) {
// no field name and the search type matches this reference's field type
results.add(createReference(variable));
}
// else there is no field and the search type does not match the reference's type
return;
}
// case 1; check the field name and the composite type
if (matchesComposite && field.getText().equals(fieldName)) {
results.add(
new DataTypeReference(compositeType, fieldName, getFunction(), getAddress(),
getContext()));
//
// Case 2
//
// The client has requested a particular field of the parent composite. We only have a
// match if the parent type matches and the field name/offset matches.
//
String text = field.getText();
int offset = field.getOffset();
if (matchesCompositeType && fieldMatcher.matches(text, offset)) {
results.add(new DataTypeReference(compositeType, fieldMatcher.getFieldName(),
getFunction(), getAddress(), getContext()));
}
}
}

View file

@ -28,8 +28,7 @@ import ghidra.app.decompiler.parallel.*;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReference;
import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils;
import ghidra.app.services.DataTypeReference;
import ghidra.app.services.DataTypeReferenceFinder;
import ghidra.app.services.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.BuiltInDataType;
import ghidra.program.model.data.DataType;
@ -58,10 +57,28 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
@Override
public void findReferences(Program program, DataType dataType, String fieldName,
Consumer<DataTypeReference> callback, TaskMonitor monitor) throws CancelledException {
Consumer<DataTypeReference> consumer, TaskMonitor monitor) throws CancelledException {
FieldMatcher fieldMatcher = new FieldMatcher(dataType, fieldName);
DecompilerDataTypeFinderQCallback qCallback =
new DecompilerDataTypeFinderQCallback(program, dataType, fieldName, callback);
new DecompilerDataTypeFinderQCallback(program, dataType, fieldMatcher, consumer);
doFindReferences(program, dataType, qCallback, consumer, monitor);
}
@Override
public void findReferences(Program program, FieldMatcher fieldMatcher,
Consumer<DataTypeReference> consumer, TaskMonitor monitor) throws CancelledException {
DataType dataType = fieldMatcher.getDataType();
DecompilerDataTypeFinderQCallback qCallback =
new DecompilerDataTypeFinderQCallback(program, dataType, fieldMatcher, consumer);
doFindReferences(program, dataType, qCallback, consumer, monitor);
}
private void doFindReferences(Program program, DataType dataType,
DecompilerDataTypeFinderQCallback qCallback, Consumer<DataTypeReference> consumer,
TaskMonitor monitor) throws CancelledException {
Set<Function> functions = filterFunctions(program, dataType, monitor);
@ -87,7 +104,7 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
buildTypeLineage(dt, types);
Set<Function> results = new HashSet<>();
accumulateFunctionCallsToDefinedData(program, types, results, monitor);
accumulateFunctionCallsToDefinedData(program, dt, types, results, monitor);
Listing listing = program.getListing();
FunctionIterator it = listing.getFunctions(true);
@ -115,21 +132,23 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
return results;
}
private void accumulateFunctionCallsToDefinedData(Program program, Set<DataType> potentialTypes,
Set<Function> results, TaskMonitor monitor) throws CancelledException {
private void accumulateFunctionCallsToDefinedData(Program program, DataType dataType,
Set<DataType> potentialTypes, Set<Function> results, TaskMonitor monitor)
throws CancelledException {
Listing listing = program.getListing();
AtomicInteger counter = new AtomicInteger();
SetAccumulator<LocationReference> accumulator = new SetAccumulator<>();
Predicate<Data> dataMatcher = data -> {
counter.incrementAndGet();
DataType dataType = data.getDataType();
boolean matches = potentialTypes.contains(dataType);
DataType dt = data.getDataType();
boolean matches = potentialTypes.contains(dt);
return matches;
};
ReferenceUtils.findDataTypeMatchesInDefinedData(accumulator, program, dataMatcher, null,
monitor);
FieldMatcher emptyMatcher = new FieldMatcher(dataType);
ReferenceUtils.findDataTypeMatchesInDefinedData(accumulator, program, dataMatcher,
emptyMatcher, monitor);
for (LocationReference ref : accumulator) {
Address address = ref.getLocationOfUse();
@ -158,12 +177,10 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
// We have a different type, should we search for it?
if (baseType instanceof BuiltInDataType) {
// When given a wrapper type (e.g., typedef) , ignore
// built-ins (e.g., int, byte, etc), as
// they will be of little value due to their volume in the program and the
// user *probably* did not intend to search for them. (Below we do not do
// this check, which allows the user to search directly for a
// built-in type, if they wish.)
// When given a wrapper type (e.g., typedef) , ignore built-ins (e.g., int, byte, etc),
// as they will be of little value due to their volume in the program and the user
// *probably* did not intend to search for them. (Below we do not do this check, which
// allows the user to search directly for a built-in type, if they wish.)
return;
}
@ -207,16 +224,16 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
private Consumer<DataTypeReference> callback;
private DataType dataType;
private String fieldName;
private FieldMatcher fieldMatcher;
/* Search for composite field access */
DecompilerDataTypeFinderQCallback(Program program, DataType dataType, String fieldName,
Consumer<DataTypeReference> callback) {
DecompilerDataTypeFinderQCallback(Program program, DataType dataType,
FieldMatcher fieldMatcher, Consumer<DataTypeReference> callback) {
super(program, new DecompilerConfigurer());
this.dataType = dataType;
this.fieldName = fieldName;
this.fieldMatcher = fieldMatcher;
this.callback = callback;
}
@ -230,7 +247,7 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
}
DecompilerDataTypeFinder finder =
new DecompilerDataTypeFinder(results, function, dataType, fieldName);
new DecompilerDataTypeFinder(results, function, dataType, fieldMatcher);
List<DataTypeReference> refs = finder.findUsage();
refs.forEach(r -> callback.accept(r));
@ -263,14 +280,14 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
private DecompileResults decompilation;
private Function function;
private DataType dataType;
private String fieldName;
private FieldMatcher fieldMatcher;
DecompilerDataTypeFinder(DecompileResults results, Function function, DataType dataType,
String fieldName) {
FieldMatcher fieldMatcher) {
this.decompilation = results;
this.function = function;
this.dataType = dataType;
this.fieldName = fieldName;
this.fieldMatcher = fieldMatcher;
}
List<DataTypeReference> findUsage() {
@ -300,7 +317,7 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
/** Finds any search input match in the given reference */
private void matchUsage(DecompilerReference reference, List<DataTypeReference> results) {
reference.accumulateMatches(dataType, fieldName, results);
reference.accumulateMatches(dataType, fieldMatcher, results);
}
private List<DecompilerReference> findVariableReferences(ClangTokenGroup tokens) {
@ -320,20 +337,20 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
* the case with Composite types, may have one of its fields accessed. Each result
* found by this method will be at least a variable access and may also itself have
* field accesses.
*
*
* <p>Sometimes a line is structured such that there are anonymous variable accesses. This
* is the case where a Composite is being accessed, but the Composite itself is
* not a variable in the current function. See {@link AnonymousVariableAccessDR} for
* more details.
*
*
* @param line the current line being processed from the Decompiler
* @param results the accumulator into which matches will be placed
*/
private void findVariablesInLine(ClangLine line, List<DecompilerReference> results) {
List<ClangToken> allTokens = line.getAllTokens();
Iterable<ClangToken> filteredTokens = IterableUtils.filteredIterable(allTokens,
token -> {
Iterable<ClangToken> filteredTokens =
IterableUtils.filteredIterable(allTokens, token -> {
// Only include desirable tokens (this is really just for easier debugging).
// Update this filter if the loop below ever needs other types of tokens.
return (token instanceof ClangTypeToken) ||

View file

@ -23,7 +23,7 @@ import ghidra.program.model.data.*;
import ghidra.util.Msg;
/**
* A class that represents access to a Decompiler {@link ClangFieldToken} object. This is the field
* A class that represents access to a Decompiler {@link ClangFieldToken} object. This is the field
* of a variable, denoted by {@link ClangVariableToken}.
*/
public class DecompilerFieldAccess extends DecompilerVariable {
@ -73,6 +73,12 @@ public class DecompilerFieldAccess extends DecompilerVariable {
return dt;
}
@Override
public int getOffset() {
ClangFieldToken field = (ClangFieldToken) variable;
return field.getOffset();
}
protected DataType getBaseType(DataType dt) {
if (dt instanceof Array) {
return getBaseType(((Array) dt).getDataType());

View file

@ -21,6 +21,7 @@ import ghidra.app.decompiler.*;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContextBuilder;
import ghidra.app.services.DataTypeReference;
import ghidra.app.services.FieldMatcher;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Function;
@ -51,12 +52,12 @@ public abstract class DecompilerReference {
* The <tt>fieldName</tt> is optional. If not included, then only data type matches will
* be sought. If it is included, then a match is only included when it is a reference
* to the given data type where that type is being accessed by the given field name.
*
*
* @param dt the data type to find
* @param fieldName the optional field name used to restrict matches.
* @param fieldMatcher the optional field matcher used to restrict matches.
* @param results the accumulator object into which will be placed any matches
*/
public abstract void accumulateMatches(DataType dt, String fieldName,
public abstract void accumulateMatches(DataType dt, FieldMatcher fieldMatcher,
List<DataTypeReference> results);
public DecompilerVariable getVariable() {
@ -160,7 +161,7 @@ public abstract class DecompilerReference {
int offset = field.getOffset();
int n = parent.getLength();
if (offset >= 0 && offset < n) {
DataTypeComponent dtc = parent.getComponentAt(field.getOffset());
DataTypeComponent dtc = parent.getComponentContaining(field.getOffset());
fieldDt = dtc.getDataType();
}
}

View file

@ -188,6 +188,10 @@ public abstract class DecompilerVariable {
return text;
}
public int getOffset() {
return Integer.MIN_VALUE; // subclasses can override
}
@Override
public String toString() {
String castString = casts.isEmpty() ? "" : "\tcasts: " + casts + ",\n";

View file

@ -20,6 +20,7 @@ import java.util.List;
import ghidra.app.decompiler.ClangLine;
import ghidra.app.decompiler.ClangTypeToken;
import ghidra.app.services.DataTypeReference;
import ghidra.app.services.FieldMatcher;
import ghidra.program.model.data.DataType;
public class ReturnTypeDR extends DecompilerReference {
@ -29,11 +30,11 @@ public class ReturnTypeDR extends DecompilerReference {
}
@Override
public void accumulateMatches(DataType dt, String fieldName, List<DataTypeReference> results) {
public void accumulateMatches(DataType dt, FieldMatcher fieldMatcher,
List<DataTypeReference> results) {
if (fieldName != null) {
// Return Types do not have any field usage
return;
if (!fieldMatcher.isIgnored()) {
return; // Return Types do not have any field usage
}
DataType myDt = getDataType();

View file

@ -21,6 +21,7 @@ import java.util.List;
import ghidra.app.decompiler.*;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext;
import ghidra.app.services.DataTypeReference;
import ghidra.app.services.FieldMatcher;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
@ -58,10 +59,11 @@ public class VariableAccessDR extends DecompilerReference {
}
@Override
public void accumulateMatches(DataType dt, String fieldName, List<DataTypeReference> results) {
public void accumulateMatches(DataType dt, FieldMatcher fieldMatcher,
List<DataTypeReference> results) {
if (fields.isEmpty()) {
DecompilerVariable var = getMatch(dt, fieldName, variable, null);
DecompilerVariable var = getMatch(dt, fieldMatcher, variable, null);
if (var != null) {
DataTypeReference ref = createReference(var);
results.add(ref);
@ -77,7 +79,7 @@ public class VariableAccessDR extends DecompilerReference {
for (DecompilerVariable field : fields) {
DecompilerVariable next = field;
DecompilerVariable var = getMatch(dt, fieldName, start, next);
DecompilerVariable var = getMatch(dt, fieldMatcher, start, next);
if (var != null) {
DataTypeReference ref = createReference(var, next);
results.add(ref);
@ -87,46 +89,46 @@ public class VariableAccessDR extends DecompilerReference {
}
//
// Handle the last variable by itself (for the case where we are matching just on the
// type, with no field name)
// Handle the last variable by itself (for the case where we are matching just on the type,
// with no field name)
//
if (fieldName != null) {
if (fieldMatcher.isIgnored()) {
return;
}
DecompilerVariable var = getMatch(dt, null, start, null);
DecompilerVariable var = getMatch(dt, fieldMatcher, start, null);
if (var != null) {
DataTypeReference ref = createReference(var);
results.add(ref);
}
}
private DecompilerVariable getMatch(DataType dt, String fieldName, DecompilerVariable var,
DecompilerVariable potentialField) {
private DecompilerVariable getMatch(DataType dt, FieldMatcher fieldMatcher,
DecompilerVariable var, DecompilerVariable potentialField) {
// Note: for now, I ignore the precedence of casting; if any cast type is a match, then
// signal hooray
boolean searchForField = fieldName != null;
boolean searchForField = !fieldMatcher.isIgnored();
DecompilerVariable fieldVar = searchForField ? potentialField : null;
DecompilerVariable match = getMatchingVarialbe(dt, var, fieldVar);
if (match == null) {
// wrong type, nothing to do
return null;
return null; // wrong type, nothing to do
}
// Matches on the type, does the field match?
if (fieldName == null) {
if (fieldMatcher.isIgnored()) {
return match; // no field to match
}
if (potentialField == null) {
// check for the case where we have not been passed a 'potential field', but the given
// 'var' is itself the field we seek, such as in an if statement like this:
// 'var' is itself may be the field we seek, such as in an if statement like this:
// if (color == RED)
// where 'RED' is the variable we are checking
String name = var.getName();
if (fieldName.equals(name)) {
int offset = var.getOffset();
if (fieldMatcher.matches(name, offset)) {
return var;
}
@ -134,7 +136,8 @@ public class VariableAccessDR extends DecompilerReference {
}
String name = potentialField.getName();
if (fieldName.equals(name)) {
int offset = potentialField.getOffset();
if (fieldMatcher.matches(name, offset)) {
return match;
}
return null;
@ -156,13 +159,13 @@ public class VariableAccessDR extends DecompilerReference {
//
// Unusual Code Alert!
// It is a bit odd to check the field when you are looking for the type that contains
// the field. BUT, in the Decompiler, SOMETIMES the 'field' happens to have the
// data type of the thing that contains it. So, if you have:
// It is a bit odd to check the field when you are looking for the type that contains the
// field. BUT, in the Decompiler, SOMETIMES the 'field' happens to have the data type of
// the thing that contains it. So, if you have:
// foo.bar
// then the 'bar' field will have a data type of Foo. Unfortunately, this is not
// always the case. For now, when the variable is global, we need to check the field
// Sad face emoji.
// then the 'bar' field will have a data type of Foo. Unfortunately, this is not always
// the case. For now, when the variable is global, we need to check the field. Sad face
// emoji.
//
HighVariable highVariable = var.variable.getHighVariable();
if (highVariable instanceof HighGlobal) {

View file

@ -20,6 +20,7 @@ import java.util.List;
import ghidra.app.decompiler.*;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext;
import ghidra.app.services.DataTypeReference;
import ghidra.app.services.FieldMatcher;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
@ -52,7 +53,8 @@ public abstract class VariableDR extends DecompilerReference {
}
@Override
public void accumulateMatches(DataType dt, String fieldName, List<DataTypeReference> results) {
public void accumulateMatches(DataType dt, FieldMatcher fieldMatcher,
List<DataTypeReference> results) {
if (variable == null) {
// This implies our API was misused in that a variable was never set after creation
@ -61,21 +63,18 @@ public abstract class VariableDR extends DecompilerReference {
DataType dataType = getDataType();
if (!isEqual(dataType, dt)) {
// wrong type, nothing to do
return;
}
LocationReferenceContext context = getContext();
Function function = getFunction();
Address address = getAddress();
if (fieldName == null) {
// no field to check, a match on the the type is good enough
results.add(new DataTypeReference(dataType, null, getFunction(), address, context));
return;
return; // wrong type, nothing to do
}
String name = variable.getName();
if (name.equals(fieldName)) {
int offset = variable.getOffset();
if (fieldMatcher.matches(name, offset)) {
// this will be null if the field matcher is empty
String fieldName = fieldMatcher.getFieldName();
Function function = getFunction();
Address address = getAddress();
LocationReferenceContext context = getContext();
results.add(new DataTypeReference(dataType, fieldName, function, address, context));
}
}