mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-22 18:14:09 +08:00
GP-7170: Added API for timeless trace byte search
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+16
@@ -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) {
|
||||
|
||||
+264
@@ -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);
|
||||
|
||||
+30
-2
@@ -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
|
||||
*
|
||||
|
||||
+108
-8
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user