From b382017ccba121e141322c9ab0f378f8ed5d63eb Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:04:42 -0500 Subject: [PATCH] GP-4014: Context and disasembly re-flow after patching instruction. --- .../RepairDisassemblyScript.java | 25 ++ .../cmd/disassemble/ReDisassembleCommand.java | 44 +++ .../assembler/PatchInstructionAction.java | 5 + .../program/disassemble/Disassembler.java | 2 +- .../program/disassemble/ReDisassembler.java | 366 ++++++++++++++++++ .../lang}/DisassemblerContextAdapter.java | 3 +- 6 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 Ghidra/Features/Base/ghidra_scripts/RepairDisassemblyScript.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/cmd/disassemble/ReDisassembleCommand.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/ReDisassembler.java rename Ghidra/Framework/{Emulation/src/main/java/ghidra/pcode/emu => SoftwareModeling/src/main/java/ghidra/program/model/lang}/DisassemblerContextAdapter.java (97%) diff --git a/Ghidra/Features/Base/ghidra_scripts/RepairDisassemblyScript.java b/Ghidra/Features/Base/ghidra_scripts/RepairDisassemblyScript.java new file mode 100644 index 0000000000..60dc43af41 --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/RepairDisassemblyScript.java @@ -0,0 +1,25 @@ +/* ### + * 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. + */ +import ghidra.app.script.GhidraScript; +import ghidra.program.disassemble.ReDisassembler; + +public class RepairDisassemblyScript extends GhidraScript { + @Override + protected void run() throws Exception { + ReDisassembler dis = new ReDisassembler(currentProgram); + dis.disasemble(currentAddress, monitor); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/disassemble/ReDisassembleCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/disassemble/ReDisassembleCommand.java new file mode 100644 index 0000000000..1e7f347efb --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/disassemble/ReDisassembleCommand.java @@ -0,0 +1,44 @@ +/* ### + * 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.disassemble; + +import ghidra.framework.cmd.BackgroundCommand; +import ghidra.framework.model.DomainObject; +import ghidra.program.disassemble.ReDisassembler; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class ReDisassembleCommand extends BackgroundCommand { + private final Address seed; + + public ReDisassembleCommand(Address seed) { + this.seed = seed; + } + + @Override + public boolean applyTo(DomainObject obj, TaskMonitor monitor) { + ReDisassembler dis = new ReDisassembler((Program) obj); + try { + dis.disasemble(seed, monitor); + return true; + } + catch (CancelledException e) { + return false; + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java index 0a8f57607d..5f01183ef4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java @@ -34,6 +34,7 @@ import docking.widgets.autocomplete.*; import docking.widgets.fieldpanel.*; import docking.widgets.fieldpanel.support.FieldLocation; import generic.theme.GThemeDefaults.Colors; +import ghidra.app.cmd.disassemble.ReDisassembleCommand; import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.core.assembler.AssemblyDualTextField.*; @@ -301,7 +302,11 @@ public class PatchInstructionAction extends AbstractPatchAction { } protected void applyPatch(byte[] data) throws MemoryAccessException { + // NB. This will immediately re-disassembly the one command. + // We'll background the context repair, which may include more disassembly assembler.patchProgram(data, getAddress()); + ReDisassembleCommand cmd = new ReDisassembleCommand(getAddress()); + tool.executeBackgroundCommand(cmd, getProgram()); } /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java index 8311545970..1992482da4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java @@ -1355,7 +1355,7 @@ public class Disassembler implements DisassemblerConflictHandler { * @return true if the call also falls through to this instruction */ private boolean isNoReturnCall(Instruction instr, Address target) { - // if allready overriden, return + // if already overridden, return // is this function a call fixup if (program == null) { return false; // can't tell without program diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/ReDisassembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/ReDisassembler.java new file mode 100644 index 0000000000..c1945049ba --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/ReDisassembler.java @@ -0,0 +1,366 @@ +/* ### + * 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.program.disassemble; + +import java.util.*; + +import ghidra.app.util.PseudoInstruction; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.*; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.program.util.ProgramContextImpl; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A class that re-disassembles where necessary + * + *
+ * Given a seed address, this will (re-)disassemble the instruction at that address. If it indicates
+ * any context changes, whether via {@code globalset} or fall-through, the affected addresses are
+ * considered for re-disassembly as well. If no instruction exists at the address, or an off-cut
+ * instruction exists at the address, the address is dropped, but the outgoing context is recorded.
+ * If one does exist, but its context is already the same, the address is dropped. Otherwise, it is
+ * queued up and the process repeats.
+ */
+public class ReDisassembler {
+
+ protected final Language language;
+ protected final AddressFactory addrFactory;
+ protected final Register ctxRegister;
+ protected final ParallelInstructionLanguageHelper parallelHelper;
+
+ private final Program program;
+ private final Listing listing;
+ private final ProgramContext programContext;
+
+ public ReDisassembler(Program program) {
+ this.program = program;
+ this.listing = program.getListing();
+ this.programContext = program.getProgramContext();
+ this.language = program.getLanguage();
+ this.addrFactory = program.getAddressFactory();
+ this.ctxRegister = language.getContextBaseRegister();
+ this.parallelHelper = language.getParallelInstructionHelper();
+ }
+
+ enum FlowType {
+ SEED, FALLTHROUGH, BRANCH, GLOBALSET;
+ }
+
+ record Flow(Address from, Address to, FlowType type) {
+ static Flow seed(Address seed) {
+ return new Flow(Address.NO_ADDRESS, seed, FlowType.SEED);
+ }
+
+ static Flow fallThrough(Instruction instruction) throws AddressOverflowException {
+ return new Flow(instruction.getAddress(),
+ instruction.getAddress().add(instruction.getLength()), FlowType.FALLTHROUGH);
+ }
+
+ static Flow branch(Instruction instruction, Address to) {
+ return new Flow(instruction.getAddress(), to, FlowType.BRANCH);
+ }
+
+ Flow globalSet(Address address) {
+ return new Flow(to, address, FlowType.GLOBALSET);
+ }
+ }
+
+ protected class ReDisState {
+ protected final TaskMonitor monitor;
+ protected final MemBuffer progMemBuffer =
+ new DumbMemBufferImpl(program.getMemory(), program.getMemory().getMinAddress());
+ protected final ProgramContext tempContext = new ProgramContextImpl(language);
+ protected final AddressSet visited = new AddressSet();
+ protected final Deque
+ * It's also not necessarily a basic block, since this doesn't care about jumps
+ * into the block. It simply starts at the next seed and proceeds until either the existing
+ * instruction and context matches what's already there, or it encounters an unconditional
+ * branch.
+ *
+ * @return true if the queue is non-empty after completing this block, false if we're done.
+ * @throws CancelledException
+ */
+ protected boolean nextBlock() throws CancelledException {
+ monitor.checkCancelled();
+ instructionSet.addBlock(new ReDisBlock(this, queue.pop()).disassembleBlock());
+ return !queue.isEmpty();
+ }
+
+ protected InstructionSet disassemble() throws CancelledException {
+ while (nextBlock()) {
+ }
+ return instructionSet;
+ }
+
+ public void writeContext() {
+ for (Address addr : ctxAddrs) {
+ RegisterValue curCtxVal = programContext.getRegisterValue(ctxRegister, addr);
+ System.err.println("Writing context at " + addr);
+ System.err.println(" Cur: " + curCtxVal);
+ RegisterValue newCtxVal = tempContext.getRegisterValue(ctxRegister, addr);
+ System.err.println(" New: " + newCtxVal);
+
+ if (curCtxVal.equals(newCtxVal)) {
+ continue;
+ }
+ try {
+ programContext.setRegisterValue(addr, addr, newCtxVal);
+ }
+ catch (ContextChangeException e) {
+ Msg.error(this, "Cannot write context at " + addr + ": " + e);
+ }
+ }
+ }
+ }
+
+ protected class ReDisBlock {
+ protected final ReDisState state;
+ protected final Flow entry;
+ protected final InstructionBlock block;
+ protected final DisassemblerContextImpl disassemblerContext;
+ protected PseudoInstruction lastInstruction;
+
+ public ReDisBlock(ReDisState state, Flow entry) {
+ this.state = state;
+ this.entry = entry;
+ this.block = new InstructionBlock(entry.to);
+ this.disassemblerContext = new DisassemblerContextImpl(state.tempContext);
+ this.disassemblerContext.flowStart(entry.to);
+ }
+
+ protected void recordContext(Address to) {
+ try {
+ state.tempContext.setRegisterValue(to, to,
+ disassemblerContext.getRegisterValue(ctxRegister));
+ state.ctxAddrs.add(to);
+ }
+ catch (ContextChangeException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ protected PseudoInstruction createInstruction(Address address,
+ InstructionPrototype prototype, MemBuffer memBuffer, ProcessorContext ctx)
+ throws AddressOverflowException {
+ PseudoInstruction instruction = new PseudoInstruction(program, address, prototype,
+ memBuffer, ctx);
+ instruction.setInstructionBlock(block);
+ System.err.println(
+ " " + instruction + " with " + instruction.getRegisterValue(ctxRegister));
+ return lastInstruction = instruction;
+ }
+
+ protected boolean shouldDisassemble(Flow flow) {
+ Instruction exists = listing.getInstructionContaining(flow.to);
+ if (exists == null) {
+ /**
+ * NOTE: New instructions are not placed until we're all done, so there is no need
+ * to worry about differences in instruction length as we progress through a block.
+ */
+ return false;
+ }
+ if (flow.type == FlowType.FALLTHROUGH) {
+ return true;
+ }
+ if (exists.getAddress().equals(flow.to)) {
+ return true;
+ }
+ return false;
+ }
+
+ protected Instruction nextInstruction(Flow flow, boolean isInDelaySlot)
+ throws CancelledException {
+ state.monitor.checkCancelled();
+ if (state.visited.contains(flow.to)) {
+ // Already disassembled here
+ // TODO: Record instruction conflicts?
+ return null;
+ }
+ recordContext(flow.to);
+ if (!shouldDisassemble(flow)) {
+ return null;
+ }
+ MemBuffer buffer = state.createBuffer(flow.to);
+ RegisterValue newCtxVal = disassemblerContext.getRegisterValue(ctxRegister);
+ RegisterValue curCtxVal = programContext.getRegisterValue(ctxRegister, flow.to);
+ if (newCtxVal.equals(curCtxVal) && flow.type != FlowType.SEED) {
+ // No need to re-disassemble if context has not changed.
+ return null;
+ }
+ System.err.println("Re-disassembling " + flow + " with " + newCtxVal);
+ ReDisassemblerContext ctx = new ReDisassemblerContext(state, flow);
+ try {
+ InstructionPrototype prototype = language.parse(buffer, ctx, false);
+ return createInstruction(flow.to, prototype, buffer, ctx);
+ }
+ catch (UnknownInstructionException e) {
+ block.setParseConflict(flow.from, disassemblerContext.getRegisterValue(
+ disassemblerContext.getBaseContextRegister()), flow.to, e.getMessage());
+ return null;
+ }
+ catch (InsufficientBytesException e) {
+ block.setInstructionMemoryError(flow.to, flow.from, e.getMessage());
+ return null;
+ }
+ catch (AddressOverflowException e) {
+ block.setInstructionMemoryError(flow.to, flow.from,
+ "Instruction does not fit within address space constraint");
+ return null;
+ }
+ }
+
+ /**
+ * Parse the next instructions, including delay-slotted ones
+ *
+ * @param address the starting address
+ * @return the if the first instruction has fall-through, the flow out from the last
+ * instruction parsed. Without delay slots, the first instruction is the last
+ * instruction.
+ * @throws CancelledException
+ * @throws AddressOverflowException if an instruction would run past the end of the address
+ * space
+ */
+ protected Flow nextInstructionsWithDelays(Flow flow) throws CancelledException {
+ Instruction instruction = nextInstruction(flow, false);
+ if (instruction == null) {
+ return null;
+ }
+ boolean hasFallthrough = instruction.hasFallthrough();
+ try {
+ flow = Flow.fallThrough(instruction);
+ block.addInstruction(instruction);
+ processInstruction(instruction);
+ disassemblerContext.flowToAddress(flow.to);
+ int remainingBytes = instruction.getPrototype().getDelaySlotByteCount();
+ while (remainingBytes > 0) {
+ instruction = nextInstruction(flow, true);
+ if (instruction == null) {
+ return null;
+ }
+ flow = Flow.fallThrough(instruction);
+ block.addInstruction(instruction);
+ processInstruction(instruction);
+ remainingBytes -= instruction.getLength();
+ }
+ return hasFallthrough ? flow : null;
+ }
+ catch (AddressOverflowException e) {
+ block.setInstructionMemoryError(flow.to, flow.from,
+ "Failed to properly process delay slot at end of address space");
+ return null;
+ }
+ }
+
+ protected void processInstruction(Instruction instruction) {
+ state.visited.add(instruction.getMinAddress(), instruction.getMaxAddress());
+ for (Address to : instruction.getFlows()) {
+ recordContext(to);
+ state.addFlow(Flow.branch(instruction, to));
+ }
+ }
+
+ protected InstructionBlock disassembleBlock() throws CancelledException {
+ Flow flow = entry;
+ while (flow != null) {
+ flow = nextInstructionsWithDelays(flow);
+ }
+ return block;
+ }
+ }
+
+ protected class ReDisassemblerContext implements DisassemblerContextAdapter {
+ protected final ReDisState state;
+ protected final Flow flow;
+
+ public ReDisassemblerContext(ReDisState state, Flow flow) {
+ this.state = state;
+ this.flow = flow;
+ }
+
+ @Override
+ public void setFutureRegisterValue(Address address, RegisterValue value) {
+ state.addFlow(flow.globalSet(address));
+ try {
+ state.tempContext.setRegisterValue(address, address, value);
+ state.ctxAddrs.add(address);
+ }
+ catch (ContextChangeException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public RegisterValue getRegisterValue(Register register) {
+ return state.tempContext.getRegisterValue(register, flow.to);
+ }
+ }
+
+ public AddressSetView disasemble(Address seed, TaskMonitor monitor) throws CancelledException {
+ ReDisState state = new ReDisState(monitor);
+ state.addSeed(seed);
+ InstructionSet set = state.disassemble();
+ for (AddressRange range : set.getAddressSet()) {
+ listing.clearCodeUnits(range.getMinAddress(), range.getMaxAddress(), true, monitor);
+ }
+ state.writeContext();
+ try {
+ listing.addInstructions(set, false);
+ }
+ catch (CodeUnitInsertionException e) {
+ Msg.error(this, "Could not overwrite with re-disassembly", e);
+ }
+ return set.getAddressSet();
+ }
+}
diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DisassemblerContextAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/DisassemblerContextAdapter.java
similarity index 97%
rename from Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DisassemblerContextAdapter.java
rename to Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/DisassemblerContextAdapter.java
index 1ff2c7233c..25638916be 100644
--- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DisassemblerContextAdapter.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/DisassemblerContextAdapter.java
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package ghidra.pcode.emu;
+package ghidra.program.model.lang;
import java.math.BigInteger;
import java.util.List;
import ghidra.program.model.address.Address;
-import ghidra.program.model.lang.*;
import ghidra.program.model.listing.ContextChangeException;
public interface DisassemblerContextAdapter extends DisassemblerContext {