From e0ccc4883bb5bf1b27cc210f57ddc382bef837cb Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 3 Jan 2025 12:55:06 -0500 Subject: [PATCH] GP-5213: Fix editing registers presented as primitives. --- .../data/debugger-launchers/local-gdb.bat | 2 +- .../service/tracermi/TraceRmiTarget.java | 119 +++++++++++---- .../register/DebuggerRegistersProvider.java | 2 +- .../debug/gui/stack/DebuggerStackPanel.java | 6 +- .../PlaceEmuBreakpointActionItem.java | 14 +- .../emulation/ProgramEmulationUtils.java | 4 +- .../DBTraceObjectBreakpointLocation.java | 6 +- .../guest/DBTraceObjectRegisterSupport.java | 137 +++--------------- .../database/guest/InternalTracePlatform.java | 24 ++- .../database/module/DBTraceObjectModule.java | 6 +- .../database/stack/DBTraceObjectStack.java | 7 +- .../database/stack/DBTraceStackManager.java | 9 +- .../trace/database/target/DBTraceObject.java | 19 +-- .../database/target/DBTraceObjectManager.java | 4 +- .../trace/model/guest/TracePlatform.java | 10 +- .../model/memory/RegisterValueConverter.java | 119 +++++++++++++++ .../model/memory/RegisterValueException.java | 22 +++ .../model/memory/TraceObjectRegister.java | 4 +- .../trace/model/target/TraceObject.java | 3 +- .../trace/model/target/path/PathFilter.java | 6 +- .../trace/model/target/path/PathMatcher.java | 55 ++++--- .../trace/model/target/path/PathPattern.java | 28 +--- .../schema/PrimitiveTraceObjectSchema.java | 6 +- .../target/schema/TraceObjectSchema.java | 34 ++--- .../DBTraceObjectRegisterSupportTest.java | 18 +-- .../target/DBTraceObjectManagerTest.java | 7 + 26 files changed, 384 insertions(+), 287 deletions(-) create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/RegisterValueConverter.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/RegisterValueException.java diff --git a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.bat b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.bat index 4d121f60e2..1c475a9cf5 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.bat +++ b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.bat @@ -16,7 +16,7 @@ ::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target" ::@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH." ::@env OPT_START_CMD:StartCmd="starti" "Run command" "The gdb command to actually run the target." -::@env OPT_ARCH:str="i386:x86-64" "Architecture" "Target architecture" +::@env OPT_ARCH:str="auto" "Architecture" "Target architecture" ::@env OPT_ENDIAN:Endian="auto" "Endian" "Target byte order" diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java index 9dbc95db5a..84f6dc0c4d 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java @@ -16,6 +16,7 @@ package ghidra.app.plugin.core.debug.service.tracermi; import java.io.IOException; +import java.math.BigInteger; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.*; @@ -1130,24 +1131,87 @@ public class TraceRmiTarget extends AbstractTarget { throw new AssertionError(); } - protected TraceObject findRegisterObject(TraceObjectThread thread, int frame, String name) { + protected TraceObjectValue tryRegister(TraceObject container, PathFilter filter, String name) { + final PathFilter applied; + if (filter.isNone()) { + applied = PathMatcher.any( + new PathPattern(KeyPath.ROOT.key(name)), + new PathPattern(KeyPath.ROOT.key(name.toLowerCase())), + new PathPattern(KeyPath.ROOT.key(name.toUpperCase())), + new PathPattern(KeyPath.ROOT.index(name)), + new PathPattern(KeyPath.ROOT.index(name.toLowerCase())), + new PathPattern(KeyPath.ROOT.index(name.toUpperCase()))); + } + else { + applied = PathMatcher.any( + filter.applyKeys(Align.RIGHT, name), + filter.applyKeys(Align.RIGHT, name.toLowerCase()), + filter.applyKeys(Align.RIGHT, name.toUpperCase())); + } + TraceObjectValPath regValPath = + container.getSuccessors(Lifespan.at(getSnap()), applied).findFirst().orElse(null); + + if (regValPath == null) { + Msg.error(this, "Cannot find register object/value for " + name + " in " + container); + return null; + } + return regValPath.getLastEntry(); + } + + record FoundRegister(Register register, TraceObjectValue value) { + String name() { + return KeyPath.parseIfIndex(value.getEntryKey()); + } + } + + protected FoundRegister findRegister(TraceObject container, PathFilter filter, + Register register) { + TraceObjectValue val; + val = tryRegister(container, filter, register.getName()); + if (val != null) { + return new FoundRegister(register, val); + } + /** + * When checking for register validity, we consider it valid if it or any of its parents are + * valid, or any alias thereof. + */ + for (String alias : register.getAliases()) { + val = tryRegister(container, filter, alias); + if (val != null) { + return new FoundRegister(register, val); + } + } + Register parent = register.getParentRegister(); + if (parent == null) { + return null; + } + return findRegister(container, filter, parent); + } + + protected FoundRegister findRegister(TraceObjectThread thread, int frame, + Register register) { TraceObject container = thread.getObject().findRegisterContainer(frame); if (container == null) { Msg.error(this, "No register container for thread=" + thread + ",frame=" + frame); return null; } - PathMatcher matcher = - container.getSchema().searchFor(TraceObjectRegister.class, true); - PathFilter filter = matcher.applyKeys(Align.RIGHT, name) - .or(matcher.applyKeys(Align.RIGHT, name.toLowerCase())) - .or(matcher.applyKeys(Align.RIGHT, name.toUpperCase())); - TraceObjectValPath regValPath = - container.getCanonicalSuccessors(filter).findFirst().orElse(null); - if (regValPath == null) { - Msg.error(this, "Cannot find register object for " + name + " in " + container); - return null; + PathFilter filter = container.getSchema().searchFor(TraceObjectRegister.class, true); + return findRegister(container, filter, register); + } + + protected byte[] getBytes(RegisterValue rv) { + return Utils.bigIntegerToBytes(rv.getUnsignedValue(), rv.getRegister().getMinimumByteSize(), + true); + } + + protected RegisterValue retrieveAndCombine(TracePlatform platform, TraceThread thread, + int frameLevel, FoundRegister found, RegisterValue value) { + TraceMemorySpace regSpace = + thread.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, frameLevel, false); + if (regSpace == null) { + return new RegisterValue(found.register, BigInteger.ZERO).combineValues(value); } - return regValPath.getDestination(container); + return regSpace.getValue(platform, getSnap(), found.register).combineValues(value); } @Override @@ -1162,17 +1226,20 @@ public class TraceRmiTarget extends AbstractTarget { Msg.error(this, "Non-object trace with TraceRmi!"); return AsyncUtils.nil(); } - Register register = value.getRegister(); - String regName = register.getName(); - byte[] data = - Utils.bigIntegerToBytes(value.getUnsignedValue(), register.getMinimumByteSize(), true); + FoundRegister found = findRegister(tot, frameLevel, value.getRegister()); + if (found == null) { + Msg.warn(this, "Could not find register " + value.getRegister() + " in object model."); + } + else if (found.register != value.getRegister()) { + value = retrieveAndCombine(platform, thread, frameLevel, found, value); + } RemoteParameter paramThread = writeReg.params.get("thread"); if (paramThread != null) { return writeReg.method.invokeAsync(Map.ofEntries( Map.entry(paramThread.name(), tot.getObject()), - Map.entry(writeReg.params.get("name").name(), regName), - Map.entry(writeReg.params.get("value").name(), data))) + Map.entry(writeReg.params.get("name").name(), found.name()), + Map.entry(writeReg.params.get("value").name(), getBytes(value)))) .toCompletableFuture() .thenApply(__ -> null); } @@ -1187,18 +1254,17 @@ public class TraceRmiTarget extends AbstractTarget { } return writeReg.method.invokeAsync(Map.ofEntries( Map.entry(paramFrame.name(), tof.getObject()), - Map.entry(writeReg.params.get("name").name(), regName), - Map.entry(writeReg.params.get("value").name(), data))) + Map.entry(writeReg.params.get("name").name(), found.name()), + Map.entry(writeReg.params.get("value").name(), getBytes(value)))) .toCompletableFuture() .thenApply(__ -> null); } - TraceObject regObj = findRegisterObject(tot, frameLevel, regName); - if (regObj == null) { + if (found == null || !found.value.isObject()) { return AsyncUtils.nil(); } return writeReg.method.invokeAsync(Map.ofEntries( - Map.entry(writeReg.params.get("frame").name(), regObj), - Map.entry(writeReg.params.get("value").name(), data))) + Map.entry(writeReg.params.get("register").name(), found.value.getChild()), + Map.entry(writeReg.params.get("value").name(), getBytes(value)))) .toCompletableFuture() .thenApply(__ -> null); } @@ -1220,8 +1286,9 @@ public class TraceRmiTarget extends AbstractTarget { if (!(thread instanceof TraceObjectThread tot)) { return false; } - TraceObject regObj = findRegisterObject(tot, frame, register.getName()); - if (regObj == null) { + // May be primitive or object + FoundRegister found = findRegister(tot, frame, register); + if (found == null) { return false; } return true; 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 e99260e295..a27a48de5e 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 @@ -888,7 +888,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter CompletableFuture future = editor.setRegister(rv); future.exceptionally(ex -> { ex = AsyncUtils.unwrapThrowable(ex); - reportError("Edit Register", "Could not write target register", ex); + reportError(null, "Could not write target register", ex); return null; }); return; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java index 156a8cd724..c8bf0c592c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java @@ -39,7 +39,7 @@ import ghidra.trace.model.stack.TraceObjectStackFrame; import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.model.target.path.KeyPath; -import ghidra.trace.model.target.path.PathMatcher; +import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.target.schema.TraceObjectSchema; public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel @@ -195,8 +195,8 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel iface) { - PathMatcher matcher = root.searchFor(iface, true); - PathPattern pattern = matcher.getSingletonPattern(); + PathFilter filter = root.searchFor(iface, true); + PathPattern pattern = filter.getSingletonPattern(); if (pattern == null || pattern.countWildcards() != 1) { throw new IllegalArgumentException( "Cannot find unique " + iface.getSimpleName() + " container"); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java index 995417bc87..3ab7f6c363 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java @@ -28,7 +28,7 @@ import ghidra.trace.model.breakpoint.*; import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.iface.TraceObjectTogglable; import ghidra.trace.model.target.info.TraceObjectInterfaceUtils; -import ghidra.trace.model.target.path.PathMatcher; +import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.target.schema.TraceObjectSchema; import ghidra.trace.model.thread.*; import ghidra.trace.util.*; @@ -301,8 +301,8 @@ public class DBTraceObjectBreakpointLocation return threads; } - PathMatcher procMatcher = schema.searchFor(TraceObjectProcess.class, false); - return object.getAncestorsRoot(getLifespan(), procMatcher) + PathFilter procFilter = schema.searchFor(TraceObjectProcess.class, false); + return object.getAncestorsRoot(getLifespan(), procFilter) .flatMap(proc -> proc.getSource(object) .querySuccessorsInterface(getLifespan(), TraceObjectThread.class, true)) diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java index 7aa3cf86af..2f53cd60b8 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java @@ -15,12 +15,10 @@ */ package ghidra.trace.database.guest; -import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.List; import java.util.stream.Stream; -import ghidra.pcode.utils.Utils; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; @@ -31,8 +29,8 @@ import ghidra.trace.model.memory.*; import ghidra.trace.model.symbol.*; import ghidra.trace.model.target.*; import ghidra.trace.model.target.path.KeyPath; +import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.target.path.PathFilter.Align; -import ghidra.trace.model.target.path.PathMatcher; import ghidra.trace.model.target.schema.TraceObjectSchema; import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceEvents; @@ -51,108 +49,6 @@ public enum DBTraceObjectRegisterSupport { } }; - static class RegisterValueException extends Exception { - public RegisterValueException(String message) { - super(message); - } - } - - static class LazyValues { - private final TraceObjectValue registerValue; - private BigInteger value; - private int bitLength = -1; - private byte[] be; - private byte[] le; - - public LazyValues(TraceObjectValue registerValue) { - this.registerValue = registerValue; - } - - BigInteger convertRegisterValueToBigInteger() throws RegisterValueException { - Object val = registerValue.getValue(); - if (val instanceof String s) { - try { - return new BigInteger(s, 16); - } - catch (NumberFormatException e) { - throw new RegisterValueException( - "Invalid register value " + s + ". Must be hex digits only."); - } - } - else if (val instanceof byte[] arr) { - // NOTE: Reg object values are always big endian - return new BigInteger(1, arr); - } - else if (val instanceof Byte b) { - return BigInteger.valueOf(b); - } - else if (val instanceof Short s) { - return BigInteger.valueOf(s); - } - else if (val instanceof Integer i) { - return BigInteger.valueOf(i); - } - else if (val instanceof Long l) { - return BigInteger.valueOf(l); - } - else if (val instanceof Address a) { - return a.getOffsetAsBigInteger(); - } - throw new RegisterValueException( - "Cannot convert register value: (" + registerValue.getValue().getClass() + ") '" + - registerValue.getValue() + - "'"); - } - - int getRegisterValueBitLength() throws RegisterValueException { - Object objBitLength = registerValue.getParent() - .getValue(registerValue.getMinSnap(), TraceObjectRegister.KEY_BITLENGTH) - .getValue(); - if (!(objBitLength instanceof Number)) { - throw new RegisterValueException( - "Register length is not numeric: (" + objBitLength.getClass() + ") '" + - objBitLength + "'"); - } - return ((Number) objBitLength).intValue(); - } - - BigInteger getValue() throws RegisterValueException { - if (value != null) { - return value; - } - return value = convertRegisterValueToBigInteger(); - } - - int getBitLength() throws RegisterValueException { - if (bitLength != -1) { - return bitLength; - } - return bitLength = getRegisterValueBitLength(); - } - - int getByteLength() throws RegisterValueException { - return (getBitLength() + 7) / 8; - } - - byte[] getBytesBigEndian() throws RegisterValueException { - if (be != null) { - return be; - } - return be = Utils.bigIntegerToBytes(getValue(), getByteLength(), true); - } - - byte[] getBytesLittleEndian() throws RegisterValueException { - if (le != null) { - return le; - } - return le = Utils.bigIntegerToBytes(getValue(), getByteLength(), false); - } - - public byte[] getBytes(boolean isBigEndian) throws RegisterValueException { - return isBigEndian ? getBytesBigEndian() : getBytesLittleEndian(); - } - } - protected AddressSpace findRegisterOverlay(TraceObject object) { TraceObject container = object .findCanonicalAncestorsInterface(TraceObjectRegisterContainer.class) @@ -173,7 +69,8 @@ public enum DBTraceObjectRegisterSupport { } protected void onValueCreatedTransferToPlatformRegister(TraceObjectValue registerValue, - TracePlatform platform, String name, LazyValues lazy) throws RegisterValueException { + TracePlatform platform, String name, RegisterValueConverter rvc) + throws RegisterValueException { Register register = platform.getLanguage().getRegister(name); if (register == null) { return; @@ -188,7 +85,7 @@ public enum DBTraceObjectRegisterSupport { long minSnap = registerValue.getMinSnap(); if (hostSpace.isMemorySpace()) { mem.getMemorySpace(hostSpace, true) - .setValue(platform, minSnap, new RegisterValue(register, lazy.getValue())); + .setValue(platform, minSnap, new RegisterValue(register, rvc.getValue())); } else if (hostSpace.isRegisterSpace()) { AddressSpace overlay = findRegisterOverlay(registerValue); @@ -196,7 +93,7 @@ public enum DBTraceObjectRegisterSupport { return; } mem.getMemorySpace(overlay, true) - .setValue(platform, minSnap, new RegisterValue(register, lazy.getValue())); + .setValue(platform, minSnap, new RegisterValue(register, rvc.getValue())); } else { throw new AssertionError(); @@ -205,10 +102,10 @@ public enum DBTraceObjectRegisterSupport { protected void transferValueToPlatformRegister(TraceObjectValue registerValue, TracePlatform platform, TraceMemorySpace mem, Register register) { - LazyValues lazy = new LazyValues(registerValue); + RegisterValueConverter rvc = new RegisterValueConverter(registerValue); try { mem.setValue(platform, registerValue.getMinSnap(), - new RegisterValue(register, lazy.getValue())); + new RegisterValue(register, rvc.getValue())); } catch (RegisterValueException e) { Msg.error(this, e.getMessage()); @@ -278,10 +175,10 @@ public enum DBTraceObjectRegisterSupport { Address address = mem.getAddressSpace().getOverlayAddress(label.getAddress()); for (TraceObjectValue registerValue : it(registerObject.getOrderedValues( label.getLifespan(), TraceObjectRegister.KEY_VALUE, true))) { - LazyValues lazy = new LazyValues(registerValue); + RegisterValueConverter rvc = new RegisterValueConverter(registerValue); try { long minSnap = registerValue.getMinSnap(); - mem.putBytes(minSnap, address, ByteBuffer.wrap(lazy.getBytes(isBigEndian))); + mem.putBytes(minSnap, address, ByteBuffer.wrap(rvc.getBytes(isBigEndian))); } catch (RegisterValueException e) { Msg.error(this, e.getMessage()); @@ -293,15 +190,15 @@ public enum DBTraceObjectRegisterSupport { throws RegisterValueException { TraceObject registerObject = registerValue.getParent(); Trace trace = registerValue.getTrace(); - LazyValues lazy = new LazyValues(registerValue); + RegisterValueConverter rvc = new RegisterValueConverter(registerValue); String name = getRegisterName(registerObject); TracePlatformManager platformManager = trace.getPlatformManager(); onValueCreatedTransferToPlatformRegister(registerValue, platformManager.getHostPlatform(), - name, lazy); + name, rvc); for (TracePlatform platform : platformManager.getGuestPlatforms()) { - onValueCreatedTransferToPlatformRegister(registerValue, platform, name, lazy); + onValueCreatedTransferToPlatformRegister(registerValue, platform, name, rvc); } TraceNamespaceSymbolView namespaces = trace.getSymbolManager().namespaces(); @@ -311,7 +208,7 @@ public enum DBTraceObjectRegisterSupport { for (TraceLabelSymbol label : trace.getSymbolManager() .labels() .getChildrenNamed(name, nsRegMapBE)) { - transferRegisterValueToLabel(registerValue, label, lazy.getBytesBigEndian()); + transferRegisterValueToLabel(registerValue, label, rvc.getBytesBigEndian()); } } TraceNamespaceSymbol nsRegMapLE = @@ -320,7 +217,7 @@ public enum DBTraceObjectRegisterSupport { for (TraceLabelSymbol label : trace.getSymbolManager() .labels() .getChildrenNamed(name, nsRegMapLE)) { - transferRegisterValueToLabel(registerValue, label, lazy.getBytesLittleEndian()); + transferRegisterValueToLabel(registerValue, label, rvc.getBytesLittleEndian()); } } } @@ -349,10 +246,10 @@ public enum DBTraceObjectRegisterSupport { if (schema == null) { return; } - PathMatcher matcher = schema.searchFor(TraceObjectRegister.class, true); - matcher = matcher.applyKeys(Align.RIGHT, List.of(label.getName())); + PathFilter filter = schema.searchFor(TraceObjectRegister.class, true); + PathFilter applied = filter.applyKeys(Align.RIGHT, List.of(label.getName())); for (TraceObjectValPath path : it( - objectManager.getValuePaths(label.getLifespan(), matcher))) { + objectManager.getValuePaths(label.getLifespan(), applied))) { Object regRaw = path.getDestinationValue(objectManager.getRootObject()); if (regRaw instanceof TraceObject regObj) { transferRegisterObjectToLabel(regObj, label, isBigEndian); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java index 199e2f01d1..220acddcac 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java @@ -26,9 +26,8 @@ import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceObjectRegister; import ghidra.trace.model.symbol.*; import ghidra.trace.model.target.TraceObject; -import ghidra.trace.model.target.path.KeyPath; +import ghidra.trace.model.target.path.*; import ghidra.trace.model.target.path.PathFilter.Align; -import ghidra.trace.model.target.path.PathMatcher; import ghidra.trace.model.target.schema.TraceObjectSchema; import ghidra.trace.util.TraceRegisterUtils; import ghidra.util.LockHold; @@ -112,34 +111,31 @@ public interface InternalTracePlatform extends TracePlatform { } @Override - default PathMatcher getConventionalRegisterPath(TraceObjectSchema schema, KeyPath path, + default PathFilter getConventionalRegisterPath(TraceObjectSchema schema, KeyPath path, Collection names) { - PathMatcher matcher = schema.searchFor(TraceObjectRegister.class, path, true); - if (matcher.isNone()) { - return matcher; + PathFilter filter = schema.searchFor(TraceObjectRegister.class, path, true); + if (filter.isNone()) { + return PathFilter.NONE; } - PathMatcher result = new PathMatcher(); - for (String name : names) { - result.addAll(matcher.applyKeys(Align.RIGHT, List.of(name))); - } - return result; + return PathMatcher.any(names.stream() + .flatMap(n -> filter.applyKeys(Align.RIGHT, List.of(n)).getPatterns().stream())); } @Override - default PathMatcher getConventionalRegisterPath(TraceObjectSchema schema, KeyPath path, + default PathFilter getConventionalRegisterPath(TraceObjectSchema schema, KeyPath path, Register register) { return getConventionalRegisterPath(schema, path, getConventionalRegisterObjectNames(register)); } @Override - default PathMatcher getConventionalRegisterPath(TraceObject container, Register register) { + default PathFilter getConventionalRegisterPath(TraceObject container, Register register) { return getConventionalRegisterPath(container.getSchema(), container.getCanonicalPath(), register); } @Override - default PathMatcher getConventionalRegisterPath(AddressSpace space, Register register) { + default PathFilter getConventionalRegisterPath(AddressSpace space, Register register) { KeyPath path = KeyPath.parse(space.getName()); TraceObjectSchema rootSchema = getTrace().getObjectManager().getRootSchema(); if (rootSchema == null) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java index 017e018bdf..0c433728af 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java @@ -28,8 +28,8 @@ import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.iface.TraceObjectInterface; import ghidra.trace.model.target.info.TraceObjectInterfaceUtils; import ghidra.trace.model.target.path.KeyPath; +import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.target.path.PathFilter.Align; -import ghidra.trace.model.target.path.PathMatcher; import ghidra.trace.model.target.schema.TraceObjectSchema; import ghidra.trace.util.*; import ghidra.util.LockHold; @@ -255,8 +255,8 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte @Override public TraceObjectSection getSectionByName(String sectionName) { - PathMatcher matcher = object.getSchema().searchFor(TraceObjectSection.class, true); - PathMatcher applied = matcher.applyKeys(Align.LEFT, List.of(sectionName)); + PathFilter filter = object.getSchema().searchFor(TraceObjectSection.class, true); + PathFilter applied = filter.applyKeys(Align.LEFT, List.of(sectionName)); return object.getSuccessors(getLifespan(), applied) .map(p -> p.getDestination(object).queryInterface(TraceObjectSection.class)) .findAny() diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStack.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStack.java index ff81c49c3d..57934672fc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStack.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStack.java @@ -24,7 +24,8 @@ import ghidra.trace.database.target.DBTraceObjectInterface; import ghidra.trace.model.Lifespan; import ghidra.trace.model.stack.*; import ghidra.trace.model.target.TraceObject; -import ghidra.trace.model.target.path.*; +import ghidra.trace.model.target.path.KeyPath; +import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.target.schema.TraceObjectSchema; import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; @@ -100,9 +101,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf protected TraceObjectStackFrame doAddStackFrame(int level) { try (LockHold hold = object.getTrace().lockWrite()) { - PathMatcher matcher = + PathFilter filter = object.getSchema().searchFor(TraceObjectStackFrame.class, true); - KeyPath relPath = matcher.applyKeys(KeyPath.makeIndex(level)).getSingletonPath(); + KeyPath relPath = filter.applyKeys(KeyPath.makeIndex(level)).getSingletonPath(); if (relPath == null) { throw new IllegalStateException("Could not determine where to create new frame"); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java index 60c8d584cd..40736a2780 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java @@ -32,7 +32,8 @@ import ghidra.trace.model.Lifespan; import ghidra.trace.model.stack.*; import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.iface.TraceObjectInterface; -import ghidra.trace.model.target.path.*; +import ghidra.trace.model.target.path.KeyPath; +import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceChangeRecord; @@ -97,12 +98,12 @@ public class DBTraceStackManager implements TraceStackManager, DBTraceManager { public static PathFilter single(TraceObject seed, Class targetIf) { - PathMatcher stackMatcher = seed.getSchema().searchFor(targetIf, false); - if (stackMatcher.getSingletonPath() == null) { + PathFilter stackFilter = seed.getSchema().searchFor(targetIf, false); + if (stackFilter.getSingletonPath() == null) { throw new IllegalStateException("Schema doesn't provide a unique " + targetIf.getSimpleName() + " for " + seed.getCanonicalPath()); } - return stackMatcher.getSingletonPattern(); + return stackFilter.getSingletonPattern(); } protected TraceObjectStack doGetOrAddObjectStack(TraceThread thread, long snap, diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java index 2b7d0563b9..669bf219b1 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java @@ -36,7 +36,8 @@ import ghidra.trace.model.target.*; import ghidra.trace.model.target.iface.TraceObjectInterface; import ghidra.trace.model.target.info.TraceObjectInterfaceFactory.Constructor; import ghidra.trace.model.target.info.TraceObjectInterfaceUtils; -import ghidra.trace.model.target.path.*; +import ghidra.trace.model.target.path.KeyPath; +import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.target.schema.TraceObjectSchema; import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceEvents; @@ -681,8 +682,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { public Stream findAncestorsInterface(Lifespan span, Class iface) { // This is a sort of meet-in-the-middle. The type search must originate from the root - PathMatcher matcher = getManager().getRootSchema().searchFor(iface, false); - return getAncestorsRoot(span, matcher); + PathFilter filter = getManager().getRootSchema().searchFor(iface, false); + return getAncestorsRoot(span, filter); } @Override @@ -694,8 +695,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { public TraceObject findOrCreateCanonicalAncestorInterface( Class iface) { - PathMatcher matcher = getManager().getRootSchema().searchFor(iface, false); - return path.streamMatchingAncestry(matcher) + PathFilter filter = getManager().getRootSchema().searchFor(iface, false); + return path.streamMatchingAncestry(filter) .limit(1) .map(kp -> manager.createObject(kp)) .findAny() @@ -711,9 +712,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { public Stream findCanonicalAncestorsInterface( Class iface) { // This is a sort of meet-in-the-middle. The type search must originate from the root - PathMatcher matcher = getManager().getRootSchema().searchFor(iface, false); + PathFilter filter = getManager().getRootSchema().searchFor(iface, false); try (LockHold hold = manager.trace.lockRead()) { - return path.streamMatchingAncestry(matcher) + return path.streamMatchingAncestry(filter) .map(kp -> manager.getObjectByCanonicalPath(kp)); } } @@ -741,8 +742,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { @Override public Stream findSuccessorsInterface(Lifespan span, Class iface, boolean requireCanonical) { - PathMatcher matcher = getSchema().searchFor(iface, requireCanonical); - return getSuccessors(span, matcher).filter(p -> isActuallyInterface(p, iface)); + PathFilter filter = getSchema().searchFor(iface, requireCanonical); + return getSuccessors(span, filter).filter(p -> isActuallyInterface(p, iface)); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java index 252010d9b5..8a04ad533d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java @@ -469,8 +469,8 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager if (rootSchema == null) { throw new IllegalStateException("There is no schema. Create a root object."); } - PathMatcher matcher = rootSchema.searchFor(iface, true); - return getValuePaths(span, matcher).filter(p -> { + PathFilter filter = rootSchema.searchFor(iface, true); + return getValuePaths(span, filter).filter(p -> { TraceObject object = p.getDestination(getRootObject()); if (object == null) { Msg.error(this, "NULL VALUE! " + p.getLastEntry()); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java index 2203f55323..006180f0b5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java @@ -25,7 +25,7 @@ import ghidra.trace.model.memory.TraceObjectRegister; import ghidra.trace.model.symbol.TraceLabelSymbol; import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.path.KeyPath; -import ghidra.trace.model.target.path.PathMatcher; +import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.target.schema.TraceObjectSchema; /** @@ -192,7 +192,7 @@ public interface TracePlatform { * @param names the possible names of the register on the target * @return the path matcher, possibly empty */ - PathMatcher getConventionalRegisterPath(TraceObjectSchema schema, KeyPath path, + PathFilter getConventionalRegisterPath(TraceObjectSchema schema, KeyPath path, Collection names); /** @@ -207,7 +207,7 @@ public interface TracePlatform { * @param register the platform register * @return the path matcher, possibly empty */ - PathMatcher getConventionalRegisterPath(TraceObjectSchema schema, KeyPath path, + PathFilter getConventionalRegisterPath(TraceObjectSchema schema, KeyPath path, Register register); /** @@ -218,7 +218,7 @@ public interface TracePlatform { * @param register the platform register * @return that path matcher, possibly empty, or null if the trace has no root schema */ - PathMatcher getConventionalRegisterPath(TraceObject container, Register register); + PathFilter getConventionalRegisterPath(TraceObject container, Register register); /** * Get the expected path where an object defining the register value would be @@ -228,7 +228,7 @@ public interface TracePlatform { * @param register the platform register * @return the path matcher, or null if there is no root schema */ - PathMatcher getConventionalRegisterPath(AddressSpace overlay, Register register); + PathFilter getConventionalRegisterPath(AddressSpace overlay, Register register); /** * Add a label the conventionally maps the value of a {@link TraceObjectRegister} in the object diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/RegisterValueConverter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/RegisterValueConverter.java new file mode 100644 index 0000000000..e81d4312e2 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/RegisterValueConverter.java @@ -0,0 +1,119 @@ +/* ### + * 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.trace.model.memory; + +import java.math.BigInteger; + +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.Address; +import ghidra.trace.model.target.TraceObjectValue; + +public class RegisterValueConverter { + private final TraceObjectValue registerValue; + private BigInteger value; + private int bitLength = -1; + private byte[] be; + private byte[] le; + + public RegisterValueConverter(TraceObjectValue registerValue) { + this.registerValue = registerValue; + } + + public static BigInteger convertValueToBigInteger(Object val) throws RegisterValueException { + if (val instanceof String s) { + try { + return new BigInteger(s, 16); + } + catch (NumberFormatException e) { + throw new RegisterValueException( + "Invalid register value " + s + ". Must be hex digits only."); + } + } + else if (val instanceof byte[] arr) { + // NOTE: Reg object values are always big endian + return new BigInteger(1, arr); + } + else if (val instanceof Byte b) { + return BigInteger.valueOf(b); + } + else if (val instanceof Short s) { + return BigInteger.valueOf(s); + } + else if (val instanceof Integer i) { + return BigInteger.valueOf(i); + } + else if (val instanceof Long l) { + return BigInteger.valueOf(l); + } + else if (val instanceof Address a) { + return a.getOffsetAsBigInteger(); + } + throw new RegisterValueException( + "Cannot convert register value: (" + val.getClass() + ") '" + val + "'"); + } + + BigInteger convertRegisterValueToBigInteger() throws RegisterValueException { + return convertValueToBigInteger(registerValue.getValue()); + } + + int getRegisterValueBitLength() throws RegisterValueException { + Object objBitLength = registerValue.getParent() + .getValue(registerValue.getMinSnap(), TraceObjectRegister.KEY_BITLENGTH) + .getValue(); + if (!(objBitLength instanceof Number numBitLength)) { + throw new RegisterValueException( + "Register length is not numeric: (" + objBitLength.getClass() + ") '" + + objBitLength + "'"); + } + return numBitLength.intValue(); + } + + public BigInteger getValue() throws RegisterValueException { + if (value != null) { + return value; + } + return value = convertRegisterValueToBigInteger(); + } + + int getBitLength() throws RegisterValueException { + if (bitLength != -1) { + return bitLength; + } + return bitLength = getRegisterValueBitLength(); + } + + int getByteLength() throws RegisterValueException { + return (getBitLength() + 7) / 8; + } + + public byte[] getBytesBigEndian() throws RegisterValueException { + if (be != null) { + return be; + } + return be = Utils.bigIntegerToBytes(getValue(), getByteLength(), true); + } + + public byte[] getBytesLittleEndian() throws RegisterValueException { + if (le != null) { + return le; + } + return le = Utils.bigIntegerToBytes(getValue(), getByteLength(), false); + } + + public byte[] getBytes(boolean isBigEndian) throws RegisterValueException { + return isBigEndian ? getBytesBigEndian() : getBytesLittleEndian(); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/RegisterValueException.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/RegisterValueException.java new file mode 100644 index 0000000000..cb01f45a50 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/RegisterValueException.java @@ -0,0 +1,22 @@ +/* ### + * 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.trace.model.memory; + +public class RegisterValueException extends Exception { + public RegisterValueException(String message) { + super(message); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java index dece014ed7..cfef708384 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java @@ -42,8 +42,8 @@ import ghidra.trace.model.thread.TraceObjectThread; * a preferred presentation. In the tree convention, each register is presented with this interface. * The name is taken from the object key, the length in bits is given in the attribute * {@link #KEY_BITLENGTH}, and the value is given in the attribute - * {@link TraceObjectInterface#KEY_VALUE}. Some connectors may present registers as primitive - * children of the container, but that convention is discouraged. + * {@link TraceObjectInterface#KEY_VALUE}. Alternatively, connectors may present registers as + * primitive children of the container. * * *

diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java index 357fe663ec..2a2e9344e5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java @@ -491,7 +491,8 @@ public interface TraceObject extends TraceUniqueObject { *

* If an object has a disjoint life, i.e., multiple canonical parents, then only the * least-recent of those is traversed. Aliased keys are excluded; those can't be canonical - * anyway. + * anyway. By definition, a primitive value is not canonical, even if it is the final value in + * the path. * * @param relativeFilter filter on the relative path from this object to desired successors * @return the stream of value paths diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathFilter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathFilter.java index 13f6fce253..1223a11ff9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathFilter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathFilter.java @@ -79,8 +79,8 @@ public interface PathFilter { } @Override - public Collection getPatterns() { - return List.of(); + public Set getPatterns() { + return Set.of(); } @Override @@ -263,7 +263,7 @@ public interface PathFilter { * * @return the patterns */ - Collection getPatterns(); + Set getPatterns(); /** * Remove count elements from the right diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathMatcher.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathMatcher.java index 3008fc0d22..9a2e4338d7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathMatcher.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathMatcher.java @@ -17,24 +17,30 @@ package ghidra.trace.model.target.path; import java.util.*; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; public class PathMatcher implements PathFilter { protected static final Set WILD_SINGLETON = Set.of(""); - protected final Set patterns = new HashSet<>(); + protected final Set patterns; - public void addPattern(KeyPath pattern) { - patterns.add(new PathPattern(pattern)); + public static PathMatcher any(Stream patterns) { + return new PathMatcher(patterns.collect(Collectors.toUnmodifiableSet())); } - public void addPattern(PathPattern pattern) { - patterns.add(pattern); + public static PathMatcher any(Collection filters) { + return any(filters.stream().flatMap(f -> f.getPatterns().stream())); } - public void addAll(PathMatcher matcher) { - patterns.addAll(matcher.patterns); + public static PathMatcher any(PathFilter... filters) { + return any(Stream.of(filters).flatMap(f -> f.getPatterns().stream())); + } + + PathMatcher(Set patterns) { + this.patterns = patterns; } @Override @@ -59,19 +65,10 @@ public class PathMatcher implements PathFilter { @Override public PathFilter or(PathFilter that) { - PathMatcher result = new PathMatcher(); - result.patterns.addAll(this.patterns); - if (that instanceof PathMatcher) { - PathMatcher matcher = (PathMatcher) that; - result.patterns.addAll(matcher.patterns); - } - else if (that instanceof PathPattern) { - result.patterns.add((PathPattern) that); - } - else { - throw new AssertionError(); - } - return result; + Set patterns = new HashSet<>(); + patterns.addAll(this.patterns); + patterns.addAll(that.getPatterns()); + return new PathMatcher(Collections.unmodifiableSet(patterns)); } /** @@ -123,7 +120,7 @@ public class PathMatcher implements PathFilter { } @Override - public Collection getPatterns() { + public Set getPatterns() { return patterns; } @@ -189,19 +186,19 @@ public class PathMatcher implements PathFilter { @Override public PathMatcher applyKeys(Align align, List indices) { - PathMatcher result = new PathMatcher(); - for (PathPattern pat : patterns) { - result.addPattern(pat.applyKeys(align, indices)); + Set patterns = new HashSet<>(); + for (PathPattern pat : this.patterns) { + patterns.add(pat.applyKeys(align, indices)); } - return result; + return new PathMatcher(Collections.unmodifiableSet(patterns)); } @Override public PathMatcher removeRight(int count) { - PathMatcher result = new PathMatcher(); - for (PathPattern pat : patterns) { - pat.doRemoveRight(count, result); + Set patterns = new HashSet<>(); + for (PathPattern pat : this.patterns) { + pat.doRemoveRight(count, patterns); } - return result; + return new PathMatcher(Collections.unmodifiableSet(patterns)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathPattern.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathPattern.java index e81eb06498..fa2861f838 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathPattern.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/path/PathPattern.java @@ -78,19 +78,7 @@ public class PathPattern implements PathFilter { if (this.equals(that)) { return this; } - PathMatcher result = new PathMatcher(); - result.addPattern(this); - if (that instanceof PathPattern) { - result.addPattern(this); - } - else if (that instanceof PathMatcher) { - PathMatcher matcher = (PathMatcher) that; - result.patterns.addAll(matcher.patterns); - } - else { - throw new AssertionError(); - } - return result; + return PathMatcher.any(this, that); } public static boolean isWildcard(String pat) { @@ -190,8 +178,8 @@ public class PathPattern implements PathFilter { } @Override - public Collection getPatterns() { - return List.of(this); + public Set getPatterns() { + return Set.of(this); } @Override @@ -315,18 +303,18 @@ public class PathPattern implements PathFilter { return result; } - void doRemoveRight(int count, PathMatcher result) { + void doRemoveRight(int count, Set result) { KeyPath parent = pattern.parent(count); if (parent == null) { return; } - result.addPattern(parent); + result.add(new PathPattern(parent)); } @Override public PathMatcher removeRight(int count) { - PathMatcher result = new PathMatcher(); - doRemoveRight(count, result); - return result; + Set patterns = new HashSet<>(); + doRemoveRight(count, patterns); + return new PathMatcher(Collections.unmodifiableSet(patterns)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/PrimitiveTraceObjectSchema.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/PrimitiveTraceObjectSchema.java index b29a918080..2ed7d1d671 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/PrimitiveTraceObjectSchema.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/PrimitiveTraceObjectSchema.java @@ -24,7 +24,7 @@ import ghidra.trace.model.TraceExecutionState; import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.iface.TraceObjectInterface; import ghidra.trace.model.target.path.KeyPath; -import ghidra.trace.model.target.path.PathMatcher; +import ghidra.trace.model.target.path.PathFilter; /** * The schemas common to all contexts, as they describe the primitive and built-in types. @@ -202,9 +202,9 @@ public enum PrimitiveTraceObjectSchema implements TraceObjectSchema { } @Override - public PathMatcher searchFor(Class type, + public PathFilter searchFor(Class type, boolean requireCanonical) { - return new PathMatcher(); + return PathFilter.NONE; } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/TraceObjectSchema.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/TraceObjectSchema.java index 32ff9c5388..de224b6d71 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/TraceObjectSchema.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/schema/TraceObjectSchema.java @@ -422,7 +422,7 @@ public interface TraceObjectSchema { * @param requireCanonical only return patterns matching a canonical location for the type * @return a set of patterns where such objects could be found */ - default PathMatcher searchFor(Class type, + default PathFilter searchFor(Class type, boolean requireCanonical) { return searchFor(type, KeyPath.ROOT, requireCanonical); } @@ -439,15 +439,15 @@ public interface TraceObjectSchema { * @param requireCanonical only return patterns matching a canonical location for the type * @return a set of patterns where such objects could be found */ - default PathMatcher searchFor(Class type, KeyPath prefix, + default PathFilter searchFor(Class type, KeyPath prefix, boolean requireCanonical) { if (type == TraceObjectInterface.class) { throw new IllegalArgumentException("Must provide a specific interface"); } - PathMatcher result = new PathMatcher(); - Private.searchFor(this, result, prefix, true, type, false, requireCanonical, + Set patterns = new HashSet<>(); + Private.searchFor(this, patterns, prefix, true, type, false, requireCanonical, new HashSet<>()); - return result; + return PathMatcher.any(patterns.stream()); } class Private { @@ -574,7 +574,7 @@ public interface TraceObjectSchema { } } - private static void searchFor(TraceObjectSchema sch, PathMatcher result, + private static void searchFor(TraceObjectSchema sch, Set patterns, KeyPath prefix, boolean parentIsCanonical, Class type, boolean requireAggregate, boolean requireCanonical, @@ -583,7 +583,7 @@ public interface TraceObjectSchema { return; } if (sch.getInterfaces().contains(type) && (parentIsCanonical || !requireCanonical)) { - result.addPattern(prefix); + patterns.add(new PathPattern(prefix)); return; } if (!visited.add(sch)) { @@ -597,24 +597,24 @@ public interface TraceObjectSchema { for (Entry ent : sch.getElementSchemas().entrySet()) { KeyPath extended = prefix.index(ent.getKey()); TraceObjectSchema elemSchema = ctx.getSchema(ent.getValue()); - searchFor(elemSchema, result, extended, isCanonical, type, requireAggregate, + searchFor(elemSchema, patterns, extended, isCanonical, type, requireAggregate, requireCanonical, visited); } KeyPath deExtended = prefix.key("[]"); TraceObjectSchema deSchema = ctx.getSchema(sch.getDefaultElementSchema()); - searchFor(deSchema, result, deExtended, isCanonical, type, requireAggregate, + searchFor(deSchema, patterns, deExtended, isCanonical, type, requireAggregate, requireCanonical, visited); for (Entry ent : sch.getAttributeSchemas().entrySet()) { KeyPath extended = prefix.key(ent.getKey()); TraceObjectSchema attrSchema = ctx.getSchema(ent.getValue().getSchema()); - searchFor(attrSchema, result, extended, isCanonical, type, requireAggregate, + searchFor(attrSchema, patterns, extended, isCanonical, type, requireAggregate, requireCanonical, visited); } KeyPath daExtended = prefix.key(""); TraceObjectSchema daSchema = ctx.getSchema(sch.getDefaultAttributeSchema().getSchema()); - searchFor(daSchema, result, daExtended, isCanonical, type, requireAggregate, + searchFor(daSchema, patterns, daExtended, isCanonical, type, requireAggregate, requireCanonical, visited); visited.remove(sch); @@ -803,14 +803,14 @@ public interface TraceObjectSchema { * @return the filter for finding objects */ default PathFilter filterForSuitable(Class type, KeyPath path) { - PathMatcher result = new PathMatcher(); + Set patterns = new HashSet<>(); Set visited = new HashSet<>(); List schemas = getSuccessorSchemas(path); for (; path != null; path = path.parent()) { TraceObjectSchema schema = schemas.get(path.size()); - Private.searchFor(schema, result, path, false, type, true, false, visited); + Private.searchFor(schema, patterns, path, false, type, true, false, visited); } - return result; + return PathMatcher.any(patterns.stream()); } /** @@ -1036,7 +1036,7 @@ public interface TraceObjectSchema { return null; } - PathMatcher result = new PathMatcher(); + Set patterns = new HashSet<>(); for (String index : List.of(Integer.toString(frameLevel), "0x" + Integer.toHexString(frameLevel))) { KeyPath framePathRelStack = @@ -1044,10 +1044,10 @@ public interface TraceObjectSchema { KeyPath framePath = stackPath.extend(framePathRelStack); KeyPath regsPath = searchForSuitable(TraceObjectRegisterContainer.class, framePath); if (regsPath != null) { - result.addPattern(regsPath); + patterns.add(new PathPattern(regsPath)); } } - return result; + return PathMatcher.any(patterns.stream()); } /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java index 12ef3e4561..f86beb961a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java @@ -37,7 +37,7 @@ import ghidra.trace.model.memory.TraceObjectRegister; import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject.ConflictResolution; import ghidra.trace.model.target.path.KeyPath; -import ghidra.trace.model.target.path.PathMatcher; +import ghidra.trace.model.target.path.PathFilter; import ghidra.trace.model.target.schema.SchemaContext; import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName; import ghidra.trace.model.target.schema.XmlSchemaContext; @@ -428,9 +428,9 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte x86.getConventionalRegisterRange(overlay, EAX)); } - protected static void assertMatches(String path, PathMatcher matcher) { - String message = matcher + " does not match " + path; - assertTrue(message, matcher.matches(KeyPath.parse(path))); + protected static void assertMatches(String path, PathFilter filter) { + String message = filter + " does not match " + path; + assertTrue(message, filter.matches(KeyPath.parse(path))); } @Test @@ -475,10 +475,10 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte .createOverlayAddressSpace("Targets[0].Threads[0].Registers", registers); } - PathMatcher matcher = b.host.getConventionalRegisterPath(overlay, r0); - assertMatches("Targets[0].Threads[0].Registers.User[r0]", matcher); - assertMatches("Targets[0].Threads[0].Registers.User[a0]", matcher); - assertMatches("Targets[0].Threads[0].Registers.User[R0]", matcher); - assertMatches("Targets[0].Threads[0].Registers.User[A0]", matcher); + PathFilter filter = b.host.getConventionalRegisterPath(overlay, r0); + assertMatches("Targets[0].Threads[0].Registers.User[r0]", filter); + assertMatches("Targets[0].Threads[0].Registers.User[a0]", filter); + assertMatches("Targets[0].Threads[0].Registers.User[R0]", filter); + assertMatches("Targets[0].Threads[0].Registers.User[A0]", filter); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java index 4c1d27b1a2..d1ba8ba4cc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java @@ -569,6 +569,13 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT .collect(Collectors.toList())); } + @Test + public void testGetCanonicalSuccessors() { + populateModel(3); + assertEquals(3, root.getCanonicalSuccessors(PathFilter.parse("Targets[]")).count()); + assertEquals(0, root.getCanonicalSuccessors(PathFilter.parse("anAttribute")).count()); + } + @Test public void testSetValue_TruncatesOrDeletes() { try (Transaction tx = b.startTransaction()) {