mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-2989: An 'auto-read' spec for pure emulation.
This commit is contained in:
parent
5b543c1847
commit
cbfb82fdcd
5 changed files with 308 additions and 48 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -166,7 +166,7 @@ public abstract class AbstractDBTraceProgramViewMemory
|
|||
|
||||
@Override
|
||||
public LiveMemoryHandler getLiveMemoryHandler() {
|
||||
return null;
|
||||
return memoryWriteRedirect;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue