From 17c571a393d387d4a1a922c677547d975f7c1f23 Mon Sep 17 00:00:00 2001
From: ghidorahrex
To control the initial stack allocation, create a STACK
block in the target
+ program database before emulating. If the stack is already in the target image's memory map,
+ create an overlay block named STACK
. This will initialize the stack pointer
+ without modifying the emulator's memory map. Note that customizing the stack initialization may
+ prevent you from adding a second thread.
This action is available whenever a "pure emulation" trace is active. It spawns a new thread diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java index 9f0fcc0fc4..9da7b249a8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java @@ -512,14 +512,14 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm if (!block.isExecute()) { return; }*/ - ProgramLocation progLoc = - staticMappings.getOpenMappedLocation(new DefaultTraceLocation(view.getTrace(), null, - Lifespan.at(view.getSnap()), tracePc)); - Program program = progLoc == null ? null : progLoc.getProgram(); - Address programPc = progLoc == null ? null : progLoc.getAddress(); long snap = view.getViewport().getOrderedSnaps().stream().filter(s -> s >= 0).findFirst().get(); + ProgramLocation progLoc = staticMappings.getOpenMappedLocation( + new DefaultTraceLocation(trace, null, Lifespan.at(snap), tracePc)); + Program program = progLoc == null ? null : progLoc.getProgram(); + Address programPc = progLoc == null ? null : progLoc.getAddress(); + TraceThread thread = ProgramEmulationUtils.launchEmulationThread(trace, snap, program, tracePc, programPc); traceManager.activateThread(thread); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java index 691396aba5..146097f5f0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java @@ -43,8 +43,7 @@ import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.*; import ghidra.trace.model.time.TraceSnapshot; -import ghidra.util.DifferenceAddressSetView; -import ghidra.util.NumericUtilities; +import ghidra.util.*; import ghidra.util.exception.DuplicateNameException; /** @@ -56,6 +55,7 @@ import ghidra.util.exception.DuplicateNameException; */ public enum ProgramEmulationUtils { ; + public static final String BLOCK_NAME_STACK = "STACK"; /** * Conventional prefix for first snapshot to identify "pure emulation" traces. @@ -227,10 +227,10 @@ public enum ProgramEmulationUtils { * @param program the program whose context to use * @param tracePc the program counter in the trace's memory map * @param programPc the program counter in the program's memory map - * @param stack optionally, the region representing the thread's stack + * @param stack optionally, the range for the thread's stack allocation */ public static void initializeRegisters(Trace trace, long snap, TraceThread thread, - Program program, Address tracePc, Address programPc, TraceMemoryRegion stack) { + Program program, Address tracePc, Address programPc, AddressRange stack) { TraceMemoryManager memory = trace.getMemoryManager(); if (thread instanceof TraceObjectThread ot) { TraceObject object = ot.getObject(); @@ -283,28 +283,92 @@ public enum ProgramEmulationUtils { } } + public static AddressRange allocateStackCustom(Trace trace, long snap, TraceThread thread, + Program program) { + if (program == null) { + return null; + } + AddressSpace space = trace.getBaseCompilerSpec().getStackBaseSpace(); + MemoryBlock stackBlock = program.getMemory().getBlock(BLOCK_NAME_STACK); + if (stackBlock == null) { + return null; + } + if (space != stackBlock.getStart().getAddressSpace().getPhysicalSpace()) { + Msg.showError(ProgramEmulationUtils.class, null, "Invalid STACK block", + "The STACK block must be in the stack's base space. Ignoring."); + return null; + } + AddressRange alloc = new AddressRangeImpl( + stackBlock.getStart().getPhysicalAddress(), + stackBlock.getEnd().getPhysicalAddress()); + if (stackBlock.isOverlay() || DebuggerStaticMappingUtils.isReal(stackBlock)) { + return alloc; + } + PathPattern patRegion = computePatternRegion(trace); + String path = PathUtils.toString( + patRegion.applyKeys(stackBlock.getStart() + "-STACK") + .getSingletonPath()); + TraceMemoryManager mm = trace.getMemoryManager(); + try { + return mm.createRegion(path, snap, alloc, + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE).getRange(); + } + catch (TraceOverlappedRegionException e) { + Msg.showError(ProgramEmulationUtils.class, null, "Stack conflict", + ("The STACK region %s conflicts with another: %s. " + + "You may need to initialize the stack pointer manually.").formatted( + alloc, e.getConflicts().iterator().next())); + return alloc; + } + catch (DuplicateNameException e) { + Msg.showError(ProgramEmulationUtils.class, null, "Stack conflict", + ("A region already exists with the same name: %s. " + + "You may need to initialize the stack pointer manually.") + .formatted(path)); + return alloc; + } + } + /** * Attempt to allocate a new stack region for the given thread * + *
+ * If successful, this will create a dynamic memory region representing the stack. If the stack + * is specified by an override (STACK block) in the program, and that block overlays the image, + * then no region is created. + * * @param trace the trace containing the stack and thread * @param snap the creation snap for the new region * @param thread the thread for which the stack is being allocated + * @param program the program being emulated (to check for stack allocation override) * @param size the desired size of the region - * @return the new region representing the allocated stack + * @return the range allocated for the stack * * @throws EmulatorOutOfMemoryException if the stack cannot be allocated */ - public static TraceMemoryRegion allocateStack(Trace trace, long snap, TraceThread thread, - long size) { + public static AddressRange allocateStack(Trace trace, long snap, TraceThread thread, + Program program, long size) { + AddressRange custom = allocateStackCustom(trace, snap, thread, program); + if (custom != null) { + return custom; + } + // Otherwise, just search for an un-allocated block of the given size. AddressSpace space = trace.getBaseCompilerSpec().getStackBaseSpace(); - AddressSet except0 = new AddressSet(space.getAddress(0x1000), space.getMaxAddress()); + Address max = space.getMaxAddress(); + AddressSet eligible; + if (max.getOffsetAsBigInteger().compareTo(BigInteger.valueOf(0x1000)) < 0) { + eligible = new AddressSet(space.getMinAddress(), max); + } + else { + eligible = new AddressSet(space.getAddress(0x1000), max); + } TraceMemoryManager mm = trace.getMemoryManager(); AddressSetView left = - new DifferenceAddressSetView(except0, mm.getRegionsAddressSet(snap)); + new DifferenceAddressSetView(eligible, mm.getRegionsAddressSet(snap)); PathPattern patRegion = computePatternRegion(trace); try { for (AddressRange candidate : left) { - if (Long.compareUnsigned(candidate.getLength(), size) > 0) { + if (Long.compareUnsigned(candidate.getLength(), size) >= 0) { AddressRange alloc = new AddressRangeImpl(candidate.getMinAddress(), size); String threadName = PathUtils.isIndex(thread.getName()) ? PathUtils.parseIndex(thread.getName()) @@ -313,7 +377,7 @@ public enum ProgramEmulationUtils { patRegion.applyKeys(alloc.getMinAddress() + "-stack " + threadName) .getSingletonPath()); return mm.createRegion(path, snap, alloc, - TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE).getRange(); } } } @@ -373,7 +437,15 @@ public enum ProgramEmulationUtils { public static TraceThread doLaunchEmulationThread(Trace trace, long snap, Program program, Address tracePc, Address programPc) { TraceThread thread = spawnThread(trace, snap); - TraceMemoryRegion stack = allocateStack(trace, snap, thread, 0x4000); + AddressRange stack; + try { + stack = allocateStack(trace, snap, thread, program, 0x4000); + } + catch (EmulatorOutOfMemoryException e) { + Msg.warn(ProgramEmulationUtils.class, + "Cannot allocate a stack. Please initialize manually."); + stack = null; + } initializeRegisters(trace, snap, thread, program, tracePc, programPc, stack); return thread; } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java index a981816957..de00be020c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java @@ -381,7 +381,8 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU assertTrue(result.error() instanceof DecodePcodeExecutionException); long scratch = result.snapshot(); - assertEquals(new BigInteger("003ffffe", 16), regs.getViewValue(scratch, regPC).getUnsignedValue()); + assertEquals(new BigInteger("003ffffe", 16), + regs.getViewValue(scratch, regPC).getUnsignedValue()); } @Test @@ -715,4 +716,34 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU assertEquals(new BigInteger("5678", 16), regs.getViewValue(scratch, regR2).getUnsignedValue()); } + + @Test + public void testCustomStack() throws Exception { + createProgram(); + intoProject(program); + Memory memory = program.getMemory(); + Address addrText = addr(program, 0x00400000); + Register regSP = program.getRegister("sp"); + try (Transaction tx = program.openTransaction("Initialize")) { + MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000, + (byte) 0, TaskMonitor.DUMMY, false); + blockText.setExecute(true); + memory.createUninitializedBlock("STACK", addr(program, 0x00001234), 0x1000, false); + } + + programManager.openProgram(program); + waitForSwing(); + codeBrowser.goTo(new ProgramLocation(program, addrText)); + waitForSwing(); + + assertTrue(emulationPlugin.actionEmulateProgram.isEnabled()); + performAction(emulationPlugin.actionEmulateProgram); + + Trace trace = traceManager.getCurrentTrace(); + assertNotNull(trace); + + TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads()); + TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false); + assertEquals(new BigInteger("2234", 16), regs.getViewValue(0, regSP).getUnsignedValue()); + } }