From e9540fa5c1eeda6f4cad5e150cb8d951acd94761 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Thu, 6 Jun 2019 11:05:39 -0400 Subject: [PATCH] GT-2882 - Decompiler - Updated to set the cursor to the function signature when a new function is loaded --- .../decompiler/component/DecompilerPanel.java | 79 +++++++++++-- .../decompiler/component/DecompilerUtils.java | 104 ++++++++++-------- .../core/decompile/DecompilerClangTest.java | 24 +++- 3 files changed, 150 insertions(+), 57 deletions(-) diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java index 0cfedee1df..32d3c06223 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java @@ -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 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 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 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 functionLines) { + for (ClangLine line : functionLines) { + List 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 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 = diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java index 461f49fa8a..42e038a273 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java @@ -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 getForwardSlice(Varnode seed) { - HashSet varnodes = new HashSet<>(); - ArrayList worklist = new ArrayList<>(); + Set varnodes = new HashSet<>(); + List worklist = new ArrayList<>(); worklist.add(seed); for (int i = 0; i < worklist.size(); i++) { @@ -97,8 +102,8 @@ public class DecompilerUtils { } public static Set getBackwardSlice(Varnode seed) { - HashSet varnodes = new HashSet<>(); - ArrayList worklist = new ArrayList<>(); + Set varnodes = new HashSet<>(); + List 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 queryTokens, Field[] fields) { + if (queryTokens.isEmpty()) { + return -1; + } + for (int i = 0; i < fields.length; i++) { ClangTextField f = (ClangTextField) fields[i]; List 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 getTokensFromView(Field[] fields, Address address) { + + AddressSetView set = new AddressSet(address); + List result = new ArrayList<>(); + for (Field f : fields) { + ClangTextField tf = (ClangTextField) f; + List 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 getTokens(ClangNode root, Address address) { - List tokenList = new ArrayList<>(); - collectTokens(tokenList, root, address); - return tokenList; - } - - private static void collectTokens(List 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 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) { diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerClangTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerClangTest.java index bd9f6b9e96..70b5682f98 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerClangTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerClangTest.java @@ -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);