GT-2882 - Decompiler - Updated to set the cursor to the function

signature when a new function is loaded
This commit is contained in:
dragonmacher 2019-06-06 11:05:39 -04:00
parent 67198eb40f
commit e9540fa5c1
3 changed files with 150 additions and 57 deletions

View file

@ -203,25 +203,77 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
public void setLocation(ProgramLocation location, ViewerPosition viewerPosition) {
repaint();
Address address = location.getAddress();
if (address == null) {
if (location.getAddress() == null) {
return;
}
if (viewerPosition != null) {
fieldPanel.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(),
viewerPosition.getYOffset());
}
List<ClangToken> tokens =
DecompilerUtils.getTokens(layoutMgr.getRoot(), translateAddress(address));
if (location instanceof DecompilerLocation) {
DecompilerLocation decompilerLocation = (DecompilerLocation) location;
fieldPanel.goTo(BigInteger.valueOf(decompilerLocation.getLineNumber()), 0, 0,
decompilerLocation.getCharPos(), false);
return;
}
else if (!tokens.isEmpty()) {
goToBeginningOfLine(tokens);
//
// Try to figure out where the given location's address maps to. If we can find the
// line that contains the address, the go to the beginning of that line. (We do not try
// to go to an actual token, since multiple tokens can share an address, we woudln't know
// which token is best.)
//
// Note: at the time of this writing, not all fields have an address value. For
// example, the ClangFuncNameToken, does not have an address. (It seems that most
// of the tokens in the function signature do not have an address, which can
// probably be fixed.) So, to deal with this oddity, we will have some special
// case code below.
//
Address address = location.getAddress();
if (goToFunctionSignature(address)) {
// special case: the address is at the function entry, which means we just navigate
// to the signature
return;
}
Address translated = translate(address);
List<ClangToken> tokens =
DecompilerUtils.getTokensFromView(layoutMgr.getFields(), translated);
goToBeginningOfLine(tokens);
}
private boolean goToFunctionSignature(Address address) {
Address entry = decompileData.getFunction().getEntryPoint();
if (!entry.equals(address)) {
return false;
}
List<ClangLine> lines = layoutMgr.getLines();
ClangLine signatureLine = getFunctionSignatureLine(lines);
if (signatureLine == null) {
return false; // can happen when there is no function decompiled
}
// -1 since the FieldPanel is 0-based; we are 1-based
int lineNumber = signatureLine.getLineNumber() - 1;
fieldPanel.goTo(BigInteger.valueOf(lineNumber), 0, 0, 0, false);
return true;
}
private ClangLine getFunctionSignatureLine(List<ClangLine> functionLines) {
for (ClangLine line : functionLines) {
List<ClangToken> tokens = line.getAllTokens();
for (ClangToken token : tokens) {
if (token.Parent() instanceof ClangFuncProto) {
return line;
}
}
}
return null;
}
/**
@ -229,6 +281,10 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
* @param tokens the tokens to search for
*/
private void goToBeginningOfLine(List<ClangToken> tokens) {
if (tokens.isEmpty()) {
return;
}
int firstLineNumber = DecompilerUtils.findIndexOfFirstField(tokens, layoutMgr.getFields());
if (firstLineNumber != -1) {
fieldPanel.goTo(BigInteger.valueOf(firstLineNumber), 0, 0, 0, false);
@ -290,7 +346,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
* @param addr the Ghidra address
* @return the decompiler address
*/
private Address translateAddress(Address addr) {
private Address translate(Address addr) {
Function func = decompileData.getFunction();
if (func == null) {
return addr;
@ -418,7 +474,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
ClangTextField textField = (ClangTextField) field;
ClangToken token = textField.getToken(location);
if (token instanceof ClangFuncNameToken) {
tryGoToFunction(token, newWindow);
tryGoToFunction((ClangFuncNameToken) token, newWindow);
}
else if (token instanceof ClangLabelToken) {
tryGoToLabel((ClangLabelToken) token, newWindow);
@ -451,16 +507,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
tryGoToScalar(word, newWindow);
}
private void tryGoToFunction(ClangToken token, boolean newWindow) {
Function function =
DecompilerUtils.getFunction(controller.getProgram(), (ClangFuncNameToken) token);
private void tryGoToFunction(ClangFuncNameToken functionToken, boolean newWindow) {
Function function = DecompilerUtils.getFunction(controller.getProgram(), functionToken);
if (function != null) {
controller.goToFunction(function, newWindow);
return;
}
// TODO no idea what this is supposed to be handling...someone doc this please
String labelName = token.getText();
String labelName = functionToken.getText();
if (labelName.startsWith("func_0x")) {
try {
Address addr =

View file

@ -24,24 +24,29 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.*;
import ghidra.util.Msg;
public class DecompilerUtils {
/**
* If the token refers to an individual Varnode, return it. Otherwise return null;
* If the token refers to an individual Varnode, return it. Otherwise return null
*
* @param token the token to check
* @return the Varnode or null otherwise
*/
public static Varnode getVarnodeRef(ClangToken vartoken) {
if (vartoken == null) {
public static Varnode getVarnodeRef(ClangToken token) {
if (token == null) {
return null;
}
if (vartoken instanceof ClangVariableToken) {
Varnode res = vartoken.getVarnode();
if (token instanceof ClangVariableToken) {
Varnode res = token.getVarnode();
if (res != null) {
return res;
}
}
ClangNode parent = vartoken.Parent();
ClangNode parent = token.Parent();
if (parent instanceof ClangVariableDecl) {
HighVariable high = ((ClangVariableDecl) parent).getHighVariable();
parent = parent.Parent();
@ -63,8 +68,8 @@ public class DecompilerUtils {
* @return set of Varnodes in the slice
*/
public static Set<Varnode> getForwardSlice(Varnode seed) {
HashSet<Varnode> varnodes = new HashSet<>();
ArrayList<Varnode> worklist = new ArrayList<>();
Set<Varnode> varnodes = new HashSet<>();
List<Varnode> worklist = new ArrayList<>();
worklist.add(seed);
for (int i = 0; i < worklist.size(); i++) {
@ -97,8 +102,8 @@ public class DecompilerUtils {
}
public static Set<Varnode> getBackwardSlice(Varnode seed) {
HashSet<Varnode> varnodes = new HashSet<>();
ArrayList<Varnode> worklist = new ArrayList<>();
Set<Varnode> varnodes = new HashSet<>();
List<Varnode> worklist = new ArrayList<>();
worklist.add(seed);
for (int i = 0; i < worklist.size(); i++) {
@ -209,7 +214,12 @@ public class DecompilerUtils {
}
/**
* @return the function referenced by the given token
* Returns the function represented by the given token. This will be either the
* decompiled function or a function referenced within the decompiled function.
*
* @param program the progam
* @param token the token
* @return the function
*/
public static Function getFunction(Program program, ClangFuncNameToken token) {
@ -221,6 +231,7 @@ public class DecompilerUtils {
return clangFunction.getHighFunction().getFunction();
}
}
if (parent instanceof ClangStatement) {
// sub-function call
PcodeOp pcodeOp = token.getPcodeOp();
@ -239,6 +250,10 @@ public class DecompilerUtils {
* @return index of field, or -1
*/
public static int findIndexOfFirstField(List<ClangToken> queryTokens, Field[] fields) {
if (queryTokens.isEmpty()) {
return -1;
}
for (int i = 0; i < fields.length; i++) {
ClangTextField f = (ClangTextField) fields[i];
List<ClangToken> fieldTokens = f.getTokens();
@ -252,6 +267,32 @@ public class DecompilerUtils {
return -1;
}
/**
* Similar to {@link #getTokens(ClangNode, AddressSetView)}, but uses the tokens from
* the given view fields. Sometimes the tokens in the model (represented by the
* {@link ClangNode}) are different than the fields in the view (such as when a list of
* comment tokens are condensed into a single comment token).
*
* @param fields the fields to check
* @param address the address each returned token must match
* @return the matching tokens
*/
public static List<ClangToken> getTokensFromView(Field[] fields, Address address) {
AddressSetView set = new AddressSet(address);
List<ClangToken> result = new ArrayList<>();
for (Field f : fields) {
ClangTextField tf = (ClangTextField) f;
List<ClangToken> fieldTokens = tf.getTokens();
for (ClangToken token : fieldTokens) {
if (intersects(token, set)) {
result.add(token);
}
}
}
return result;
}
/**
* Find all ClangNodes that have a minimum address in the AddressSetView
* @param root the root of the token tree
@ -265,26 +306,8 @@ public class DecompilerUtils {
}
public static List<ClangToken> getTokens(ClangNode root, Address address) {
List<ClangToken> tokenList = new ArrayList<>();
collectTokens(tokenList, root, address);
return tokenList;
}
private static void collectTokens(List<ClangToken> tokenList, ClangNode parentNode,
Address address) {
int nchild = parentNode.numChildren();
for (int i = 0; i < nchild; i++) {
ClangNode node = parentNode.Child(i);
if (node.numChildren() > 0) {
collectTokens(tokenList, node, address);
}
else if (node instanceof ClangToken) {
ClangToken token = (ClangToken) node;
if (intersects(token, address)) {
tokenList.add((ClangToken) node);
}
}
}
AddressSet set = new AddressSet(address);
return getTokens(root, set);
}
private static void collectTokens(List<ClangToken> tokenList, ClangNode parentNode,
@ -292,6 +315,11 @@ public class DecompilerUtils {
int nchild = parentNode.numChildren();
for (int i = 0; i < nchild; i++) {
ClangNode node = parentNode.Child(i);
if (node instanceof ClangFuncProto) {
Msg.debug(null, "");
}
if (node.numChildren() > 0) {
collectTokens(tokenList, node, addressSet);
}
@ -314,18 +342,6 @@ public class DecompilerUtils {
return addressSet.intersects(minAddress, maxAddress);
}
private static boolean intersects(ClangToken token, Address address) {
Address minAddress = token.getMinAddress();
if (minAddress == null) {
return false;
}
Address maxAddress = token.getMaxAddress();
if (maxAddress == null) {
return minAddress.equals(maxAddress);
}
return address.compareTo(minAddress) >= 0 && address.compareTo(maxAddress) <= 0;
}
public static Address getClosestAddress(ClangToken token) {
Address address = token.getMinAddress();
if (address != null) {
@ -378,7 +394,7 @@ public class DecompilerUtils {
/**
* Find closest addressed token to a specified token or null if one is not found.
* Only adjacent tokens on the same line are examined.
* @param token
* @param token the query token
* @return closest addressed token
*/
private static ClangToken findClosestAddressedToken(ClangToken token) {

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.decompile;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import java.util.List;
@ -97,6 +97,20 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
assertCurrentAddress(addr(linkAddress));
}
@Test
public void testNewDecompileNavigatesToFunctionSignature() {
decompile("100000bf0"); // 'main'
int line = 5; // arbitrary value in view
int charPosition = 5; // arbitrary
setDecompilerLocation(line, charPosition);
decompile("100000d60"); // _call_structure_A()
line = 0; // function signature
charPosition = 0; // start of signature
assertCurrentLocation(line, charPosition);
}
//==================================================================================================
// Private Methods
//==================================================================================================
@ -141,6 +155,14 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
assertEquals("Line text not as expected at line " + line, expected, actual);
}
private void assertCurrentLocation(int line, int col) {
int oneBasedLine = line + 1;
DecompilerPanel panel = provider.getDecompilerPanel();
FieldLocation actual = panel.getCursorPosition();
FieldLocation expected = loc(oneBasedLine, col);
assertEquals("Decompiler cursor is not at the expected location", expected, actual);
}
private String getTokenText(FieldLocation loc) {
ClangTextField field = getFieldForLine(loc.getIndex().intValue());
ClangToken token = field.getToken(loc);