From df9a1e27566eaed3d915b3683ddf0cce0dcb4b68 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 12 Jan 2023 13:38:17 -0500 Subject: [PATCH] GP-2834: Add Unwind Stack action, hovers for dynamic variable values. --- Ghidra/Debug/Debugger/certification.manifest | 5 + .../ComputeUnwindInfoScript.java | 64 + .../ghidra_scripts/EmuDeskCheckScript.java | 22 +- .../src/main/help/help/TOC_Source.xml | 6 +- .../DebuggerStackPlugin.html | 55 + .../images/DebuggerStackUnwindInListing.png | Bin 0 -> 46585 bytes .../VariableValueHoverPlugin.html | 141 ++ .../VariableValueHoverPluginBrowser.png | Bin 0 -> 63084 bytes .../VariableValueHoverPluginDecompiler.png | Bin 0 -> 31913 bytes .../VariableValueHoverPluginListing.png | Bin 0 -> 57552 bytes .../gui/diff/DebuggerTraceViewDiffPlugin.java | 21 +- .../debug/gui/memview/MemviewMapModel.java | 18 +- .../gui/stack/DebuggerStackProvider.java | 37 +- .../stack/vars/VariableValueHoverPlugin.java | 66 + .../stack/vars/VariableValueHoverService.java | 675 +++++++ .../gui/stack/vars/VariableValueRow.java | 647 +++++++ .../gui/stack/vars/VariableValueTable.java | 115 ++ .../gui/stack/vars/VariableValueUtils.java | 847 +++++++++ .../plugin/core/debug/gui/watch/WatchRow.java | 2 +- ...stractRWTargetPcodeExecutorStatePiece.java | 22 + .../RWTargetMemoryPcodeExecutorState.java | 12 +- ...RWTargetMemoryPcodeExecutorStatePiece.java | 43 +- .../RWTargetRegistersPcodeExecutorState.java | 12 +- ...argetRegistersPcodeExecutorStatePiece.java | 44 +- .../emulation/data/PcodeDebuggerAccess.java | 4 + .../DebuggerStaticMappingServicePlugin.java | 1 + .../debug/stack/AbstractUnwoundFrame.java | 359 ++++ .../debug/stack/AnalysisUnwoundFrame.java | 400 ++++ .../core/debug/stack/FakeUnwoundFrame.java | 108 ++ .../debug/stack/FrameStructureBuilder.java | 278 +++ .../core/debug/stack/ListingUnwoundFrame.java | 348 ++++ .../core/debug/stack/SavedRegisterMap.java | 333 ++++ .../core/debug/stack/StackUnwindWarning.java | 199 ++ .../debug/stack/StackUnwindWarningSet.java | 160 ++ .../core/debug/stack/StackUnwinder.java | 316 ++++ .../app/plugin/core/debug/stack/Sym.java | 300 +++ .../core/debug/stack/SymPcodeArithmetic.java | 102 + .../core/debug/stack/SymPcodeExecutor.java | 392 ++++ .../debug/stack/SymPcodeExecutorState.java | 282 +++ .../core/debug/stack/SymStateSpace.java | 322 ++++ .../core/debug/stack/UnwindAnalysis.java | 516 +++++ .../core/debug/stack/UnwindException.java} | 18 +- .../plugin/core/debug/stack/UnwindInfo.java | 250 +++ .../core/debug/stack/UnwindStackCommand.java | 63 + .../plugin/core/debug/stack/UnwoundFrame.java | 286 +++ .../core/debug/utils/BackgroundUtils.java | 62 +- .../ghidra/pcode/exec/DebuggerPcodeUtils.java | 270 ++- .../stack/DebuggerStackPluginScreenShots.java | 216 ++- .../VariableValueHoverPluginScreenShots.java | 391 ++++ .../core/debug/stack/StackUnwinderTest.java | 1680 +++++++++++++++++ ...chedWriteBytesPcodeExecutorStatePiece.java | 46 +- ...essesReadTracePcodeExecutorStatePiece.java | 31 +- .../trace/BytesTracePcodeExecutorState.java | 9 + .../BytesTracePcodeExecutorStatePiece.java | 48 +- .../trace/DefaultTracePcodeExecutorState.java | 5 + .../DirectBytesTracePcodeExecutorState.java | 10 + ...rectBytesTracePcodeExecutorStatePiece.java | 26 +- .../trace/PairedTracePcodeExecutorState.java | 20 +- .../PairedTracePcodeExecutorStatePiece.java | 6 + ...aceCachedWriteBytesPcodeExecutorState.java | 10 + ...chedWriteBytesPcodeExecutorStatePiece.java | 11 + ...aceCachedWriteBytesPcodeExecutorState.java | 10 + ...chedWriteBytesPcodeExecutorStatePiece.java | 12 + ...aceMemoryStatePcodeExecutorStatePiece.java | 32 +- .../exec/trace/TracePcodeExecutorState.java | 9 +- .../trace/TracePcodeExecutorStatePiece.java | 3 + .../listing/UndefinedDBTraceData.java | 2 +- .../space/DBTraceDelegatingManager.java | 3 +- .../ghidra/trace/model/listing/TraceData.java | 3 + .../model/memory/TraceMemoryOperations.java | 23 + .../trace/model/time/schedule/Scheduler.java | 3 + .../ghidra/trace/util/TraceRegisterUtils.java | 2 +- .../ExpanderArrowExpansionVetoException.java | 20 - .../docking/widgets/ExpanderArrowPanel.java | 155 -- .../src/main/java/generic/Span.java | 2 +- .../src/main/java/generic/Unique.java | 10 + .../util/datastruct/SemisparseByteArray.java | 34 +- .../pcode/emu/ThreadPcodeExecutorState.java | 16 +- .../pcode/eval/AbstractVarnodeEvaluator.java | 430 +++++ .../eval/ArithmeticVarnodeEvaluator.java | 150 ++ .../ghidra/pcode/eval/VarnodeEvaluator.java | 66 + .../AbstractBytesPcodeExecutorStatePiece.java | 22 +- ...ractLongOffsetPcodeExecutorStatePiece.java | 88 +- .../pcode/exec/AddressOfPcodeArithmetic.java | 95 - .../pcode/exec/BytesPcodeExecutorState.java | 9 + .../exec/BytesPcodeExecutorStatePiece.java | 44 +- .../exec/BytesPcodeExecutorStateSpace.java | 30 +- .../pcode/exec/DefaultPcodeExecutorState.java | 17 +- .../pcode/exec/LocationPcodeArithmetic.java | 123 ++ ...a => LocationPcodeExecutorStatePiece.java} | 55 +- .../pcode/exec/PairedPcodeExecutorState.java | 13 + .../exec/PairedPcodeExecutorStatePiece.java | 23 + .../ghidra/pcode/exec/PcodeArithmetic.java | 59 +- .../java/ghidra/pcode/exec/PcodeExecutor.java | 22 +- .../ghidra/pcode/exec/PcodeExecutorState.java | 3 + .../pcode/exec/PcodeExecutorStatePiece.java | 18 + .../java/ghidra/pcode/exec/PcodeProgram.java | 36 +- .../java/ghidra/pcode/exec/ValueLocation.java | 258 +++ .../AbstractTaintPcodeExecutorStatePiece.java | 9 + .../plain/TaintPcodeExecutorStatePiece.java | 15 + .../pcode/emu/taint/plain/TaintSpace.java | 20 +- .../TaintTracePcodeExecutorStatePiece.java | 15 + .../Decompiler/src/main/doc/pcoderef.xml | 2 +- .../app/plugin/assembler/AssemblyBuffer.java | 44 +- .../plugin/assembler/AssemblySelector.java | 37 +- 105 files changed, 12292 insertions(+), 482 deletions(-) create mode 100644 Ghidra/Debug/Debugger/ghidra_scripts/ComputeUnwindInfoScript.java create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackUnwindInListing.png create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/VariableValueHoverPlugin/VariableValueHoverPlugin.html create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginBrowser.png create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginDecompiler.png create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginListing.png create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPlugin.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverService.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueRow.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueTable.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueUtils.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AbstractUnwoundFrame.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AnalysisUnwoundFrame.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/FakeUnwoundFrame.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/FrameStructureBuilder.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/ListingUnwoundFrame.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SavedRegisterMap.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwindWarning.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwindWarningSet.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/Sym.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeArithmetic.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutor.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutorState.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymStateSpace.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java rename Ghidra/Debug/{ProposedUtils/src/main/java/docking/widgets/ExpanderArrowExpansionListener.java => Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindException.java} (64%) create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindInfo.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindStackCommand.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwoundFrame.java create mode 100644 Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowExpansionVetoException.java delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowPanel.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/AbstractVarnodeEvaluator.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/ArithmeticVarnodeEvaluator.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/VarnodeEvaluator.java delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeArithmetic.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/LocationPcodeArithmetic.java rename Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/{AddressOfPcodeExecutorStatePiece.java => LocationPcodeExecutorStatePiece.java} (55%) create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ValueLocation.java diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index fa575a47f0..b9baa1875d 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -68,6 +68,7 @@ src/main/help/help/topics/DebuggerRegistersPlugin/images/DebuggerAvailableRegist src/main/help/help/topics/DebuggerRegistersPlugin/images/DebuggerRegistersPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackPlugin.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackUnwindInListing.png||GHIDRA||||END| src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerTargetsPlugin/DebuggerTargetsPlugin.html||GHIDRA||||END| @@ -83,6 +84,10 @@ src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTimeSelecti src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTraceViewDiffPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END| +src/main/help/help/topics/VariableValueHoverPlugin/VariableValueHoverPlugin.html||GHIDRA||||END| +src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginBrowser.png||GHIDRA||||END| +src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginDecompiler.png||GHIDRA||||END| +src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginListing.png||GHIDRA||||END| src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END| src/main/resources/images/add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/resources/images/attach.png||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/ComputeUnwindInfoScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/ComputeUnwindInfoScript.java new file mode 100644 index 0000000000..b02b3bdd67 --- /dev/null +++ b/Ghidra/Debug/Debugger/ghidra_scripts/ComputeUnwindInfoScript.java @@ -0,0 +1,64 @@ +/* ### + * 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. + */ +//A script to analyze unwind information for the current function wrt. the current location +//as a program counter. The resulting information can be used to interpret how the function +//is using various elements on the stack when the program counter is at the cursor. This +//script is more for diagnostic and demonstration purposes, since the application of unwind +//information is already integrated into the Debugger. +//@author +//@category Stack +//@keybinding +//@menupath +//@toolbar + +import java.util.Map.Entry; + +import ghidra.app.plugin.core.debug.stack.*; +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.Register; + +public class ComputeUnwindInfoScript extends GhidraScript { + + String addressToString(Address address) { + Register[] registers = currentProgram.getLanguage().getRegisters(address); + if (registers.length == 0) { + return address.toString(); + } + return registers[0].getBaseRegister().toString(); + } + + @Override + protected void run() throws Exception { + UnwindAnalysis ua = new UnwindAnalysis(currentProgram); + UnwindInfo info = ua.computeUnwindInfo(currentAddress, monitor); + + if (info == null) { + println("Could not unwind"); + return; + } + println("Stack depth at " + currentAddress + ": " + info.depth()); + println("Return address address: " + addressToString(info.ofReturn())); + println("Saved registers:"); + for (Entry entry : info.saved().entrySet()) { + println(" " + entry); + } + println("Warnings:"); + for (StackUnwindWarning warning : info.warnings()) { + println(" " + warning.getMessage()); + } + } +} diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/EmuDeskCheckScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/EmuDeskCheckScript.java index 2487297a66..811eed3ef3 100644 --- a/Ghidra/Debug/Debugger/ghidra_scripts/EmuDeskCheckScript.java +++ b/Ghidra/Debug/Debugger/ghidra_scripts/EmuDeskCheckScript.java @@ -143,11 +143,11 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI protected BytesPcodeThread createThread(String name) { return new BytesPcodeThread(name, this) { TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, name); - PcodeExecutor> inspector = + PcodeExecutor> inspector = new PcodeExecutor<>(language, new PairedPcodeArithmetic<>(arithmetic, - AddressOfPcodeArithmetic.INSTANCE), - state.paired(new AddressOfPcodeExecutorStatePiece(language)), + LocationPcodeArithmetic.forEndian(language.isBigEndian())), + state.paired(new LocationPcodeExecutorStatePiece(language)), Reason.INSPECT); { @@ -169,7 +169,7 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI } public CheckRow createRow() { - List> values = new ArrayList<>(); + List> values = new ArrayList<>(); for (PcodeExpression exp : compiled) { values.add(exp.evaluate(inspector)); } @@ -254,17 +254,17 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI class CheckRow implements AddressableRowObject { private final TraceSchedule schedule; private final Address pc; - private final List> values; + private final List> values; public CheckRow(TraceSchedule schedule, Address pc, - List> values) { + List> values) { this.schedule = schedule; this.pc = pc; this.values = values; } @Override - public Address getAddress() { + public Address getAddress() { // Instruction address TraceProgramView view = getCurrentView(); if (view == null) { return Address.NO_ADDRESS; @@ -352,8 +352,8 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI private Object getObjectValue(CheckRow r) { try { - Pair p = r.values.get(index); - Address addr = p.getRight(); + Pair p = r.values.get(index); + Address addr = p.getRight() == null ? null : p.getRight().getAddress(); byte[] bytes = p.getLeft(); return type.getValue(new ByteMemBufferImpl(addr, bytes, isBigEndian), watch.settings, bytes.length); @@ -365,8 +365,8 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI private String getStringValue(CheckRow r) { try { - Pair p = r.values.get(index); - Address addr = p.getRight(); + Pair p = r.values.get(index); + Address addr = p.getRight() == null ? null : p.getRight().getAddress(); byte[] bytes = p.getLeft(); return type.getRepresentation(new ByteMemBufferImpl(addr, bytes, isBigEndian), watch.settings, bytes.length); diff --git a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml index a34ed2bfe5..1045bbc448 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -153,8 +153,12 @@ sortgroup="n" target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" /> - + + Comment - a user-modifiable comment. + +

Action

+ +

The stack plugin provides a single action:

+ +

Unwind Stack (U)

+ +

This action is in the main menu: Debugger → Analysis → Unwind + from frame 0. It attempts to unwind the current thread's stack segment, creating frame + data units in the listing. It starts by reading the program counter and stack pointer from the + innermost frame of the current thread. It then maps the program counter to the program database + and analyzes the function containing it. If successful, it can determine the frame's base + address and locate variables, saved registers, and the return address. Knowing the return + address and frame depth, it can derive the program counter and stack pointer of the next frame + and unwind it in the same manner. This proceeds until analysis fails or the stack segment is + exhausted. For best results, ensure you have imported and opened the Ghidra program database + for every module, or at least the subset you expect to see in your stack. To view the results, + navigate to or follow the stack pointer in a Dynamic Listing.

+ + + + + + + +
+ +

Each call record generates a structure data unit derived from the function's frame. The + exact contents of the structure depend on the current program counter within that function. + Only those entries actually allocated at the program counter are included. Each field in that + structure can be one of five kinds:

+ +
    +
  • Local stack variable: These are named with the local_ prefix. They + correspond exactly to those entries found in the function's stack frame in the program + database.
  • + +
  • Stack parameter: These are named with the param_ prefix. They + correspond exactly to those entries found in the function's stack frame in the program + database.
  • + +
  • Return address: This is named return_address. It is determined by + interpreting the function's machine code.
  • + +
  • Saved register: These are named with the saved_ prefix. They are + determined by interpreting the function's machine code.
  • + +
  • Slack space: These are named with the offset_ prefix (or + posOff_ for positive offsets). They represent unused or unknown entries.
  • +
+ +

The frame entries are not automatically updated when a function's frame changes in a program + database. To update the unwind after changing a function's stack frame, you must unwind + again.

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackUnwindInListing.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackUnwindInListing.png new file mode 100644 index 0000000000000000000000000000000000000000..35b4e8f7e03b135074ce676e6d4e6c7737b4b3de GIT binary patch literal 46585 zcmce;1zgnK-Yu@8phzeJl7os$Bb@_?C`flVNFyCXtEeb7DlH}5-7VeS(hS{0H+K*C zoacGY|Gf7-=l$P%XZR=%>Tmycti9IyuKoKgBPD{3NrHLl(j{!sm(S%cT|$Fgx^#K_ zIy(3y&-rNU(xo?-M4t;Pz_eB(F<|)phYj~f&8TBAV`ySwvR~dmcn|Roc`SMP2Bv6s z41UV4@Wa5TpPt?ldgizN=E3!cZ!g`yW%}~on@^ZSw`gLg70(8JT+a?V3_lL%<>iGJ z3kA=(MbGcO>Nqeo>}*>bj^s=Vx_ABE@2{IX#~XK(-$&dKymj^bYlf!XN8Q*G`ryF> z&^pxXXB-g;Tv|Hv8tMp3k_36Q)c=NHv zczC}l47eLNsRh|naA&X8?j*YIcQ>M&-*U|#CnmkNED(I~9D%@Q2{$*-&k@w$!sgG( zk=6I%3paPor5O%wZ_YQd^=4L=yA~e#wOld#g>(xC?>7Uv-j$cP&5LsSqZIteISPmK z>_<9Cir5hutlh>wI<(ZPV^TjsZ%!MB=adtu*l zjH>S7bgfAkk>Rfl)7ieHw2hVsCU@COBP*PL?kOxy^s1OGZ8PD{~0oiIn zBeD(xvYr69Hh=oC2!sRK0GFT_d?+9Tnc^3>J4422BE5#~KjUuL$s8pAE{?Ww$Ca^3 zkvnGd19_tYzv6)$qPV5G_hW?2!HO^WCN4)d`p*5Pfb5yU%a?w_{(Lu~zJ+~JT;|}O zAwchFiQ&K6d~oeWP(;>rIvFQvAYx!%soaqX^4GVP1vEDo?z5+UHY_vcLc1QVz3=v& z)yN-hYp&gPh_g!Nz$!3$*+B`%k_}SfvUjT9G|yTSX+xA_{sON*O&Jb>Ov8QCIJ%_a06I+F#zat$r9)gQB9MEqhBO82h~O zKju6qEKfU(0+8b_>ogMwX@L`A23N+TcpWo8A@^7YZy&%Mo7QV@Pu$7Gh&GpMK3W@# zcNIvGP2A)j)TXR5(wHBsurQka;wSPM@6pvw_qAynL`+BoM?il=oKl8bzU3Kz+2Clo z)1CfI%@JK~M`~kkU#^1#WgI6ZtH~}|XZJd45_V^@g!S#5 z=qp0@W?88?f(_}~yd%ZeX+0()k039qIDKL#lwnUk8deE}Yq=p5iE#h=#wEr*rB4>t z-&yLf!B5vHkrUr?yf&eJATmyH>~d6%K|x)GOINo}6CKivWofIWrx&|9lB+><=l6Y{ z?QU%S)SKmWx^Z^8xt_OK)^6Z-cETmAd=T|S!M*H&^X+mc9f4b3|6oHZlU!%?ylxbIH{=SyYM|)(Gn8hd)V#0N|!9Sg^tf|miu<`4U*cT!LzE^ zG7$+o2vT0$9kINdMMsL;VarB#F$;0PjIBWGC?H~Lj+z=v-krrXC3;e9^iL=pO-x*E zcipJmM6OM)fuW~79h21#h`zC(W%{%j>o^3lwWW7lvAV|2OXtOksHXjNAoFNGjf>6; zQnIYz{m~HxqXtiUmjH%8uekWp9Js5{h@6LDMfo+bLpoL>*-Z5wiv}dA-m~-{ta^K8B z^-WlB95o+bq^nq9?8Mo=K(&zDvlPuDDr#$6j%$v@BIIS|5|O3;aq}V)NptNkJ=?_Q zOs{%%Ss|vZiEucsVyc4Za20pO!CbhO5wzG>i3oS+&_JP=*>TOKukbZ*s!C4m=HuYo zCsf*QJBsbF1vqkRE6%X}dw05O-WUgDb2$~i5IN~~mWfQ(s^F1~BuOB~;h)d2fEJJ9 zBznF@z5#FQ;07T}{$hi76m21lnyG!r*ACI-j-}c;VUq(`?mytcxA%lq84HwCsuNO|u}OKb}x&^LiAOl?oFW%`&k<=%rd4 zLu14`l&zI;_{<@`bq7`PSawgVs;K-#+R{txI{EgxG#$rYu$v9Us}-2ZM)Eky?Lo#? zc3sRTFQsZE z%%C+mVNly+EX~(Tkpbd*V#omTBVmt);0j3CPK1jfTXrfpThjWnb%l5lF@+B06r9?w z&7+PJZU-!o@uBId8wlClSE9(lmze0Qjszg zbNv%9PhZb6KN=kqTm6-w=kvsF#1#xc-NffSqfWxA=417aXioNqton)1HTyFK9UENu zY3fjg*+$poZ(E8HK|=5%BKXmIRkW7dD#7=SY1pqkBiz;Tig4jdrU>Ufr_g6nM9)3F zKK2H5^1tZB_V_sN5A%s4-_0Kqn2x-@nWZ&08YZAPSK~C9Im3;E z_U(1!2K~)In(awXT)7gP+2>}Li_qJm_(WJCuFxvNqI@f#nww926HiZ$d`{97QXZ%g z`l>gwy|!#Q3zOLIF8=guZ8E)V2Ol+33h zbtv!D49aDEFD~lDrHCQMqq(*e? z_`$~_vaj0Aon1EBNH|Qf`0!ZXA9_KBarc%4+tu0Rqe!*r?GRI$2VbJeN8O@_VSQzL z+OhSBjL!je=~|=HNS!T0JLw9S6rsir{3@N#n=WCDYEMT`B3cZX>EOc^i1ny}7S_+M zj0FBAN1bTI6$dibjdn}`l!<$jdZYzT;zQw)+b0J%-$|D3_IBF}bbGn@cZu-AyQ#|X zd*D4$p3!pQyA{TpeI@9LwI#?KC9T9qX}9NMP3>%*%I0w zhdw%)2~a4q&=f*+XK4#e9Vbag-}l85xTJ0X-G5ssKA4Hm$J&s9lu?H+4Q8#&BDxdA zmFw~832>KO;idM=jP9>K^6in0WrS9|Bf_=#=<1^PUT!U@rQ+aCspGmJF5k9gFClzm zBWAiHde1$g7+zgX9=N=U(Bavh6PoC;rhLr`dFx%GS!|W_rfB$ja)W0(VYoIVyXorH zt6e|82s$*x8KfzB(`Y=*Tt`x&%X^|@vm>gbBb((Ddw$t3*Pkp7=M`=8JM{WCe(B0C z??gVB<XeKNR#Srz zJB6#oj{PXPxy7zkyog)0Z)UVW$a-V_=fRmZ6)ejM2}JdDsB5|J@7cAnUK-Xm9mNL0 zCZYX^Xtpxle~LD{mvs$WL84LIQaZN#j#lhjxPYMB5$=yl_->Nd5&tt?X#pX@40W2$ z>yEwD_30`GP>tp$*QWV^sXfIP{@j%I>OB3KZeBzjKQqW&b`5DJ+!~FirVU!$oS)@? z%%)i!PQz^vDG~VO7=75`UpR!SQ8K;`fzQGQycWM;2arb(a zlQ}ez8rO34@F%N&4sxL@2957%g{d9Z7QrpQY*??&e|9|oTB=Mf=V0>e-m;&Fxp}&A z%j{ro!o0VI60*hY1VM4e#0EJ%J*^?*w#%dafn<6n07mszNVq!suY42AmIZTP3{qwb z>=roqktKA0>U)`*+eWp`SFInT;%kQW!VA%7$3|~`y+-@Jk5+7MdWRt=Raxe!2QwY= z^lE=Hpyy6m4Z_+!yGbu=u^V-0bWBP8huui zVbM{lmZ4S=Y>}m=$O-`)eQe2kJ+VW<^pMNeG*+1!a+xO-2mQAI5HfGT$?eVow8KgX z$I;j8v`CI$OZYmJ)1NP<*2%ct#GcLH8MKoqnX8JP}~ZjH?rzLNh0_KdhCeWp28vL?%kY zWh>=vpQkl``Wja+p}ZP+#Qe}aITY!=*Djrgfsk?eyT)rWGueg)mv4cDHiwj(0KJt3WHgM}8rt_Kg7s#YXvIKm-(fo7&UL({TfR5SKr z$_$W0W=O;U9GRW4WeH7`443jFx%SE8_0q}IUPBvf!;^2lc4Hr#VA!hI3|dT|iky7S zk#YX(bMWh`2W?gsC#p$bLyVw$1kzV-V++@~FF3f)&5aNEy`K~55d-2Oe-h;<^| zVb@}P=+MiiL?I4{^HxkIa>@izJ&|`zVs5ejM4QwjWUTQzg|4yO%8`@IQJ++n$ak zgcBWAvue*$Pp}^o2QTMSSdxf(!=*!hTWNwI4pwZ^>n0hy9 zbQr+cWw{-hyTfqe)zQ5tCZ%5r%b|DM8>_He)8NibwIIb^Q>#BKQTA&z%c^*6_yRT? zq+I85-2ZmgHT`(jxHS(5L`yFC<*6s~hce4Piv}N8i>g z68HhKa1Y2lz-F4hKKaC`;kxqe5l2AcckI(E0e&JGY6U@^ldeS=AzKG+z8FQu#te|0 z_1UueS}>HAQ1n;z-u-%K1azh#0Rb55>=`D_^B#;j)jS z#Q_(v;5Z)@9=o-8A|DPrk+HIr#7&n5=hK7v7o7TQ33x;CH?DEu#&T4X8z(bCPI1m% z1pGRfiU|+VsfNW^lpw=9H960ZxLt00oVP|OwTb8&Jn-#~pSy$MNCAvjxT6y{cQM;s zDa}Ma@iQU3kD=La?bxZh`UzbA*XN4-c@2)${m0_Wi&0coH^P|w#j&BDG zDq-5oiG%!D+iE1Rl+&yV3+pr=>Eo{ zFPuNC4~3!AX~Fd>Z75*oXlHxxv_~`%)yQzLbBizLG@^T(O?vHOgmd1trbOtHCZFaK zLAjKNLomMEYOs?YPXwsuy{4N7i3Vo%1wj=2KtOjm>kg^ zFDg&M#q^vNf`*QjPl1RjXM`UC{Iwb^9@O%X&o`~3qQ^sP=guSn`)xTh zA5taT>PEwF|A&hRx6<;k^8==I3|%-5u`<{Xe8%lI)>+RK8+{gJ+r0?%p+ICa>NKHy zwN_V&A4fJ0wp0y{YJS%azk6q<^Sdj=2x#Xpqz*6u9-W@D^@N5SLpOU4PVUWT6piB@6`J**-~89gz9P$;Ne!M;bKD%5<^C8R zRVwpCdMlCS>hXk8Sc`&`gh#Lf=uWLf(RkAVdW@lL}gzlgEZJ(DJ?0=SvVqGJ5H;@dAlVo>wI{xPcv^vm;I1;$JPeSISU7~7gqvGfBy-0VrWk`Yba|h24ng6S{PMp@H{AM@bvK5 z=ah|-3i0slzq};e;HeMHm9-ny(b}hwNl{5W!XeOZC1Q?y8iic5s(OB#M?cq}OqgA8 z46uNh*~vMZVND3G)6C1(No1p2@s82sY?C>cx0~YNU2$mWU1)ycpFBvmY|zam02P-) zG{ECT4*A4RFh0r@c~JmCFe2Y;h{FrbabJ>ViZ_1#ohJPuFu@<5<&+{$+k<%X5vij+GvM!6Ld z3_5+hmq~ieS6@X1JjlS+J+CdUj`h8E7RV+Hhce%dfd%55Bmw9RH*Ck6gTkDJ$RcQ7 z32w;n?ztn~8hQu9%E{DejigiGw#4WPDV}tu#u_7)z*)=vPRwGhERA+EkDY`RSyqz~ z|1r*r0#D>jMVTtB4d&{&#^D{|iowSmk5lk^?;f^`)InGM{@o_nh6iCW@%m?~s3x=1 z59gEI>e4xZh#Mlst&BeG^uY?psf2G@XHHH)PWk4L_?!4e(AJq_*eOHwx>dyOHGVQ= z#q{!AmFxP!$;_h70jnKT>u$+3)Ia5@<$M8*GD8lk#Sqo78at+IG>rrKn)%zFL2Rbm zD$G(_+TO#HRdHHGl%*uA944B!!h$WbLmQ4)wAXV9L=(rn)8~M14q=4|bA89z^Y!}G zzl8n|_Y&o~9Y0r1ii{@euB-g5NW04ACBE(y=+T2&ijWf`u#gx&*$*gj7c*n zvY@=VKi*$ZRvoo4s{Zm#8{?9W9u!rO0k59{0NaNnFaB`p=ft%Jw2SX`xDPlW(6NjN zWg?h3w*_JSs`F*kPo8$yRKqsVGFkU8Svp2xt}Xp}gkBV>`}J7V{mE6)fH(>GSCigCa=wGUynNoRTjWqzUqVJLVFvXA>!bbS>BX({ zW?iykrTK#V^c~6A4^+NFsRXg2Hi_J7jb8 z>k-&kA9f6FQBKxz_LbNo6lCLKi;yc!4Q|c1k8#(_jk1i7h|xYgbBn#jEdye1F6xTAMm8rQGLfYzP-M%cMkFE_FQ)UiTvUeJTG(4MP6F zP~(d=3y#RDVIb*EZ0xG^n0LvV`-n*(>0-KPxG ze(Bhfp+i`AtsKT`d2sdDwUGuG^+w6IHAH@M>QFMa;Ef*!{J=_y z-%zh^#gTEG32}SR_8SH2e5J~>PNBy? zIuJB<=}L|Rw;Kd0;BJ=uV~!qZ-3dNMMbno@J6amS&M)7Ib!RLs)F{erJjNx|)i+{} zofr1g6?m=hn|1EwxEUo9&R^bfwK%cP%nWG!j^*MWPqbG(_c^O~VP`mSV@30cFR`Wa z{-CU2PQ8(=eyFJ~M*F8nk;rij^qn*_z{q35K5Zk$O&S)$6fz%F_@fJ!=XbGFrIxCFHEhZXhh zw};?blXJBVh`QzgyV-f|g&w#-&)!|eo}!89q^wRiX>E@^zQ~hZvdDuuUgTcc*4+iK zR^^4<-1W}0gZ3$6fhtho3XKzi%c-b@!n%xVYHMjYIR|q?qa0R$5r6mj&TiK8tH5k^ z6a4XoW~trn`81WB2iOp(O1VFYcCpoc_Se4xUESQ8Cnlm3CBmkyw8FrDMl)NU9Kj)k-VN>sXFry*FxK%!;)ty(ef=fYm}*Xwp6sn z*gsdJdXpM!2u8;19+7#xee=W$`HhKGJL2Fw|IsFW-??oc8yFh)0JfCk zxIT_E|HWywU_Hp@G&683P_9E>jN3!G#i_#p3!k5yr>-|ukvK3QK-`CLRigt|@`VFIdvWTBG9Y#0~dppBDa|>9RkQYv@OE8IT$j_e>K{iqSJ=WGn zpiOfu=;b~O3$Hf!;5JQ;1az5K8)$b}C6xt=Xm(gRvev%W4Z$QvxxUg$W;{QYJ!KPk zU0GX#f?jDfgzWnbP`dN=#bRh5i7-<1owAS{b*>%}W`GC-OK^8(I4)rr7D%ToWv-m6 z@VNm_A>E&qvZfU7jKGtF>Hn8LAiGi!dLpV5zi0LIZw|oIR8gvu_BXMWcGuB zWLe5G3t8dEwbbx-^rBZ*KIlc}zNDP~w91+EdZU{$9(-%x>NDI# zOi1X3GWEy737Nsh><5qaU$OXZ7Q28Mx z4F9gK*4Uc_cL*IsXG**;um5b*2(9<#Emsm*@RsTR;5~I25iw8um=v2E8(dc9tvM8j ztR?m~rN}TboRHz2@u~=^&L8yk8gTuPHwj5d;EmBlS5Qk5^`a)qMgHUgIjQp(9PN+K zv&sGy>Kj$ZCfKdiiI%#~8Uc5Kpww!S#m#mN>gKddN`$+yH_b{>DzD1EDuRNj`i)S|zi0vdm+ngP? z)PLIlh?rIBFQ7bhkU{DWxE&`8?U+>UNFSPYAT!pHNIM#v@AW>txeB8BOf#94aq01MtFZjd-5QR)BXPa?pF6 ze}FTO3gbZ~;ZYTf*7bJ2&bss!+byK{$PWDP5I&!|Hov&{A3o=s*ORDKtmy;NaL4U* zcF=BWQ<@1f?j0RXVesmevB~%xG))i7F<^S&<~WBblBq?XHOHtE!qDDOo6Ot4&>fdz zIZ=soAiRHYk`TE1e+a3ezdV3acE4Id&a6u0@3j6TKH)Q+$!?Rif?%186`K$!^F@_iZm?99xk)yYYnoD2v1+y}vdfzK7wl=>y!qd;hRyn^?` z`}dy^Lw4JXy^S&A15>qLDQX3#`R*sqUI;%K9OV`V4RZ_(ZGsVg{z1)#H;!HD!x|(k zueeu7ov5{Qru{p4!B*3r7QN+JUnu6!iF;B&XxgOx*>h zyQ5rc`LDPiWsE&lmn-b;&^%huQDU4W;}aF}kBp@(iSFKw470npOnq{^ybk*Olj`z& zf*fH#lFzDXeW9op>*sYBm?<+CN3_=$TO*QfW?LMNBqF$EiO3IBj6w@cL+jr-%H+O! z-+TGY5VTG;?W3v$1aUtJ1vqrD%@iXVy&eU%LK}wH`wtaEefV7RjL)hm1B2W&SRo~i z0!iiuE`w2?c55|{4m&A4c%X=RKtOS85zKgac=`EIrP^L*CoC^$u+W%DG{*xLmTx=` zE6E8?1L>ed+XDnA`K~=W*d1OnK^k=l(I%BXHd;S4AMBSl@%73JHD{Y)1MSeccAVX* znf{G`^ksNqd>9EF5$!Ij%H6oQ@L7JF&evwwOE9ITmK zG%BIPuU)oGUe;H+9l(s*{zv)Jz+N%4yeuvm&XMrp=G|ZQ;cO<)@(kN;xH_=3(tyOx zU3gXxfDq3q6YRZAtam~0<7D;UE`6Y27o!LJ$u(c%{f`X_b=B!SR-De=Yk$}nZ}hJC zUP!1*KW+Z8YXtu{I|4=nnhK_LWGh`Dsbt#COVngcZj4~M)m&hIPxqOwGOo@w?WQz*6X$toAG@5QOPgUhr>0|)Y^v}+2 zZDAg~IyDs%XjAI{w#KL%wJ~-A1Mz3yX1pE4oQd*JsjI7#sc_!G@CVf%Qiv$xXi=xTZUP0W`0FH`-^PA_bXC3!@suD`*|rSKooVdMGH5?iYN6nPP|M^mpm z0#Ix?^S@%lvlaY-S%8zylP9>(&GmO4RO^jUJ-BF2_U)}*TW3txGiF=U=352Poavin)3nqI6wkg7`<&s?QE34`{S%(&z4T z+cu$mKuKA@qFBDVyiC_%pUgZyXHBum%mVeNyk89?^HRlnqfQJMaki$iKms$QLoc`c zH@QYN7>1tu=FKH$G=f^R!ks*MC!PzwdAPeum)PFk))xN{{JO*J&R{V+Ie=ra1Io(+ zfV{)zQr}z}X;Z}N)Jur&Z&3v!`AF=ZShcjb7tkkq4URCe+(l(33(N+8&943`@F}%h ze9)nsqxXUY3d=6!_@vr~r4>R+zmjV+$}R}of0PiBssCdmjFl>gge_DuL0qKgWUn)^ zuFkGV?`NSBMpmb+yO@*UK4yYDq&2PV@oy@3-h;}-^Fj3>Cj6g+ltx3_{eYfSpP-{O(9~wQ_wSywg4eEOs_Cj*yR7s#3g;kQU|i zV#JoeoPh1t()_Vb&gz}77hb^7rwxaJuk@RK+xA?X;PR6uC?qh?9zy_u;cLd)-NXpL~7e z{mFTGX3D%5r>x0t+-N{>wdq|NZQ0=Rf4^FKNaKgJA`I~R7=hyH>F2xnpHwLDFH~r- z$WmKON-D9fO#&E4i3^zp=EE!@%&h-^;G^s>@PYPs_;{eLt!?@%pN2uBxGN)+7${)l z1p}afZ`>?Sgl{PyRa)h*U{QNpSskzS&?$mquT@YQR9Irdddl z*&yT6U*)URAiDM4-;mK^_HJWCT#Q?@@#z$+krXYvF;lo$h=2UidbH%{&x;oIZ(e8M zXhp{z2X4OGAOPlCFlq*!-^1_Avvkn;Y+)j3XlPo{)X~Zan)t}&KY#ulPW2LfH?kC23Yh!^?QBr))+dppHdCavELIJb4(fS&p{8(C)J0Y;w9i=Q~_U#sK zNvA0p$Vf>sGN|PXF{iPP0(!MHQ~rQEOCrkVX-ae+=}?Bo8vVcT?EQauOgN_{VFp#X zjgySb7bfVEyWv%Il+lElkJH{5pJ3wu0f`|ZzQ+Cf`KwnC?N^3m z%?FcZJq}@a336AWGeXfY>f2La=B3h$Ygv`A)uDe1OCyS^ z7ZXSe`in3;mkKv2+1M29KIp+tu}L*rps$!Vq)H#KL>tVT(gmGRM6AY0|#WBKPFZ;Ha6bRQZKUT18MAE6)v0!NlEe` zBxzjp{>B4CI~>RVM|?&C?VQj2X?Z!1Cd;; z6BCo~xUPj%<}0E*K0cLe*-Z)VXyQtl(X=H7F0`Rx;ijsZRAprVHLa=72yj{^@Dtn( z7nkOB6WpabP`r`@GKy`cz02=CL?pOl)bIEgrZJj9Lq#P5Y$Z?$C`bz+$cc}Cn5tHg zYWM&pZ?wQZLG$q8bF#+7IO%BqU))Y-`T1-)?H(s5C*=N|&$u;cMyx@l!d21yXkWt- z)to#-Ta%_Qv-!63F|NR&=3l4YVdczn@4zJ-;lF(W*PedYEse;VA56T|{)^_+9pKS@ zkuDo242*boLzF8%WNx?mOA(7PODTf{B!W@(h4j@dlE{@6^U~GA5i)ReET3e`+oXaDI>ktn%zD=LF+JHCisp5yD+tIwVr6dvz{B z9Xmlpfbih(P_8#o(qyXUgToP=i1}HYX@9PPc$tNkAxb{QgaG;Un11D3lPv+-_}j1& zCeT?Zm?DS)b%k2A2JTM9zg+BJ6ng&#Hy6Nj=`XTNDu)U3`$+Ruf=nIgJ@Ep&MLB?q z-$EbXeRpsDmUQ$@)PZMCbpWg{Jx+G+f+3ZHF0cYO{KxWOmO5c?8lcr5=VHN84?w>4 zLma1St}RVeRdHzt(JC5mFLZ<1z+UDCP3cYg?7Z_}cE@~sP10^Rz5K3%idxm_Lv}Ml zX@gd&n8%WH^1=;+7WF?!O)Ik53sn0W7}vLhdi%@_o}}U{igvjeSn?e8s^0cQz5%DQ zF@;bK%E`&;D|fP;jX9)`b1?!vF!$}9uPWJ?yx7s-uGt1UufpX|7 zTdz!8HA;L^d2gxPGg*QdbnA*bTTAex!<|7=lY}OP%uzqK89ud&L$3Zza!R9{VfEg5 z{MrqzhOahE5NT-MKc9(WugaMkI&Qn%(0$~6c{%iEp4>?OM$)w3B&4RDI!4#&`fhKf<=!yyr>&}}Bp3a4g2$e~n$ovEoVL7Mze{m;+ix+oABhWpsgAhz4A{V#H_u9*5+o_cn+;}x zBt1JmU+`X61MGOaQF?pMpEG5IWduVT<ZRFEaf*e9|#!m4`ZS zDagMvL+)?)N+0H~N8ziHXN5!wl6?7vK#_($7WR{CHQ4&>|>_*v+;O zsO1^@Eo26Th0*AtYQn%VK3q!6dcNGUoK?k0{+ix`X}GYR<0B#=WW~=*&b7OxI)i+4 z2|1u{o@`MUQ=<$jwl=|!K^=OKl{7kL2rWm=dG~a9d!@ms-l6N=y?^|D|DxpCE1)Y- z=!<%CL;5Y?YT;#-FgvWMbQ5?J3%Hjz<9GpXrDSSO5N3s{t zatD4NL?#9oI(XOPK|!q-n3DD)JcGFg&reUE2hruJ=5j)oe3eIqe8EicEEh4HtD#pK zwclGQTkI9*RJ$&%0lmMeqJ6yoVZow)EQ<)#xq?v@i}QJVp9uGl$4SuN^~Jb&jKo#J z;F}F-e>`QP^%~+bs$ap^mst=}{{8TYCYx&5(~FO9y(&RrbUj`87UryQ?^0>pJBZOk ztyh{f-eU@7w-m@eEZTxe@T2D9&a;>;M!AqbIweAx-ezi7S*S{60j)y%{XLKU5>SW! zn&Y4nk?e7HI_(P1Psen}iSz=OY-Ly0z)nh8&RfrzF);Rv0)Ep?P|XrG9iWeD366Xw z`MoX4*Eeg-TX1)&vb-!Z=8H0$vaJ4iESR0$$qaG%JHlE+o9~)DLG!(%fG_d|7G$lZ z3euGVwsXFKNVZ)N$w!YK$<}_v#+G~sy!(h3z!Z;9igy3bs9h-`aJ+$rO~UpmHZE>< z?7eS?$>RiSF;!nbFG1ORPh_&R=oTldZBYo&?N4q*;K|jRL1ZT+N)^b%l@;TOw0fL3 z)IQKw!J1P04QIRk$;9<{Ay<6}hEN?7ONBx_u))ijnx+Oz0RvzD`^)s&YsJ{90R4{;N3f%A@aswS^+ z2O*0Ofsy7(mK?@};?m?GF*v@AR7KhyZI7flNW2z1V0kelX;7Su3-A!40J3pp%o~Af-OV4fQ>W-vzK;Z7d895y-~wg ztlk^#aY|F@vb#c4C>}&>pi0ln8*bW{920h(GC(KrE*{>qS})wYjX8_gZ{F--J~VwUq@OYDoA z%w|EfsHQX;>i>LmSYTHo-RaT9841MlVFEQ^s*1v1#Iu6q zF@S+K>2zGV53ohq=V}5Dq`P0R8O9Yr7SPKn?=FARH4PS|f&^-K>JZF@7GtS@Pq}2~ z!;pGQV`W7{bA)hk%v#EPKB?Z*k@eX)#yjtCGXq#9inU=gp%Il~&P=C*Wz3LrKV}r) zdLfTuFHkf^;GTrO1v!x40@>gdp6@2=;4e7annNp~B;3_&!)lfX!X zHTV*PZsld+X`{y7#0jFlec#B0#Yb?rlt3M7_^RWFlsIp9QI#v*LUBH2McKNh6DL5P zAagK#xZv8VBs;PY^L@e6#+AlpcEb#XAWrL2B6O$VTVmNuV zwS7><${oxwhMw*Xo*CE8-Hvr3V71wLnT~=kh3Ee!g8fFqT#Y~3rz+#$0rFJ}%)nMF zru;|m^s`$tP6Tt?C7|PEkOb$L{$C-R&;JhDJS@lEwbY)|B-%)kiZP2aB&uT3x@Ks5 zeEZJslayi-rk{m6_RKxzH(~q>$NkrYF%d9s4@Z|Dg+w9C{;Bt@l+;h8lve0#?54;gEzqhYZIry zvMRK}S2S^J-XlmTF=v_)mCrj+);iJXmNyS^ruw5Kse6K~5=h>j>Cq!SXC?Sx27f!x zWe_r`eNs(R_-*BZQ){=zQifEXzjz@FoT?XXgM-SUw>Y5SIZ$k5lzI>Il(50bkkkGn zmKXAmaK^ty{Pxxu3fD{KAwWtTpBaTWS}%AnGBwnyV!g94KyvwKB-!uClOU!pih}<` zeg`B0iyx4&bwCb2GAy?df!YSiNlXLX3`xs8FGv0fSOkv9Hbudk@BUdh{~Ofpx$i8s zd?FU8(gyeR^yCj&R-Fg~E^$XQYpr5WORdBvVeRGo^aGlDe9dH{gwO|_ieS9L!fpbb zznj(1%wdrJw-Uog6w{)+`tPx?e;>O>jny<+3AVYeztgzcJO2#8*N6LnemvCD)*mgl zo_-q{#-w{iv&wxeH&hGcLpj-Q*e~_PfD~z2`OUkGGH0hJ^#>?}5(JeKL79GdRo^Zm z45o>b5Zc21n!5gBL8u{f@WZAUipj*Ur~(RIRt`u%Sc$suCHY{BS;~x$_u6*uJ3yDk^uuXwSRj zzhh&Nzq2v=10qUR{f7ym@pOrDQV&pl&{UTHR~DvRpaCHBiT%#j%>;MGwhJrmpSaiv zibT9i#{)}I4jejP0Kp@*!|8e-1yB}l)uUf~T%U25{+v-XAQ~w=`g+WvEg?^`Mr!xD zSZV4XYBC4e;HA{F`2Y?kyt9Xg}<>QRS4=zR=~jO4*na@g+Zz9RJ9eD>^_@&3ltG#3ZC zuCDGcZbf2pqsMDuLCO*d5rwaJ0znV(_Enzfk+41v>xe#6#0hKgE^{51U7FW++Od5h zouj!FdC>}x7T#$*JN?lEg5R%E6FFB7MMTaEmumUO_YI@v3{Y1bGRFeBWZ>a}1m}wj zI9lCh=IM|)uO&3`D*PiL#)v|tP-ugYln|KTUkJ|kMkEoih5LIpRj{QSXL5*y^;u@} zzY;#tk^oOWD{Qr$=c)h^h|EwNeP~4ey#*ygIAX|I?Jq$H@IT#p_2FDPpr`)$aqq85 zh}I^0qFHQmoTs87X{C z!@wXpUcbS`YW+1(&zn)fS55*Id$=G+>BT;SOGM!;N`DJ&{n0o7rKI~;ppvPl3-#CL zx_RN+Q9u8CsB3@ZVMC(qnYV0TUfn~l&@!h>Dj)tAW8WQ5_5Uxffl@|`G7d##C!2#% zHb-U%l{iNBI>teSN=Af)WA9bjo5IQ7dy~D#aX8lRbyT1G{eJKL-N!u-|2XG8p0DTY z`Fid#IM*Ha71lxjriWMOr8B^ z;lns__<@J`C{pZ3Qa=|*OK$DEPpI!6^aKQB7eM&HYbq_d!(MqT>3z0Q!F~r1HKkB6 zr3%b(p#4Ur`z^Z7mbp|&r{ha~& zl)_J7&y1W%wdyq9kNtkFix}g3gYqwT%Zb>ZvyH9)vr)gYi}%lE(5)_xjAFV@Y=KtM_xE8j4g=_O;#RYtg6nLJhHk&{x8aX)TfJB*Y9&Tm z%IHV5C#yb^%t?l(dFHfY!ZW+Rz&-y~mv1AYyL^N(7_pp?2GU4IW&>rtk@?!Ir-X-B5 z%l4K#t|n{JJA>Twtw+uEo(HmppJ8tH5{?Lc#@}~yuAL#Mal2&(}24kVhPZ z3|4>V^FNG9@C*szd1pIT@XUgBX-j>|y6#FA?GBH3a5U~^sMAPt2MxNus^}4vVarnn zOD`eMD}$W3skmg)Z$)WT!eKs$={IwFB58C_o_?9~SZ8*{+hBj1AKo=I<2L`ZIL*vW zhODv=+kj0OtQ7cx-*%q1G2&}U6$#k3)L<>RTAQ}vxg>$yZYj>$MNG`Iol*fev3%^S za;i#R#BF2bUg=$aYq6q{NyosGje1{P0ld>|_-sXnvir1$tM3O;9mZb=RyCUrtuD!s zUp3)rYi<#~S+vJ3foR;nev=-3tgmVG%(*>rrQDu2=ut4Qw-Fw8MP*SAU8){iO575k zXl^`@nCK2|LWntUou`+uCD^a^!|446mX8srq>4VX@=t@65o<{-t8hsplQo~J3Of`k z#=0t5wn?eC%Onr(=iVNJ%$@1yb)%PBbC2AC6vl?>l|u3$mRMIAn-=`CUAp%{dWix4 z@Okb_1elOtmEmbqr@yl+nYCsaMz_QBeOYMLsg9g$lPC9Xxl{gtz4Gi^9oL>uM(MqV`{X8LSfi_^p@uk{2$06d*B^wLgOH|d;O zoTo~W+f!$ZtI4NP-h4cGyR-*iTFECX6;v*J&h+zt{}0fLs(R!J*Ay+c??0oQuBeoK ze;f@u9K{?-G#wavTAdFG{s(_y?K5xfKi6M}JP}{tStzjDv*HZqDR*+7tlB=q_R!2M z-;L8SzZW1ntT~=LpsE7L_am;9U(xBjPmR%)?fp-cs2iJN-JZFv2C(1*@z-Pg0pHr) zC%?qu{vxc=_)ZJ<6v0F#zS^vAnWtSamW9aa9yCnJ!z7r;HU4gZEu_7vPG5F6uY8*L z*Yq}WzVvv1fb4=Pc5-@3B8eBB?JP8QF5f~hpm9DAoVU|xW|jae0oIJ5?hlmPLoQkz z;`GH4*}5f)7ge;4Ot3>2yDh#jFi$UUG0%}^-;3?b8)a!wX}Mn7wt{WHxQAMh=m%T9 zBfgG^ZG`0}NT;cEwpGe5U}VK@{+zT=?>of=n|RB)_gusXYI5*l-TN((?LN;+Y0ms0 zSB_y>o4c4qOwtzCi8iS$xH-#zca@fUF;*3*+!9b;lOKYbsz3G zI5)J6X&EDiIJ6S^(RY*@D@sg<-!SQa`cSE19z`<~H5z$P0U?;^1J&IGHqB4BCr)Rd z!h(pCR~nvKjNF@ewmbXE4dheQbmMoy>c;wA_pO>rkQ9W^RQeMJ`d@Q{M-o3GQeWS1 z6HH=26U-^eqvU4{(0EVk%$h`+s9{xrKOC~(|$j=W2(gwEA z4k2g1+@(EmrArhohpuW(6b3WSzj{NUAXXTyCea-4XDxV{xjplfg3=H)6N{_9xE9@> z59&xvO150NVXk2iJn*XDvjCcPK+K!D{#W^`@Ys1o=%Q8QcR6JrB-m^<;a(4HU z;ef1Ulk0tafOXEL;14c*z9z%gnaYIxdTTIm451Ub(mGJ#1zFYqk%{$Y`}vEgY=ux1 zvQ+Dz)!Fei>7Yw!ARW6M#{n{{z6XTi6)!Vs)Q4g%*HR|~s^c~H1-psEBofUlA+{Ay(MH$G_n2|drWLCB z$oPaNCTe`nP^eGTx(MZ={yQ{sBQvj$7o5^;e*~0I|FS^AIJK9xd?Qwl8aI?kW=pX- zk+nIjG}UR*dSi)4AISLQejScYxju1b(BzcpDvr=q`Lf8bJ62cN-BUvA_Wpf_m6DlN z&e05aLV!Uu?5yT$zs#+%5UtTG~nl;cpLSc z_bmoUPpdQKyJ9Z+MvGLTg;l@)f&%rv)# z9+?IEVI#?Adb2f);F*HzLqAiD7GU&;QJd8$35KU1gW$~$=pVu;^n_iXH>t$YsxvfM zM0(s1Ob1PB948ITSfWC@^KKYAXVfsWNy27+a$@<0;itBqu2 z9NznD3*Uz2u{+1!T>q^hmq`Wc3Iwg1?-O5BvN6ivb~83zZjx`}>HJw(SL1&0vvuoY(7KO{z;V z>V0rJ&%JZRI$AXd&8xnUuj_kj)uW#G`N9Ing34QYH2_HD{>Y_2q^O_3=9Hx*1}C@a zD|HLwGv*=&SrhZeB*U9cY0U+JRor?zC71I~-YfLQVtX(vQ;$3VPCi{=>QbiUXr!GwFpmM2K&e{4()5fk2?ST>*q&Z=2Skb_o|m zp-rqVWg~MzHNOKxcVuSBG^8|zT;gM>Bz$AyWOCEoGPFjAP%y5%Cpm0L0T=iCt|}K^ z{S#7pM0NU~P*Wn;*?;2u5B1MMrydkMO$kMj;Y}X!S1O7B>Mw1@k7PryAo#4snbNnL z-rk<+t4SJ;uW~T@@O=;^O5LQlC}0|d5nr&XgozL#P99M2zo{`{i`y1%YN?=LPzjE# znV#v7&h!({YK(3*{gDvcWaw!lTPIeW+3(tO{mF<{*;VDwqGAlAiU|-{q@$^9S>$%B zr0ee2ZigR%R2hiI3)}8V(*-YIxGK>-TNV=Z5rE{|N$?6rv9VYg=5 zxsg?t<3PWJ{3gO6R&07$8{1bX&rBBdSa~dF-jy5Sb^@I-D~u3A`{L8YHsU<=Xztbi z9pm0x3Oc1BP2tBE3fCO>&WDYP$c*Ioa@(FeoP>Gu5kbzMKYz+BR7x1dq(1RzBY~IC zVdUnummEhfCc8eiX7MbJ{p@cs;2*wps&}~CkfKG7?z{@^ zvi8SXQc`JN1tZBtqb9ojCSM#a1s?aC_&8b;>Ghjvsh)Tr;yG@v$M2FrW!(9P4Tcq6#u{pqtL2}vk5$O|SJe(ZSD zaeq>J<)3>^bf?47my0lYiOp~_TQfqs4flncT*p|Lrso2)xW`+yxf~JEdF?UwL3sAN za;*iB&n}{r6q@3yUb|G*wI7?Bv3;YtUvDkp!ID@{y(>AXdZ33hq{?Tr&uv__@H69l zU|$%W^-QjY>r`pyVq4v@xX{Phym4Ap=CCI1-8@Ij2%)7PRZm{((~LX&{W>A7k10?p z1a$vpIYGVp#8=a1Y8K1R*l8bo0;3uQUbLtDJPhX)od&IP)`9Q9o#FdGKsa&0aqGiWED$6m8@R{eT+@GlV zn!hYAzt(k%#47CESn4`M=8Ti(m+9CxUdqAkMrDeNJV_aMfq5>871X(sr|7OFEU?ws z%m=u+HBr~iy=(^UE>c0K{M^>|Hj&5~zwe{ou760X zoe;u2#k&5uSVA*LLaiL|_n(ol9Q(*LIkziwN=bU}#uXHcay6Gy8Gd}6?OUvr07%ZY z?@O8Xy~VozG}uvb<3grr*y_@^chBiOi*;vnbt+;)+KW9K!}x65xEFVKu3vF>`7sKI z%8@`z_Cryt;2FccqZH04dgoM;y#8P9iw&B%ohrVyG-Y!amqmfH!UuU?hf#IF0>KeP@;S;dydW=&ql~6xA z*b849lMykv6MCAw@0(>64+J{!1nVI0%)kWeGK2_O^bTHv(>X)k!SCJoS3ZPA05rbp zb4$-3YKR>-=rh#mHyG9-B&6kdf^;weY(&XRyV`D z05+E#yk0D@-bkFt@pwa^{*=u;p)YwMmEa9$`bN>qj zpYSYg@D&>ASvfAq`0+N1u1QQ(Gg(X@j|i>h)aM_CY_qQsyo}c#vsx`yb_#-rvaY9V zZ52@eJe+P5D|q_Dz`O_Q!zOms{XKE}J;!lF?)H1eZ{aa^z{@maEW5fXV;=YQP9DJz z_R0Zev21LI8o$3>BWCzK#hP^FU1>o>JN+I0ZuDLZJf|n)r`4ot{x63usF#q40O8f}JS%~eezw$@b zit==@&?O+6NDQ4KS;Dq0@j;7oB(Q&%KpcOsr#Oq3SHB+xuu=9`JCvlj>Il??DXD{s zfJnpkc$$DG8s?iOM3zEVN5Yt9c*VGxuFCI!gq~mfU= zE+-s7@+%Ojs!!(qXt=u)Io)D052LYNq`url7$)CP?($du1+d=-@7%lJaGpQ^U}@+MT^`Gzhu>0Fr5xGXyH1@T=Ks;^vC_=I@n12RMi?p$HC^QxXUc z{{Q~oe(r=G&jEV$w`-@InUN>CZRm7{+$=8iBrHd9bxCUCRK4%+jq8D*;pw^Z*$vh& z(hHuVm{{ZXg&~73sQ&=_58m%Ag4_P+3aUN6e>4&hL`eTX>Kp&If3`P0#_F5%;2nVv zx!Cw!czpzDNmk`UH|2lt0L9fic+9?u!?nV2TChWd(%`0Wi8<^88*W(WN7x^X#NR*J z{TcR{luL$mc76Uxmp7|<2!V7K`6iY>TcjEfsKqvc#;QW@udGlMyjpd=2M|cuZ^~e} zql(9@s?_PuvcvM;O~yi4*34`7tN;Cy5C>wZO(yxh-|P%f?9~&JGUB=%)@sDX^xr{) zHunG~18oL8#A^=M}#x?Kh?WAGSjFb-~kG zZ!f+Rng`drOHEw5`B#_rX0X1w{w9P1@#}pV>7t?_C99IQzN4lWv7~lpR_@~U5F&7- zNad@I0I7Th$M2FFlnAm!z8W8CXq`!o%VxF~NFw!jl4-Tia?OzqG^{8N=Xw}6t3ob^ zRZB;A8`BMaSiOz34}o|r^Nrd}KV&o5JS{dpFT^=m#sZY3gP34&OusbQKFVl}zM-Y# z?Us<~ngdEzNS))<#2cHNn^sOWtmnN7DUW z#gypMur__eum!2$?Tn0wJl7Srl&om|z#2xicj%fV%i^GI3GxwH)-1|LO-;TUxd(>n z$ULq`TQ4z+v{IhC-Sl9s|0jL$pupbad#UTtUK3JFr)^K{{AlxqhliKR^&BG>w8w0) ztTSO171<*YL_w?(lFUyx&SwQIw;UsU{W+3sR{uze1eN2VnA85NMgm88R;|iLOfR)2 zY3dCd-!{5k-D zK&IH!hFE!+59UJau+DzG5m3V@gA0dF{o3!Ix>mFxK^s!slp6S#C#)mxDeyd4)}O zKXvGLW^QJ2z(Jbf7ArL2#D`5ZtKuo{p^-For|#!FR~O~VYGudw#>Y7^@Ih@a0bntF$^J_hj@I3EkCzpZeQiId{e|HY zZz%H)Yo6DP@npk_A;bG`3iQy6s=i?2?qS(u)a*(sWS#SyDE7z9Ayj3T@qXT}H zQ8MV`bI@B%WYF9PxFb6stC9^O-2&sl{3_Ee^Rk5Xz(-q;Nk8)^z>hKBx_#A%%C6Ol zIV()k7jvu$E`Cc^BWIQ|h-w*8>!2A@W8|_3aZPgkjPbE$uIaAV&!@B;T*~Gis_18H zu8%jk%WoR-tatEy;WC$X$^OSpc>^m7!C4xC{dW0DV{nd~giel#ys2NRh-!N?V zr=H4<%O=;2hEcBABFT0h$N8*AN`!f%&F>E4lvGL%qLx2l_QLS{K|+b(7%xVReZrAx z%DdmG@p-OyKzFPf=%3SG?~Xz0V>%bvV#^!F`zHvOwhC%-(890cu=eX)M2fsQ(Lv~i zz;T2!U7q?es&DS7{nWmIJWkAY*6_HXD$<~^%E#kdl@Im)KO1s^SV_7eD3O1^sH^yb zdU~5WMtYOs&Xd;hD(|DTX{16kHru)D65@emDLcQLvXds~p1{ zXvONm&N;ZaRGj0v;hIuf=(^UvIL_dv|65nyd0c$8I=&3DP{oyXr*FeK%Xsy3ee?M} z_x(ty{FS+b#31Wn9@|L45v0N%6Sv?Snl1Z&E4AhW-9!y*G`qpK;_23}279Z%tktZo zRrcRp*PPf?8NOtpSPd73_q!_M+|hcdvOp`&PId-ZQxoZ_y~KpqDu6#u%(U0e753Ok zrQsQ6bV(ylX{W09hT3JiO|udfw= zzAuc^neD}q&qovHo30#Ldf-K3?t>ZeqYGjq<$i|L#E|o?m+u&ExdZx6L0p6;mlJlb zA8p=u;kO81Sum_HFk@s1)I64%X^%79TeVD{_D#%sUE@>uGTQn*F}Xm$n10vj>Bg|_ zNa8kKN2fX+=YPc;@Q@L)<&9SRlCvyPr>eV*1q+NsKkR9DhOR3XQMcu_bfX1fy_Ji+QE&DH?AVP5ytG=+ovXrsf zJmR}FEj}uRC#&0BH>5pHv9fGqWlXp8qNb9kgJiE>4Sq$8*WMu0*-J+Jz#(SXRW@SW z)vt5V-dfY{pTTVbVVpX#yvo?*CThX4BfnO`Rloyz+c>*%DU{`HGrTK&Sskc0rII6` z;)=PJg8cV$iN-g+R4$Kgg!JjqKAx9+5G9K13>HO_o)1≠a{#@^H$$f9^hw{p3t3 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
  • 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 + + + + + + Debugger: Variable Hovers + + + + + +

    Debugger: Variable Hovers

    + +

    This service plugin provides hovers to the Static Listing, the Dynamic Listing, and the Decompiler. Hovering the mouse over + variables or operands in any of those windows will cause the service to display a tip showing + the value of that variable, if it can. For stack and register variables, the service will + attempt to unwind the stack until it finds a call record for the function using the variable. + Thus, it is important to have all the modules imported, analyzed, and open in the Debugger so + that the unwinder can access the static analysis of every function it needs to unwind.

    + +

    Stack unwinding can be tenuous. It relies heavily on accurate static analysis and expects + functions to follow certain conventions. Thus, it's very easy to break and may frequently be + incorrect. For hovers that include a Frame row, the displayed value depends on an + accurately unwound stack. Take the value with a grain of salt, especially if the hover also + includes a Warnings: row. To diagnose the unwound stack, use the Unwind Stack + action.

    + +

    Table Rows

    + +

    A hover may display any of the following rows:

    + +
      +
    • Name: The name of the variable or operand.
    • + +
    • Frame: A description of the frame (call record) unwound to evaluate the variable. + This is omitted for global or static variables and for raw register operands.
    • + +
    • Storage: The statically-defined storage of the variable.
    • + +
    • Type: The data type of the variable.
    • + +
    • Instruction: If the operand refers to code, the instruction at the target + address.
    • + +
    • Location: The actual location of the variable on the target.
    • + +
    • Bytes: The bytes in the variable, subject to the target's endianness. For long + buffers, the bytes are split into lines of 16 bytes each for at most 16 lines.
    • + +
    • Integer: The value displayed as an integer in various formats: decimal, + hexadecimal; unsigned, signed. The alternative formats are only included if they differ from + the formats already presented.
    • + +
    • Value: The value as given by its type's default representation. This only applies + if the variable has a type and it was able to interpret the variable's bytes.
    • + +
    • Status: If the evaluation is taking significant time, this provides feedback while + evaluation proceeds in the background.
    • + +
    • Warnings: Displays any warnings encountered while unwinding the stack, if + applicable.
    • + +
    • Error: Displays an exception or error message when there was a problem evaluating + the variable. Other rows may be present, but overall the table is incomplete.
    • +
    + +

    Examples

    + + + + + + + + + + + +
    A register operand in the Dynamic Listing
    + +

    When hovering over operands in the Dynamic Listing, that operand is most likely a register. + The register's value is displayed without regard to the stack frame. It will always use the + "innermost frame," meaning it will display the register's current value. In the example, the + user has hovered over register EDX; however, the value of EDX was not recorded, so its integer + value 0 is displayed in gray. Furthermore, the user had not assigned a type to EDX + in the Registers + window, and so the service cannot interpret the value except as an integer. Register values are + never displayed as raw byte arrays.

    + + + + + + + + + + + +
    A stack variable in the Static Listing
    + +

    When hovering over operands in the Static Listing, the service will gather context about the + operand and find a frame for the relevant function. It will take the first appropriate frame it + encounters during unwinding (from innermost out) so long as the frame's level is at least the + current frame level. In the example, the user has hovered over the parameter n, which + is a stack variable of the function fib. The curent frame is 0, so the service unwinds + the stack, finds that the current frame is a call record for fib, and selects it. It + displays the variable's static storage Stack[0x4]:4 and type uint. It + then applies this information to determine the actual run-time location and value. Since the + frame base is 00004fa0, it adds the stack offset to compute the run-time location + 00004fa4:4. It reads the bytes 01 00 00 00 from the target and + computes the integer value 1. It also interprets the value using the assigned data + type, giving 1h.

    + + + + + + + + + + + +
    A stack variable in the Decompiler
    + +

    When hovering over variables in the Decompiler, the service behaves similarly to how it does + for operands in the Static Listing. It locates the appropriate frame and attempts to derive the + variable's run-time value. Just as in the Static Listing example above, the user has hovered + over the variable n, so the service has again computed the value 1h.

    + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginBrowser.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginBrowser.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7ee4de7cdd643700d9990faa2d216d7ebd1a14 GIT binary patch literal 63084 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

    =) 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 literal 0 HcmV?d00001 diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginDecompiler.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginDecompiler.png new file mode 100644 index 0000000000000000000000000000000000000000..0a44d28236a1dac6b5fc4898a323d2fb3828a6ec GIT binary patch literal 31913 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 z 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%0AkGcedC$xLn;?wmpX<{bG$*sM4?KQRZM z*|xd48HbP%$dSU!5VzgahRE$dOsodcex3=EUU5D2DjM9o$9VL0Vz+5;2h(078jb>K z>o8`V2ErYewB>|=)-BTRvA|c#X`v&DAxk4Plm)|;l6{XK7e!ZV=O{6g_)L6pyv|EX z2+JWO6bMNzWAd-^t&P#mam=80=Ej6b)@MLm9&Rrzq4!ZrkpRK@vle$_dN>=aL;3Eg zH5BOzK7N;_x*6=Gd5{VaePzrjVWnaA{sq_E)ABaW1_nF+Kd-dxXf@#2v!IZdA#0sog}^15HMrrMpr7g_eadK?bOx53|m@XtL#B8Wc<97{*39Po0byig0X4z zE?!b5?s^jAoR7&`vMn1nRx919xh{G1dXHM!+wS&!)og$r5_%WVg^CWBHL<8;wqRlc z0}a|6N+#R(oP(}#77h((@&I^~plrLxHfm_#P0m=&oU=BH`JK*HR8K&kxMkotos(%% zu+13{%`$ad`9U9xIL4>tL`V)q71$iQ^plLtz9sRgF??ra-zCJuG+G{`4>*u2~!w&bw z`UBT-@)vq4wKwcdwzuUVI*j5wBBtJ3QVw?M=JUsWE!Aze}hT zR(?{icr!%x(>@6WN6Nm1^sw=%w7uhgXcdlyr^YA z0B4%z?J<$eVW-EB2CVG9=TX|(G%SfO7MUV;QV7h2Q`Gakvzw0zQXNmBRRX-(c8S-# zqCq*@i+4$^YNI=#Q*TLpVZiON#zki)_*qw%zL>!M^`qgnRV-tk&bs5pTW$H+#r7k$ zTjUD!=y{s2{%U(G)q|&*zY!|D=O1vl>n=;StSi17@bG31{33&0XrL9Mw203q@(PIF9_(77cr&K7CCE;k1oIt|i0~)mAMyZ(I~_D2Rcm8fWbRqhn-;AJ zZcwq2Cm!{`W-U+C%yIqr?wrzUJ;?>L8(ljOEWP>OZ_B;&xk}S()sdJXPJ%J<>h)R4 zp-~|qdQz4SObeQk?%&ko7C zTm0VH%jqbFm(8tLW!t4)XP0;IAfWkyO#PHvnNhBifY8E#MwXT5_}O}@P3Bb>F$fO! z!ozZJs-4^acp|yejqGU|Z^u{{W8&e1D>^2 { clearMarkers(); TraceProgramView altView = alternate.getTrace().getFixedProgramView(snap); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java index d150793213..4febfd30b6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java @@ -109,10 +109,9 @@ class MemviewMapModel extends AbstractSortedTableModel { } /** - * Convenience method for locating columns by name. - * Implementation is naive so this should be overridden if - * this method is to be called often. This method is not - * in the TableModel interface and is not used by the JTable. + * Convenience method for locating columns by name. Implementation is naive so this should be + * overridden if this method is to be called often. This method is not in the TableModel + * interface and is not used by the JTable. */ @Override public int findColumn(String columnName) { @@ -125,7 +124,7 @@ class MemviewMapModel extends AbstractSortedTableModel { } /** - * Returns Object.class by default + * Returns Object.class by default */ @Override public Class getColumnClass(int columnIndex) { @@ -136,7 +135,7 @@ class MemviewMapModel extends AbstractSortedTableModel { } /** - * Return whether this column is editable. + * Return whether this column is editable. */ @Override public boolean isCellEditable(int rowIndex, int columnIndex) { @@ -144,10 +143,9 @@ class MemviewMapModel extends AbstractSortedTableModel { } /** - * Returns the number of records managed by the data source object. A - * JTable uses this method to determine how many rows it - * should create and display. This method should be quick, as it - * is call by JTable quite frequently. + * Returns the number of records managed by the data source object. A JTable uses this + * method to determine how many rows it should create and display. This method should be quick, + * as it is call by JTable quite frequently. * * @return the number or rows in the model * @see #getColumnCount diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java index 9c6f34fb11..153186c853 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java @@ -16,31 +16,49 @@ package ghidra.app.plugin.core.debug.gui.stack; import java.awt.BorderLayout; +import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.Objects; -import javax.swing.JComponent; -import javax.swing.JPanel; +import javax.swing.*; import org.apache.commons.lang3.ArrayUtils; import docking.ActionContext; import docking.WindowPosition; +import docking.action.DockingAction; +import docking.action.builder.ActionBuilder; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.stack.UnwindStackCommand; import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.framework.plugintool.AutoService; -import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; import ghidra.program.util.ProgramLocation; import ghidra.trace.model.*; import ghidra.trace.model.thread.TraceThread; +import ghidra.util.HelpLocation; public class DebuggerStackProvider extends ComponentProviderAdapter { + public interface UnwindStackAction { + String NAME = "Unwind from frame 0"; + String DESCRIPTION = "Unwind the stack, placing frames in the dynamic listing"; + String HELP_ANCHOR = "unwind_stack"; + KeyStroke KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_U, 0); + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName).description(DESCRIPTION) + .menuPath(DebuggerPluginPackage.NAME, "Analysis", NAME) + .keyBinding(KEY_STROKE) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { if (!Objects.equals(a.getTrace(), b.getTrace())) { return false; @@ -71,6 +89,8 @@ public class DebuggerStackProvider extends ComponentProviderAdapter { /*testing*/ DebuggerStackPanel panel; /*testing*/ DebuggerLegacyStackPanel legacyPanel; + DockingAction actionUnwindStack; + public DebuggerStackProvider(DebuggerStackPlugin plugin) { super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_STACK, plugin.getName()); this.plugin = plugin; @@ -96,7 +116,14 @@ public class DebuggerStackProvider extends ComponentProviderAdapter { } protected void createActions() { - // TODO: Anything? + actionUnwindStack = UnwindStackAction.builder(plugin) + .enabledWhen(ctx -> current.getTrace() != null) + .onAction(this::activatedUnwindStack) + .buildAndInstall(tool); + } + + private void activatedUnwindStack(ActionContext ignored) { + new UnwindStackCommand(tool, current).run(tool, current.getTrace()); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPlugin.java new file mode 100644 index 0000000000..1ead244f69 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPlugin.java @@ -0,0 +1,66 @@ +/* ### + * 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.plugin.core.debug.gui.stack.vars; + +import ghidra.app.decompiler.component.hover.DecompilerHoverService; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.core.codebrowser.hover.ListingHoverService; +import ghidra.app.plugin.core.debug.DebuggerPluginPackage; +import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; + +@PluginInfo( + status = PluginStatus.RELEASED, + packageName = DebuggerPluginPackage.NAME, + category = PluginCategoryNames.DEBUGGER, + shortDescription = "Variable Values Hover", + description = "Displays live variable values in a tooltip as you hover over a variable in " + + "the listings or decompiler.", + eventsConsumed = { + TraceClosedPluginEvent.class + }, + servicesProvided = { + ListingHoverService.class, + DecompilerHoverService.class + }) +public class VariableValueHoverPlugin extends Plugin { + private VariableValueHoverService hoverService; + + public VariableValueHoverPlugin(PluginTool tool) { + super(tool); + hoverService = new VariableValueHoverService(tool); + registerServiceProvided(ListingHoverService.class, hoverService); + registerServiceProvided(DecompilerHoverService.class, hoverService); + } + + public VariableValueHoverService getHoverService() { + return hoverService; + } + + @Override + protected void dispose() { + hoverService.dispose(); + } + + @Override + public void processEvent(PluginEvent event) { + super.processEvent(event); + if (event instanceof TraceClosedPluginEvent evt) { + hoverService.traceClosed(evt.getTrace()); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverService.java new file mode 100644 index 0000000000..f603dda212 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverService.java @@ -0,0 +1,675 @@ +/* ### + * 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.plugin.core.debug.gui.stack.vars; + +import java.awt.Window; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import javax.swing.*; + +import docking.widgets.fieldpanel.field.Field; +import docking.widgets.fieldpanel.support.FieldLocation; +import ghidra.GhidraOptions; +import ghidra.app.decompiler.ClangFieldToken; +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.component.ClangTextField; +import ghidra.app.decompiler.component.hover.DataTypeDecompilerHover; +import ghidra.app.decompiler.component.hover.DecompilerHoverService; +import ghidra.app.plugin.core.codebrowser.hover.ListingHoverService; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.*; +import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueUtils.VariableEvaluator; +import ghidra.app.plugin.core.debug.stack.*; +import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService; +import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService.TaskOpt; +import ghidra.app.plugin.core.hover.AbstractConfigurableHover; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.async.AsyncUtils; +import ghidra.async.SwingExecutorService; +import ghidra.framework.plugintool.AutoService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.pcode.exec.DebuggerPcodeUtils; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValuePcodeExecutorState; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.PointerDataType; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.*; +import ghidra.program.model.scalar.Scalar; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.util.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.listing.*; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.task.TaskMonitor; + +public class VariableValueHoverService extends AbstractConfigurableHover + implements ListingHoverService, DecompilerHoverService { + private static final String NAME = "Variable Value Display"; + private static final String DESCRIPTION = + "Show a variable's value when hovering over it and debugging"; + + private static final int PRIORITY = 100; + + private static class LRUCache extends LinkedHashMap { + private static final int DEFAULT_MAX_SIZE = 5; + + private int maxSize; + + public LRUCache() { + this(DEFAULT_MAX_SIZE); + } + + public LRUCache(int maxSize) { + super(maxSize, 0.75f, true); + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + if (size() > maxSize) { + removed(eldest); + return true; + } + return false; + } + + protected void removed(Map.Entry eldest) { + } + } + + // TODO: Option to always unwind from frame 0, or take the nearest frame? + + @AutoServiceConsumed + private DebuggerTraceManagerService traceManager; + @AutoServiceConsumed + private DebuggerStaticMappingService mappingService; + @SuppressWarnings("unused") + private final AutoService.Wiring autoServiceWiring; + + private final Map cachedEvaluators = + new LRUCache<>() { + protected void removed(Map.Entry eldest) { + eldest.getValue().dispose(); + } + }; + + public VariableValueHoverService(PluginTool tool) { + super(tool, PRIORITY); + autoServiceWiring = AutoService.wireServicesConsumed(tool, this); + } + + @Override + public void dispose() { + super.dispose(); + for (VariableEvaluator eval : cachedEvaluators.values()) { + eval.dispose(); + } + } + + @Override + protected String getName() { + return NAME; + } + + @Override + protected String getDescription() { + return DESCRIPTION; + } + + @Override + protected String getOptionsCategory() { + return GhidraOptions.CATEGORY_DECOMPILER_POPUPS; + } + + public static class TableFiller { + private final VariableValueTable table; + private final PluginTool tool; + private final DebuggerCoordinates current; + private final List warnings; + + private final DebuggerStaticMappingService mappingService; + private final VariableEvaluator eval; + + public TableFiller(VariableValueTable table, PluginTool tool, DebuggerCoordinates current, + VariableEvaluator eval, List warnings) { + this.table = table; + this.tool = tool; + this.current = current; + this.warnings = warnings; + + this.mappingService = tool.getService(DebuggerStaticMappingService.class); + this.eval = eval; + } + + protected CompletableFuture executeBackground( + java.util.function.Function command) { + PluginToolExecutorService executor = + new PluginToolExecutorService(tool, "Get Variable Value", current.getTrace(), 250, + TaskOpt.IS_BACKGROUND, TaskOpt.CAN_CANCEL); + return CompletableFuture.supplyAsync(() -> command.apply(executor.getLastMonitor()), + executor); + } + + public VariableValueTable fillUndefinedUnit(TraceData dynData, Program stProg, + Address stAddr) { + if (stProg == null) { + return fillDefinedData(dynData); + } + CodeUnit stUnit = stProg.getListing().getCodeUnitAt(stAddr); + if (stUnit == null) { + return fillDefinedData(dynData); + } + if (stUnit instanceof Data stData) { + DataType stType = stData.getDataType(); + if (stType == DataType.DEFAULT) { + return fillDefinedData(dynData); + } + table.add(StorageRow.fromCodeUnit(stUnit)); + table.add(new TypeRow(stType)); + AddressRange dynRange = new AddressRangeImpl(dynData.getMinAddress(), + dynData.getMinAddress().add(stData.getLength() - 1)); + table.add(LocationRow.fromRange(dynRange)); + BytesRow bytesRow = + BytesRow.fromRange(current.getPlatform(), dynRange, current.getViewSnap()); + table.add(bytesRow); + table.add(new IntegerRow(bytesRow)); + String repr = eval.getRepresentation(dynData.getAddress(), bytesRow.bytes().bytes(), + stType, stData); + if (repr != null) { + table.add(new ValueRow(repr, bytesRow.state())); + } + return table; + } + if (stUnit instanceof Instruction stIns) { + fillDefinedData(dynData); + table.add(new InstructionRow(stIns)); + table.add(new WarningsRow("Instruction taken from static listing")); + return table; + } + throw new AssertionError(); + } + + public VariableValueTable fillDefinedData(TraceData data) { + table.add(new TypeRow(data.getDataType())); + table.add(LocationRow.fromCodeUnit(data)); + BytesRow bytesRow = BytesRow.fromCodeUnit(data, current.getViewSnap()); + table.add(bytesRow); + table.add(new IntegerRow(bytesRow)); + String repr = data.getDefaultValueRepresentation(); + if (repr != null) { + table.add(new ValueRow(repr, bytesRow.state())); + } + return table; + } + + public VariableValueTable fillInstruction(TraceInstruction ins) { + table.add(LocationRow.fromCodeUnit(ins)); + table.add(BytesRow.fromCodeUnit(ins, current.getViewSnap())); + table.add(new InstructionRow(ins)); + return table; + } + + public VariableValueTable fillCodeUnit(TraceCodeUnit unit, Program stProg, Address stAddr) { + Symbol[] dynSymbols = unit.getSymbols(); + if (dynSymbols.length != 0) { + table.add(new NameRow(dynSymbols[0].getName(true))); + } + else if (stProg != null) { + Symbol[] stSymbols = stProg.getSymbolTable().getSymbols(stAddr); + if (stSymbols.length != 0) { + table.add(new NameRow(stSymbols[0].getName(true))); + } + } + + if (unit instanceof TraceData data) { + if (data.getDataType() == DataType.DEFAULT) { + return fillUndefinedUnit(data, stProg, stAddr); + } + return fillDefinedData(data); + } + else if (unit instanceof TraceInstruction ins) { + return fillInstruction(ins); + } + else { + throw new AssertionError(); + } + } + + record MappedLocation(Program stProg, Address stAddr, Address dynAddr) { + } + + protected MappedLocation mapLocation(Program programOrView, Address address) { + if (programOrView instanceof TraceProgramView view) { + ProgramLocation stLoc = + mappingService.getStaticLocationFromDynamic(new ProgramLocation(view, address)); + return stLoc == null + ? new MappedLocation(null, null, address) + : new MappedLocation(stLoc.getProgram(), stLoc.getAddress(), address); + } + ProgramLocation dynLoc = mappingService.getDynamicLocationFromStatic(current.getView(), + new ProgramLocation(programOrView, address)); + return new MappedLocation(programOrView, address, + dynLoc == null ? null : dynLoc.getAddress()); + } + + public CompletableFuture fillMemory(Program programOrView, + Address refAddress) { + MappedLocation mapped = mapLocation(programOrView, refAddress); + if (mapped.dynAddr == null) { + return null; + } + WatchValuePcodeExecutorState state = DebuggerPcodeUtils.buildWatchState(tool, current); + TraceCodeUnitsView codeUnits = current.getTrace().getCodeManager().codeUnits(); + TraceCodeUnit unit = codeUnits.getContaining(current.getViewSnap(), mapped.dynAddr); + if (unit == null) { + // Not sure this should ever happen.... + return null; + } + return CompletableFuture.supplyAsync(() -> { + state.getVar(mapped.dynAddr, unit.getLength(), true, Reason.INSPECT); + TraceCodeUnit unitAfterUpdate = + codeUnits.getContaining(current.getViewSnap(), mapped.dynAddr); + return fillCodeUnit(unitAfterUpdate, mapped.stProg, mapped.stAddr); + }); + } + + public CompletableFuture fillStack(Instruction ins, + Address stackAddress) { + Function function = + ins.getProgram().getFunctionManager().getFunctionContaining(ins.getMinAddress()); + if (function == null) { + return null; + } + Variable variable = VariableValueUtils.findStackVariable(function, stackAddress); + return executeBackground(monitor -> { + UnwoundFrame frame = eval.getStackFrame(function, warnings, monitor); + if (frame == null) { + throw new UnwindException("Cannot find frame for " + function); + } + if (variable != null) { + return fillFrameStorage(frame, variable.getName(), variable.getDataType(), + variable.getVariableStorage()); + } + Address dynAddr = frame.getBasePointer().add(stackAddress.getOffset()); + TraceCodeUnit unit = current.getTrace() + .getCodeManager() + .codeUnits() + .getContaining(current.getViewSnap(), dynAddr); + if (unit instanceof TraceData data && ListingUnwoundFrame.isFrame(data)) { + int offset = (int) dynAddr.subtract(data.getMinAddress()); + TraceData comp = data.getComponentContaining(offset); + return fillCodeUnit(comp, null, null); + } + return fillCodeUnit(unit, null, null); + }); + } + + public CompletableFuture fillReference(CodeUnit unit, + Address refAddress) { + if (refAddress.isMemoryAddress()) { + return fillMemory(unit.getProgram(), refAddress); + } + if (refAddress.isStackAddress() && unit instanceof Instruction ins) { + return fillStack(ins, refAddress); + } + return null; + } + + public VariableValueTable fillRegisterNoFrame(Register register) { + TraceData data = eval.getRegisterUnit(register); + if (data != null) { + table.add(new NameRow(register.getName())); + table.add(new TypeRow(data.getDataType())); + IntegerRow intRow = IntegerRow.fromCodeUnit(data, current.getSnap()); + table.add(intRow); + table.add(new ValueRow(data.getDefaultValueRepresentation(), intRow.state())); + return table; + } + // Just display the raw register value + table.add(new NameRow(register.getName())); + WatchValue raw = eval.getRawRegisterValue(register); + table.add(new IntegerRow(raw)); + return table; + } + + public CompletableFuture fillRegister(Instruction ins, + Register register) { + Function function = + ins.getProgram().getFunctionManager().getFunctionContaining(ins.getMinAddress()); + Variable variable = + function == null ? null : VariableValueUtils.findVariable(function, register); + return executeBackground(monitor -> { + UnwoundFrame frame; + if (function == null) { + warnings.add("Instruction is not in a function. Using innermost frame."); + frame = VariableValueUtils.locateInnermost(tool, current); + } + else { + frame = eval.getStackFrame(function, warnings, monitor); + } + if (frame == null) { + warnings.add( + "Could not locate " + function + " in stack. Using innermost frame."); + return fillRegisterNoFrame(register); + } + + if (variable != null) { + return fillFrameStorage(frame, variable.getName(), variable.getDataType(), + variable.getVariableStorage()); + } + + if (frame.getLevel() == 0) { + return fillRegisterNoFrame(register); + } + + // Still raw register value, but this time it can be restored from stack + table.add(new NameRow(register.getName())); + if (!frame.isFake()) { + table.add(new FrameRow(frame)); + } + WatchValue value = frame.getValue(register); + table.add(LocationRow.fromWatchValue(value, current.getPlatform().getLanguage())); + table.add(new IntegerRow(value)); + return table; + }); + } + + public CompletableFuture fillOperand(OperandFieldLocation opLoc, + Instruction ins) { + RefType refType = ins.getOperandRefType(opLoc.getOperandIndex()); + if (refType.isFlow()) { + return null; + } + Object operand = ins.getDefaultOperandRepresentationList(opLoc.getOperandIndex()) + .get(opLoc.getSubOperandIndex()); + if (operand instanceof Register register) { + return fillRegister(ins, register); + } + Address refAddress = opLoc.getRefAddress(); + if (operand instanceof Scalar scalar && refAddress != null) { + return fillReference(ins, refAddress); + } + if (operand instanceof Address address) { + return fillReference(ins, address); + } + return null; + } + + public CompletableFuture fillStorage(Function function, String name, + DataType type, VariableStorage storage, AddressSetView symbolStorage) { + return executeBackground(monitor -> { + UnwoundFrame frame = + VariableValueUtils.requiresFrame(storage, symbolStorage) + ? eval.getStackFrame(function, warnings, monitor) + : eval.getGlobalsFakeFrame(); + if (frame == null) { + throw new UnwindException("Cannot find frame for " + function); + } + return fillFrameStorage(frame, name, type, storage); + }); + } + + public CompletableFuture fillPcodeOp(Function function, String name, + DataType type, PcodeOp op, AddressSetView symbolStorage) { + return executeBackground(monitor -> { + UnwoundFrame frame = VariableValueUtils.requiresFrame(op, symbolStorage) + ? eval.getStackFrame(function, warnings, monitor) + : eval.getGlobalsFakeFrame(); + if (frame == null) { + throw new UnwindException("Cannot find frame for " + function); + } + return fillFrameOp(frame, function.getProgram(), name, type, op, symbolStorage); + }); + } + + public VariableValueTable fillWatchValue(UnwoundFrame frame, Address address, + DataType type, WatchValue value) { + table.add(LocationRow.fromWatchValue(value, current.getPlatform().getLanguage())); + if (value.address() != null && !value.address().isRegisterAddress()) { + table.add(new BytesRow(value)); + } + table.add(new IntegerRow(value)); + if (type != DataType.DEFAULT) { + String repr = eval.getRepresentation(frame, address, value, type); + table.add(new ValueRow(repr, value.state())); + } + return table; + } + + public VariableValueTable fillFrameStorage(UnwoundFrame frame, String name, + DataType type, VariableStorage storage) { + table.add(new NameRow(name)); + if (!frame.isFake()) { + table.add(new FrameRow(frame)); + } + table.add(new StorageRow(storage)); + table.add(new TypeRow(type)); + WatchValue value = frame.getValue(storage); + return fillWatchValue(frame, storage.getMinAddress(), type, value); + } + + public VariableValueTable fillFrameOp(UnwoundFrame frame, Program program, + String name, DataType type, PcodeOp op, AddressSetView symbolStorage) { + table.add(new NameRow(name)); + if (!frame.isFake()) { + table.add(new FrameRow(frame)); + } + table.add(new TypeRow(type)); + WatchValue value = frame.evaluate(program, op, symbolStorage); + // TODO: What if the type is dynamic with non-fixed size? + if (type.getLength() != value.length()) { + value = frame.zext(value, type.getLength()); + } + return fillWatchValue(frame, op.getOutput().getAddress(), type, value); + } + + public CompletableFuture fillHighVariable(HighVariable hVar, + String name, + AddressSetView symbolStorage) { + Function function = hVar.getHighFunction().getFunction(); + VariableStorage storage = VariableValueUtils.fabricateStorage(hVar); + if (storage.isUniqueStorage()) { + table.add(new NameRow(name)); + table.add(new StorageRow(storage)); + table.add(new ValueRow("(Unique)", TraceMemoryState.KNOWN)); + return CompletableFuture.completedFuture(table); + } + return fillStorage(function, name, hVar.getDataType(), storage, symbolStorage); + } + + public CompletableFuture fillHighVariable(HighVariable hVar, + AddressSetView symbolStorage) { + return fillHighVariable(hVar, hVar.getName(), symbolStorage); + } + + public CompletableFuture fillComponent(ClangFieldToken token, + AddressSetView symbolStorage) { + Function function = token.getClangFunction().getHighFunction().getFunction(); + Program program = function.getProgram(); + PcodeOp op = token.getPcodeOp(); + Varnode vn = op.getOutput(); + HighVariable hVar = vn.getHigh(); + DataType type = DataTypeDecompilerHover.getFieldDataType(token); + if (hVar.getDataType().isEquivalent(new PointerDataType(type))) { + op = VariableValueUtils.findDeref(program.getAddressFactory(), vn); + } + return fillPcodeOp(function, token.getText(), type, op, symbolStorage); + } + + public CompletableFuture fillComposite(HighSymbol hSym, + HighVariable hVar, AddressSetView symbolStorage) { + return fillStorage(hVar.getHighFunction().getFunction(), hSym.getName(), + hSym.getDataType(), hSym.getStorage(), symbolStorage); + } + + public CompletableFuture fillToken(ClangToken token) { + if (token == null) { + return null; + } + + /** + * I can't get just the expression tree here, except as p-code AST, which doesn't seem + * to include token info. A line should contain the full expression, though. I'll grab + * the symbols' storage from it and ensure my evaluation recurses until it hits those + * symbols. + */ + AddressSet symbolStorage = + VariableValueUtils.collectSymbolStorage(token.getLineParent()); + + if (token instanceof ClangFieldToken fieldToken) { + return fillComponent(fieldToken, symbolStorage); + } + + HighVariable hVar = token.getHighVariable(); + if (hVar == null) { + return null; + } + + HighSymbol hSym = hVar.getSymbol(); + if (hSym == null) { + // This is apparently the case for literals. + return null; + } + VariableStorage storage = hSym.getStorage(); + + String name = hVar.getName(); + if (name == null) { + name = hSym.getName(); + } + + Varnode representative = hVar.getRepresentative(); + if (!storage.contains(representative.getAddress())) { + // I'm not sure this can ever happen.... + return fillHighVariable(hVar, symbolStorage); + } + + if (Arrays.asList(storage.getVarnodes()).equals(List.of(representative))) { + // The var is the symbol + return fillHighVariable(hVar, symbolStorage); + } + + // Presumably, there's some component path from symbol to high var + return fillComposite(hSym, hVar, symbolStorage); + } + + public CompletableFuture fillVariable(Variable variable) { + Function function = variable.getFunction(); + return executeBackground(monitor -> { + UnwoundFrame frame = eval.getStackFrame(function, warnings, monitor); + if (frame == null) { + throw new UnwindException("Cannot find frame for " + function); + } + return fillFrameStorage(frame, variable.getName(), variable.getDataType(), + variable.getVariableStorage()); + }); + } + } + + public CompletableFuture fillVariableValueTable(VariableValueTable table, + ProgramLocation programLocation, DebuggerCoordinates current, + FieldLocation fieldLocation, Field field, List warnings) { + if (traceManager == null || mappingService == null) { + return null; + } + VariableEvaluator eval; + synchronized (cachedEvaluators) { + eval = cachedEvaluators.computeIfAbsent(current, c -> new VariableEvaluator(tool, c)); + } + TableFiller filler = new TableFiller(table, tool, current, eval, warnings); + if (field instanceof ClangTextField clangField) { + return filler.fillToken(clangField.getToken(fieldLocation)); + } + if (programLocation == null) { + return null; + } + Address refAddress = programLocation.getRefAddress(); + CodeUnit unit = programLocation.getProgram() + .getListing() + .getCodeUnitContaining(programLocation.getAddress()); + if (programLocation instanceof OperandFieldLocation opLoc && + unit instanceof Instruction ins) { + return filler.fillOperand(opLoc, ins); + } + if (programLocation instanceof OperandFieldLocation && refAddress != null && + refAddress.isMemoryAddress()) { + return filler.fillReference(unit, refAddress); + } + if (programLocation instanceof VariableLocation varLoc) { + return filler.fillVariable(varLoc.getVariable()); + } + return null; + } + + @Override + public JComponent getHoverComponent(Program program, ProgramLocation programLocation, + FieldLocation fieldLocation, Field field) { + if (!enabled || traceManager == null) { + return null; + } + VariableValueTable table = new VariableValueTable(); + List warnings = new ArrayList<>(); + CompletableFuture future; + try { + future = fillVariableValueTable(table, programLocation, + traceManager.getCurrent(), fieldLocation, field, warnings); + } + catch (Exception e) { + table.add(new ErrorRow(e)); + return createTooltipComponent("" + table.toHtml()); + } + if (future == null) { + return null; + } + if (!future.isDone()) { + table.add(new StatusRow("In Progress")); + } + JComponent component = createTooltipComponent("" + table.toHtml()); + if (!(component instanceof JToolTip tooltip)) { + throw new AssertionError("Expected a JToolTip"); + } + future.handleAsync((__, ex) -> { + table.remove(RowKey.STATUS); + if (ex != null) { + table.add(new ErrorRow(AsyncUtils.unwrapThrowable(ex))); + } + else { + table.add(new WarningsRow(warnings)); + } + tooltip.setTipText("" + table.toHtml()); + Window window = SwingUtilities.getWindowAncestor(tooltip); + if (window != null) { + window.pack(); + } // else, the computation completed before tooltip was returned + return null; + }, SwingExecutorService.MAYBE_NOW); + return tooltip; + } + + public void traceClosed(Trace trace) { + synchronized (cachedEvaluators) { + cachedEvaluators.keySet().removeIf(coords -> coords.getTrace() == trace); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueRow.java new file mode 100644 index 0000000000..a1b1445df2 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueRow.java @@ -0,0 +1,647 @@ +/* ### + * 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.plugin.core.debug.gui.stack.vars; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import generic.theme.GColor; +import ghidra.app.plugin.core.debug.stack.UnwoundFrame; +import ghidra.pcode.exec.DebuggerPcodeUtils.PrettyBytes; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue; +import ghidra.pcode.exec.ValueLocation; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.data.DataType; +import ghidra.program.model.lang.Language; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.pcode.Varnode; +import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.listing.TraceCodeUnit; +import ghidra.trace.model.memory.*; +import ghidra.trace.util.TraceAddressSpace; +import ghidra.util.HTMLUtilities; +import ghidra.util.exception.InvalidInputException; + +/** + * A row to be displayed in a variable value hover's table + */ +public interface VariableValueRow { + // TODO: Colors specific to hovers? + GColor COLOR_ERROR = new GColor("color.fg.error"); + GColor COLOR_STALE = new GColor("color.fg.debugger.value.stale"); + + /** + * Perform the simplest styling of the object + * + *

    + * This merely invokes the object's {@link Object#toString()} method and escapes its. If it's + * null, it will render "None" is the error color. + * + * @param obj the object, possibly null + * @return the HTML-styled string + */ + static String styleSimple(Object obj) { + return obj == null ? htmlFg(COLOR_ERROR, "None") : HTMLUtilities.escapeHTML(obj.toString()); + } + + /** + * Style a given string according to the given memory state + * + *

    + * This renders stale ({@link TraceMemoryState#UNKNOWN}) values in the stale color, usually + * gray. + * + * @param state the state + * @param str the HTML string + * @return the HTML-styled string + */ + static String styleState(TraceMemoryState state, String str) { + if (state == TraceMemoryState.KNOWN) { + return str; + } + return "" + str + ""; + } + + /** + * Escape and style the given text in the given color + * + * @param color the color + * @param text the text + * @return the HTML-styled string + */ + static String htmlFg(GColor color, String text) { + return "" + HTMLUtilities.escapeHTML(text) + + ""; + } + + /** + * A key naming a given row type + * + *

    + * This ensures the rows always appear in conventional order, and that there is only one of + * each. + */ + enum RowKey { + NAME("Name"), + FRAME("Frame"), + STORAGE("Storage"), + TYPE("Type"), + INSTRUCTION("Instruction"), + LOCATION("Location"), + BYTES("Bytes"), + INTEGER("Integer"), + VALUE("Value"), + STATUS("Status"), + WARNINGS("Warnings"), + ERROR("Error"), + ; + + private final String display; + + RowKey(String display) { + this.display = display; + } + + @Override + public String toString() { + return display; + } + } + + /** + * Get the key for this row type + * + * @return the key + */ + RowKey key(); + + /** + * Render the key for display in diagnostics + * + * @return the the key as a string + */ + default String keyToSimpleString() { + return key().toString(); + } + + /** + * Render the key for display in the table + * + * @return the key as an HTML string + */ + default String keyToHtml() { + return HTMLUtilities.escapeHTML(key() + ":"); + } + + /** + * Render the value for display in diagnostics + * + * @return the value as a string + */ + String valueToSimpleString(); + + /** + * Render the value for display in the table + * + * @return the value as an HTML string + */ + String valueToHtml(); + + /** + * Render this complete row for display in diagnostics + * + * @return the row as a string + */ + default String toSimpleString() { + return String.format("%s: %s", keyToSimpleString(), valueToSimpleString()); + } + + /** + * Render this complete row for display in the table + * + * @return the row as an HTMl string + */ + default String toHtml() { + return String.format("%s%s", + keyToHtml(), valueToHtml()); + } + + /** + * A row for the variable's name + */ + record NameRow(String name) implements VariableValueRow { + @Override + public RowKey key() { + return RowKey.NAME; + } + + @Override + public String valueToHtml() { + return styleSimple(name); + } + + @Override + public String valueToSimpleString() { + return name; + } + } + + /** + * A row for the frame used to compute the location and value + */ + record FrameRow(UnwoundFrame frame) implements VariableValueRow { + @Override + public RowKey key() { + return RowKey.FRAME; + } + + @Override + public String valueToHtml() { + return styleSimple(frame.getDescription()); + } + + @Override + public String valueToSimpleString() { + return frame.getDescription(); + } + } + + /** + * A row for the variable's statically-defined storage + */ + record StorageRow(VariableStorage storage) implements VariableValueRow { + public static StorageRow fromCodeUnit(CodeUnit unit) { + try { + return new StorageRow(new VariableStorage(unit.getProgram(), + new Varnode(unit.getMinAddress(), unit.getLength()))); + } + catch (InvalidInputException e) { + throw new AssertionError(e); + } + } + + @Override + public RowKey key() { + return RowKey.STORAGE; + } + + @Override + public String valueToHtml() { + return styleSimple(storage); + } + + @Override + public String valueToSimpleString() { + return storage.toString(); + } + } + + /** + * A row for the variable's type + */ + record TypeRow(DataType type) implements VariableValueRow { + @Override + public RowKey key() { + return RowKey.TYPE; + } + + @Override + public String valueToHtml() { + return styleSimple(type.getDisplayName()); + } + + @Override + public String valueToSimpleString() { + return type.getDisplayName(); + } + } + + /** + * If an operand refers to code, a row for the target instruction + */ + record InstructionRow(Instruction instruction) implements VariableValueRow { + @Override + public RowKey key() { + return RowKey.INSTRUCTION; + } + + @Override + public String valueToHtml() { + return styleSimple(instruction); + } + + @Override + public String valueToSimpleString() { + return instruction.toString(); + } + } + + /** + * A row for the variable's dynamic location + */ + record LocationRow(String locString) implements VariableValueRow { + /** + * Create a row from the given range + * + * @param range the range + * @return the row + */ + public static LocationRow fromRange(AddressRange range) { + return new LocationRow( + String.format("%s:%d", range.getMinAddress(), range.getLength())); + } + + /** + * Create a row from the given code unit + * + * @param unit the unit + * @return the row + */ + public static LocationRow fromCodeUnit(CodeUnit unit) { + return new LocationRow( + String.format("%s:%d", unit.getMinAddress(), unit.getLength())); + } + + /*** + * Create a row from the given watch value + * + * @param value the value + * @param language the language (for register name substitution) + * @return the row + */ + public static LocationRow fromWatchValue(WatchValue value, Language language) { + ValueLocation loc = value.location(); + if (loc == null || loc.isEmpty()) { + return new LocationRow(null); + } + return new LocationRow(loc.toString(language)); + } + + @Override + public RowKey key() { + return RowKey.LOCATION; + } + + @Override + public String valueToHtml() { + return styleSimple(locString); + } + + @Override + public String valueToSimpleString() { + return locString == null ? "None" : locString; + } + } + + /** + * Compute the memory state of a given range + * + *

    + * If any part of the range is not {@link TraceMemoryState#KNOWN} the result is + * {@link TraceMemoryState#UNKNOWN}. + * + * @param trace the trace + * @param space the thread, frame level, and address space + * @param range the address range + * @param snap the snapshot key + * @return the composite state + */ + static TraceMemoryState computeState(Trace trace, TraceAddressSpace space, AddressRange range, + long snap) { + TraceMemoryManager mem = trace.getMemoryManager(); + TraceMemoryOperations ops; + if (space != null && space.getAddressSpace().isRegisterSpace()) { + ops = mem.getMemoryRegisterSpace(space.getThread(), space.getFrameLevel(), false); + } + else { + ops = mem; + } + return ops != null && ops.isKnown(snap, range) + ? TraceMemoryState.KNOWN + : TraceMemoryState.UNKNOWN; + } + + /** + * Compute the memory state of a given code unit + * + * @param unit the code unit + * @param snap the snapshot key + * @return the composite state. + * @see #computeState(Trace, TraceAddressSpace, AddressRange, long) + */ + static TraceMemoryState computeState(TraceCodeUnit unit, long snap) { + return computeState(unit.getTrace(), unit.getTraceSpace(), unit.getRange(), snap); + } + + /** + * A row to display the bytes in the variable + */ + record BytesRow(PrettyBytes bytes, TraceMemoryState state) implements VariableValueRow { + /** + * Create a row from a given range + * + * @param platform the platform (for trace memory and language) + * @param range the range + * @param snap the snapshot key + * @return the row + */ + public static BytesRow fromRange(TracePlatform platform, AddressRange range, long snap) { + long size = range.getLength(); + ByteBuffer buf = ByteBuffer.allocate((int) size); + Trace trace = platform.getTrace(); + if (size != trace.getMemoryManager().getViewBytes(snap, range.getMinAddress(), buf)) { + throw new AssertionError(new MemoryAccessException("Could not read bytes")); + } + return new BytesRow( + new PrettyBytes(platform.getLanguage().isBigEndian(), buf.array()), + computeState(trace, null, range, snap)); + } + + /** + * Create a row from a given code unit + * + * @param unit unit + * @param snap the snapshot key + * @return the row + */ + public static BytesRow fromCodeUnit(TraceCodeUnit unit, long snap) { + try { + return new BytesRow(new PrettyBytes(unit.isBigEndian(), unit.getBytes()), + computeState(unit, snap)); + } + catch (MemoryAccessException e) { + throw new AssertionError(e); + } + } + + /** + * Create a row from a given watch value + * + * @param value the value + */ + public BytesRow(WatchValue value) { + this(value.bytes(), value.state()); + } + + @Override + public RowKey key() { + return RowKey.BYTES; + } + + @Override + public String valueToHtml() { + return styleState(state, bytes.toBytesString().replace("\n", "
    ")); + } + + @Override + public String valueToSimpleString() { + return String.format("(%s) %s", state, bytes.toBytesString()); + } + } + + /** + * A row to display a variable's value as an integer in various formats + */ + record IntegerRow(PrettyBytes bytes, TraceMemoryState state) implements VariableValueRow { + /** + * Create a row from a given code unit + * + * @param unit the unit + * @param snap the snapshot key + * @return the row + */ + public static IntegerRow fromCodeUnit(TraceCodeUnit unit, long snap) { + try { + return new IntegerRow(new PrettyBytes(unit.isBigEndian(), unit.getBytes()), + computeState(unit, snap)); + } + catch (MemoryAccessException e) { + throw new AssertionError(e); + } + } + + /** + * Create a row from the given {@link BytesRow} + * + * @param bytes the bytes row + */ + public IntegerRow(BytesRow bytes) { + this(bytes.bytes, bytes.state); + } + + /** + * Create a row from the given watch value + * + * @param value the value + */ + public IntegerRow(WatchValue value) { + this(value.bytes(), value.state()); + } + + @Override + public RowKey key() { + return RowKey.INTEGER; + } + + @Override + public String toHtml() { + if (bytes.length() > 16) { + return ""; + } + return VariableValueRow.super.toHtml(); + } + + @Override + public String valueToHtml() { + return styleState(state, bytes.collectDisplays().replace("\n", "
    ")); + } + + @Override + public String valueToSimpleString() { + return String.format("(%s) %s", state, bytes.collectDisplays()); + } + } + + /** + * A row to display the variable's value in its type's default representation + */ + record ValueRow(String value, TraceMemoryState state) implements VariableValueRow { + @Override + public RowKey key() { + return RowKey.VALUE; + } + + @Override + public String valueToHtml() { + return styleState(state, HTMLUtilities.escapeHTML(value)); + } + + @Override + public String valueToSimpleString() { + return String.format("(%s) %s", state, value); + } + } + + /** + * A row to indicate the computation status, in case it takes a moment + */ + record StatusRow(String status) implements VariableValueRow { + @Override + public RowKey key() { + return RowKey.STATUS; + } + + @Override + public String valueToHtml() { + return String.format("%s", HTMLUtilities.escapeHTML(status.toString())); + } + + @Override + public String valueToSimpleString() { + return status.toString(); + } + } + + /** + * A row to display the warnings encountered while unwinding the frame used to evaluate the + * variable + */ + record WarningsRow(String warnings) implements VariableValueRow { + /** + * Create a row from the given list of warnings + * + * @param warnings the warnings + */ + public WarningsRow(List warnings) { + this(warnings.stream() + .map(String::trim) + .filter(w -> !w.isBlank()) + .collect(Collectors.joining("\n"))); + } + + @Override + public RowKey key() { + return RowKey.WARNINGS; + } + + @Override + public String keyToHtml() { + return htmlFg(COLOR_ERROR, key() + ":"); + } + + @Override + public String valueToHtml() { + String[] split = warnings.split("\n"); + String formatted = Stream.of(split) + .map(w -> String.format("

  • %s
  • ", HTMLUtilities.escapeHTML(w))) + .collect(Collectors.joining("\n ")); + return String.format(""" +
      + %s +
    + """, formatted); + } + + @Override + public String valueToSimpleString() { + return warnings; + } + + @Override + public String toHtml() { + if (warnings.isBlank()) { + return ""; + } + return String.format("%s%s", + keyToHtml(), valueToHtml()); + } + } + + /** + * A row to display an error in case the table is incomplete + */ + record ErrorRow(Throwable error) implements VariableValueRow { + @Override + public RowKey key() { + return RowKey.ERROR; + } + + @Override + public String keyToHtml() { + return htmlFg(COLOR_ERROR, key() + ":"); + } + + @Override + public String valueToHtml() { + return styleSimple(error); + } + + @Override + public String valueToSimpleString() { + return error.toString(); + } + + @Override + public String toHtml() { + return String.format("%s%s", + keyToHtml(), valueToHtml()); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueTable.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueTable.java new file mode 100644 index 0000000000..d982127d62 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueTable.java @@ -0,0 +1,115 @@ +/* ### + * 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.plugin.core.debug.gui.stack.vars; + +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.RowKey; + +/** + * A table for display in a variable value hover + */ +public class VariableValueTable { + private final Map rows = new TreeMap<>(); + + /** + * Add a row to the table + *

    + * At most one of each row type can be present. Adding a row whose type already exists will + * remove the old row of the same type. + * + * @param row + */ + public void add(VariableValueRow row) { + synchronized (rows) { + rows.put(row.key(), row); + } + } + + @Override + public String toString() { + synchronized (rows) { + return String.format(""" + <%s: + %s + > + """, + getClass().getSimpleName(), + rows.values() + .stream() + .map(VariableValueRow::toSimpleString) + .collect(Collectors.joining("\n "))); + } + } + + /** + * Render the table as HTML for display in the GUI + * + *

    + * The rows are always ordered as in {@link RowKey}. + * + * @return the HTML string + */ + public String toHtml() { + synchronized (rows) { + return String.format(""" + + %s +
    + """, + rows.values() + .stream() + .map(VariableValueRow::toHtml) + .collect(Collectors.joining("\n"))); + } + } + + /** + * Count the number of rows + * + * @return the count + */ + public int getNumRows() { + synchronized (rows) { + return rows.size(); + } + } + + /** + * Get the row of the given type + * + * @param key the key / type + * @return the row, or null + */ + public VariableValueRow get(RowKey key) { + synchronized (rows) { + return rows.get(key); + } + } + + /** + * Remove the row of the given type + * + * @param key the key / type + */ + public void remove(RowKey key) { + synchronized (rows) { + rows.remove(key); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueUtils.java new file mode 100644 index 0000000000..b8990b686a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueUtils.java @@ -0,0 +1,847 @@ +/* ### + * 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.plugin.core.debug.gui.stack.vars; + +import java.util.*; + +import ghidra.app.decompiler.ClangLine; +import ghidra.app.decompiler.ClangToken; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.stack.*; +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.eval.AbstractVarnodeEvaluator; +import ghidra.pcode.exec.DebuggerPcodeUtils; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValuePcodeExecutorState; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.opbehavior.BinaryOpBehavior; +import ghidra.pcode.opbehavior.UnaryOpBehavior; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.ByteMemBufferImpl; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.pcode.*; +import ghidra.trace.model.*; +import ghidra.trace.model.Trace.TraceMemoryBytesChangeType; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.listing.*; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.stack.TraceStack; +import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceAddressSpace; +import ghidra.util.MathUtilities; +import ghidra.util.Msg; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; + +/** + * Various utilities for evaluating statically-defined variables in the context of a dynamic trace. + */ +public enum VariableValueUtils { + ; + + /** + * An "evaluator" which simply determines whether actual evaluation will require a frame for + * context + */ + private static final class RequiresFrameEvaluator extends AbstractVarnodeEvaluator { + private final AddressSetView symbolStorage; + + private RequiresFrameEvaluator(AddressSetView symbolStorage) { + this.symbolStorage = symbolStorage; + } + + @Override + protected boolean isLeaf(Varnode vn) { + return vn.isConstant() || + symbolStorage.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1)); + } + + @Override + protected Address applyBase(long offset) { + throw new AssertionError(); + } + + @Override + protected Boolean evaluateConstant(long value, int size) { + return false; + } + + @Override + protected Boolean evaluateRegister(Address address, int size) { + return true; + } + + @Override + protected Boolean evaluateStack(long offset, int size) { + return true; + } + + @Override + protected Boolean evaluateMemory(Address address, int size) { + return false; + } + + @Override + protected Boolean evaluateUnique(long offset, int size) { + /** + * Generally speaking, this getting called is bad. We'll "let it go" here and the error + * should surface in actual evaluation. + */ + return false; + } + + @Override + protected Boolean evaluateAbstract(Program program, AddressSpace space, Boolean offset, + int size, Map already) { + /** + * This generally happens for dereferences. The evaluator will already have determined + * if computing the address requires a frame, which we should just echo back. Neither + * the location of the target nor its value has any bearing on whether or not a frame is + * required. + */ + return offset; + } + + @Override + protected Boolean evaluateUnaryOp(Program program, PcodeOp op, UnaryOpBehavior unOp, + Map already) { + return evaluateVarnode(program, op.getInput(0), already); + } + + @Override + protected Boolean evaluateBinaryOp(Program program, PcodeOp op, BinaryOpBehavior binOp, + Map already) { + return evaluateVarnode(program, op.getInput(0), already) || + evaluateVarnode(program, op.getInput(1), already); + } + + @Override + protected Boolean evaluateLoad(Program program, PcodeOp op, + Map already) { + return evaluateVarnode(program, op.getInput(1), already); + } + + @Override + protected Boolean evaluatePtrAdd(Program program, PcodeOp op, + Map already) { + // Third input is a constant, according to pcoderef.xml + return evaluateBinaryOp(program, op, null, already); + } + + @Override + protected Boolean evaluatePtrSub(Program program, PcodeOp op, + Map already) { + return evaluateBinaryOp(program, op, null, already); + } + + @Override + protected Boolean catenate(int total, Boolean value, Boolean piece, int size) { + return value || piece; + } + + @Override + public Boolean evaluateStorage(VariableStorage storage) { + return evaluateStorage(storage, false); + } + } + + /** + * A settings that provides the given space as the default for pointers + */ + static class DefaultSpaceSettings implements Settings { + final Settings delegate; + final AddressSpace space; + + public DefaultSpaceSettings(Settings delegate, AddressSpace space) { + this.delegate = delegate; + this.space = space; + } + + @Override + public boolean isChangeAllowed(SettingsDefinition settingsDefinition) { + return delegate.isChangeAllowed(settingsDefinition); + } + + @Override + public Long getLong(String name) { + return delegate.getLong(name); + } + + @Override + public String getString(String name) { + if (AddressSpaceSettingsDefinition.DEF.getStorageKey().equals(name)) { + return space.getName(); + } + return delegate.getString(name); + } + + @Override + public Object getValue(String name) { + if (AddressSpaceSettingsDefinition.DEF.getStorageKey().equals(name)) { + return space.getName(); + } + return delegate.getValue(name); + } + + @Override + public void setLong(String name, long value) { + throw new UnsupportedOperationException(); + } + + @Override + public void setString(String name, String value) { + throw new UnsupportedOperationException(); + } + + @Override + public void setValue(String name, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearSetting(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearAllSettings() { + throw new UnsupportedOperationException(); + } + + @Override + public String[] getNames() { + return delegate.getNames(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public Settings getDefaultSettings() { + return delegate.getDefaultSettings(); + } + } + + /** + * Compute the address range where annotated frames would be expected in the listing + * + * @param coordinates the coordinates + * @return the range, usually from stack pointer to the end of the stack segment + */ + public static AddressRange computeFrameSearchRange(DebuggerCoordinates coordinates) { + TraceThread thread = coordinates.getThread(); + if (thread == null) { + return null; + } + Trace trace = thread.getTrace(); + long viewSnap = coordinates.getViewSnap(); + TraceMemoryManager mem = trace.getMemoryManager(); + TracePlatform platform = coordinates.getPlatform(); + CompilerSpec cSpec = platform.getCompilerSpec(); + Register sp = cSpec.getStackPointer(); + + TraceMemorySpace regs = mem.getMemoryRegisterSpace(thread, 0, false); + RegisterValue spRV = regs.getValue(platform, viewSnap, sp); + Address spVal = cSpec.getStackBaseSpace().getAddress(spRV.getUnsignedValue().longValue()); + Address max; + TraceMemoryRegion stackRegion = mem.getRegionContaining(coordinates.getSnap(), spVal); + if (stackRegion != null) { + max = stackRegion.getMaxAddress(); + } + else { + long toMax = spVal.getAddressSpace().getMaxAddress().subtract(spVal); + max = spVal.add(MathUtilities.unsignedMin(4095, toMax)); + } + return new AddressRangeImpl(spVal, max); + } + + /** + * Find the innermost frame for the given coordinates + * + * @param tool the tool + * @param coordinates the coordinates + * @return the frame, or null + */ + public static ListingUnwoundFrame locateInnermost(PluginTool tool, + DebuggerCoordinates coordinates) { + AddressRange range = computeFrameSearchRange(coordinates); + if (range == null) { + return null; + } + // TODO: Positive stack growth? + for (TraceData data : coordinates.getTrace() + .getCodeManager() + .definedData() + .get(coordinates.getViewSnap(), range, true)) { + try { + return new ListingUnwoundFrame(tool, coordinates, data); + } + catch (UnwindException e) { + Msg.warn(VariableValueUtils.class, "Skipping frame " + data + ". " + e); + // Just try the next + } + } + return null; + } + + /** + * Locate an already unwound frame in the listing at the given coordinates + * + * @param tool the tool for context, especially for mappings to static programs + * @param coordinates the coordinates to search. Note that recursive calls are distinguished by + * the coordinates' frame level, though unwinding starts at frame 0. + * @param function the function the allocated the desired frame / call record + * @see AnalysisUnwoundFrame#applyToListing(int, TaskMonitor) + * @return the frame or null + */ + public static ListingUnwoundFrame locateFrame(PluginTool tool, DebuggerCoordinates coordinates, + Function function) { + int minLevel = coordinates.getFrame(); + AddressRange range = computeFrameSearchRange(coordinates); + if (range == null) { + return null; + } + + // TODO: Positive stack growth? + for (TraceData data : coordinates.getTrace() + .getCodeManager() + .definedData() + .get(coordinates.getViewSnap(), range, true)) { + try { + ListingUnwoundFrame frame = new ListingUnwoundFrame(tool, coordinates, data); + minLevel--; + if (minLevel < 0 && frame.getFunction() == function) { + return frame; + } + } + catch (UnwindException e) { + Msg.warn(VariableValueUtils.class, "Skipping frame " + data + ". " + e); + // Just try the next + } + } + Msg.info(VariableValueUtils.class, "Cannot find frame for function " + function); + return null; + } + + /** + * Check if evaluation of the given storage will require a frame + * + * @param storage the storage to evaluate + * @param symbolStorage the leaves of evaluation, usually storage used by symbols in scope. See + * {@link #collectSymbolStorage(ClangLine)} + * @return true if a frame is required, false otherwise + */ + public static boolean requiresFrame(VariableStorage storage, AddressSetView symbolStorage) { + return new RequiresFrameEvaluator(symbolStorage).evaluateStorage(storage); + } + + /** + * Check if evaluation of the given p-code op will require a frame + * + * @param op the op whose output to evaluation + * @param symbolStorage the leaves of evaluation, usually storage used by symbols in scope. See + * {@link #collectSymbolStorage(ClangLine)} + * @return true if a frame is required, false otherwise + */ + public static boolean requiresFrame(PcodeOp op, AddressSetView symbolStorage) { + return new RequiresFrameEvaluator(symbolStorage).evaluateOp(null, op); + } + + /** + * Get the program counter for the given thread's innermost frame using its {@link TraceStack} + * + *

    + * This will prefer the program counter in the {@link TraceStackFrame}. If that's not available, + * it will use the value of the program counter register from the thread's register bank for + * frame 0. + * + * @param platform the platform + * @param thread the thread + * @param snap the snapshot key + * @return the address + */ + public static Address getProgramCounterFromStack(TracePlatform platform, TraceThread thread, + long snap) { + TraceStack stack = thread.getTrace().getStackManager().getStack(thread, snap, false); + if (stack == null) { + return null; + } + TraceStackFrame frame = stack.getFrame(0, false); + if (frame == null) { + return null; + } + return frame.getProgramCounter(snap); + } + + /** + * Get the program counter for the given thread's innermost frame using its + * {@link TraceMemorySpace}, i.e., registers + * + * @param platform the platform + * @param thread the thread + * @param snap the snapshot key + * @return the address + */ + public static Address getProgramCounterFromRegisters(TracePlatform platform, TraceThread thread, + long snap) { + TraceMemorySpace regs = + thread.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false); + if (regs == null) { + return null; + } + RegisterValue value = + regs.getValue(platform, snap, platform.getLanguage().getProgramCounter()); + return platform.getLanguage() + .getDefaultSpace() + .getAddress(value.getUnsignedValue().longValue()); + } + + /** + * Get the program counter from the innermost frame of the given thread's stack + * + *

    + * This will prefer the program counter in the {@link TraceStackFrame}. If that's not available, + * it will use the value of the program counter register from the thread's register bank for + * frame 0. + * + * @param platform the platform + * @param thread the thread + * @param snap the snapshot key + * @return the address + */ + public static Address getProgramCounter(TracePlatform platform, TraceThread thread, long snap) { + Address pcFromStack = getProgramCounterFromStack(platform, thread, snap); + if (pcFromStack != null) { + return pcFromStack; + } + return getProgramCounterFromRegisters(platform, thread, snap); + } + + /** + * Check if the unwound frames annotated in the listing are "fresh" + * + *

    + * It can be difficult to tell. The heuristic we use is if the PC of the innermost frame agrees + * with the PC recorded for the current thread. + * + * @param tool the tool + * @param coordinates the coordinates + * @return true if the unwind appears fresh + */ + public static boolean hasFreshUnwind(PluginTool tool, DebuggerCoordinates coordinates) { + ListingUnwoundFrame innermost = locateInnermost(tool, coordinates); + if (innermost == null || !Objects.equals(innermost.getProgramCounter(), + getProgramCounter(coordinates.getPlatform(), coordinates.getThread(), + coordinates.getViewSnap()))) { + return false; + } + return true; + } + + /** + * Find the function's variable whose storage is exactly the given register + * + * @param function the function + * @param register the register + * @return the variable, or null + */ + public static Variable findVariable(Function function, Register register) { + for (Variable variable : function.getAllVariables()) { + if (variable.isRegisterVariable() && variable.getRegister() == register) { + return variable; + } + } + return null; + } + + /** + * Find the fuction's variable whose storage contains the given stack offset + * + * @param function the function + * @param stackAddress the stack offset + * @return the variable, or null + */ + public static Variable findStackVariable(Function function, Address stackAddress) { + if (!stackAddress.isStackAddress()) { + throw new IllegalArgumentException("stackAddress is not a stack address"); + } + return function.getStackFrame().getVariableContaining((int) stackAddress.getOffset()); + } + + /** + * Convert the given varnode to an address range + * + * @param vn the varnode + * @return the address range + */ + public static AddressRange rangeFromVarnode(Varnode vn) { + return new AddressRangeImpl(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1)); + } + + /** + * Check if the given address set completely contains the given varnode + * + * @param set the set + * @param vn the varnode + * @return true if completely contained + */ + public static boolean containsVarnode(AddressSetView set, Varnode vn) { + return set.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1)); + } + + /** + * Collect the addresses used for storage by any symbol in the given line of decompiled C code + * + *

    + * It's not the greatest, but an variable to be evaluated should only be expressed in terms of + * symbols on the same line (at least by the decompiler's definition, wrapping shouldn't count + * against us). This can be used to determine where evaluation should cease descending into + * defining p-code ops. See {@link #requiresFrame(PcodeOp, AddressSetView)}, and + * {@link UnwoundFrame#evaluate(Program, PcodeOp, AddressSetView)}. + * + * @param line the line + * @return the address set + */ + public static AddressSet collectSymbolStorage(ClangLine line) { + AddressSet storage = new AddressSet(); + for (ClangToken tok : line.getAllTokens()) { + HighVariable hVar = tok.getHighVariable(); + if (hVar == null) { + continue; + } + HighSymbol hSym = hVar.getSymbol(); + if (hSym == null) { + continue; + } + for (Varnode vn : hSym.getStorage().getVarnodes()) { + storage.add(rangeFromVarnode(vn)); + } + } + return storage; + } + + /** + * Find the descendent that dereferences this given varnode + * + *

    + * This searches only one hop for a {@link PcodeOp#LOAD} or {@link PcodeOp#STORE}. If it find a + * load, it simply returns it. If it find a store, it generates the inverse load and returns it. + * This latter behavior ensures we can evaluate the lval or a decompiled assignment statement. + * + * @param factory an address factory for generating unique varnodes + * @param vn the varnode for which a dereference is expected + * @return the dereference, as a {@link PcodeOp#LOAD} + */ + public static PcodeOp findDeref(AddressFactory factory, Varnode vn) { + Iterable it = (Iterable) () -> vn.getDescendants(); + for (PcodeOp desc : it) { + if (desc.getOpcode() == PcodeOp.LOAD) { + return desc; + } + } + for (PcodeOp desc : it) { + if (desc.getOpcode() == PcodeOp.STORE) { + PcodeOpAST op = new PcodeOpAST(desc.getSeqnum(), PcodeOp.LOAD, 2); + op.setInput(desc.getInput(0), 0); + op.setInput(desc.getInput(1), 1); + VarnodeAST out = new VarnodeAST(factory.getUniqueSpace().getAddress(1L << 31), + desc.getInput(2).getSize(), 0xf00d); + op.setOutput(out); + out.setDef(op); + return op; + } + } + return null; + } + + /** + * Find an instance that occurs in the variable's symbol's storage + * + *

    + * This goal is to find a stable location for evaluating the high variable, rather than some + * temporary register or worse unique location. If no satisfying instance is found, it defaults + * to the variable's representative instance. + * + * @param hVar the high variable + * @return the instance found + */ + public static Varnode getInstanceInSymbolStorage(HighVariable hVar) { + Varnode representative = hVar.getRepresentative(); + HighSymbol hSym = hVar.getSymbol(); + if (hSym == null) { + return representative; + } + AddressSet storageSet = new AddressSet(); + for (Varnode vn : hSym.getStorage().getVarnodes()) { + storageSet.add(rangeFromVarnode(vn)); + } + if (containsVarnode(storageSet, representative)) { + return representative; + } + for (Varnode instance : hVar.getInstances()) { + if (containsVarnode(storageSet, instance)) { + return instance; + } + } + return representative; + } + + /** + * Create a {@link VariableStorage} object for the given high variable + * + *

    + * This is not necessarily the same as the variable's symbol's storage. In fact, if the variable + * represents a field, it is likely a subset of the symbol's storage. + * + * @param hVar the high variable + * @return the storage + */ + public static VariableStorage fabricateStorage(HighVariable hVar) { + try { + return new VariableStorage(hVar.getHighFunction().getFunction().getProgram(), + getInstanceInSymbolStorage(hVar)); + } + catch (InvalidInputException e) { + throw new AssertionError(e); + } + } + + /** + * A class which supports evaluating variables + */ + public static class VariableEvaluator { + /** + * A listener that invalidates the stack unwind whenever the trace's bytes change + */ + private class ListenerForChanges extends TraceDomainObjectListener { + public ListenerForChanges() { + listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged); + } + + private void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range) { + TraceThread thread = space.getThread(); + // TODO: Consider the lifespan, too? Would have to use viewport.... + if (thread == null || thread == coordinates.getThread()) { + invalidateCache(); + } + } + } + + private final Object lock = new Object(); + private final PluginTool tool; + private final DebuggerCoordinates coordinates; + private final Language language; + private final ListenerForChanges listenerForChanges = new ListenerForChanges(); + + private List> unwound; + private FakeUnwoundFrame fakeFrame; + + /** + * Construct an evaluator for the given tool and coordinates + * + * @param tool the tool + * @param coordinates the coordinates + */ + public VariableEvaluator(PluginTool tool, DebuggerCoordinates coordinates) { + this.tool = tool; + this.coordinates = coordinates; + this.language = coordinates.getPlatform().getLanguage(); + + coordinates.getTrace().addListener(listenerForChanges); + } + + /** + * Dispose of this evaluator, removing its listener + */ + public void dispose() { + coordinates.getTrace().removeListener(listenerForChanges); + } + + /** + * Invalidate the stack unwind + */ + public void invalidateCache() { + synchronized (lock) { + unwound = null; + } + } + + /** + * Get a fake frame for global / static variables + * + * @return the fake frame + */ + public UnwoundFrame getGlobalsFakeFrame() { + synchronized (lock) { + if (fakeFrame == null) { + fakeFrame = new FakeUnwoundFrame<>(tool, coordinates, + DebuggerPcodeUtils.buildWatchState(tool, coordinates.frame(0))); + } + return fakeFrame; + } + } + + /** + * Refresh the stack unwind + * + * @param monitor a monitor for cancellation + */ + protected void doUnwind(TaskMonitor monitor) { + monitor.setMessage("Unwinding Stack"); + StackUnwinder unwinder = new StackUnwinder(tool, coordinates.getPlatform()); + unwound = new ArrayList<>(); + for (AnalysisUnwoundFrame frame : unwinder.frames(coordinates.frame(0), + monitor)) { + unwound.add(frame); + } + } + + /** + * Get the stack frame for the given function at or beyond the coordinates' frame level + * + * @param function the desired function + * @param warnings a place to emit warnings + * @param monitor a monitor for cancellation + * @return the frame if found, or null + */ + public UnwoundFrame getStackFrame(Function function, List warnings, + TaskMonitor monitor) { + synchronized (lock) { + if (unwound == null) { + doUnwind(monitor); + } + + for (UnwoundFrame frame : unwound.subList(coordinates.getFrame(), + unwound.size())) { + if (frame.getFunction() == function) { + String unwindWarnings = frame.getWarnings(); + if (unwindWarnings != null && !unwindWarnings.isBlank()) { + warnings.add(unwindWarnings); + } + return frame; + } + } + return null; + } + } + + /** + * Get the data unit for a register + * + *

    + * This accounts for memory-mapped registers. + * + * @param register the register + * @return the data unit, or null if undefined or mismatched + */ + public TraceData getRegisterUnit(Register register) { + TraceCodeOperations code; + TraceCodeManager codeManager = coordinates.getTrace().getCodeManager(); + if (register.getAddressSpace().isRegisterSpace()) { + TraceThread thread = coordinates.getThread(); + if (thread == null) { + return null; + } + code = codeManager.getCodeRegisterSpace(thread, false); + if (code == null) { + return null; + } + } + else { + code = codeManager; + } + return code.definedData() + .getForRegister(coordinates.getPlatform(), coordinates.getViewSnap(), register); + } + + /** + * Obtain the value of a register + * + *

    + * In order to accommodate user-provided types on registers, it's preferable to obtain the + * data unit using {@link #getRegisterUnit(Register)}. Fall back to this method only if that + * one fails. + * + * @param register + * @return + */ + public WatchValue getRawRegisterValue(Register register) { + WatchValuePcodeExecutorState state = + DebuggerPcodeUtils.buildWatchState(tool, coordinates.frame(0)); + return state.getVar(register, Reason.INSPECT); + } + + /** + * Get the representation of a variable's value according to a given data type + * + * @param address the best static address giving the location of the variable + * @param bytes the bytes giving the variable's value + * @param type the type of the variable + * @param settings settings to configure the data type + * @return the string representation, or null + */ + public String getRepresentation(Address address, byte[] bytes, DataType type, + Settings settings) { + if (type instanceof Pointer && !AddressSpaceSettingsDefinition.DEF.hasValue(settings) && + address.isRegisterAddress()) { + settings = new DefaultSpaceSettings(settings, language.getDefaultSpace()); + } + ByteMemBufferImpl buf = + new ByteMemBufferImpl(address, bytes, language.isBigEndian()) { + @Override + public Memory getMemory() { + return coordinates.getView().getMemory(); + } + }; + return type.getRepresentation(buf, settings, bytes.length); + } + + /** + * Get the representation of a variable's value according to a given data type + * + * @param frame the frame that evaluated the variable's value + * @param address the best static address giving the location of the variable. Note that the + * address given by {@link WatchValue#address()} is its dynamic address. The + * static address should instead be taken from the variable's storage or a p-code + * op's output varnode. + * @param value the value of the variable + * @param type the type of the variable + * @return the string representation, or null + */ + public String getRepresentation(UnwoundFrame frame, Address address, WatchValue value, + DataType type) { + if (type == DataType.DEFAULT) { + return null; + } + Settings settings = type.getDefaultSettings(); + if (address.isStackAddress()) { + address = frame.getBasePointer().add(address.getOffset()); + if (frame instanceof ListingUnwoundFrame listingFrame) { + settings = listingFrame.getComponentContaining(address); + } + } + return getRepresentation(address, value.bytes().bytes(), type, settings); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java index 9fde899120..55214b963d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java @@ -126,7 +126,7 @@ public class WatchRow { prevValue = prevExec == null ? null : compiled.evaluate(prevExec); TracePlatform platform = provider.current.getPlatform(); - value = fullValue.bytes(); + value = fullValue.bytes().bytes(); error = null; state = fullValue.state(); // TODO: Optional column for guest address? diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java index ade16ff8a6..4aedcbd732 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java @@ -15,11 +15,14 @@ */ package ghidra.app.plugin.core.debug.service.emulation; +import java.util.Map; import java.util.concurrent.*; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess; +import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.pcode.exec.AccessPcodeExecutionException; import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.trace.model.memory.TraceMemoryState; @@ -44,6 +47,11 @@ public abstract class AbstractRWTargetPcodeExecutorStatePiece super(language, space, backing); } + protected AbstractRWTargetCachedSpace(Language language, AddressSpace space, + PcodeTraceDataAccess backing, SemisparseByteArray bytes, AddressSet written) { + super(language, space, backing, bytes, written); + } + protected abstract void fillUninitialized(AddressSet uninitialized); @Override @@ -99,6 +107,20 @@ public abstract class AbstractRWTargetPcodeExecutorStatePiece */ protected abstract class TargetBackedSpaceMap extends CacheingSpaceMap { + + public TargetBackedSpaceMap() { + super(); + } + + protected TargetBackedSpaceMap(Map spaces) { + super(spaces); + } + + @Override + public CachedSpace fork(CachedSpace s) { + return s.fork(); + } + @Override protected PcodeDebuggerDataAccess getBacking(AddressSpace space) { return data; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java index 8bf601a6f0..85fb25c77b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java @@ -16,7 +16,7 @@ package ghidra.app.plugin.core.debug.service.emulation; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerMemoryAccess; -import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState; +import ghidra.pcode.exec.trace.*; /** * A state composing a single {@link RWTargetMemoryPcodeExecutorStatePiece} @@ -31,4 +31,14 @@ public class RWTargetMemoryPcodeExecutorState extends DefaultTracePcodeExecutorS public RWTargetMemoryPcodeExecutorState(PcodeDebuggerMemoryAccess data, Mode mode) { super(new RWTargetMemoryPcodeExecutorStatePiece(data, mode)); } + + protected RWTargetMemoryPcodeExecutorState( + TracePcodeExecutorStatePiece piece) { + super(piece); + } + + @Override + public RWTargetMemoryPcodeExecutorState fork() { + return new RWTargetMemoryPcodeExecutorState(piece.fork()); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java index a800872a14..e80f37baad 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java @@ -15,10 +15,12 @@ */ package ghidra.app.plugin.core.debug.service.emulation; +import java.util.Map; import java.util.concurrent.CompletableFuture; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerMemoryAccess; +import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.trace.model.memory.TraceMemoryState; @@ -66,6 +68,18 @@ public class RWTargetMemoryPcodeExecutorStatePiece this.backing = backing; } + protected RWTargetMemoryCachedSpace(Language language, AddressSpace space, + PcodeDebuggerMemoryAccess backing, SemisparseByteArray bytes, AddressSet written) { + super(language, space, backing, bytes, written); + this.backing = backing; + } + + @Override + public RWTargetMemoryCachedSpace fork() { + return new RWTargetMemoryCachedSpace(language, space, backing, bytes.fork(), + new AddressSet(written)); + } + @Override protected void fillUninitialized(AddressSet uninitialized) { if (space.isUniqueSpace()) { @@ -114,14 +128,29 @@ public class RWTargetMemoryPcodeExecutorStatePiece this.mode = mode; } + class WRTargetMemorySpaceMap extends TargetBackedSpaceMap { + public WRTargetMemorySpaceMap() { + super(); + } + + protected WRTargetMemorySpaceMap(Map spaceMap) { + super(spaceMap); + } + + @Override + public AbstractSpaceMap fork() { + return new WRTargetMemorySpaceMap(fork(spaces)); + } + + @Override + protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) { + return new RWTargetMemoryCachedSpace(language, space, + (PcodeDebuggerMemoryAccess) data); + } + } + @Override protected AbstractSpaceMap newSpaceMap() { - return new TargetBackedSpaceMap() { - @Override - protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) { - return new RWTargetMemoryCachedSpace(language, space, - (PcodeDebuggerMemoryAccess) data); - } - }; + return new WRTargetMemorySpaceMap(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java index a9beec92aa..46a7f40ea7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java @@ -16,7 +16,7 @@ package ghidra.app.plugin.core.debug.service.emulation; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerRegistersAccess; -import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState; +import ghidra.pcode.exec.trace.*; /** * A state composing a single {@link RWTargetRegistersPcodeExecutorStatePiece} @@ -31,4 +31,14 @@ public class RWTargetRegistersPcodeExecutorState extends DefaultTracePcodeExecut public RWTargetRegistersPcodeExecutorState(PcodeDebuggerRegistersAccess data, Mode mode) { super(new RWTargetRegistersPcodeExecutorStatePiece(data, mode)); } + + protected RWTargetRegistersPcodeExecutorState( + TracePcodeExecutorStatePiece piece) { + super(piece); + } + + @Override + public RWTargetRegistersPcodeExecutorState fork() { + return new RWTargetRegistersPcodeExecutorState(piece.fork()); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java index 2f26847bf1..f09212652a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java @@ -15,10 +15,12 @@ */ package ghidra.app.plugin.core.debug.service.emulation; +import java.util.Map; import java.util.concurrent.CompletableFuture; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerRegistersAccess; +import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.trace.model.memory.TraceMemoryState; @@ -62,6 +64,19 @@ public class RWTargetRegistersPcodeExecutorStatePiece this.backing = backing; } + protected RWTargetRegistersCachedSpace(Language language, AddressSpace space, + PcodeDebuggerRegistersAccess backing, SemisparseByteArray bytes, + AddressSet written) { + super(language, space, backing, bytes, written); + this.backing = backing; + } + + @Override + public RWTargetRegistersCachedSpace fork() { + return new RWTargetRegistersCachedSpace(language, uniqueSpace, backing, bytes.fork(), + new AddressSet(written)); + } + @Override protected void fillUninitialized(AddressSet uninitialized) { if (space.isUniqueSpace()) { @@ -98,14 +113,29 @@ public class RWTargetRegistersPcodeExecutorStatePiece this.mode = mode; } + class WRTargetRegistersSpaceMap extends TargetBackedSpaceMap { + public WRTargetRegistersSpaceMap() { + super(); + } + + protected WRTargetRegistersSpaceMap(Map spaceMap) { + super(spaceMap); + } + + @Override + public AbstractSpaceMap fork() { + return new WRTargetRegistersSpaceMap(fork(spaces)); + } + + @Override + protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) { + return new RWTargetRegistersCachedSpace(language, space, + (PcodeDebuggerRegistersAccess) data); + } + } + @Override protected AbstractSpaceMap newSpaceMap() { - return new TargetBackedSpaceMap() { - @Override - protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) { - return new RWTargetRegistersCachedSpace(language, space, - (PcodeDebuggerRegistersAccess) data); - } - }; + return new WRTargetRegistersSpaceMap(); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerAccess.java index 5c39380c4e..79e07561cd 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerAccess.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.service.emulation.data; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.trace.data.PcodeTraceAccess; +import ghidra.trace.model.thread.TraceThread; /** * A trace-and-debugger access shim @@ -34,4 +35,7 @@ public interface PcodeDebuggerAccess extends PcodeTraceAccess { @Override PcodeDebuggerRegistersAccess getDataForLocalState(PcodeThread thread, int frame); + + @Override + PcodeDebuggerRegistersAccess getDataForLocalState(TraceThread thread, int frame); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index 0e7a1296fd..d8810dd0e8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java @@ -29,6 +29,7 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent; +import ghidra.app.plugin.core.debug.stack.*; import ghidra.app.plugin.core.debug.utils.*; import ghidra.app.services.*; import ghidra.app.services.ModuleMapProposal.ModuleMapEntry; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AbstractUnwoundFrame.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AbstractUnwoundFrame.java new file mode 100644 index 0000000000..440aa8965c --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AbstractUnwoundFrame.java @@ -0,0 +1,359 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.apache.commons.lang3.ArrayUtils; + +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.services.DebuggerStateEditingService.StateEditor; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.async.AsyncFence; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.eval.AbstractVarnodeEvaluator; +import ghidra.pcode.eval.ArithmeticVarnodeEvaluator; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.opbehavior.BinaryOpBehavior; +import ghidra.pcode.opbehavior.UnaryOpBehavior; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.VariableStorage; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.Trace; +import ghidra.trace.model.TraceLocation; +import ghidra.trace.model.guest.TracePlatform; + +/** + * An abstract implementation of {@link UnwoundFrame} + * + *

    + * This generally contains all the methods for interpreting and retrieving higher-level variables + * once the frame context is known. It doesn't contain the mechanisms for creating or reading + * annotations. + * + * @param the type of values retrievable from the unwound frame + */ +public abstract class AbstractUnwoundFrame implements UnwoundFrame { + /** + * A class which can evaluate high p-code varnodes in the context of a stack frame using a + * p-code arithmetic + * + * @param the evaluation result type + */ + protected abstract class ArithmeticFrameVarnodeEvaluator + extends ArithmeticVarnodeEvaluator { + public ArithmeticFrameVarnodeEvaluator(PcodeArithmetic arithmetic) { + super(arithmetic); + } + + @Override + protected Address applyBase(long offset) { + return AbstractUnwoundFrame.this.applyBase(offset); + } + + @Override + protected Address translateMemory(Program program, Address address) { + TraceLocation location = mappingService.getOpenMappedLocation(trace, + new ProgramLocation(program, address), snap); + return location == null ? null : location.getAddress(); + } + } + + /** + * A class which can evaluate high p-code varnodes in the context of a stack frame + * + * @param the evaluation result type + */ + protected abstract class AbstractFrameVarnodeEvaluator extends AbstractVarnodeEvaluator { + @Override + protected Address applyBase(long offset) { + return AbstractUnwoundFrame.this.applyBase(offset); + } + + @Override + protected Address translateMemory(Program program, Address address) { + TraceLocation location = mappingService.getOpenMappedLocation(trace, + new ProgramLocation(program, address), snap); + return location == null ? null : location.getAddress(); + } + } + + /** + * A frame evaluator which descends to symbol storage + * + *

    + * This ensure that if a register is used as a temporary value in an varnode AST, that + * evaluation proceeds all the way to the "source" symbols. + * + * @param the evaluation result type + */ + protected abstract class FrameVarnodeEvaluator extends ArithmeticFrameVarnodeEvaluator { + private final AddressSetView symbolStorage; + + /** + * Construct an evaluator with the given arithmetic and symbol storage + * + *

    + * Varnodes contained completely in symbol storage are presumed to be the inputs of the + * evaluation. All other varnodes are evaluated by examining their defining p-code op. It is + * an error to include any unique space in symbol storage. + * + * @param arithmetic the arithmetic for evaluating p-code ops + * @param symbolStorage the address ranges to regard as input, i.e., the leaves of evalution + */ + public FrameVarnodeEvaluator(PcodeArithmetic arithmetic, AddressSetView symbolStorage) { + super(arithmetic); + this.symbolStorage = symbolStorage; + } + + @Override + protected boolean isLeaf(Varnode vn) { + return vn.isConstant() || + symbolStorage.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1)); + } + } + + /** + * A frame "evaluator" which merely gets values + * + *

    + * This evaluator never descends to defining p-code ops. It is an error to ask it for the value + * of unique varnodes. With some creativity, this can also be used as a varnode visitor to set + * values. + * + * @param the evaluation result type + */ + protected abstract class FrameVarnodeValueGetter extends ArithmeticFrameVarnodeEvaluator { + public FrameVarnodeValueGetter(PcodeArithmetic arithmetic) { + super(arithmetic); + } + + @Override + protected boolean isLeaf(Varnode vn) { + return true; + } + } + + /** + * A frame "evaluator" which actually sets values + * + * @param the evaluation result type + */ + protected abstract class FrameVarnodeValueSetter extends AbstractFrameVarnodeEvaluator { + @Override + protected boolean isLeaf(Varnode vn) { + return true; + } + + @Override + protected U evaluateUnaryOp(Program program, PcodeOp op, UnaryOpBehavior unOp, + Map already) { + throw new UnsupportedOperationException(); + } + + @Override + protected U evaluateBinaryOp(Program program, PcodeOp op, BinaryOpBehavior binOp, + Map already) { + throw new UnsupportedOperationException(); + } + + @Override + protected U evaluateAbstract(Program program, AddressSpace space, U offset, int size, + Map already) { + throw new UnsupportedOperationException(); + } + + @Override + protected U evaluateConstant(long value, int size) { + throw new UnsupportedOperationException(); + } + + @Override + protected U evaluateLoad(Program program, PcodeOp op, Map already) { + throw new UnsupportedOperationException(); + } + + @Override + protected U evaluatePtrAdd(Program program, PcodeOp op, Map already) { + throw new UnsupportedOperationException(); + } + + @Override + protected U evaluatePtrSub(Program program, PcodeOp op, Map already) { + throw new UnsupportedOperationException(); + } + } + + protected final DebuggerCoordinates coordinates; + protected final Trace trace; + protected final TracePlatform platform; + protected final long snap; + protected final long viewSnap; + protected final Language language; + protected final AddressSpace codeSpace; + protected final Register pc; + protected final PcodeExecutorState state; + + protected final DebuggerStaticMappingService mappingService; + + /** + * Construct an unwound frame + * + * @param tool the tool requesting interpretation of the frame, which provides context for + * mapped static programs. + * @param coordinates the coordinates (trace, thread, snap, etc.) to examine + * @param state the machine state, typically the watch value state for the same coordinates. It + * is the caller's (i.e., subclass') responsibility to ensure the given state + * corresponds to the given coordinates. + */ + public AbstractUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates, + PcodeExecutorState state) { + this.coordinates = coordinates; + this.trace = coordinates.getTrace(); + this.platform = coordinates.getPlatform(); + this.snap = coordinates.getSnap(); + this.viewSnap = coordinates.getViewSnap(); + this.language = platform.getLanguage(); + this.codeSpace = language.getDefaultSpace(); + this.pc = language.getProgramCounter(); + + this.state = state; + this.mappingService = tool.getService(DebuggerStaticMappingService.class); + } + + /** + * Get or recover the saved register map + * + *

    + * This indicates the location of saved registers on the stack that apply to this frame. + * + * @return the register map + */ + protected abstract SavedRegisterMap computeRegisterMap(); + + /** + * Compute the address of the return address + * + * @return the address of the return address + */ + protected abstract Address computeAddressOfReturnAddress(); + + /** + * Compute the address (in physical stack space) of the given stack offset + * + * @param offset the stack offset, relative to the stack pointer at the entry to the function + * that allocated this frame. + * @return the address in physical stack space + */ + protected abstract Address applyBase(long offset); + + @Override + public T getValue(VariableStorage storage) { + SavedRegisterMap registerMap = computeRegisterMap(); + return new FrameVarnodeValueGetter(state.getArithmetic()) { + @Override + protected T evaluateMemory(Address address, int size) { + return registerMap.getVar(state, address, size, Reason.INSPECT); + } + }.evaluateStorage(storage); + } + + @Override + public T getValue(Register register) { + SavedRegisterMap registerMap = computeRegisterMap(); + return registerMap.getVar(state, register.getAddress(), register.getNumBytes(), + Reason.INSPECT); + } + + @Override + public T evaluate(VariableStorage storage, AddressSetView symbolStorage) { + SavedRegisterMap registerMap = computeRegisterMap(); + return new FrameVarnodeEvaluator(state.getArithmetic(), symbolStorage) { + @Override + protected T evaluateMemory(Address address, int size) { + return registerMap.getVar(state, address, size, Reason.INSPECT); + } + }.evaluateStorage(storage); + } + + @Override + public T evaluate(Program program, PcodeOp op, AddressSetView symbolStorage) { + SavedRegisterMap registerMap = computeRegisterMap(); + return new FrameVarnodeEvaluator(state.getArithmetic(), symbolStorage) { + @Override + protected T evaluateMemory(Address address, int size) { + return registerMap.getVar(state, address, size, Reason.INSPECT); + } + }.evaluateOp(program, op); + } + + @Override + public CompletableFuture setValue(StateEditor editor, VariableStorage storage, + BigInteger value) { + SavedRegisterMap registerMap = computeRegisterMap(); + ByteBuffer buf = ByteBuffer.wrap(Utils.bigIntegerToBytes(value, storage.size(), true)); + AsyncFence fence = new AsyncFence(); + new FrameVarnodeValueSetter() { + @Override + protected ByteBuffer evaluateMemory(Address address, int size) { + byte[] bytes = new byte[size]; + buf.get(bytes); + if (!language.isBigEndian()) { + ArrayUtils.reverse(bytes); + } + fence.include(registerMap.setVar(editor, address, bytes)); + return buf; + } + + @Override + protected ByteBuffer catenate(int total, ByteBuffer value, ByteBuffer piece, int size) { + return value; + } + + @Override + public ByteBuffer evaluateStorage(VariableStorage storage) { + return evaluateStorage(storage, buf); + } + }.evaluateStorage(storage); + return fence.ready(); + } + + @Override + public CompletableFuture setReturnAddress(StateEditor editor, Address addr) { + if (addr.getAddressSpace() != codeSpace) { + throw new IllegalArgumentException("Return address must be in " + codeSpace); + } + BytesPcodeArithmetic bytesArithmetic = BytesPcodeArithmetic.forLanguage(language); + byte[] bytes = bytesArithmetic.fromConst(addr.getOffset(), pc.getNumBytes()); + return editor.setVariable(computeAddressOfReturnAddress(), bytes); + } + + @Override + public T zext(T value, int length) { + PcodeArithmetic arithmetic = state.getArithmetic(); + return arithmetic.unaryOp(PcodeOp.INT_ZEXT, length, (int) arithmetic.sizeOf(value), value); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AnalysisUnwoundFrame.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AnalysisUnwoundFrame.java new file mode 100644 index 0000000000..faadeedafb --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/AnalysisUnwoundFrame.java @@ -0,0 +1,400 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletableFuture; + +import ghidra.app.plugin.core.bookmark.BookmarkNavigator; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.services.DebuggerStateEditingService.StateEditor; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.exec.BytesPcodeArithmetic; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRangeImpl; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.bookmark.*; +import ghidra.trace.model.listing.TraceData; +import ghidra.trace.model.symbol.TraceReferenceManager; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A frame recovered from analysis of a thread's register bank and stack segment + * + *

    + * The typical pattern for invoking analysis to unwind an entire stack is to use + * {@link StackUnwinder#start(DebuggerCoordinates, TaskMonitor)} or similar, followed by + * {@link #unwindNext(TaskMonitor)} in a chain until the stack is exhausted or analysis fails to + * unwind a frame. It may be more convenient to use + * {@link StackUnwinder#frames(DebuggerCoordinates, TaskMonitor)}. Its iterator implements that + * pattern. Because unwinding can be expensive, it is recommended to cache the unwound stack when + * possible. A centralized service for stack unwinding may be added later. + * + * @param the type of values retrievable from the unwound frame + */ +public class AnalysisUnwoundFrame extends AbstractUnwoundFrame { + + private final StackUnwinder unwinder; + private final int level; + private final Address pcVal; + private final Address spVal; + private final UnwindInfo info; + private final UnwindException infoErr; + private final SavedRegisterMap registerMap; + + private final Address base; + + /** + * Construct an unwound frame + * + *

    + * Clients should instead use {@link StackUnwinder#start(DebuggerCoordinates, TaskMonitor)} or + * similar, or {@link #unwindNext(TaskMonitor)}. + * + * @param tool the tool requesting interpretation of the frame, which provides context for + * mapped static programs. + * @param coordinates the coordinates (trace, thread, snap, etc.) to examine + * @param unwinder the unwinder that produced this frame, and may be used to unwind the next + * frame + * @param state the machine state, typically the watch value state for the same coordinates. It + * is the caller's (i.e., subclass') responsibility to ensure the given state + * corresponds to the given coordinates. + * @param level the level of this frame + * @param pcVal the address of the next instruction when this frame becomes the current frame + * @param spVal the address of the top of the stack when this frame becomes the current frame + * @param info the information used to unwind this frame + * @param infoErr if applicable, an error describing why the unwind info is missing or + * incomplete + * @param registerMap a map from registers to the offsets of their saved values on the stack + */ + AnalysisUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates, StackUnwinder unwinder, + PcodeExecutorState state, int level, Address pcVal, Address spVal, UnwindInfo info, + UnwindException infoErr, SavedRegisterMap registerMap) { + super(tool, coordinates, state); + if ((info == null) == (infoErr == null)) { + throw new AssertionError(); + } + this.unwinder = unwinder; + this.level = level; + + this.pcVal = pcVal; + this.spVal = spVal; + this.info = info; + this.infoErr = infoErr; + this.registerMap = registerMap; + + if (info != null) { + this.base = info.computeBase(spVal); + } + else { + this.base = null; + } + } + + @Override + public boolean isFake() { + return false; + } + + /** + * Unwind the next frame up + * + *

    + * Unwind the frame that would become current if the function that allocated this frame were to + * return. For example, if this frame is at level 3, {@code unwindNext} will attempt to unwind + * the frame at level 4. + * + *

    + * The program counter and stack pointer for the next frame are computed using the state + * originally given in + * {@link StackUnwinder#start(DebuggerCoordinates, PcodeExecutorState, TaskMonitor)} and this + * frame's unwind information. The state is usually the watch-value state bound to the starting + * coordinates. The program counter is evaluated like any other variable. The stack pointer is + * computed by removing the depth of this frame. Then registers are restored and unwinding + * proceeds the same as the starting frame. + * + * @param monitor a monitor for cancellation + * @return the next frame up + * @throws CancelledException if the monitor is cancelled + * @throws UnwindException if unwinding fails + */ + public AnalysisUnwoundFrame unwindNext(TaskMonitor monitor) throws CancelledException { + if (info == null || info.ofReturn() == null) { + throw new NoSuchElementException(); + } + SavedRegisterMap registerMap = this.registerMap.fork(); + info.mapSavedRegisters(base, registerMap); + Address pcVal = info.computeNextPc(base, state, codeSpace, pc); + Address spVal = info.computeNextSp(base); + return unwinder.unwind(coordinates, level + 1, pcVal, spVal, state, registerMap, + monitor); + } + + @Override + protected Address applyBase(long offset) { + if (base == null) { + throw new UnwindException("Cannot compute stack address for offset " + offset, + infoErr); + } + return base.add(offset); + } + + @Override + protected SavedRegisterMap computeRegisterMap() { + return registerMap; + } + + @Override + protected Address computeAddressOfReturnAddress() { + return info.ofReturn(base); + } + + @Override + public Address getReturnAddress() { + return info.computeNextPc(base, state, codeSpace, pc); + } + + @Override + public CompletableFuture setReturnAddress(StateEditor editor, Address addr) { + if (addr.getAddressSpace() != codeSpace) { + throw new IllegalArgumentException("Return address must be in " + codeSpace); + } + BytesPcodeArithmetic bytesArithmetic = BytesPcodeArithmetic.forLanguage(language); + byte[] bytes = bytesArithmetic.fromConst(addr.getOffset(), pc.getNumBytes()); + return editor.setVariable(info.ofReturn(base), bytes); + } + + @Override + public int getLevel() { + return level; + } + + @Override + public String getDescription() { + return String.format("%s %s pc=%s sp=%s base=%s", level, info.function(), + pcVal.toString(false), spVal.toString(false), base.toString(false)); + } + + @Override + public Address getBasePointer() { + return base; + } + + @Override + public Address getProgramCounter() { + return pcVal; + } + + public Address getStackPointer() { + return spVal; + } + + @Override + public Function getFunction() { + return info.function(); + } + + /** + * Generate the structure for {@link #resolveStructure(int)} + * + * @param prevParamSize the number of bytes occupied by the parameters for the next frame down. + * @return the generated structure + */ + protected Structure generateStructure(int prevParamSize) { + FrameStructureBuilder builder = + new FrameStructureBuilder(language, pcVal, info, prevParamSize); + return builder.build(StackUnwinder.FRAMES_PATH, "frame_" + pcVal.toString(false), + trace.getDataTypeManager()); + } + + /** + * Create or resolve the structure data type representing this frame + * + *

    + * The structure composes a variety of information: 1) The stack variables (locals and + * parameters) of the function that allocated the frame. Note that some variables may be omitted + * if the function has not allocated them or has already freed them relative to the frame's + * program counter. 2) Saved registers. Callee-saved registers will typically appear closer to + * the next frame up. Caller-saved registers, assuming Ghidra hasn't already assigned the stack + * offset to a local variable, will typically appear close to the next frame down. 3) The return + * address, if on the stack. + * + * @param prevParamSize the number of bytes occupied by the parameters for the next frame down. + * Parameters are pushed by the caller, and so appear to be allocated by the caller; + * however, the really belong to the callee, so this specifies the number of bytes to + * "donate" to the callee's frame. + * @return the structure, to be placed {@code prevParamSize} bytes after the frame's stack + * pointer. + */ + public Structure resolveStructure(int prevParamSize) { + Structure structure = generateStructure(prevParamSize); + return structure == null ? null + : trace.getDataTypeManager() + .addType(structure, DataTypeConflictHandler.DEFAULT_HANDLER); + } + + /** + * Get or create the bookmark type for warnings + * + * @return the bookmark type + */ + protected TraceBookmarkType getWarningBookmarkType() { + TraceBookmarkType type = trace.getBookmarkManager().getBookmarkType(BookmarkType.WARNING); + if (type != null) { + return type; + } + BookmarkNavigator.defineBookmarkTypes(trace.getProgramView()); + return trace.getBookmarkManager().getBookmarkType(BookmarkType.WARNING); + } + + protected static void truncateOrDelete(TraceBookmark tb, Lifespan remove) { + List newLifespan = tb.getLifespan().subtract(remove); + if (newLifespan.isEmpty()) { + tb.delete(); + } + else { + tb.setLifespan(newLifespan.get(0)); + } + } + + /** + * Apply this unwound frame to the trace's listing + * + *

    + * This performs the following, establishing some conventions for trace stack analysis: + *

      + *
    • Places a bookmark at the frame start indicating any warnings encountered while analyzing + * it.
    • + *
    • Places a structure at (or near) the derived stack pointer whose fields denote the various + * stack entries: local variables, saved registers, return address, parameters. The structure + * may be placed a little after the derived stack pointer to accommodate the parameters of an + * inner stack frame. The structure data type will have the category path + * {@link StackUnwinder#FRAMES_PATH}. This allows follow-on analysis to identify data units + * representing unwound frames. See {@link #isFrame(TraceData)}.
    • + *
    • Places a comment at the start of the frame. This is meant for human consumption, so + * follow-on analysis should not attempt to parse or otherwise interpret it. It will indicate + * the frame level (0 being the innermost), the function name, the program counter, the stack + * pointer, and the frame base pointer.
    • + *
    • Places a {@link RefType#DATA} reference from the frame start to its own base address. + * This permits follow-on analysis to derive variable values stored on the stack. See + * {@link #getBase(TraceData)} and {@link #getValue(TraceData, VariableStorage)}.
    • + *
    • Places a {@link RefType#DATA} reference from the program counter to the frame start. This + * allows follow-on analysis to determine the function for the frame. See + * {@link #getProgramCounter(TraceData)} and + * {@link #getFunction(TraceData, DebuggerStaticMappingService)}.
    • + *
    + * + *

    + * The resulting data unit can be retrieved from the trace database and later used to construct + * a {@link ListingUnwoundFrame}. If the frame structure would have length 0 it is not applied. + * + * @param prevParamSize the number of bytes occupied by the parameters for the next frame down. + * See {@link #resolveStructure(int)}. + * @param monitor a monitor for cancellation + * @return the data unit for the frame structure applied, or null + * @throws CancelledException if the monitor is cancelled + */ + public TraceData applyToListing(int prevParamSize, TaskMonitor monitor) + throws CancelledException { + // TODO: Positive stack growth + Address spPlusParams = spVal.add(prevParamSize); + TraceBookmarkManager bm = trace.getBookmarkManager(); + TraceBookmarkType btWarn = getWarningBookmarkType(); + Lifespan span = Lifespan.nowOnMaybeScratch(viewSnap); + String warnings = info.warnings().summarize(); + Structure structure = resolveStructure(prevParamSize); + if (structure == null || structure.isZeroLength()) { + for (TraceBookmark existing : bm.getBookmarksAt(viewSnap, spPlusParams)) { + truncateOrDelete(existing, span); + } + bm.addBookmark(span, spPlusParams, btWarn, "Stack Unwind", + "Frame " + level + " has lenght 0"); + return null; + } + for (TraceBookmark existing : bm.getBookmarksIntersecting(span, + new AddressRangeImpl(spPlusParams, spPlusParams.add(structure.getLength() - 1)))) { + truncateOrDelete(existing, span); + } + if (!warnings.isBlank()) { + bm.addBookmark(span, spPlusParams, btWarn, "Unwind Stack", warnings); + } + + try { + trace.getCodeManager() + .definedUnits() + .clear(Lifespan.at(viewSnap), new AddressRangeImpl(spPlusParams, + spPlusParams.add(structure.getLength() - 1)), false, monitor); + TraceData frame = trace.getCodeManager() + .definedData() + .create(span, spPlusParams, structure); + frame.setComment(CodeUnit.PRE_COMMENT, getDescription()); + TraceReferenceManager refs = trace.getReferenceManager(); + refs.clearReferencesFrom(span, frame.getRange()); + refs.clearReferencesTo(span, frame.getRange()); + frame.addOperandReference(StackUnwinder.BASE_OP_INDEX, base, RefType.DATA, + SourceType.ANALYSIS); + refs.addMemoryReference(span, pcVal, spPlusParams, RefType.DATA, SourceType.ANALYSIS, + StackUnwinder.PC_OP_INDEX); + return frame; + } + catch (CodeUnitInsertionException e) { + throw new AssertionError(e); + } + } + + /** + * Get the unwind information from the analysis used to unwind this frame + * + * @return the information + */ + public UnwindInfo getUnwindInfo() { + return info; + } + + /** + * If the unwind information is absent or incomplete, get the error explaining why. + * + *

    + * When analysis is incomplete, the frame may still be partially unwound, meaning only certain + * variables can be evaluated, and the return address may not be available. Typically, a + * partially unwound frame is the last frame that can be recovered in the stack. If the base + * pointer could not be recovered, then only register variables and static variables can be + * evaluated. + * + * @return the error + */ + public UnwindException getError() { + return infoErr; + } + + @Override + public String getWarnings() { + if (info == null) { + return ""; + } + return info.warnings().summarize(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/FakeUnwoundFrame.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/FakeUnwoundFrame.java new file mode 100644 index 0000000000..52bcf74c74 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/FakeUnwoundFrame.java @@ -0,0 +1,108 @@ +/* ### + * 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.plugin.core.debug.stack; + +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; + +/** + * A fake frame which can be used to evaluate variables for which an actual frame is not necessary + * or not available. + * + *

    + * This "frame" can only evaluate static / global variables. Neither register variables nor stack + * variables can be evaluated. The reason for excluding registers is because some register values + * may be saved to the stack, so the values in the bank may not be the correct value in the context + * of a given stack frame. Based on an inspection of a variable's storage, it may not be necessary + * to attempt a stack unwind to evaluate it. If that is the case, this "frame" may be used to + * evaluate it where a frame interface is expected or convenient. + */ +public class FakeUnwoundFrame extends AbstractUnwoundFrame { + private static final SavedRegisterMap IDENTITY_MAP = new SavedRegisterMap(); + + /** + * Construct a fake "frame" + * + * @param tool the tool requesting interpretation of the frame, which provides context for + * mapped static programs. + * @param coordinates the coordinates (trace, thread, snap, etc.) to examine + * @param state the machine state, typically the watch value state for the same coordinates. It + * is the caller's (i.e., subclass') responsibility to ensure the given state + * corresponds to the given coordinates. + */ + public FakeUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates, + PcodeExecutorState state) { + super(tool, coordinates, state); + } + + @Override + public boolean isFake() { + return true; + } + + @Override + public int getLevel() { + throw new UnsupportedOperationException(); + } + + @Override + public String getDescription() { + return "(No frame required)"; + } + + @Override + public Address getProgramCounter() { + return null; + } + + @Override + public Function getFunction() { + return null; + } + + @Override + public Address getBasePointer() { + return null; + } + + @Override + public Address getReturnAddress() { + return null; + } + + @Override + public String getWarnings() { + return ""; + } + + @Override + protected SavedRegisterMap computeRegisterMap() { + return IDENTITY_MAP; + } + + @Override + protected Address computeAddressOfReturnAddress() { + return null; + } + + @Override + protected Address applyBase(long offset) { + throw new UnsupportedOperationException(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/FrameStructureBuilder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/FrameStructureBuilder.java new file mode 100644 index 0000000000..458f4351f7 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/FrameStructureBuilder.java @@ -0,0 +1,278 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.*; +import java.util.Map.Entry; + +import generic.ULongSpan; +import generic.ULongSpan.DefaultULongSpanSet; +import generic.ULongSpan.MutableULongSpanSet; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.Varnode; +import ghidra.trace.model.data.TraceBasedDataTypeManager; +import ghidra.util.Msg; + +/** + * The implementation of {@link AnalysisUnwoundFrame#generateStructure(int)} + */ +class FrameStructureBuilder { + public static final String RETURN_ADDRESS_FIELD_NAME = "return_address"; + public static final String SAVED_REGISTER_FIELD_PREFIX = "saved_"; + + private record FrameField(Address address, String name, DataType type, int length, + int scopeStart) { + AddressRange range() { + return new AddressRangeImpl(address, address.add(length - 1)); + } + + boolean overlaps(FrameStructureBuilder.FrameField that) { + return range().intersects(that.range()); + } + } + + private final AddressSpace codeSpace; + private final Register pc; + private final Address min; + private Address max; // Exclusive + private final long functionOffset; + private final NavigableMap fields = new TreeMap<>(); + + /** + * Builder for a structure based on unwind info and function stack variables + * + * @param language the language defining the program counter register and code address space + * @param pcVal the value of the program counter, used to determine variable scope + * @param info the unwind information + * @param prevParamSize the number of bytes past the stack pointer at entry used by the previous + * frame, typically for its parameters + */ + FrameStructureBuilder(Language language, Address pcVal, UnwindInfo info, int prevParamSize) { + this.codeSpace = language.getDefaultSpace(); + this.pc = language.getProgramCounter(); + this.min = info.function() + .getProgram() + .getAddressFactory() + .getStackSpace() + .getAddress(info.depth() + prevParamSize); + this.max = min; + this.functionOffset = pcVal.subtract(info.function().getEntryPoint()); + processSaved(info.saved()); + if (info.ofReturn() != null) { + processOfReturn(info.ofReturn()); + } + processFunction(info.function()); + } + + /** + * Remove overlapping fields + * + *

    + * Entries with later {@link FrameField#scopeStart()} are preferred. No fields are generated for + * variables whose scope starts come after the current instruction offset, so that should ensure + * the most relevant variables are selected. Variables are always preferred over saved + * registers. Ideally, the return address should not conflict, but if it does, the return + * address is preferred, esp., since it is necessary to unwind the next frame. + * + * @return the list of non-overlapping fields + */ + protected List resolveOverlaps() { + List result = new ArrayList<>(fields.size()); + Entry ent1 = fields.pollFirstEntry(); + next1: while (ent1 != null) { + FrameStructureBuilder.FrameField field1 = ent1.getValue(); + next2: while (true) { + Entry ent2 = fields.pollFirstEntry(); + if (ent2 == null) { + result.add(field1); + return result; + } + FrameStructureBuilder.FrameField field2 = ent2.getValue(); + if (!field1.overlaps(field2)) { + result.add(field1); + ent1 = ent2; + continue next1; + } + if (field1.scopeStart() > field2.scopeStart()) { + // Drop field2, but we still need to check if field1 overlaps the next + continue next2; + } + else if (field1.scopeStart() < field2.scopeStart()) { + // Drop field1 + ent1 = ent2; + continue next1; + } + else { + Msg.warn(this, + "Two overlapping variables with equal first use offsets...."); + // Prefer field1, I guess + continue next2; + } + } + } + return result; + } + + /** + * Build the resulting structure + * + * @param path the category path for the new structure + * @param name the name of the new structure + * @param dtm the data type manager for the structure + * @return the new structure + */ + public Structure build(CategoryPath path, String name, TraceBasedDataTypeManager dtm) { + List resolved = resolveOverlaps(); + if (resolved.isEmpty()) { + return null; + } + int length = (int) max.subtract(min); + if (length == 0) { + return null; + } + Structure structure = new StructureDataType(path, name, length, dtm); + MutableULongSpanSet undefined = new DefaultULongSpanSet(); + undefined.add(ULongSpan.extent(0, structure.getLength())); + for (FrameStructureBuilder.FrameField field : resolved) { + int offset = (int) field.address().subtract(min); + if (offset < 0) { + /** + * No function should reach beyond the current stack pointer, especially near a + * call, since that space is presumed to belong to the callee. When we see variables + * beyond the current depth, it's likely they're just not in scope. For example, a + * local variable may be allocated on the stack when entering a block, so Ghidra + * will show that variable in the frame. However, we may encounter a program counter + * during unwinding that has not entered that block, or if it did, has already + * exited and deallocated the local. We must omit such variables. NOTE: For some + * variables, notably those that re-use storage, we do note the start of it scope, + * but not the end. + * + * The min also accounts for the parameters in the previous frame. We'll observe + * writes to those, but we shouldn't see reads. Depending on changes to the unwind + * static analysis, passed parameters could get mistaken for saved registers. + * Nevertheless, we should prefer to assign overlapped portions of frames to the + * frame that uses them for parameters rather than scratch space. If diagnostics are + * desired, we may need to distinguish min from the SP vs min from SP + prevParams + * so that we can better assess each conflict. + */ + continue; + } + DataType type = field.type(); + if (type == IntegerDataType.dataType) { + type = IntegerDataType.getUnsignedDataType(field.length(), dtm); + } + else if (type == PointerDataType.dataType) { + type = new PointerTypedefBuilder(PointerDataType.dataType, dtm) + .addressSpace(codeSpace) + .build(); + } + structure.replaceAtOffset(offset, type, field.length(), field.name(), ""); + undefined.remove(ULongSpan.extent(offset, field.length())); + } + for (ULongSpan undefSpan : undefined.spans()) { + int spanLength = (int) undefSpan.length(); + DataType type = new ArrayDataType(DataType.DEFAULT, spanLength, 1, dtm); + int offset = undefSpan.min().intValue(); + Address addr = min.add(offset); + String fieldName = addr.getOffset() < 0 + ? String.format("offset_0x%x", -addr.getOffset()) + : String.format("posOff_0x%x", addr.getOffset()); + structure.replaceAtOffset(offset, type, spanLength, fieldName, ""); + } + return structure; + } + + void processVar(Address address, String name, DataType type, int length, int scopeStart) { + if (!address.isStackAddress()) { + return; + } + Address varMax = address.add(length); + if (varMax.compareTo(max) > 0) { + max = varMax; + } + fields.put(address, new FrameField(address, name, type, length, scopeStart)); + } + + void processRegisterVar(Address address, String name, DataType dataType, + Register register, int scopeStart) { + processVar(address, name, dataType, register.getNumBytes(), scopeStart); + } + + void processSavedRegister(Address address, Register register) { + processRegisterVar(address, SAVED_REGISTER_FIELD_PREFIX + register.getName(), + IntegerDataType.dataType, + register, -1); + } + + void processSaved(Map saved) { + for (Entry entry : saved.entrySet()) { + processSavedRegister(entry.getValue(), entry.getKey()); + } + } + + void processOfReturn(Address address) { + processRegisterVar(address, RETURN_ADDRESS_FIELD_NAME, PointerDataType.dataType, pc, + Integer.MAX_VALUE); + } + + void processFunction(Function function) { + for (Variable variable : function.getStackFrame().getStackVariables()) { + if (variable.getFirstUseOffset() > functionOffset) { + continue; + } + processVariable(variable); + } + } + + String prependIfAbsent(String prefix, String name) { + if (name.startsWith(prefix)) { + return name; + } + return prefix + name; + } + + void processVariable(Variable variable) { + Varnode[] varnodes = variable.getVariableStorage().getVarnodes(); + String name = variable.getName(); + if (variable instanceof Parameter) { + name = prependIfAbsent("param_", name); + } + else if (variable instanceof LocalVariable) { + name = prependIfAbsent("local_", name); + } + else { + throw new AssertionError(); + } + if (varnodes.length == 1) { + processVarnode(name, variable.getDataType(), varnodes[0], + variable.getFirstUseOffset()); + } + else { + for (int i = 0; i < varnodes.length; i++) { + processVarnode(name + "_pt" + i, IntegerDataType.dataType, varnodes[i], + variable.getFirstUseOffset()); + } + } + } + + void processVarnode(String name, DataType type, Varnode vn, int scopeStart) { + processVar(vn.getAddress(), name, type, vn.getSize(), scopeStart); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/ListingUnwoundFrame.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/ListingUnwoundFrame.java new file mode 100644 index 0000000000..51f8b06055 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/ListingUnwoundFrame.java @@ -0,0 +1,348 @@ +/* ### + * 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.plugin.core.debug.stack; + +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.exec.DebuggerPcodeUtils; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataType; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.Reference; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.DefaultTraceLocation; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.bookmark.TraceBookmark; +import ghidra.trace.model.listing.TraceData; +import ghidra.trace.model.symbol.TraceReference; +import ghidra.trace.util.TraceRegisterUtils; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; + +/** + * A frame restored from annotations applied to the trace listing + * + *

    + * This frame operates on {@link WatchValue}s, which are more than sufficient for most GUI elements. + * The unwinding and display of abstract values introduced by custom emulators is yet to be + * complete. + * + *

    + * This class may become deprecated. It allowed the GUI to use existing analysis that had been + * annotated in this listing. Certainly, that feature will remain, since the annotations are human + * consumable and help make sense of the stack segment. However, when other features need stack + * frames, they may or may not pull those frames from the listing. The trouble comes when a frame + * has 0 length. This can happen when a function has not pushed anything to the stack. On + * architectures without link registers, it should only happen in contingent cases, e.g., the + * analyzer can't find an exit path from the function, and so the return address location is not + * known. However, an invocation of a leaf function on an architecture with a link register may in + * fact have a 0-length frame for its entire life. Ghidra does not cope well with 0-length + * structures, and for good reason. Thus, in most cases, it is recommended to unwind, using + * {@link StackUnwinder}, and cache frames for later re-use. That pattern may be encapsulated in a + * centralized service later. + * + * @see AnalysisUnwoundFrame#applyToListing(int, TaskMonitor) + */ +public class ListingUnwoundFrame extends AbstractUnwoundFrame { + + /** + * Check if the given data unit conventionally represents a frame + * + *

    + * This is a simple conventional check, but it should rule out accidents. It checks that the + * unit's data type belongs to the {@link StackUnwinder#FRAMES_PATH} category. If the user or + * something else puts data types in that category, it's likely data units using those types may + * be mistaken for frames.... + * + * @param data the candidate frame + * @return true if it is likely a frame + */ + public static boolean isFrame(TraceData data) { + DataType type = data.getDataType(); + return type.getCategoryPath().equals(StackUnwinder.FRAMES_PATH); + } + + /** + * Get the conventional level of the frame represented by the givn data unit + * + *

    + * Technically, this violates the convention a little in that it parses the comment to determine + * the frame's level. One alternative is to examine the upper (or lower for positive growth) + * addresses for other frames, but that might require strict absence of gaps between frames. + * Another alternative is to encode the level into a property instead, which is more hidden from + * the user. + * + * @param data the frame + * @return the level + */ + private static Integer getLevel(TraceData data) { + // TODO: Should this go into a property instead? + String comment = data.getComment(CodeUnit.PRE_COMMENT); + if (comment == null) { + return null; + } + String[] parts = comment.split("\\s+"); + if (parts.length == 0) { + return null; + } + try { + return Integer.parseInt(parts[0]); + } + catch (NumberFormatException e) { + return null; + } + } + + private final TraceData frame; + + private final int level; + private final Address pcVal; + private final Function function; + private final Address base; + + private SavedRegisterMap registerMap; + + /** + * Recover a frame from annotations already in the trace listing + * + * @param tool the tool requesting interpretation of the frame, which provides context for + * mapped static programs. + * @param coordinates the coordinates (trace, thread, snap, etc.) to examine + * @param frame the data unit representing the frame + */ + public ListingUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates, TraceData frame) { + // NOTE: Always unwinding from frame 0 + super(tool, coordinates, DebuggerPcodeUtils.buildWatchState(tool, coordinates.frame(0))); + if (!isFrame(frame)) { + throw new IllegalArgumentException("frame does not appear to represent a frame"); + } + this.frame = frame; + + this.level = loadLevel(); + this.pcVal = loadProgramCounter(); + this.function = loadFunction(); + this.base = loadBasePointer(); + } + + @Override + public boolean isFake() { + return false; + } + + /** + * Get the data unit representing this frame + * + * @return the data unit + */ + public TraceData getData() { + return frame; + } + + @Override + protected Address applyBase(long offset) { + return base.add(offset); + } + + private int loadLevel() { + Integer l = getLevel(frame); + if (l == null) { + throw new IllegalStateException("Frame has no comment indicating its level"); + } + return l; + } + + private Address loadProgramCounter() { + for (Reference ref : frame.getReferenceIteratorTo()) { + if (ref.getReferenceType() != RefType.DATA) { + continue; + } + if (ref.getOperandIndex() != StackUnwinder.PC_OP_INDEX) { + continue; + } + return ref.getFromAddress(); + } + throw new UnwindException("The program counter reference is missing for the frame!"); + } + + private Function loadFunction() { + ProgramLocation staticLoc = + mappingService.getOpenMappedLocation(new DefaultTraceLocation(frame.getTrace(), null, + Lifespan.at(coordinates.getSnap()), pcVal)); + if (staticLoc == null) { + throw new UnwindException( + "The program containing the frame's function is unavailable," + + " or the mappings have changed."); + } + Function function = staticLoc.getProgram() + .getFunctionManager() + .getFunctionContaining(staticLoc.getAddress()); + if (function == null) { + throw new UnwindException( + "The function for the frame is no longer present in the mapped program."); + } + return function; + } + + private Address loadBasePointer() { + for (TraceReference ref : frame.getOperandReferences(StackUnwinder.BASE_OP_INDEX)) { + if (ref.getReferenceType() != RefType.DATA) { + continue; + } + return ref.getToAddress(); + } + return null; + } + + @Override + public String getDescription() { + return frame.getComment(CodeUnit.PRE_COMMENT); + } + + @Override + public int getLevel() { + return level; + } + + @Override + public Address getProgramCounter() { + return pcVal; + } + + @Override + public Function getFunction() { + return function; + } + + @Override + public Address getBasePointer() { + return base; + } + + @Override + protected Address computeAddressOfReturnAddress() { + int numComponents = frame.getNumComponents(); + for (int i = 0; i < numComponents; i++) { + TraceData component = frame.getComponent(i); + if (FrameStructureBuilder.RETURN_ADDRESS_FIELD_NAME.equals(component.getFieldName())) { + return component.getMinAddress(); + } + } + return null; + } + + @Override + public Address getReturnAddress() { + int numComponents = frame.getNumComponents(); + for (int i = 0; i < numComponents; i++) { + TraceData component = frame.getComponent(i); + if (FrameStructureBuilder.RETURN_ADDRESS_FIELD_NAME.equals(component.getFieldName())) { + Object value = component.getValue(); + if (value instanceof Address returnAddress) { + return returnAddress; + } + } + } + return null; + } + + @Override + public String getWarnings() { + for (TraceBookmark bookmark : frame.getTrace() + .getBookmarkManager() + .getBookmarksAt(frame.getStartSnap(), frame.getMinAddress())) { + if (bookmark.getTypeString().equals(BookmarkType.WARNING)) { + return bookmark.getComment(); + } + } + return null; + } + + @Override + protected synchronized SavedRegisterMap computeRegisterMap() { + if (registerMap != null) { + return registerMap; + } + TraceData[] innerFrames = new TraceData[level]; + for (TraceData inner : trace.getCodeManager() + .definedData() + .get(viewSnap, frame.getMinAddress(), false)) { + if (inner == frame) { + continue; + } + if (!isFrame(inner)) { + break; + } + Integer il = getLevel(inner); + if (il == null) { + break; + } + innerFrames[il] = inner; + } + registerMap = new SavedRegisterMap(); + for (TraceData inner : innerFrames) { + mapSavedRegisters(language, inner, registerMap); + } + return registerMap; + } + + /** + * Apply the saved registers for a given frame to the given map + * + *

    + * To be used effectively, all inner frames, excluding the desired context frame, must be + * visited from innermost to outermost, i.e, from level 0 to level n-1. This will ensure that + * the nearest saved value is used for each register. If a register was never saved, then its + * value is presumed still in the actual register. + * + * @param language the language defining the registers to map + * @param frame the data unit for the frame whose saved registers to consider + * @param map the map to modify with saved register information + */ + protected static void mapSavedRegisters(Language language, TraceData frame, + SavedRegisterMap map) { + int numComponents = frame.getNumComponents(); + for (int i = 0; i < numComponents; i++) { + TraceData component = frame.getComponent(i); + String name = component.getFieldName(); + if (!name.startsWith(FrameStructureBuilder.SAVED_REGISTER_FIELD_PREFIX)) { + continue; + } + String regName = + name.substring(FrameStructureBuilder.SAVED_REGISTER_FIELD_PREFIX.length()); + Register register = language.getRegister(regName); + if (register == null) { + Msg.warn(ListingUnwoundFrame.class, + "Unknown register name in saved_register field: " + regName); + continue; + } + map.put(TraceRegisterUtils.rangeForRegister(register), component.getRange()); + } + } + + /** + * Get the stack entry containing the given address + * + * @param address the address, must already have base applied + * @return the component, or null + */ + public TraceData getComponentContaining(Address address) { + return frame.getComponentContaining((int) address.subtract(frame.getMinAddress())); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SavedRegisterMap.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SavedRegisterMap.java new file mode 100644 index 0000000000..923c47ed6c --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SavedRegisterMap.java @@ -0,0 +1,333 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.nio.ByteBuffer; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; + +import ghidra.app.services.DebuggerStateEditingService.StateEditor; +import ghidra.async.AsyncFence; +import ghidra.pcode.eval.ArithmeticVarnodeEvaluator; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.pcode.Varnode; +import ghidra.trace.database.DBTraceUtils.AddressRangeMapSetter; +import ghidra.trace.util.TraceRegisterUtils; + +/** + * A map from registers to physical stack addresses + * + *

    + * This is used by an unwound frame to ensure that register reads are translated to stack reads when + * the register's value was saved to the stack by some inner frame. If a register is not saved to + * the stack by such a frame, then its value is read from the register bank. + */ +public class SavedRegisterMap { + /** + * An entry in the map + */ + record SavedEntry(AddressRange from, Address to) { + /** + * The range in register space to be redirected to the stack + * + * @return the "from" range + */ + public AddressRange from() { + return from; + } + + /** + * The physical address in the stack segment to which the register is redirected + * + *

    + * The length of the "to" range is given by the length of the "from" range + * + * @return the "to" address + */ + public Address to() { + return to; + } + + /** + * Check if an access should be redirected according to this entry + * + * @param address the address to be accessed + * @return true to redirect, false otherwise + */ + boolean contains(Address address) { + return from.contains(address); + } + + /** + * Produce an equivalent entry that redirects only the given new "from" range + * + * @param range the new "from" range, which must be enclosed by the current "from" range + * @return the same or truncated entry + */ + public SavedEntry truncate(AddressRange range) { + int right = (int) from.getMaxAddress().subtract(range.getMaxAddress()); + if (right < 0) { + throw new AssertionError("Cannot grow"); + } + int left = (int) from.getMinAddress().subtract(range.getMinAddress()); + if (left < 0) { + throw new AssertionError("Cannot grow"); + } + if (left == 0 && right == 0) { + return this; + } + return new SavedEntry(range, to.add(left)); + } + + /** + * Produce the same or equivalent entry that redirects at most the given "from" range + * + * @param range the "from" range to intersect + * @return the same or truncated entry + */ + public SavedEntry intersect(AddressRange range) { + AddressRange intersection = from.intersect(range); + return intersection == null ? null : truncate(intersection); + } + + /** + * Produce an equivalent entry which excludes any "from" address beyond the given max + * + * @param max the max "from" address + * @return the same or truncated entry + */ + public SavedEntry truncateMax(Address max) { + if (from.getMaxAddress().compareTo(max) <= 0) { + return this; + } + if (from.getMinAddress().compareTo(max) <= 0) { + return truncate(new AddressRangeImpl(from.getMinAddress(), max)); + } + return null; + } + + /** + * Produce an equivalent entry which exclude any "from" address before the given min + * + * @param min the min "from" address + * @return the same or truncated entry + */ + public SavedEntry truncateMin(Address min) { + if (from.getMinAddress().compareTo(min) >= 0) { + return this; + } + if (from.getMaxAddress().compareTo(min) >= 0) { + return truncate(new AddressRangeImpl(min, from.getMaxAddress())); + } + return null; + } + + /** + * The length of the mapped ranges + * + * @return the length + */ + public int size() { + return (int) from.getLength(); + } + } + + /** + * A class which can set values over a range, ensuring no overlapping entries + */ + protected class SavedEntrySetter + extends AddressRangeMapSetter, SavedEntry> { + @Override + protected AddressRange getRange(Entry entry) { + return entry.getValue().from; + } + + @Override + protected SavedEntry getValue(Entry entry) { + return entry.getValue(); + } + + @Override + protected void remove(Entry entry) { + saved.remove(entry.getKey()); + } + + @Override + protected Iterable> getIntersecting(Address lower, + Address upper) { + return subMap(lower, upper).entrySet(); + } + + @Override + protected Entry put(AddressRange range, SavedEntry value) { + saved.put(range.getMinAddress(), value.truncate(range)); + return null; + } + } + + private final NavigableMap saved; + private final SavedEntrySetter setter = new SavedEntrySetter(); + + /** + * Construct an empty (identity) register map + */ + public SavedRegisterMap() { + this.saved = new TreeMap<>(); + } + + /** + * Copy a given register map + * + * @param saved the map to copy + */ + public SavedRegisterMap(TreeMap saved) { + this.saved = new TreeMap<>(); + } + + private NavigableMap subMap(Address lower, Address upper) { + Entry adjEnt = saved.floorEntry(lower); + if (adjEnt != null && adjEnt.getValue().contains(upper)) { + lower = adjEnt.getKey(); + } + return saved.subMap(lower, true, upper, true); + } + + static AddressRange rangeForVarnode(Varnode vn) { + return new AddressRangeImpl(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1)); + } + + /** + * Map a register to a stack varnode + * + * @param from the register + * @param stackVar the stack varnode + */ + public void put(Register from, Varnode stackVar) { + put(TraceRegisterUtils.rangeForRegister(from), rangeForVarnode(stackVar)); + } + + /** + * Map the given ranges, which must have equal lengths + * + * @param from the range in register space + * @param to the range in the stack segment + */ + public void put(AddressRange from, AddressRange to) { + if (from.getLength() != to.getLength()) { + throw new IllegalArgumentException("from and to must match in length"); + } + put(from, to.getMinAddress()); + } + + /** + * Map the given range to the given address + * + * @param from the range in register space + * @param to the address in the stack segment + */ + public void put(AddressRange from, Address to) { + setter.set(from, new SavedEntry(from, to)); + } + + /** + * Copy this register map + * + * @return the copy + */ + public SavedRegisterMap fork() { + return new SavedRegisterMap(new TreeMap<>(saved)); + } + + private abstract class PieceVisitor { + public U visitVarnode(Address address, int size, U user) { + AddressRange range = new AddressRangeImpl(address, address.add(size - 1)); + SavedEntry identity = new SavedEntry(range, address); + for (SavedEntry se : subMap(range.getMinAddress(), range.getMaxAddress()).values()) { + Address prev = se.from.getMinAddress().previous(); + if (prev != null) { + SavedEntry idLeft = identity.truncateMax(prev); + if (idLeft != null) { + user = visitPiece(idLeft.to, idLeft.size(), user); + } + } + SavedEntry piece = se.intersect(range); + user = visitPiece(piece.to, piece.size(), user); + Address next = se.from.getMaxAddress().next(); + if (next == null) { + return user; + } + identity = identity.truncateMin(next); + } + if (identity != null) { + user = visitPiece(identity.to, identity.size(), user); + } + return user; + } + + abstract U visitPiece(Address address, int size, U user); + } + + /** + * Get a variable from the given state wrt. this mapping + * + *

    + * Register reads are redirected to the mapped addresses when applicable. + * + * @param the type of values in the state + * @param state the state to access + * @param address the address of the variable + * @param size the size of the variable + * @param reason a reason for reading the variable + * @return the variable's value + */ + public T getVar(PcodeExecutorState state, Address address, int size, Reason reason) { + PcodeArithmetic arithmetic = state.getArithmetic(); + return new PieceVisitor() { + @Override + T visitPiece(Address address, int sz, T value) { + T piece = state.getVar(address, size, true, reason); + return ArithmeticVarnodeEvaluator.catenate(arithmetic, size, value, piece, sz); + } + }.visitVarnode(address, size, arithmetic.fromConst(0, size)); + } + + /** + * Set a variable using the given editor wrt. this mapping + * + * @param editor the editor + * @param address the address of the variable + * @param bytes the bytes (in language-dependent endianness) giving the variable's value + * @return a future that completes when all editing commands have completed + */ + public CompletableFuture setVar(StateEditor editor, Address address, byte[] bytes) { + AsyncFence fence = new AsyncFence(); + new PieceVisitor() { + @Override + ByteBuffer visitPiece(Address address, int size, ByteBuffer buf) { + byte[] sub = new byte[size]; + buf.get(sub); + fence.include(editor.setVariable(address, sub)); + return buf; + } + }.visitVarnode(address, bytes.length, ByteBuffer.wrap(bytes)); + return fence.ready(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwindWarning.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwindWarning.java new file mode 100644 index 0000000000..d598483917 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwindWarning.java @@ -0,0 +1,199 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.Function; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.PcodeOpAST; + +/** + * A warning issued while unwinding a stack + * + *

    + * This is designed to avoid the untamed bucket of messages that a warning set usually turns into. + * In essence, it's still a bucket of messages; however, each type is curated and has some logic for + * how it interacts with other messages and additional instances of itself. + */ +public interface StackUnwindWarning { + /** + * A warning that can be combined with other instances of itself + * + * @param the same type as me (recursive) + */ + interface Combinable { + String summarize(Collection all); + } + + /** + * Get the message for display + * + * @return the message + */ + String getMessage(); + + /** + * Check if the given warning can be omitted on account of this warning + * + *

    + * Usually, the unwinder should be careful not to emit unnecessary warnings, but at times that + * can be difficult, and its proper implementation may complicate the actual unwind logic. This + * allows the unnecessary warning to be removed afterward. + * + * @param other the other warning + * @return true if this warning deems the other unnecessary + */ + default boolean moots(StackUnwindWarning other) { + return false; + } + + /** + * The unwind analyzer could not find an exit path from the frame's program counter. + */ + public record NoReturnPathStackUnwindWarning(Address pc) implements StackUnwindWarning { + @Override + public String getMessage() { + return "Could not find a path from " + pc + " to a return"; + } + + @Override + public boolean moots(StackUnwindWarning other) { + return other instanceof OpaqueReturnPathStackUnwindWarning; + } + } + + /** + * The unwind analyzer discovered at last one exit path, but none could be analyzed. + */ + public record OpaqueReturnPathStackUnwindWarning(Address pc) implements StackUnwindWarning { + @Override + public String getMessage() { + return "Could not analyze any path from " + pc + " to a return"; + } + } + + /** + * While analyzing instructions, the unwind analyzer encountered a call to a function whose + * effect on the stack is unknown. + * + *

    + * The analyzer does not descend into calls or otherwise implement inter-procedural analysis. + * Instead, it relies on analysis already performed by Ghidra's other analyzers and/or the human + * user. The analyzer will assume a reasonable default. + */ + public record UnknownPurgeStackUnwindWarning(Function function) + implements StackUnwindWarning, Combinable { + @Override + public String getMessage() { + return "Function " + function + " has unknown/invalid stack purge"; + } + + @Override + public String summarize(Collection all) { + Stream sortedDisplay = + all.stream().map(w -> w.function.getName(false)).sorted(); + if (all.size() > 7) { + return "Functions " + + sortedDisplay.limit(7).collect(Collectors.joining(", ")) + + ", ... have unknown/invalid stack purge."; + } + return "Functions " + sortedDisplay.collect(Collectors.joining(", ")) + + " have unknown/invalid stack purge."; + } + } + + /** + * While analyzing instructions, the unwind analyzer encountered a call to a function whose + * convention is not known. + * + *

    + * The analyzer will assume the default convention for the program's compiler. + */ + public record UnspecifiedConventionStackUnwindWarning(Function function) + implements StackUnwindWarning, Combinable { + @Override + public String getMessage() { + return "Function " + function + " has unspecified convention. Using default"; + } + + @Override + public String summarize(Collection all) { + Stream sortedDisplay = + all.stream().map(w -> w.function.getName(false)).sorted(); + if (all.size() > 7) { + return "Functions " + + sortedDisplay.limit(7).collect(Collectors.joining(", ")) + + ", ... have unspecified convention."; + } + return "Functions " + sortedDisplay.collect(Collectors.joining(", ")) + + " have unspecified convention."; + } + } + + /** + * While analyzing an indirect call, using the decompiler, the unwind analyzer obtained multiple + * high {@link PcodeOp#CALL} or {@link PcodeOp#CALLIND} p-code ops. + * + *

    + * Perhaps this should be replaced by an assertion, but failing fast may not be a good approach + * for this case. + */ + public record MultipleHighCallsStackUnwindWarning(List found) + implements StackUnwindWarning { + @Override + public String getMessage() { + return "Caller generated multiple decompiled calls. How?: " + found; + } + } + + /** + * Similar to {@link MultipleHighCallsStackUnwindWarning}, except no high call p-code ops. + */ + public record NoHighCallsStackUnwindWarning(PcodeOp op) implements StackUnwindWarning { + @Override + public String getMessage() { + return "Caller generated no decompiled calls. How?:" + op; + } + } + + /** + * While analyzing an indirect call, the target's type was not a function pointer. + */ + public record UnexpectedTargetTypeStackUnwindWarning(DataType type) + implements StackUnwindWarning { + @Override + public String getMessage() { + return "Indirect call target has unexpected type: " + type; + } + } + + /** + * While analyzing an indirect call, the signature could not be derived from call-site context. + */ + public record CouldNotRecoverSignatureStackUnwindWarning(PcodeOpAST op) + implements StackUnwindWarning { + @Override + public String getMessage() { + return "Could not recover signature of indirect call: " + op; + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwindWarningSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwindWarningSet.java new file mode 100644 index 0000000000..3d3a652cd7 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwindWarningSet.java @@ -0,0 +1,160 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.*; +import java.util.stream.Collectors; + +import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.Combinable; + +/** + * A bucket of warnings + * + *

    + * This collects stack unwind warnings and then culls, and combines them for display. + */ +public class StackUnwindWarningSet implements Collection { + private final Collection warnings = new LinkedHashSet<>(); + + /** + * Create a new empty set + */ + public StackUnwindWarningSet() { + } + + /** + * Create a new set with the given initial warnings + * + * @param warnings the warnings + */ + public StackUnwindWarningSet(StackUnwindWarning... warnings) { + this.warnings.addAll(Arrays.asList(warnings)); + } + + /** + * Copy the given set + * + * @param warnings the other set + */ + public StackUnwindWarningSet(Collection warnings) { + this.warnings.addAll(warnings); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof StackUnwindWarningSet that)) { + return false; + } + return this.warnings.equals(that.warnings); + } + + @Override + public int size() { + return warnings.size(); + } + + @Override + public boolean isEmpty() { + return warnings.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return warnings.contains(o); + } + + @Override + public Iterator iterator() { + return warnings.iterator(); + } + + @Override + public Object[] toArray() { + return warnings.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return warnings.toArray(a); + } + + @Override + public boolean add(StackUnwindWarning e) { + return warnings.add(e); + } + + @Override + public boolean remove(Object o) { + return warnings.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return warnings.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return warnings.addAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return warnings.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return warnings.retainAll(c); + } + + @Override + public void clear() { + warnings.clear(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public String summarize() { + Set> combined = new LinkedHashSet<>(); + List lines = new ArrayList<>(); + for (StackUnwindWarning w : warnings) { + if (warnings.stream().anyMatch(mw -> mw.moots(w))) { + // do nothing + } + else if (w instanceof Combinable c) { + Class cls = w.getClass(); + if (!combined.add(cls)) { + continue; + } + Collection all = + warnings.stream().filter(cw -> cls.isInstance(cw)).collect(Collectors.toList()); + if (all.size() == 1) { + lines.add(w.getMessage()); + } + else { + lines.add(c.summarize(all)); + } + } + else { + lines.add(w.getMessage()); + } + } + return lines.stream().collect(Collectors.joining("\n")); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java new file mode 100644 index 0000000000..ccfc5a81b2 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java @@ -0,0 +1,316 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.*; + +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.exec.DebuggerPcodeUtils; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValuePcodeExecutorState; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.data.CategoryPath; +import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Reference; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.*; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.stack.TraceStack; +import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A mechanism for unwinding the stack or parts of it + * + *

    + * It can start at any frame for which the program counter and stack pointer are known. The choice + * of starting frame is informed by some tradeoffs. For making sense of a specific frame, it might + * be best to start at the nearest frame with confidently recorded PC and SP values. This will + * ensure there is little room for error unwinding from the known frame to the desired frame. For + * retrieving variable values, esp. variables stored in registers, it might be best to start at the + * innermost frame, unless all registers in a nearer frame are confidently recorded. The registers + * in frame 0 are typically recorded with highest confidence. This will ensure that all saved + * register values are properly restored from the stack into the desired frame. + * + *

    + * The usage pattern is typically: + * + *

    + * StackUnwinder unwinder = new StackUnwinder(tool, coordinates.getPlatform());
    + * for (AnalysisUnwoundFrame frame : unwinder.frames(coordinates.frame(0), monitor)) {
    + * 	// check and/or cache the frame
    + * }
    + * 
    + * + *

    + * Typically, a frame is sought either by its level or by its function. Once found, several + * operations can be performed with it, including applying annotations to the listing for the stack + * segment (see {@link AnalysisUnwoundFrame#applyToListing(int, TaskMonitor)}) and computing values + * of variables (see {@link UnwoundFrame}.) The iterator unwinds each frame lazily. If the iterator + * stops sooner than expected, consider using {@link #start(DebuggerCoordinates, TaskMonitor)} and + * {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)} directly to get better diagnostics. + */ +public class StackUnwinder { + public static final CategoryPath FRAMES_PATH = new CategoryPath("/Frames"); + public static final int PC_OP_INDEX = Reference.MNEMONIC; + public static final int BASE_OP_INDEX = 0; + + private static DebuggerStaticMappingService getMappings(PluginTool tool) { + return tool.getService(DebuggerStaticMappingService.class); + } + + private static WatchValuePcodeExecutorState getState(PluginTool tool, + DebuggerCoordinates coordinates) { + return DebuggerPcodeUtils.buildWatchState(tool, coordinates); + } + + private final PluginTool tool; + private final DebuggerStaticMappingService mappings; + final TracePlatform platform; + final Trace trace; + + final Register pc; + final AddressSpace codeSpace; + private final Register sp; + private final AddressSpace stackSpace; + + /** + * Construct an unwinder + * + * @param tool the tool with applicable modules opened as programs + * @param platform the trace platform (for registers, spaces, and stack conventions) + */ + public StackUnwinder(PluginTool tool, TracePlatform platform) { + this.tool = tool; + this.mappings = getMappings(tool); + this.platform = platform; + this.trace = platform.getTrace(); + + this.pc = Objects.requireNonNull(platform.getLanguage().getProgramCounter(), + "Platform must have a program counter"); + this.codeSpace = platform.getLanguage().getDefaultSpace(); + + CompilerSpec compiler = platform.getCompilerSpec(); + this.sp = Objects.requireNonNull(compiler.getStackPointer(), + "Platform must have a stack pointer"); + this.stackSpace = compiler.getStackBaseSpace(); + } + + /** + * Begin unwinding frames that can evaluate variables as {@link WatchValue}s + * + *

    + * While the returned frame is not technically "unwound," it is necessary to derive its base + * pointer in order to evaluate any of its variables and unwind subsequent frames. The returned + * frame has the {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)} method. + * + * @param coordinates the starting coordinates, particularly the frame level + * @param monitor a monitor for cancellation + * @return the frame for the given level + * @throws CancelledException if the monitor is cancelled + */ + public AnalysisUnwoundFrame start(DebuggerCoordinates coordinates, + TaskMonitor monitor) + throws CancelledException { + if (coordinates.getPlatform() != platform) { + throw new IllegalArgumentException("Not same platform"); + } + return start(coordinates, getState(tool, coordinates), monitor); + } + + /** + * Begin unwinding frames that can evaluate variables from the given state + * + *

    + * If is the caller's responsibility to ensure that the given state corresponds to the given + * coordinates. If they do not, the result is undefined. + * + *

    + * The starting frame's program counter and stack pointer are derived from the trace (in + * coordinates), not the state. The program counter will be retrieved from the + * {@link TraceStackFrame} if available. Otherwise, it will use the value in the register bank + * for the starting frame level. If it is not known, the unwind fails. The static (module) + * mappings are used to find the function containing the program counter, and that function is + * analyzed for its unwind info, wrt. the mapped program counter. See + * {@link UnwindAnalysis#computeUnwindInfo(Address, TaskMonitor)}. Depending on the complexity + * of the function, that analysis may be expensive. If the function cannot be found, the unwind + * fails. If analysis fails, the resulting frame may be incomplete, or the unwind may fail. + * Subsequent frames are handled similarly. See + * {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)}. + * + * @param the type of values in the state, and the result of variable evaluations + * @param coordinates the starting coordinates, particularly the frame level + * @param state the state, which must correspond to the given coordinates + * @param monitor a monitor for cancellation + * @return the frame for the given level + * @throws CancelledException if the monitor is cancelled + */ + public AnalysisUnwoundFrame start(DebuggerCoordinates coordinates, + PcodeExecutorState state, TaskMonitor monitor) throws CancelledException { + return start(coordinates, coordinates.getFrame(), state, monitor); + } + + protected AnalysisUnwoundFrame start(DebuggerCoordinates coordinates, int level, + PcodeExecutorState state, TaskMonitor monitor) throws CancelledException { + Address pcVal = null; + TraceThread thread = coordinates.getThread(); + long viewSnap = coordinates.getViewSnap(); + TraceStack stack = trace.getStackManager().getLatestStack(thread, viewSnap); + if (stack != null) { + TraceStackFrame frame = stack.getFrame(level, false); + if (frame != null) { + pcVal = frame.getProgramCounter(viewSnap); + } + } + TraceMemorySpace regs = Objects.requireNonNull( + trace.getMemoryManager().getMemoryRegisterSpace(thread, level, false), + "Frame must have a register bank"); + if (pcVal == null) { + if (TraceMemoryState.KNOWN != regs.getState(platform, viewSnap, pc)) { + throw new UnwindException("Frame must have KNOWN " + pc + " value"); + } + pcVal = codeSpace.getAddress( + regs.getValue(platform, viewSnap, pc).getUnsignedValue().longValue()); + } + if (TraceMemoryState.KNOWN != regs.getState(platform, viewSnap, sp)) { + throw new UnwindException("Frame must have KNOWN " + sp + " value"); + } + Address spVal = stackSpace.getAddress( + regs.getValue(platform, viewSnap, sp).getUnsignedValue().longValue()); + return unwind(coordinates, level, pcVal, spVal, state, new SavedRegisterMap(), monitor); + } + + /** + * Compute the unwind information for the given program counter and context + * + *

    + * For the most part, this just translates the dynamic program counter to a static program + * address and then invokes {@link UnwindAnalysis#computeUnwindInfo(Address, TaskMonitor)}. + * + * @param snap the snapshot key (used for mapping the program counter to a program database) + * @param level the frame level, used only for error messages + * @param pcVal the program counter (dynamic) + * @param monitor a monitor for cancellation + * @return the unwind info, possibly incomplete + * @throws CancelledException if the monitor is cancelled + */ + public UnwindInfo computeUnwindInfo(long snap, int level, Address pcVal, TaskMonitor monitor) + throws CancelledException { + // TODO: Try markup in trace first? + ProgramLocation staticPcLoc = mappings == null ? null + : mappings.getOpenMappedLocation( + new DefaultTraceLocation(trace, null, Lifespan.at(snap), pcVal)); + if (staticPcLoc == null) { + throw new UnwindException("Cannot find static program for frame " + level + " (" + + pc + "=" + pcVal + ")"); + } + Program program = staticPcLoc.getProgram(); + Address staticPc = staticPcLoc.getAddress(); + // TODO: Cache these? + UnwindAnalysis ua = new UnwindAnalysis(program); + return ua.computeUnwindInfo(staticPc, monitor); + } + + AnalysisUnwoundFrame unwind(DebuggerCoordinates coordinates, int level, Address pcVal, + Address spVal, PcodeExecutorState state, SavedRegisterMap registerMap, + TaskMonitor monitor) + throws CancelledException { + try { + UnwindInfo info = computeUnwindInfo(coordinates.getSnap(), level, pcVal, monitor); + return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal, + info, null, registerMap); + } + catch (UnwindException e) { + return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal, + null, e, registerMap); + } + } + + /** + * An iterable wrapper for {@link #start(DebuggerCoordinates, PcodeExecutorState, TaskMonitor)} + * and {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)} + * + * @param the type of values in the state + * @param coordinates the starting coordinates + * @param state the state + * @param monitor the monitor + * @return the iterable over unwound frames + */ + public Iterable> frames(DebuggerCoordinates coordinates, + PcodeExecutorState state, TaskMonitor monitor) { + return new Iterable<>() { + @Override + public Iterator> iterator() { + return new Iterator<>() { + AnalysisUnwoundFrame next = tryStart(); + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public AnalysisUnwoundFrame next() { + AnalysisUnwoundFrame cur = next; + next = tryNext(); + return cur; + } + + private AnalysisUnwoundFrame tryStart() { + try { + return start(coordinates, state, monitor); + } + catch (UnwindException | CancelledException e) { + return null; + } + } + + private AnalysisUnwoundFrame tryNext() { + try { + return next.unwindNext(monitor); + } + catch (NoSuchElementException | UnwindException | CancelledException e) { + return null; + } + } + }; + } + }; + } + + /** + * An iterable wrapper for {@link #start(DebuggerCoordinates, TaskMonitor)} and + * {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)} + * + * @param coordinates the starting coordinates + * @param monitor the monitor + * @return the iterable over unwound frames + */ + public Iterable> frames(DebuggerCoordinates coordinates, + TaskMonitor monitor) { + return frames(coordinates, getState(tool, coordinates), monitor); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/Sym.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/Sym.java new file mode 100644 index 0000000000..756e8d3dd2 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/Sym.java @@ -0,0 +1,300 @@ +/* ### + * 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.plugin.core.debug.stack; + +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.lang.Register; + +/** + * A symbolic value tailored for stack unwind analysis + * + *

    + * The goals of stack unwind analysis are 1) to figure the stack depth at a particular instruction, + * 2) to figure the locations of saved registers on the stack, 3) to figure the location of the + * return address, whether in a register or on the stack, and 4) to figure the change in stack depth + * from calling the function. Not surprisingly, these are the fields of {@link UnwindInfo}. To these + * ends, symbols may have only one of the following forms: + * + *

      + *
    • An opaque value: {@link OpaqueSym}, to represent expressions too complex.
    • + *
    • A constant: {@link ConstSym}, to fold constants and use as offsets.
    • + *
    • A register: {@link RegisterSym}, to detect saved registers and to generate stack offsets
    • + *
    • A stack offset, i.e., SP + c: {@link StackOffsetSym}, to fold offsets, detect stack depth, + * and to generate stack dereferences
    • + *
    • A dereference of a stack offset, i.e., *(SP + c): {@link StackDerefSym}, to detect restored + * registers and return address location
    • + *
    + * + *

    + * The rules are fairly straightforward: + * + *

      + *
    • a:Opaque + b:Any => Opaque()
    • + *
    • a:Const + b:Const => Const(val=a.val + b.val)
    • + *
    • a:Const + b:Register(reg==SP) => Offset(offset=a.val)
    • + *
    • a:Offset: + b:Const => Offset(offset=a.offset + b.val)
    • + *
    • *a:Offset => Deref(offset=a.offset)
    • + *
    • *a:Register(reg==SP) => Deref(offset=0)
    • + *
    + * + *

    + * Some minute operations are omitted for clarity. Any other operation results in Opaque(). There is + * a small fault in that Register(reg=SP) and Offset(offset=0) represent the same thing, but with + * some extra bookkeeping, it's not too terrible. By interpreting p-code and then examining the + * symbolic machine state, simple movement of data between registers and the stack can be + * summarized. + */ +sealed interface Sym { + /** + * Get the opaque symbol + * + * @return the symbol + */ + static Sym opaque() { + return OpaqueSym.OPAQUE; + } + + /** + * Add this and another symbol with the given compiler for context + * + * @param cSpec the compiler specification + * @param in2 the second symbol + * @return the resulting symbol + */ + Sym add(CompilerSpec cSpec, Sym in2); + + /** + * Subtract another symbol from this with the given compiler for context + * + * @param cSpec the compiler specification + * @param in2 the second symbol + * @return the resulting symbol + */ + default Sym sub(CompilerSpec cSpec, Sym in2) { + return add(cSpec, in2.twosComp()); + } + + /** + * Negate this symbol + * + * @return the resulting symbol + */ + Sym twosComp(); + + /** + * Get the size of this symbol with the given compiler for context + * + * @param cSpec the compiler specification + * @return the size in bytes + */ + long sizeOf(CompilerSpec cSpec); + + /** + * Get a constant symbol + * + * @param value the value + * @return the constant (with size 8 bytes) + */ + static Sym constant(long value) { + return new ConstSym(value, 8); + } + + /** + * When this symbol is used as the offset in a given address space, translate it to the address + * if possible + * + *

    + * The address will be used by the state to retrieve the appropriate (symbolic) value, possibly + * generating a fresh symbol. If the address is {@link Address#NO_ADDRESS}, then the state will + * yield the opaque symbol. For sets, the state will store the given symbolic value at the + * address. If it is {@link Address#NO_ADDRESS}, then the value is ignored. + * + * @param space the space being dereferenced + * @param cSpec the compiler specification + * @return the address, or {@link Address#NO_ADDRESS} + */ + Address addressIn(AddressSpace space, CompilerSpec cSpec); + + /** + * The singleton opaque symbol + */ + public enum OpaqueSym implements Sym { + /** + * Singleton instance + */ + OPAQUE; + + @Override + public Sym add(CompilerSpec cSpec, Sym in2) { + return this; + } + + @Override + public Sym twosComp() { + return this; + } + + @Override + public long sizeOf(CompilerSpec cSpec) { + throw new UnsupportedOperationException(); + } + + @Override + public Address addressIn(AddressSpace space, CompilerSpec cSpec) { + return Address.NO_ADDRESS; + } + } + + /** + * A constant symbol + */ + public record ConstSym(long value, int size) implements Sym { + @Override + public Sym add(CompilerSpec cSpec, Sym in2) { + if (in2 instanceof ConstSym const2) { + return new ConstSym(value + const2.value, size); + } + if (in2 instanceof RegisterSym reg2) { + if (reg2.register() == cSpec.getStackPointer()) { + return new StackOffsetSym(value); + } + return Sym.opaque(); + } + if (in2 instanceof StackOffsetSym off2) { + return new StackOffsetSym(value + off2.offset); + } + return Sym.opaque(); + } + + @Override + public Sym twosComp() { + return new ConstSym(-value, size); + } + + @Override + public long sizeOf(CompilerSpec cSpec) { + return size; + } + + @Override + public Address addressIn(AddressSpace space, CompilerSpec cSpec) { + if (space.isConstantSpace() || space.isRegisterSpace() || space.isUniqueSpace()) { + return space.getAddress(value); + } + return Address.NO_ADDRESS; + } + } + + /** + * A register symbol + */ + public record RegisterSym(Register register) implements Sym { + @Override + public Sym add(CompilerSpec cSpec, Sym in2) { + if (in2 instanceof ConstSym const2) { + return const2.add(cSpec, this); + } + return Sym.opaque(); + } + + @Override + public Sym twosComp() { + return Sym.opaque(); + } + + @Override + public long sizeOf(CompilerSpec cSpec) { + return register.getMinimumByteSize(); + } + + @Override + public Address addressIn(AddressSpace space, CompilerSpec cSpec) { + if (register != cSpec.getStackPointer()) { + return Address.NO_ADDRESS; + } + if (space != cSpec.getStackBaseSpace()) { + return Address.NO_ADDRESS; + } + return cSpec.getStackSpace().getAddress(0); + } + } + + /** + * A stack offset symbol + * + *

    + * This represents a value in the form SP + c, where SP is the stack pointer register and c is a + * constant. + */ + public record StackOffsetSym(long offset) implements Sym { + @Override + public Sym add(CompilerSpec cSpec, Sym in2) { + if (in2 instanceof ConstSym const2) { + return new StackOffsetSym(offset + const2.value()); + } + return Sym.opaque(); + } + + @Override + public Sym twosComp() { + return Sym.opaque(); + } + + @Override + public long sizeOf(CompilerSpec cSpec) { + return cSpec.getStackPointer().getMinimumByteSize(); + } + + @Override + public Address addressIn(AddressSpace space, CompilerSpec cSpec) { + if (space != cSpec.getStackBaseSpace()) { + return Address.NO_ADDRESS; + } + return cSpec.getStackSpace().getAddress(offset); + } + } + + /** + * A stack dereference symbol + * + *

    + * This represents a dereferenced {@link StackOffsetSym} (or the dereferenced stack pointer + * register, in which is treated as a stack offset of 0). + */ + public record StackDerefSym(long offset, int size) implements Sym { + @Override + public Sym add(CompilerSpec cSpec, Sym in2) { + return Sym.opaque(); + } + + @Override + public Sym twosComp() { + return Sym.opaque(); + } + + @Override + public long sizeOf(CompilerSpec cSpec) { + return size; + } + + @Override + public Address addressIn(AddressSpace space, CompilerSpec cSpec) { + return Address.NO_ADDRESS; + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeArithmetic.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeArithmetic.java new file mode 100644 index 0000000000..5687e82e94 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeArithmetic.java @@ -0,0 +1,102 @@ +/* ### + * 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.plugin.core.debug.stack; + +import ghidra.app.plugin.core.debug.stack.Sym.ConstSym; +import ghidra.pcode.exec.ConcretionError; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.lang.*; +import ghidra.program.model.pcode.PcodeOp; + +/** + * The interpretation of arithmetic p-code ops in the domain of {@link Sym} for a specific compiler + * specification + */ +class SymPcodeArithmetic implements PcodeArithmetic { + + private final Language language; + private final CompilerSpec cSpec; + + /** + * Construct the arithmetic + * + * @param cSpec the compiler specification + */ + public SymPcodeArithmetic(CompilerSpec cSpec) { + this.cSpec = cSpec; + this.language = cSpec.getLanguage(); + } + + @Override + public Endian getEndian() { + return language.isBigEndian() ? Endian.BIG : Endian.LITTLE; + } + + @Override + public Sym unaryOp(int opcode, int sizeout, int sizein1, Sym in1) { + switch (opcode) { + case PcodeOp.COPY: + return in1; + default: + return Sym.opaque(); + } + } + + @Override + public Sym binaryOp(int opcode, int sizeout, int sizein1, Sym in1, int sizein2, + Sym in2) { + switch (opcode) { + case PcodeOp.INT_ADD: + return in1.add(cSpec, in2); + case PcodeOp.INT_SUB: + return in1.sub(cSpec, in2); + default: + return Sym.opaque(); + } + } + + @Override + public Sym modBeforeStore(int sizeout, int sizeinAddress, Sym inAddress, + int sizeinValue, Sym inValue) { + return inValue; + } + + @Override + public Sym modAfterLoad(int sizeout, int sizeinAddress, Sym inAddress, + int sizeinValue, Sym inValue) { + return inValue; + } + + @Override + public Sym fromConst(byte[] value) { + return new ConstSym(Utils.bytesToLong(value, value.length, language.isBigEndian()), + value.length); + } + + @Override + public byte[] toConcrete(Sym value, Purpose purpose) { + if (value instanceof ConstSym constVal) { + return Utils.longToBytes(constVal.value(), constVal.size(), language.isBigEndian()); + } + throw new ConcretionError("Not a constant: " + value, purpose); + } + + @Override + public long sizeOf(Sym value) { + return value.sizeOf(cSpec); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutor.java new file mode 100644 index 0000000000..61c11be009 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutor.java @@ -0,0 +1,392 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.*; + +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileResults; +import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.*; +import ghidra.app.plugin.processors.sleigh.SleighException; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.*; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; + +/** + * The interpreter of p-code ops in the domain of {@link Sym} + * + *

    + * This is used for static analysis by executing specific basic blocks. As such, it should never be + * expected to interpret a conditional jump. (TODO: This rule might be violated if a fall-through + * instruction has internal conditional branches.... To fix would require breaking the p-code down + * into basic blocks.) We also do not want it to descend into subroutines. Thus, we must treat calls + * differently. Most of the implementation of this class is to attend to function calls, especially, + * indirect calls. For direct calls, it attempts to find the function in the same program (possibly + * in its import table) and derive the resulting stack effects from the database. Failing that, it + * issues warnings and makes reasonable assumptions. For indirect calls, it attempts to decompile + * the caller and examines the call site. If the target's type is known (presumably a function + * pointer), then the stack effects are derived from the signature and its calling convention. If + * not, then it examines the inputs and output (if applicable) to derive a signature and then + * figures the stack effects. In many cases, the stack adjustment is defined solely by the compiler, + * but for the {@code __stdcall} convention prominent in 32-bit x86 binaries for Windows, the input + * parameters must also be examined. + */ +class SymPcodeExecutor extends PcodeExecutor { + + /** + * Construct an executor for performing stack unwind analysis of a given program + * + * @param program the program to analyze + * @param state the symbolic state + * @param reason a reason to give when reading state + * @param warnings a place to emit warnings + * @param monitor a monitor for analysis, usually decompilation + * @return the executor + */ + public static SymPcodeExecutor forProgram(Program program, SymPcodeExecutorState state, + Reason reason, Set warnings, TaskMonitor monitor) { + CompilerSpec cSpec = program.getCompilerSpec(); + SleighLanguage language = (SleighLanguage) cSpec.getLanguage(); + SymPcodeArithmetic arithmetic = new SymPcodeArithmetic(cSpec); + return new SymPcodeExecutor(program, cSpec, language, arithmetic, state, reason, + warnings, monitor); + } + + private final Program program; + private final Register sp; + private final Set warnings; + private final TaskMonitor monitor; + + private final DecompInterface decomp = new DecompInterface(); + // TODO: This could perhaps be moved into AnalysisForPC? + // Meh, as it is, it should only have at most 1 entry + private final Map decompCache = new HashMap<>(); + + public SymPcodeExecutor(Program program, CompilerSpec cSpec, SleighLanguage language, + SymPcodeArithmetic arithmetic, SymPcodeExecutorState state, Reason reason, + Set warnings, TaskMonitor monitor) { + super(language, arithmetic, state, reason); + this.program = program; + this.sp = cSpec.getStackPointer(); + this.warnings = warnings; + this.monitor = monitor; + } + + @Override + public void executeCallother(PcodeOp op, PcodeFrame frame, + PcodeUseropLibrary library) { + // Do nothing + // TODO: Is there a way to know if a userop affects the stack? + } + + /** + * Attempt to figure the stack depth change for a given function + * + * @param function the function whose depth change to compute + * @param warnings a place to emit warnings + * @return the depth change, i.e., change to SP + */ + public static int computeStackChange(Function function, Set warnings) { + // TODO: How does this work for varargs functions with stack parameters? + // Seems not much heed is given to signature or call site + // Analyzers set stackPurgeSize, but still, that's on the function, not the site. + // NOTE: It seems stdcall doesn't support varargs, so this issue should not arise. + PrototypeModel convention = function.getCallingConvention(); + if (convention == null) { + if (warnings != null) { + warnings.add(new UnspecifiedConventionStackUnwindWarning(function)); + } + convention = function.getProgram().getCompilerSpec().getDefaultCallingConvention(); + } + int extrapop = convention.getExtrapop(); + if (extrapop == PrototypeModel.UNKNOWN_EXTRAPOP) { + throw new PcodeExecutionException( + "Cannot get stack change for function " + function); + } + if (function.isStackPurgeSizeValid()) { + return extrapop + function.getStackPurgeSize(); + } + if (warnings != null) { + warnings.add(new UnknownPurgeStackUnwindWarning(function)); + } + return extrapop; + } + + /** + * Attempt to figure the stack depth change for a given function + * + * @param callee the function being called + * @return the depth change, i.e., change to SP + */ + public int computeStackChange(Function callee) { + return computeStackChange(callee, warnings); + } + + @Override + public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary library) { + Address target = op.getInput(0).getAddress(); + Function callee = program.getFunctionManager().getFunctionAt(target); + if (callee == null) { + throw new PcodeExecutionException("Callee at " + target + " is not a function.", frame); + } + String fixupName = callee.getCallFixup(); + if (fixupName != null && !"".equals(fixupName)) { + PcodeProgram snippet = + PcodeProgram.fromInject(program, fixupName, InjectPayload.CALLFIXUP_TYPE); + execute(snippet, library); + return; + } + int change = computeStackChange(callee); + adjustStack(change); + } + + /** + * Decompile the given low p-code op to its high p-code op + * + *

    + * Note this is not decompilation of the op in isolation. Decompilation usually requires a + * complete function for context. This will decompile the full containing function then examine + * the resulting high p-code ops at the same address as the given op, which are presumably those + * derived from it. It then seeks a unique call (or call indirect) op. + * + * @param op the low p-code op + * @return the high p-code op + */ + protected PcodeOpAST getHighCallOp(PcodeOp op) { + Address callSite = op.getSeqnum().getTarget(); + Function caller = program.getFunctionManager().getFunctionContaining(callSite); + + HighFunction hfunc = decompCache.computeIfAbsent(caller, c -> { + decomp.openProgram(program); + DecompileResults results = decomp.decompileFunction(c, 3, monitor); + return results.getHighFunction(); + }); + + List found = new ArrayList<>(); + Iterator oit = hfunc.getPcodeOps(callSite); + while (oit.hasNext()) { + PcodeOpAST hop = oit.next(); + if (hop.getOpcode() == PcodeOp.CALLIND || hop.getOpcode() == PcodeOp.CALL) { + found.add(hop); + } + } + if (found.size() == 1) { + return found.get(0); + } + if (found.size() > 1) { + warnings.add(new MultipleHighCallsStackUnwindWarning(found)); + return found.get(0); + } + warnings.add(new NoHighCallsStackUnwindWarning(op)); + return null; + } + + /** + * Derive the signature from the call op's target (first input) type + * + * @param op the call or call indirect op + * @return the signature if successful, or null + */ + protected FunctionSignature getSignatureFromTargetPointerType(PcodeOpAST op) { + VarnodeAST target = (VarnodeAST) op.getInput(0); + DataType dataType = target.getHigh().getDataType(); + if (!(dataType instanceof Pointer ptrType)) { + warnings.add(new UnexpectedTargetTypeStackUnwindWarning(dataType)); + return null; + } + if (!(ptrType.getDataType() instanceof FunctionSignature sigType)) { + warnings.add(new UnexpectedTargetTypeStackUnwindWarning(dataType)); + return null; + } + return sigType; + } + + /** + * Derive the signature from the call op's parameters (second and on inputs) types + * + * @param op the call or call indirect op + * @return the signature if successful, or null + */ + protected FunctionSignature getSignatureFromContextAtCallSite(PcodeOpAST op) { + FunctionDefinitionDataType sig = new FunctionDefinitionDataType("__indirect"); + sig.setReturnType(op.getOutput().getHigh().getDataType()); + // input 0 is the target, so drop it. + int numInputs = op.getNumInputs(); + Parameter[] params = new Parameter[numInputs - 1]; + ParameterDefinition[] arguments = new ParameterDefinition[numInputs - 1]; + for (int i = 1; i < numInputs; i++) { + Varnode input = op.getInput(i); + HighVariable highVar = input.getHigh(); + try { + /** + * NOTE: Not specifying storage, since: 1) It's not germane to the function + * signature, and 2) It may require chasing use-def chains through uniques. + */ + params[i - 1] = new ParameterImpl("param_" + i, highVar.getDataType(), + /*new VariableStorage(program, input),*/ program); + } + catch (InvalidInputException e) { + throw new AssertionError(e); + } + arguments[i - 1] = new ParameterDefinitionImpl("param_" + i, + input.getHigh().getDataType(), "generated"); + } + sig.setArguments(arguments); + sig.setComment("generated"); + + // TODO: Does the decompiler communicate the inferred calling convention? + try { + PrototypeModel convention = program.getCompilerSpec().findBestCallingConvention(params); + sig.setGenericCallingConvention(convention.getGenericCallingConvention()); + } + catch (SleighException e) { + // Whatever, just leave sig at "unknown" + } + return sig; + } + + /** + * Derive the function signature for an indirect call + * + *

    + * This first examines the target's type. Failing that, it examines the parameter and return + * types at the call site. + * + * @param lowOp the low p-code op + * @return the signature if successful, or null + */ + protected FunctionSignature getSignatureOfIndirectCall(PcodeOp lowOp) { + PcodeOpAST callOp = getHighCallOp(lowOp); + if (callOp == null) { + return null; + } + FunctionSignature signature = getSignatureFromTargetPointerType(callOp); + if (signature != null) { + return signature; + } + signature = getSignatureFromContextAtCallSite(callOp); + if (signature != null) { + return signature; + } + warnings.add(new CouldNotRecoverSignatureStackUnwindWarning(callOp)); + return null; + } + + /** + * Assuming the convention represents {@code __stdcall} determine the stack depth change for the + * given signature + * + * @param convention the convention, which must represent {@code __stdcall} + * @param sig the signature + * @return the depth + */ + protected int computeStdcallExtrapop(PrototypeModel convention, FunctionSignature sig) { + ParameterDefinition[] arguments = sig.getArguments(); + DataType[] types = new DataType[arguments.length + 1]; + types[0] = sig.getReturnType(); + for (int i = 0; i < arguments.length; i++) { + types[i + 1] = arguments[0].getDataType(); + } + VariableStorage[] vsLocs = convention.getStorageLocations(program, types, false); + Address min = null; + Address max = null; // Exclusive + for (VariableStorage vs : vsLocs) { + if (vs == null) { + continue; + } + for (Varnode vn : vs.getVarnodes()) { + if (!vn.getAddress().isStackAddress()) { + continue; + } + Address vnMin = vn.getAddress(); + Address vnMax = vnMin.add(vn.getSize()); + min = min == null || vnMin.compareTo(min) < 0 ? vnMin : min; + max = max == null || vnMax.compareTo(max) > 0 ? vnMax : max; + } + } + int purge = max == null ? 0 : (int) max.subtract(min); + // AFAIK, this stdcall only applies to x86, so presume return address on stack + return purge + program.getLanguage().getProgramCounter().getNumBytes(); + } + + /** + * Compute the stack change for an indirect call + * + * @param op the low p-code op + * @return the depth change + */ + protected int computeStackChangeIndirect(PcodeOp op) { + FunctionSignature sig = getSignatureOfIndirectCall(op); + if (sig == null) { + int extrapop = program.getCompilerSpec().getDefaultCallingConvention().getExtrapop(); + if (extrapop != PrototypeModel.UNKNOWN_EXTRAPOP) { + return extrapop; + } + throw new PcodeExecutionException("Cannot get stack change for indirect call: " + op); + } + PrototypeModel convention = + program.getCompilerSpec().matchConvention(sig.getGenericCallingConvention()); + if (convention == null) { + warnings.add(new UnspecifiedConventionStackUnwindWarning(null)); + convention = program.getCompilerSpec().getDefaultCallingConvention(); + } + int extrapop = convention.getExtrapop(); + if (extrapop != PrototypeModel.UNKNOWN_EXTRAPOP) { + return extrapop; + } + return computeStdcallExtrapop(convention, sig); + } + + /** + * Apply the given stack change to the machine state + * + *

    + * The overall effect is simply: {@code SP = SP + change} + * + * @param change the change + */ + protected void adjustStack(int change) { + Sym spVal = state.getVar(sp, reason); + int size = sp.getNumBytes(); + Sym spChanged = arithmetic.binaryOp(PcodeOp.INT_ADD, size, size, spVal, size, + arithmetic.fromConst(change, size)); + state.setVar(sp, spChanged); + } + + @Override + public void executeIndirectCall(PcodeOp op, PcodeFrame frame) { + int change = computeStackChangeIndirect(op); + assert change != PrototypeModel.UNKNOWN_EXTRAPOP; + adjustStack(change); + } + + @Override + public void executeConditionalBranch(PcodeOp op, PcodeFrame frame) { + // This should always end a basic block, so just do nothing + } + + @Override + protected void doExecuteIndirectBranch(PcodeOp op, PcodeFrame frame) { + // This should always end a basic block, so just do nothing + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutorState.java new file mode 100644 index 0000000000..589085dbb0 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutorState.java @@ -0,0 +1,282 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.HashMap; +import java.util.Map; + +import ghidra.app.plugin.core.debug.stack.Sym.*; +import ghidra.app.plugin.core.debug.stack.SymStateSpace.SymEntry; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemBuffer; +import ghidra.program.model.mem.MemoryBufferImpl; +import ghidra.util.Msg; + +/** + * A symbolic state for stack unwind analysis + * + *

    + * This state can store symbols in stack, register, and unique spaces. It ignores physical memory, + * since that is not typically used as temporary storage when moving values between registers and + * stack. When an address is read that does not have an entry, the state will generate a fresh + * symbol representing that address, if applicable. + */ +public class SymPcodeExecutorState implements PcodeExecutorState { + private final Program program; + private final CompilerSpec cSpec; + private final Language language; + private final SymPcodeArithmetic arithmetic; + + private final SymStateSpace stackSpace; + private final SymStateSpace registerSpace; + private final SymStateSpace uniqueSpace; + + /** + * Construct a new state for the given program + */ + public SymPcodeExecutorState(Program program) { + this.program = program; + this.cSpec = program.getCompilerSpec(); + this.language = cSpec.getLanguage(); + this.arithmetic = new SymPcodeArithmetic(cSpec); + this.stackSpace = new SymStateSpace(); + this.registerSpace = new SymStateSpace(); + this.uniqueSpace = new SymStateSpace(); + } + + protected SymPcodeExecutorState(Program program, SymPcodeArithmetic arithmetic, + SymStateSpace stackSpace, SymStateSpace registerSpace, SymStateSpace uniqueSpace) { + this.program = program; + this.cSpec = program.getCompilerSpec(); + this.language = cSpec.getLanguage(); + this.arithmetic = new SymPcodeArithmetic(cSpec); + this.stackSpace = stackSpace; + this.registerSpace = registerSpace; + this.uniqueSpace = uniqueSpace; + } + + @Override + public String toString() { + return String.format(""" + %s[ + cSpec=%s + stack=%s + registers=%s + unique=%s + ] + """, getClass().getSimpleName(), + cSpec.toString(), + stackSpace.toString(" ", language), + registerSpace.toString(" ", language), + uniqueSpace.toString(" ", language)); + } + + @Override + public Language getLanguage() { + return language; + } + + @Override + public PcodeArithmetic getArithmetic() { + return arithmetic; + } + + @Override + public void setVar(AddressSpace space, Sym offset, int size, boolean quantize, + Sym val) { + Address address = offset.addressIn(space, cSpec); + if (address.isRegisterAddress()) { + registerSpace.set(address, size, val); + } + else if (address.isUniqueAddress()) { + uniqueSpace.set(address, size, val); + } + else if (address.isConstantAddress()) { + throw new IllegalArgumentException(); + } + else if (address.isStackAddress()) { + stackSpace.set(address, size, val); + } + else { + Msg.trace(this, "Ignoring set: space=" + space + ",offset=" + offset + ",size=" + size + + ",val=" + val); + } + } + + @Override + public Sym getVar(AddressSpace space, Sym offset, int size, boolean quantize, + Reason reason) { + Address address = offset.addressIn(space, cSpec); + if (address.isRegisterAddress()) { + return registerSpace.get(address, size, arithmetic, language); + } + else if (address.isUniqueAddress()) { + return uniqueSpace.get(address, size, arithmetic, language); + } + else if (address.isConstantAddress()) { + return offset; + } + else if (address.isStackAddress()) { + return stackSpace.get(address, size, arithmetic, language); + } + return Sym.opaque(); + } + + @Override + public Map getRegisterValues() { + return Map.of(); + } + + @Override + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { + return new MemoryBufferImpl(program.getMemory(), address); + } + + @Override + public void clear() { + registerSpace.clear(); + stackSpace.clear(); + } + + @Override + public SymPcodeExecutorState fork() { + return new SymPcodeExecutorState(program, arithmetic, stackSpace.fork(), + registerSpace.fork(), uniqueSpace.fork()); + } + + /** + * Create a new state whose registers are forked from those of this state + */ + public SymPcodeExecutorState forkRegs() { + return new SymPcodeExecutorState(program, arithmetic, new SymStateSpace(), + registerSpace.fork(), new SymStateSpace()); + } + + public void dump() { + System.err.println("Registers: "); + registerSpace.dump(" ", language); + System.err.println("Unique: "); + uniqueSpace.dump(" ", language); + System.err.println("Stack: "); + stackSpace.dump(" ", language); + } + + /** + * Examine this state's SP for the overall change in stack depth + * + *

    + * There are two cases: + *

      + *
    • SP:Register(reg==SP) => depth is 0
    • + *
    • SP:Offset => depth is SP.offset
    • + *
    + * + *

    + * If SP has any other form, the depth is unknown + * + * @return the depth, or null if not known + */ + public Long computeStackDepth() { + Register sp = cSpec.getStackPointer(); + Sym expr = getVar(sp, Reason.INSPECT); + if (expr instanceof RegisterSym regVar && regVar.register() == sp) { + return 0L; + } + if (expr instanceof StackOffsetSym stackOff) { + return stackOff.offset(); + } + return null; + } + + /** + * Examine this state's PC for the location of the return address + * + *

    + * There are two cases: + *

      + *
    • PC:Register => location is PC.reg.address + *
    • PC:Deref => location is [Stack]:PC.offset + *
    + * + * @return + */ + public Address computeAddressOfReturn() { + Sym expr = getVar(language.getProgramCounter(), Reason.INSPECT); + if (expr instanceof StackDerefSym stackVar) { + return cSpec.getStackSpace().getAddress(stackVar.offset()); + } + if (expr instanceof RegisterSym regVar) { + return regVar.register().getAddress(); + } + return null; + } + + /** + * Compute a map of (saved) registers + * + *

    + * Any entry of the form (addr, v:Register) is collected as (v.register, addr). Note that the + * size of the stack entry is implied by the size of the register. + * + * @return the map from register to address + */ + public Map computeMapUsingStack() { + Map result = new HashMap<>(); + for (SymEntry ent : stackSpace.map.values()) { + if (ent.isTruncated()) { + continue; + } + if (!(ent.sym() instanceof RegisterSym regVar)) { + continue; + } + result.put(regVar.register(), ent.entRange().getMinAddress()); + } + return result; + } + + /** + * Compute the map of (restored) registers + * + *

    + * Any entry of the form (reg, v:Deref) is collected as (reg, [Stack]:v.offset). Note that the + * size of the stack entry is implied by the size of the register. + * + * @return + */ + public Map computeMapUsingRegisters() { + Map result = new HashMap<>(); + for (SymEntry ent : registerSpace.map.values()) { + if (ent.isTruncated()) { + continue; + } + if (!(ent.sym() instanceof StackDerefSym stackVar)) { + continue; + } + Register register = ent.getRegister(language); + if (register == null) { + continue; + } + result.put(register, cSpec.getStackSpace().getAddress(stackVar.offset())); + } + return result; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymStateSpace.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymStateSpace.java new file mode 100644 index 0000000000..475ba09c84 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymStateSpace.java @@ -0,0 +1,322 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import ghidra.app.plugin.core.debug.stack.Sym.RegisterSym; +import ghidra.app.plugin.core.debug.stack.Sym.StackDerefSym; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.trace.database.DBTraceUtils.AddressRangeMapSetter; +import ghidra.util.Msg; + +/** + * The portion of a {@link SymPcodeExecutorState} associated with a specific {@link AddressSpace}. + */ +public class SymStateSpace { + + /** + * A symbolic entry in the state + * + *

    + * It's possible the entry becomes truncated if another entry set later would overlap. Thus, it + * is necessary to remember the original range and the effective range as well as the symbol. + */ + record SymEntry(AddressRange entRange, AddressRange symRange, Sym sym) { + /** + * Create a new entry for the given range and symbol + * + * @param range the range + * @param sym the symbol + */ + SymEntry(AddressRange range, Sym sym) { + this(range, range, sym); + } + + /** + * Create a new entry for the given range and symbol + * + * @param start the min address of the range + * @param size the size in bytes of the range + * @param sym the symbol + * @throws AddressOverflowException + */ + SymEntry(Address start, int size, Sym sym) throws AddressOverflowException { + this(new AddressRangeImpl(start, size), sym); + } + + /** + * Render a human-friendly string, substituting register names for ranges where appropriate + * + * @param language optional language. If omitted, no register names are substituted + * @return the string + */ + public String toString(Language language) { + Register reg = getRegister(language); + if (reg == null) { + return toString(); + } + return String.format("%s[entRanage=%s,symRange=%s,sym=%s]", getClass().getSimpleName(), + reg, symRange, sym); + } + + /** + * Check if this entry has been truncated + * + * @return true if the effective range is equal to the original range + */ + boolean isTruncated() { + return !entRange.equals(symRange); + } + + /** + * Get the register in the language this entry's range represents + * + * @param language the language + * @return the register, or null + */ + Register getRegister(Language language) { + return language.getRegister(entRange.getMinAddress(), (int) entRange.getLength()); + } + + /** + * Create a new entry that represents a truncation of this entry + * + * @param range the subrange + * @return the new entry + */ + SymEntry truncate(AddressRange range) { + if (entRange.getMinAddress().compareTo(range.getMinAddress()) > 0) { + throw new AssertionError(); + } + if (entRange.getMaxAddress().compareTo(range.getMaxAddress()) < 0) { + throw new AssertionError(); + } + return new SymEntry(range, symRange, sym); + } + + /** + * Check if the effective range contains the given address + * + * @param address the address + * @return true if contained by this entry + */ + boolean contains(Address address) { + return entRange.contains(address); + } + + /** + * Get the symbol from this entry, applying appropriate arithmetic for truncation, if + * applicable. + * + * @param range the range to extract + * @param arithmetic the arithmetic for extracting the appropriate bytes + * @return the symbol + */ + Sym extract(AddressRange range, SymPcodeArithmetic arithmetic) { + if (symRange.equals(range)) { + return sym; + } + // TODO: Implement the extraction logic. Not sure it matters, anyway + return Sym.opaque(); + /* long shift = arithmetic.getEndian().isBigEndian() + ? symRange.getMaxAddress().subtract(range.getMaxAddress()) + : range.getMinAddress().subtract(symRange.getMinAddress()); */ + } + } + + /** + * A setter that knows how to remove or truncate overlapping entries + */ + protected class ExprMapSetter + extends AddressRangeMapSetter, SymEntry> { + @Override + protected AddressRange getRange(Entry entry) { + return entry.getValue().entRange; + } + + @Override + protected SymEntry getValue(Entry entry) { + return entry.getValue(); + } + + @Override + protected void remove(Entry entry) { + map.remove(entry.getKey()); + } + + @Override + protected Iterable> getIntersecting(Address lower, + Address upper) { + return subMap(lower, upper).entrySet(); + } + + @Override + protected Entry put(AddressRange range, SymEntry value) { + map.put(range.getMinAddress(), value.truncate(range)); + return null; + } + } + + final NavigableMap map; + private final ExprMapSetter setter = new ExprMapSetter(); + + /** + * Construct a new empty space + */ + public SymStateSpace() { + this.map = new TreeMap<>(); + } + + /** + * Construct a space with the given map (for forking) + * + * @param map the map + */ + protected SymStateSpace(NavigableMap map) { + this.map = map; + } + + @Override + public String toString() { + return toString("", null); + } + + /** + * Render a human-friendly string showing this state space + * + * @param indent the indentation + * @param language the language, optional, for register substitution + * @return the string + */ + public String toString(String indent, Language language) { + return map.values() + .stream() + .map(se -> se.toString(language)) + .collect(Collectors.joining("\n" + indent, indent + "{", "\n" + indent + "}")); + } + + private NavigableMap subMap(Address lower, Address upper) { + Entry adjEnt = map.floorEntry(lower); + if (adjEnt != null && adjEnt.getValue().contains(upper)) { + lower = adjEnt.getKey(); + } + return map.subMap(lower, true, upper, true); + } + + /** + * Set a value in this space + * + * @param address the address of the entry + * @param size the size of the entry + * @param sym the symbol + */ + public void set(Address address, int size, Sym sym) { + SymEntry entry; + try { + entry = new SymEntry(address, size, sym); + } + catch (AddressOverflowException e) { + throw new AssertionError(e); + } + setter.set(entry.entRange, entry); + } + + /** + * Get a value from this space + * + * @param address the address of the value + * @param size the size of the value + * @param arithmetic the arithmetic, in case truncation is necessary + * @param language the language, for generating symbols + * @return the symbol + */ + public Sym get(Address address, int size, SymPcodeArithmetic arithmetic, Language language) { + AddressRange range; + range = new AddressRangeImpl(address, address.add(size - 1)); + Sym result = null; + Address expectedNext = null; + for (SymEntry ent : subMap(range.getMinAddress(), range.getMaxAddress()).values()) { + if (ent.entRange.equals(range)) { + return ent.extract(range, arithmetic); + } + AddressRange intersection = range.intersect(ent.entRange); + if (expectedNext != null && !expectedNext.equals(intersection.getMinAddress())) { + return Sym.opaque(); + } + expectedNext = intersection.getMaxAddress().next(); + Sym piece = ent.extract(intersection, arithmetic); + piece = + arithmetic.unaryOp(PcodeOp.INT_ZEXT, size, (int) intersection.getLength(), piece); + if (result == null) { + result = piece; + continue; + } + result = arithmetic.binaryOp(PcodeOp.INT_OR, size, size, piece, size, result); + } + if (result != null) { + return result; + } + if (address.isRegisterAddress()) { + Register register = language.getRegister(address, size); + if (register == null) { + Msg.warn(this, "Could not figure register: address=" + address + ",size=" + size); + return Sym.opaque(); + } + return new RegisterSym(register); + } + if (address.isStackAddress()) { + return new StackDerefSym(address.getOffset(), size); + } + return Sym.opaque(); + } + + /** + * Reset this state + * + *

    + * Clears the state as if it were new. That is, it will generate fresh symbols for reads without + * existing entries. + */ + public void clear() { + map.clear(); + } + + public void dump(String prefix, Language language) { + for (SymEntry ent : map.values()) { + Register register = ent.getRegister(language); + if (register != null) { + System.err.println(prefix + register + " = " + ent.sym); + continue; + } + System.err.println(prefix + ent); + } + } + + /** + * Copy this state + * + * @return the new state + */ + public SymStateSpace fork() { + return new SymStateSpace(new TreeMap<>(map)); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java new file mode 100644 index 0000000000..b9763d2e2d --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java @@ -0,0 +1,516 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.*; +import java.util.stream.Collectors; + +import generic.Unique; +import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.NoReturnPathStackUnwindWarning; +import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.OpaqueReturnPathStackUnwindWarning; +import ghidra.graph.*; +import ghidra.graph.algo.DijkstraShortestPathsAlgorithm; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.PcodeProgram; +import ghidra.pcode.exec.PcodeUseropLibrary; +import ghidra.program.model.address.*; +import ghidra.program.model.block.*; +import ghidra.program.model.lang.PrototypeModel; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.FlowType; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A class for analyzing a given program's functions as a means of unwinding their stack frames in + * traces, possibly for live debug sessions. + * + * @see StackUnwinder + */ +public class UnwindAnalysis { + + /** + * A graph used for finding execution paths from function entry through the program counter to a + * return. + * + *

    + * This just wraps {@link UnwindAnalysis#blockModel} in a {@link GImplicitDirectedGraph}. + */ + class BlockGraph implements GImplicitDirectedGraph { + final TaskMonitor monitor; + + public BlockGraph(TaskMonitor monitor) { + this.monitor = monitor; + } + + List toEdgeList(CodeBlockReferenceIterator it) throws CancelledException { + List result = new ArrayList<>(); + while (it.hasNext()) { + CodeBlockReference ref = it.next(); + if (ref.getFlowType().isCall()) { + continue; + } + result.add(new BlockEdge(ref)); + } + return result; + } + + @Override + public Collection getInEdges(BlockVertex v) { + try { + return toEdgeList(blockModel.getSources(v.block, monitor)); + } + catch (CancelledException e) { + throw new AssertionError(e); + } + } + + @Override + public Collection getOutEdges(BlockVertex v) { + try { + return toEdgeList(blockModel.getDestinations(v.block, monitor)); + } + catch (CancelledException e) { + throw new AssertionError(e); + } + } + + @Override + public GDirectedGraph copy() { + throw new UnsupportedOperationException(); + } + } + + /** + * Wrap a {@link CodeBlock} + */ + record BlockVertex(CodeBlock block) { + } + + /** + * Wrap a {@link CodeBlockReference} + */ + record BlockEdge(CodeBlockReference ref) + implements GEdge { + @Override + public BlockVertex getStart() { + return new BlockVertex(ref.getSourceBlock()); + } + + @Override + public BlockVertex getEnd() { + return new BlockVertex(ref.getDestinationBlock()); + } + } + + private final Program program; + private final CodeBlockModel blockModel; + + /** + * Prepare analysis on the given program + * + * @param program the program + */ + public UnwindAnalysis(Program program) { + this.program = program; + // PartitionCodeSubModel seems to call each subroutine a block + this.blockModel = new BasicBlockModel(program); + } + + /** + * The analysis surrounding a single frame for a given program counter, i.e., instruction + * address + */ + class AnalysisForPC { + private final Address pc; + private final TaskMonitor monitor; + private final Function function; + private final BlockGraph graph; + private final BlockVertex pcBlock; + private final DijkstraShortestPathsAlgorithm pathFinder; + private final Set warnings = new HashSet<>(); + + /** + * Begin analysis for unwinding a frame, knowing only the program counter for that frame + * + *

    + * This will look up the function containing the program counter. If there's isn't one, then + * this analysis cannot proceed. + * + * @param pc the program counter + * @param monitor a monitor for progress and cancellation + * @throws CancelledException if the monitor cancels the analysis + */ + public AnalysisForPC(Address pc, TaskMonitor monitor) throws CancelledException { + this.pc = pc; + this.function = program.getFunctionManager().getFunctionContaining(pc); + if (function == null) { + throw new UnwindException("No function contains " + pc); + } + this.monitor = monitor; + this.graph = new BlockGraph(monitor); + this.pathFinder = + new DijkstraShortestPathsAlgorithm<>(graph, GEdgeWeightMetric.unitMetric()); + this.pcBlock = new BlockVertex( + Unique.assertAtMostOne(blockModel.getCodeBlocksContaining(pc, monitor))); + } + + /** + * Compute the shortest path(s) from function entry to the program counter + * + * @return the paths. There's usually only one + * @throws CancelledException if the monitor cancels the analysis + */ + public Collection> getEntryPaths() throws CancelledException { + BlockVertex entryBlock = new BlockVertex(Unique.assertAtMostOne( + blockModel.getCodeBlocksContaining(function.getEntryPoint(), monitor))); + return pathFinder.computeOptimalPaths(entryBlock, pcBlock); + } + + /** + * Find terminating blocks that return from the function + * + *

    + * If there are none, then the function is presumed non-returning. Analysis will not be + * complete. + * + *

    + * For non-returning functions, we can still use the entry path. From limited + * experimentation, it seems the extra saved-register entries are not problematic. One case + * is register parameters that the function saves to the stack for its own sake. While + * restoring those would technically be incorrect, it doesn't seem problematic to do so. + * This doesn't help us compute {@link UnwindInfo#adjust}, but that might just be + * {@link PrototypeModel#getExtrapop()}.... + * + * @return the blocks + * @throws CancelledException if the monitor cancels the analysis + */ + public Collection getReturnBlocks() + throws CancelledException { + // TODO: What to do if function is non-returning? + List returns = new ArrayList<>(); + for (CodeBlock funcBlock : blockModel.getCodeBlocksContaining(function.getBody(), + monitor)) { + FlowType flowType = funcBlock.getFlowType(); + // Omit CALL_TERMINATORs, since those are calls to non-returning functions + // TODO: This also omits tail calls by JMP + if (flowType.isTerminal() && !flowType.isCall()) { + returns.add(new BlockVertex(funcBlock)); + } + } + return returns; + } + + /** + * Compute the shortest path(s) from the program counter to a function return + * + *

    + * Because the shortest-path API does not readily permit the searching for the shortest path + * from one vertex to many vertices, we instead search for the shortest path from the + * program counter to each of the found function returns, collect all the resulting paths, + * and sort. Still, usually only the first (shortest of all) is needed. + * + * @return the paths sorted shortest first + * @throws CancelledException if the monitor cancels the analysis + */ + public Collection> getExitsPaths() throws CancelledException { + return getReturnBlocks().stream() + .flatMap(rb -> pathFinder.computeOptimalPaths(pcBlock, rb).stream()) + .sorted(Comparator.comparing(d -> d.size())) + .collect(Collectors.toList()); + } + + /** + * Execute the instructions, ordered by address, in the given address set + * + * @param exec the executor + * @param set the address set indicating the instructions to execute + * @throws CancelledException if the monitor cancels the analysis + */ + public void executeSet(SymPcodeExecutor exec, AddressSetView set) + throws CancelledException { + for (Instruction i : program.getListing().getInstructions(set, true)) { + monitor.checkCanceled(); + exec.execute(PcodeProgram.fromInstruction(i, true), PcodeUseropLibrary.nil()); + } + } + + /** + * Execute the instructions in the given block preceding the given address + * + *

    + * The instruction at {@code to} is omitted. + * + * @param exec the executor + * @param block the block whose instructions to execute + * @param to the ending address, usually the program counter + * @throws CancelledException if the monitor cancels the analysis + */ + public void executeBlockTo(SymPcodeExecutor exec, CodeBlock block, Address to) + throws CancelledException { + AddressSet set = + block.intersectRange(to.getAddressSpace().getMinAddress(), to.previous()); + executeSet(exec, set); + } + + /** + * Execute the instructions in the given block + * + * @param exec the executor + * @param block the block whose instructions to execute + * @throws CancelledException if the monitor cancels the analysis + */ + public void executeBlock(SymPcodeExecutor exec, CodeBlock block) + throws CancelledException { + executeSet(exec, block); + } + + /** + * Execute the instructions in the given block starting at the given address + * + *

    + * Instructions preceding the given address are omitted. + * + * @param exec the executor + * @param block the block whose instructions to execute + * @param from the starting address, usually the program counter + * @throws CancelledException if the monitor cancels the analysis + */ + public void executeBlockFrom(SymPcodeExecutor exec, CodeBlock block, Address from) + throws CancelledException { + AddressSet set = block.intersectRange(from, from.getAddressSpace().getMaxAddress()); + executeSet(exec, set); + } + + /** + * Execute the instructions along the given path to a destination block, omitting the final + * destination block. + * + *

    + * The given path is usually from the function entry to the block containing the program + * counter. The final block is omitted, since it should only be partially executed, i.e., + * using {@link #executeBlockTo(SymPcodeExecutor, CodeBlock, Address)}. + * + * @param exec the executor + * @param to the path to the program counter + * @see #executeToPc(Deque) + * @throws CancelledException if the monitor cancels the analysis + */ + public void executePathTo(SymPcodeExecutor exec, Deque to) + throws CancelledException { + for (BlockEdge et : to) { + executeBlock(exec, et.ref.getSourceBlock()); + } + } + + /** + * Execute the instructions along the given path from a source block, omitting the initial + * source block. + * + *

    + * The given path us usually from the block containing the program counter to a function + * return. The initial source is omitted, since it should only be partially executed, i.e., + * using {@link #executeBlockFrom(SymPcodeExecutor, CodeBlock, Address)}. + * + * @param exec the executor + * @param from the path from the program counter + * @see #executeFromPc(SymPcodeExecutorState, Deque) + * @throws CancelledException if the monitor cancels the analysis + */ + public void executePathFrom(SymPcodeExecutor exec, Deque from) + throws CancelledException { + for (BlockEdge ef : from) { + executeBlock(exec, ef.ref.getDestinationBlock()); + } + } + + /** + * Execute the instructions from entry to the program counter, using the given path + * + *

    + * This constructs a new symbolic state for stack analysis, performs the execution, and + * returns the state. The state can then be analyzed before finishing execution to a + * function return and analyzing it again. + * + * @param to the path from entry to the program counter + * @return the resulting state + * @throws CancelledException if the monitor cancels the analysis + */ + public SymPcodeExecutorState executeToPc(Deque to) throws CancelledException { + SymPcodeExecutorState state = new SymPcodeExecutorState(program); + SymPcodeExecutor exec = + SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE, warnings, monitor); + executePathTo(exec, to); + executeBlockTo(exec, pcBlock.block, pc); + return state; + } + + /** + * Finish execution from the program counter to a function return, using the given path + * + *

    + * This returns the same (but mutated) state as passed to it. The state should be forked + * from the result of {@link #executeToPc(Deque)}, but resetting the stack portion. + * + * @param state the state, whose registers are forked from the result of + * {@link #executeToPc(Deque)}. + * @param from the path from the program counter to a return + * @return the resulting state + * @throws CancelledException if the monitor cancels the analysis + */ + public SymPcodeExecutorState executeFromPc(SymPcodeExecutorState state, + Deque from) throws CancelledException { + SymPcodeExecutor exec = + SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE, warnings, monitor); + executeBlockFrom(exec, pcBlock.block, pc); + executePathFrom(exec, from); + return state; + } + + /** + * Compute the unwinding information for a frame presumably produced by executing the + * current function up to but excluding the program counter + * + *

    + * The goal is to compute a base pointer for the current frame so that the values of stack + * variables can be retrieved from a dynamic trace, as well as enough information to unwind + * and achieve the same for the next frame up on the stack. That is, the frame of the + * function that called the current function. We'll also need to figure out what registers + * were saved where on the stack so that the values of register variables can be retrieved + * from a dynamic trace. For architectures with a link register, register restoration is + * necessary to unwind the next frame, since that register holds its program counter. + * Ideally, this unwinding can be applied iteratively, until we reach the process' entry + * point. + * + *

    + * The analytic strategy is fairly straightforward and generalized, though not universally + * applicable. It employs a somewhat rudimentary symbolic interpretation. A symbol can be a + * constant, a register's initial value at function entry, a stack offset relative to the + * stack pointer at function entry, a dereferenced stack offset, or an opaque value. See + * {@link Sym}. + * + *

      + *
    1. Interpret the instructions along the shortest path from function entry to the program + * counter.
    2. + *
    3. Examine the symbol in the stack pointer register. It should be a stack offset. That + * offset is the "stack depth." See {@link UnwindInfo#depth()}, + * {@link UnwindInfo#computeBase(Address)}, and + * {@link SymPcodeExecutorState#computeStackDepth()}.
    4. + *
    5. Search the stack for register symbols, creating an offset-register map. A subset of + * these are the saved registers on the stack. See {@link UnwindInfo#saved} and + * {@link SymPcodeExecutorState#computeMapUsingStack()}.
    6. + *
    7. Reset the stack state. (This implies stack dereferences from further interpretation + * refer to their values at the program counter rather than function entry.) See + * {@link SymPcodeExecutorState#forkRegs()}.
    8. + *
    9. Interpret the instructions along the shortest path from the program counter to a + * function return.
    10. + *
    11. Examine the symbol in the program counter register. This gives the location (register + * or stack offset) of the return address. This strategy should work whether or not a link + * register is involved. See {@link SymPcodeExecutorState#computeAddressOfReturn()}. + *
    12. Examine the symbol in the stack pointer register, again. It should be a stack offset. + * That offset is the "stack adjustment." See {@link UnwindInfo#adjust()}, + * {@link UnwindInfo#computeNextSp(Address)}, and + * {@link SymPcodeExecutorState#computeStackDepth()}. + *
    13. Search the registers for stack dereference symbols, creating an offset-register map. + * This intersected with the same from entry to program counter is the saved registers map. + * See {@link UnwindInfo#saved()}, + * {@link UnwindInfo#mapSavedRegisters(Address, SavedRegisterMap)}, and + * {@link SymPcodeExecutorState#computeMapUsingRegisters()}. + *
    + * + *

    + * This strategy does make some assumptions: + *

      + *
    • The function returns.
    • + *
    • For every edge in the basic block graph, the stack depth at the end of its source + * block is equal to the stack depth at the start of its destination block.
    • + *
    • The function follows a "sane" convention. While it doesn't have to be any particular + * convention, it does need to restore its saved registers, and those registers should be + * saved to the stack in a straightforward manner.
    • + *
    + * + * @return the unwind information + * @throws CancelledException if the monitor cancels the analysis + */ + public UnwindInfo computeUnwindInfo() throws CancelledException { + // TODO: Find out to what other pc values this applies and cache? + Collection> entryPaths = getEntryPaths(); + if (entryPaths.isEmpty()) { + throw new UnwindException( + "Could not find a path from " + function + " entry to " + pc); + } + Collection> exitsPaths = getExitsPaths(); + // TODO: Proper exceptions for useless results + for (Deque entryPath : entryPaths) { + SymPcodeExecutorState entryState = executeToPc(entryPath); + Long depth = entryState.computeStackDepth(); + if (depth == null) { + continue; + } + if (exitsPaths.isEmpty()) { + warnings.add(new NoReturnPathStackUnwindWarning(pc)); + } + Map mapByEntry = entryState.computeMapUsingStack(); + for (Deque exitPath : exitsPaths) { + SymPcodeExecutorState exitState = + executeFromPc(entryState.forkRegs(), exitPath); + Address addressOfReturn = exitState.computeAddressOfReturn(); + Long adjust = exitState.computeStackDepth(); + if (addressOfReturn == null || adjust == null) { + continue; + } + Map mapByExit = exitState.computeMapUsingRegisters(); + mapByExit.entrySet().retainAll(mapByEntry.entrySet()); + return new UnwindInfo(function, depth, adjust, addressOfReturn, mapByExit, + new StackUnwindWarningSet(warnings)); + } + warnings.add(new OpaqueReturnPathStackUnwindWarning(pc)); + long adjust = SymPcodeExecutor.computeStackChange(function, warnings); + return new UnwindInfo(function, depth, adjust, null, mapByEntry, + new StackUnwindWarningSet(warnings)); + } + throw new UnwindException( + "Could not analyze any path from " + function + " entry to " + pc); + } + } + + /** + * Start analysis at the given program counter + * + * @param pc the program counter + * @param monitor a monitor for all the analysis that follows + * @return the pc-specific analyzer + * @throws CancelledException if the monitor cancels the analysis + */ + AnalysisForPC start(Address pc, TaskMonitor monitor) throws CancelledException { + return new AnalysisForPC(pc, monitor); + } + + /** + * Compute the unwind information for the given program counter + * + * @param pc the program counter + * @param monitor a monitor for progress and cancellation + * @return the unwind information + * @throws CancelledException if the monitor cancels the analysis + */ + public UnwindInfo computeUnwindInfo(Address pc, TaskMonitor monitor) + throws CancelledException { + AnalysisForPC analysis = start(pc, monitor); + return analysis.computeUnwindInfo(); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowExpansionListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindException.java similarity index 64% rename from Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowExpansionListener.java rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindException.java index 9681346212..8f77ad232c 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowExpansionListener.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindException.java @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.widgets; +package ghidra.app.plugin.core.debug.stack; -public interface ExpanderArrowExpansionListener { - /** - * @throws ExpanderArrowExpansionVetoException - */ - default void changing(boolean expanding) throws ExpanderArrowExpansionVetoException { - // Nothing +/** + * An exception to indicate failed or incomplete stack uwinding + */ +public class UnwindException extends RuntimeException { + public UnwindException(String message) { + super(message); } - void changed(boolean expanded); + public UnwindException(String message, UnwindException cause) { + super(message, cause); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindInfo.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindInfo.java new file mode 100644 index 0000000000..64f59ddca5 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindInfo.java @@ -0,0 +1,250 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.util.Map; +import java.util.Map.Entry; + +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Variable; +import ghidra.trace.util.TraceRegisterUtils; +import ghidra.util.task.TaskMonitor; + +/** + * Information for interpreting the current stack frame and unwinding to the next + */ +public record UnwindInfo(Function function, long depth, long adjust, Address ofReturn, + Map saved, StackUnwindWarningSet warnings) { + + /** + * The function that was analyzed + * + * @return the function + */ + public Function function() { + return function; + } + + /** + * The change in the stack pointer from function entry to the given program counter + * + *

    + * This is necessary to retrieve stack variables from the current frame. By subtracting this + * from the current stack pointer, the frame's base address is computed. See + * {@link #computeBase(Address)}. The offsets of stack variables are all relative to that base + * address. See {@link AnalysisUnwoundFrame#getValue(Variable)}. + * + * @return the depth + */ + public long depth() { + return depth; + } + + /** + * The adjustment to the stack pointer, at function entry, to return from this function + * + *

    + * This is used to unwind the stack pointer value for the next frame. + * + * @return the adjustment + */ + public long adjust() { + return adjust; + } + + /** + * The address of the return address + * + *

    + * The address may be a register or a stack offset, relative to the stack pointer at function + * entry. + * + * @return the address of the return address + */ + public Address ofReturn() { + return ofReturn; + } + + /** + * The address of the return address, given a stack base + * + *

    + * The address may be a register or a stack offset, relative to the stack pointer at function + * entry, i.e., base. If it's the latter, then this will resolve it with respect to the given + * base. The result can be used to retrieve the return address from a state. See + * {@link #computeNextPc(Address, PcodeExecutorState, Register)}. + * + * @param base the stack pointer at function entry + * @return the address of the return address + */ + public Address ofReturn(Address base) { + if (ofReturn.isRegisterAddress()) { + return ofReturn; + } + else if (ofReturn.isStackAddress()) { + return base.add(ofReturn.getOffset()); + } + throw new AssertionError(); + } + + /** + * The map of registers to stack offsets for saved registers + * + *

    + * This is not necessary until its time to unwind the next frame. The saved registers should be + * restored, then the next PC and SP computed, then the next frame unwound. See + * {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)}. + * + * @return the map of registers to stack addresses + */ + public Map saved() { + return saved; + } + + /** + * The list of warnings issues during analysis + * + * @return the warnings + */ + public StackUnwindWarningSet warnings() { + return warnings; + } + + /** + * Compute the current frame's base address given the current (or unwound) stack pointer. + * + *

    + * This is used to retrieve variable values for the current frame. + * + * @param spVal the stack pointer + * @return the base address + */ + public Address computeBase(Address spVal) { + return spVal.subtract(depth); + } + + /** + * Restore saved registers in the given state + * + *

    + * This is used as part of unwinding the next frame. + * + * @param the type of values in the state + * @param base the current frame's base pointer, as in {@link #computeBase(Address)}. + * @param state the state to modify, usually forked from the current frame's state + * @see AnalysisUnwoundFrame#unwindNext(TaskMonitor) + */ + public void restoreRegisters(Address base, PcodeExecutorState state) { + for (Entry ent : saved.entrySet()) { + Register reg = ent.getKey(); + Address offset = ent.getValue(); + assert offset.isStackAddress(); + Address address = base.add(offset.getOffset()); + T value = state.getVar(address, reg.getNumBytes(), true, Reason.INSPECT); + state.setVar(reg, value); + } + } + + /** + * Add register map entries for the saved registers in this frame + * + * @param base the current frame's base pointer, as in {@link #computeBase(Address)} + * @param registerMap the register map of the stack to this point, to be modified + */ + public void mapSavedRegisters(Address base, SavedRegisterMap map) { + for (Entry ent : saved.entrySet()) { + Register reg = ent.getKey(); + Address offset = ent.getValue(); + assert offset.isStackAddress(); + Address address = base.add(offset.getOffset()); + map.put(TraceRegisterUtils.rangeForRegister(reg), address); + } + } + + /** + * Compute the return address of the current frame, giving the unwound program counter of the + * next frame + * + *

    + * This is used as part of unwinding the next frame. + * + * @param the type of values in the state + * @param base the current frame's base pointer, as in {@link #computeBase(Address)} + * @param state the state of the next frame, whose program counter this method is computing + * @param pc the program counter register, used for its size + * @return the value of the program counter for the next frame + * @see AnalysisUnwoundFrame#unwindNext(TaskMonitor) + */ + public T computeNextPc(Address base, PcodeExecutorState state, Register pc) { + return state.getVar(ofReturn(base), pc.getNumBytes(), true, Reason.INSPECT); + } + + /** + * Compute the return address of the current frame, giving the unwound program counter (as a + * code address) of the next frame. + * + *

    + * This is used as part of unwinding the next frame. + * + * @param the type of values in the state + * @param base the current frame's base pointer, as in {@link #computeBase(Address)} + * @param state the state of the next frame, whose program counter this method is computing + * @param codeSpace the address space where the program counter points + * @param pc the program counter register, used for its size + * @return the address of the next instruction for the next frame + * @see AnalysisUnwoundFrame#unwindNext(TaskMonitor) + */ + public Address computeNextPc(Address base, PcodeExecutorState state, + AddressSpace codeSpace, Register pc) { + T value = computeNextPc(base, state, pc); + long concrete = state.getArithmetic().toLong(value, Purpose.INSPECT); + return codeSpace.getAddress(concrete); + } + + /** + * Compute the unwound stack pointer for the next frame + * + *

    + * This is used as part of unwinding the next frame. + * + * @param base the current frame's based pointer, as in {@link #computeBase(Address)} + * @return the stack pointer for the next frame + * @see AnalysisUnwoundFrame#unwindNext(TaskMonitor) + */ + public Address computeNextSp(Address base) { + return base.add(adjust); + } + + /** + * Get the number of bytes in the parameter portion of the frame + * + *

    + * These are the entries on the opposite side of the base pointer from the rest of the frame. In + * fact, these are pushed onto the stack by the caller, so these slots should be "stolen" from + * the caller's frame and given to the callee's frame. + * + * @return the total parameter size in bytes + */ + public int computeParamSize() { + return function.getStackFrame().getParameterSize(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindStackCommand.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindStackCommand.java new file mode 100644 index 0000000000..73ad2c2071 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindStackCommand.java @@ -0,0 +1,63 @@ +/* ### + * 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.plugin.core.debug.stack; + +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.framework.cmd.TypedBackgroundCommand; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue; +import ghidra.trace.model.Trace; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A command to unwind as much of the stack as possible and annotate the resulting frame in the + * dynamic listing + */ +public class UnwindStackCommand extends TypedBackgroundCommand { + + private final PluginTool tool; + private final DebuggerCoordinates where; + + public UnwindStackCommand(PluginTool tool, DebuggerCoordinates where) { + super("Unwind Stack", false, true, false); + this.tool = tool; + this.where = where; + } + + @Override + public boolean applyToTyped(Trace obj, TaskMonitor monitor) { + try { + StackUnwinder unwinder = new StackUnwinder(tool, where.getPlatform()); + int prevParamSize = 0; + for (AnalysisUnwoundFrame frame : unwinder.frames(where.frame(0), + monitor)) { + UnwindInfo info = frame.getUnwindInfo(); + if (info != null) { + frame.applyToListing(prevParamSize, monitor); + prevParamSize = info.computeParamSize(); + } + else { + tool.setStatusInfo(frame.getError().getMessage()); + } + } + return true; + } + catch (CancelledException e) { + return true; + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwoundFrame.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwoundFrame.java new file mode 100644 index 0000000000..fe508f5183 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwoundFrame.java @@ -0,0 +1,286 @@ +/* ### + * 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.plugin.core.debug.stack; + +import java.math.BigInteger; +import java.util.concurrent.CompletableFuture; + +import ghidra.app.decompiler.ClangLine; +import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueUtils; +import ghidra.app.services.DebuggerStateEditingService.StateEditor; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.PcodeOp; + +/** + * A frame that has been unwound through analysis or annotated in the listing + * + *

    + * An unwound frame can be obtained via {@link StackUnwinder} or {@link ListingUnwoundFrame}. The + * former is used when stack unwind analysis has not yet been applied to the current trace snapshot. + * It actually returns a {@link AnalysisUnwoundFrame}, which can apply the resulting analysis to the + * snapshot. The latter is used when those annotations are already present. + * + * @param the type of values retrievable from the unwound frame + */ +public interface UnwoundFrame { + /** + * Check if this is an actual frame + * + * @see FakeUnwoundFrame + * @return true if fake + */ + boolean isFake(); + + /** + * Get the level of this frame, 0 being the innermost + * + * @return the level + */ + int getLevel(); + + /** + * Get a description of this frame, for display purposes + * + * @return the description + */ + String getDescription(); + + /** + * Get the frame's program counter + * + *

    + * If this is the innermost frame, this is the next instruction to be executed. Otherwise, this + * is the return address of the next inner frame, i.e., the instruction to be executed after + * control is returned to the function that allocated this frame. + * + * @return the frame's program counter + */ + Address getProgramCounter(); + + /** + * Get the function that allocated this frame + * + *

    + * This is the function whose body contains the program counter + * + * @return the frame's allocating function + */ + Function getFunction(); + + /** + * Get the base pointer for this frame + * + *

    + * This is the value of the stack pointer at entry of the allocating function. Note while + * related, this is a separate thing from the "base pointer" register. Not all architectures + * offer one, and even on those that do, not all functions use it. Furthermore, a function that + * does use it may place a different value in the than we define as the base pointer. The value + * here is that recovered from an examination of stack operations from the function's entry to + * the program counter. It is designed such that varnodes with stack offsets can be located in + * this frame by adding the offset to this base pointer. + * + * @return the frame's base pointer + */ + Address getBasePointer(); + + /** + * Get the frame's return address + * + *

    + * The address of the return address is determined by an examination of stack and register + * operations from the program counter to a return of the function allocating this frame. Three + * cases are known: + *

      + *
    1. The return address is on the stack. This happens for architectures where the caller must + * push the return address to the stack. It can also happen on architectures with a link + * register if the callee saves that register to the stack.
    2. + *
    3. The return address is in a register. This happens for architectures with a link register + * assuming the callee has not saved that register to the stack.
    4. + *
    5. The return address cannot be recovered. This happens when the function appears to be non + * returning, or the analysis otherwise fails to recover the return address. In this case, this + * method will throw an exception. + *
    + * + * @return the return address + */ + Address getReturnAddress(); + + /** + * Get the warnings generated during analysis + * + *

    + * Several warnings may be returned, each on its own line. + * + * @return the warnings + */ + String getWarnings(); + + /** + * Get the value of the storage from the frame + * + *

    + * Each varnode in the storage is retrieved and concatenated together. The lower-indexed + * varnodes have higher significance -- like big endian. A varnode is retrieved from the state, + * with register accesses potentially redirected to a location where its value has been saved to + * the stack. + * + *

    + * Each varnode's value is simply retrieved from the state, in contrast to + * {@link #evaluate(VariableStorage, AddressSetView)}, which ascends to varnodes' defining + * p-code ops. + * + *

    + * WARNING: Never invoke this method from the Swing thread. The state could be associated + * with a live session, and this may block to retrieve live state. + * + * @param storage the storage + * @return the value + */ + T getValue(VariableStorage storage); + + /** + * Get the value of the variable from the frame + * + *

    + * WARNING: Never invoke this method from the Swing thread. The state could be associated + * with a live session, and this may block to retrieve live state. + * + * @see #getValue(VariableStorage) + * @param variable the variable + * @return the value + */ + default T getValue(Variable variable) { + return getValue(variable.getVariableStorage()); + } + + /** + * Get the value of the register, possible saved elsewhere on the stack, relative to this frame + * + *

    + * WARNING: Never invoke this method from the Swing thread. The state could be associated + * with a live session, and this may block to retrieve live state. + * + * @param register the register + * @return the value + */ + T getValue(Register register); + + /** + * Evaluate the given storage, following defining p-code ops until symbol storage is reached + * + *

    + * This behaves similarly to {@link #getValue(VariableStorage)}, except this one will ascend + * recursively to each varnode's defining p-code op. The recursion terminates when a varnode is + * contained in the given symbol storage. The symbol storage is usually collected by examining + * the tokens on the same line, searching for ones that represent "high symbols." This ensures + * that any temporary storage used by the original program in the evaluation of, e.g., a field + * access, are not read from the current state but re-evaluated in terms of the symbols' current + * values. + * + *

    + * WARNING: Never invoke this method from the Swing thread. The state could be associated + * with a live session, and this may block to retrieve live state. + * + * @see VariableValueUtils#collectSymbolStorage(ClangLine) + * @param storage the storage to evaluate + * @param symbolStorage the terminal storage, usually that of symbols + * @return the value + */ + T evaluate(VariableStorage storage, AddressSetView symbolStorage); + + /** + * Evaluate the output for the given p-code op, ascending until symbol storage is reached + * + *

    + * WARNING: Never invoke this method from the Swing thread. The state could be associated + * with a live session, and this may block to retrieve live state. + * + * @see #evaluate(VariableStorage, AddressSetView) + * @param program the program containing the op + * @param op the op + * @param symbolStorage the terminal storage, usually that of symbols + * @return the value + */ + T evaluate(Program program, PcodeOp op, AddressSetView symbolStorage); + + /** + * Set the value of the given storage + * + *

    + * Register accesses may be redirected to the location where its current value is saved to the + * stack. + * + * @param editor the editor for setting values + * @param storage the storage to modify + * @param value the desired value + * @return a future which completes when the necessary commands have all completed + */ + CompletableFuture setValue(StateEditor editor, VariableStorage storage, BigInteger value); + + /** + * Set the value of the given variable + * + * @see #setValue(StateEditor, VariableStorage, BigInteger) + * @param editor the editor for setting values + * @param variable the variable to modify + * @param value the desired value + * @return a future which completes when the necessary commands have all completed + */ + default CompletableFuture setValue(StateEditor editor, Variable variable, + BigInteger value) { + return setValue(editor, variable.getVariableStorage(), value); + } + + /** + * Set the return address of this frame + * + *

    + * This is typically used to set up a mechanism in pure emulation that traps execution when the + * entry function has returned. For example, to emulate a target function in isolation, a script + * could load or map the target program into a trace, initialize a thread at the target + * function's entry, allocate a stack, and "unwind" that stack. Then, it can initialize the + * function's parameters and return address. The return address is usually a fake but + * recognizable address, such as {@code 0xdeadbeef}. The script would then place a breakpoint at + * that address and allow the emulator to run. Once it breaks at {@code 0xdeadbeef}, the script + * can read the return value, if applicable. + * + * @param editor the editor for setting values + * @param address the desired return address + * @return a future which completes when the necessary commands have all completed + */ + CompletableFuture setReturnAddress(StateEditor editor, Address address); + + /** + * Match length by zero extension or truncation + * + *

    + * This is to cope with a small imperfection in field expression evaluation: Fields are + * evaluated using the high p-code from the decompiled function that yielded the expression. + * That code is likely loading the value into a register, which is likely a machine word in + * size, even if the field being accessed is smaller. Thus, the type of a token's high variable + * may disagree in size with the output varnode of the token's associated high p-code op. To + * rectify this discrepancy during evaluation, the type's size is assumed correct, and the + * output value is resized to match. + * + * @param value the value + * @param length the desired length + * @return the extended or truncated value + */ + T zext(T value, int length); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/BackgroundUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/BackgroundUtils.java index 996f35ca9d..0b7db5ce97 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/BackgroundUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/BackgroundUtils.java @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.utils; -import java.util.List; +import java.util.*; import java.util.concurrent.*; import java.util.function.BiFunction; import java.util.function.Function; @@ -166,20 +166,24 @@ public enum BackgroundUtils { } public static class PluginToolExecutorService extends AbstractExecutorService { - private final PluginTool tool; - private String name; - private boolean canCancel; - private boolean hasProgress; - private boolean isModal; - private final int delay; + public enum TaskOpt { + CAN_CANCEL, HAS_PROGRESS, IS_MODAL, IS_BACKGROUND; + } - public PluginToolExecutorService(PluginTool tool, String name, boolean canCancel, - boolean hasProgress, boolean isModal, int delay) { + private final PluginTool tool; + private final String name; + private final UndoableDomainObject obj; + private final int delay; + private final EnumSet opts; + + private TaskMonitor lastMonitor; + + public PluginToolExecutorService(PluginTool tool, String name, UndoableDomainObject obj, + int delay, TaskOpt... opts) { this.tool = tool; this.name = name; - this.canCancel = canCancel; - this.hasProgress = hasProgress; - this.isModal = isModal; + this.obj = obj; + this.opts = EnumSet.copyOf(Arrays.asList(opts)); this.delay = delay; } @@ -210,13 +214,45 @@ public enum BackgroundUtils { @Override public void execute(Runnable command) { - Task task = new Task(name, canCancel, hasProgress, isModal) { + if (opts.contains(TaskOpt.IS_BACKGROUND)) { + executeBackground(command); + } + else { + executeForeground(command); + } + } + + protected void executeForeground(Runnable command) { + Task task = new Task(name, + opts.contains(TaskOpt.CAN_CANCEL), + opts.contains(TaskOpt.HAS_PROGRESS), + opts.contains(TaskOpt.IS_MODAL)) { @Override public void run(TaskMonitor monitor) throws CancelledException { + lastMonitor = monitor; command.run(); } }; tool.execute(task, delay); } + + protected void executeBackground(Runnable command) { + BackgroundCommand cmd = new BackgroundCommand(name, + opts.contains(TaskOpt.HAS_PROGRESS), + opts.contains(TaskOpt.CAN_CANCEL), + opts.contains(TaskOpt.IS_MODAL)) { + @Override + public boolean applyTo(DomainObject obj, TaskMonitor monitor) { + lastMonitor = monitor; + command.run(); + return true; + } + }; + tool.executeBackgroundCommand(cmd, obj); + } + + public TaskMonitor getLastMonitor() { + return lastMonitor; + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java index a2a753700c..55983a510d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java @@ -15,6 +15,13 @@ */ package ghidra.pcode.exec; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.bouncycastle.util.Arrays; + import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.service.emulation.*; import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; @@ -26,13 +33,14 @@ import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.exec.trace.*; import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; +import ghidra.pcode.utils.Utils; import ghidra.program.model.address.*; -import ghidra.program.model.lang.Endian; -import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemBuffer; import ghidra.trace.model.Trace; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.util.NumericUtilities; /** * Utilities for evaluating or executing Sleigh/p-code in the Debugger @@ -102,10 +110,160 @@ public enum DebuggerPcodeUtils { } /** - * The value of a watch expression including its state, address, and addresses read + * A wrapper on a byte array to pretty print it */ - public record WatchValue(byte[] bytes, TraceMemoryState state, Address address, + public record PrettyBytes(boolean bigEndian, byte[] bytes) { + @Override + public byte[] bytes() { + return Arrays.copyOf(bytes, bytes.length); + } + + @Override + public String toString() { + return "PrettyBytes[bigEndian=" + bigEndian + ",bytes=" + + NumericUtilities.convertBytesToString(bytes, ":") + ",value=" + + toBigInteger(false) + "]"; + } + + /** + * Render at most 256 bytes in lines of 16 space-separated bytes each + * + *

    + * If the total exceeds 256 bytes, the last line will contain ellipses and indicate the + * total size in bytes. + * + * @return the rendered string + */ + public String toBytesString() { + StringBuffer buf = new StringBuffer(); + boolean first = true; + for (int i = 0; i < bytes.length; i += 16) { + if (i >= 256) { + buf.append("\n... (count="); + buf.append(bytes.length); + buf.append(")"); + break; + } + if (first) { + first = false; + } + else { + buf.append('\n'); + } + int len = Math.min(16, bytes.length - i); + buf.append(NumericUtilities.convertBytesToString(bytes, i, len, " ")); + } + return buf.toString(); + } + + /** + * Render the bytes as an unsigned decimal integer + * + *

    + * The endianness is taken from {@link #bigEndian()} + * + * @return the rendered string + */ + public String toIntegerString() { + return toBigInteger(false).toString(); + } + + /** + * Collect various integer representations: signed, unsigned; decimal, hexadecimal + * + *

    + * This only presents those forms that differ from those already offered. The preferred form + * is unsigned decimal. If all four differ, then they are formatted on two lines: unsigned + * then signed. + * + * @return the rendered string + */ + public String collectDisplays() { + BigInteger unsigned = toBigInteger(false); + StringBuffer sb = new StringBuffer(); + String uDec = unsigned.toString(); + sb.append(uDec); + String uHex = unsigned.toString(16); + boolean radixMatters = !uHex.equals(uDec); + if (radixMatters) { + sb.append(", 0x"); + sb.append(uHex); + } + BigInteger signed = toBigInteger(true); + if (!signed.equals(unsigned)) { + sb.append(radixMatters ? "\n" : ", "); + String sDec = signed.toString(); + sb.append(sDec); + String sHex = signed.toString(16); + if (!sHex.equals(sDec)) { + sb.append(", -0x"); + sb.append(sHex.subSequence(1, sHex.length())); + } + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PrettyBytes that)) { + return false; + } + if (this.bigEndian != that.bigEndian) { + return false; + } + return Arrays.areEqual(this.bytes, that.bytes); + } + + /** + * Convert the array to a big integer with the given signedness + * + * @param signed true for signed, false for unsigned + * @return the big integer + */ + public BigInteger toBigInteger(boolean signed) { + return Utils.bytesToBigInteger(bytes, bytes.length, bigEndian, signed); + } + + /** + * Get the number of bytes + * + * @return the count + */ + public int length() { + return bytes.length; + } + } + + /** + * The value of a watch expression including its state, location, and addresses read + */ + public record WatchValue(PrettyBytes bytes, TraceMemoryState state, ValueLocation location, AddressSetView reads) { + /** + * Get the value as a big integer with the given signedness + * + * @param signed true for signed, false for unsigned + * @return the big integer + */ + public BigInteger toBigInteger(boolean signed) { + return bytes.toBigInteger(signed); + } + + public Address address() { + return location == null ? null : location.getAddress(); + } + + /** + * Get the number of bytes + * + * @return the count + */ + public int length() { + return bytes.length(); + } } /** @@ -116,8 +274,8 @@ public enum DebuggerPcodeUtils { * unwieldy. */ public enum WatchValuePcodeArithmetic implements PcodeArithmetic { - BIG_ENDIAN(BytesPcodeArithmetic.BIG_ENDIAN), - LITTLE_ENDIAN(BytesPcodeArithmetic.LITTLE_ENDIAN); + BIG_ENDIAN(BytesPcodeArithmetic.BIG_ENDIAN, LocationPcodeArithmetic.BIG_ENDIAN), + LITTLE_ENDIAN(BytesPcodeArithmetic.LITTLE_ENDIAN, LocationPcodeArithmetic.LITTLE_ENDIAN); public static WatchValuePcodeArithmetic forEndian(boolean isBigEndian) { return isBigEndian ? BIG_ENDIAN : LITTLE_ENDIAN; @@ -129,15 +287,16 @@ public enum DebuggerPcodeUtils { private static final TraceMemoryStatePcodeArithmetic STATE = TraceMemoryStatePcodeArithmetic.INSTANCE; - private static final AddressOfPcodeArithmetic ADDRESS = - AddressOfPcodeArithmetic.INSTANCE; private static final AddressesReadPcodeArithmetic READS = AddressesReadPcodeArithmetic.INSTANCE; private final BytesPcodeArithmetic bytes; + private final LocationPcodeArithmetic location; - private WatchValuePcodeArithmetic(BytesPcodeArithmetic bytes) { + private WatchValuePcodeArithmetic(BytesPcodeArithmetic bytes, + LocationPcodeArithmetic location) { this.bytes = bytes; + this.location = location; } @Override @@ -148,9 +307,10 @@ public enum DebuggerPcodeUtils { @Override public WatchValue unaryOp(int opcode, int sizeout, int sizein1, WatchValue in1) { return new WatchValue( - bytes.unaryOp(opcode, sizeout, sizein1, in1.bytes), + new PrettyBytes(getEndian().isBigEndian(), + bytes.unaryOp(opcode, sizeout, sizein1, in1.bytes.bytes)), STATE.unaryOp(opcode, sizeout, sizein1, in1.state), - ADDRESS.unaryOp(opcode, sizeout, sizein1, in1.address), + location.unaryOp(opcode, sizeout, sizein1, in1.location), READS.unaryOp(opcode, sizeout, sizein1, in1.reads)); } @@ -158,9 +318,11 @@ public enum DebuggerPcodeUtils { public WatchValue binaryOp(int opcode, int sizeout, int sizein1, WatchValue in1, int sizein2, WatchValue in2) { return new WatchValue( - bytes.binaryOp(opcode, sizeout, sizein1, in1.bytes, sizein2, in2.bytes), + new PrettyBytes(getEndian().isBigEndian(), + bytes.binaryOp(opcode, sizeout, sizein1, in1.bytes.bytes, sizein2, + in2.bytes.bytes)), STATE.binaryOp(opcode, sizeout, sizein1, in1.state, sizein2, in2.state), - ADDRESS.binaryOp(opcode, sizeout, sizein1, in1.address, sizein2, in2.address), + location.binaryOp(opcode, sizeout, sizein1, in1.location, sizein2, in2.location), READS.binaryOp(opcode, sizeout, sizein1, in1.reads, sizein2, in2.reads)); } @@ -168,12 +330,13 @@ public enum DebuggerPcodeUtils { public WatchValue modBeforeStore(int sizeout, int sizeinAddress, WatchValue inAddress, int sizeinValue, WatchValue inValue) { return new WatchValue( - bytes.modBeforeStore(sizeout, sizeinAddress, inAddress.bytes, - sizeinValue, inValue.bytes), + new PrettyBytes(inValue.bytes.bigEndian, + bytes.modBeforeStore(sizeout, sizeinAddress, inAddress.bytes.bytes, + sizeinValue, inValue.bytes.bytes)), STATE.modBeforeStore(sizeout, sizeinAddress, inAddress.state, sizeinValue, inValue.state), - ADDRESS.modBeforeStore(sizeout, sizeinAddress, inAddress.address, - sizeinValue, inValue.address), + location.modBeforeStore(sizeout, sizeinAddress, inAddress.location, + sizeinValue, inValue.location), READS.modBeforeStore(sizeout, sizeinAddress, inAddress.reads, sizeinValue, inValue.reads)); } @@ -182,12 +345,13 @@ public enum DebuggerPcodeUtils { public WatchValue modAfterLoad(int sizeout, int sizeinAddress, WatchValue inAddress, int sizeinValue, WatchValue inValue) { return new WatchValue( - bytes.modAfterLoad(sizeout, sizeinAddress, inAddress.bytes, - sizeinValue, inValue.bytes), + new PrettyBytes(getEndian().isBigEndian(), + bytes.modAfterLoad(sizeout, sizeinAddress, inAddress.bytes.bytes, + sizeinValue, inValue.bytes.bytes)), STATE.modAfterLoad(sizeout, sizeinAddress, inAddress.state, sizeinValue, inValue.state), - ADDRESS.modAfterLoad(sizeout, sizeinAddress, inAddress.address, - sizeinValue, inValue.address), + location.modAfterLoad(sizeout, sizeinAddress, inAddress.location, + sizeinValue, inValue.location), READS.modAfterLoad(sizeout, sizeinAddress, inAddress.reads, sizeinValue, inValue.reads)); } @@ -195,20 +359,20 @@ public enum DebuggerPcodeUtils { @Override public WatchValue fromConst(byte[] value) { return new WatchValue( - bytes.fromConst(value), + new PrettyBytes(getEndian().isBigEndian(), bytes.fromConst(value)), STATE.fromConst(value), - ADDRESS.fromConst(value), + location.fromConst(value), READS.fromConst(value)); } @Override public byte[] toConcrete(WatchValue value, Purpose purpose) { - return bytes.toConcrete(value.bytes, purpose); + return bytes.toConcrete(value.bytes.bytes, purpose); } @Override public long sizeOf(WatchValue value) { - return bytes.sizeOf(value.bytes); + return bytes.sizeOf(value.bytes.bytes); } } @@ -216,7 +380,7 @@ public enum DebuggerPcodeUtils { implements PcodeExecutorStatePiece { private final PcodeExecutorStatePiece bytesPiece; private final PcodeExecutorStatePiece statePiece; - private final PcodeExecutorStatePiece addressPiece; + private final PcodeExecutorStatePiece locationPiece; private final PcodeExecutorStatePiece readsPiece; private final PcodeArithmetic arithmetic; @@ -224,11 +388,11 @@ public enum DebuggerPcodeUtils { public WatchValuePcodeExecutorStatePiece( PcodeExecutorStatePiece bytesPiece, PcodeExecutorStatePiece statePiece, - PcodeExecutorStatePiece addressPiece, + PcodeExecutorStatePiece locationPiece, PcodeExecutorStatePiece readsPiece) { this.bytesPiece = bytesPiece; this.statePiece = statePiece; - this.addressPiece = addressPiece; + this.locationPiece = locationPiece; this.readsPiece = readsPiece; this.arithmetic = WatchValuePcodeArithmetic.forLanguage(bytesPiece.getLanguage()); } @@ -248,12 +412,18 @@ public enum DebuggerPcodeUtils { return arithmetic; } + @Override + public WatchValuePcodeExecutorStatePiece fork() { + return new WatchValuePcodeExecutorStatePiece( + bytesPiece.fork(), statePiece.fork(), locationPiece.fork(), readsPiece.fork()); + } + @Override public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize, WatchValue val) { - bytesPiece.setVar(space, offset, size, quantize, val.bytes); + bytesPiece.setVar(space, offset, size, quantize, val.bytes.bytes); statePiece.setVar(space, offset, size, quantize, val.state); - addressPiece.setVar(space, offset, size, quantize, val.address); + locationPiece.setVar(space, offset, size, quantize, val.location); readsPiece.setVar(space, offset, size, quantize, val.reads); } @@ -261,12 +431,30 @@ public enum DebuggerPcodeUtils { public WatchValue getVar(AddressSpace space, byte[] offset, int size, boolean quantize, Reason reason) { return new WatchValue( - bytesPiece.getVar(space, offset, size, quantize, reason), + new PrettyBytes(getLanguage().isBigEndian(), + bytesPiece.getVar(space, offset, size, quantize, reason)), statePiece.getVar(space, offset, size, quantize, reason), - addressPiece.getVar(space, offset, size, quantize, reason), + locationPiece.getVar(space, offset, size, quantize, reason), readsPiece.getVar(space, offset, size, quantize, reason)); } + @Override + public Map getRegisterValues() { + Map result = new HashMap<>(); + for (Entry entry : bytesPiece.getRegisterValues().entrySet()) { + Register reg = entry.getKey(); + AddressSpace space = reg.getAddressSpace(); + long offset = reg.getAddress().getOffset(); + int size = reg.getNumBytes(); + result.put(reg, new WatchValue( + new PrettyBytes(getLanguage().isBigEndian(), entry.getValue()), + statePiece.getVar(space, offset, size, false, Reason.INSPECT), + locationPiece.getVar(space, offset, size, false, Reason.INSPECT), + readsPiece.getVar(space, offset, size, false, Reason.INSPECT))); + } + return result; + } + @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { return bytesPiece.getConcreteBuffer(address, purpose); @@ -276,7 +464,7 @@ public enum DebuggerPcodeUtils { public void clear() { bytesPiece.clear(); statePiece.clear(); - addressPiece.clear(); + locationPiece.clear(); readsPiece.clear(); } } @@ -298,16 +486,26 @@ public enum DebuggerPcodeUtils { return piece.arithmetic; } + @Override + public WatchValuePcodeExecutorState fork() { + return new WatchValuePcodeExecutorState(piece.fork()); + } + @Override public void setVar(AddressSpace space, WatchValue offset, int size, boolean quantize, WatchValue val) { - piece.setVar(space, offset.bytes, size, quantize, val); + piece.setVar(space, offset.bytes.bytes, size, quantize, val); } @Override public WatchValue getVar(AddressSpace space, WatchValue offset, int size, boolean quantize, Reason reason) { - return piece.getVar(space, offset.bytes, size, quantize, reason); + return piece.getVar(space, offset.bytes.bytes, size, quantize, reason); + } + + @Override + public Map getRegisterValues() { + return piece.getRegisterValues(); } @Override @@ -330,7 +528,7 @@ public enum DebuggerPcodeUtils { return new WatchValuePcodeExecutorState(new WatchValuePcodeExecutorStatePiece( bytesState, new TraceMemoryStatePcodeExecutorStatePiece(data), - new AddressOfPcodeExecutorStatePiece(data.getLanguage()), + new LocationPcodeExecutorStatePiece(data.getLanguage()), new AddressesReadTracePcodeExecutorStatePiece(data))); } diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java index 7ea69b52ff..953847f7e8 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java @@ -15,31 +15,63 @@ */ package ghidra.app.plugin.core.debug.gui.stack; +import java.io.IOException; +import java.math.BigInteger; +import java.util.List; +import java.util.Set; + import org.junit.*; +import generic.Unique; +import ghidra.app.plugin.assembler.*; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.action.SPLocationTrackingSpec; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; +import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; +import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; +import ghidra.app.plugin.core.debug.stack.*; import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; import ghidra.app.services.*; +import ghidra.app.services.DebuggerEmulationService.EmulationResult; +import ghidra.app.services.DebuggerStateEditingService.StateEditingMode; +import ghidra.app.services.DebuggerStateEditingService.StateEditor; +import ghidra.async.AsyncTestUtils; import ghidra.framework.model.DomainFolder; +import ghidra.framework.model.DomainObject; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue; +import ghidra.program.database.ProgramDB; +import ghidra.program.disassemble.Disassembler; import ghidra.program.model.address.*; -import ghidra.program.model.listing.FunctionManager; -import ghidra.program.model.listing.Program; +import ghidra.program.model.data.UnsignedIntegerDataType; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function.FunctionUpdateType; import ghidra.program.model.symbol.SourceType; import ghidra.program.util.ProgramLocation; import ghidra.test.ToyProgramBuilder; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.model.DefaultTraceLocation; import ghidra.trace.model.Lifespan; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.schedule.Scheduler; +import ghidra.util.InvalidNameException; +import ghidra.util.Msg; import ghidra.util.database.UndoableTransaction; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.ConsoleTaskMonitor; import ghidra.util.task.TaskMonitor; import help.screenshot.GhidraScreenShotGenerator; -public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator { +public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator + implements AsyncTestUtils { ProgramManager programManager; DebuggerTraceManagerService traceManager; @@ -125,4 +157,182 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator { captureIsolatedProvider(DebuggerStackProvider.class, 600, 300); } + + protected ConsoleTaskMonitor monitor = new ConsoleTaskMonitor(); + + // TODO: Propose this replace waitForProgram + public static void waitForDomainObject(DomainObject object) { + object.flushEvents(); + waitForSwing(); + } + + protected void intoProject(DomainObject obj) { + waitForDomainObject(obj); + DomainFolder rootFolder = tool.getProject() + .getProjectData() + .getRootFolder(); + waitForCondition(() -> { + try { + rootFolder.createFile(obj.getName(), obj, monitor); + return true; + } + catch (InvalidNameException | CancelledException e) { + throw new AssertionError(e); + } + catch (IOException e) { + // Usually "object is busy". Try again. + return false; + } + }); + } + + protected void createProgram(Language lang, CompilerSpec cSpec) throws IOException { + program = new ProgramDB("fibonacci", lang, cSpec, this); + } + + protected void createProgram(String languageID, String cSpecID) throws IOException { + Language language = getLanguageService().getLanguage(new LanguageID(languageID)); + CompilerSpec cSpec = cSpecID == null ? language.getDefaultCompilerSpec() + : language.getCompilerSpecByID(new CompilerSpecID(cSpecID)); + createProgram(language, cSpec); + } + + Address retInstr; + + protected Register register(String name) { + return program.getLanguage().getRegister(name); + } + + protected Function createFibonacciProgramX86_32() throws Throwable { + createProgram("x86:LE:32:default", "gcc"); + intoProject(program); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + Address entry = addr(program, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + Assembler asm = + Assemblers.getAssembler(program.getLanguage(), StackUnwinderTest.NO_16BIT_CALLS); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + buf.assemble("PUSH EBP"); + buf.assemble("MOV EBP, ESP"); + + buf.assemble("CMP dword ptr [EBP+8], 1"); + Address jumpBase = buf.getNext(); + buf.assemble("JBE 0x" + buf.getNext()); + + // Recursive case. Let EDX be sum + // sum = fib(n - 1) + buf.assemble("MOV ECX, dword ptr [EBP+8]"); + buf.assemble("DEC ECX"); + buf.assemble("PUSH ECX"); // pass n - 1 + buf.assemble("CALL 0x" + entry); + buf.assemble("ADD ESP, 4"); // Clear parameters + buf.assemble("MOV EDX, EAX"); + // sum += fib(n - 2) + buf.assemble("MOV ECX, dword ptr [EBP+8]"); + buf.assemble("SUB ECX, 2"); + buf.assemble("PUSH EDX"); // Caller Save EDX + buf.assemble("PUSH ECX"); // pass n - 2 + buf.assemble("CALL 0x" + entry); + buf.assemble("ADD ESP, 4"); // Clear parameters + buf.assemble("POP EDX"); // Restore EDX + buf.assemble("ADD EAX, EDX"); + + Address labelRet = buf.getNext(); + buf.assemble("LEAVE"); + retInstr = buf.getNext(); + buf.assemble("RET"); + + Address labelBase = buf.getNext(); + buf.assemble(jumpBase, "JBE 0x" + labelBase); + buf.assemble("MOV EAX, dword ptr [EBP+8]"); + buf.assemble("JMP 0x" + labelRet); + + byte[] bytes = buf.getBytes(); + program.getMemory().setBytes(entry, bytes); + + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, null); + + Function function = program.getFunctionManager() + .createFunction("fib", entry, + new AddressSet(entry, entry.add(bytes.length - 1)), + SourceType.USER_DEFINED); + + function.updateFunction("__cdecl", + new ReturnParameterImpl(UnsignedIntegerDataType.dataType, program), + List.of( + new ParameterImpl("n", UnsignedIntegerDataType.dataType, program)), + FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS); + // NOTE: The decompiler doesn't actually use sum.... For some reason, it re-uses n + // Still, in the tests, I can use uVar1 (EAX) as a register variable + function.addLocalVariable( + new LocalVariableImpl("sum", 0, UnsignedIntegerDataType.dataType, register("EDX"), + program), + SourceType.USER_DEFINED); + return function; + } + } + + @Test + public void testCaptureDebuggerStackUnwindInListing() throws Throwable { + addPlugin(tool, DebuggerListingPlugin.class); + + DebuggerStateEditingService editingService = + addPlugin(tool, DebuggerStateEditingServicePlugin.class); + DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + + Function function = createFibonacciProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + tb.close(); + tb = new ToyDBTraceBuilder( + ProgramEmulationUtils.launchEmulationTrace(program, entry, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + StateEditor editor = editingService.createStateEditor(tb.trace); + + DebuggerCoordinates atSetup = traceManager.getCurrent(); + StackUnwinder unwinder = new StackUnwinder(tool, atSetup.getPlatform()); + AnalysisUnwoundFrame frameAtSetup = unwinder.start(atSetup, monitor); + + Parameter param1 = function.getParameter(0); + waitOn(frameAtSetup.setValue(editor, param1, BigInteger.valueOf(9))); + waitOn(frameAtSetup.setReturnAddress(editor, tb.addr(0xdeadbeef))); + waitForTasks(); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getBreakpointManager() + .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr, + Set.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack"); + } + + EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor, + Scheduler.oneThread(thread)); + Msg.debug(this, "Broke after " + result.schedule()); + + traceManager.activateTime(result.schedule()); + waitForTasks(); + DebuggerCoordinates tallest = traceManager.getCurrent(); + try (UndoableTransaction tid = tb.startTransaction()) { + new UnwindStackCommand(tool, tallest).applyTo(tb.trace, monitor); + } + waitForDomainObject(tb.trace); + + DebuggerListingProvider listingProvider = + waitForComponentProvider(DebuggerListingProvider.class); + listingProvider.setTrackingSpec(SPLocationTrackingSpec.INSTANCE); + waitForSwing(); + + captureIsolatedProvider(listingProvider, 800, 600); + } } diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java new file mode 100644 index 0000000000..109fedbd0a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java @@ -0,0 +1,391 @@ +/* ### + * 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.plugin.core.debug.gui.stack.vars; + +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.*; + +import org.junit.Test; + +import docking.widgets.fieldpanel.FieldPanel; +import docking.widgets.fieldpanel.support.FieldLocation; +import generic.Unique; +import ghidra.app.decompiler.component.DecompilerPanel; +import ghidra.app.plugin.assembler.*; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; +import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; +import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; +import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; +import ghidra.app.plugin.core.debug.stack.*; +import ghidra.app.plugin.core.debug.stack.StackUnwinderTest.HoverLocation; +import ghidra.app.plugin.core.decompile.DecompilerProvider; +import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; +import ghidra.app.services.*; +import ghidra.app.services.DebuggerEmulationService.EmulationResult; +import ghidra.app.services.DebuggerStateEditingService.StateEditingMode; +import ghidra.app.services.DebuggerStateEditingService.StateEditor; +import ghidra.app.util.viewer.listingpanel.ListingPanel; +import ghidra.async.AsyncTestUtils; +import ghidra.framework.model.DomainFolder; +import ghidra.framework.model.DomainObject; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue; +import ghidra.program.database.ProgramDB; +import ghidra.program.disassemble.Disassembler; +import ghidra.program.model.address.*; +import ghidra.program.model.data.UnsignedIntegerDataType; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function.FunctionUpdateType; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.scalar.Scalar; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.util.GhidraProgramUtilities; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.schedule.Scheduler; +import ghidra.util.InvalidNameException; +import ghidra.util.Msg; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.ConsoleTaskMonitor; +import help.screenshot.GhidraScreenShotGenerator; + +public class VariableValueHoverPluginScreenShots extends GhidraScreenShotGenerator + implements AsyncTestUtils { + + ProgramManager programManager; + DebuggerTraceManagerService traceManager; + DebuggerStaticMappingService mappingService; + ToyDBTraceBuilder tb; + Program program; + + protected ConsoleTaskMonitor monitor = new ConsoleTaskMonitor(); + + // TODO: Propose this replace waitForProgram + public static void waitForDomainObject(DomainObject object) { + object.flushEvents(); + waitForSwing(); + } + + protected void intoProject(DomainObject obj) { + waitForDomainObject(obj); + DomainFolder rootFolder = tool.getProject() + .getProjectData() + .getRootFolder(); + waitForCondition(() -> { + try { + rootFolder.createFile(obj.getName(), obj, monitor); + return true; + } + catch (InvalidNameException | CancelledException e) { + throw new AssertionError(e); + } + catch (IOException e) { + // Usually "object is busy". Try again. + return false; + } + }); + } + + protected void createProgram(Language lang, CompilerSpec cSpec) throws IOException { + program = new ProgramDB("fibonacci", lang, cSpec, this); + } + + protected void createProgram(String languageID, String cSpecID) throws IOException { + Language language = getLanguageService().getLanguage(new LanguageID(languageID)); + CompilerSpec cSpec = cSpecID == null ? language.getDefaultCompilerSpec() + : language.getCompilerSpecByID(new CompilerSpecID(cSpecID)); + createProgram(language, cSpec); + } + + Map stackRefInstrs = new HashMap<>(); + Address registerRefInstr; + Address retInstr; + + protected Register register(String name) { + return program.getLanguage().getRegister(name); + } + + private static Address addr(Program program, long offset) { + return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset); + } + + protected Function createFibonacciProgramX86_32() throws Throwable { + createProgram("x86:LE:32:default", "gcc"); + intoProject(program); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + Address entry = addr(program, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + Assembler asm = + Assemblers.getAssembler(program.getLanguage(), StackUnwinderTest.NO_16BIT_CALLS); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + buf.assemble("PUSH EBP"); + buf.assemble("MOV EBP, ESP"); + + stackRefInstrs.put(buf.getNext(), 0); + buf.assemble("CMP dword ptr [EBP+8], 1"); + Address jumpBase = buf.getNext(); + buf.assemble("JBE 0x" + buf.getNext()); + + // Recursive case. Let EDX be sum + // sum = fib(n - 1) + stackRefInstrs.put(buf.getNext(), 1); + buf.assemble("MOV ECX, dword ptr [EBP+8]"); + buf.assemble("DEC ECX"); + buf.assemble("PUSH ECX"); // pass n - 1 + buf.assemble("CALL 0x" + entry); + buf.assemble("ADD ESP, 4"); // Clear parameters + registerRefInstr = buf.getNext(); + buf.assemble("MOV EDX, EAX"); + // sum += fib(n - 2) + stackRefInstrs.put(buf.getNext(), 1); + buf.assemble("MOV ECX, dword ptr [EBP+8]"); + buf.assemble("SUB ECX, 2"); + buf.assemble("PUSH EDX"); // Caller Save EDX + buf.assemble("PUSH ECX"); // pass n - 2 + buf.assemble("CALL 0x" + entry); + buf.assemble("ADD ESP, 4"); // Clear parameters + buf.assemble("POP EDX"); // Restore EDX + buf.assemble("ADD EAX, EDX"); + + Address labelRet = buf.getNext(); + buf.assemble("LEAVE"); + retInstr = buf.getNext(); + buf.assemble("RET"); + + Address labelBase = buf.getNext(); + buf.assemble(jumpBase, "JBE 0x" + labelBase); + stackRefInstrs.put(buf.getNext(), 1); + buf.assemble("MOV EAX, dword ptr [EBP+8]"); + buf.assemble("JMP 0x" + labelRet); + + byte[] bytes = buf.getBytes(); + program.getMemory().setBytes(entry, bytes); + + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, null); + + Function function = program.getFunctionManager() + .createFunction("fib", entry, + new AddressSet(entry, entry.add(bytes.length - 1)), + SourceType.USER_DEFINED); + + function.updateFunction("__cdecl", + new ReturnParameterImpl(UnsignedIntegerDataType.dataType, program), + List.of( + new ParameterImpl("n", UnsignedIntegerDataType.dataType, program)), + FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS); + // NOTE: The decompiler doesn't actually use sum.... For some reason, it re-uses n + // Still, in the tests, I can use uVar1 (EAX) as a register variable + function.addLocalVariable( + new LocalVariableImpl("sum", 0, UnsignedIntegerDataType.dataType, register("EDX"), + program), + SourceType.USER_DEFINED); + + AddressSpace stack = program.getAddressFactory().getStackSpace(); + for (Map.Entry ent : stackRefInstrs.entrySet()) { + Instruction ins = program.getListing().getInstructionAt(ent.getKey()); + ins.addOperandReference(ent.getValue(), stack.getAddress(4), RefType.READ, + SourceType.ANALYSIS); + } + return function; + } + } + + protected void prepareContext() throws Throwable { + programManager = addPlugin(tool, ProgramManagerPlugin.class); + programManager.closeAllPrograms(true); + traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class); + mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class); + addPlugin(tool, DebuggerListingPlugin.class); + addPlugin(tool, VariableValueHoverPlugin.class); + + DebuggerStateEditingService editingService = + addPlugin(tool, DebuggerStateEditingServicePlugin.class); + DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + + Function function = createFibonacciProgramX86_32(); + GhidraProgramUtilities.setAnalyzedFlag(program, true); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + tb = new ToyDBTraceBuilder( + ProgramEmulationUtils.launchEmulationTrace(program, entry, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + try (UndoableTransaction tid = tb.startTransaction()) { + MemoryBlock block = program.getMemory().getBlock(".text"); + byte[] text = new byte[(int) block.getSize()]; + block.getBytes(block.getStart(), text); + + tb.trace.getMemoryManager().putBytes(0, block.getStart(), ByteBuffer.wrap(text)); + + Disassembler dis = + Disassembler.getDisassembler(tb.trace.getProgramView(), monitor, null); + dis.disassemble(entry, null); + } + waitForDomainObject(tb.trace); + + editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + StateEditor editor = editingService.createStateEditor(tb.trace); + + DebuggerCoordinates atSetup = traceManager.getCurrent(); + StackUnwinder unwinder = new StackUnwinder(tool, atSetup.getPlatform()); + AnalysisUnwoundFrame frameAtSetup = unwinder.start(atSetup, monitor); + + Parameter param1 = function.getParameter(0); + waitOn(frameAtSetup.setValue(editor, param1, BigInteger.valueOf(9))); + waitOn(frameAtSetup.setReturnAddress(editor, tb.addr(0xdeadbeef))); + waitForTasks(); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getBreakpointManager() + .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr, + Set.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack"); + } + + EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor, + Scheduler.oneThread(thread)); + Msg.debug(this, "Broke after " + result.schedule()); + + traceManager.activateTime(result.schedule()); + waitForTasks(); + DebuggerCoordinates tallest = traceManager.getCurrent(); + try (UndoableTransaction tid = tb.startTransaction()) { + new UnwindStackCommand(tool, tallest).applyTo(tb.trace, monitor); + } + waitForDomainObject(tb.trace); + } + + @Test + public void testCaptureVariableValueHoverPluginListing() throws Throwable { + prepareContext(); + + // We're cheating the address game, but both use the same language without relocation + Instruction ins = tb.trace.getCodeManager().instructions().getAt(0, registerRefInstr); + + DebuggerListingProvider listingProvider = + waitForComponentProvider(DebuggerListingProvider.class); + ListingPanel listingPanel = listingProvider.getListingPanel(); + + Window window = moveProviderToItsOwnWindow(listingProvider, 900, 600); + window.toFront(); + waitForSwing(); + + HoverLocation loc = + StackUnwinderTest.findOperandLocation(listingPanel, ins, register("EDX")); + FieldLocation fLoc = loc.fLoc(); + BigInteger refIndex = listingPanel.getAddressIndexMap().getIndex(registerRefInstr); + + FieldPanel fieldPanel = listingPanel.getFieldPanel(); + runSwing(() -> fieldPanel.goTo(refIndex, fLoc.fieldNum, fLoc.row, fLoc.col, false)); + waitForSwing(); + + Rectangle rect = listingPanel.getCursorBounds(); + + MouseEvent event = + new MouseEvent(fieldPanel, 0, System.currentTimeMillis(), 0, rect.x, rect.y, 0, false); + fieldPanel.getHoverHandler().mouseHovered(event); + waitForSwing(); + + captureProviderWithScreenShot(listingProvider); + } + + @Test + public void testCaptureVariableValueHoverPluginBrowser() throws Throwable { + CodeViewerProvider browserProvider = waitForComponentProvider(CodeViewerProvider.class); + prepareContext(); + + List

    stackRefs = new ArrayList<>(stackRefInstrs.keySet()); + Address refAddr = stackRefs.get(2); + Instruction ins = program.getListing().getInstructionAt(refAddr); + + ListingPanel listingPanel = browserProvider.getListingPanel(); + + Window window = moveProviderToItsOwnWindow(browserProvider, 1000, 600); + window.toFront(); + waitForSwing(); + + HoverLocation loc = + StackUnwinderTest.findOperandLocation(listingPanel, ins, new Scalar(32, 8)); + FieldLocation fLoc = loc.fLoc(); + BigInteger refIndex = listingPanel.getAddressIndexMap().getIndex(refAddr); + + FieldPanel fieldPanel = listingPanel.getFieldPanel(); + runSwing(() -> fieldPanel.goTo(refIndex, fLoc.fieldNum, fLoc.row, fLoc.col, false)); + waitForSwing(); + + Rectangle rect = listingPanel.getCursorBounds(); + + MouseEvent event = + new MouseEvent(fieldPanel, 0, System.currentTimeMillis(), 0, rect.x, rect.y, 0, false); + fieldPanel.getHoverHandler().mouseHovered(event); + waitForSwing(); + + captureProviderWithScreenShot(browserProvider); + } + + @Test + public void testCaptureVariableValueHoverPluginDecompiler() throws Throwable { + DecompilerProvider decompilerProvider = waitForComponentProvider(DecompilerProvider.class); + tool.showComponentProvider(decompilerProvider, true); + prepareContext(); + + Function function = program.getFunctionManager().getFunctionContaining(registerRefInstr); + + DecompilerPanel decompilerPanel = decompilerProvider.getDecompilerPanel(); + + Window window = moveProviderToItsOwnWindow(decompilerProvider, 600, 600); + window.toFront(); + waitForSwing(); + + HoverLocation loc = + StackUnwinderTest.findTokenLocation(decompilerPanel, function, "n", "if (1 < n) {"); + runSwing(() -> decompilerPanel.goToToken(loc.token())); + waitForSwing(); + + FieldPanel fieldPanel = decompilerPanel.getFieldPanel(); + Rectangle rect = fieldPanel.getCursorBounds(); + + MouseEvent event = + new MouseEvent(fieldPanel, 0, System.currentTimeMillis(), 0, rect.x, rect.y, 0, false); + fieldPanel.getHoverHandler().mouseHovered(event); + waitForSwing(); + + captureProviderWithScreenShot(decompilerProvider); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java new file mode 100644 index 0000000000..c8ba1092d7 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java @@ -0,0 +1,1680 @@ +/* ### + * 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.plugin.core.debug.stack; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.function.Predicate; + +import org.junit.Test; + +import docking.widgets.fieldpanel.Layout; +import docking.widgets.fieldpanel.field.Field; +import docking.widgets.fieldpanel.support.FieldLocation; +import generic.Unique; +import ghidra.app.decompiler.*; +import ghidra.app.decompiler.component.*; +import ghidra.app.plugin.assembler.*; +import ghidra.app.plugin.assembler.sleigh.sem.*; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.gui.stack.vars.*; +import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.*; +import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; +import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; +import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.*; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.plugin.core.decompile.DecompilerProvider; +import ghidra.app.plugin.core.disassembler.DisassemblerPlugin; +import ghidra.app.services.*; +import ghidra.app.services.DebuggerEmulationService.EmulationResult; +import ghidra.app.services.DebuggerStateEditingService.StateEditingMode; +import ghidra.app.services.DebuggerStateEditingService.StateEditor; +import ghidra.app.util.viewer.field.FieldFactory; +import ghidra.app.util.viewer.field.ListingField; +import ghidra.app.util.viewer.listingpanel.ListingPanel; +import ghidra.lifecycle.Unfinished; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue; +import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValuePcodeArithmetic; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.program.disassemble.Disassembler; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function.FunctionUpdateType; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.scalar.Scalar; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.util.*; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.TraceLocation; +import ghidra.trace.model.breakpoint.TraceBreakpoint; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.trace.model.listing.TraceData; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.schedule.Scheduler; +import ghidra.util.Msg; +import ghidra.util.NumericUtilities; +import ghidra.util.database.UndoableTransaction; + +public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest { + + public static final AssemblySelector NO_16BIT_CALLS = new AssemblySelector() { + @Override + public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, + AssemblyPatternBlock ctx) throws AssemblySemanticException { + for (AssemblyResolvedPatterns res : filterCompatibleAndSort(rr, ctx)) { + byte[] ins = res.getInstruction().getVals(); + // HACK to avoid 16-bit CALL.... TODO: Why does this happen? + if (ins.length >= 2 && ins[0] == (byte) 0x66 && ins[1] == (byte) 0xe8) { + System.err.println( + "Filtered 16-bit call " + NumericUtilities.convertBytesToString(ins)); + continue; + } + return AssemblyResolution.resolved(res.getInstruction().fillMask(), + res.getContext(), "Selected", null, null, null); + } + throw new AssemblySemanticException(semanticErrors); + } + }; + + protected void createProgram(String languageID, String cSpecID) throws IOException { + Language language = getLanguageService().getLanguage(new LanguageID(languageID)); + CompilerSpec cSpec = cSpecID == null ? language.getDefaultCompilerSpec() + : language.getCompilerSpecByID(new CompilerSpecID(cSpecID)); + createProgram(language, cSpec); + } + + Address bodyInstr; + Address retInstr; + Address globalRefInstr; + Address stackRefInstr; + Address registerRefInstr; + Address funcInstr; + + CodeBrowserPlugin codeBrowserPlugin; + ListingPanel staticListing; + DebuggerListingPlugin listingPlugin; + ListingPanel dynamicListing; + DebuggerStateEditingService editingService; + DebuggerEmulationService emuService; + DecompilerProvider decompilerProvider; + DecompilerPanel decompilerPanel; + + protected Address stack(long offset) { + return program.getCompilerSpec().getStackSpace().getAddress(offset); + } + + protected Register register(String name) { + return program.getLanguage().getRegister(name); + } + + protected Function createSumSquaresProgramX86_32() throws Throwable { + createProgram("x86:LE:32:default", "gcc"); + intoProject(program); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + Address entry = addr(program, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + Assembler asm = Assemblers.getAssembler(program.getLanguage(), NO_16BIT_CALLS); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + buf.assemble("PUSH EBP"); + buf.assemble("MOV EBP, ESP"); + buf.assemble("SUB ESP, 0x10"); + + buf.assemble("XOR ECX,ECX"); + buf.assemble("MOV dword ptr [EBP+-4], ECX"); + buf.assemble("MOV dword ptr [EBP+-8], ECX"); + Address jumpCheck = buf.getNext(); + buf.assemble("JMP 0x" + buf.getNext()); + Address labelLoop = buf.getNext(); + buf.assemble("MOV EAX, dword ptr [EBP+-4]"); + bodyInstr = buf.getNext(); + buf.assemble("IMUL EAX, dword ptr [EBP+-4]"); + buf.assemble("ADD dword ptr [EBP+-8], EAX"); + + buf.assemble("INC dword ptr [EBP+-4]"); + Address labelCheck = buf.getNext(); + buf.assemble(jumpCheck, "JMP 0x" + labelCheck); + buf.assemble("MOV EAX, dword ptr [EBP+-4]"); + buf.assemble("CMP EAX, dword ptr [EBP+8]"); + buf.assemble("JLE 0x" + labelLoop); + + buf.assemble("MOV EAX, dword ptr [EBP+-8]"); + + buf.assemble("LEAVE"); + retInstr = buf.getNext(); + buf.assemble("RET"); + + byte[] bytes = buf.getBytes(); + program.getMemory().setBytes(entry, bytes); + + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, null); + + Function function = program.getFunctionManager() + .createFunction("sumSquares", entry, + new AddressSet(entry, entry.add(bytes.length - 1)), + SourceType.USER_DEFINED); + + function.updateFunction("__cdecl", + new ReturnParameterImpl(IntegerDataType.dataType, program), + List.of( + new ParameterImpl("n", IntegerDataType.dataType, program)), + FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS); + function.addLocalVariable( + new LocalVariableImpl("i", IntegerDataType.dataType, -8, program), + SourceType.USER_DEFINED); + function.addLocalVariable( + new LocalVariableImpl("sum", IntegerDataType.dataType, -12, program), + SourceType.USER_DEFINED); + return function; + } + } + + protected Function createFibonacciProgramX86_32() throws Throwable { + createProgram("x86:LE:32:default", "gcc"); + intoProject(program); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + Address entry = addr(program, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + Assembler asm = Assemblers.getAssembler(program.getLanguage(), NO_16BIT_CALLS); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + buf.assemble("PUSH EBP"); + buf.assemble("MOV EBP, ESP"); + + buf.assemble("CMP dword ptr [EBP+8], 1"); + Address jumpBase = buf.getNext(); + buf.assemble("JBE 0x" + buf.getNext()); + + // Recursive case. Let EDX be sum + // sum = fib(n - 1) + buf.assemble("MOV ECX, dword ptr [EBP+8]"); + buf.assemble("DEC ECX"); + buf.assemble("PUSH ECX"); // pass n - 1 + buf.assemble("CALL 0x" + entry); + buf.assemble("ADD ESP, 4"); // Clear parameters + registerRefInstr = buf.getNext(); + buf.assemble("MOV EDX, EAX"); + // sum += fib(n - 2) + buf.assemble("MOV ECX, dword ptr [EBP+8]"); + buf.assemble("SUB ECX, 2"); + buf.assemble("PUSH EDX"); // Caller Save EDX + buf.assemble("PUSH ECX"); // pass n - 2 + buf.assemble("CALL 0x" + entry); + buf.assemble("ADD ESP, 4"); // Clear parameters + buf.assemble("POP EDX"); // Restore EDX + buf.assemble("ADD EAX, EDX"); + + Address labelRet = buf.getNext(); + buf.assemble("LEAVE"); + retInstr = buf.getNext(); + buf.assemble("RET"); + + Address labelBase = buf.getNext(); + buf.assemble(jumpBase, "JBE 0x" + labelBase); + stackRefInstr = buf.getNext(); + buf.assemble("MOV EAX, dword ptr [EBP+8]"); + buf.assemble("JMP 0x" + labelRet); + + byte[] bytes = buf.getBytes(); + program.getMemory().setBytes(entry, bytes); + + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, null); + + Function function = program.getFunctionManager() + .createFunction("fib", entry, + new AddressSet(entry, entry.add(bytes.length - 1)), + SourceType.USER_DEFINED); + + function.updateFunction("__cdecl", + new ReturnParameterImpl(UnsignedIntegerDataType.dataType, program), + List.of( + new ParameterImpl("n", UnsignedIntegerDataType.dataType, program)), + FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS); + // NOTE: The decompiler doesn't actually use sum.... For some reason, it re-uses n + // Still, in the tests, I can use uVar1 (EAX) as a register variable + function.addLocalVariable( + new LocalVariableImpl("sum", 0, UnsignedIntegerDataType.dataType, register("EDX"), + program), + SourceType.USER_DEFINED); + + Instruction ins = program.getListing().getInstructionAt(stackRefInstr); + ins.addOperandReference(1, stack(4), RefType.READ, SourceType.ANALYSIS); + return function; + } + } + + protected Function createCallExternProgramX86_32() throws Throwable { + createProgram("x86:LE:32:default", "gcc"); + intoProject(program); + Address entry; + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + entry = addr(program, 0x00400000); + Address externs = addr(program, 0x00700000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + program.getMemory() + .createUninitializedBlock(MemoryBlock.EXTERNAL_BLOCK_NAME, externs, 0x10, + false); + + program.getFunctionManager() + .createFunction("myExtern", externs, new AddressSet(externs), + SourceType.USER_DEFINED); + + Assembler asm = Assemblers.getAssembler(program.getLanguage(), NO_16BIT_CALLS); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + buf.assemble("PUSH EBP"); + buf.assemble("MOV EBP, ESP"); + buf.assemble("SUB ESP, 0x10"); + + buf.assemble("XOR ECX,ECX"); + buf.assemble("MOV dword ptr [EBP+-4], ECX"); + buf.assemble("MOV dword ptr [EBP+-8], ECX"); + Address jumpCheck = buf.getNext(); + buf.assemble("JMP 0x" + buf.getNext()); + Address labelLoop = buf.getNext(); + buf.assemble("MOV EAX, dword ptr [EBP+-4]"); + bodyInstr = buf.getNext(); + buf.assemble("PUSH EAX"); + buf.assemble("CALL 0x" + externs); + buf.assemble("ADD ESP, 0x4"); + buf.assemble("ADD dword ptr [EBP+-8], EAX"); + + buf.assemble("INC dword ptr [EBP+-4]"); + Address labelCheck = buf.getNext(); + buf.assemble(jumpCheck, "JMP 0x" + labelCheck); + buf.assemble("MOV EAX, dword ptr [EBP+-4]"); + buf.assemble("CMP EAX, dword ptr [EBP+8]"); + buf.assemble("JL 0x" + labelLoop); + + buf.assemble("MOV EAX, dword ptr [EBP+-8]"); + + buf.assemble("LEAVE"); + buf.assemble("RET"); + + byte[] bytes = buf.getBytes(); + program.getMemory().setBytes(entry, bytes); + + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, null); + + return program.getFunctionManager() + .createFunction("sumReturns", entry, + new AddressSet(entry, entry.add(bytes.length - 1)), + SourceType.USER_DEFINED); + } + } + + protected Function createCallPointerProgramX86_32() throws Throwable { + createProgram("x86:LE:32:default", "gcc"); + intoProject(program); + Address entry; + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + entry = addr(program, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + + Assembler asm = Assemblers.getAssembler(program.getLanguage(), NO_16BIT_CALLS); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + buf.assemble("PUSH EBP"); + buf.assemble("MOV EBP, ESP"); + buf.assemble("SUB ESP, 0x10"); + + buf.assemble("XOR ECX,ECX"); + buf.assemble("MOV dword ptr [EBP+-4], ECX"); + buf.assemble("MOV dword ptr [EBP+-8], ECX"); + Address jumpCheck = buf.getNext(); + buf.assemble("JMP 0x" + buf.getNext()); + Address labelLoop = buf.getNext(); + buf.assemble("MOV EDX, dword ptr [EBP+-4]"); + buf.assemble("MOV EAX, dword ptr [EBP+12]"); + bodyInstr = buf.getNext(); + buf.assemble("PUSH EDX"); + buf.assemble("CALL EAX"); + buf.assemble("ADD ESP, 0x4"); + buf.assemble("ADD dword ptr [EBP+-8], EAX"); + + buf.assemble("INC dword ptr [EBP+-4]"); + Address labelCheck = buf.getNext(); + buf.assemble(jumpCheck, "JMP 0x" + labelCheck); + buf.assemble("MOV EAX, dword ptr [EBP+-4]"); + buf.assemble("CMP EAX, dword ptr [EBP+8]"); + buf.assemble("JL 0x" + labelLoop); + + buf.assemble("MOV EAX, dword ptr [EBP+-8]"); + + buf.assemble("LEAVE"); + buf.assemble("RET"); + + byte[] bytes = buf.getBytes(); + program.getMemory().setBytes(entry, bytes); + + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, null); + + return program.getFunctionManager() + .createFunction("sumReturns", entry, + new AddressSet(entry, entry.add(bytes.length - 1)), + SourceType.USER_DEFINED); + } + } + + protected Function createSetGlobalProgramX86_32() throws Throwable { + createProgram("x86:LE:32:default", "gcc"); + intoProject(program); + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + Address entry = addr(program, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + Address global = addr(program, 0x00600000); + program.getMemory() + .createInitializedBlock(".data", global, 0x1000, (byte) 0, monitor, false); + + Assembler asm = Assemblers.getAssembler(program.getLanguage(), NO_16BIT_CALLS); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + buf.assemble("MOV EAX, 0xdeadbeef"); + globalRefInstr = buf.getNext(); + buf.assemble("MOV dword ptr [0x00600000], EAX"); + retInstr = buf.getNext(); + buf.assemble("RET"); + + byte[] bytes = buf.getBytes(); + program.getMemory().setBytes(entry, bytes); + + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, null); + + program.getListing().createData(global, IntegerDataType.dataType, 4); + program.getSymbolTable().createLabel(global, "myGlobal", SourceType.USER_DEFINED); + + return program.getFunctionManager() + .createFunction("setGlobal", entry, + new AddressSet(entry, entry.add(bytes.length - 1)), + SourceType.USER_DEFINED); + } + } + + protected Function createFillStructProgramX86_32() throws Throwable { + createProgram("x86:LE:32:default", "gcc"); + intoProject(program); + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + ProgramBasedDataTypeManager dtm = program.getDataTypeManager(); + Structure structure = new StructureDataType("MyStruct", 0, dtm); + structure.add(WordDataType.dataType, "y", ""); + structure.add(ByteDataType.dataType, "m", ""); + structure.add(ByteDataType.dataType, "d", ""); + structure = + (Structure) dtm.addDataType(structure, DataTypeConflictHandler.DEFAULT_HANDLER); + + Address entry = addr(program, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + Address global = addr(program, 0x00600000); + MemoryBlock dataBlock = program.getMemory() + .createInitializedBlock(".data", global, 0x1000, (byte) 0, monitor, false); + dataBlock.setWrite(true); + + Assembler asm = Assemblers.getAssembler(program.getLanguage(), NO_16BIT_CALLS); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + buf.assemble("PUSH EBP"); + buf.assemble("MOV EBP, ESP"); + buf.assemble("SUB ESP, 4"); + buf.assemble("LEA EAX, [0x00600000]"); + buf.assemble("PUSH EAX"); + Address call1 = buf.getNext(); + buf.assemble("CALL 0x" + buf.getNext()); + buf.assemble("ADD ESP, 4"); + buf.assemble("LEA EAX, [ESP]"); + buf.assemble("PUSH EAX"); + Address call2 = buf.getNext(); + buf.assemble("CALL 0x" + buf.getNext()); + buf.assemble("ADD ESP, 4"); + buf.assemble("MOVZX EAX, word ptr [ESP]"); + buf.assemble("MOVZX EDX, byte ptr [0x00600002]"); + buf.assemble("ADD EAX, EDX"); + buf.assemble("LEAVE"); + buf.assemble("RET"); + + funcInstr = buf.getNext(); + buf.assemble(call1, "CALL 0x" + funcInstr); + buf.assemble(call2, "CALL 0x" + funcInstr); + + buf.assemble("MOV EAX, dword ptr [ESP+4]"); + buf.assemble("MOV word ptr [EAX], 2022"); + buf.assemble("MOV byte ptr [EAX+2], 12"); + buf.assemble("MOV byte ptr [EAX+3], 9"); + retInstr = buf.getNext(); + buf.assemble("RET"); + Address end = buf.getNext(); + + byte[] bytes = buf.getBytes(); + program.getMemory().setBytes(entry, bytes); + + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, null); + + program.getListing().createData(global, structure, 4); + program.getSymbolTable().createLabel(global, "myGlobal", SourceType.USER_DEFINED); + + Function funFillStruct = program.getFunctionManager() + .createFunction("fillStruct", funcInstr, + new AddressSet(funcInstr, end.previous()), SourceType.USER_DEFINED); + funFillStruct.updateFunction("__cdecl", null, + List.of( + new ParameterImpl("s", new PointerDataType(structure), program)), + FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS); + + Function main = program.getFunctionManager() + .createFunction("main", entry, new AddressSet(entry, funcInstr.previous()), + SourceType.USER_DEFINED); + main.updateFunction("__cdecl", + new ReturnParameterImpl(DWordDataType.dataType, program), + FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS); + main.addLocalVariable( + new LocalVariableImpl("myStack", structure, -8, program), + SourceType.ANALYSIS); + return main; + } + } + + protected Function createFillStructArrayProgramX86_32() throws Throwable { + createProgram("x86:LE:32:default", "gcc"); + intoProject(program); + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + ProgramBasedDataTypeManager dtm = program.getDataTypeManager(); + Structure structure = new StructureDataType("MyStruct", 0, dtm); + structure.add(WordDataType.dataType, "y", ""); + structure.add(ByteDataType.dataType, "m", ""); + structure.add(ByteDataType.dataType, "d", ""); + structure = + (Structure) dtm.addDataType(structure, DataTypeConflictHandler.DEFAULT_HANDLER); + + Address entry = addr(program, 0x00400000); + program.getMemory() + .createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + Address global = addr(program, 0x00600000); + MemoryBlock dataBlock = program.getMemory() + .createInitializedBlock(".data", global, 0x1000, (byte) 0, monitor, false); + dataBlock.setWrite(true); + + Assembler asm = Assemblers.getAssembler(program.getLanguage(), NO_16BIT_CALLS); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + buf.assemble("PUSH EBP"); + buf.assemble("MOV EBP, ESP"); + buf.assemble("PUSH 2"); + buf.assemble("LEA EAX, [0x00600000]"); + buf.assemble("PUSH EAX"); + Address call = buf.getNext(); + buf.assemble("CALL 0x" + buf.getNext()); + buf.assemble("ADD ESP, 8"); + buf.assemble("LEAVE"); + buf.assemble("RET"); + + funcInstr = buf.getNext(); + buf.assemble(call, "CALL 0x" + funcInstr); + + buf.assemble("MOV EAX, dword ptr [ESP+4]"); + buf.assemble("MOV ECX, dword ptr [ESP+8]"); + //buf.assemble("LEA EAX, [EAX+ECX*4]"); + buf.assemble("MOV word ptr [EAX+ECX*4], 2022"); + buf.assemble("MOV byte ptr [EAX+ECX*4+2], 12"); + buf.assemble("MOV byte ptr [EAX+ECX*4+3], 9"); + retInstr = buf.getNext(); + buf.assemble("RET"); + Address end = buf.getNext(); + + byte[] bytes = buf.getBytes(); + program.getMemory().setBytes(entry, bytes); + + Disassembler dis = Disassembler.getDisassembler(program, monitor, null); + dis.disassemble(entry, null); + + program.getListing() + .createData(global, + new ArrayDataType(structure, 10, 4, program.getDataTypeManager())); + program.getSymbolTable().createLabel(global, "myGlobal", SourceType.USER_DEFINED); + + Function funFillStruct = program.getFunctionManager() + .createFunction("fillStruct", funcInstr, + new AddressSet(funcInstr, end.previous()), SourceType.USER_DEFINED); + funFillStruct.updateFunction("__cdecl", null, + List.of( + new ParameterImpl("s", new PointerDataType(structure), program), + new ParameterImpl("i", IntegerDataType.dataType, program)), + FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS); + + Function main = program.getFunctionManager() + .createFunction("main", entry, new AddressSet(entry, funcInstr.previous()), + SourceType.USER_DEFINED); + main.updateFunction("__cdecl", + new ReturnParameterImpl(DWordDataType.dataType, program), + FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS); + return main; + } + } + + @Test + public void testComputeUnwindInfoX86_32() throws Throwable { + addPlugin(tool, CodeBrowserPlugin.class); + + Function function = createSumSquaresProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + UnwindAnalysis ua = new UnwindAnalysis(program); + + UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor); + assertEquals( + new UnwindInfo(function, 0, 4, stack(0), Map.of(), new StackUnwindWarningSet()), + infoAtEntry); + + UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor); + assertEquals(new UnwindInfo(function, -20, 4, stack(0), + Map.of( + register("EBP"), stack(-4)), + new StackUnwindWarningSet()), + infoAtBody); + } + + @Test + public void testComputeUnwindInfoWithExternCallsX86_32() throws Throwable { + addPlugin(tool, CodeBrowserPlugin.class); + + Function function = createCallExternProgramX86_32(); + Address entry = function.getEntryPoint(); + Function myExtern = + (Function) Unique.assertOne(program.getSymbolTable().getSymbols("myExtern")) + .getObject(); + + programManager.openProgram(program); + + UnwindAnalysis ua = new UnwindAnalysis(program); + + UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor); + assertEquals( + new UnwindInfo(function, 0, 4, stack(0), Map.of(), new StackUnwindWarningSet()), + infoAtEntry); + + UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor); + assertEquals(new UnwindInfo(function, -20, 4, stack(0), + Map.of( + register("EBP"), stack(-4)), + new StackUnwindWarningSet( + new UnspecifiedConventionStackUnwindWarning(myExtern), + new UnknownPurgeStackUnwindWarning(myExtern))), + infoAtBody); + } + + @Test + public void testComputeUnwindInfoWithIndirectCallsX86_32() throws Throwable { + addPlugin(tool, CodeBrowserPlugin.class); + addPlugin(tool, DecompilePlugin.class); + + Function function = createCallPointerProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + UnwindAnalysis ua = new UnwindAnalysis(program); + + UnwindInfo infoAtEntry = ua.computeUnwindInfo(entry, monitor); + assertEquals( + new UnwindInfo(function, 0, 4, stack(0), Map.of(), new StackUnwindWarningSet()), + infoAtEntry); + + UnwindInfo infoAtBody = ua.computeUnwindInfo(bodyInstr, monitor); + DataType ptr2Undef = new PointerDataType(DataType.DEFAULT, program.getDataTypeManager()); + assertEquals(new UnwindInfo(function, -20, 4, stack(0), + Map.of( + register("EBP"), stack(-4)), + new StackUnwindWarningSet( + new UnexpectedTargetTypeStackUnwindWarning(ptr2Undef))), + infoAtBody); + } + + @Test + public void testUnwindTopFrameX86_32() throws Throwable { + addPlugin(tool, CodeBrowserPlugin.class); + addPlugin(tool, DebuggerListingPlugin.class); + addPlugin(tool, DisassemblerPlugin.class); + addPlugin(tool, DecompilePlugin.class); + DebuggerStateEditingService editingService = + addPlugin(tool, DebuggerStateEditingServicePlugin.class); + DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + + Function function = createSumSquaresProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + useTrace(ProgramEmulationUtils.launchEmulationTrace(program, entry, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + StateEditor editor = editingService.createStateEditor(tb.trace); + + DebuggerCoordinates atSetup = traceManager.getCurrent(); + StackUnwinder unwinder = new StackUnwinder(tool, atSetup.getPlatform()); + AnalysisUnwoundFrame frameAtSetup = unwinder.start(atSetup, monitor); + + Parameter param1 = function.getParameter(0); + waitOn(frameAtSetup.setValue(editor, param1, BigInteger.valueOf(4))); + waitForTasks(); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getBreakpointManager() + .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr, Set.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "capture return value"); + } + + EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor, + Scheduler.oneThread(thread)); + Msg.debug(this, "Broke after " + result.schedule()); + + traceManager.activateTime(result.schedule()); + waitForTasks(); + DebuggerCoordinates after = traceManager.getCurrent(); + AnalysisUnwoundFrame frameAfter = unwinder.start(after, monitor); + + WatchValuePcodeArithmetic wa = + WatchValuePcodeArithmetic.forLanguage(after.getPlatform().getLanguage()); + for (Variable variable : function.getVariables(null)) { + BigInteger value = wa.toBigInteger(frameAfter.getValue(variable), Purpose.INSPECT); + Msg.debug(this, variable + " = " + value); + } + Variable retVar = function.getReturn(); + BigInteger retVal = wa.toBigInteger(frameAfter.getValue(retVar), Purpose.INSPECT); + Msg.debug(this, "Return " + retVal); + assertEquals(BigInteger.valueOf(30), retVal); + } + + @Test + public void testUnwindRecursiveX86_32() throws Throwable { + addPlugin(tool, CodeBrowserPlugin.class); + addPlugin(tool, DebuggerListingPlugin.class); + addPlugin(tool, DisassemblerPlugin.class); + addPlugin(tool, DecompilePlugin.class); + DebuggerStateEditingService editingService = + addPlugin(tool, DebuggerStateEditingServicePlugin.class); + DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + + Function function = createFibonacciProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + useTrace(ProgramEmulationUtils.launchEmulationTrace(program, entry, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + StateEditor editor = editingService.createStateEditor(tb.trace); + + DebuggerCoordinates atSetup = traceManager.getCurrent(); + StackUnwinder unwinder = new StackUnwinder(tool, atSetup.getPlatform()); + AnalysisUnwoundFrame frameAtSetup = unwinder.start(atSetup, monitor); + + Parameter param1 = function.getParameter(0); + waitOn(frameAtSetup.setValue(editor, param1, BigInteger.valueOf(9))); + waitOn(frameAtSetup.setReturnAddress(editor, tb.addr(0xdeadbeef))); + waitForTasks(); + + TraceBreakpoint bptUnwind; + try (UndoableTransaction tid = tb.startTransaction()) { + bptUnwind = tb.trace.getBreakpointManager() + .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr, + Set.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack"); + tb.trace.getBreakpointManager() + .addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), tb.addr(0xdeadbeef), + Set.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "capture return value"); + } + + EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor, + Scheduler.oneThread(thread)); + Msg.debug(this, "Broke after " + result.schedule()); + + traceManager.activateTime(result.schedule()); + waitForTasks(); + DebuggerCoordinates tallest = traceManager.getCurrent(); + AnalysisUnwoundFrame frameTallest = unwinder.start(tallest, monitor); + while (true) { + Msg.debug(this, "Frame " + frameTallest.getLevel()); + for (Variable variable : function.getVariables(null)) { + try { + WatchValue value = frameTallest.getValue(variable); + Msg.debug(this, " " + variable + " = " + value); + } + catch (UnwindException e) { + Msg.debug(this, " Cannot get " + variable + ": " + e); + } + } + try { + frameTallest = frameTallest.unwindNext(monitor); + } + catch (NoSuchElementException e) { + break; + } + } + + try (UndoableTransaction tid = tb.startTransaction()) { + bptUnwind.delete(); + } + + result = emuService.run(tallest.getPlatform(), tallest.getTime(), monitor, + Scheduler.oneThread(thread)); + + // Step back, so PC is in the function + traceManager.activateTime(result.schedule().steppedBackward(tb.trace, 1)); + waitForTasks(); + DebuggerCoordinates after = traceManager.getCurrent(); + AnalysisUnwoundFrame frameAfter = unwinder.start(after, monitor); + + for (Variable variable : function.getVariables(null)) { + WatchValue value = frameAfter.getValue(variable); + Msg.debug(this, variable + " = " + value); + } + Variable retVar = function.getReturn(); + WatchValue retVal = frameAfter.getValue(retVar); + Msg.debug(this, "Return " + retVal); + assertEquals(BigInteger.valueOf(34), retVal.toBigInteger(false)); + } + + @Test + public void testCreateFramesAtEntryX86_32() throws Throwable { + addPlugin(tool, CodeBrowserPlugin.class); + addPlugin(tool, DebuggerListingPlugin.class); + addPlugin(tool, DisassemblerPlugin.class); + addPlugin(tool, DecompilePlugin.class); + + Function function = createFibonacciProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + useTrace(ProgramEmulationUtils.launchEmulationTrace(program, entry, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + DebuggerCoordinates atSetup = traceManager.getCurrent(); + try (UndoableTransaction tid = tb.startTransaction()) { + new UnwindStackCommand(tool, atSetup).applyTo(tb.trace, monitor); + } + waitForDomainObject(tb.trace); + + ListingUnwoundFrame frame = VariableValueUtils.locateFrame(tool, atSetup, function); + TraceData data = frame.getData(); + assertEquals(8, data.getLength()); + assertEquals(2, data.getNumComponents()); + assertEquals("return_address", data.getComponent(0).getFieldName()); + assertEquals("param_n", data.getComponent(1).getFieldName()); + + assertNull(VariableValueUtils.locateFrame(tool, atSetup.frame(1), function)); + } + + protected static void assertField(Data data, String name, Object value) { + assertEquals(name, data.getFieldName()); + assertEquals(value, data.getValue()); + } + + protected void addPlugins() throws Throwable { + codeBrowserPlugin = addPlugin(tool, CodeBrowserPlugin.class); + staticListing = codeBrowserPlugin.getProvider().getListingPanel(); + listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); + dynamicListing = listingPlugin.getProvider().getListingPanel(); + addPlugin(tool, DisassemblerPlugin.class); + addPlugin(tool, DecompilePlugin.class); + editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class); + emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + + decompilerProvider = waitForComponentProvider(DecompilerProvider.class); + decompilerPanel = decompilerProvider.getDecompilerPanel(); + } + + protected Function runToTallestRecursionAndCreateFrames(int n) throws Throwable { + addPlugins(); + + Function function = createFibonacciProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + useTrace(ProgramEmulationUtils.launchEmulationTrace(program, entry, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + StateEditor editor = editingService.createStateEditor(tb.trace); + + DebuggerCoordinates atSetup = traceManager.getCurrent(); + StackUnwinder unwinder = new StackUnwinder(tool, atSetup.getPlatform()); + AnalysisUnwoundFrame frameAtSetup = unwinder.start(atSetup, monitor); + + Parameter paramN = function.getParameter(0); + assertEquals("n", paramN.getName()); // Sanity + waitOn(frameAtSetup.setValue(editor, paramN, BigInteger.valueOf(n))); + waitOn(frameAtSetup.setReturnAddress(editor, tb.addr(0xdeadbeef))); + waitForTasks(); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getBreakpointManager() + .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr, + Set.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack"); + } + + EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor, + Scheduler.oneThread(thread)); + Msg.debug(this, "Broke after " + result.schedule()); + + traceManager.activateTime(result.schedule()); + waitForTasks(); + DebuggerCoordinates tallest = traceManager.getCurrent(); + try (UndoableTransaction tid = tb.startTransaction()) { + new UnwindStackCommand(tool, tallest).applyTo(tb.trace, monitor); + } + waitForDomainObject(tb.trace); + + return function; + } + + protected Function runToRetSetGlobalAndCreateFrames() throws Throwable { + addPlugins(); + + Function function = createSetGlobalProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + useTrace(ProgramEmulationUtils.launchEmulationTrace(program, entry, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + StateEditor editor = editingService.createStateEditor(tb.trace); + // Move stack where it shows in UI. Not required, but nice for debugging. + Register sp = program.getCompilerSpec().getStackPointer(); + waitOn(editor.setRegister(new RegisterValue(sp, BigInteger.valueOf(0x4ff0)))); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getBreakpointManager() + .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr, + Set.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack"); + } + + DebuggerCoordinates atSetup = traceManager.getCurrent(); + EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor, + Scheduler.oneThread(thread)); + Msg.debug(this, "Broke after " + result.schedule()); + + traceManager.activateTime(result.schedule()); + waitForTasks(); + DebuggerCoordinates atRet = traceManager.getCurrent(); + try (UndoableTransaction tid = tb.startTransaction()) { + new UnwindStackCommand(tool, atRet).applyTo(tb.trace, monitor); + } + waitForDomainObject(tb.trace); + + return function; + } + + protected Function runToRetFillStructAndCreateFrames() throws Throwable { + addPlugins(); + + Function function = createFillStructProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + useTrace(ProgramEmulationUtils.launchEmulationTrace(program, entry, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + StateEditor editor = editingService.createStateEditor(tb.trace); + // Move stack where it shows in UI. Not required, but nice for debugging. + Register sp = program.getCompilerSpec().getStackPointer(); + waitOn(editor.setRegister(new RegisterValue(sp, BigInteger.valueOf(0x4ff0)))); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getBreakpointManager() + .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr, + Set.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack"); + } + + DebuggerCoordinates atSetup = traceManager.getCurrent(); + EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor, + Scheduler.oneThread(thread)); + Msg.debug(this, "Broke after " + result.schedule()); + + traceManager.activateTime(result.schedule()); + waitForTasks(); + DebuggerCoordinates atRet = traceManager.getCurrent(); + try (UndoableTransaction tid = tb.startTransaction()) { + new UnwindStackCommand(tool, atRet).applyTo(tb.trace, monitor); + } + waitForDomainObject(tb.trace); + + return function; + } + + protected Function runToRetFillStructArrayAndCreateFrames() throws Throwable { + addPlugins(); + + Function function = createFillStructArrayProgramX86_32(); + Address entry = function.getEntryPoint(); + + programManager.openProgram(program); + + useTrace(ProgramEmulationUtils.launchEmulationTrace(program, entry, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + StateEditor editor = editingService.createStateEditor(tb.trace); + // Move stack where it shows in UI. Not required, but nice for debugging. + Register sp = program.getCompilerSpec().getStackPointer(); + waitOn(editor.setRegister(new RegisterValue(sp, BigInteger.valueOf(0x4ff0)))); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getBreakpointManager() + .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr, + Set.of(), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack"); + } + + DebuggerCoordinates atSetup = traceManager.getCurrent(); + EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor, + Scheduler.oneThread(thread)); + Msg.debug(this, "Broke after " + result.schedule()); + + traceManager.activateTime(result.schedule()); + waitForTasks(); + DebuggerCoordinates atRet = traceManager.getCurrent(); + try (UndoableTransaction tid = tb.startTransaction()) { + new UnwindStackCommand(tool, atRet).applyTo(tb.trace, monitor); + } + waitForDomainObject(tb.trace); + + return function; + } + + @Test + public void testCreateFramesTallestX86_32() throws Throwable { + Function function = runToTallestRecursionAndCreateFrames(9); + DebuggerCoordinates tallest = traceManager.getCurrent(); + + ListingUnwoundFrame frame; + TraceData data; + + frame = VariableValueUtils.locateFrame(tool, tallest, function); + data = frame.getData(); + assertEquals(8, data.getLength()); + assertEquals(2, data.getNumComponents()); + assertEquals(tb.addr(0x40002c), frame.getProgramCounter()); + assertEquals(tb.addr(0x4fa0), frame.getBasePointer()); + // Saved EBP has already been popped by LEAVE + assertField(data.getComponent(0), "return_address", tb.addr(0x400013)); + assertField(data.getComponent(1), "param_n", new Scalar(32, 1)); + + frame = VariableValueUtils.locateFrame(tool, tallest.frame(1), function); + data = frame.getData(); + assertEquals(12, data.getLength()); + assertEquals(3, data.getNumComponents()); + assertEquals(tb.addr(0x400013), frame.getProgramCounter()); + assertEquals(tb.addr(0x4fac), frame.getBasePointer()); + assertField(data.getComponent(0), "saved_EBP", new Scalar(32, 0x4fb4)); + assertField(data.getComponent(1), "return_address", tb.addr(0x400013)); + assertField(data.getComponent(2), "param_n", new Scalar(32, 2)); + + assertNotNull(VariableValueUtils.locateFrame(tool, tallest.frame(8), function)); + assertNull(VariableValueUtils.locateFrame(tool, tallest.frame(9), function)); + } + + public record HoverLocation(ProgramLocation pLoc, FieldLocation fLoc, Field field, + ClangToken token) { + } + + public static HoverLocation findLocation(ListingPanel panel, + Address address, Class locType, Predicate predicate) { + Layout layout = panel.getLayout(address); + int numFields = layout.getNumFields(); + for (int i = 0; i < numFields; i++) { + Field field = layout.getField(i); + if (!(field instanceof ListingField listingField)) { + continue; + } + FieldFactory factory = listingField.getFieldFactory(); + int numRows = field.getNumRows(); + for (int r = 0; r < numRows; r++) { + int numCols = field.getNumCols(r); + for (int c = 0; c < numCols; c++) { + ProgramLocation loc = factory.getProgramLocation(r, c, listingField); + if (!locType.isInstance(loc)) { + continue; + } + if (!predicate.test(locType.cast(loc))) { + continue; + } + return new HoverLocation(loc, new FieldLocation(0, i, r, c), field, null); + } + } + } + return null; + } + + public static HoverLocation findVariableLocation(ListingPanel panel, Function function, + String name) { + return findLocation(panel, function.getEntryPoint(), VariableLocation.class, + varLoc -> name.equals(varLoc.getVariable().getName())); + } + + public static HoverLocation findOperandLocation(ListingPanel panel, Instruction ins, + Object operand) { + return findLocation(panel, ins.getAddress(), OperandFieldLocation.class, opLoc -> { + int subIdx = opLoc.getSubOperandIndex(); + if (subIdx == -1) { + return false; + } + return operand.equals( + ins.getDefaultOperandRepresentationList(opLoc.getOperandIndex()).get(subIdx)); + }); + } + + protected static void assertTable(Map texts, VariableValueTable table) { + ErrorRow error = (ErrorRow) table.get(RowKey.ERROR); + if (error != null && !texts.containsKey(RowKey.ERROR)) { + throw new AssertionError("ErrorRow present", error.error()); + } + for (Map.Entry ent : texts.entrySet()) { + RowKey key = ent.getKey(); + VariableValueRow row = table.get(key); + assertNotNull("Missing " + key, row); + if (key != RowKey.WARNINGS) { + assertEquals(ent.getValue(), row.toSimpleString()); + } + } + assertEquals(texts.size(), table.getNumRows()); + } + + protected VariableValueTable getVariableValueTable(VariableValueHoverService valuesService, + ProgramLocation programLocation, DebuggerCoordinates current, + FieldLocation fieldLocation, Field field) throws Throwable { + VariableValueTable table = new VariableValueTable(); + List warnings = new ArrayList<>(); + waitOn(valuesService.fillVariableValueTable(table, programLocation, current, + fieldLocation, field, warnings)); + table.add(new WarningsRow(warnings)); + return table; + } + + @Test + public void testStackVariableHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToTallestRecursionAndCreateFrames(2); + HoverLocation loc = findVariableLocation(staticListing, function, "n"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: n", + RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", + RowKey.STORAGE, "Storage: Stack[0x4]:4", + RowKey.TYPE, "Type: uint", + RowKey.LOCATION, "Location: 00004ff8:4", + RowKey.BYTES, "Bytes: (KNOWN) 01 00 00 00", + RowKey.INTEGER, "Integer: (KNOWN) 1", + RowKey.VALUE, "Value: (KNOWN) 1h", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testRegisterVariableHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToTallestRecursionAndCreateFrames(2); + HoverLocation loc = findVariableLocation(staticListing, function, "sum"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: sum", + RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", + RowKey.STORAGE, "Storage: EDX:4", + RowKey.TYPE, "Type: uint", + RowKey.LOCATION, "Location: EDX:4", + RowKey.INTEGER, "Integer: (UNKNOWN) 0", + RowKey.VALUE, "Value: (UNKNOWN) 0h", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testReturnParameterHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToTallestRecursionAndCreateFrames(2); + HoverLocation loc = findVariableLocation(staticListing, function, ""); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: ", + RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", + RowKey.STORAGE, "Storage: EAX:4", + RowKey.TYPE, "Type: uint", + RowKey.LOCATION, "Location: EAX:4", + RowKey.INTEGER, "Integer: (KNOWN) 1", + RowKey.VALUE, "Value: (KNOWN) 1h", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testGlobalOperandHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + runToRetSetGlobalAndCreateFrames(); + Instruction ins = program.getListing().getInstructionAt(globalRefInstr); + HoverLocation loc = findOperandLocation(staticListing, ins, addr(program, 0x00600000)); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: myGlobal", + RowKey.STORAGE, "Storage: 00600000:4", + RowKey.TYPE, "Type: int", + RowKey.LOCATION, "Location: 00600000:4", + RowKey.BYTES, "Bytes: (KNOWN) ef be ad de", + RowKey.INTEGER, """ + Integer: (KNOWN) 3735928559, 0xdeadbeef + -559038737, -0x21524111""", + RowKey.VALUE, "Value: (KNOWN) DEADBEEFh", + RowKey.WARNINGS, "IGNORED"), table); + } + + protected Instruction copyToDynamic(Instruction stIns) throws Throwable { + DebuggerStaticMappingService mappingService = + tool.getService(DebuggerStaticMappingService.class); + DebuggerCoordinates current = traceManager.getCurrent(); + TraceLocation dynLoc = mappingService.getOpenMappedLocation(tb.trace, + new ProgramLocation(program, stIns.getAddress()), current.getSnap()); + Address dynamicAddress = dynLoc.getAddress(); + try (UndoableTransaction tid = tb.startTransaction()) { + int length = stIns.getLength(); + assertEquals(length, tb.trace.getMemoryManager() + .putBytes(current.getSnap(), dynamicAddress, + ByteBuffer.wrap(stIns.getBytes()))); + new TraceDisassembleCommand(current.getPlatform(), dynamicAddress, + new AddressSet(dynamicAddress, dynamicAddress.add(length - 1))) + .applyToTyped(current.getView(), monitor); + } + waitForDomainObject(tb.trace); + return Objects.requireNonNull(tb.trace.getCodeManager() + .instructions() + .getAt(current.getViewSnap(), dynamicAddress)); + } + + @Test + public void testGlobalOperandInTraceHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + runToRetSetGlobalAndCreateFrames(); + Instruction ins = copyToDynamic(program.getListing().getInstructionAt(globalRefInstr)); + // I guess the listing needs a moment??? + HoverLocation loc = + waitForValue(() -> findOperandLocation(dynamicListing, ins, addr(program, 0x00600000))); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: myGlobal", + RowKey.STORAGE, "Storage: 00600000:4", + RowKey.TYPE, "Type: int", + RowKey.LOCATION, "Location: 00600000:4", + RowKey.BYTES, "Bytes: (KNOWN) ef be ad de", + RowKey.INTEGER, """ + Integer: (KNOWN) 3735928559, 0xdeadbeef + -559038737, -0x21524111""", + RowKey.VALUE, "Value: (KNOWN) DEADBEEFh", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testStackReferenceHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + runToTallestRecursionAndCreateFrames(2); + Instruction ins = program.getListing().getInstructionAt(stackRefInstr); + HoverLocation loc = findOperandLocation(staticListing, ins, new Scalar(32, 8)); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: n", + RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", + RowKey.STORAGE, "Storage: Stack[0x4]:4", + RowKey.TYPE, "Type: uint", + RowKey.LOCATION, "Location: 00004ff8:4", + RowKey.BYTES, "Bytes: (KNOWN) 01 00 00 00", + RowKey.INTEGER, "Integer: (KNOWN) 1", + RowKey.VALUE, "Value: (KNOWN) 1h", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testRegisterReferenceHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + runToTallestRecursionAndCreateFrames(2); + Instruction ins = program.getListing().getInstructionAt(registerRefInstr); + HoverLocation loc = findOperandLocation(staticListing, ins, register("EDX")); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: sum", + RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", + RowKey.STORAGE, "Storage: EDX:4", + RowKey.TYPE, "Type: uint", + RowKey.LOCATION, "Location: EDX:4", + RowKey.INTEGER, "Integer: (UNKNOWN) 0", + RowKey.VALUE, "Value: (UNKNOWN) 0h", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testSavedRegisterReferenceHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + // need 3 frames. 0 has already popped EBP, so not saved. 1 will save on behalf of 2. + Function function = runToTallestRecursionAndCreateFrames(3); + traceManager.activateFrame(2); + + Instruction ins = program.getListing().getInstructionAt(function.getEntryPoint()); + HoverLocation loc = findOperandLocation(staticListing, ins, register("EBP")); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: EBP", + RowKey.FRAME, "Frame: 2 fib pc=00400013 sp=00004ff8 base=00005000", + RowKey.LOCATION, "Location: 00004ff0:4", + RowKey.INTEGER, "Integer: (KNOWN) 20476, 0x4ffc", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testRegisterReferenceInTraceHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + runToTallestRecursionAndCreateFrames(2); + Instruction ins = copyToDynamic(program.getListing().getInstructionAt(registerRefInstr)); + // I guess the listing needs a moment??? + HoverLocation loc = + waitForValue(() -> findOperandLocation(dynamicListing, ins, register("EDX"))); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: EDX", + RowKey.INTEGER, "Integer: (UNKNOWN) 0", + RowKey.WARNINGS, "IGNORED"), table); + } + + public static HoverLocation findTokenLocation(DecompilerPanel decompilerPanel, + Function function, String tokText, String fieldText) { + DecompileResults results = waitForValue(() -> { + ProgramLocation pLoc = decompilerPanel.getCurrentLocation(); + if (!(pLoc instanceof DecompilerLocation dLoc)) { + return null; + } + DecompileResults dr = dLoc.getDecompile(); + if (dr == null || dr.getFunction() != function) { + return null; + } + return dr; + }); + + return runSwing(() -> { + Program program = function.getProgram(); + ClangLayoutController layoutController = decompilerPanel.getLayoutController(); + BigInteger numIndexes = layoutController.getNumIndexes(); + for (BigInteger i = BigInteger.ZERO; i.compareTo(numIndexes) < 0; i = + i.add(BigInteger.ONE)) { + Layout layout = layoutController.getLayout(i); + int numFields = layout.getNumFields(); + for (int j = 0; j < numFields; j++) { + Field field = layout.getField(j); + if (!(field instanceof ClangTextField clangField)) { + continue; + } + if (!fieldText.equals(field.getText())) { + continue; + } + int numRows = field.getNumRows(); + for (int r = 0; r < numRows; r++) { + int numCols = field.getNumCols(r); + for (int c = 0; c < numCols; c++) { + FieldLocation fLoc = new FieldLocation(i, j, r, c); + ClangToken token = clangField.getToken(fLoc); + if (token != null && tokText.equals(token.getText())) { + DecompilerLocation loc = token.getMinAddress() == null ? null + : new DecompilerLocation(program, token.getMinAddress(), + function.getEntryPoint(), results, token, i.intValue(), + 0); + return new HoverLocation(loc, fLoc, field, token); + } + } + } + } + } + return null; + }); + } + + protected HoverLocation findTokenLocation(Function function, String tokText, String fieldText) { + tool.showComponentProvider(decompilerProvider, true); + return findTokenLocation(decompilerPanel, function, tokText, fieldText); + } + + @Test + public void testGlobalHighVarHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToRetSetGlobalAndCreateFrames(); + HoverLocation loc = findTokenLocation(function, "myGlobal", "myGlobal = -0x21524111;"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: myGlobal", + RowKey.STORAGE, "Storage: 00600000:4", + RowKey.TYPE, "Type: int", + RowKey.LOCATION, "Location: 00600000:4", + RowKey.BYTES, "Bytes: (KNOWN) ef be ad de", + RowKey.INTEGER, """ + Integer: (KNOWN) 3735928559, 0xdeadbeef + -559038737, -0x21524111""", + RowKey.VALUE, "Value: (KNOWN) DEADBEEFh", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testStackHighVarHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToTallestRecursionAndCreateFrames(2); + HoverLocation loc = findTokenLocation(function, "n", "if (1 < n) {"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: n", + RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", + RowKey.STORAGE, "Storage: Stack[0x4]:4", + RowKey.TYPE, "Type: uint", + RowKey.LOCATION, "Location: 00004ff8:4", + RowKey.BYTES, "Bytes: (KNOWN) 01 00 00 00", + RowKey.INTEGER, "Integer: (KNOWN) 1", + RowKey.VALUE, "Value: (KNOWN) 1h", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testRegisterHighVarHover() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToTallestRecursionAndCreateFrames(2); + // TODO: Line matching seems fragile + HoverLocation loc = findTokenLocation(function, "uVar1", "uVar1 = fib(n - 1);"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: uVar1", + RowKey.FRAME, "Frame: 0 fib pc=0040002c sp=00004ff4 base=00004ff4", + RowKey.STORAGE, "Storage: EAX:4", + RowKey.TYPE, "Type: uint", + RowKey.LOCATION, "Location: EAX:4", + RowKey.INTEGER, "Integer: (KNOWN) 1", + RowKey.VALUE, "Value: (KNOWN) 1h", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testStructureGlobalHighVarStruct() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToRetFillStructAndCreateFrames(); + goTo(staticListing, new ProgramLocation(program, function.getEntryPoint())); + HoverLocation loc = + findTokenLocation(function, "myGlobal", "return (uint)myStack.y + (uint)myGlobal.m;"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: myGlobal", + RowKey.STORAGE, "Storage: 00600000:4", + RowKey.TYPE, "Type: MyStruct", + RowKey.LOCATION, "Location: 00600000:4", + RowKey.BYTES, "Bytes: (KNOWN) e6 07 0c 09", + RowKey.INTEGER, "Integer: (KNOWN) 151783398, 0x90c07e6", + RowKey.VALUE, "Value: (KNOWN) ", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testStructureGlobalHighVarStructField() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToRetFillStructAndCreateFrames(); + goTo(staticListing, new ProgramLocation(program, function.getEntryPoint())); + HoverLocation loc = + findTokenLocation(function, "m", "return (uint)myStack.y + (uint)myGlobal.m;"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: m", + RowKey.TYPE, "Type: byte", + RowKey.LOCATION, "Location: 00600002:1", + RowKey.BYTES, "Bytes: (KNOWN) 0c", + RowKey.INTEGER, "Integer: (KNOWN) 12, 0xc", + RowKey.VALUE, "Value: (KNOWN) Ch", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testStructureStackHighVarStruct() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToRetFillStructAndCreateFrames(); + goTo(staticListing, new ProgramLocation(program, function.getEntryPoint())); + HoverLocation loc = + findTokenLocation(function, "myStack", "return (uint)myStack.y + (uint)myGlobal.m;"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: myStack", + RowKey.FRAME, "Frame: 1 main pc=00400012 sp=00004fe4 base=00004ff0", + RowKey.STORAGE, "Storage: Stack[-0x8]:4", + RowKey.TYPE, "Type: MyStruct", + RowKey.LOCATION, "Location: 00004fe8:4", + RowKey.BYTES, "Bytes: (UNKNOWN) 00 00 00 00", + RowKey.INTEGER, "Integer: (UNKNOWN) 0", + RowKey.VALUE, "Value: (UNKNOWN) ", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testStructureStackHighVarStructField() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + Function function = runToRetFillStructAndCreateFrames(); + goTo(staticListing, new ProgramLocation(program, function.getEntryPoint())); + HoverLocation loc = + findTokenLocation(function, "y", "return (uint)myStack.y + (uint)myGlobal.m;"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: y", + RowKey.FRAME, "Frame: 1 main pc=00400012 sp=00004fe4 base=00004ff0", + RowKey.TYPE, "Type: word", + RowKey.LOCATION, "Location: 00004fe8:2", + RowKey.BYTES, "Bytes: (UNKNOWN) 00 00", + RowKey.INTEGER, "Integer: (UNKNOWN) 0", + RowKey.VALUE, "Value: (UNKNOWN) 0h", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testStructurePointerRegisterHighVarStruct() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + runToRetFillStructAndCreateFrames(); + Function function = program.getFunctionManager().getFunctionContaining(retInstr); + goTo(staticListing, new ProgramLocation(program, retInstr)); + HoverLocation loc = findTokenLocation(function, "s", "s->y = 0x7e6;"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: s", + RowKey.FRAME, "Frame: 0 fillStruct pc=00400041 sp=00004fe0 base=00004fe0", + RowKey.STORAGE, "Storage: Stack[0x4]:4", + RowKey.TYPE, "Type: MyStruct *", + RowKey.LOCATION, "Location: 00004fe4:4", + // NOTE: Value is the pointer, not the struct + RowKey.BYTES, "Bytes: (KNOWN) 00 00 60 00", + RowKey.INTEGER, "Integer: (KNOWN) 6291456, 0x600000", + RowKey.VALUE, "Value: (KNOWN) 00600000", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testStructurePointerRegisterHighVarStructField() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + runToRetFillStructAndCreateFrames(); + Function function = program.getFunctionManager().getFunctionContaining(retInstr); + goTo(staticListing, new ProgramLocation(program, retInstr)); + HoverLocation loc = findTokenLocation(function, "y", "s->y = 0x7e6;"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: y", + RowKey.FRAME, "Frame: 0 fillStruct pc=00400041 sp=00004fe0 base=00004fe0", + RowKey.TYPE, "Type: word", + RowKey.LOCATION, "Location: 00600000:2", + RowKey.BYTES, "Bytes: (KNOWN) e6 07", + RowKey.INTEGER, "Integer: (KNOWN) 2022, 0x7e6", + RowKey.VALUE, "Value: (KNOWN) 7E6h", + RowKey.WARNINGS, "IGNORED"), table); + } + + @Test + public void testArrayGlobalHighVarIndexedField() throws Throwable { + VariableValueHoverPlugin valuesPlugin = addPlugin(tool, VariableValueHoverPlugin.class); + VariableValueHoverService valuesService = valuesPlugin.getHoverService(); + runToRetFillStructArrayAndCreateFrames(); + Function function = program.getFunctionManager().getFunctionContaining(retInstr); + goTo(staticListing, new ProgramLocation(program, retInstr)); + HoverLocation loc = findTokenLocation(function, "m", "s[i].m = 0xc;"); + VariableValueTable table = getVariableValueTable(valuesService, loc.pLoc, + traceManager.getCurrent(), loc.fLoc, loc.field); + + assertTable(Map.of( + RowKey.NAME, "Name: m", + RowKey.FRAME, "Frame: 0 fillStruct pc=0040002e sp=00004fe0 base=00004fe0", + RowKey.TYPE, "Type: byte", + RowKey.LOCATION, "Location: 0060000a:1", + RowKey.BYTES, "Bytes: (KNOWN) 0c", + RowKey.INTEGER, "Integer: (KNOWN) 12, 0xc", + RowKey.VALUE, "Value: (KNOWN) Ch", + RowKey.WARNINGS, "IGNORED"), table); + } + + // @Test + /** + * e.g., dstack._12_4_ + */ + public void testOffcutPieceReference() throws Throwable { + Unfinished.TODO(); + } + + // @Test + public void testMultiVarnodeStorage() { + Unfinished.TODO(); + } + + // @Test + public void testWithPositiveGrowingStack() { + Unfinished.TODO(); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java index b376d84577..37432fa331 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -15,7 +15,10 @@ */ package ghidra.pcode.exec.trace; +import java.util.Map; + import generic.ULongSpan.ULongSpanSet; +import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; @@ -38,6 +41,17 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiec super(language, space, backing); } + protected CheckedCachedSpace(Language language, AddressSpace space, + PcodeTraceDataAccess backing, SemisparseByteArray bytes, AddressSet written) { + super(language, space, backing, bytes, written); + } + + @Override + public CachedSpace fork() { + return new CheckedCachedSpace(language, space, backing, bytes.fork(), + new AddressSet(written)); + } + @Override public byte[] read(long offset, int size, Reason reason) { ULongSpanSet uninitialized = @@ -59,14 +73,34 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiec super(data); } + protected AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data, + AbstractSpaceMap spaceMap) { + super(data, spaceMap); + } + + protected class CheckedCachedSpaceMap extends TraceBackedSpaceMap { + public CheckedCachedSpaceMap() { + super(); + } + + protected CheckedCachedSpaceMap(Map spaces) { + super(spaces); + } + + @Override + protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) { + return new CheckedCachedSpace(language, space, backing); + } + + @Override + public CheckedCachedSpaceMap fork() { + return new CheckedCachedSpaceMap(fork(spaces)); + } + } + @Override protected AbstractSpaceMap newSpaceMap() { - return new TraceBackedSpaceMap() { - @Override - protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) { - return new CheckedCachedSpace(language, space, backing); - } - }; + return new CheckedCachedSpaceMap(); } /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java index 415e580eb1..e06e81ee5e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java @@ -15,8 +15,7 @@ */ package ghidra.pcode.exec.trace; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import javax.help.UnsupportedOperationException; @@ -24,6 +23,7 @@ import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; /** @@ -43,7 +43,7 @@ public class AddressesReadTracePcodeExecutorStatePiece implements TracePcodeExecutorStatePiece { protected final PcodeTraceDataAccess data; - private final Map unique = new HashMap<>(); + private final Map unique; /** * Construct the state piece @@ -54,6 +54,15 @@ public class AddressesReadTracePcodeExecutorStatePiece super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()), AddressesReadPcodeArithmetic.INSTANCE); this.data = data; + this.unique = new HashMap<>(); + } + + protected AddressesReadTracePcodeExecutorStatePiece(PcodeTraceDataAccess data, + Map unique) { + super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()), + AddressesReadPcodeArithmetic.INSTANCE); + this.data = data; + this.unique = unique; } @Override @@ -66,11 +75,27 @@ public class AddressesReadTracePcodeExecutorStatePiece return data; } + @Override + public AddressesReadTracePcodeExecutorStatePiece fork() { + return new AddressesReadTracePcodeExecutorStatePiece(data, new HashMap<>(unique)); + } + @Override public void writeDown(PcodeTraceDataAccess into) { throw new UnsupportedOperationException(); } + @Override + protected Map getRegisterValuesFromSpace(AddressSpace s, + List registers) { + return Map.of(); + } + + @Override + public Map getRegisterValues() { + return Map.of(); + } + @Override protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) { return space; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java index ecb33504be..79f29a4d01 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java @@ -29,4 +29,13 @@ public class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState public BytesTracePcodeExecutorState(PcodeTraceDataAccess data) { super(new BytesTracePcodeExecutorStatePiece(data)); } + + protected BytesTracePcodeExecutorState(TracePcodeExecutorStatePiece piece) { + super(piece); + } + + @Override + public BytesTracePcodeExecutorState fork() { + return new BytesTracePcodeExecutorState(piece.fork()); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java index 1a1ec05ce7..0bd21e49ab 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java @@ -16,11 +16,12 @@ package ghidra.pcode.exec.trace; import java.nio.ByteBuffer; +import java.util.Map; import generic.ULongSpan; import generic.ULongSpan.ULongSpanSet; -import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece; -import ghidra.pcode.exec.BytesPcodeExecutorStateSpace; +import ghidra.generic.util.datastruct.SemisparseByteArray; +import ghidra.pcode.exec.*; import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; @@ -41,11 +42,23 @@ public class BytesTracePcodeExecutorStatePiece protected static class CachedSpace extends BytesPcodeExecutorStateSpace { - protected final AddressSet written = new AddressSet(); + protected final AddressSet written; public CachedSpace(Language language, AddressSpace space, PcodeTraceDataAccess backing) { // Backing could be null, so we need language parameter super(language, space, backing); + this.written = new AddressSet(); + } + + protected CachedSpace(Language language, AddressSpace space, PcodeTraceDataAccess backing, + SemisparseByteArray bytes, AddressSet written) { + super(language, space, backing, bytes); + this.written = written; + } + + @Override + public CachedSpace fork() { + return new CachedSpace(language, space, backing, bytes.fork(), new AddressSet(written)); } @Override @@ -118,11 +131,22 @@ public class BytesTracePcodeExecutorStatePiece this.data = data; } + protected BytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data, + AbstractSpaceMap spaceMap) { + super(data.getLanguage(), spaceMap); + this.data = data; + } + @Override public PcodeTraceDataAccess getData() { return data; } + @Override + public BytesTracePcodeExecutorStatePiece fork() { + return new BytesTracePcodeExecutorStatePiece(data, spaceMap.fork()); + } + @Override public void writeDown(PcodeTraceDataAccess into) { if (into.getLanguage() != language) { @@ -139,6 +163,14 @@ public class BytesTracePcodeExecutorStatePiece */ protected class TraceBackedSpaceMap extends CacheingSpaceMap { + public TraceBackedSpaceMap() { + super(); + } + + protected TraceBackedSpaceMap(Map spaces) { + super(spaces); + } + @Override protected PcodeTraceDataAccess getBacking(AddressSpace space) { return data; @@ -148,6 +180,16 @@ public class BytesTracePcodeExecutorStatePiece protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) { return new CachedSpace(language, space, backing); } + + @Override + public TraceBackedSpaceMap fork() { + return new TraceBackedSpaceMap(fork(spaces)); + } + + @Override + public CachedSpace fork(CachedSpace s) { + return s.fork(); + } } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java index 1636d8ff68..6b2dfe9e75 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java @@ -44,6 +44,11 @@ public class DefaultTracePcodeExecutorState extends DefaultPcodeExecutorState return piece.getData(); } + @Override + public DefaultTracePcodeExecutorState fork() { + return new DefaultTracePcodeExecutorState<>(piece.fork()); + } + @Override public void writeDown(PcodeTraceDataAccess into) { piece.writeDown(into); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java index 276b349cb0..f77f970d48 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java @@ -60,6 +60,11 @@ public class DirectBytesTracePcodeExecutorState extends DefaultTracePcodeExecuto super(new DirectBytesTracePcodeExecutorStatePiece(data)); } + protected DirectBytesTracePcodeExecutorState( + TracePcodeExecutorStatePiece piece) { + super(piece); + } + /** * Create the state * @@ -83,4 +88,9 @@ public class DirectBytesTracePcodeExecutorState extends DefaultTracePcodeExecuto return new PairedPcodeExecutorState<>(this, new TraceMemoryStatePcodeExecutorStatePiece(getData())); } + + @Override + public DirectBytesTracePcodeExecutorState fork() { + return new DirectBytesTracePcodeExecutorState(piece.fork()); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java index 195e634797..7e5fa46de3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java @@ -16,6 +16,8 @@ package ghidra.pcode.exec.trace; import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; import javax.help.UnsupportedOperationException; @@ -27,6 +29,7 @@ import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; import ghidra.trace.model.memory.TraceMemoryState; @@ -48,7 +51,7 @@ public class DirectBytesTracePcodeExecutorStatePiece protected final PcodeTraceDataAccess data; - protected final SemisparseByteArray unique = new SemisparseByteArray(); + protected final SemisparseByteArray unique; /** * Construct a piece @@ -57,9 +60,10 @@ public class DirectBytesTracePcodeExecutorStatePiece * @param data the trace-data access shim */ protected DirectBytesTracePcodeExecutorStatePiece(PcodeArithmetic arithmetic, - PcodeTraceDataAccess data) { + PcodeTraceDataAccess data, SemisparseByteArray unique) { super(data.getLanguage(), arithmetic, arithmetic); this.data = data; + this.unique = unique; } /** @@ -68,7 +72,7 @@ public class DirectBytesTracePcodeExecutorStatePiece * @param data the trace-data access shim */ public DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data); + this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data, new SemisparseByteArray()); } @Override @@ -76,6 +80,11 @@ public class DirectBytesTracePcodeExecutorStatePiece return data; } + @Override + public DirectBytesTracePcodeExecutorStatePiece fork() { + return new DirectBytesTracePcodeExecutorStatePiece(arithmetic, data, unique.fork()); + } + /** * Create a state which computes an expression's {@link TraceMemoryState} as an auxiliary * attribute @@ -129,6 +138,17 @@ public class DirectBytesTracePcodeExecutorStatePiece return buf.array(); } + @Override + protected Map getRegisterValuesFromSpace(AddressSpace s, + List registers) { + return Map.of(); + } + + @Override + public Map getRegisterValues() { + return Map.of(); + } + @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { throw new UnsupportedOperationException(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java index dad5e0b46b..8ad244b217 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java @@ -30,30 +30,30 @@ import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; public class PairedTracePcodeExecutorState extends PairedPcodeExecutorState implements TracePcodeExecutorState> { - private final TracePcodeExecutorStatePiece left; - private final TracePcodeExecutorStatePiece right; + private final PairedTracePcodeExecutorStatePiece piece; public PairedTracePcodeExecutorState(PairedTracePcodeExecutorStatePiece piece) { super(piece); - this.left = piece.getLeft(); - this.right = piece.getRight(); + this.piece = piece; } public PairedTracePcodeExecutorState(TracePcodeExecutorState left, TracePcodeExecutorStatePiece right) { - super(left, right); - this.left = left; - this.right = right; + this(new PairedTracePcodeExecutorStatePiece<>(left, right)); } @Override public PcodeTraceDataAccess getData() { - return left.getData(); + return piece.getData(); + } + + @Override + public PairedTracePcodeExecutorState fork() { + return new PairedTracePcodeExecutorState<>(piece.fork()); } @Override public void writeDown(PcodeTraceDataAccess into) { - left.writeDown(into); - right.writeDown(into); + piece.writeDown(into); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java index 9b5c82b0eb..ec9fdd5aaa 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java @@ -56,6 +56,12 @@ public class PairedTracePcodeExecutorStatePiece return left.getData(); } + @Override + public PairedTracePcodeExecutorStatePiece fork() { + return new PairedTracePcodeExecutorStatePiece<>(left.fork(), right.fork(), + getAddressArithmetic(), getArithmetic()); + } + @Override public void writeDown(PcodeTraceDataAccess into) { left.writeDown(into); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java index 67d94d9972..c5ae94b7dc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java @@ -31,4 +31,14 @@ public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(PcodeTraceDataAccess data) { super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data)); } + + protected RequireHasKnownTraceCachedWriteBytesPcodeExecutorState( + TracePcodeExecutorStatePiece piece) { + super(piece); + } + + @Override + public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState fork() { + return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(piece.fork()); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java index 5b974be400..990ebbb2f2 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -40,6 +40,17 @@ public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece super(data); } + protected RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data, + AbstractSpaceMap spaceMap) { + super(data, spaceMap); + } + + @Override + public RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece fork() { + return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data, + spaceMap.fork()); + } + @Override protected AddressSetView getKnown(PcodeTraceDataAccess backing) { return backing.getKnownBefore(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java index 78c5e19731..eb42d6568f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java @@ -31,4 +31,14 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(PcodeTraceDataAccess data) { super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data)); } + + protected RequireIsKnownTraceCachedWriteBytesPcodeExecutorState( + TracePcodeExecutorStatePiece piece) { + super(piece); + } + + @Override + public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState fork() { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(piece.fork()); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java index 24b7a17c9b..38bdc460ce 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -16,6 +16,7 @@ package ghidra.pcode.exec.trace; import ghidra.pcode.exec.AccessPcodeExecutionException; +import ghidra.pcode.exec.PcodeExecutorStatePiece; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; import ghidra.trace.model.memory.TraceMemorySpace; @@ -34,6 +35,17 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece super(data); } + protected RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data, + AbstractSpaceMap spaceMap) { + super(data, spaceMap); + } + + @Override + public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece fork() { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data, + spaceMap.fork()); + } + /** * Construct a piece * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java index 76a7ba67b1..ba60a5e2ae 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java @@ -15,6 +15,8 @@ */ package ghidra.pcode.exec.trace; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; import generic.ULongSpan; @@ -23,6 +25,7 @@ import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemoryState; @@ -42,7 +45,7 @@ import ghidra.trace.model.memory.TraceMemoryState; public class TraceMemoryStatePcodeExecutorStatePiece extends AbstractLongOffsetPcodeExecutorStatePiece { - protected final MutableULongSpanMap unique = new DefaultULongSpanMap<>(); + protected final MutableULongSpanMap unique; protected final PcodeTraceDataAccess data; /** @@ -55,6 +58,22 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends BytesPcodeArithmetic.forLanguage(data.getLanguage()), TraceMemoryStatePcodeArithmetic.INSTANCE); this.data = data; + this.unique = new DefaultULongSpanMap<>(); + } + + protected TraceMemoryStatePcodeExecutorStatePiece(PcodeTraceDataAccess data, + MutableULongSpanMap unique) { + super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()), + TraceMemoryStatePcodeArithmetic.INSTANCE); + this.data = data; + this.unique = unique; + } + + @Override + public TraceMemoryStatePcodeExecutorStatePiece fork() { + MutableULongSpanMap copyUnique = new DefaultULongSpanMap<>(); + copyUnique.putAll(unique); + return new TraceMemoryStatePcodeExecutorStatePiece(data, copyUnique); } protected AddressRange range(AddressSpace space, long offset, int size) { @@ -107,6 +126,17 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends return TraceMemoryState.UNKNOWN; } + @Override + protected Map getRegisterValuesFromSpace(AddressSpace s, + List registers) { + return Map.of(); + } + + @Override + public Map getRegisterValues() { + return Map.of(); + } + @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", purpose); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java index 02ac75f706..6bfe5301e3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java @@ -23,13 +23,14 @@ import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; * *

    * In particular, because this derives from {@link TracePcodeExecutorStatePiece}, such states are - * required to implement {@link #writeDown(PcodeTraceDataAccess)}. This interface also - * derives from {@link PcodeExecutorState} so that, as the name implies, they can be used where a - * state is required. + * required to implement {@link #writeDown(PcodeTraceDataAccess)}. This interface also derives from + * {@link PcodeExecutorState} so that, as the name implies, they can be used where a state is + * required. * * @param the type of values */ public interface TracePcodeExecutorState extends PcodeExecutorState, TracePcodeExecutorStatePiece { - // Nothing to add. Simply a composition of interfaces. + @Override + TracePcodeExecutorState fork(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java index 80a74e44ea..79c7de62d0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java @@ -38,6 +38,9 @@ public interface TracePcodeExecutorStatePiece extends PcodeExecutorStatePi */ PcodeTraceDataAccess getData(); + @Override + TracePcodeExecutorStatePiece fork(); + /** * Write the accumulated values (cache) into the given trace * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java index 5997f623dc..425ced0584 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java @@ -256,7 +256,7 @@ public class UndefinedDBTraceData implements DBTraceDataAdapter, DBTraceSpaceKey } @Override - public Data getComponentContaining(int offset) { + public TraceData getComponentContaining(int offset) { return null; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceDelegatingManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceDelegatingManager.java index e39985ecc1..b4ab4a993b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceDelegatingManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceDelegatingManager.java @@ -42,7 +42,8 @@ public interface DBTraceDelegatingManager { default void checkIsInMemory(AddressSpace space) { if (!space.isMemorySpace() && space != Address.NO_ADDRESS.getAddressSpace()) { - throw new IllegalArgumentException("Address must be in memory or NO_ADDRESS"); + throw new IllegalArgumentException( + "Address must be in memory or NO_ADDRESS. Got " + space); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceData.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceData.java index eec5fa206a..f3317f6474 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceData.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceData.java @@ -29,6 +29,9 @@ public interface TraceData extends TraceCodeUnit, Data { @Override TraceData getComponentAt(int offset); + @Override + TraceData getComponentContaining(int offset); + @Override TraceData getComponent(int[] componentPath); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java index 4b4161f36f..78d3b1d8cc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java @@ -333,6 +333,29 @@ public interface TraceMemoryOperations { Collection> getStates(long snap, AddressRange range); + /** + * Check if a range addresses are all known + * + * @param snap the time + * @param range the range to examine + * @return true if the entire range is {@link TraceMemoryState#KNOWN} + */ + default boolean isKnown(long snap, AddressRange range) { + Collection> states = getStates(snap, range); + if (states.isEmpty()) { + return false; + } + if (states.size() != 1) { + return false; + } + AddressRange entryRange = states.iterator().next().getKey().getRange(); + if (!entryRange.contains(range.getMinAddress()) || + !entryRange.contains(range.getMaxAddress())) { + return false; + } + return true; + } + /** * Break a range of addresses into smaller ranges each mapped to its most recent state at the * given time diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java index 904f1ad2d7..b45abe10aa 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java @@ -118,6 +118,9 @@ public interface Scheduler { TickStep slice = nextSlice(trace); eventThread = slice.getThread(tm, eventThread); emuThread = machine.getThread(eventThread.getPath(), true); + if (emuThread.getFrame() != null) { + emuThread.finishInstruction(); + } for (int i = 0; i < slice.tickCount; i++) { monitor.checkCanceled(); emuThread.stepInstruction(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java index 24fc51340a..5ac2a3bb25 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java @@ -200,7 +200,7 @@ public enum TraceRegisterUtils { public static void requireByteBound(Register register) { if (!isByteBound(register)) { throw new IllegalArgumentException( - "Cannot work with sub-byte registers. Consider a parent, instead."); + "Cannot work with sub-byte registers. Consider a parent instead."); } } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowExpansionVetoException.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowExpansionVetoException.java deleted file mode 100644 index 18bb60b851..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowExpansionVetoException.java +++ /dev/null @@ -1,20 +0,0 @@ -/* ### - * 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 docking.widgets; - -public class ExpanderArrowExpansionVetoException extends Exception { - -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowPanel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowPanel.java deleted file mode 100644 index 633d29ea7d..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/ExpanderArrowPanel.java +++ /dev/null @@ -1,155 +0,0 @@ -/* ### - * 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 docking.widgets; - -import java.awt.*; -import java.awt.event.*; -import java.util.concurrent.CompletableFuture; - -import javax.swing.JPanel; - -import ghidra.util.datastruct.ListenerSet; - -public class ExpanderArrowPanel extends JPanel { - // TODO: Can I make this consistent with the UI LaF - protected final static Polygon ARROW = - new Polygon(new int[] { 5, -5, -5 }, new int[] { 0, -5, 5 }, 3); - protected final static Dimension SIZE = new Dimension(16, 16); - protected final static int ANIM_MILLIS = 80; - protected final static int FRAME_MILLIS = 30; // Approx 30 fps - - private final ListenerSet listeners = - new ListenerSet<>(ExpanderArrowExpansionListener.class); - - private boolean expanded = false; - - private double animTheta; - private boolean animActive = false; - private long animTimeEnd; - private double animThetaEnd; - private double animThetaOverTimeRate; - - private final MouseListener mouseListener = new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - toggle(); - } - }; - - { - addMouseListener(mouseListener); - } - - public void addExpansionListener(ExpanderArrowExpansionListener listener) { - listeners.add(listener); - } - - public void removeExpansionListener(ExpanderArrowExpansionListener listener) { - listeners.remove(listener); - } - - protected synchronized void animateTheta(double destTheta) { - animTimeEnd = System.currentTimeMillis() + ANIM_MILLIS; - animThetaEnd = destTheta; - animThetaOverTimeRate = (destTheta - animTheta) / ANIM_MILLIS; - animActive = true; - scheduleNextFrame(); - } - - public void toggle() { - setExpanded(!expanded); - } - - protected boolean fireChanging(boolean newExpanded) { - try { - listeners.fire.changing(newExpanded); - } - catch (ExpanderArrowExpansionVetoException e) { - return false; - } - return true; - } - - protected void fireChanged() { - listeners.fire.changed(expanded); - } - - public void setExpanded(boolean expanded) { - if (this.expanded == expanded) { - return; - } - if (!fireChanging(expanded)) { - return; - } - double destTheta = expanded ? Math.PI / 2 : 0; - animateTheta(destTheta); - this.expanded = expanded; - fireChanged(); - } - - public boolean isExpanded() { - return expanded; - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - Graphics2D g2 = (Graphics2D) g.create(); - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.translate((double) SIZE.width / 2, (double) SIZE.height / 2); - g2.rotate(animTheta); - g2.fillPolygon(ARROW); - - if (!animActive) { - return; - } - long time = System.currentTimeMillis(); - double timeDiff = Math.max(0, animTimeEnd - time); - if (timeDiff != 0) { - double thetaDiff = timeDiff * animThetaOverTimeRate; - animTheta = animThetaEnd - thetaDiff; - scheduleNextFrame(); - return; - } - animActive = false; - if (animTheta != animThetaEnd) { - animTheta = animThetaEnd; - scheduleNextFrame(); - } - } - - @Override - public Dimension getPreferredSize() { - return SIZE; - } - - @Override - public Dimension getMinimumSize() { - return SIZE; - } - - protected void scheduleNextFrame() { - CompletableFuture.runAsync(() -> { - try { - Thread.sleep(FRAME_MILLIS); - } - catch (InterruptedException e) { - // Whatever. Render early. - } - repaint(); - }); - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/generic/Span.java b/Ghidra/Debug/ProposedUtils/src/main/java/generic/Span.java index a62948b210..160a989c89 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/generic/Span.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/generic/Span.java @@ -912,7 +912,7 @@ public interface Span> extends Comparable { * @param max the upper endpoint of the span * @return the sub map */ - protected SortedMap> subMap(N min, N max) { + protected NavigableMap> subMap(N min, N max) { Entry> adjEnt = spanTree.floorEntry(min); if (adjEnt != null && adjEnt.getValue().getKey().contains(min)) { min = adjEnt.getKey(); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/generic/Unique.java b/Ghidra/Debug/ProposedUtils/src/main/java/generic/Unique.java index a8f7bb5ccc..e6dce531c3 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/generic/Unique.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/generic/Unique.java @@ -24,6 +24,16 @@ import java.util.stream.Stream; */ public interface Unique { + static T assertAtMostOne(T[] arr) { + if (arr.length == 0) { + return null; + } + if (arr.length == 1) { + return arr[0]; + } + throw new AssertionError("Expected at most one. Got many: " + List.of(arr)); + } + /** * Assert that exactly one element is in an iterable and get that element * diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/generic/util/datastruct/SemisparseByteArray.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/generic/util/datastruct/SemisparseByteArray.java index 5123ec3c0a..69e65d2636 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/generic/util/datastruct/SemisparseByteArray.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/generic/util/datastruct/SemisparseByteArray.java @@ -17,8 +17,9 @@ package ghidra.generic.util.datastruct; import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; import generic.ULongSpan; import generic.ULongSpan.*; @@ -62,8 +63,33 @@ public class SemisparseByteArray { /** The size of blocks used internally to store array values */ public static final int BLOCK_SIZE = 0x1000; - private final Map blocks = new HashMap<>(); - private final MutableULongSpanSet defined = new DefaultULongSpanSet(); + private final Map blocks; + private final MutableULongSpanSet defined; + + public SemisparseByteArray() { + this.blocks = new HashMap<>(); + this.defined = new DefaultULongSpanSet(); + } + + protected SemisparseByteArray(Map blocks, MutableULongSpanSet defined) { + this.blocks = blocks; + this.defined = defined; + } + + static byte[] copyArr(Map.Entry ent) { + byte[] b = ent.getValue(); + return Arrays.copyOf(b, b.length); + } + + public synchronized SemisparseByteArray fork() { + // TODO Could use some copy-on-write optimization here and in parents + Map copyBlocks = blocks.entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, SemisparseByteArray::copyArr)); + MutableULongSpanSet copyDefined = new DefaultULongSpanSet(); + copyDefined.addAll(defined); + return new SemisparseByteArray(copyBlocks, copyDefined); + } /** * Clear the array diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java index fec087c579..3499e957de 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java @@ -15,7 +15,7 @@ */ package ghidra.pcode.emu; -import java.util.Objects; +import java.util.*; import ghidra.pcode.exec.PcodeArithmetic; import ghidra.pcode.exec.PcodeArithmetic.Purpose; @@ -23,6 +23,7 @@ import ghidra.pcode.exec.PcodeExecutorState; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; /** @@ -62,6 +63,11 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { return arithmetic; } + @Override + public ThreadPcodeExecutorState fork() { + return new ThreadPcodeExecutorState<>(sharedState.fork(), localState.fork()); + } + /** * Decide whether or not access to the given space is directed to thread-local state * @@ -89,6 +95,14 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { return sharedState.getVar(space, offset, size, quantize, reason); } + @Override + public Map getRegisterValues() { + Map result = new HashMap<>(); + result.putAll(localState.getRegisterValues()); + result.putAll(sharedState.getRegisterValues()); + return result; + } + @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { assert !isThreadLocalSpace(address.getAddressSpace()); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/AbstractVarnodeEvaluator.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/AbstractVarnodeEvaluator.java new file mode 100644 index 0000000000..e4073741dd --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/AbstractVarnodeEvaluator.java @@ -0,0 +1,430 @@ +/* ### + * 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.pcode.eval; + +import java.util.HashMap; +import java.util.Map; + +import ghidra.pcode.error.LowlevelError; +import ghidra.pcode.exec.*; +import ghidra.pcode.opbehavior.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.VariableStorage; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; + +/** + * An abstract implementation of {@link VarnodeEvaluator} + * + *

    + * Unlike {@link PcodeExecutor} this abstract class is not explicitly bound to a p-code state nor + * arithmetic. Instead it defines abstract methods for accessing "leaf" varnodes and evaluating ops. + * To evaluate a varnode, it first checks if the varnode is a leaf, which is defined by an extension + * class. If it is, it converts the static address to a dynamic one and invokes the appropriate + * value getter. An extension class would likely implement those getters using a + * {@link PcodeExecutorState}. If the varnode is not a leaf, the evaluator will ascend by examining + * its defining p-code op, evaluate its input varnodes recursively and then compute the output using + * the provided p-code arithmetic. This implementation maintains a map of evaluated varnodes and + * their values so that any intermediate varnode is evaluated just once. Note that the evaluation + * algorithm assumes their are no cycles in the AST, which should be the case by definition. + * + * @param the type of values resulting from evaluation + */ +public abstract class AbstractVarnodeEvaluator implements VarnodeEvaluator { + /** + * Concatenate the given values + * + * @param sizeTotal the expected output size in bytes + * @param upper the value of the left (more significant) piece + * @param lower the value of the right (less significant) piece + * @param sizeLower the size of the lower piece + * @return the result of concatenation + */ + protected abstract T catenate(int sizeTotal, T upper, T lower, int sizeLower); + + /** + * Check if the given varnode is a leaf in the evaluation + * + *

    + * This allows the extension class to determine the base case when recursively ascending the + * AST. + * + * @param vn the varnode + * @return true to treat the varnode as a base case, or false to ascend to its defining p-code + * op + */ + protected abstract boolean isLeaf(Varnode vn); + + /** + * Resolve a (static) stack offset to its physical (dynamic) address in the frame + * + *

    + * When a leaf varnode is a stack address, this is used to map it to a physical address before + * invoking {@link #evaluateMemory(Address, int)}. + * + * @param offset the offset + * @return the address in target memory + */ + protected abstract Address applyBase(long offset); + + /** + * Map the given static address to dynamic + * + *

    + * When a leaf varnode is a memory address, this is used to map it to a dynamic address before + * invoking {@link #evaluateMemory(Address, int)}. This is needed in case the module has been + * relocated in the dynamic context. Note this is not used to translate register or stack + * addresses, since those are abstract concepts. Stack addresses are translated using + * {@link #applyBase(long)}, the result of which should already be a dynamic address. + * + * @param program the program specifying the static context + * @param address the address in the static context + * @return the address in the dynamic context + */ + protected Address translateMemory(Program program, Address address) { + return address; + } + + /** + * Evaluate a leaf varnode + * + *

    + * This method translates the varnode accordingly and delegates the evaluation, indirectly, to + * {@link #evaluateMemory(Address, int)}. Notable exceptions are constants, which are just + * evaluated to their immediate value, and unique variables, which cannot ordinarily be leaves. + * + * @param program the program defining the static context + * @param vn the varnode + * @return the value obtained from the dynamic context + */ + protected T evaluateLeaf(Program program, Varnode vn) { + Address address = vn.getAddress(); + if (address.isConstantAddress()) { + return evaluateConstant(vn.getOffset(), vn.getSize()); + } + else if (address.isRegisterAddress()) { + return evaluateRegister(address, vn.getSize()); + } + else if (address.isStackAddress()) { + return evaluateStack(address.getOffset(), vn.getSize()); + } + else if (address.isMemoryAddress()) { + return evaluateMemory(translateMemory(program, address), vn.getSize()); + } + else if (address.isUniqueAddress()) { + return evaluateUnique(vn.getOffset(), vn.getSize()); + } + else { + throw new PcodeExecutionException("Unrecognized address space in " + vn); + } + } + + /** + * Evaluate a varnode, which could be either a leaf or a branch + * + *

    + * This method is invoked by {@link #evaluateVarnode(Program, Varnode, Map)} when the value has + * not already been computed. Only that method should invoke this one directly. + * + * @param program the program defining the static context + * @param vn the varnode + * @param already a cache of already-evaluated varnodes and their values + * @return the value + */ + protected T doEvaluateVarnode(Program program, Varnode vn, Map already) { + if (isLeaf(vn)) { + return evaluateLeaf(program, vn); + } + return evaluateBranch(program, vn, already); + } + + /** + * Evaluate a varnode, which could be either a leaf or a branch, taking its cached value if + * available + * + * @param program the program defining the static context + * @param vn the varnode + * @param already a cache of already-evaluated varnodes and their values + * @return the value + */ + protected T evaluateVarnode(Program program, Varnode vn, Map already) { + // computeIfAbsent does nto work because of the recursion. Will get CME. + if (already.containsKey(vn)) { + return already.get(vn); + } + T result = doEvaluateVarnode(program, vn, already); + already.put(vn, result); + return result; + } + + /** + * Evaluate a varnode + * + * @param program the program containing the varnode + * @param vn the varnode to evaluate + * @return the value of the varnode + */ + @Override + public T evaluateVarnode(Program program, Varnode vn) { + return evaluateVarnode(program, vn, new HashMap<>()); + } + + /** + * Evaluate variable storage, providing an "identity" value + * + * @param storage the storage to evaluate + * @param identity the value if storage had no varnodes + * @return the value of the storage + */ + protected T evaluateStorage(VariableStorage storage, T identity) { + Program program = storage.getProgram(); + int total = storage.size(); + T value = identity; + for (Varnode vn : storage.getVarnodes()) { + T piece = evaluateVarnode(program, vn); + value = catenate(total, value, piece, vn.getSize()); + } + return value; + } + + /** + * Evaluate the given varnode's defining p-code op + * + * @param program the program defining the static context + * @param vn the varnode + * @param already a cache of already-evaluated varnodes and their values + * @return the value + */ + protected T evaluateBranch(Program program, Varnode vn, Map already) { + PcodeOp def = vn.getDef(); + if (def == null || def.getOutput() != vn) { + throw new PcodeExecutionException("No defining p-code op for " + vn); + } + return evaluateOp(program, def, already); + } + + /** + * Evaluate a constant + * + * @param value the constant value + * @param size the size of the value in bytes + * @return the value as a {@link T} + */ + protected abstract T evaluateConstant(long value, int size); + + /** + * Evaluate the given register variable + * + * @param address the address of the register + * @param size the size of the variable in bytes + * @return the value + */ + protected T evaluateRegister(Address address, int size) { + return evaluateMemory(address, size); + } + + /** + * Evaluate the given stack variable + * + * @param offset the stack offset of the variable + * @param size the size of the variable in bytes + * @return the value + */ + protected T evaluateStack(long offset, int size) { + return evaluateMemory(applyBase(offset), size); + } + + /** + * Evaluate a variable in memory + * + *

    + * By default all register, stack, and memory addresses are directed here. Register addresses + * are passed through without modification. Stack addresses will have the frame base applied via + * {@link #applyBase(long)}, and memory addresses will be mapped through + * {@link #translateMemory(Program, Address)}. + * + * @param address the address of the variable + * @param size the size of the variable in bytes + * @return the value + */ + protected abstract T evaluateMemory(Address address, int size); + + /** + * Evaluate a unique variable + * + *

    + * This is only invoked when trying to evaluate a leaf, which should never occur for a unique + * variable. Thus, by default, this throws a {@link PcodeExecutionException}. + * + * @param long the offset of the variable + * @param size the size of the variable in bytes + * @return the value + */ + protected T evaluateUnique(long offset, int size) { + throw new PcodeExecutionException( + String.format("Cannot evaluate unique $U%x:%d", offset, size)); + } + + /** + * Evaluate a variable whose offset is of type {@link T} + * + *

    + * The three parameters {@code space}, {@code offset}, and {@code size} imitate the varnode + * triple, except that the offset is abstract. This is typically invoked for a + * {@link PcodeOp#LOAD}, i.e., a dereference. + * + * @param program the program defining the static context + * @param space the address space of the variable + * @param offset the offset of the variable + * @param size the size of the variable in bytes + * @param already a cache of already-evaluated varnodes and their values + * @return the value + */ + protected abstract T evaluateAbstract(Program program, AddressSpace space, T offset, int size, + Map already); + + /** + * Evaluate a unary op + * + *

    + * This evaluates the input varnode then computes the output value. + * + * @param program the program defining the static context + * @param op the op whose output to evaluate + * @param unOp the concrete behavior of the op + * @param already a cache of already-evaluated varnodes and their values + * @return the output value + */ + protected abstract T evaluateUnaryOp(Program program, PcodeOp op, UnaryOpBehavior unOp, + Map already); + + /** + * Evaluate a binary op + * + *

    + * This evaluates the input varnodes then computes the output value. + * + * @param program the program defining the static context + * @param op the op whose output to evaluate + * @param binOp the concrete behavior of the op + * @param already a cache of already-evaluated varnodes and their values + * @return the output value + */ + protected abstract T evaluateBinaryOp(Program program, PcodeOp op, BinaryOpBehavior binOp, + Map already); + + /** + * Evaluate a {@link PcodeOp#PTRADD} op + * + * @param program the program defining the static context + * @param op the op whose output to evaluate + * @param already a cache of already-evaluated varnodes and their values + * @return the output value + */ + protected abstract T evaluatePtrAdd(Program program, PcodeOp op, Map already); + + /** + * Evaluate a {@link PcodeOp#PTRSUB} op + * + * @param program the program defining the static context + * @param op the op whose output to evaluate + * @param already a cache of already-evaluated varnodes and their values + * @return the output value + */ + protected abstract T evaluatePtrSub(Program program, PcodeOp op, Map already); + + /** + * Assert that a varnode is constant and get its value as an integer. + * + *

    + * Here "constant" means a literal or immediate value. It does not read from the state. + * + * @param vn the varnode + * @return the value + */ + protected int getIntConst(Varnode vn) { + if (!vn.isConstant()) { + throw new IllegalArgumentException(vn + " is not a constant"); + } + return (int) vn.getAddress().getOffset(); + } + + /** + * Evaluate a {@link PcodeOp#LOAD} op + * + * @param program the program defining the static context + * @param op the op whose output to evaluate + * @param already a cache of already-evaluated varnodes and their values + * @return the output value + */ + protected abstract T evaluateLoad(Program program, PcodeOp op, Map already); + + @Override + public T evaluateOp(Program program, PcodeOp op) { + return evaluateOp(program, op, new HashMap<>()); + } + + /** + * Like {@link #evaluateOp(Program, PcodeOp)}, but uses a cache + * + * @param program the program defining the static context + * @param op the op whose output to evaluate + * @param already a cache of already-evaluated varnodes and their values + * @return the output value + */ + protected T evaluateOp(Program program, PcodeOp op, Map already) { + OpBehavior b = OpBehaviorFactory.getOpBehavior(op.getOpcode()); + if (b == null) { + return badOp(op); + } + if (b instanceof UnaryOpBehavior unOp) { + return evaluateUnaryOp(program, op, unOp, already); + } + if (b instanceof BinaryOpBehavior binOp) { + return evaluateBinaryOp(program, op, binOp, already); + } + switch (op.getOpcode()) { + case PcodeOp.LOAD: + return evaluateLoad(program, op, already); + case PcodeOp.PTRADD: + return evaluatePtrAdd(program, op, already); + case PcodeOp.PTRSUB: + return evaluatePtrSub(program, op, already); + default: + return badOp(op); + } + } + + /** + * The method invoked when an unrecognized or unsupported operator is encountered + * + * @param op the op + * @return the value, but this usually throws an exception + */ + protected T badOp(PcodeOp op) { + switch (op.getOpcode()) { + case PcodeOp.UNIMPLEMENTED: + throw new LowlevelError( + "Encountered an unimplemented instruction at " + + op.getSeqnum().getTarget()); + default: + throw new LowlevelError( + "Unsupported p-code op at " + op.getSeqnum().getTarget() + ": " + op); + } + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/ArithmeticVarnodeEvaluator.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/ArithmeticVarnodeEvaluator.java new file mode 100644 index 0000000000..628e893631 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/ArithmeticVarnodeEvaluator.java @@ -0,0 +1,150 @@ +/* ### + * 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.pcode.eval; + +import java.util.Map; + +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.opbehavior.BinaryOpBehavior; +import ghidra.pcode.opbehavior.UnaryOpBehavior; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.VariableStorage; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; + +/** + * An abstract implementation of {@link VarnodeEvaluator} that evaluates ops using a bound + * {@link PcodeArithmetic}. + * + * @param the type of values resulting from evaluation + */ +public abstract class ArithmeticVarnodeEvaluator extends AbstractVarnodeEvaluator { + /** + * A convenience for concatenating two varnodes + * + *

    + * There is no p-code op for catenation, but it is easily achieved as one might do in C or + * SLEIGH: {@code shift} the left piece then {@code or} it with the right piece. + * + * @param the type of values + * @param arithmetic the p-code arithmetic for values of type {@link T} + * @param sizeTotal the expected output size in bytes + * @param upper the value of the left (more significant) piece + * @param lower the value of the right (less significant) piece + * @param sizeLower the size of the lower piece + * @return the result of concatenation + */ + public static T catenate(PcodeArithmetic arithmetic, int sizeTotal, T upper, T lower, + int sizeLower) { + T zext = arithmetic.unaryOp(PcodeOp.INT_ZEXT, sizeTotal, sizeLower, lower); + T shift = arithmetic.binaryOp(PcodeOp.INT_LEFT, sizeTotal, sizeTotal, upper, 4, + arithmetic.fromConst(sizeLower * 8, 4)); + return arithmetic.binaryOp(PcodeOp.INT_OR, sizeTotal, sizeTotal, shift, sizeTotal, zext); + } + + private final PcodeArithmetic arithmetic; + + /** + * Construct an evaluator + * + * @param arithmetic the arithmetic for computing p-code op outputs + */ + public ArithmeticVarnodeEvaluator(PcodeArithmetic arithmetic) { + this.arithmetic = arithmetic; + } + + @Override + protected T catenate(int sizeTotal, T upper, T lower, int sizeLower) { + return catenate(arithmetic, sizeTotal, upper, lower, sizeLower); + } + + @Override + public T evaluateStorage(VariableStorage storage) { + return evaluateStorage(storage, arithmetic.fromConst(0, storage.size())); + } + + @Override + protected T evaluateConstant(long value, int size) { + return arithmetic.fromConst(value, size); + } + + @Override + protected T evaluateAbstract(Program program, AddressSpace space, T offset, int size, + Map already) { + long concrete = arithmetic.toLong(offset, Purpose.LOAD); + Address address = space.getAddress(concrete); + // There is no actual varnode to have a defining op, so this will be a leaf + return evaluateMemory(translateMemory(program, address), size); + } + + @Override + protected T evaluateUnaryOp(Program program, PcodeOp op, UnaryOpBehavior unOp, + Map already) { + Varnode in1Var = op.getInput(0); + T in1 = evaluateVarnode(program, in1Var, already); + return arithmetic.unaryOp(op, in1); + } + + @Override + protected T evaluateBinaryOp(Program program, PcodeOp op, BinaryOpBehavior binOp, + Map already) { + Varnode in1Var = op.getInput(0); + Varnode in2Var = op.getInput(1); + T in1 = evaluateVarnode(program, in1Var, already); + T in2 = evaluateVarnode(program, in2Var, already); + return arithmetic.binaryOp(op, in1, in2); + } + + @Override + protected T evaluatePtrAdd(Program program, PcodeOp op, Map already) { + Varnode baseVar = op.getInput(0); + Varnode indexVar = op.getInput(1); + int size = getIntConst(op.getInput(2)); + Varnode outVar = op.getOutput(); + T base = evaluateVarnode(program, baseVar, already); + T index = evaluateVarnode(program, indexVar, already); + return arithmetic.ptrAdd(outVar.getSize(), baseVar.getSize(), base, indexVar.getSize(), + index, size); + } + + @Override + protected T evaluatePtrSub(Program program, PcodeOp op, Map already) { + Varnode baseVar = op.getInput(0); + Varnode offsetVar = op.getInput(1); + Varnode outVar = op.getOutput(); + T base = evaluateVarnode(program, baseVar, already); + T offset = evaluateVarnode(program, offsetVar, already); + return arithmetic.ptrSub(outVar.getSize(), + baseVar.getSize(), base, + offsetVar.getSize(), offset); + } + + @Override + protected T evaluateLoad(Program program, PcodeOp op, Map already) { + int spaceID = getIntConst(op.getInput(0)); + AddressSpace space = program.getAddressFactory().getAddressSpace(spaceID); + Varnode inOffset = op.getInput(1); + T offset = evaluateVarnode(program, inOffset, already); + Varnode outVar = op.getOutput(); // Only for measuring size + T out = evaluateAbstract(program, space, offset, outVar.getSize(), already); + return arithmetic.modAfterLoad(outVar.getSize(), + inOffset.getSize(), offset, + outVar.getSize(), out); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/VarnodeEvaluator.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/VarnodeEvaluator.java new file mode 100644 index 0000000000..fc55df7e41 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/eval/VarnodeEvaluator.java @@ -0,0 +1,66 @@ +/* ### + * 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.pcode.eval; + +import ghidra.pcode.exec.PcodeExecutor; +import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.VariableStorage; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; + +/** + * An evaluator of high varnodes + * + *

    + * This is a limited analog to {@link PcodeExecutor} but for high p-code. It is limited in that it + * can only "execute" parts of the AST that represent expressions, as a means of evaluating them. If + * it encounters, e.g., a {@link PcodeOp#MULTIEQUAL} or phi node, it will terminate throw an + * exception. + * + * @param the type of values resulting from evaluation + */ +public interface VarnodeEvaluator { + /** + * Evaluate a varnode + * + * @param program the program containing the varnode + * @param vn the varnode to evaluate + * @return the value of the varnode + */ + T evaluateVarnode(Program program, Varnode vn); + + /** + * Evaluate variable storage + * + *

    + * Each varnode is evaluated as in {@link #evaluateStorage(VariableStorage)} and then + * concatenated. The lower-indexed varnodes in storage are the more significant pieces, similar + * to big endian. + * + * @param storage the storage + * @return the value of the storage + */ + T evaluateStorage(VariableStorage storage); + + /** + * Evaluate a high p-code op + * + * @param program the program containing the op + * @param op the p-code op + * @return the value of the op's output + */ + T evaluateOp(Program program, PcodeOp op); +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java index f59267a974..398af7b215 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java @@ -16,10 +16,13 @@ package ghidra.pcode.exec; import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.*; import ghidra.program.model.pcode.PcodeOp; import ghidra.util.Msg; @@ -74,7 +77,7 @@ public abstract class AbstractBytesPcodeExecutorStatePiece spaceMap = newSpaceMap(); + protected final AbstractSpaceMap spaceMap; /** * Construct a state for the given language @@ -85,6 +88,11 @@ public abstract class AbstractBytesPcodeExecutorStatePiece spaceMap) { + this(language, BytesPcodeArithmetic.forLanguage(language), spaceMap); + } + /** * Construct a state for the given language * @@ -94,6 +102,13 @@ public abstract class AbstractBytesPcodeExecutorStatePiece arithmetic) { super(language, arithmetic, arithmetic); + spaceMap = newSpaceMap(); + } + + protected AbstractBytesPcodeExecutorStatePiece(Language language, + PcodeArithmetic arithmetic, AbstractSpaceMap spaceMap) { + super(language, arithmetic, arithmetic); + this.spaceMap = spaceMap; } /** @@ -138,6 +153,11 @@ public abstract class AbstractBytesPcodeExecutorStatePiece getRegisterValuesFromSpace(S s, List registers) { + return s.getRegisterValues(registers); + } + @Override public MemBuffer getConcreteBuffer(Address address, PcodeArithmetic.Purpose purpose) { return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false)); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java index 5d92a35447..9dda9b32ac 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java @@ -16,10 +16,13 @@ package ghidra.pcode.exec; import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; /** * An abstract executor state piece which internally uses {@code long} to address contents @@ -41,13 +44,48 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @param the type of object for each address space */ public abstract static class AbstractSpaceMap { - protected final Map spaces = new HashMap<>(); + protected final Map spaces; + + public AbstractSpaceMap() { + this.spaces = new HashMap<>(); + } + + protected AbstractSpaceMap(Map spaces) { + this.spaces = spaces; + } public abstract S getForSpace(AddressSpace space, boolean toWrite); public Collection values() { return spaces.values(); } + + /** + * Deep copy this map, for use in a forked state (or piece) + * + * @return the copy + */ + public abstract AbstractSpaceMap fork(); + + /** + * Deep copy the given space + * + * @param s the space + * @return the copy + */ + public abstract S fork(S s); + + /** + * Produce a deep copy of the given map + * + * @param spaces the map to copy + * @return the copy + */ + public Map fork(Map spaces) { + return spaces.entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, e -> fork(e.getValue()))); + } } /** @@ -56,6 +94,14 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @param the type of object for each address space */ public abstract static class SimpleSpaceMap extends AbstractSpaceMap { + public SimpleSpaceMap() { + super(); + } + + protected SimpleSpaceMap(Map spaces) { + super(spaces); + } + /** * Construct a new space internally associated with the given address space * @@ -68,7 +114,7 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece protected abstract S newSpace(AddressSpace space); @Override - public S getForSpace(AddressSpace space, boolean toWrite) { + public synchronized S getForSpace(AddressSpace space, boolean toWrite) { return spaces.computeIfAbsent(space, s -> newSpace(s)); } } @@ -80,6 +126,14 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @param the type of cache for each address space */ public abstract static class CacheingSpaceMap extends AbstractSpaceMap { + public CacheingSpaceMap() { + super(); + } + + protected CacheingSpaceMap(Map spaces) { + super(spaces); + } + /** * Get the object backing the cache for the given address space * @@ -102,7 +156,7 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece protected abstract S newSpace(AddressSpace space, B backing); @Override - public S getForSpace(AddressSpace space, boolean toWrite) { + public synchronized S getForSpace(AddressSpace space, boolean toWrite) { return spaces.computeIfAbsent(space, s -> newSpace(s, s.isUniqueSpace() ? null : getBacking(s))); } @@ -249,7 +303,8 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece } @Override - public T getVar(AddressSpace space, long offset, int size, boolean quantize, Reason reason) { + public T getVar(AddressSpace space, long offset, int size, boolean quantize, + Reason reason) { checkRange(space, offset, size); if (space.isConstantSpace()) { return arithmetic.fromConst(offset, size); @@ -264,4 +319,29 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece offset = quantizeOffset(space, offset); return getFromSpace(s, offset, size, reason); } + + /** + * Can the given space for register values, as in {@link #getRegisterValues()} + * + * @param s the space to scan + * @param registers the registers known to be in the corresponding address space + * @return the map of registers to values + */ + protected abstract Map getRegisterValuesFromSpace(S s, List registers); + + @Override + public Map getRegisterValues() { + Map> regsBySpace = language.getRegisters() + .stream() + .collect(Collectors.groupingBy(Register::getAddressSpace)); + Map result = new HashMap<>(); + for (Map.Entry> ent : regsBySpace.entrySet()) { + S s = getForSpace(ent.getKey(), false); + if (s == null) { + continue; + } + result.putAll(getRegisterValuesFromSpace(s, ent.getValue())); + } + return result; + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeArithmetic.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeArithmetic.java deleted file mode 100644 index 122ff41666..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeArithmetic.java +++ /dev/null @@ -1,95 +0,0 @@ -/* ### - * 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.pcode.exec; - -import java.math.BigInteger; - -import ghidra.program.model.address.Address; -import ghidra.program.model.lang.Endian; - -/** - * An auxiliary arithmetic that reports the address of the control value - * - *

    - * This is intended for use as the right side of a {@link PairedPcodeArithmetic}. Note that constant - * and unique spaces are never returned. Furthermore, any computation performed on a value, - * producing a temporary value, philosophically does not exist at any address in the state. Thus, - * every operation in this arithmetic results in {@code null}. The accompanying state piece - * {@link AddressOfPcodeExecutorStatePiece} does the real "address of" logic. - */ -public enum AddressOfPcodeArithmetic implements PcodeArithmetic

    { - // NB: No temp value has a real address - /** The singleton instance */ - INSTANCE; - - @Override - public Endian getEndian() { - return null; - } - - @Override - public Address unaryOp(int opcode, int sizeout, int sizein1, Address in1) { - return null; - } - - @Override - public Address binaryOp(int opcode, int sizeout, int sizein1, Address in1, int sizein2, - Address in2) { - return null; - } - - @Override - public Address modBeforeStore(int sizeout, int sizeinAddress, Address inAddress, - int sizeinValue, Address inValue) { - return inValue; - } - - @Override - public Address modAfterLoad(int sizeout, int sizeinAddress, Address inAddress, int sizeinValue, - Address inValue) { - return inValue; - } - - @Override - public Address fromConst(byte[] value) { - return null; // TODO: Do we care about constant space? - } - - @Override - public Address fromConst(BigInteger value, int size, boolean isContextreg) { - return null; - } - - @Override - public Address fromConst(BigInteger value, int size) { - return null; - } - - @Override - public Address fromConst(long value, int size) { - return null; - } - - @Override - public byte[] toConcrete(Address value, Purpose purpose) { - throw new ConcretionError("Cannot make 'address of' concrete", purpose); - } - - @Override - public long sizeOf(Address value) { - return value.getAddressSpace().getSize() / 8; - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java index 5c4d73427d..f4071d2d8f 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java @@ -30,4 +30,13 @@ public class BytesPcodeExecutorState extends DefaultPcodeExecutorState { super(new BytesPcodeExecutorStatePiece(language), BytesPcodeArithmetic.forLanguage(language)); } + + protected BytesPcodeExecutorState(PcodeExecutorStatePiece piece) { + super(piece); + } + + @Override + public BytesPcodeExecutorState fork() { + return new BytesPcodeExecutorState(piece.fork()); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java index a167d36ce6..d418dd5ec9 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java @@ -15,6 +15,8 @@ */ package ghidra.pcode.exec; +import java.util.Map; + import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; @@ -33,13 +35,43 @@ public class BytesPcodeExecutorStatePiece super(language); } + protected BytesPcodeExecutorStatePiece(Language language, + AbstractSpaceMap> spaceMap) { + super(language, spaceMap); + } + + @Override + public BytesPcodeExecutorStatePiece fork() { + return new BytesPcodeExecutorStatePiece(language, spaceMap.fork()); + } + + class BytesSpaceMap extends SimpleSpaceMap> { + BytesSpaceMap() { + super(); + } + + BytesSpaceMap(Map> spaces) { + super(spaces); + } + + @Override + protected BytesPcodeExecutorStateSpace newSpace(AddressSpace space) { + return new BytesPcodeExecutorStateSpace<>(language, space, null); + } + + @Override + public AbstractSpaceMap> fork() { + return new BytesSpaceMap(fork(spaces)); + } + + @Override + public BytesPcodeExecutorStateSpace fork(BytesPcodeExecutorStateSpace s) { + return s.fork(); + } + } + @Override protected AbstractSpaceMap> newSpaceMap() { - return new SimpleSpaceMap<>() { - @Override - protected BytesPcodeExecutorStateSpace newSpace(AddressSpace space) { - return new BytesPcodeExecutorStateSpace<>(language, space, null); - } - }; + return new BytesSpaceMap(); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java index 22b02f9269..33eabd48c1 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java @@ -32,7 +32,7 @@ import ghidra.util.Msg; * @param if this space is a cache, the type of object backing this space */ public class BytesPcodeExecutorStateSpace { - protected final SemisparseByteArray bytes = new SemisparseByteArray(); + protected final SemisparseByteArray bytes; protected final Language language; // for logging diagnostics protected final AddressSpace space; protected final B backing; @@ -48,6 +48,19 @@ public class BytesPcodeExecutorStateSpace { this.language = language; this.space = space; this.backing = backing; + this.bytes = new SemisparseByteArray(); + } + + protected BytesPcodeExecutorStateSpace(Language language, AddressSpace space, B backing, + SemisparseByteArray bytes) { + this.language = language; + this.space = space; + this.backing = backing; + this.bytes = bytes; + } + + public BytesPcodeExecutorStateSpace fork() { + return new BytesPcodeExecutorStateSpace<>(language, space, backing, bytes.fork()); } /** @@ -148,6 +161,21 @@ public class BytesPcodeExecutorStateSpace { return readBytes(offset, size, reason); } + public Map getRegisterValues(List registers) { + Map result = new HashMap<>(); + for (Register reg : registers) { + long min = reg.getAddress().getOffset(); + long max = min + reg.getNumBytes(); + if (!bytes.isInitialized(min, max)) { + continue; + } + byte[] data = new byte[reg.getNumBytes()]; + bytes.getData(min, data); + result.put(reg, data); + } + return result; + } + public void clear() { bytes.clear(); } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java index 9aea5e3053..3c1cf22164 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java @@ -15,10 +15,13 @@ */ package ghidra.pcode.exec; +import java.util.Map; + import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; /** @@ -51,6 +54,16 @@ public class DefaultPcodeExecutorState implements PcodeExecutorState { return piece.getLanguage(); } + @Override + public PcodeArithmetic getArithmetic() { + return arithmetic; + } + + @Override + public DefaultPcodeExecutorState fork() { + return new DefaultPcodeExecutorState<>(piece.fork(), arithmetic); + } + @Override public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) { return piece.getVar(space, offset, size, quantize, reason); @@ -62,8 +75,8 @@ public class DefaultPcodeExecutorState implements PcodeExecutorState { } @Override - public PcodeArithmetic getArithmetic() { - return arithmetic; + public Map getRegisterValues() { + return piece.getRegisterValues(); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/LocationPcodeArithmetic.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/LocationPcodeArithmetic.java new file mode 100644 index 0000000000..36dfb9670a --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/LocationPcodeArithmetic.java @@ -0,0 +1,123 @@ +/* ### + * 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.pcode.exec; + +import java.math.BigInteger; + +import ghidra.pcode.utils.Utils; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.PcodeOp; + +/** + * An auxiliary arithmetic that reports the location the control value + * + *

    + * This is intended for use as the right side of a {@link PairedPcodeArithmetic}. Note that constant + * and unique spaces are never returned. Furthermore, any computation performed on a value, + * producing a temporary value, philosophically does not exist at any location in the state. Thus, + * most operations in this arithmetic result in {@code null}. The accompanying state piece + * {@link LocationPcodeExecutorStatePiece} generates the actual locations. + */ +public enum LocationPcodeArithmetic implements PcodeArithmetic { + BIG_ENDIAN(Endian.BIG), LITTLE_ENDIAN(Endian.LITTLE); + + public static LocationPcodeArithmetic forEndian(boolean bigEndian) { + return bigEndian ? BIG_ENDIAN : LITTLE_ENDIAN; + } + + private final Endian endian; + + private LocationPcodeArithmetic(Endian endian) { + this.endian = endian; + } + + @Override + public Endian getEndian() { + return endian; + } + + @Override + public ValueLocation unaryOp(int opcode, int sizeout, int sizein1, ValueLocation in1) { + switch (opcode) { + case PcodeOp.COPY: + case PcodeOp.INT_ZEXT: + case PcodeOp.INT_SEXT: + return in1; + default: + return null; + } + } + + @Override + public ValueLocation binaryOp(int opcode, int sizeout, int sizein1, ValueLocation in1, + int sizein2, ValueLocation in2) { + switch (opcode) { + case PcodeOp.INT_LEFT: + BigInteger amount = in2 == null ? null : in2.getConst(); + if (in2 == null) { + return null; + } + return in1.shiftLeft(amount.intValue()); + case PcodeOp.INT_OR: + return in1 == null || in2 == null ? null : in1.intOr(in2); + default: + return null; + } + } + + @Override + public ValueLocation modBeforeStore(int sizeout, int sizeinAddress, ValueLocation inAddress, + int sizeinValue, ValueLocation inValue) { + return inValue; + } + + @Override + public ValueLocation modAfterLoad(int sizeout, int sizeinAddress, ValueLocation inAddress, + int sizeinValue, ValueLocation inValue) { + return inValue; + } + + @Override + public ValueLocation fromConst(byte[] value) { + return ValueLocation.fromConst(Utils.bytesToLong(value, value.length, endian.isBigEndian()), + value.length); + } + + @Override + public ValueLocation fromConst(BigInteger value, int size, boolean isContextreg) { + return ValueLocation.fromConst(value.longValueExact(), size); + } + + @Override + public ValueLocation fromConst(BigInteger value, int size) { + return ValueLocation.fromConst(value.longValueExact(), size); + } + + @Override + public ValueLocation fromConst(long value, int size) { + return ValueLocation.fromConst(value, size); + } + + @Override + public byte[] toConcrete(ValueLocation value, Purpose purpose) { + throw new ConcretionError("Cannot make 'location' concrete", purpose); + } + + @Override + public long sizeOf(ValueLocation value) { + return value == null ? 0 : value.size(); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/LocationPcodeExecutorStatePiece.java similarity index 55% rename from Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java rename to Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/LocationPcodeExecutorStatePiece.java index fccad463bd..b384252044 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/LocationPcodeExecutorStatePiece.java @@ -22,32 +22,45 @@ import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; /** - * An auxiliary state piece that reports the address of the control value + * An auxiliary state piece that reports the location of the control value * *

    * This is intended for use as the right side of a {@link PairedPcodeExecutorState} or * {@link PairedPcodeExecutorStatePiece}. Except for unique spaces, sets are ignored, and gets - * simply echo back the address of the requested read. In unique spaces, the "address of" is treated - * as the value, so that values transiting unique space can correctly have their source addresses + * simply echo back the location of the requested read. In unique spaces, the "location" is treated + * as the value, so that values transiting unique space can correctly have their source locations * reported. */ -public class AddressOfPcodeExecutorStatePiece - implements PcodeExecutorStatePiece { +public class LocationPcodeExecutorStatePiece + implements PcodeExecutorStatePiece { private final Language language; + private final LocationPcodeArithmetic arithmetic; private final BytesPcodeArithmetic addressArithmetic; - private final Map unique = new HashMap<>(); + private final Map unique; /** - * Construct an "address of" state piece + * Construct a "location" state piece * * @param isBigEndian true if the control language is big endian */ - public AddressOfPcodeExecutorStatePiece(Language language) { + public LocationPcodeExecutorStatePiece(Language language) { this.language = language; - this.addressArithmetic = BytesPcodeArithmetic.forEndian(language.isBigEndian()); + boolean isBigEndian = language.isBigEndian(); + this.arithmetic = LocationPcodeArithmetic.forEndian(isBigEndian); + this.addressArithmetic = BytesPcodeArithmetic.forEndian(isBigEndian); + this.unique = new HashMap<>(); + } + + protected LocationPcodeExecutorStatePiece(Language language, + BytesPcodeArithmetic addressArithmetic, Map unique) { + this.language = language; + this.arithmetic = LocationPcodeArithmetic.forEndian(language.isBigEndian()); + this.addressArithmetic = addressArithmetic; + this.unique = unique; } @Override @@ -61,12 +74,19 @@ public class AddressOfPcodeExecutorStatePiece } @Override - public PcodeArithmetic

    getArithmetic() { - return AddressOfPcodeArithmetic.INSTANCE; + public PcodeArithmetic getArithmetic() { + return arithmetic; } @Override - public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize, Address val) { + public LocationPcodeExecutorStatePiece fork() { + return new LocationPcodeExecutorStatePiece(language, addressArithmetic, + new HashMap<>(unique)); + } + + @Override + public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize, + ValueLocation val) { if (!space.isUniqueSpace()) { return; } @@ -76,18 +96,23 @@ public class AddressOfPcodeExecutorStatePiece } @Override - public Address getVar(AddressSpace space, byte[] offset, int size, boolean quantize, + public ValueLocation getVar(AddressSpace space, byte[] offset, int size, boolean quantize, Reason reason) { long lOffset = addressArithmetic.toLong(offset, Purpose.LOAD); if (!space.isUniqueSpace()) { - return space.getAddress(lOffset); + return ValueLocation.fromVarnode(space.getAddress(lOffset), size); } return unique.get(lOffset); } + @Override + public Map getRegisterValues() { + return Map.of(); + } + @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { - throw new ConcretionError("Cannot make 'address of' concrete buffers", purpose); + throw new ConcretionError("Cannot make 'location' concrete buffers", purpose); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java index ea69019d56..104dad6375 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java @@ -15,12 +15,15 @@ */ package ghidra.pcode.exec; +import java.util.Map; + import org.apache.commons.lang3.tuple.Pair; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; /** @@ -82,6 +85,16 @@ public class PairedPcodeExecutorState implements PcodeExecutorState> getRegisterValues() { + return piece.getRegisterValues(); + } + + @Override + public PairedPcodeExecutorState fork() { + return new PairedPcodeExecutorState<>(piece.fork()); + } + @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { return piece.getConcreteBuffer(address, purpose); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java index 64ddc7bad0..6e63f83f13 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java @@ -15,12 +15,15 @@ */ package ghidra.pcode.exec; +import java.util.*; + import org.apache.commons.lang3.tuple.Pair; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; /** @@ -88,6 +91,26 @@ public class PairedPcodeExecutorStatePiece return arithmetic; } + @Override + public Map> getRegisterValues() { + Map leftRVs = left.getRegisterValues(); + Map rightRVs = right.getRegisterValues(); + Set union = new HashSet<>(); + union.addAll(leftRVs.keySet()); + union.addAll(rightRVs.keySet()); + Map> result = new HashMap<>(); + for (Register k : union) { + result.put(k, Pair.of(leftRVs.get(k), rightRVs.get(k))); + } + return result; + } + + @Override + public PairedPcodeExecutorStatePiece fork() { + return new PairedPcodeExecutorStatePiece<>(left.fork(), right.fork(), addressArithmetic, + arithmetic); + } + @Override public void setVar(AddressSpace space, A offset, int size, boolean quantize, Pair val) { left.setVar(space, offset, size, quantize, val.getLeft()); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java index 36cc91eedd..eb6aef9f5c 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java @@ -160,13 +160,70 @@ public interface PcodeArithmetic { * @param op * @param in1 * @param in2 - * @return + * @return the output value */ default T binaryOp(PcodeOp op, T in1, T in2) { return binaryOp(op.getOpcode(), op.getOutput().getSize(), op.getInput(0).getSize(), in1, op.getInput(1).getSize(), in2); } + /** + * Apply the {@link PcodeOp#PTRADD} operator to the given inputs + * + *

    + * The "pointer add" op takes three operands: base, index, size; and is used as a more compact + * representation of array index address computation. The {@code size} operand must be constant. + * Suppose {@code arr} is an array whose elements are {@code size} bytes each, and the address + * of its first element is {@code base}. The decompiler would likely render the + * {@link PcodeOp#PTRADD} op as {@code &arr[index]}. An equivalent SLEIGH expression is + * {@code base + index*size}. + * + *

    + * NOTE: This op is always a result of decompiler simplification, not low p-code generation, and + * so are not ordinarily used by a {@link PcodeExecutor}. + * + * @param sizeout the size (in bytes) of the output variable + * @param sizeinBase the size (in bytes) of the variable used for the array's base address + * @param inBase the value used as the array's base address + * @param sizeinIndex the size (in bytes) of the variable used for the index + * @param inIndex the value used as the index + * @param inSize the size of each array element in bytes + * @return the output value + */ + default T ptrAdd(int sizeout, int sizeinBase, T inBase, int sizeinIndex, T inIndex, + int inSize) { + T indexSized = binaryOp(PcodeOp.INT_MULT, sizeout, + sizeinIndex, inIndex, 4, fromConst(inSize, 4)); + return binaryOp(PcodeOp.INT_ADD, sizeout, + sizeinBase, inBase, sizeout, indexSized); + } + + /** + * Apply the {@link PcodeOp#PTRSUB} operator to the given inputs + * + *

    + * The "pointer subfield" op takes two operands: base, offset; and is used as a more specific + * representation of structure field address computation. Its behavior is exactly equivalent to + * {@link PcodeOp#INT_ADD}. Suppose {@code st} is a structure pointer with a field {@code f} + * located {@link offset} bytes into the structure, and {@code st} has the value {@code base}. + * The decompiler would likely render the {@link PcodeOp#PTRSUB} op as {@code &st->f}. An + * equivalent SLEIGH expression is {@code base + offset}. + * + *

    + * NOTE: This op is always a result of decompiler simplification, not low p-code generation, and + * so are not ordinarily used by a {@link PcodeExecutor}. + * + * @param sizeout the size (in bytes) of the output variable + * @param sizeinBase the size (in bytes) of the variable used for the structure's base address + * @param inBase the value used as the structure's base address + * @param sizeinOffset the size (in bytes) of the variable used for the offset + * @param inOffset the value used as the offset + * @return the output value + */ + default T ptrSub(int sizeout, int sizeinBase, T inBase, int sizeinOffset, T inOffset) { + return binaryOp(PcodeOp.INT_ADD, sizeout, sizeinBase, inBase, sizeinOffset, inOffset); + } + /** * Apply any modifications before a value is stored * diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java index cc359cc6ac..bb59ee9a30 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java @@ -215,12 +215,12 @@ public class PcodeExecutor { badOp(op); return; } - if (b instanceof UnaryOpBehavior) { - executeUnaryOp(op, (UnaryOpBehavior) b); + if (b instanceof UnaryOpBehavior unOp) { + executeUnaryOp(op, unOp); return; } - if (b instanceof BinaryOpBehavior) { - executeBinaryOp(op, (BinaryOpBehavior) b); + if (b instanceof BinaryOpBehavior binOp) { + executeBinaryOp(op, binOp); return; } switch (op.getOpcode()) { @@ -240,7 +240,7 @@ public class PcodeExecutor { executeIndirectBranch(op, frame); return; case PcodeOp.CALL: - executeCall(op, frame); + executeCall(op, frame, library); return; case PcodeOp.CALLIND: executeIndirectCall(op, frame); @@ -349,12 +349,12 @@ public class PcodeExecutor { Varnode inOffset = op.getInput(1); T offset = state.getVar(inOffset, reason); checkLoad(space, offset); - Varnode outvar = op.getOutput(); + Varnode outVar = op.getOutput(); - T out = state.getVar(space, offset, outvar.getSize(), true, reason); - T mod = arithmetic.modAfterLoad(outvar.getSize(), inOffset.getSize(), offset, - outvar.getSize(), out); - state.setVar(outvar, mod); + T out = state.getVar(space, offset, outVar.getSize(), true, reason); + T mod = arithmetic.modAfterLoad(outVar.getSize(), inOffset.getSize(), offset, + outVar.getSize(), out); + state.setVar(outVar, mod); } /** @@ -506,7 +506,7 @@ public class PcodeExecutor { * @param op the op * @param frame the frame */ - public void executeCall(PcodeOp op, PcodeFrame frame) { + public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary library) { Address target = op.getInput(0).getAddress(); branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame); branchToAddress(target); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java index 0be2d367c0..710be426d3 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java @@ -37,6 +37,9 @@ public interface PcodeExecutorState extends PcodeExecutorStatePiece { return getArithmetic(); } + @Override + PcodeExecutorState fork(); + /** * Use this state as the control, paired with the given auxiliary state. * diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java index 80ff70d1f1..4f62aec502 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java @@ -15,6 +15,7 @@ */ package ghidra.pcode.exec; +import java.util.Map; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; @@ -87,6 +88,13 @@ public interface PcodeExecutorStatePiece { */ PcodeArithmetic getArithmetic(); + /** + * Create a deep copy of this state + * + * @return the copy + */ + PcodeExecutorStatePiece fork(); + /** * Set the value of a register variable * @@ -219,6 +227,16 @@ public interface PcodeExecutorStatePiece { return getVar(address.getAddressSpace(), address.getOffset(), size, quantize, reason); } + /** + * Get all register values known to this state + * + *

    + * When the state acts as a cache, it should only return those cached. + * + * @return a map of registers and their values + */ + Map getRegisterValues(); + /** * Bind a buffer of concrete bytes at the given start address * diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java index 137b6291ca..0e7fe1f9d1 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java @@ -22,8 +22,9 @@ import ghidra.app.plugin.processors.sleigh.template.OpTpl; import ghidra.app.util.pcode.AbstractAppender; import ghidra.app.util.pcode.AbstractPcodeFormatter; import ghidra.pcodeCPort.slghsymbol.UserOpSymbol; -import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.*; import ghidra.program.model.listing.Instruction; +import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.PcodeOp; /** @@ -88,22 +89,49 @@ public class PcodeProgram { } } + /** + * Generate a p-code program from the given instruction, without overrides + * + * @param instruction the instruction + * @return the p-code program + */ + public static PcodeProgram fromInstruction(Instruction instruction) { + return fromInstruction(instruction, false); + } + /** * Generate a p-code program from the given instruction * * @param instruction the instruction - * @return the p-code program. + * @param includeOverrides as in {@link Instruction#getPcode(boolean)} + * @return the p-code program */ - public static PcodeProgram fromInstruction(Instruction instruction) { + public static PcodeProgram fromInstruction(Instruction instruction, boolean includeOverrides) { Language language = instruction.getPrototype().getLanguage(); if (!(language instanceof SleighLanguage)) { throw new IllegalArgumentException("Instruction must be parsed using Sleigh"); } - PcodeOp[] pcode = instruction.getPcode(false); + PcodeOp[] pcode = instruction.getPcode(includeOverrides); return new PcodeProgram((SleighLanguage) language, List.of(pcode), Map.of()); } + /** + * Generate a p-code program from a given program's inject library + * + * @param program the program + * @param name the name of the snippet + * @param type the type of the snippet + * @return the p-code program + */ + public static PcodeProgram fromInject(Program program, String name, int type) { + PcodeInjectLibrary library = program.getCompilerSpec().getPcodeInjectLibrary(); + InjectContext ctx = library.buildInjectContext(); + InjectPayload payload = library.getPayload(type, name); + PcodeOp[] pcode = payload.getPcode(program, ctx); + return new PcodeProgram((SleighLanguage) program.getLanguage(), List.of(pcode), Map.of()); + } + protected final SleighLanguage language; protected final List code; protected final Map useropNames = new HashMap<>(); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ValueLocation.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ValueLocation.java new file mode 100644 index 0000000000..caa24b7883 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ValueLocation.java @@ -0,0 +1,258 @@ +/* ### + * 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.pcode.exec; + +import java.math.BigInteger; +import java.util.*; +import java.util.stream.Collectors; + +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.VariableStorage; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; + +/** + * The location of a value + * + *

    + * This is an analog to {@link VariableStorage}, except that this records the actual storage + * location of the evaluated variable or expression. This does not incorporate storage of + * intermediate dereferenced values. For example, suppose {@code R0 = 0xdeadbeef}, and we want to + * evaluate {@code *:4 R0}. The storage would be {@code ram:deadbeef:4}, not + * {@code R0,ram:deadbeef:4}. + */ +public class ValueLocation { + + private static final AddressSpace CONST = + new GenericAddressSpace("const", 64, AddressSpace.TYPE_CONSTANT, 0); + + public static String vnToString(Varnode vn, Language language) { + Register register = + language == null ? null : language.getRegister(vn.getAddress(), vn.getSize()); + if (register != null) { + return String.format("%s:%d", register.getName(), vn.getSize()); + } + return String.format("%s:%d", vn.getAddress(), vn.getSize()); + } + + private static boolean isZero(Varnode vn) { + return vn.isConstant() && vn.getOffset() == 0; + } + + private static List removeLeading0s(List nodes) { + for (int i = 0; i < nodes.size(); i++) { + if (!isZero(nodes.get(i))) { + return nodes.subList(i, nodes.size()); + } + } + return List.of(); + } + + /** + * Generate the "location" of a constant + * + * @param value the value + * @param size the size of the constant in bytes + * @return the "location" + */ + public static ValueLocation fromConst(long value, int size) { + return new ValueLocation(new Varnode(CONST.getAddress(value), size)); + } + + /** + * Generate a location from a varnode + * + * @param address the dynamic address of the variable + * @param size the size of the variable in bytes + * @return the location + */ + public static ValueLocation fromVarnode(Address address, int size) { + return new ValueLocation(new Varnode(address, size)); + } + + private final List nodes; + + /** + * Construct a location from a list of varnodes + * + *

    + * Any leading varnodes which are constant 0s are removed. + * + * @param nodes the varnodes + */ + public ValueLocation(Varnode... nodes) { + this.nodes = removeLeading0s(List.of(nodes)); + } + + /** + * Construct a location from a list of varnodes + * + *

    + * Any leading varnodes which are constant 0s are removed. + * + * @param nodes the varnodes + */ + public ValueLocation(List nodes) { + this.nodes = removeLeading0s(List.copyOf(nodes)); + } + + /** + * Get the number of varnodes for this location + * + * @return the count + */ + public int nodeCount() { + return nodes.size(); + } + + /** + * Get the address of the first varnode + * + * @return the address, or null if this location has no varnodes + */ + public Address getAddress() { + return nodes.isEmpty() ? null : nodes.get(0).getAddress(); + } + + /** + * Render this location as a string, substituting registers where applicable + * + * @param language the optional language for register substitution + * @return the string + */ + public String toString(Language language) { + return nodes.stream().map(vn -> vnToString(vn, language)).collect(Collectors.joining(",")); + } + + @Override + public String toString() { + return toString(null); + } + + /** + * Apply a {@link PcodeOp#INT_OR} operator + * + *

    + * There is a very restrictive set of constraints for which this yields a non-null location. If + * either this or that is empty, the other is returned. Otherwise, the varnodes are arranged in + * pairs by taking one from each storage starting at the right, or least-significant varnode. + * Each pair must match in length, and one of the pair must be a constant zero. The non-zero + * varnode is taken. The unpaired varnodes to the left, if any, are all taken. If any pair does + * not match in length, or if neither is zero, the resulting location is null. This logic is to + * ensure location information is accrued during concatenation. + * + * @param that the other location + * @return the location + */ + public ValueLocation intOr(ValueLocation that) { + if (this.isEmpty()) { + return that; + } + if (that.isEmpty()) { + return this; + } + ListIterator itA = this.nodes.listIterator(this.nodeCount()); + ListIterator itB = that.nodes.listIterator(that.nodeCount()); + Varnode[] result = new Varnode[Math.max(this.nodeCount(), that.nodeCount())]; + int i = result.length; + while (itA.hasNext() && itB.hasPrevious()) { + Varnode vnA = itA.previous(); + Varnode vnB = itB.previous(); + if (vnA.getSize() != vnB.getSize()) { + return null; + } + if (isZero(vnA)) { + result[--i] = vnB; + } + else if (isZero(vnB)) { + result[--i] = vnA; + } + } + while (itA.hasPrevious()) { + result[--i] = itA.previous(); + } + while (itB.hasPrevious()) { + result[--i] = itB.previous(); + } + return new ValueLocation(result); + } + + /** + * If the location represents a constant, get its value + * + * @return the constant value + */ + public BigInteger getConst() { + BigInteger result = BigInteger.ZERO; + for (Varnode vn : nodes) { + if (!vn.isConstant()) { + return null; + } + result = result.shiftLeft(vn.getSize() * 8); + result = result.or(vn.getAddress().getOffsetAsBigInteger()); + } + return result; + } + + /** + * Apply a {@link PcodeOp#INT_LEFT} operator + * + *

    + * This requires the shift amount to represent an integral number of bytes. Otherwise, the + * result is null. This simply inserts a constant zero to the right, having the number of bytes + * indicated by the shift amount. This logic is to ensure location information is accrued during + * concatenation. + * + * @param amount the number of bits to shift + * @return the location. + */ + public ValueLocation shiftLeft(int amount) { + if (amount % 8 != 0) { + return null; + } + List result = new ArrayList<>(nodes); + result.add(new Varnode(CONST.getAddress(0), amount / 8)); + return new ValueLocation(result); + } + + /** + * Get the total size of this location in bytes + * + * @return the size in bytes + */ + public int size() { + int result = 0; + for (Varnode vn : nodes) { + result += vn.getSize(); + } + return result; + } + + /** + * Check if this location includes any varnodes + * + *

    + * Note that a location cannot consist entirely of constant zeros and be non-empty. The + * constructor will have removed them all. + * + * @return true if empty + */ + public boolean isEmpty() { + return nodes.isEmpty(); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java index 2fe8b9e30d..d7f5adb254 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java @@ -15,12 +15,16 @@ */ package ghidra.pcode.emu.taint; +import java.util.List; +import java.util.Map; + import ghidra.pcode.emu.taint.plain.TaintSpace; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; import ghidra.taint.model.TaintVec; @@ -116,6 +120,11 @@ public abstract class AbstractTaintPcodeExecutorStatePiece return space.get(offset, size); } + @Override + protected Map getRegisterValuesFromSpace(S space, List registers) { + return space.getRegisterValues(registers); + } + @Override public void clear() { for (S space : spaceMap.values()) { diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java index dbebde4d18..89824771f0 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java @@ -71,6 +71,21 @@ public class TaintPcodeExecutorStatePiece extends AbstractTaintPcodeExecutorStat protected TaintSpace newSpace(AddressSpace space) { return new TaintSpace(); } + + @Override + public AbstractSpaceMap fork() { + throw new UnsupportedOperationException(); + } + + @Override + public TaintSpace fork(TaintSpace s) { + throw new UnsupportedOperationException(); + } }; } + + @Override + public TaintPcodeExecutorStatePiece fork() { + throw new UnsupportedOperationException(); + } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java index fe2dc0d7b5..5637b0df6b 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java @@ -15,10 +15,10 @@ */ package ghidra.pcode.emu.taint.plain; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import ghidra.pcode.emu.taint.trace.TaintTraceSpace; +import ghidra.program.model.lang.Register; import ghidra.taint.model.TaintSet; import ghidra.taint.model.TaintVec; @@ -112,4 +112,20 @@ public class TaintSpace { public void clear() { taints.clear(); } + + public Map getRegisterValues(List registers) { + Map result = new HashMap<>(); + for (Register r : registers) { + long offset = r.getAddress().getOffset(); + TaintVec vec = new TaintVec(r.getNumBytes()); + for (int i = 0; i < vec.length; i++) { + TaintSet s = taints.get(offset + i); + if (s == null) { + continue; + } + } + result.put(r, vec); + } + return result; + } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java index cbda94751b..44c126d26a 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java @@ -82,9 +82,24 @@ public class TaintTracePcodeExecutorStatePiece PcodeTracePropertyAccess backing) { return new TaintTraceSpace(space, property); } + + @Override + public AbstractSpaceMap fork() { + throw new UnsupportedOperationException(); + } + + @Override + public TaintTraceSpace fork(TaintTraceSpace s) { + throw new UnsupportedOperationException(); + } }; } + @Override + public TaintTracePcodeExecutorStatePiece fork() { + throw new UnsupportedOperationException(); + } + /** * {@inheritDoc} * diff --git a/Ghidra/Features/Decompiler/src/main/doc/pcoderef.xml b/Ghidra/Features/Decompiler/src/main/doc/pcoderef.xml index 52cd43a88b..6c1394dbb2 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/pcoderef.xml +++ b/Ghidra/Features/Decompiler/src/main/doc/pcoderef.xml @@ -22,7 +22,7 @@ INT_SUB FLOAT_EQUAL - +P STORE INT_CARRY diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblyBuffer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblyBuffer.java index ed6020307a..0fc34e3b62 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblyBuffer.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblyBuffer.java @@ -82,12 +82,54 @@ public class AssemblyBuffer { return emit(asm.assembleLine(getNext(), line)); } + /** + * Assemble a line and patch into the buffer + * + *

    + * This will not grow the buffer, so the instruction being patched must already exist in the + * buffer. The typical use case is to fix up a reference: + * + *

    +	 * AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
    +	 * // ...
    +	 * Address jumpCheck = buf.getNext();
    +	 * buf.assemble("JMP 0x" + buf.getNext()); // Template must accommodate expected jump distance
    +	 * // ...
    +	 * Address labelCheck = buf.getNext();
    +	 * buf.assemble(jumpCheck, "JMP 0x" + labelCheck);
    +	 * buf.assemble("CMP ECX, 0");
    +	 * // ...
    +	 * 
    + * + *

    + * This does not check that the patched instruction matches length with the new instruction. In + * fact, the buffer does not remember instruction boundaries at all. If verification is needed, + * the caller should check the lengths of the returned byte arrays for the template and the + * patch. + * + * @param at the address of the instruction to patch + * @param line the line + * @return the resulting bytes for the assembled instruction + * @throws AssemblySyntaxException if the instruction cannot be parsed + * @throws AssemblySemanticException if the instruction cannot be encoded + * @throws IOException if the buffer cannot be written + */ + public byte[] assemble(Address at, String line) + throws AssemblySyntaxException, AssemblySemanticException, IOException { + byte[] full = baos.toByteArray(); + byte[] bytes = asm.assembleLine(at, line); + System.arraycopy(bytes, 0, full, (int) at.subtract(entry), bytes.length); + baos.reset(); + baos.write(full); + return bytes; + } + /** * Append arbitrary bytes to the buffer * * @param bytes the bytes to append * @return bytes - * @throws IOException if the bufgfer cannot be written + * @throws IOException if the buffer cannot be written */ public byte[] emit(byte[] bytes) throws IOException { baos.write(bytes); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblySelector.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblySelector.java index 88ec0ceac7..5733ba229c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblySelector.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblySelector.java @@ -82,6 +82,7 @@ public class AssemblySelector { */ public Collection filterParse(Collection parse) throws AssemblySyntaxException { + syntaxErrors.clear(); boolean gotOne = false; for (AssemblyParseResult pr : parse) { if (pr.isError()) { @@ -97,6 +98,26 @@ public class AssemblySelector { return parse; } + protected List filterCompatibleAndSort(AssemblyResolutionResults rr, + AssemblyPatternBlock ctx) throws AssemblySemanticException { + semanticErrors.clear(); + List sorted = new ArrayList<>(); + // Select only non-erroneous results whose contexts are compatible. + for (AssemblyResolution ar : rr) { + if (ar.isError()) { + semanticErrors.add((AssemblyResolvedError) ar); + continue; + } + AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; + sorted.add(rc); + } + if (sorted.isEmpty()) { + throw new AssemblySemanticException(semanticErrors); + } + sorted.sort(compareBySizeThenBits); + return sorted; + } + /** * Select an instruction from the possible results. * @@ -117,21 +138,7 @@ public class AssemblySelector { */ public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx) throws AssemblySemanticException { - List sorted = new ArrayList<>(); - // Select only non-erroneous results whose contexts are compatible. - for (AssemblyResolution ar : rr) { - if (ar.isError()) { - semanticErrors.add((AssemblyResolvedError) ar); - continue; - } - AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; - sorted.add(rc); - } - if (sorted.isEmpty()) { - throw new AssemblySemanticException(semanticErrors); - } - // Sort them - sorted.sort(compareBySizeThenBits); + List sorted = filterCompatibleAndSort(rr, ctx); // Pick just the first AssemblyResolvedPatterns res = sorted.get(0);