GP-2989: An 'auto-read' spec for pure emulation.

This commit is contained in:
Dan 2023-02-08 09:17:27 -05:00
parent 5b543c1847
commit cbfb82fdcd
5 changed files with 308 additions and 48 deletions

View file

@ -957,11 +957,13 @@ public interface DebuggerResources {
String NAME_VIS_RO_ONCE = "Read Visible Memory, RO Once";
String NAME_VISIBLE = "Read Visible Memory";
String NAME_LOAD_EMU = "Load Emulator from Programs";
String NAME_NONE = "Do Not Read Memory";
// TODO: Separate icon for each
Icon ICON_VIS_RO_ONCE = ICON_AUTOREAD;
Icon ICON_VISIBLE = ICON_AUTOREAD;
Icon ICON_LOAD_EMU = ICON_EMULATE;
Icon ICON_NONE = ICON_DELETE;
static <T> MultiStateActionBuilder<T> builder(Plugin owner) {

View file

@ -0,0 +1,103 @@
/* ###
* 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.app.plugin.core.debug.gui.action;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AutoReadMemoryAction;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.model.record.RecorderUtils;
import ghidra.app.plugin.core.debug.utils.AbstractMappedMemoryBytesVisitor;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.async.AsyncUtils;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.util.database.UndoableTransaction;
public class LoadEmulatorAutoReadMemorySpec implements AutoReadMemorySpec {
public static final String CONFIG_NAME = "LOAD_EMULATOR";
@Override
public String getConfigName() {
return CONFIG_NAME;
}
@Override
public String getMenuName() {
return AutoReadMemoryAction.NAME_LOAD_EMU;
}
@Override
public Icon getMenuIcon() {
return AutoReadMemoryAction.ICON_LOAD_EMU;
}
@Override
public CompletableFuture<?> readMemory(PluginTool tool, DebuggerCoordinates coordinates,
AddressSetView visible) {
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
if (mappingService == null) {
return AsyncUtils.NIL;
}
Trace trace = coordinates.getTrace();
if (trace == null || coordinates.isAlive() ||
!ProgramEmulationUtils.isEmulatedProgram(trace)) {
// Never interfere with a live target
return AsyncUtils.NIL;
}
TraceMemoryManager mm = trace.getMemoryManager();
AddressSet toRead = new AddressSet(RecorderUtils.INSTANCE.quantize(12, visible));
for (Lifespan span : coordinates.getView().getViewport().getOrderedSpans()) {
AddressSetView alreadyKnown =
mm.getAddressesWithState(span.lmin(), visible, s -> s == TraceMemoryState.KNOWN);
toRead.delete(alreadyKnown);
if (span.lmax() != span.lmin() || toRead.isEmpty()) {
break;
}
}
if (toRead.isEmpty()) {
return AsyncUtils.NIL;
}
long snap = coordinates.getSnap();
ByteBuffer buf = ByteBuffer.allocate(4096);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Load Visible")) {
new AbstractMappedMemoryBytesVisitor(mappingService, buf.array()) {
@Override
protected void visitData(Address hostAddr, byte[] data, int size) {
buf.position(0);
buf.limit(size);
mm.putBytes(snap, hostAddr, buf);
}
}.visit(trace, snap, toRead);
return AsyncUtils.NIL;
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
}
}

View file

@ -15,11 +15,10 @@
*/
package ghidra.app.plugin.core.debug.service.emulation.data;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.utils.AbstractMappedMemoryBytesVisitor;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.app.services.TraceRecorder;
@ -31,10 +30,8 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
@ -105,61 +102,46 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc
@Override
public boolean readFromStaticImages(SemisparseByteArray bytes, AddressSetView guestView) {
boolean result = false;
// TODO: Expand to block? DON'T OVERWRITE KNOWN!
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
if (mappingService == null) {
return false;
}
byte[] data = new byte[4096];
Trace trace = platform.getTrace();
AddressSetView hostView = platform.mapGuestToHost(guestView);
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
.getOpenMappedViews(trace, hostView, snap)
.entrySet()) {
Program program = ent.getKey();
Memory memory = program.getMemory();
AddressSetView initialized = memory.getLoadedAndInitializedAddressSet();
try {
return new AbstractMappedMemoryBytesVisitor(mappingService, new byte[4096]) {
@Override
protected int read(Memory memory, Address addr, byte[] dest, int size)
throws MemoryAccessException {
int read = super.read(memory, addr, dest, size);
if (read < size) {
Msg.warn(this,
String.format(" Partial read of %s. Wanted %d bytes. Got %d.",
addr, size, read));
}
return read;
}
Collection<MappedAddressRange> mappedSet = ent.getValue();
for (MappedAddressRange mappedRng : mappedSet) {
AddressRange progRng = mappedRng.getDestinationAddressRange();
AddressSpace progSpace = progRng.getAddressSpace();
for (AddressRange subProgRng : initialized.intersectRange(progRng.getMinAddress(),
progRng.getMaxAddress())) {
@Override
protected boolean visitRange(Program program, AddressRange progRng,
MappedAddressRange mappedRng) throws MemoryAccessException {
Msg.debug(this,
"Filling in unknown trace memory in emulator using mapped image: " +
program + ": " + subProgRng);
long lower = subProgRng.getMinAddress().getOffset();
long fullLen = subProgRng.getLength();
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(data.length, fullLen);
try {
Address progAddr = progSpace.getAddress(lower);
int read = memory.getBytes(progAddr, data, 0, len);
if (read < len) {
Msg.warn(this,
" Partial read of " + subProgRng + ". Got " + read +
" bytes");
}
Address hostAddr = mappedRng.mapDestinationToSource(progAddr);
Address guestAddr = platform.mapHostToGuest(hostAddr);
// write(lower - shift, data, 0 ,read);
bytes.putData(guestAddr.getOffset(), data, 0, read);
}
catch (MemoryAccessException | AddressOutOfBoundsException e) {
throw new AssertionError(e);
}
lower += len;
fullLen -= len;
}
result = true;
program + ": " + progRng);
return super.visitRange(program, progRng, mappedRng);
}
}
@Override
protected void visitData(Address hostAddr, byte[] data, int size) {
Address guestAddr = platform.mapHostToGuest(hostAddr);
bytes.putData(guestAddr.getOffset(), data, 0, size);
}
}.visit(platform.getTrace(), snap, platform.mapGuestToHost(guestView));
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
return result;
}
@Override

View file

@ -0,0 +1,173 @@
/* ###
* 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.app.plugin.core.debug.utils;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Objects;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.*;
import ghidra.trace.model.Trace;
import ghidra.util.MathUtilities;
/**
* An object for visiting the memory of mapped programs on a block-by-block basis
*
* <p>
* The task for reading portions of program memory from the perspective of a trace, via the static
* mapping service turns out to be fairly onerous. This class attempts to ease that logic. In its
* simplest use, the client need only implement {@link #visitData(Address, byte[], int)} and provide
* a reference to the mapping service. Then, calling {@link #visit(Trace, long, AddressSetView)}
* will result in several calls to {@link #visitData(Address, byte[], int)}, which will provide the
* bytes from the mapped programs, along with the trace address where they apply.
*/
public abstract class AbstractMappedMemoryBytesVisitor {
private final DebuggerStaticMappingService mappingService;
private final byte[] buffer;
/**
* Construct a visitor object
*
* @param mappingService the mapping service
* @param buffer a buffer for the data. This is passed directly into
* {@link #visitData(Address, byte[], int)}. If a mapped range exceeds the buffer
* size, the range is broken down into smaller pieces.
*/
public AbstractMappedMemoryBytesVisitor(DebuggerStaticMappingService mappingService,
byte[] buffer) {
this.mappingService = Objects.requireNonNull(mappingService);
this.buffer = buffer;
}
/**
* Choose what portions of a mapped program to include
*
* <p>
* By default, this is the set of loaded and initialized memory addresses
*
* @param memory the mapped program's memory
* @return the address set to include
*/
protected AddressSetView includeFromProgram(Memory memory) {
return memory.getLoadedAndInitializedAddressSet();
}
/**
* Read bytes from a mapped program into a buffer
*
* <p>
* By default, this is a straightforward call to
* {@link Memory#getBytes(Address, byte[], int, int)}.
*
* @param memory the mapped program's memory
* @param addr the starting address
* @param dest the destination buffer
* @param size the number of bytes to read
* @return the number of bytes actually read
* @throws MemoryAccessException if the read fails
*/
protected int read(Memory memory, Address addr, byte[] dest, int size)
throws MemoryAccessException {
return memory.getBytes(addr, dest, 0, size);
}
/**
* Visit a trace's mapped programs
*
* @param trace the trace
* @param snap the snapshot for the mappings
* @param hostView the address set (per the trace's "host" platform)
* @return true if any range was visited
* @throws MemoryAccessException upon the first read failure
*/
public boolean visit(Trace trace, long snap, AddressSetView hostView)
throws MemoryAccessException {
boolean result = false;
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
.getOpenMappedViews(trace, hostView, snap)
.entrySet()) {
result |= visitProgram(ent.getKey(), ent.getValue());
}
return result;
}
/**
* Visit a mapped program
*
* @param program the mapped program
* @param mappedSet the portion of memory that was mapped from the trace
* @return true if any range was visited
* @throws MemoryAccessException upon the first read failure
*/
protected boolean visitProgram(Program program, Collection<MappedAddressRange> mappedSet)
throws MemoryAccessException {
boolean result = false;
Memory memory = program.getMemory();
AddressSetView included = includeFromProgram(memory);
for (MappedAddressRange mappedRng : mappedSet) {
AddressRange progRng = mappedRng.getDestinationAddressRange();
for (AddressRange subProgRng : included.intersectRange(progRng.getMinAddress(),
progRng.getMaxAddress())) {
result |= visitRange(program, subProgRng, mappedRng);
}
}
return result;
}
/**
* Visit a mapped range
*
* @param program the program
* @param progRng the range in the program
* @param mappedRng the mapped range from the trace
* @return true if the range was visited
* @throws MemoryAccessException upon the first read failure
*/
protected boolean visitRange(Program program, AddressRange progRng,
MappedAddressRange mappedRng) throws MemoryAccessException {
Memory memory = program.getMemory();
AddressSpace progSpace = progRng.getAddressSpace();
long lower = progRng.getMinAddress().getOffset();
long fullLen = progRng.getLength();
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(buffer.length, fullLen);
Address progAddr = progSpace.getAddress(lower);
int read = read(memory, progAddr, buffer, len);
Address hostAddr = mappedRng.mapDestinationToSource(progAddr);
visitData(hostAddr, buffer, read);
lower += len;
fullLen -= len;
}
return true;
}
/**
* Visit a block of data
*
* <p>
* <b>NOTE:</b> Not to be confused with {@link MemoryBlock}. This delivers the final results of
* the visit. It is called once per block of data read from a mapped program.
*
* @param hostAddr the trace address (per the trace's "host" platform)
* @param data the buffer of bytes read from the program
* @param size the number of valid bytes in the buffer. Valid bytes, if any, start at index 0
*/
protected abstract void visitData(Address hostAddr, byte[] data, int size);
}

View file

@ -166,7 +166,7 @@ public abstract class AbstractDBTraceProgramViewMemory
@Override
public LiveMemoryHandler getLiveMemoryHandler() {
return null;
return memoryWriteRedirect;
}
@Override