GP-4483: Fix stale ADDs (disassembled 0s) in emulator's dynamic listing.

This commit is contained in:
Dan 2024-10-17 08:45:16 -04:00
parent f292bad0ed
commit ff18db760f
11 changed files with 265 additions and 39 deletions

View File

@ -4,9 +4,9 @@
* 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.
@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.mapping;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.disassemble.DisassemblyInject;
@ -27,7 +28,11 @@ import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Endian;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.TraceInstruction;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.classfinder.ClassSearcher;
@ -61,7 +66,13 @@ public abstract class AbstractDebuggerPlatformMapper implements DebuggerPlatform
}
protected boolean isCancelSilently(Address start, long snap) {
return trace.getCodeManager().instructions().getAt(snap, start) != null;
TraceInstruction exists = trace.getCodeManager().instructions().getAt(snap, start);
if (exists == null) {
return false;
}
var states = trace.getMemoryManager().getStates(snap, exists.getRange());
return TraceMemoryOperations.isStateEntirely(exists.getRange(), states,
TraceMemoryState.KNOWN);
}
protected Collection<DisassemblyInject> getDisassemblyInjections(TracePlatform platform) {

View File

@ -4,9 +4,9 @@
* 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.
@ -16,6 +16,7 @@
package ghidra.trace.database.listing;
import java.util.*;
import java.util.Map.Entry;
import org.apache.commons.lang3.tuple.Pair;
@ -29,9 +30,13 @@ import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.database.context.DBTraceRegisterContextManager;
import ghidra.trace.database.context.DBTraceRegisterContextSpace;
import ghidra.trace.database.guest.InternalTracePlatform;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.*;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.util.*;
import ghidra.util.LockHold;
import ghidra.util.exception.CancelledException;
@ -92,7 +97,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
protected void truncateOrDelete(TraceInstruction exists) {
if (exists.getStartSnap() < lifespan.lmin()) {
exists.setEndSnap(lifespan.lmin());
exists.setEndSnap(lifespan.lmin() - 1);
}
else {
exists.delete();
@ -471,6 +476,15 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
conflict, conflictCodeUnit, overwrite);
}
protected boolean isKnown(DBTraceMemorySpace ms, long snap, CodeUnit cu) {
if (ms == null) {
return false;
}
AddressRange range = new AddressRangeImpl(cu.getMinAddress(), cu.getMaxAddress());
var states = ms.getStates(snap, range);
return TraceMemoryOperations.isStateEntirely(range, states, TraceMemoryState.KNOWN);
}
/**
* Checks the intended locations for conflicts with existing units.
*
@ -486,6 +500,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
Set<Address> skipDelaySlots) {
// NOTE: Partly derived from CodeManager#checkInstructionSet()
// Attempted to factor more fluently
DBTraceMemoryManager mm = space.trace.getMemoryManager();
for (InstructionBlock block : instructionSet) {
// If block contains a known error, record its address, and do not proceed beyond it
Address errorAddress = null;
@ -519,6 +534,12 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
lastProtoInstr = protoInstr;
}
CodeUnit existsCu = overlap.getRight();
DBTraceMemorySpace ms =
mm.getMemorySpace(existsCu.getAddress().getAddressSpace(), false);
if (!isKnown(ms, startSnap, existsCu) && existsCu instanceof TraceCodeUnit tcu) {
tcu.delete();
continue;
}
int cmp = existsCu.getMinAddress().compareTo(protoInstr.getMinAddress());
boolean existsIsInstruction = (existsCu instanceof TraceInstruction);
if (cmp == 0 && existsIsInstruction) {
@ -552,7 +573,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
}
// NOTE: existsIsInstruction implies cmp != 0, so record as off-cut conflict
block.setCodeUnitConflict(existsCu.getAddress(), protoInstr.getAddress(),
flowFromAddress, existsIsInstruction, existsIsInstruction);
flowFromAddress, existsIsInstruction, true);
}
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -39,13 +39,17 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetView<T> extends Abstrac
private final Predicate<? super T> predicate;
/**
* TODO Document me
* Construct an {@link AddressSetView} based on the given map of entries and predicate.
*
* The caller must reduce the map if only a certain range is desired.
* <p>
* The spatial map is a 2-dimensional collection of entries, but only the address dimension is
* considered. This set behaves as the union of address ranges for all entries whose values pass
* the predicate. Typically, the caller reduces the map first.
*
* @param lock
* @param map
* @param predicate
* @param space the address space of the given map
* @param lock a lock to ensure access to the underlying database is synchronized
* @param map the map whose entries to test
* @param predicate the predicate for testing entry values
*/
public DBTraceAddressSnapRangePropertyMapAddressSetView(AddressSpace space, ReadWriteLock lock,
SpatialMap<TraceAddressSnapRange, T, TraceAddressSnapRangeQuery> map,

View File

@ -4,9 +4,9 @@
* 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.
@ -515,6 +515,12 @@ public class DBTraceAddressSnapRangePropertyMapTree<T, DR extends AbstractDBTrac
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
}
public static TraceAddressSnapRangeQuery mostRecent(AddressRange range, Lifespan span) {
return intersecting(
new ImmutableTraceAddressSnapRange(range, span),
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
}
public static TraceAddressSnapRangeQuery equalTo(TraceAddressSnapRange shape) {
return equalTo(shape, null, TraceAddressSnapRangeQuery::new);
}

View File

@ -4,9 +4,9 @@
* 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.
@ -279,6 +279,13 @@ public class DBTraceMemoryManager extends AbstractDBTraceSpaceBasedManager<DBTra
m -> m.getMostRecentStateEntry(snap, address));
}
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
AddressRange range, Predicate<TraceMemoryState> predicate) {
return delegateRead(range.getAddressSpace(),
m -> m.getViewMostRecentStateEntry(snap, range, predicate));
}
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address) {

View File

@ -406,11 +406,17 @@ public class DBTraceMemorySpace
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address) {
return getViewMostRecentStateEntry(snap, new AddressRangeImpl(address, address), s -> true);
}
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
AddressRange range, Predicate<TraceMemoryState> predicate) {
assertInSpace(range);
for (Lifespan span : viewport.getOrderedSpans(snap)) {
Entry<TraceAddressSnapRange, TraceMemoryState> entry =
stateMapSpace.reduce(TraceAddressSnapRangeQuery.mostRecent(address, span))
.firstEntry();
if (entry != null) {
var entry = stateMapSpace.reduce(TraceAddressSnapRangeQuery.mostRecent(range, span))
.firstEntry();
if (entry != null && predicate.test(entry.getValue())) {
return entry;
}
}

View File

@ -22,6 +22,7 @@ import org.apache.commons.collections4.IteratorUtils;
import generic.NestedIterator;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.code.InstructionDB;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
@ -41,6 +42,7 @@ import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.model.*;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewListing;
import ghidra.trace.model.property.TracePropertyMapOperations;
@ -717,9 +719,21 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
public Instruction createInstruction(Address addr, InstructionPrototype prototype,
MemBuffer memBuf, ProcessorContextView context, int forcedLengthOverride)
throws CodeUnitInsertionException {
// TODO: Why memBuf? Can it vary from program memory?
int checkLengthOverride =
InstructionDB.checkLengthOverride(forcedLengthOverride, prototype);
int length = checkLengthOverride != 0 ? checkLengthOverride : prototype.getLength();
AddressRange range;
try {
range = new AddressRangeImpl(addr, length);
}
catch (AddressOverflowException e) {
throw new CodeUnitInsertionException("Code unit would extend beyond address space");
}
var mostRecent = program.memory.memoryManager.getViewMostRecentStateEntry(program.snap,
range, s -> s == TraceMemoryState.KNOWN);
long snap = mostRecent == null ? program.snap : mostRecent.getKey().getY2();
return codeOperations.instructions()
.create(Lifespan.nowOn(program.snap), addr, platform, prototype, context,
.create(Lifespan.nowOn(snap), addr, platform, prototype, context,
forcedLengthOverride);
}

View File

@ -1635,6 +1635,17 @@ public class DBTraceProgramView implements TraceProgramView {
}
protected boolean isCodeVisible(TraceCodeUnit cu, Lifespan lifespan) {
try {
byte[] cubytes = cu.getBytes();
byte[] mmbytes = new byte[cubytes.length];
memory.getBytes(cu.getAddress(), mmbytes);
if (!Arrays.equals(cubytes, mmbytes)) {
return false;
}
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
return viewport.isCompletelyVisible(cu.getRange(), lifespan, cu,
getCodeOcclusion(cu.getTraceSpace()));
}

View File

@ -4,9 +4,9 @@
* 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.
@ -48,7 +48,13 @@ public interface TraceInstructionsView extends TraceBaseDefinedUnitsView<TraceIn
/**
* Create an instruction for the host platform
*
* @see #create(Lifespan, Address, TracePlatform, InstructionPrototype, ProcessorContextView)
* @param lifespan the lifespan for the instruction unit
* @param address the starting address of the instruction
* @param prototype the instruction prototype
* @param context the input disassembly context for the instruction
* @param forcedLengthOverride reduced instruction byte-length (1..7) or 0 to use default length
* @return the new instruction
* @throws CodeUnitInsertionException if the instruction cannot be created
*/
default TraceInstruction create(Lifespan lifespan, Address address,
InstructionPrototype prototype, ProcessorContextView context, int forcedLengthOverride)
@ -76,7 +82,14 @@ public interface TraceInstructionsView extends TraceBaseDefinedUnitsView<TraceIn
/**
* Create several instructions for the host platform
*
* @see #addInstructionSet(Lifespan, TracePlatform, InstructionSet, boolean)
* <p>
* <b>NOTE:</b> This does not throw {@link CodeUnitInsertionException}. Conflicts are instead
* recorded in the {@code instructionSet}.
*
* @param lifespan the lifespan for all instruction units
* @param instructionSet the set of instructions to add
* @param overwrite true to replace conflicting instructions
* @return the (host) address set of instructions actually added
*/
default AddressSetView addInstructionSet(Lifespan lifespan, InstructionSet instructionSet,
boolean overwrite) {

View File

@ -4,9 +4,9 @@
* 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.
@ -62,6 +62,38 @@ import ghidra.util.task.TaskMonitor;
* accidentally rely on implied temporal relationships in scratch space.
*/
public interface TraceMemoryOperations {
/**
* Check if the return value of {@link #getStates(long, AddressRange)} or similar represents a
* single entry of the given state.
*
* <p>
* This method returns false if there is not exactly one entry of the given state whose range
* covers the given range. As a special case, an empty collection will cause this method to
* return true iff state is {@link TraceMemoryState#UNKNOWN}.
*
* @param range the range to check, usually that passed to
* {@link #getStates(long, AddressRange)}.
* @param stateEntries the collection returned by {@link #getStates(long, AddressRange)}.
* @param state the expected state
* @return true if the state matches
*/
static boolean isStateEntirely(AddressRange range,
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> stateEntries,
TraceMemoryState state) {
if (stateEntries.isEmpty()) {
return state == TraceMemoryState.UNKNOWN;
}
if (stateEntries.size() != 1) {
return false;
}
Entry<TraceAddressSnapRange, TraceMemoryState> ent = stateEntries.iterator().next();
if (ent.getValue() != state) {
return false;
}
AddressRange entRange = ent.getKey().getRange();
return entRange.contains(range.getMinAddress()) && entRange.contains(range.getMaxAddress());
}
/**
* Get the trace to which the memory manager belongs
*
@ -261,13 +293,25 @@ public interface TraceMemoryOperations {
* Get the entry recording the most recent state at the given snap and address, following
* schedule forks
*
* @param snap the time
* @param address the location
* @return the state
* @param snap the latest time to consider
* @param address the address
* @return the most-recent entry
*/
Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address);
/**
* Get the entry recording the most recent state since the given snap within the given range
* that satisfies a given predicate, following schedule forks
*
* @param snap the latest time to consider
* @param range the range of addresses
* @param predicate a predicate on the state
* @return the most-recent entry
*/
Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
AddressRange range, Predicate<TraceMemoryState> predicate);
/**
* Get at least the subset of addresses having state satisfying the given predicate
*

View File

@ -4,9 +4,9 @@
* 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.
@ -19,13 +19,16 @@ import static ghidra.lifecycle.Unfinished.TODO;
import static org.junit.Assert.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import org.junit.*;
import db.Transaction;
import ghidra.app.plugin.assembler.*;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.AddressSet;
import ghidra.program.disassemble.Disassembler;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
@ -35,6 +38,13 @@ import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.listing.DBTraceCodeManager;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -48,9 +58,11 @@ public class DBTraceProgramViewListingTest extends AbstractGhidraHeadlessIntegra
DBTraceCodeManager code;
protected static void assertUndefined(CodeUnit cu) {
Data data = (Data) cu;
assertEquals(DataType.DEFAULT, data.getDataType());
assertFalse(data.isDefined());
if (cu instanceof Data data && DataType.DEFAULT.equals(data.getDataType()) &&
!data.isDefined()) {
return;
}
fail("Expected undefined unit, but was '%s'".formatted(cu));
}
protected <T> List<T> takeN(int n, Iterator<T> it) {
@ -896,4 +908,81 @@ public class DBTraceProgramViewListingTest extends AbstractGhidraHeadlessIntegra
assertArrayEquals(b.arr(1), cu0.getBytes());
assertArrayEquals(b.arr(8), cu1.getBytes());
}
@Test
public void testGetCodeUnitsInScratchView() throws Throwable {
TraceTimeManager tm = b.trace.getTimeManager();
Address entry = b.addr(0x00400000);
AddressSetView set = b.set(b.range(0x00400000, 0x00400003));
Assembler asm = Assemblers.getAssembler(b.language);
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
buf.assemble("imm r1, #234");
buf.assemble("add r1, r1");
final long snap;
try (Transaction tx = b.startTransaction()) {
TraceThread thread = b.getOrAddThread("Threads[1]", 0);
tm.getSnapshot(0, true);
memory.addRegion("Memory[test]", Lifespan.nowOn(0), b.range(0x00400000, 0x00400fff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
TraceSnapshot scratch = tm.getSnapshot(Long.MIN_VALUE, true);
snap = scratch.getKey();
scratch.setSchedule(TraceSchedule.ZERO.steppedForward(thread, 1));
view.setSnap(snap);
Disassembler dis =
Disassembler.getDisassembler(view, TaskMonitor.DUMMY, msg -> Msg.error(this, msg));
AddressSetView result = dis.disassemble(entry, set);
assertEquals(set, result);
assertEquals(4, memory.putBytes(0, entry, ByteBuffer.wrap(buf.getBytes())));
// No disassembly at snap 0
}
byte[] arr = new byte[4];
view.getMemory().getBytes(entry, arr);
assertArrayEquals(buf.getBytes(), arr);
assertUndefined(listing.getCodeUnitAt(entry));
}
@Test
public void testCreateCodeUnitsInScratchViewAfterBytesChanged() throws Throwable {
TraceTimeManager tm = b.trace.getTimeManager();
Address entry = b.addr(0x00400000);
AddressSetView set = b.set(b.range(0x00400000, 0x00400003));
Assembler asm = Assemblers.getAssembler(b.language);
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
buf.assemble("imm r1, #234");
buf.assemble("add r1, r1");
final long snap;
try (Transaction tx = b.startTransaction()) {
TraceThread thread = b.getOrAddThread("Threads[1]", 0);
tm.getSnapshot(0, true);
memory.addRegion("Memory[test]", Lifespan.nowOn(0), b.range(0x00400000, 0x00400fff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
TraceSnapshot scratch = tm.getSnapshot(Long.MIN_VALUE, true);
snap = scratch.getKey();
scratch.setSchedule(TraceSchedule.ZERO.steppedForward(thread, 1));
view.setSnap(snap);
Disassembler dis =
Disassembler.getDisassembler(view, TaskMonitor.DUMMY, msg -> Msg.error(this, msg));
AddressSetView result = dis.disassemble(entry, set);
assertEquals(set, result);
assertEquals(4, memory.putBytes(0, entry, ByteBuffer.wrap(buf.getBytes())));
// No disassembly at snap 0
// Attempt re-disassembly at scratch snap
result = dis.disassemble(entry, set);
assertEquals(set, result);
}
assertEquals("imm r1,#0xea", listing.getCodeUnitAt(entry).toString());
}
}