diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateFunctionCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateFunctionCmd.java index 4d2dd15f2d..c395312bd5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateFunctionCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateFunctionCmd.java @@ -637,7 +637,7 @@ public class CreateFunctionCmd extends BackgroundCommand { FlowType[] dontFollow = { RefType.COMPUTED_CALL, RefType.CONDITIONAL_CALL, RefType.UNCONDITIONAL_CALL, RefType.INDIRECTION }; AddressSet start = new AddressSet(entry, entry); - FollowFlow flow = new FollowFlow(program, start, dontFollow, includeOtherFunctions); + FollowFlow flow = new FollowFlow(program, start, dontFollow, includeOtherFunctions, false); return flow.getFlowAddressSet(monitor); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/flow/AbstractFollowFlowTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/flow/AbstractFollowFlowTest.java index acc578a3c2..2d756a98a0 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/flow/AbstractFollowFlowTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/flow/AbstractFollowFlowTest.java @@ -74,6 +74,19 @@ public abstract class AbstractFollowFlowTest extends AbstractGhidraHeadedIntegra return followFlow.getFlowAddressSet(TaskMonitor.DUMMY); } + AddressSetView getFlowsFrom(int startAddressOffset, FlowType[] excludedFlows, boolean includeFunctions, boolean includeData) { + return getFlowsFrom(addr(startAddressOffset), excludedFlows, includeFunctions, includeData); + } + + AddressSetView getFlowsFrom(Address startAddress, FlowType[] excludedFlows, boolean includeFunctions, boolean includeData) { + return getFlowsFrom(new AddressSet(startAddress), excludedFlows, includeFunctions, includeData); + } + + AddressSetView getFlowsFrom(AddressSet startSet, FlowType[] excludedFlows, boolean includeFunctions, boolean includeData) { + FollowFlow followFlow = new FollowFlow(program, startSet, excludedFlows, includeFunctions, includeData); + return followFlow.getFlowAddressSet(TaskMonitor.DUMMY); + } + AddressSetView getFlowsTo(int startAddressOffset, FlowType[] excludedFlows) { return getFlowsTo(addr(startAddressOffset), excludedFlows); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/flow/FollowFlowForwardTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/flow/FollowFlowForwardTest.java index eba7d2b268..df014e8bec 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/flow/FollowFlowForwardTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/flow/FollowFlowForwardTest.java @@ -80,6 +80,33 @@ public class FollowFlowForwardTest extends AbstractFollowFlowTest { assertEquals(new MySelection(expectedAddresses), new MySelection(flowAddresses)); } + + @Test + public void testFollowAllFlowsFromNoData0x10() { + + AddressSetView flowAddresses = getFlowsFrom(0x10, followAllFlows(), true, false); + + AddressSet expectedAddresses = new AddressSet(); + expectedAddresses.add(addr(0x0), addr(0x24)); + expectedAddresses.add(addr(0x26), addr(0x2f)); + expectedAddresses.add(addr(0x30), addr(0x52)); + expectedAddresses.add(addr(0x54), addr(0x5f)); + expectedAddresses.add(addr(0x60), addr(0x84)); + expectedAddresses.add(addr(0x86), addr(0x8f)); + expectedAddresses.add(addr(0x90), addr(0xb4)); + expectedAddresses.add(addr(0xb6), addr(0xbf)); + expectedAddresses.add(addr(0x130), addr(0x131)); + expectedAddresses.add(addr(0x160), addr(0x161)); + expectedAddresses.add(addr(0x190), addr(0x191)); + expectedAddresses.add(addr(0x230), addr(0x231)); + expectedAddresses.add(addr(0x260), addr(0x261)); + expectedAddresses.add(addr(0x290), addr(0x291)); + expectedAddresses.add(addr(0x330), addr(0x331)); + expectedAddresses.add(addr(0x360), addr(0x361)); + expectedAddresses.add(addr(0x390), addr(0x391)); + + assertEquals(new MySelection(expectedAddresses), new MySelection(flowAddresses)); + } @Test public void testFollowAllFlowsFrom0x17() { @@ -104,6 +131,27 @@ public class FollowFlowForwardTest extends AbstractFollowFlowTest { assertEquals(new MySelection(expectedAddresses), new MySelection(flowAddresses)); } + @Test + public void testFollowAllFlowsFromNoData0x17() { + + AddressSetView flowAddresses = getFlowsFrom(0x17, followAllFlows(), true, false); + + AddressSet expectedAddresses = new AddressSet(); + expectedAddresses.add(addr(0x17), addr(0x24)); + expectedAddresses.add(addr(0x26), addr(0x2f)); + expectedAddresses.add(addr(0x60), addr(0x84)); + expectedAddresses.add(addr(0x86), addr(0x8f)); + expectedAddresses.add(addr(0x90), addr(0xb4)); + expectedAddresses.add(addr(0xb6), addr(0xbf)); + expectedAddresses.add(addr(0x230), addr(0x231)); + expectedAddresses.add(addr(0x260), addr(0x261)); + expectedAddresses.add(addr(0x290), addr(0x291)); + expectedAddresses.add(addr(0x330), addr(0x331)); + expectedAddresses.add(addr(0x360), addr(0x361)); + expectedAddresses.add(addr(0x390), addr(0x391)); + + assertEquals(new MySelection(expectedAddresses), new MySelection(flowAddresses)); + } @Test public void testFollowAllFlowsFrom0x2f() { @@ -159,6 +207,20 @@ public class FollowFlowForwardTest extends AbstractFollowFlowTest { assertEquals(new MySelection(expectedAddresses), new MySelection(flowAddresses)); } + + @Test + public void testFollowAllFlowsFromNoData0x77() { + + AddressSetView flowAddresses = getFlowsFrom(0x77, followAllFlows(), true, false); + + AddressSet expectedAddresses = new AddressSet(); + expectedAddresses.add(addr(0x77), addr(0x84)); + expectedAddresses.add(addr(0x86), addr(0x8f)); + expectedAddresses.add(addr(0x260), addr(0x261)); + expectedAddresses.add(addr(0x290), addr(0x291)); + + assertEquals(new MySelection(expectedAddresses), new MySelection(flowAddresses)); + } @Test public void testFollowAllFlowsFrom0x5000() { diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/cmd/function/CreateFunctionCmdWithFlowTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/cmd/function/CreateFunctionCmdWithFlowTest.java new file mode 100644 index 0000000000..b07a2634d4 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/cmd/function/CreateFunctionCmdWithFlowTest.java @@ -0,0 +1,197 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.cmd.function; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.app.plugin.core.analysis.AnalysisBackgroundCommand; +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.framework.cmd.Command; +import ghidra.framework.options.Options; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.database.function.OverlappingFunctionException; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.SourceType; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; + +/** + * Test for the {@link CreateFunctionCmdWithFlowTest}. + */ +public class CreateFunctionCmdWithFlowTest extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; + private PluginTool tool; + + private Program program; + private ProgramBuilder builder; + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + tool = env.getTool(); + + builder = new ProgramBuilder("notepad.exe", ProgramBuilder._PPC_32); + builder.createMemory("test", "0x07000000", 1024); + + program = builder.getProgram(); + + // + // Create some functions (byte patterns, not Ghidra objects) with varying separation + // + // single function + builder.setBytes("0x07000008", "3d 60 07 00 61 6b 00 20 7d 69 03 a6 4e 80 04 20"); + builder.disassemble("0x07000008", 16); + builder.createMemoryJumpReference("0x070000014", "0x07000020"); + + // Thunk to above single function + builder.setBytes("0x07000020", "7c 69 1b 78 88 04 00 00 38 84 00 01 7c 00 07 74 2f 80 00 00 98 09 00 00 39 29 00 01 40 9e ff e8 4e 80 00 20"); + } + + private void analyze() { + // turn off some analyzers + setAnalysisOptions("Stack"); + setAnalysisOptions("Embedded Media"); + setAnalysisOptions("DWARF"); + setAnalysisOptions("Create Address Tables"); + setAnalysisOptions("MIPS Constant Reference Analyzer"); + + AutoAnalysisManager analysisMgr = AutoAnalysisManager.getAnalysisManager(program); + analysisMgr.reAnalyzeAll(null); + + Command cmd = new AnalysisBackgroundCommand(analysisMgr, false); + tool.execute(cmd, program); + waitForBusyTool(tool); + } + + protected void setAnalysisOptions(String optionName) { + int txId = program.startTransaction("Analyze"); + Options analysisOptions = program.getOptions(Program.ANALYSIS_PROPERTIES); + analysisOptions.setBoolean(optionName, false); + program.endTransaction(txId, true); + } + + @Test + public void testCreateFunction() { + + int transactionID = program.startTransaction("Perform the TEST"); + + CreateFunctionCmd createCmd = new CreateFunctionCmd(addr(0x07000008)); + createCmd.applyTo(program); + + program.endTransaction(transactionID, true); + + Function func8 = func(addr(0x07000008)); + assertNotNull("Created normal function", func8); + + assertEquals("Normal function body size", 16, func8.getBody().getNumAddresses()); + } + + @Test + public void testCreateFunctionOneByte() throws OverlappingFunctionException { + + int transactionID = program.startTransaction("Perform the TEST"); + + CreateFunctionCmd createCmd = new CreateFunctionCmd(addr(0x07000008)); + createCmd.applyTo(program); + + // doctor body + AddressSet body = new AddressSet(addr(0x07000008),addr(0x07000017)); + body.add(addr(0x07000020)); + + Function func8 = func(addr(0x07000008)); + + func8.setBody(body); + + assertEquals("Normal function body size", 17, func8.getBody().getNumAddresses()); + + builder.disassemble("0x07000020", 36); + + createCmd = new CreateFunctionCmd(addr(0x07000020)); + createCmd.applyTo(program); + + program.endTransaction(transactionID, true); + + assertNotNull("Created normal function", func8); + + assertEquals("Normal function body size", 16, func8.getBody().getNumAddresses()); + + Function func20 = func(addr(0x07000020)); + + assertNotNull("Created normal function", func20); + + assertEquals("Normal function body size", 36, func20.getBody().getNumAddresses()); + } + + @Test + public void testPPCDisassemblyRef() throws OverlappingFunctionException { + + int transactionID = program.startTransaction("Perform the TEST"); + + CreateFunctionCmd createCmd = new CreateFunctionCmd(addr(0x07000008)); + createCmd.applyTo(program); + + Function func8 = func(addr(0x07000008)); + + program.getMemory().getBlock(addr(0x07000000)).setExecute(true); + + assertFalse("is not Thunk yet", func8.isThunk()); + + Instruction instructionAt = program.getListing().getInstructionAt(addr(0x07000020)); + + assertNull("Not disassembled yet", instructionAt); + + builder.analyze(); + + assertNotNull("Created normal function", func8); + + assertEquals("Normal function body size", 16, func8.getBody().getNumAddresses()); + + instructionAt = program.getListing().getInstructionAt(addr(0x07000020)); + + assertNotNull("Disassembled from computed branch", instructionAt); + + createCmd = new CreateFunctionCmd(addr(0x07000020)); + createCmd.applyTo(program); + + Function func20 = func(addr(0x07000020)); + + builder.analyze(); + + program.endTransaction(transactionID, true); + + assertTrue("is Thunk ", func8.isThunk()); + + assertEquals("Normal function body size", 36, func20.getBody().getNumAddresses()); + } + + private Address addr(long l) { + AddressSpace addressSpace = program.getAddressFactory().getDefaultAddressSpace(); + return addressSpace.getAddress(l); + } + + private Function func(Address a) { + FunctionManager fm = program.getFunctionManager(); + return fm.getFunctionAt(a); + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/FollowFlow.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/FollowFlow.java index 6d03bc28c7..a9579ca53b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/FollowFlow.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/block/FollowFlow.java @@ -46,10 +46,14 @@ public class FollowFlow { private boolean followPointers = true; private boolean followIntoFunction = true; + private boolean includeData = true; private Address nextSymbolAddr; /** * Constructor + * + * Note: flow into existing functions will be included + * Note: flow into un-disassembled locations will be included * * @param program the program whose flow we are following. * @param addressSet the initial addresses that should be flowed from or flowed to. @@ -63,6 +67,7 @@ public class FollowFlow { *
FlowType.CONDITIONAL_JUMP *
FlowType.UNCONDITIONAL_JUMP *
FlowType.INDIRECTION + * */ public FollowFlow(Program program, AddressSet addressSet, FlowType[] doNotFollow) { this.program = program; @@ -73,6 +78,8 @@ public class FollowFlow { /** * Constructor * + * Note: flow into un-disassembled locations will be included + * * @param program the program whose flow we are following. * @param addressSet the initial addresses that should be flowed from or flowed to. * @param doNotFollow array of flow types that are not to be followed. @@ -93,6 +100,31 @@ public class FollowFlow { this(program, addressSet, doNotFollow); this.followIntoFunction = followIntoFunctions; } + + /** + * Constructor + * + * @param program the program whose flow we are following. + * @param addressSet the initial addresses that should be flowed from or flowed to. + * @param doNotFollow array of flow types that are not to be followed. + * null or empty array indicates follow all flows. The following are valid + * flow types for the doNotFollow array: + *
FlowType.COMPUTED_CALL + *
FlowType.CONDITIONAL_CALL + *
FlowType.UNCONDITIONAL_CALL + *
FlowType.COMPUTED_JUMP + *
FlowType.CONDITIONAL_JUMP + *
FlowType.UNCONDITIONAL_JUMP + *
FlowType.INDIRECTION + * @param followIntoFunctions true if flows into (or back from) defined functions + * should be followed. + * @param includeData true if instruction flows into un-disassembled data should be included + */ + public FollowFlow(Program program, AddressSet addressSet, FlowType[] doNotFollow, + boolean followIntoFunctions, boolean includeData) { + this(program, addressSet, doNotFollow, followIntoFunctions); + this.includeData = includeData; + } /** * updateFollowFlags @@ -289,7 +321,9 @@ public class FollowFlow { codeUnit = instructionStack.pop(); if (!(codeUnit instanceof Instruction)) { // Probably undefined data which should be disassembled - flowAddressSet.addRange(codeUnit.getMinAddress(), codeUnit.getMaxAddress()); + if (includeData) { + flowAddressSet.addRange(codeUnit.getMinAddress(), codeUnit.getMaxAddress()); + } continue; } @@ -475,7 +509,7 @@ public class FollowFlow { if (nextAddress != null) { CodeUnit nextCodeUnit = program.getListing().getCodeUnitContaining(nextAddress); if (nextCodeUnit != null) { - if (nextCodeUnit instanceof Data) { + if (nextCodeUnit instanceof Data && includeData) { followData(instructionStack, flowAddressSet, (Data) nextCodeUnit, nextAddress); } diff --git a/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/plugin/core/analysis/PowerPCAddressAnalyzer.java b/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/plugin/core/analysis/PowerPCAddressAnalyzer.java index 4f579a5c81..c59193a3be 100644 --- a/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/plugin/core/analysis/PowerPCAddressAnalyzer.java +++ b/Ghidra/Processors/PowerPC/src/main/java/ghidra/app/plugin/core/analysis/PowerPCAddressAnalyzer.java @@ -226,7 +226,9 @@ public class PowerPCAddressAnalyzer extends ConstantPropagationAnalyzer { public boolean evaluateReference(VarnodeContext context, Instruction instr, int pcodeop, Address address, int size, DataType dataType, RefType refType) { - if (instr.getFlowType().isJump()) { + if (refType.isJump() && refType.isComputed() && + program.getMemory().contains(address) && address.getOffset() != 0) { + super.evaluateReference(context, instr, pcodeop, address, size, dataType, refType); // for branching instructions, if we have a good target, mark it // if this isn't straight code (thunk computation), let someone else lay down the reference return !symEval.encounteredBranch();