From f2b0883c704ed01dd7a78df15bb7460a12ed25aa Mon Sep 17 00:00:00 2001 From: d-millar <33498836+d-millar@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:38:05 -0500 Subject: [PATCH] GP-1268: Fix recording of multiple register banks. Supports GP-2870. --- .../RefreshRegistersScript.java | 14 ++++-- .../register/DebuggerRegistersProvider.java | 12 ++--- .../frida/FridaArmDebuggerMappingOpinion.java | 2 +- .../frida/FridaDebuggerPlatformOpinion.java | 2 +- .../lldb/LldbArmDebuggerMappingOpinion.java | 2 +- .../lldb/LldbDebuggerPlatformOpinion.java | 29 ++++++++++- .../service/model/DefaultStackRecorder.java | 7 ++- .../service/model/DefaultThreadRecorder.java | 50 ++++++++++++++----- .../service/model/DefaultTraceRecorder.java | 2 +- .../record/ObjectBasedTraceRecorder.java | 4 +- .../ghidra/app/services/TraceRecorder.java | 2 +- .../record/ObjectBasedTraceRecorderTest.java | 2 +- 12 files changed, 93 insertions(+), 35 deletions(-) diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/RefreshRegistersScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/RefreshRegistersScript.java index 18234e2400..cdc907ce53 100644 --- a/Ghidra/Debug/Debugger/ghidra_scripts/RefreshRegistersScript.java +++ b/Ghidra/Debug/Debugger/ghidra_scripts/RefreshRegistersScript.java @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import java.util.Set; + import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.script.GhidraScript; import ghidra.app.services.*; @@ -69,10 +71,12 @@ public class RefreshRegistersScript extends GhidraScript { // Now, we need to get the relevant recorder TraceRecorder recorder = modelService.getRecorder(current.getTrace()); // There's a chance of an NPE here if there is no "current frame" - TargetRegisterBank bank = - recorder.getTargetRegisterBank(current.getThread(), current.getFrame()); - // Now do the same to the bank as before - bank.invalidateCaches().get(); - bank.fetchElements(true).get(); + Set banks = + recorder.getTargetRegisterBanks(current.getThread(), current.getFrame()); + for (TargetRegisterBank bank : banks) { + // Now do the same to the bank as before + bank.invalidateCaches().get(); + bank.fetchElements(true).get(); + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java index cae61d45f6..a0070dbb60 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java @@ -1296,8 +1296,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter return AsyncUtils.NIL; } toRead.retainAll(regMapper.getRegistersOnTarget()); - TargetRegisterBank bank = recorder.getTargetRegisterBank(traceThread, current.getFrame()); - if (bank == null || !bank.isValid()) { + Set banks = recorder.getTargetRegisterBanks(traceThread, current.getFrame()); + if (banks == null || banks.isEmpty()) { Msg.error(this, "Current frame's bank does not exist"); return AsyncUtils.NIL; } @@ -1320,10 +1320,10 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter if (recorder.getSnap() != current.getSnap()) { return AsyncUtils.NIL; } - if (current.getFrame() == 0) { - // Should have been pushed by model. non-zero frames are poll-only - return AsyncUtils.NIL; - } +// if (current.getFrame() == 0) { +// // Should have been pushed by model. non-zero frames are poll-only +// return AsyncUtils.NIL; +// } TraceThread traceThread = current.getThread(); TargetThread targetThread = recorder.getTargetThread(traceThread); if (targetThread == null) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaArmDebuggerMappingOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaArmDebuggerMappingOpinion.java index 894c514c48..d4bff8ee9c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaArmDebuggerMappingOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaArmDebuggerMappingOpinion.java @@ -67,7 +67,7 @@ public class FridaArmDebuggerMappingOpinion implements DebuggerMappingOpinion { boolean is64Bit = arch.contains("AARCH64") || arch.contains("arm64") || arch.contains("arm"); String os = env.getOperatingSystem(); - if (os.contains("macos")) { + if (os.contains("macos") || os.contains("ios")) { if (is64Bit) { Msg.info(this, "Using os=" + os + " arch=" + arch); return Set.of(new FridaAarch64MacosOffer((TargetProcess) target)); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java index 0bc8f55c10..08db7f4cba 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java @@ -95,7 +95,7 @@ public class FridaDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinio } String lcOS = os.toLowerCase(); boolean isLinux = lcOS.contains("linux"); - boolean isMacOS = lcOS.contains("darwin") || lcOS.contains("macos"); + boolean isMacOS = lcOS.contains("darwin") || lcOS.contains("macos") || lcOS.contains("ios"); boolean isWindows = lcOS.contains("windows"); String lcArch = arch.toLowerCase(); // "arm" subsumes "arm64" diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbArmDebuggerMappingOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbArmDebuggerMappingOpinion.java index 04842b9a1b..23545ab032 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbArmDebuggerMappingOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbArmDebuggerMappingOpinion.java @@ -66,7 +66,7 @@ public class LldbArmDebuggerMappingOpinion implements DebuggerMappingOpinion { String arch = env.getArchitecture(); boolean is64Bit = arch.contains("AARCH64") || arch.contains("arm64"); String os = env.getOperatingSystem(); - if (os.contains("macos")) { + if (os.contains("macos") || os.contains("ios")) { if (is64Bit) { Msg.info(this, "Using os=" + os + " arch=" + arch); return Set.of(new LldbAarch64MacosOffer((TargetProcess) target)); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java index 626477fd98..56d6b559e6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java @@ -86,6 +86,33 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion } } + protected static class LldbDebuggerPlatformOffer extends AbstractDebuggerPlatformOffer { + public static LldbDebuggerPlatformOffer fromArchLCSP(String arch, + LanguageCompilerSpecPair lcsp) + throws CompilerSpecNotFoundException, LanguageNotFoundException { + return new LldbDebuggerPlatformOffer("Default LLDB for " + arch, lcsp.getCompilerSpec()); + } + + public LldbDebuggerPlatformOffer(String description, CompilerSpec cSpec) { + super(description, cSpec); + } + + @Override + public int getConfidence() { + return 10; + } + + @Override + public DebuggerPlatformMapper take(PluginTool tool, Trace trace) { + return new LldbDebuggerPlatformMapper(tool, trace, cSpec); + } + + @Override + public boolean isCreatorOf(DebuggerPlatformMapper mapper) { + return mapper.getClass() == LldbDebuggerPlatformMapper.class; + } + } + @Override protected Set getOffers(TraceObject object, long snap, TraceObject env, String debugger, String arch, String os, Endian endian, boolean includeOverrides) { @@ -95,7 +122,7 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion } String lcOS = os.toLowerCase(); boolean isLinux = lcOS.contains("linux"); - boolean isMacOS = lcOS.contains("darwin") || lcOS.contains("macos"); + boolean isMacOS = lcOS.contains("darwin") || lcOS.contains("macos") || lcOS.contains("ios"); boolean isWindows = lcOS.contains("windows"); String lcArch = arch.toLowerCase(); // "arm" subsumes "arm64" diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultStackRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultStackRecorder.java index 61262c0f08..7ba6ea8356 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultStackRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultStackRecorder.java @@ -117,7 +117,7 @@ public class DefaultStackRecorder implements ManagedStackRecorder { for (TargetObject p = successor; p != null; p = p.getParent()) { if (p instanceof TargetStackFrame) { if (!PathUtils.isIndex(p.getPath())) { - return 0; + throw new AssertionError("Invalid path index "+p.getPath()); } int index = Integer.decode(p.getIndex()); TargetStackFrame frame; @@ -125,7 +125,10 @@ public class DefaultStackRecorder implements ManagedStackRecorder { frame = stack.get(index); } if (!Objects.equals(p, frame)) { - return 0; + // NB: This really ought to be an error but the dead frames ask for updates + // after they're dead - until we can figure that out... + //throw new AssertionError("Recorder stack has lost synchronization"); + return -1; } return index; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java index ff5414226c..b6a8ce4e6f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.service.model.interfaces.*; +import ghidra.async.AsyncFence; import ghidra.async.AsyncUtils; import ghidra.dbg.target.*; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; @@ -48,7 +49,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { //private AbstractRecorderRegisterSet threadRegisters; protected TargetBreakpointSpecContainer threadBreakpointContainer; - protected Map regs = new HashMap<>(); + protected Map> regs = new HashMap<>(); protected Collection extraRegs; protected TargetExecutionState state = TargetExecutionState.ALIVE; @@ -173,16 +174,27 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { List tRegs = registers.stream().map(regMapper::traceToTarget).collect(Collectors.toList()); - TargetRegisterBank bank = getTargetRegisterBank(thread, frameLevel); - if (bank == null) { + Set banks = getTargetRegisterBank(thread, frameLevel); + if (banks == null) { throw new IllegalArgumentException( "Given thread and frame level does not have a live register bank"); } // NOTE: Cache update, if applicable, will cause recorder to write values to trace - return bank.readRegisters(tRegs).thenApply(regMapper::targetToTrace); + AsyncFence fence = new AsyncFence(); + Map result = new HashMap<>(); + for (TargetRegisterBank bank : banks) { + fence.include(bank.readRegisters(tRegs) + .thenApply(regMapper::targetToTrace) + .thenAccept(br -> { + synchronized (result) { + result.putAll(br); + } + })); + } + return fence.ready().thenApply(__ -> result); } - public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) { + public Set getTargetRegisterBank(TraceThread thread, int frameLevel) { return regs.get(frameLevel); } @@ -210,18 +222,23 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { doFetchAndInitRegMapper(bank); } int frameLevel = stackRecorder.getSuccessorFrameLevel(bank); - //System.err.println("offerRegisters " + this.targetThread.getDisplay() + ":" + frameLevel); - TargetRegisterBank old = regs.put(frameLevel, bank); - if (null != old) { + Set set = regs.get(frameLevel); + if (set == null) { + set = new HashSet<>(); + regs.put(frameLevel, set); + } + if (set.contains(bank)) { Msg.warn(this, "Unexpected register bank replacement"); } + set.add(bank); } @Override public void removeRegisters(TargetRegisterBank bank) { int frameLevel = stackRecorder.getSuccessorFrameLevel(bank); - TargetRegisterBank old = regs.remove(frameLevel); - if (bank != old) { + Set set = regs.get(frameLevel); + boolean remove = set.remove(bank); + if (!remove) { Msg.warn(this, "Unexpected register bank upon removal"); } } @@ -261,6 +278,9 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { } } int frameLevel = stackRecorder.getSuccessorFrameLevel(bank); + if (frameLevel < 0) { + return; + } long snap = recorder.getSnap(); String path = bank.getJoinedPath("."); TimedMsg.debug(this, "Reg values changed: " + updates.keySet()); @@ -364,13 +384,17 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { return regMapper.traceToTarget(ent.getValue()); }).collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - TargetRegisterBank bank = getTargetRegisterBank(traceThread, frameLevel); - if (bank == null) { + Set banks = getTargetRegisterBank(traceThread, frameLevel); + if (banks == null) { throw new IllegalArgumentException( "Given thread and frame level does not have a live register bank"); } // NOTE: Model + recorder will cause applicable trace updates - return bank.writeRegistersNamed(tVals).thenApply(__ -> null); + AsyncFence fence = new AsyncFence(); + for (TargetRegisterBank bank : banks) { + fence.include(bank.writeRegistersNamed(tVals).thenApply(__ -> null)); + } + return fence.ready(); } Address registerValueToTargetAddress(RegisterValue rv, byte[] value) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java index 4bed4ab24b..865a1bb4cd 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java @@ -232,7 +232,7 @@ public class DefaultTraceRecorder implements TraceRecorder { } @Override - public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) { + public Set getTargetRegisterBanks(TraceThread thread, int frameLevel) { DefaultThreadRecorder rec = getThreadRecorder(thread); return rec.getTargetRegisterBank(thread, frameLevel); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java index fdd73a1ae2..58e95f5ff0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java @@ -426,8 +426,8 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { } @Override - public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) { - return objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetRegisterBank.class); + public Set getTargetRegisterBanks(TraceThread thread, int frameLevel) { + return Set.of(objectRecorder.getTargetFrameInterface(thread, frameLevel, TargetRegisterBank.class)); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java index 29c87640e8..9cd3a0c9cd 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java @@ -332,7 +332,7 @@ public interface TraceRecorder { * @param frameLevel the frame level * @return the bank, or null */ - TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel); + Set getTargetRegisterBanks(TraceThread thread, int frameLevel); /** * Get the trace thread corresponding to the given target thread diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java index 484877e2d1..243047b4f0 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java @@ -512,7 +512,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU TraceObjectThread thread = (TraceObjectThread) recorder.getTraceThread(mb.testThread1); assertNotNull(thread); - assertEquals(mb.testBank1, recorder.getTargetRegisterBank(thread, 0)); + assertEquals(Set.of(mb.testBank1), recorder.getTargetRegisterBanks(thread, 0)); assertEquals(thread, recorder.getTraceThreadForSuccessor(mb.testBank1)); TraceObject traceBank = thread.getObject()