From 852db3a80a0f863ee63ab9a9496dcdd11e357b95 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Mon, 24 Mar 2025 19:16:21 +0000 Subject: [PATCH 1/2] GP-5459: For userop libraries, treat empty as empty, not unimpl --- .../struct/sub/StructuredSleighTest.java | 20 +++++++++++++++---- .../pcode/exec/SleighProgramCompiler.java | 7 ++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java b/Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java index 0481a12a6e..4ce89004b1 100644 --- a/Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java +++ b/Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java @@ -4,9 +4,9 @@ * 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. @@ -16,6 +16,7 @@ package ghidra.pcode.struct.sub; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; @@ -25,8 +26,7 @@ import org.junit.Before; import org.junit.Test; import ghidra.app.plugin.processors.sleigh.SleighException; -import ghidra.pcode.exec.PcodeUseropLibrary; -import ghidra.pcode.exec.SleighPcodeUseropDefinition; +import ghidra.pcode.exec.*; import ghidra.pcode.struct.StructuredSleigh; import ghidra.program.model.lang.*; import ghidra.program.model.pcode.Varnode; @@ -238,4 +238,16 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest // TODO: Test that the generated code compiles in a slaspec file. // It's rejected for injects because "return" is not valid there. } + + @Test + public void testEmpty() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop + public void my_userop() { + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + PcodeProgram program = myUserop.programFor(null, List.of(), PcodeUseropLibrary.nil()); + assertTrue(program.getCode().isEmpty()); + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java index 29e3348896..c4aab1246b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import ghidra.app.plugin.processors.sleigh.*; import ghidra.app.plugin.processors.sleigh.template.ConstructTpl; +import ghidra.app.plugin.processors.sleigh.template.OpTpl; import ghidra.pcode.utils.MessageFormattingUtils; import ghidra.pcodeCPort.pcoderaw.VarnodeData; import ghidra.pcodeCPort.sleighbase.SleighBase; @@ -149,6 +150,9 @@ public enum SleighProgramCompiler { */ public static ConstructTpl compileTemplate(Language language, PcodeParser parser, String sourceName, String source) { + if (source.isBlank()) { + return new ConstructTpl(new OpTpl[] {}); + } return parser.compilePcode(source, sourceName, 1); } @@ -285,7 +289,8 @@ public enum SleighProgramCompiler { * evaluator p-code program uses its own library as a means of capturing the result; however, * userop libraries are easily composed. It should be easy to add that feature if needed. * - * @param language the languge of the target p-code machine + * @param parser a parser for the given language + * @param language the language of the target p-code machine * @param expression the Sleigh expression to be evaluated * @return a p-code program whose {@link PcodeExpression#evaluate(PcodeExecutor)} method will * evaluate the expression on the given executor and its state. From f0a9e138e2e2fec70cccdb22afc4db8b75f1f58f Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Mon, 24 Mar 2025 20:04:23 +0000 Subject: [PATCH 2/2] GP-5460: Fix branching from injected p-code in emulation. --- .../java/ghidra/pcode/exec/PcodeExecutor.java | 28 +++++++- .../emu/jit/AbstractPcodeEmulatorTest.java | 71 +++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutor.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutor.java index 59fd138f44..bdfe6f07ad 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutor.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutor.java @@ -474,7 +474,7 @@ public class PcodeExecutor { } else { branchToOffset(op, target.getOffset(), frame); - branchToAddress(op, target); + branchToAddress(op, checkInjectedTarget(target)); } } @@ -530,6 +530,28 @@ public class PcodeExecutor { return op.getInput(0); } + /** + * Check and correct the given target address, if it resides in "NO ADDRESS" space. + * + *

+ * At some point, we made a change to set the "target address" of compiled p-code userops to + * {@link Address#NO_ADDRESS} instead of pretending its at {@code ram:00000000}. This is + * philosophically cleaner, but leads to a practical issue in that the p-code compiler sets the + * target address of any branch to be in the same space, which for injects, will wind up in "NO + * ADDRESS." I don't know the use case for having target addresses anywhere but default space, + * so I'll maintain that behavior, but if it ever lands in "NO ADDRESS," we're going to assume + * it was an inject, and that the intended target was the default space. + * + * @param target the proposed target address + * @return the same or corrected target address + */ + protected Address checkInjectedTarget(Address target) { + if (target.getAddressSpace() != Address.NO_ADDRESS.getAddressSpace()) { + return target; + } + return language.getDefaultSpace().getAddress(target.getOffset()); + } + /** * Perform the actual logic of an indirect branch p-code op * @@ -548,7 +570,7 @@ public class PcodeExecutor { long concrete = arithmetic.toLong(offset, Purpose.BRANCH); Address target = op.getSeqnum().getTarget().getNewAddress(concrete, true); - branchToAddress(op, target); + branchToAddress(op, checkInjectedTarget(target)); } /** @@ -576,7 +598,7 @@ public class PcodeExecutor { public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary library) { Address target = getBranchTarget(op); branchToOffset(op, target.getOffset(), frame); - branchToAddress(op, target); + branchToAddress(op, checkInjectedTarget(target)); } /** diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/AbstractPcodeEmulatorTest.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/AbstractPcodeEmulatorTest.java index 5d9f8b10a4..607f49f99c 100644 --- a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/AbstractPcodeEmulatorTest.java +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/AbstractPcodeEmulatorTest.java @@ -298,4 +298,75 @@ public abstract class AbstractPcodeEmulatorTest extends AbstractGTest { arithmetic.toLong(thread.getState().getVar(r1, Reason.INSPECT), Purpose.INSPECT)); assertEquals(inject, thread.getCounter()); } + + @Test + public void testInjectedBranch() throws Exception { + PcodeEmulator emu = createEmulator(getLanguage(LANGID_TOY_BE)); + PcodeArithmetic arithmetic = emu.getArithmetic(); + AddressSpace space = emu.getLanguage().getDefaultSpace(); + AssemblyBuffer asm = new AssemblyBuffer(Assemblers.getAssembler(emu.getLanguage()), + space.getAddress(0x00400000)); + + asm.assemble("imm r1, #1"); + Address inject = asm.getNext(); + asm.assemble("add r1, #1"); + Address target = asm.getNext(); + + byte[] bytes = asm.getBytes(); + emu.getSharedState().setVar(asm.getEntry(), bytes.length, false, bytes); + PcodeThread thread = emu.newThread(); + thread.overrideCounter(asm.getEntry()); + + emu.inject(inject, "goto 0x%08x;".formatted(target.getOffset())); + + try { + thread.run(); + fail("Should have crashed on decode error"); + } + catch (DecodePcodeExecutionException e) { + // Space assertion is subsumed by counter assertion + } + + Register r1 = emu.getLanguage().getRegister("r1"); + assertEquals(1, + arithmetic.toLong(thread.getState().getVar(r1, Reason.INSPECT), Purpose.INSPECT)); + assertEquals(target, thread.getCounter()); + } + + @Test + public void testInjectedIndirectBranch() throws Exception { + PcodeEmulator emu = createEmulator(getLanguage(LANGID_TOY_BE)); + PcodeArithmetic arithmetic = emu.getArithmetic(); + AddressSpace space = emu.getLanguage().getDefaultSpace(); + AssemblyBuffer asm = new AssemblyBuffer(Assemblers.getAssembler(emu.getLanguage()), + space.getAddress(0x00400000)); + + asm.assemble("imm r1, #1"); + Address inject = asm.getNext(); + asm.assemble("add r1, #1"); + Address target = asm.getNext(); + + byte[] bytes = asm.getBytes(); + emu.getSharedState().setVar(asm.getEntry(), bytes.length, false, bytes); + PcodeThread thread = emu.newThread(); + thread.overrideCounter(asm.getEntry()); + + emu.inject(inject, """ + r2 = 0x%08x; + goto [r2]; + """.formatted(target.getOffset())); + + try { + thread.run(); + fail("Should have crashed on decode error"); + } + catch (DecodePcodeExecutionException e) { + // Space assertion is subsumed by counter assertion + } + + Register r1 = emu.getLanguage().getRegister("r1"); + assertEquals(1, + arithmetic.toLong(thread.getState().getVar(r1, Reason.INSPECT), Purpose.INSPECT)); + assertEquals(target, thread.getCounter()); + } }