Merge remote-tracking branch 'origin/GP-7170_ghidra_red_timeless_byte_search--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-08-28 14:32:26 -04:00
6 changed files with 556 additions and 11 deletions
@@ -0,0 +1,137 @@
/* ###
* 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.
*/
// Search the entire trace, across all time, for a user-specified byte pattern.
//@category Debugger
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import docking.ComponentProvider;
import docking.widgets.table.*;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.AsyncUtils;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.TraceMemoryManager;
public class SearchBytesAcrossTime extends GhidraScript {
public record Row(Address address, long startSnap, long endSnap) {
Row(TraceAddressSnapRange match) {
this(match.getRange().getMinAddress(), match.getLifespan().lmin(),
match.getLifespan().lmax());
}
}
private static class ResultsProvider extends ComponentProvider {
private final JComponent component;
ResultsProvider(PluginTool tool, String title, List<Row> rows) {
super(tool, title, ResultsProvider.class.getSimpleName());
AnyObjectTableModel<Row> model =
new AnyObjectTableModel<>("Matches", Row.class, "startSnap", "endSnap",
"address");
model.setModelData(rows);
GTable table = new GTable(model);
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() != 2) {
return;
}
int viewRow = table.getSelectedRow();
if (viewRow == -1) {
return;
}
int modelRow = table.convertRowIndexToModel(viewRow);
Row row = rows.get(modelRow);
navigateTo(tool, row.startSnap, row.address);
}
});
GTableFilterPanel<Row> filterPanel = new GTableFilterPanel<>(table, model);
final JPanel panel = new JPanel(new BorderLayout());
panel.add(new JScrollPane(table));
panel.add(filterPanel, BorderLayout.SOUTH);
component = panel;
setTransient();
setVisible(true);
}
private void navigateTo(PluginTool tool, long snap, Address address) {
DebuggerTraceManagerService traceManager =
tool.getService(DebuggerTraceManagerService.class);
DebuggerListingService listingService = tool.getService(DebuggerListingService.class);
DebuggerCoordinates coords = traceManager.getCurrent().snap(snap);
traceManager.activateAndNotify(coords, ActivationCause.USER)
.thenRunAsync(() -> listingService.goTo(address, true),
AsyncUtils.SWING_EXECUTOR);
}
@Override
public JComponent getComponent() {
return component;
}
}
@Override
protected void run() throws Exception {
DebuggerTraceManagerService traceManager =
state.getTool().getService(DebuggerTraceManagerService.class);
Trace trace = traceManager.getCurrentTrace();
if (trace == null) {
printerr("No trace is active");
return;
}
byte[] pattern = askBytes("Search Across Time", "Enter byte pattern to search for:");
if (pattern == null || pattern.length == 0) {
printerr("No pattern given");
return;
}
TraceMemoryManager mem = trace.getMemoryManager();
List<Row> rows = new ArrayList<>();
// This searches the default address space, if your architecture uses other address spaces
// this needs to be updated.
AddressRange fullRange = new AddressRangeImpl(toAddr(0L), toAddr(0xFFFF_FFFF_FFFF_FFFFL));
List<TraceAddressSnapRange> bytesAcrossLifespan =
mem.findBytesAcrossLifespan(Lifespan.ALL, fullRange, pattern, monitor);
for (TraceAddressSnapRange traceAddressSnapRange : bytesAcrossLifespan) {
rows.add(new Row(traceAddressSnapRange));
}
println("Found " + rows.size() + " match(es)");
new ResultsProvider(state.getTool(), "Search Across Time Results MEMORY_BLOCKS", rows);
}
}
@@ -516,7 +516,7 @@ public class DBTraceAddressSnapRangePropertyMapTree<T,
new ImmutableTraceAddressSnapRange(space.getMinAddress(), range.getMaxAddress(),
span),
new ImmutableTraceAddressSnapRange(range.getMinAddress(), space.getMaxAddress(),
Lifespan.nowOnMaybeScratch(span.lmax())),
Lifespan.nowOnMaybeScratch(span.lmin())),
null);
}
@@ -268,6 +268,22 @@ public class DBTraceMemoryManager extends AbstractDBTraceSpaceBasedManager<DBTra
Collections.emptyList());
}
@Override
public Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(Lifespan span,
AddressRange range) {
return delegateReadOr(range.getAddressSpace(), m -> m.getStates(span, range),
() -> List.of(
Map.entry(new ImmutableTraceAddressSnapRange(range, span),
TraceMemoryState.UNKNOWN)));
}
@Override
public List<TraceAddressSnapRange> findBytesAcrossLifespan(Lifespan span, AddressRange range, byte[] pattern,
TaskMonitor monitor) {
return delegateReadOr(range.getAddressSpace(),
m -> m.findBytesAcrossLifespan(span, range, pattern, monitor), List::of);
}
@Override
public Iterable<Entry<TraceAddressSnapRange, TraceMemoryState>> getMostRecentStates(
TraceAddressSnapRange within) {
@@ -1083,6 +1083,270 @@ public class DBTraceMemorySpace
}
}
private static List<Integer> withinBlockMatchOffsets(byte[] buf, byte[] pattern) {
List<Integer> offsets = new ArrayList<>();
byte firstByte = pattern[0];
for (int off = 0; off < buf.length - pattern.length; off++) {
if (buf[off] == firstByte &&
Arrays.mismatch(buf, off, off + pattern.length, pattern, 0, pattern.length) == -1) {
offsets.add(off);
}
}
return offsets;
}
private static List<Integer> partialUpperBoundaryMatchOffsets(byte[] buf, byte[] pattern) {
List<Integer> offsets = new ArrayList<>();
int startOffset = buf.length - Math.min(buf.length, pattern.length) + 1;
byte firstByte = pattern[0];
for (int off = startOffset; off < buf.length; off++) {
int lengthToCheck = buf.length - off;
if (buf[off] == firstByte &&
Arrays.mismatch(buf, off, off + lengthToCheck, pattern, 0, lengthToCheck) == -1) {
offsets.add(off);
}
}
return offsets;
}
private static List<TraceAddressSnapRange> coalesceLifespansAndPruneToExactSearch(int hitLength,
Map<Address, List<Lifespan>> hits, AddressRange targetRange, Lifespan targetSpan) {
hits.values().forEach(l -> l.sort(Comparator.comparing(Lifespan::lmin)));
List<TraceAddressSnapRange> retHits = new ArrayList<>();
for (Entry<Address, List<Lifespan>> entry : hits.entrySet()) {
AddressRangeImpl range =
new AddressRangeImpl(entry.getKey(), entry.getKey().add(hitLength));
if (!targetRange.intersects(range.getMinAddress(), range.getMaxAddress())) {
continue;
}
Lifespan accumulator = entry.getValue().getFirst();
for (Lifespan lifespan : entry.getValue()) {
if (accumulator != lifespan) {
if (accumulator.intersects(lifespan) ||
accumulator.lmax() + 1 == lifespan.lmin()) {
accumulator = accumulator.bound(lifespan);
}
else {
retHits.add(new ImmutableTraceAddressSnapRange(range, accumulator));
accumulator = lifespan;
}
}
}
ImmutableTraceAddressSnapRange snapRange =
new ImmutableTraceAddressSnapRange(range, accumulator);
if (!retHits.contains(snapRange)) {
retHits.add(snapRange);
}
}
return retHits.stream().filter(e -> e.getLifespan().intersects(targetSpan)).toList();
}
@Override
public Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(Lifespan span,
AddressRange range) {
assertInSpace(range);
return doGetStates(span, range);
}
@Override
public List<TraceAddressSnapRange> findBytesAcrossLifespan(Lifespan span, AddressRange range, byte[] pattern,
TaskMonitor monitor) {
assertInSpace(range);
if (pattern.length == 0) {
return List.of();
}
Map<Address, List<Lifespan>> hits = searchBytesByMemoryBlocks(range, pattern, monitor);
return coalesceLifespansAndPruneToExactSearch(pattern.length, hits, range, span);
}
private Map<Address, List<Lifespan>> searchBytesByMemoryBlocks(AddressRange range,
byte[] pattern, TaskMonitor monitor) {
// Cheat a little to include a lower block in the case that the pattern crosses the lower
// search boundary
long minOffset = Math.max(range.getMinAddress().getUnsignedOffset() - pattern.length, 0);
long maxOffset = range.getMaxAddress().getUnsignedOffset();
long firstBlock = minOffset & BLOCK_MASK;
long lastBlock = maxOffset & BLOCK_MASK;
OffsetSnap lo = new OffsetSnap(firstBlock, Long.MIN_VALUE);
OffsetSnap hi = new OffsetSnap(lastBlock, Long.MAX_VALUE);
Map<Address, List<Lifespan>> hits = new HashMap<>();
for (Entry<OffsetSnap, DBTraceMemoryBlockEntry> ent : blocksByOffset.sub(lo, true, hi,
true).entries()) {
if (monitor.isCancelled()) {
return Map.of();
}
byte[] blockBuf = new byte[BLOCK_SIZE];
try (var _ = LockHold.lock(lock.readLock())) {
ent.getValue().getBytes(ByteBuffer.wrap(blockBuf), 0, BLOCK_SIZE);
}
catch (IOException e) {
blockStore.dbError(e);
continue;
}
Map<OffsetSnap, Lifespan> lifespanCache = new HashMap<>();
patternSearchWithinBlock(pattern, ent, blockBuf, hits, lifespanCache);
patternSearchSpanningBlocks(pattern, monitor, ent, blockBuf, hits, lifespanCache);
}
return hits;
}
private void patternSearchWithinBlock(byte[] pattern,
Entry<OffsetSnap, DBTraceMemoryBlockEntry> ent, byte[] blockBuf,
Map<Address, List<Lifespan>> hits, Map<OffsetSnap, Lifespan> lifespanCache) {
for (int off : withinBlockMatchOffsets(blockBuf, pattern)) {
Address address = toAddress(ent.getValue().getOffset() + off);
hits.computeIfAbsent(address, e -> new ArrayList<>());
hits.get(address).add(getLifespanOfBlock(ent.getKey(), lifespanCache));
}
}
private void patternSearchSpanningBlocks(byte[] pattern, TaskMonitor monitor,
Entry<OffsetSnap, DBTraceMemoryBlockEntry> ent, byte[] blockBuf,
Map<Address, List<Lifespan>> hits, Map<OffsetSnap, Lifespan> lifespanCache) {
int adjacentBlocksToCheck = (int) Math.ceil((double) pattern.length / BLOCK_SIZE) + 1;
int leadingZeros = 0;
for (byte b : pattern) {
if (b != 0) {
break;
}
leadingZeros++;
}
byte[] modifiedPattern = Arrays.copyOfRange(pattern, leadingZeros, pattern.length);
// Check upper bounds if we hit a partial match
for (int off : partialUpperBoundaryMatchOffsets(blockBuf, modifiedPattern)) {
if (monitor.isCancelled()) {
return;
}
List<Lifespan> adjacentBlocks =
getIntersectingLifespansOfAdjacentBlocks(ent.getKey(), adjacentBlocksToCheck,
null, lifespanCache);
for (Lifespan lifespan : adjacentBlocks) {
byte[] buf = new byte[pattern.length];
getBytes(lifespan.lmin(),
toAddress(ent.getValue().getOffset() + off - leadingZeros),
ByteBuffer.wrap(buf));
if (Arrays.mismatch(buf, pattern) == -1) {
Address address = toAddress(ent.getValue().getOffset() + off - leadingZeros);
hits.computeIfAbsent(address, e -> new ArrayList<>());
hits.get(address).add(lifespan);
}
}
}
// Check lower bounds since we got leading 0s to deal with
if (leadingZeros > 0 && !lowerMemoryBlockExists(ent.getKey())) {
byte[] buf = new byte[pattern.length];
getBytes(ent.getKey().snap, toAddress(ent.getValue().getOffset() - leadingZeros),
ByteBuffer.wrap(buf));
if (Arrays.mismatch(buf, pattern) == -1) {
Address address = toAddress(ent.getValue().getOffset() - leadingZeros);
hits.computeIfAbsent(address, e -> new ArrayList<>());
hits.get(address).add(getLifespanOfBlock(ent.getKey(), lifespanCache));
}
}
}
private boolean lowerMemoryBlockExists(OffsetSnap offsetSnap) {
OffsetSnap start = new OffsetSnap(offsetSnap.offset - BLOCK_SIZE, 0);
OffsetSnap stop = new OffsetSnap(offsetSnap.offset - BLOCK_SIZE, Long.MAX_VALUE);
return blocksByOffset.sub(start, true, stop, true).keys().iterator().hasNext();
}
private boolean upperMemoryBlockExists(OffsetSnap offsetSnap) {
OffsetSnap start = new OffsetSnap(offsetSnap.offset + BLOCK_SIZE, 0);
OffsetSnap stop = new OffsetSnap(offsetSnap.offset + BLOCK_SIZE, Long.MAX_VALUE);
return blocksByOffset.sub(start, true, stop, true).keys().iterator().hasNext();
}
private Lifespan getLifespanOfBlock(OffsetSnap offsetSnap, Map<OffsetSnap, Lifespan> cache) {
if (cache != null && cache.containsKey(offsetSnap)) {
return cache.get(offsetSnap);
}
Lifespan lifespan;
OffsetSnap nextOffsetSnap = blocksByOffset.higherKey(offsetSnap);
if (nextOffsetSnap == null) {
lifespan = Lifespan.nowOn(offsetSnap.snap);
}
else if (offsetSnap.offset == nextOffsetSnap.offset &&
offsetSnap.isScratch() == nextOffsetSnap.isScratch()) {
lifespan = Lifespan.span(offsetSnap.snap, nextOffsetSnap.snap - 1);
}
else if (offsetSnap.isScratch()) {
lifespan = Lifespan.at(offsetSnap.snap);
}
else {
lifespan = Lifespan.nowOn(offsetSnap.snap);
}
if (cache != null) {
cache.put(offsetSnap, lifespan);
}
return lifespan;
}
private List<Lifespan> getIntersectingLifespansOfAdjacentBlocks(OffsetSnap offsetSnap,
int numberOfBlocks, Lifespan accumulatorSpan,
Map<OffsetSnap, Lifespan> lifespanCache) {
if (numberOfBlocks == 1) {
return List.of(accumulatorSpan);
}
List<Lifespan> intersectingLifespans = new ArrayList<>();
Lifespan curLifespan;
if (accumulatorSpan != null) {
curLifespan = accumulatorSpan;
}
else {
curLifespan = getLifespanOfBlock(offsetSnap, lifespanCache);
}
if (upperMemoryBlockExists(offsetSnap)) {
// Check all lifespans of next block for intersections
OffsetSnap firstHit = null;
OffsetSnap start = new OffsetSnap(offsetSnap.offset + BLOCK_SIZE, 0);
OffsetSnap stop = new OffsetSnap(offsetSnap.offset + BLOCK_SIZE, Long.MAX_VALUE);
for (OffsetSnap adjacentOffsetSnap : blocksByOffset.sub(start, true, stop, true)
.keys()) {
Lifespan intersected =
getLifespanOfBlock(adjacentOffsetSnap, lifespanCache).intersect(
curLifespan);
if (firstHit == null) {
firstHit = adjacentOffsetSnap;
}
if (!intersected.isEmpty()) {
intersectingLifespans.addAll(
getIntersectingLifespansOfAdjacentBlocks(adjacentOffsetSnap,
numberOfBlocks - 1, intersected, lifespanCache));
}
}
// Check if lifespan before block was initialized (i.e. when it was all 0s) intersects
assert firstHit != null;
Lifespan intersected = Lifespan.before(firstHit.snap).intersect(curLifespan);
if (!intersected.isEmpty()) {
intersectingLifespans.addAll(getIntersectingLifespansOfAdjacentBlocks(
new OffsetSnap(firstHit.offset, Long.MIN_VALUE), numberOfBlocks - 1,
intersected, lifespanCache));
}
}
else {
// Upper block was never touched so just return same lifespan
intersectingLifespans.add(curLifespan);
}
return intersectingLifespans;
}
@Override
public MemBuffer getBufferAt(long snap, Address start, ByteOrder byteOrder) {
return new DBTraceMemBuffer(this, snap, start, byteOrder);
@@ -17,8 +17,7 @@ package ghidra.trace.model.memory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collection;
import java.util.Iterator;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
@@ -323,6 +322,35 @@ public interface TraceMemoryOperations {
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap,
AddressRange range);
/**
* Get all the entries covering the given range effective within the given span
* <p>
* Note that {@link TraceMemoryState#UNKNOWN} entries will not appear in the result. Gaps in
* the returned entries are implied to be {@link TraceMemoryState#UNKNOWN}.
*
* @param span the time span to examine
* @param range the range to examine
* @return the map of ranges to states
*/
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(Lifespan span,
AddressRange range);
/**
* Search the given range, across a given lifespan, for a byte pattern.
* <p>
* Addresses outside the given range may be returned if the pattern matches at the edges of
* the given range. Full lifespans for each hit are included as long as they intersect the
* lifespan given.
*
* @param span the range of time
* @param range the range to search
* @param pattern the pattern
* @param monitor a monitor for cancellation/progress
* @return the list of matches, each with the span of time during which it holds
*/
List<TraceAddressSnapRange> findBytesAcrossLifespan(Lifespan span, AddressRange range,
byte[] pattern, TaskMonitor monitor);
/**
* Check if a range addresses are all known
*
@@ -15,9 +15,6 @@
*/
package ghidra.trace.database.memory;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeFalse;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.file.Files;
@@ -25,9 +22,6 @@ import java.nio.file.Path;
import java.util.*;
import java.util.Map.Entry;
import org.junit.Ignore;
import org.junit.Test;
import db.DBHandle;
import db.Transaction;
import ghidra.framework.data.OpenMode;
@@ -35,8 +29,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.database.DBTrace;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.thread.TraceThread;
@@ -44,6 +37,11 @@ import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.util.SystemUtilities;
import ghidra.util.task.ConsoleTaskMonitor;
import ghidra.util.task.TaskMonitor;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeFalse;
public abstract class AbstractDBTraceMemoryManagerMemoryTest
extends AbstractDBTraceMemoryManagerTest {
@@ -1010,6 +1008,108 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
}
}
@Test
public void testFindBytesAcrossLifespan() {
try (Transaction tx = b.startTransaction()) {
assertEquals(5, memory.putBytes(2, b.addr(0x4ffb), b.buf(1, 1, 1, 1, 1)));
assertEquals(5, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4, 5)));
// Test lifespan
assertEquals(5, memory.putBytes(4, b.addr(0x4001), b.buf(1, 2, 3, 4, 5)));
assertEquals(5, memory.putBytes(5, b.addr(0x4200), b.buf(1, 2, 3, 4, 5)));
// Test block spanning
assertEquals(5, memory.putBytes(6, b.addr(0x4fff), b.buf(1, 2, 3, 4, 5)));
assertEquals(5, memory.putBytes(7, b.addr(0x4321), b.buf(1, 2, 3, 4, 5)));
assertEquals(5, memory.putBytes(8, b.addr(0x4000), b.buf(1, 2, 3, 4, 5)));
assertEquals(5, memory.putBytes(9, b.addr(0x4000), b.buf(1, 1, 1, 1, 1)));
assertEquals(5, memory.putBytes(10, b.addr(0x5ffb), b.buf(1, 1, 1, 1, 1)));
}
ImmutableTraceAddressSnapRange hit1 =
new ImmutableTraceAddressSnapRange(b.range(0x4000, 0x4005), Lifespan.at(3));
ImmutableTraceAddressSnapRange hit2 =
new ImmutableTraceAddressSnapRange(b.range(0x4001, 0x4006), Lifespan.span(4, 7));
ImmutableTraceAddressSnapRange hit3 =
new ImmutableTraceAddressSnapRange(b.range(0x4200, 0x4205), Lifespan.nowOn(5));
ImmutableTraceAddressSnapRange hit4 =
new ImmutableTraceAddressSnapRange(b.range(0x4fff, 0x5004), Lifespan.nowOn(6));
ImmutableTraceAddressSnapRange hit5 =
new ImmutableTraceAddressSnapRange(b.range(0x4321, 0x4326), Lifespan.nowOn(7));
ImmutableTraceAddressSnapRange hit6 =
new ImmutableTraceAddressSnapRange(b.range(0x4000, 0x4005), Lifespan.at(8));
ImmutableTraceAddressSnapRange hit7 =
new ImmutableTraceAddressSnapRange(b.range(0x3fff, 0x4005), Lifespan.nowOn(9));
ImmutableTraceAddressSnapRange hit8 =
new ImmutableTraceAddressSnapRange(b.range(0x4ffb, 0x5001), Lifespan.span(2, 5));
ImmutableTraceAddressSnapRange hit9 =
new ImmutableTraceAddressSnapRange(b.range(0x5ffb, 0x6001), Lifespan.nowOn(10));
// Lifespan All
List<TraceAddressSnapRange> bytesAcrossLifespan =
memory.findBytesAcrossLifespan(Lifespan.ALL, b.range(0, 0xFFFF_FFFF_FFFF_FFFFL),
new byte[] { 1, 2, 3, 4, 5 }, TaskMonitor.DUMMY);
assertContainsExactly(List.of(hit1, hit2, hit3, hit4, hit5, hit6), bytesAcrossLifespan);
// Narrow lifespan
bytesAcrossLifespan = memory.findBytesAcrossLifespan(Lifespan.before(5),
b.range(0, 0xFFFF_FFFF_FFFF_FFFFL),
new byte[] { 1, 2, 3, 4, 5 }, TaskMonitor.DUMMY);
assertContainsExactly(List.of(hit1, hit2), bytesAcrossLifespan);
// Narrow Range
bytesAcrossLifespan = memory.findBytesAcrossLifespan(Lifespan.ALL, b.range(0x4200, 0x4500),
new byte[] { 1, 2, 3, 4, 5 }, TaskMonitor.DUMMY);
assertContainsExactly(List.of(hit3, hit5), bytesAcrossLifespan);
// Narrow lifespan/range
bytesAcrossLifespan = memory.findBytesAcrossLifespan(Lifespan.since(5),
b.range(0x4200, 0x4500),
new byte[] { 1, 2, 3, 4, 5 }, TaskMonitor.DUMMY);
assertContainsExactly(List.of(hit3), bytesAcrossLifespan);
// Narrow lifespan/range (miss)
bytesAcrossLifespan = memory.findBytesAcrossLifespan(Lifespan.before(5),
b.range(0x4200, 0x4500),
new byte[] { 1, 2, 3, 4, 5 }, TaskMonitor.DUMMY);
assertContainsExactly(List.of(), bytesAcrossLifespan);
// Empty pattern
bytesAcrossLifespan =
memory.findBytesAcrossLifespan(Lifespan.ALL, b.range(0, 0xFFFF_FFFF_FFFF_FFFFL), new byte[] {},
TaskMonitor.DUMMY);
assertContainsExactly(List.of(), bytesAcrossLifespan);
// Leading 0s, with block crossing
bytesAcrossLifespan = memory.findBytesAcrossLifespan(Lifespan.ALL,
b.range(0, 0xFFFF_FFFF_FFFF_FFFFL),
new byte[] { 0, 1, 1, 1, 1, 1 }, TaskMonitor.DUMMY);
// hit7 should work, no memory block means it's all 0s
assertContainsExactly(List.of(hit7), bytesAcrossLifespan);
// Trailing 0s, with block crossing
bytesAcrossLifespan = memory.findBytesAcrossLifespan(Lifespan.before(6),
b.range(0, 0xFFFF_FFFF_FFFF_FFFFL),
new byte[] { 1, 1, 1, 1, 1, 0 }, TaskMonitor.DUMMY);
// hit8 should work, no memory block means it's all 0s
assertContainsExactly(List.of(hit8), bytesAcrossLifespan);
// 1 byte intersect on range, start of pattern, with block crossing
bytesAcrossLifespan = memory.findBytesAcrossLifespan(Lifespan.ALL, b.range(0x4fff, 0x4fff),
new byte[] { 1, 2, 3, 4, 5 }, TaskMonitor.DUMMY);
assertContainsExactly(List.of(hit4), bytesAcrossLifespan);
// 1 byte intersect on range, end of pattern, with block crossing
bytesAcrossLifespan = memory.findBytesAcrossLifespan(Lifespan.ALL, b.range(0x5004, 0x5004),
new byte[] { 1, 2, 3, 4, 5 }, TaskMonitor.DUMMY);
assertContainsExactly(List.of(hit4), bytesAcrossLifespan);
// Block crossing into never touched memory
bytesAcrossLifespan = memory.findBytesAcrossLifespan(Lifespan.nowOn(6),
b.range(0, 0xFFFF_FFFF_FFFF_FFFFL),
new byte[] { 1, 1, 1, 1, 1, 0 }, TaskMonitor.DUMMY);
assertContainsExactly(List.of(hit9), bytesAcrossLifespan);
}
/**
* Based on old issue: https://github.com/NationalSecurityAgency/ghidra/issues/2760 that came up
* again in another context.