You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Describe the bug
When the emulator (ghidra.pcode.emu.PcodeEmulator) encounters a loop instruction in PIC, XTensa or ARCompact (with Pull Request #3006), it fails to keep the value of context registers set through globalset statements in Sleigh. Instead, it warns "Context applied somewhere other than the counter." and forgets the value. This makes emulation of loop instructions broken.
The README contains more detailed build instructions.
Built pcodetest binaries for ARCompact are available in ARC-pcodetests/toolchain-arc700-2026.03/. Ghidra expects these programs to be located in Ghidra/Processors/ARC/data/pcodetests/.
A simple PyGhidra program (to be copy-pasted in the PyGhidra window once a pcodetest binary is loaded in Ghidra) is provided in emulate_pcodetest_functions/emulate_udivmodsi4.py. This program creates an emulator and runs function __udivmodsi4, showing incorrect result.
Instruction lpne did not execute a loop correctly, which made __udivmodsi4 return an incorrect result. The reason why it did not execute was that it is implemented using globalset to set the value of a context register:
Expected behavior
Using setglobal to a context register on an address different from the current instruction is expected to work.
Environment (please complete the following information):
OS: Debian 13 (Linux) on x86_64
Java Version: openjdk 25.0.3
Ghidra Version: branch master and version 12.1.2
Ghidra Origin: locally built
Additional context
I was updating Pull Request #3006 to a more recent version of Ghidra and ran the pcodetests again, which failed because of this issue. While I only tested on ARCompact, I am quite confident the same issue exists with XTensa loop:
Msg.warn(this, "Context applied somewhere other than the counter.");
return;
}
By the way, I asked Claude Code (model Opus 4.8) to fix this issue and it generated the following patch: future_context.patch
Fix suggested by Claude Code
diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java
index 88cb52b59dc9..53d7f0705830 100644
--- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java+++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java@@ -253,6 +253,16 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
private Address counter;
private RegisterValue context;
+ /**+ * Processor-context values committed (via Sleigh {@code globalset}) to an address other than+ * the instruction that produced them. These are recorded here and applied once the program+ * counter actually reaches the target address. This is what allows Sleigh constructs that set+ * context at a future address — notably zero-overhead hardware loops (e.g.,+ * ARCompact/Xtensa {@code LP}, dsPIC {@code DO}) which mark their loop-end address — to+ * function under emulation, rather than being silently dropped.+ */+ private final Map<Address, RegisterValue> futureContexts = new HashMap<>();+
protected Instruction instruction;
protected PcodeFrame frame;
@@ -519,6 +529,26 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
}
public static RegisterValue getContextAfterCommits(Instruction instruction, long counter) {
+ return getContextAfterCommits(instruction, counter, null);+ }++ /**+ * Compute the processor context resulting from the given instruction's context commits.+ *+ * <p>+ * Commits targeting the (next) counter or the instruction's own address are folded into the+ * returned value, which becomes the context used to decode the next instruction. Commits+ * targeting any other address are recorded in {@code futureContexts} (when non-null), keyed by+ * the target address, so they can be applied later, when the program counter actually reaches+ * that address. Passing {@code null} discards such commits (the historical behavior).+ *+ * @param instruction the instruction whose commits to apply+ * @param counter the offset of the (next) program counter+ * @param futureContexts a map collecting commits to other addresses, or null to discard them+ * @return the context to apply at the counter+ */+ public static RegisterValue getContextAfterCommits(Instruction instruction, long counter,+ Map<Address, RegisterValue> futureContexts) {
PseudoInstruction pins = (PseudoInstruction) instruction;
Language language = instruction.getPrototype().getLanguage();
try {
@@ -531,12 +561,15 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
if (!value.getRegister().isProcessorContext()) {
return;
}
- if (address.getOffset() != counter &&- !Objects.equals(pins.getAddress(), address)) {- Msg.warn(this, "Context applied somewhere other than the counter.");- return;+ if (address.getOffset() == counter ||+ Objects.equals(pins.getAddress(), address)) {+ ctxVal = ctxVal.assign(value.getRegister(), value);+ }+ else if (futureContexts != null) {+ RegisterValue existing = futureContexts.get(address);+ futureContexts.put(address,+ existing == null ? value : existing.combineValues(value));
}
- ctxVal = ctxVal.assign(value.getRegister(), value);
}
};
parserCtx.applyCommits(procCtx);
@@ -548,7 +581,7 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
}
protected RegisterValue getContextAfterCommits() {
- return getContextAfterCommits(instruction, counter.getOffset());+ return getContextAfterCommits(instruction, counter.getOffset(), futureContexts);
}
/**
@@ -568,6 +601,13 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
.combineValues(defaultContext.getDefaultValue(contextreg, counter))
.combineValues(defaultContext.getFlowValue(context))
.combineValues(getContextAfterCommits());
+ // Apply any context previously committed (via globalset) to this address. This is kept+ // after being applied, so an address reached repeatedly (e.g., the end of a+ // zero-overhead hardware loop) sees the committed context on every pass.+ RegisterValue future = futureContexts.get(counter);+ if (future != null) {+ ctx = ctx.combineValues(future);+ }
writeContext(ctx);
}
postExecuteInstruction();
This patch builds fine and makes the whole pcodetests suite pass for ARCompact (and makes function __udivmodsi4 returns the right values). While the changes look simple, adding a map of future contextreg values probably incurs some side-effects in the Emulator API, which is why I am opening an issue to discuss first, before eventually opening a PR with changes which would be tested more thoroughly.
Describe the bug
When the emulator (
ghidra.pcode.emu.PcodeEmulator) encounters a loop instruction in PIC, XTensa or ARCompact (with Pull Request #3006), it fails to keep the value of context registers set throughglobalsetstatements in Sleigh. Instead, it warns "Context applied somewhere other than the counter." and forgets the value. This makes emulation of loop instructions broken.To Reproduce
Steps to reproduce the behavior:
test_BIOPS4To ease reproducing this bug, I created a repository https://github.com/niooss-ledger/ghidra-arcompact-test where:
READMEcontains more detailed build instructions.ARC-pcodetests/toolchain-arc700-2026.03/. Ghidra expects these programs to be located inGhidra/Processors/ARC/data/pcodetests/.emulate_pcodetest_functions/emulate_udivmodsi4.py. This program creates an emulator and runs function__udivmodsi4, showing incorrect result.emulate_pcodetest_functions/emulate_udivmodsi4.output.txtThis output contains:
Instruction
lpnedid not execute a loop correctly, which made__udivmodsi4return an incorrect result. The reason why it did not execute was that it is implemented usingglobalsetto set the value of a context register:Expected behavior
Using
setglobalto a context register on an address different from the current instruction is expected to work.Environment (please complete the following information):
masterand version12.1.2Additional context
I was updating Pull Request #3006 to a more recent version of Ghidra and ran the pcodetests again, which failed because of this issue. While I only tested on ARCompact, I am quite confident the same issue exists with XTensa
loop:ghidra/Ghidra/Processors/Xtensa/data/languages/xtensaInstructions.sinc
Lines 1904 to 1910 in 47ae57a
and with PIC
do:ghidra/Ghidra/Processors/PIC/data/languages/PIC24.sinc
Lines 3792 to 3804 in 47ae57a
The warning is generated from:
ghidra/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java
Lines 534 to 538 in 47ae57a
By the way, I asked Claude Code (model Opus 4.8) to fix this issue and it generated the following patch: future_context.patch
Fix suggested by Claude Code
This patch builds fine and makes the whole pcodetests suite pass for ARCompact (and makes function
__udivmodsi4returns the right values). While the changes look simple, adding a map of futurecontextregvalues probably incurs some side-effects in the Emulator API, which is why I am opening an issue to discuss first, before eventually opening a PR with changes which would be tested more thoroughly.What do you think?