diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanel.java index 6b731398d3..92ff43db38 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanel.java @@ -476,6 +476,12 @@ public class ListingMergePanel extends JPanel return false; } + @Override + public boolean goToExternalLocation(Navigatable navigatable, ExternalLocation externalLoc, + boolean checkNavigationOption) { + return false; + } + @Override public GoToOverrideService getOverrideService() { return null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java index 04ba387ba1..7a0d13ab4d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java @@ -17,6 +17,8 @@ package ghidra.app.plugin.core.gotoquery; import java.util.Stack; +import org.apache.commons.lang3.StringUtils; + import docking.widgets.OptionDialog; import ghidra.app.cmd.refs.SetExternalNameCmd; import ghidra.app.nav.Navigatable; @@ -96,6 +98,9 @@ public class GoToHelper { } ExternalLocation externalLoc = program.getExternalManager().getExternalLocation(externalSym); + + // TODO - this seems like a mistake to always pass 'false' here; please doc why we + // wish to ignore the user options for when to navigate to external programs return goToExternalLinkage(navigatable, externalLoc, false); } @@ -248,7 +253,7 @@ public class GoToHelper { * external program association has not yet been established, the user will be prompted to make * an association if they choose before completing the navigation. * @param nav Navigatable - * @param externalLoc external location + * @param externalLocation external location * @param checkNavigationOption if true the {@link NavigationOptions#isGotoExternalProgramEnabled} * option will be used to determine if navigation to the external program will be * attempted, or if navigation to the external linkage location within the current @@ -257,32 +262,47 @@ public class GoToHelper { * @return true if navigation to the external program was successful or navigation to a * linkage location was performed. */ - public boolean goToExternalLocation(Navigatable nav, ExternalLocation externalLoc, + public boolean goToExternalLocation(Navigatable nav, ExternalLocation externalLocation, boolean checkNavigationOption) { if (checkNavigationOption && !navOptions.isGotoExternalProgramEnabled()) { - return goToExternalLinkage(nav, externalLoc, true); + return goToExternalLinkage(nav, externalLocation, true); } - Symbol externalSym = externalLoc.getSymbol(); + Program externalProgram = openExternalProgram(externalLocation); + if (externalProgram == null) { + return false; + } + + // try the address first if it exists + Address addr = externalLocation.getAddress(); + if (addr != null && externalProgram.getMemory().contains(addr)) { + goTo(nav, new AddressFieldLocation(externalProgram, addr), externalProgram); + return true; + } + + // then try the symbol + Symbol symbol = getExternalSymbol(externalProgram, externalLocation); + if (symbol != null) { + goTo(nav, symbol.getProgramLocation(), externalProgram); + return true; + } + + performDefaultExternalProgramNavigation(nav, externalLocation, externalProgram, addr); + return false; // return false because we did not go to the requested address + } + + private Program openExternalProgram(ExternalLocation externalLocation) { + + if (externalLocation == null) { + return null; + } + + Symbol externalSym = externalLocation.getSymbol(); Program program = externalSym.getProgram(); - - ExternalManager extMgr = program.getExternalManager(); - String extProgName = externalLoc.getLibraryName(); - if (Library.UNKNOWN.equals(extProgName)) { - tool.setStatusInfo(" External location refers to " + Library.UNKNOWN + - " library. Unable to " + "perform navigation.", true); - return false; - } - String pathName = extMgr.getExternalLibraryPath(extProgName); - if (pathName == null || pathName.length() == 0) { - createExternalAssociation(program, extProgName); - pathName = extMgr.getExternalLibraryPath(extProgName); - } - if (pathName == null || pathName.length() == 0) { - tool.setStatusInfo( - " External location is not resolved. Unable to " + "perform navigation.", true); - return false; + String pathName = getExternalLibraryPath(externalLocation, program); + if (pathName == null) { + return null; } ProjectData pd = tool.getProject().getProjectData(); @@ -290,36 +310,20 @@ public class GoToHelper { ProgramManager service = tool.getService(ProgramManager.class); if (domainFile == null || service == null) { tool.setStatusInfo("Unable to navigate to external location. " + - "Destination program [" + pathName + "] does not exist."); - return false; - } - Program externalProgram = service.openProgram(domainFile, -1, ProgramManager.OPEN_VISIBLE); - if (externalProgram == null) { - return false; + "Destination program [" + externalLocation + "] does not exist."); + return null; } - String label = externalLoc.getOriginalImportedName(); - if (label == null) { - label = externalLoc.getLabel(); - } - Address addr = externalLoc.getAddress(); + return service.openProgram(domainFile, -1, ProgramManager.OPEN_VISIBLE); + } - // try the address first if it exists. - if (addr != null && externalProgram.getMemory().contains(addr)) { - goTo(nav, new AddressFieldLocation(externalProgram, addr), externalProgram); - return true; - } + private void performDefaultExternalProgramNavigation(Navigatable nav, + ExternalLocation externalLocation, Program externalProgram, Address addr) { - Symbol symbol = getExternalSymbol(externalProgram, externalLoc); - if (symbol != null) { - goTo(nav, symbol.getProgramLocation(), externalProgram); - return true; - } - - // failed to navigate to address or symbol + // failed to navigate to address or symbol; alert the user if (addr != null) { - if (externalLoc.getSymbol().getSource() != SourceType.DEFAULT) { - tool.setStatusInfo("Symbol [" + getExternalName(externalLoc) + + if (externalLocation.getSymbol().getSource() != SourceType.DEFAULT) { + tool.setStatusInfo("Symbol [" + getExternalName(externalLocation) + "] does not exist, and address [" + addr + "] is not in memory.", true); } else { @@ -327,14 +331,39 @@ public class GoToHelper { } } else { - tool.setStatusInfo("Symbol [" + getExternalName(externalLoc) + "] does not exist.", + tool.setStatusInfo("Symbol [" + getExternalName(externalLocation) + "] does not exist.", true); } // navigate to top of external program - addr = externalProgram.getMinAddress(); - goTo(nav, new AddressFieldLocation(externalProgram, addr), externalProgram); - return false; // return false because we did not go to the requested address + AddressFieldLocation location = + new AddressFieldLocation(externalProgram, externalProgram.getMinAddress()); + goTo(nav, location, externalProgram); + } + + private String getExternalLibraryPath(ExternalLocation externalLocation, Program program) { + + ExternalManager externalManager = program.getExternalManager(); + String extProgName = externalLocation.getLibraryName(); + if (Library.UNKNOWN.equals(extProgName)) { + tool.setStatusInfo(" External location refers to " + Library.UNKNOWN + + " library. Unable to " + "perform navigation.", true); + return null; + } + + String pathName = externalManager.getExternalLibraryPath(extProgName); + if (StringUtils.isBlank(pathName)) { + createExternalAssociation(program, extProgName); + pathName = externalManager.getExternalLibraryPath(extProgName); + } + + if (StringUtils.isBlank(pathName)) { + tool.setStatusInfo(" External location is not resolved. Unable to perform navigation.", + true); + return null; + } + + return pathName; } private Stack getExternalNamespaceStack(ExternalLocation externalLoc) { @@ -375,9 +404,7 @@ public class GoToHelper { // assume global symbol name if mangled name (same as original name) exists String label = externalLoc.getOriginalImportedName(); - SymbolPath symbolPath; - if (label == null) { // use alternate symbol (may or may not be mangled) Symbol s = externalLoc.getSymbol(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/GoToService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/GoToService.java index 33ad9ac470..26fe2eb0d8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/GoToService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/GoToService.java @@ -105,7 +105,7 @@ public interface GoToService { /** * Navigate to either the external program location or address linkage location. * Specific behavior may vary based upon implementation. - * @param nav Navigatable + * @param externalLoc external location * @param checkNavigationOption if true the service navigation * option will be used to determine if navigation to the external program will be @@ -118,6 +118,23 @@ public interface GoToService { public boolean goToExternalLocation(ExternalLocation externalLoc, boolean checkNavigationOption); + /** + * Navigate to either the external program location or address linkage location. + * Specific behavior may vary based upon implementation. + * + * @param navigatable Navigatable + * @param externalLoc external location + * @param checkNavigationOption if true the service navigation + * option will be used to determine if navigation to the external program will be + * attempted, or if navigation to the external linkage location within the current + * program will be attempted. If false, the implementations default behavior + * will be performed. + * @return true if either navigation to the external program or to a + * linkage location was completed successfully. + */ + public boolean goToExternalLocation(Navigatable navigatable, ExternalLocation externalLoc, + boolean checkNavigationOption); + /** * Parses the input string as either: * an address/symbol expression (0x1000+5, or LAB1000+5) @@ -131,10 +148,10 @@ public interface GoToService { * * The listener will be notified after query and will indicate the query status. * - * @param queryInput this input string to parse. * @param fromAddr The address used to determine the scope of the query - * @param caseSensitive if true, the search must match case. + * @param queryData the query input data * @param listener the listener that will be notified when the query completes. + * @param monitor the task monitor * @return true if the queryInput is found or appears to be a wildcard search. */ public boolean goToQuery(Address fromAddr, QueryData queryData, GoToServiceListener listener, diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToServiceImpl.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToServiceImpl.java index 3674ae9e52..897002a98d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToServiceImpl.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToServiceImpl.java @@ -125,6 +125,12 @@ public class GoToServiceImpl implements GoToService { return helper.goToExternalLocation(defaultNavigatable, extLoc, checkNavigationOption); } + @Override + public boolean goToExternalLocation(Navigatable navigatable, ExternalLocation extLoc, + boolean checkNavigationOption) { + return helper.goToExternalLocation(navigatable, extLoc, checkNavigationOption); + } + @Override public boolean goToQuery(Navigatable navigatable, Address fromAddr, QueryData queryData, GoToServiceListener listener, TaskMonitor monitor) { @@ -133,9 +139,8 @@ public class GoToServiceImpl implements GoToService { navigatable = defaultNavigatable; } - GoToQuery query = - new GoToQuery(navigatable, plugin, this, queryData, fromAddr, listener, - helper.getOptions(), monitor); + GoToQuery query = new GoToQuery(navigatable, plugin, this, queryData, fromAddr, listener, + helper.getOptions(), monitor); return query.processQuery(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java index 7c931108dd..63851635bb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java @@ -401,8 +401,8 @@ abstract class OperandFieldHelper extends FieldFactory { for (int opIndex = 0; opIndex < numOperands; opIndex++) { OperandRepresentationList operandRepresentationList = codeUnitFormat.getOperandRepresentationList(inst, opIndex); - addElementsForOperand(inst, elements, opIndex, operandRepresentationList, - characterOffset); + characterOffset = addElementsForOperand(inst, elements, opIndex, + operandRepresentationList, characterOffset); characterOffset = 0; } @@ -504,8 +504,8 @@ abstract class OperandFieldHelper extends FieldFactory { } private boolean containsNonPrimary(Reference[] refs) { - for (Reference ref : refs) { - if (!ref.isPrimary()) { + for (int i = 0; i < refs.length; i++) { + if (!refs[i].isPrimary()) { return true; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldMouseHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldMouseHandler.java index f55011bdde..eeb14f5e1b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldMouseHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldMouseHandler.java @@ -46,10 +46,6 @@ public class OperandFieldMouseHandler implements FieldMouseHandlerExtension { private final static Class[] SUPPORTED_CLASSES = new Class[] { OperandFieldLocation.class }; - /** - * @see FieldMouseHandlerExtension#fieldElementClicked(Object, Navigatable, - * MouseEvent, ServiceProvider) - */ @Override public boolean fieldElementClicked(Object clickedObject, Navigatable navigatable, ProgramLocation location, MouseEvent mouseEvent, ServiceProvider serviceProvider) { @@ -101,9 +97,6 @@ public class OperandFieldMouseHandler implements FieldMouseHandlerExtension { return false; } - /** - * @see FieldMouseHandlerExtension#getSupportedProgramLocations() - */ @Override public Class[] getSupportedProgramLocations() { return SUPPORTED_CLASSES; @@ -130,19 +123,55 @@ public class OperandFieldMouseHandler implements FieldMouseHandlerExtension { private boolean checkExternalReference(Navigatable navigatable, CodeUnit codeUnit, OperandFieldLocation loc, GoToService goToService) { + Address refAddr = loc.getRefAddress(); if (refAddr == null || !refAddr.isExternalAddress()) { - return false; + return checkExternalThunkFunctionReference(navigatable, codeUnit, loc, goToService); } + Program program = codeUnit.getProgram(); Symbol s = program.getSymbolTable().getPrimarySymbol(refAddr); if (s == null) { return false; } + ExternalLocation extLoc = program.getExternalManager().getExternalLocation(s); return goToService.goToExternalLocation(extLoc, true); } + private boolean checkExternalThunkFunctionReference(Navigatable navigatable, CodeUnit codeUnit, + OperandFieldLocation loc, GoToService goToService) { + + Address refAddr = loc.getRefAddress(); + Program program = codeUnit.getProgram(); + Symbol s = program.getSymbolTable().getPrimarySymbol(refAddr); + if (s == null) { + return false; + } + + SymbolType type = s.getSymbolType(); + if (type != SymbolType.FUNCTION) { + return false; + } + + Function refFunction = (Function) s.getObject(); + Function thunked = refFunction.getThunkedFunction(true); + if (thunked == null) { + return false; + } + + if (thunked.getBody().contains(codeUnit.getAddress())) { + // this handles the unlikely case where the user double-clicks a reference to a + // local thunk label--don't navigate externally + return false; + } + + Symbol thunkedSymbol = thunked.getSymbol(); + ExternalLocation extLoc = program.getExternalManager().getExternalLocation(thunkedSymbol); + boolean success = goToService.goToExternalLocation(extLoc, true); + return success; + } + private boolean checkVariableReference(Navigatable navigatable, CodeUnit codeUnit, OperandFieldLocation loc, GoToService goToService) { @@ -150,40 +179,18 @@ public class OperandFieldMouseHandler implements FieldMouseHandlerExtension { return false; } - VariableOffset variableOffset = loc.getVariableOffset(); - if (variableOffset != null) { - Variable variable = variableOffset.getVariable(); - if (variable != null) { - goToService.goTo(navigatable, - new VariableNameFieldLocation(navigatable.getProgram(), variable, 0), - navigatable.getProgram()); - return true; - } + if (goToExplicitOperandVariable(navigatable, loc, goToService)) { + return true; } Program p = codeUnit.getProgram(); Address cuAddr = codeUnit.getMinAddress(); - Address refAddr = loc.getRefAddress(); - Function func = p.getFunctionManager().getFunctionContaining(cuAddr); - if (func == null) { + Function function = p.getFunctionManager().getFunctionContaining(cuAddr); + if (function == null) { return false; } - ProgramLocation varLoc = null; - if (refAddr != null && !refAddr.isStackAddress()) { - Register reg = p.getRegister(refAddr, 1); - if (reg != null) { - Variable variable = p.getFunctionManager().getReferencedVariable(cuAddr, refAddr, - reg.getMinimumByteSize(), - !isWrite((Instruction) codeUnit, loc.getOperandIndex(), reg)); - if (variable != null) { - varLoc = new VariableNameFieldLocation(navigatable.getProgram(), variable, 0); - } - } - } - - if (varLoc != null) { - goToService.goTo(navigatable, varLoc, navigatable.getProgram()); + if (goToRegisterVariable(navigatable, codeUnit, loc, goToService)) { return true; } @@ -195,20 +202,71 @@ public class OperandFieldMouseHandler implements FieldMouseHandlerExtension { Variable variable = p.getFunctionManager().getReferencedVariable(cuAddr, reference.getToAddress(), 0, reference.getReferenceType().isRead()); if (variable != null) { - varLoc = new VariableNameFieldLocation(navigatable.getProgram(), variable, 0); - goToService.goTo(navigatable, varLoc, navigatable.getProgram()); + ProgramLocation pl = + new VariableNameFieldLocation(navigatable.getProgram(), variable, 0); + goToService.goTo(navigatable, pl, navigatable.getProgram()); return true; } if (reference.isStackReference()) { - ProgramLocation fnLoc = new FunctionSignatureFieldLocation(p, func.getEntryPoint(), - null, 0, func.getPrototypeString(false, false)); - goToService.goTo(navigatable, fnLoc, navigatable.getProgram()); + ProgramLocation pl = new FunctionSignatureFieldLocation(p, function.getEntryPoint(), + null, 0, function.getPrototypeString(false, false)); + goToService.goTo(navigatable, pl, navigatable.getProgram()); return true; } return false; } + private boolean goToRegisterVariable(Navigatable navigatable, CodeUnit codeUnit, + OperandFieldLocation loc, GoToService goToService) { + + Address refAddr = loc.getRefAddress(); + Address cuAddr = codeUnit.getMinAddress(); + if (refAddr == null || refAddr.isStackAddress()) { + return false; + } + + Program p = codeUnit.getProgram(); + Register reg = p.getRegister(refAddr, 1); + if (reg == null) { + return false; + } + + Variable variable = p.getFunctionManager().getReferencedVariable(cuAddr, refAddr, + reg.getMinimumByteSize(), !isWrite((Instruction) codeUnit, loc.getOperandIndex(), reg)); + if (variable == null) { + return false; + } + + ProgramLocation pl = new VariableNameFieldLocation(navigatable.getProgram(), variable, 0); + goToService.goTo(navigatable, pl, navigatable.getProgram()); + return true; + } + + /** + * Navigate to the variable, when directly supplied by the field location + * + * @param navigatable the navigatable to which we should navigate + * @param loc the source location + * @param goToService the GoTo service + * @return true if we decide to attempt navigation + */ + private boolean goToExplicitOperandVariable(Navigatable navigatable, OperandFieldLocation loc, + GoToService goToService) { + + VariableOffset variableOffset = loc.getVariableOffset(); + if (variableOffset != null) { + Variable variable = variableOffset.getVariable(); + if (variable != null) { + goToService.goTo(navigatable, + new VariableNameFieldLocation(navigatable.getProgram(), variable, 0), + navigatable.getProgram()); + return true; + } + } + return false; + } + private boolean isWrite(Instruction inst, int operandIndex, Register reg) { for (Object obj : inst.getResultObjects()) { if (obj == reg) { @@ -220,12 +278,12 @@ public class OperandFieldMouseHandler implements FieldMouseHandlerExtension { private boolean checkMemRefs(Navigatable navigatable, CodeUnit codeUnit, OperandFieldLocation loc, ServiceProvider serviceProvider) { + Address refAddr = loc.getRefAddress(); if (refAddr == null || !refAddr.isMemoryAddress()) { return false; } - int opIndex = loc.getOperandIndex(); Reference[] refs = codeUnit.getOperandReferences(loc.getOperandIndex()); Address[] addrs = getAddressesForReferences(refs, codeUnit, serviceProvider); if (addrs.length == 0) { @@ -251,26 +309,8 @@ public class OperandFieldMouseHandler implements FieldMouseHandlerExtension { return true; } - Address gotoAddr = null; - if (addrs.length == 1) { - gotoAddr = addrs[0]; - } - else { - gotoAddr = codeUnit.getAddress(opIndex); - if (gotoAddr == null) { - Scalar scalar = codeUnit.getScalar(opIndex); - if (scalar != null) { - Address minAddress = codeUnit.getMinAddress(); - try { - gotoAddr = minAddress.getNewAddress(scalar.getUnsignedValue(), true); - } - catch (Exception e) { - // handled below by checking for null - } - } - } - } - + // 1 address found + Address gotoAddr = addrs[0]; if (gotoAddr == null) { return false; } @@ -294,27 +334,28 @@ public class OperandFieldMouseHandler implements FieldMouseHandlerExtension { ServiceProvider serviceProvider, boolean skipExternal) { Address address = reference.getToAddress(); RefType refType = reference.getReferenceType(); - if (refType.isIndirect()) { - Program program = codeUnit.getProgram(); - Data data = program.getListing().getDefinedDataAt(address); - Address indirectAddrress = null; - if (data != null) { - if (data.isPointer()) { - Reference ref = data.getPrimaryReference(0); - indirectAddrress = ref != null ? ref.getToAddress() : (Address) data.getValue(); - } - } - else { - PseudoDisassembler pdis = new PseudoDisassembler(program); - indirectAddrress = pdis.getIndirectAddr(address); - } - if (indirectAddrress != null && - (!indirectAddrress.isExternalAddress() || !skipExternal) && - followIndirectReference(serviceProvider, indirectAddrress, program)) { - return indirectAddrress; - } + if (!refType.isIndirect()) { + return address; } + Program program = codeUnit.getProgram(); + Data data = program.getListing().getDefinedDataAt(address); + Address indirectAddrress = null; + if (data != null) { + if (data.isPointer()) { + Reference ref = data.getPrimaryReference(0); + indirectAddrress = ref != null ? ref.getToAddress() : (Address) data.getValue(); + } + } + else { + PseudoDisassembler pdis = new PseudoDisassembler(program); + indirectAddrress = pdis.getIndirectAddr(address); + } + + if (indirectAddrress != null && (!indirectAddrress.isExternalAddress() || !skipExternal) && + followIndirectReference(serviceProvider, indirectAddrress, program)) { + return indirectAddrress; + } return address; } @@ -324,6 +365,7 @@ public class OperandFieldMouseHandler implements FieldMouseHandlerExtension { if (optionsService == null) { return false; } + NavigationOptions navOptions = new NavigationOptions(optionsService); try { if (!navOptions.isFollowIndirectionEnabled()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingGoToService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingGoToService.java index b51f1f3439..de2aa3b298 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingGoToService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingGoToService.java @@ -73,7 +73,7 @@ class DualListingGoToService implements GoToService { @Override public boolean goTo(Navigatable navigatable, ProgramLocation loc, Program program) { - return dualGoTo(loc); + return dualGoTo(loc); } @Override @@ -95,8 +95,8 @@ class DualListingGoToService implements GoToService { AddressSetView addresses = isLeftSide ? dualListing.getLeftAddresses() : dualListing.getRightAddresses(); if (!addresses.contains(addr)) { - dualListing.setStatusInfo("\"" + addr.toString() + - "\" is outside the current listing's view."); + dualListing.setStatusInfo( + "\"" + addr.toString() + "\" is outside the current listing's view."); return false; } return true; @@ -150,6 +150,13 @@ class DualListingGoToService implements GoToService { "Connot Go To an external address from a dual listing view."); } + @Override + public boolean goToExternalLocation(Navigatable navigatable, ExternalLocation extLoc, + boolean checkNavigationOption) { + throw new UnsupportedOperationException( + "Connot Go To an external address from a dual listing view."); + } + @Override public boolean goToQuery(Address fromAddr, QueryData queryData, GoToServiceListener listener, TaskMonitor monitor) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java index ca14dfa747..fee2ae523a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java @@ -519,10 +519,8 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc /** * Moves the cursor to the given program location and repositions the scrollbar to - * show that location in the screen - * - * @param loc the location to move to - * @return true if the 'go to' was successful + * show that location in the screen. + * @param loc the location to move to. */ public boolean goTo(ProgramLocation loc) { return goTo(loc, true); @@ -538,7 +536,6 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc * the given location will be placed in the center of the screen; * when the parameter is false, then the screen will be scrolled * only enough to show the cursor. - * @return true if the 'go to' was successful */ public boolean goTo(ProgramLocation loc, boolean centerWhenNotVisible) { final FieldLocation floc = getFieldLocation(loc); @@ -558,27 +555,15 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc return true; } - /** - * Scroll the view of the listing to the given location - * @param location the location - */ + /** Scroll the view of the listing to the given location. */ public void scrollTo(ProgramLocation location) { FieldLocation fieldLocation = getFieldLocation(location); - if (fieldLocation == null) { - return; // this can happen when using restricted views - } fieldPanel.scrollTo(fieldLocation); } - /** - * Center the view of the listing around the given location - * @param location the location - */ + /** Center the view of the listing around the given location. */ public void center(ProgramLocation location) { FieldLocation fieldLocation = getFieldLocation(location); - if (fieldLocation == null) { - return; // this can happen when using restricted views - } fieldPanel.center(fieldLocation); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java index ea643769bd..a091bb3d66 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java @@ -17,8 +17,6 @@ package ghidra.program.model.listing; import java.util.*; -import org.apache.commons.lang3.StringUtils; - import ghidra.app.util.NamespaceUtils; import ghidra.app.util.viewer.field.CommentUtils; import ghidra.program.model.address.*; @@ -38,8 +36,6 @@ import ghidra.util.MathUtilities; public class CodeUnitFormat { - private static final String EMPTY = ""; - protected static final String PLUS = "+"; protected static final String UNDERSCORE = "_"; @@ -116,39 +112,37 @@ public class CodeUnitFormat { */ public String getRepresentationString(CodeUnit cu, boolean includeEOLcomment) { - StringBuilder buffy = new StringBuilder(getMnemonicRepresentation(cu)); + StringBuffer stringBuffer = new StringBuffer(getMnemonicRepresentation(cu)); if (cu instanceof Instruction) { Instruction instr = (Instruction) cu; int n = instr.getNumOperands(); - if (n > 1) { - buffy.append(' '); - } - for (int i = 0; i < n; i++) { - String sep = instr.getSeparator(i); - buffy.append(StringUtils.isBlank(sep) ? EMPTY : sep); - buffy.append(getOperandRepresentationString(cu, i)); + if (i == 0) { + stringBuffer.append(" "); + } + else { + String separator = instr.getSeparator(i); + if (separator != null && separator.length() != 0) { + stringBuffer.append(separator); + } + } + stringBuffer.append(getOperandRepresentationString(cu, i)); } - - // grab any trailing separator at n (index + 1; see Instruction.getSeparator()) - String sep = instr.getSeparator(n); - buffy.append(StringUtils.isBlank(sep) ? EMPTY : sep); } else { // data always has one operand - buffy.append(' '); - buffy.append(getOperandRepresentationString(cu, 0)); + stringBuffer.append(" "); + stringBuffer.append(getOperandRepresentationString(cu, 0)); } - if (includeEOLcomment) { String eolComment = cu.getComment(CodeUnit.EOL_COMMENT); if (eolComment != null) { // fixup annotations eolComment = CommentUtils.getDisplayString(eolComment, cu.getProgram()); - buffy.append(" // "); - buffy.append(eolComment); + stringBuffer.append(" // "); + stringBuffer.append(eolComment); } } - return buffy.toString(); + return stringBuffer.toString(); } /** @@ -158,19 +152,19 @@ public class CodeUnitFormat { * @return mnemonic representation */ public String getMnemonicRepresentation(CodeUnit cu) { - StringBuilder buffy = new StringBuilder(); + StringBuffer stringBuffer = new StringBuffer(); String mnemonic = cu.getMnemonicString(); if (options.showDataMutability && (cu instanceof Data) && mnemonic != null) { Data d = (Data) cu; if (d.isConstant()) { - buffy.append("const "); + stringBuffer.append("const "); } else if (d.isVolatile()) { - buffy.append("volatile "); + stringBuffer.append("volatile "); } } - buffy.append(mnemonic); - return buffy.toString(); + stringBuffer.append(mnemonic); + return stringBuffer.toString(); } /** @@ -279,7 +273,7 @@ public class CodeUnitFormat { * Perform register markup with explicit and implied register variable * reference. * - * @param instr instruction + * @param inst instruction * @param opIndex * @param func function containing instruction * @param primaryRef primary reference or null @@ -452,17 +446,18 @@ public class CodeUnitFormat { * @param opIndex operand index * @param func function containing instruction * @param primaryRef primary reference - * @param representationList the representation objects + * @param referencedVariable optional variable associated with reference + * @param regIndexMap register index map + * @param representationList * @return true if primaryRef was included in scalar mark-up */ private boolean performAddressMarkup(Instruction instr, int opIndex, Function func, Reference primaryRef, List representationList) { - if (primaryRef == null || !primaryRef.isMemoryReference()) { return false; } - Address refAddr = primaryRef.getToAddress(); + int size = representationList.size(); for (int i = 0; i < size; i++) { Object obj = representationList.get(i); @@ -1138,9 +1133,13 @@ public class CodeUnitFormat { * Get a representation object corresponding to the specified reference. * Format options are considered when generating label. * - * @param cu the code unit - * @param ref the reference + * @param cu + * @param ref * @param var variable which corresponds to reference or null + * @param showIndirectValue if true, indirect memory references which refer + * to a pointer will get an additional "=value" appended where + * value corresponds to data pointed to by the referenced + * pointer. * @return reference representation object */ private Object getReferenceRepresentation(CodeUnit cu, Reference ref, Variable var) { @@ -1418,6 +1417,9 @@ public class CodeUnitFormat { return symbol.getName(); } + /** + * Returns ShowBlockName setting + */ public ShowBlockName getShowBlockName() { return options.showBlockName; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserNavigationTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserNavigationTest.java index ab40fb65a7..71b3f10de7 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserNavigationTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserNavigationTest.java @@ -45,6 +45,7 @@ public class AbstractCodeBrowserNavigationTest extends AbstractGhidraHeadedInteg private DockingActionIf nextFunction; private DockingActionIf prevFunction; + protected ToyProgramBuilder builder; protected Program program; protected CodeBrowserPlugin cb; @@ -83,7 +84,7 @@ public class AbstractCodeBrowserNavigationTest extends AbstractGhidraHeadedInteg } private Program buildProgram() throws Exception { - ToyProgramBuilder builder = new ToyProgramBuilder("Test", true, this); + builder = new ToyProgramBuilder("Test", true, this); builder.createMemory(".text", "0x1001000", 0x6600); builder.createMemory(".data", "0x1008000", 0x600); builder.createMemory(".rsrc", "0x100a000", 0x5400); @@ -98,7 +99,6 @@ public class AbstractCodeBrowserNavigationTest extends AbstractGhidraHeadedInteg builder.applyDataType("0x1001000", PointerDataType.dataType, 1); builder.createExternalReference("0x1001000", "ADVAPI32.dll", "IsTextUnicode", 0); // linkage location - builder.createExternalReference("0x1001020", "ADVAPI32.dll", "IsTextUnicode", 0); // without pointer builder.addBytesBranch("1004000", "1004010"); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/ExternalCodeBrowserNavigationTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/ExternalCodeBrowserNavigationTest.java index 353827f24f..7d2d49c558 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/ExternalCodeBrowserNavigationTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/ExternalCodeBrowserNavigationTest.java @@ -63,8 +63,22 @@ public class ExternalCodeBrowserNavigationTest extends AbstractCodeBrowserNaviga int txId = program.startTransaction("Set Path"); program.getExternalManager().setExternalPath("ADVAPI32.dll", "/FILE1", true); - program.endTransaction(txId, true); + // + // Create a call reference to a thunk function + // Create a thunk function to an external location + // + String arbitraryAddress = "0x1001000"; + ExternalLocation externalLocation = builder.createExternalFunction(arbitraryAddress, + "ADVAPI32.dll", "externalFunctionXyz", "_Zxyz"); + String thunkAddress = "0x1006300"; + Function thunk = builder.createFunction(thunkAddress); + thunk.setThunkedFunction(externalLocation.getFunction()); + + builder.createMemoryCallReference("0x1001030", thunkAddress); + + program.endTransaction(txId, true); + program.flushEvents(); waitForSwing(); } @@ -180,17 +194,35 @@ public class ExternalCodeBrowserNavigationTest extends AbstractCodeBrowserNaviga cb.goTo(new OperandFieldLocation(program, addr("1001020"), null, null, null, 0, 0)); assertEquals(addr("1001020"), cb.getCurrentAddress()); - assertEquals("FILE2", program.getDomainFile().getName()); // verify that navigation to the external program, address 0x1001888, is performed // since navigation initiated from linkage location click(cb, 2); assertEquals(addr("1001888"), cb.getCurrentAddress()); - assertEquals("FILE1", lastNavigationProgram.getDomainFile().getName()); assertEquals(addr("1001888"), lastNavigationLocation.getAddress()); + } + @Test + public void testOperandExternalProgramNavigation_OnThunk() throws Exception { + + getTool().getOptions("Navigation").setEnum("External Navigation", + NavigationOptions.ExternalNavigationEnum.NavigateToExternalProgram); + + String fromAddress = "1001030"; + cb.goTo(new OperandFieldLocation(program, addr(fromAddress), null, null, null, 0, 0)); + assertEquals(addr(fromAddress), cb.getCurrentAddress()); + assertEquals("FILE2", program.getDomainFile().getName()); + + // verify that navigation to the external program, address 0x1001888, is performed + // since navigation initiated from linkage location + click(cb, 2); + + String otherAddress = "0x01001000"; + assertEquals(addr(otherAddress), cb.getCurrentAddress()); + assertEquals("FILE1", lastNavigationProgram.getDomainFile().getName()); + assertEquals(addr(otherAddress), lastNavigationLocation.getAddress()); } /** @@ -270,6 +302,7 @@ public class ExternalCodeBrowserNavigationTest extends AbstractCodeBrowserNaviga @Mock public boolean goTo(Invocation inv, final Navigatable navigatable, ProgramLocation loc, Program p) { + // Track last navigation location lastNavigationLocation = loc; lastNavigationProgram = p; diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDummyGoToService.java b/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDummyGoToService.java index 8102aa9f4c..d82a4e61c2 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDummyGoToService.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDummyGoToService.java @@ -84,6 +84,13 @@ public class TestDummyGoToService implements GoToService { return false; } + @Override + public boolean goToExternalLocation(Navigatable navigatable, ExternalLocation externalLoc, + boolean checkNavigationOption) { + // stub + return false; + } + @Override public boolean goToQuery(Address fromAddr, QueryData queryData, GoToServiceListener listener, TaskMonitor monitor) { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/CDisplayPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/CDisplayPanel.java index ae1eb471c5..d9f9be66de 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/CDisplayPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/CDisplayPanel.java @@ -101,20 +101,17 @@ public class CDisplayPanel extends JPanel implements DecompilerCallbackHandler { @Override public void contextChanged() { - // TODO Auto-generated method stub - + // stub } @Override public void decompileDataChanged(DecompileData decompileData) { - // TODO Auto-generated method stub - + // stub } @Override public void exportLocation() { - // TODO Auto-generated method stub - + // stub } @Override @@ -124,20 +121,22 @@ public class CDisplayPanel extends JPanel implements DecompilerCallbackHandler { @Override public void goToAddress(Address addr, boolean newWindow) { - // TODO Auto-generated method stub - + // stub } @Override public void goToLabel(String labelName, boolean newWindow) { - // TODO Auto-generated method stub - + // stub } @Override public void goToScalar(long value, boolean newWindow) { - // TODO Auto-generated method stub + // stub + } + @Override + public void goToFunction(Function function, boolean newWindow) { + // stub } @Override @@ -150,14 +149,12 @@ public class CDisplayPanel extends JPanel implements DecompilerCallbackHandler { @Override public void selectionChanged(ProgramSelection programSelection) { - // TODO Auto-generated method stub - + // stub } @Override public void setStatusMessage(String message) { - // TODO Auto-generated method stub - + // stub } public void clearAndShowMessage(String message) { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCallbackHandler.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCallbackHandler.java index f451f44851..9432f5b8fe 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCallbackHandler.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCallbackHandler.java @@ -16,6 +16,7 @@ package ghidra.app.decompiler.component; import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.util.bean.field.AnnotatedTextFieldElement; @@ -42,4 +43,5 @@ public interface DecompilerCallbackHandler { void exportLocation(); + void goToFunction(Function function, boolean newWindow); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java index 1124f6e83f..60acda7434 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java @@ -248,14 +248,11 @@ public class DecompilerController { } void goToFunction(Function function, boolean newWindow) { - while (function.isThunk()) { - Function thunkedFunction = function.getThunkedFunction(false); - if (thunkedFunction == null || thunkedFunction.isExternal()) { - break; - } + Function thunkedFunction = function.getThunkedFunction(true); + if (thunkedFunction != null) { function = thunkedFunction; } - goToAddress(function.getEntryPoint(), newWindow); + callbackHandler.goToFunction(function, newWindow); } void goToLabel(String labelName, boolean newWindow) { 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 9a08c05bb1..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,6 +24,7 @@ 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 { @@ -314,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); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java index 4c3e49a758..8f048c2fb9 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java @@ -43,10 +43,11 @@ import ghidra.framework.plugintool.util.ServiceListener; import ghidra.program.model.address.*; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; -import ghidra.program.model.symbol.SymbolIterator; +import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; +import ghidra.util.Swing; import ghidra.util.bean.field.AnnotatedTextFieldElement; import ghidra.util.task.SwingUpdateManager; import resources.Icons; @@ -209,16 +210,25 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter @Override public boolean goTo(Program gotoProgram, ProgramLocation location) { - if (gotoProgram != program) { - if (!isConnected()) { + + if (!isConnected()) { + if (program == null) { + // Special Case: this 'disconnected' provider is waiting to be initialized + // with the first goTo() callback + doSetProgram(gotoProgram); + } + else if (gotoProgram != program) { + // this disconnected provider only works with its given program tool.setStatusInfo("Program location not applicable for this provider!"); return false; } - ProgramManager programManagerService = tool.getService(ProgramManager.class); - if (programManagerService != null) { - programManagerService.setCurrentProgram(gotoProgram); - } } + + ProgramManager programManagerService = tool.getService(ProgramManager.class); + if (programManagerService != null) { + programManagerService.setCurrentProgram(gotoProgram); + } + setLocation(location, null); pendingViewerPosition = null; plugin.locationChanged(this, location); @@ -451,13 +461,19 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter plugin.locationChanged(this, programLocation); } + @Override + public void selectionChanged(ProgramSelection programSelection) { + currentSelection = programSelection; + contextChanged(); + plugin.selectionChanged(this, programSelection); + } + @Override public void annotationClicked(AnnotatedTextFieldElement annotation, boolean newWindow) { Navigatable navigatable = this; if (newWindow) { DecompilerProvider newProvider = plugin.createNewDisconnectedProvider(); - newProvider.doSetProgram(program); navigatable = newProvider; } @@ -466,6 +482,12 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter @Override public void goToLabel(String symbolName, boolean newWindow) { + + GoToService service = tool.getService(GoToService.class); + if (service == null) { + return; + } + SymbolIterator symbolIterator = program.getSymbolTable().getSymbols(symbolName); if (!symbolIterator.hasNext()) { tool.setStatusInfo(symbolName + " not found."); @@ -475,19 +497,21 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter Navigatable navigatable = this; if (newWindow) { DecompilerProvider newProvider = plugin.createNewDisconnectedProvider(); - newProvider.doSetProgram(program); navigatable = newProvider; } - GoToService service = tool.getService(GoToService.class); - if (service != null) { - QueryData queryData = new QueryData(symbolName, true); - service.goToQuery(navigatable, null, queryData, null, null); - } + QueryData queryData = new QueryData(symbolName, true); + service.goToQuery(navigatable, null, queryData, null, null); } @Override public void goToScalar(long value, boolean newWindow) { + + GoToService service = tool.getService(GoToService.class); + if (service == null) { + return; + } + try { // try space/overlay which contains function AddressSpace space = controller.getFunction().getEntryPoint().getAddressSpace(); @@ -510,33 +534,51 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter @Override public void goToAddress(Address address, boolean newWindow) { + + GoToService service = tool.getService(GoToService.class); + if (service == null) { + return; + } + Navigatable navigatable = this; if (newWindow) { DecompilerProvider newProvider = plugin.createNewDisconnectedProvider(); - newProvider.doSetProgram(program); navigatable = newProvider; } - GoToService service = tool.getService(GoToService.class); - if (service != null) { - service.goTo(navigatable, new ProgramLocation(program, address), program); - } + service.goTo(navigatable, new ProgramLocation(program, address), program); } @Override - public void selectionChanged(ProgramSelection programSelection) { - currentSelection = programSelection; - contextChanged(); - plugin.selectionChanged(this, programSelection); + public void goToFunction(Function function, boolean newWindow) { + + GoToService service = tool.getService(GoToService.class); + if (service == null) { + return; + } + + Navigatable navigatable = this; + if (newWindow) { + DecompilerProvider newProvider = plugin.createNewDisconnectedProvider(); + navigatable = newProvider; + } + + Symbol symbol = function.getSymbol(); + ExternalManager externalManager = program.getExternalManager(); + ExternalLocation externalLocation = externalManager.getExternalLocation(symbol); + if (externalLocation != null) { + service.goToExternalLocation(navigatable, externalLocation, true); + } + else { + Address address = function.getEntryPoint(); + service.goTo(navigatable, new ProgramLocation(program, address), program); + } } //================================================================================================== // methods called from other members //================================================================================================== - /** - * Called by the DecompilerClipboardProvider to get the Decompiler Panel - */ DecompilerPanel getDecompilerPanel() { return controller.getDecompilerPanel(); } @@ -545,7 +587,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter final DecompilerProvider newProvider = plugin.createNewDisconnectedProvider(); // invoke later to give the window manage a chance to create the new window // (its done in an invoke later) - SwingUtilities.invokeLater(() -> { + Swing.runLater(() -> { newProvider.doSetProgram(program); newProvider.controller.setDecompileData(controller.getDecompileData()); newProvider.setLocation(currentLocation, diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerTest.java index 89d4e6ea28..3804323d3e 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerTest.java @@ -15,10 +15,18 @@ */ package ghidra.app.plugin.core.decompile; +import static org.junit.Assert.assertEquals; + +import java.util.List; + import org.junit.After; import org.junit.Before; import docking.widgets.fieldpanel.FieldPanel; +import docking.widgets.fieldpanel.field.Field; +import docking.widgets.fieldpanel.support.FieldLocation; +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.component.ClangTextField; import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.test.AbstractProgramBasedTest; @@ -67,11 +75,43 @@ public abstract class AbstractDecompilerTest extends AbstractProgramBasedTest { protected void setDecompilerLocation(int line, int charPosition) { runSwing(() -> provider.setCursorLocation(line, charPosition)); + + DecompilerPanel panel = provider.getDecompilerPanel(); + FieldPanel fp = panel.getFieldPanel(); + click(fp, 1, true); } protected void doubleClick() { DecompilerPanel panel = provider.getDecompilerPanel(); FieldPanel fp = panel.getFieldPanel(); click(fp, 2, true); + waitForSwing(); + } + + protected FieldLocation loc(int lineNumber, int col) { + FieldLocation loc = new FieldLocation(lineNumber, 0, 0, col); + return loc; + } + + protected ClangTextField getFieldForLine(int lineNumber) { + + DecompilerPanel panel = provider.getDecompilerPanel(); + List fields = panel.getFields(); + Field line = fields.get(lineNumber - 1); // 0-based + return (ClangTextField) line; + } + + protected String getTokenText(FieldLocation loc) { + ClangTextField field = getFieldForLine(loc.getIndex().intValue()); + ClangToken token = field.getToken(loc); + return token.getText(); + } + + protected void assertToken(String tokenText, int line, int... cols) { + for (int col : cols) { + FieldLocation loc = loc(line, col); + String text = getTokenText(loc); + assertEquals(tokenText, text); + } } } 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 0fd0570a28..6eee08b88a 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,18 +15,16 @@ */ package ghidra.app.plugin.core.decompile; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; -import java.util.List; import java.util.Set; import org.junit.Test; import docking.action.DockingActionIf; -import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.support.FieldLocation; import ghidra.app.cmd.comments.SetCommentCmd; -import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.component.ClangTextField; import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.program.model.listing.CodeUnit; @@ -196,14 +194,6 @@ public class DecompilerClangTest extends AbstractDecompilerTest { return field; } - private void assertToken(String tokenText, int line, int... cols) { - for (int col : cols) { - FieldLocation loc = loc(line, col); - String text = getTokenText(loc); - assertEquals(tokenText, text); - } - } - private void assertDisplayText(String expected, int line) { FieldLocation loc = loc(line, 0 /*column*/); ClangTextField field = getFieldForLine(loc.getIndex().intValue()); @@ -219,12 +209,6 @@ public class DecompilerClangTest extends AbstractDecompilerTest { 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); - return token.getText(); - } - private int getTokenIndex(ClangTextField field, FieldLocation loc) { Integer index = (Integer) invokeInstanceMethod("getTokenIndex", field, new Class[] { FieldLocation.class }, new Object[] { loc }); @@ -237,16 +221,4 @@ public class DecompilerClangTest extends AbstractDecompilerTest { return index; } - private FieldLocation loc(int lineNumber, int col) { - FieldLocation loc = new FieldLocation(lineNumber, 0, 0, col); - return loc; - } - - private ClangTextField getFieldForLine(int lineNumber) { - - DecompilerPanel panel = provider.getDecompilerPanel(); - List fields = panel.getFields(); - Field line = fields.get(lineNumber - 1); // 0-based - return (ClangTextField) line; - } } diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerNavigationTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerNavigationTest.java index f8647cc293..2deaea5069 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerNavigationTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerNavigationTest.java @@ -15,22 +15,28 @@ */ package ghidra.app.plugin.core.decompile; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; +import ghidra.app.cmd.function.CreateFunctionCmd; +import ghidra.app.nav.Navigatable; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; -import ghidra.program.model.listing.Program; -import ghidra.program.model.symbol.RefType; -import ghidra.program.model.symbol.SourceType; +import ghidra.app.plugin.core.gotoquery.GoToHelper; +import ghidra.app.plugin.core.navigation.NavigationOptions; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; import ghidra.program.util.OperandFieldLocation; import ghidra.program.util.ProgramLocation; import ghidra.test.ClassicSampleX86ProgramBuilder; +import mockit.*; public class DecompilerNavigationTest extends AbstractDecompilerTest { + private boolean goToExternalLinkageCalled; + @Before @Override public void setUp() throws Exception { @@ -80,4 +86,128 @@ public class DecompilerNavigationTest extends AbstractDecompilerTest { assertTrue(currentLocation instanceof OperandFieldLocation); assertEquals(operandLocation.getAddress(), currentLocation.getAddress()); } + + @Test + public void testFunctionNavigation_ExternalProgramFunction_OptionNavigateToExternal() + throws Exception { + + // this call triggers jMockit to load our spy + new SpyGoToHelper(); + + tool.getOptions("Navigation").setEnum("External Navigation", + NavigationOptions.ExternalNavigationEnum.NavigateToExternalProgram); + + // + // Take an existing function with a call reference and change it to call a thunk with + // an external program reference. + // + + /* + 01005a32 e8 be d2 CALL ghidra + ff ff + */ + + String thunkAddress = "1002cf5"; // function 'ghidra' + createThunkToExternal(thunkAddress); + + decompile("10059a3"); // function that calls 'ghidra' + + int line = 35; + int character = 1; + assertToken("ghidra", line, character); + setDecompilerLocation(line, character); + doubleClick(); + + assertExternalNavigationPerformed(); + assertNotEquals(thunkAddress, codeBrowser.getCurrentAddress()); + } + + @Test + public void testFunctionNavigation_ExternalProgramFunction_OptionNavigateToLinkage() + throws Exception { + + // this call triggers jMockit to load our spy + new SpyGoToHelper(); + + tool.getOptions("Navigation").setEnum("External Navigation", + NavigationOptions.ExternalNavigationEnum.NavigateToLinkage); + + // + // Take an existing function with a call reference and change it to call a thunk with + // an external program reference. + // + + /* + 01005a32 e8 be d2 CALL ghidra + ff ff + */ + + String thunkAddress = "1002cf5"; // function 'ghidra' + createThunkToExternal(thunkAddress); + + decompile("10059a3"); // function that calls 'ghidra' + + int line = 35; + int character = 1; + assertToken("ghidra", line, character); + setDecompilerLocation(line, character); + doubleClick(); + + assertExternalNavigationNotPerformed(); + assertEquals(addr(thunkAddress), codeBrowser.getCurrentAddress()); + } + + private void assertExternalNavigationPerformed() { + // going to the 'external linkage' means we went to the thunk function and not the + // external program + assertFalse("External navigation did not take place", goToExternalLinkageCalled); + } + + private void assertExternalNavigationNotPerformed() { + // going to the 'external linkage' means we went to the thunk function and not the + // external program + assertTrue("External navigation should not have taken place", goToExternalLinkageCalled); + } + + private void createThunkToExternal(String addressString) throws Exception { + + int txId = program.startTransaction("Set External Location"); + try { + + program.getExternalManager().setExternalPath("ADVAPI32.dll", "/FILE1", true); + + Address address = addr(addressString); + CreateFunctionCmd cmd = new CreateFunctionCmd(address); + cmd.applyTo(program); + + String extAddress = "0x1001000"; + ExternalManager em = program.getExternalManager(); + + // "ADVAPI32.dll", "externalFunctionXyz", "_Zxyz" + ExternalLocation externalLocation = + em.addExtFunction(Library.UNKNOWN, "_Zxyz", addr(extAddress), SourceType.IMPORTED); + Library lib = em.addExternalLibraryName("ADVAPI32.dll", SourceType.IMPORTED); + externalLocation.setName(lib, "externalFunctionXyz", SourceType.IMPORTED); + + Function function = program.getFunctionManager().getFunctionAt(addr(addressString)); + function.setThunkedFunction(externalLocation.getFunction()); + } + finally { + program.endTransaction(txId, true); + } + + program.flushEvents(); + waitForSwing(); + } + + public class SpyGoToHelper extends MockUp { + + @Mock + private boolean goToExternalLinkage(Invocation invocation, Navigatable nav, + ExternalLocation externalLoc, boolean popupAllowed) { + + goToExternalLinkageCalled = true; + return invocation.proceed(nav, externalLoc, popupAllowed); + } + } } diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffGoToService.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffGoToService.java index 0800eea13c..6809619030 100644 --- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffGoToService.java +++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffGoToService.java @@ -139,6 +139,13 @@ public class DiffGoToService implements GoToService { return false; // Can only go to locations in the Diff's second program. } + @Override + public boolean goToExternalLocation(Navigatable navigatable, ExternalLocation extLoc, + boolean checkNavigationOption) { + showProgramFailureStatus(); + return false; // Can only go to locations in the Diff's second program. + } + @Override public boolean goToQuery(Address fromAddr, QueryData queryData, GoToServiceListener listener, TaskMonitor monitor) {