From cbfb82fdcd711ba6a1a1bf4c2a6cf74b7615ac4c Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 8 Feb 2023 09:17:27 -0500 Subject: [PATCH] GP-2989: An 'auto-read' spec for pure emulation. --- .../core/debug/gui/DebuggerResources.java | 2 + .../LoadEmulatorAutoReadMemorySpec.java | 103 +++++++++++ .../DefaultPcodeDebuggerMemoryAccess.java | 76 +++----- .../AbstractMappedMemoryBytesVisitor.java | 173 ++++++++++++++++++ .../AbstractDBTraceProgramViewMemory.java | 2 +- 5 files changed, 308 insertions(+), 48 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/AbstractMappedMemoryBytesVisitor.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 1a44a2e5c5..3c6d69dcc9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -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 MultiStateActionBuilder builder(Plugin owner) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java new file mode 100644 index 0000000000..756379ab30 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java @@ -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); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java index ffcbb32a14..80d025fa14 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java @@ -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> 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 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 diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/AbstractMappedMemoryBytesVisitor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/AbstractMappedMemoryBytesVisitor.java new file mode 100644 index 0000000000..137780eef9 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/AbstractMappedMemoryBytesVisitor.java @@ -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 + * + *

+ * 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 + * + *

+ * 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 + * + *

+ * 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> 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 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 + * + *

+ * NOTE: 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); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java index b1d569e5ce..8ed236a282 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java @@ -166,7 +166,7 @@ public abstract class AbstractDBTraceProgramViewMemory @Override public LiveMemoryHandler getLiveMemoryHandler() { - return null; + return memoryWriteRedirect; } @Override