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 54bca75719..c3f7f44e3b 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 @@ -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. @@ -390,8 +390,108 @@ public class ProgramEmulationUtils { } } - public static AddressRange allocateStackCustom(Trace trace, long snap, TraceThread thread, - Program program) { + /** + * Attempt allocation of the stack using the program context and the initial PC. + * + *

+ * This examines the program context for a stack pointer value at the thread's initial program + * counter. If it has a value, this computes a range, based on the expected stack growth + * direction, of the specified size. If the range would wrap, it is truncated toe the space's + * bounds. This then attempts to create a region at the computed range to allocate the stack. If + * it already exists, an error dialog is presented, but the SP is still initialized as + * specified. + * + * @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 + * @param programPc the program counter in the program's memory map, in case SP is given by the + * program context + * @return the range allocated for the stack, or null if no SP value is set + */ + public static AddressRange allocateStackCustomByContext(Trace trace, long snap, + TraceThread thread, Program program, long size, Address programPc) { + if (program == null) { + return null; + } + ProgramContext ctx = program.getProgramContext(); + CompilerSpec cSpec = trace.getBaseCompilerSpec(); + Register sp = cSpec.getStackPointer(); + RegisterValue spVal = ctx.getRegisterValue(sp, programPc); + if (spVal == null || !spVal.hasValue()) { + return null; + } + + Address spAddr = cSpec.getStackBaseSpace().getAddress(spVal.getUnsignedValue().longValue()); + + final AddressRange alloc; + if (cSpec.stackGrowsNegative()) { + Address max = spAddr.subtractWrap(1); + Address min = spAddr.subtractWrapSpace(size); + if (min.compareTo(max) > 0) { + alloc = new AddressRangeImpl(max.getAddressSpace().getMinAddress(), max); + } + else { + alloc = new AddressRangeImpl(min, max); + } + } + else { + Address min = spAddr; + Address max = spAddr.addWrap(size - 1); + if (min.compareTo(max) > 0) { + alloc = new AddressRangeImpl(min, min.getAddressSpace().getMaxAddress()); + } + else { + alloc = new AddressRangeImpl(min, max); + } + } + + PathPattern patRegion = computePatternRegion(trace); + String threadName = PathUtils.isIndex(thread.getName()) + ? PathUtils.parseIndex(thread.getName()) + : thread.getName(); + String path = PathUtils.toString( + patRegion.applyKeys(alloc.getMinAddress() + "-stack " + threadName) + .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 allocation of the stack using the program's STACK block. + * + *

+ * This tries to find a block named STACK in the emulated program. If it finds one, it will + * attempt to create a region in the trace at the mapped dynamic location. It's possible (likely + * even, for a multi-threaded emulation session) that the region already exists. In that case, + * an error dialog is displayed, but the stack pointer is still initialized to the block. + * + * @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) + * @return the range allocated for the stack, or null if no STACK block exists + */ + public static AddressRange allocateStackCustomByBlock(Trace trace, long snap, + TraceThread thread, Program program) { if (program == null) { return null; } @@ -441,23 +541,30 @@ public class ProgramEmulationUtils { * *

* 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. + * is specified by an override (SP register context or STACK block) in the program, and that + * block overlays the image, then no region is created, but the range is still returned. * * @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 + * @param programPc the program counter in the program's memory map, in case SP is given by the + * program context * @return the range allocated for the stack * * @throws EmulatorOutOfMemoryException if the stack cannot be allocated */ 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; + Program program, long size, Address programPc) { + AddressRange customByContext = + allocateStackCustomByContext(trace, snap, thread, program, size, programPc); + if (customByContext != null) { + return customByContext; + } + AddressRange customByBlock = allocateStackCustomByBlock(trace, snap, thread, program); + if (customByBlock != null) { + return customByBlock; } // Otherwise, just search for an un-allocated block of the given size. AddressSpace space = trace.getBaseCompilerSpec().getStackBaseSpace(); @@ -565,7 +672,7 @@ public class ProgramEmulationUtils { TraceThread thread = spawnThread(trace, snap); AddressRange stack; try { - stack = allocateStack(trace, snap, thread, program, 0x4000); + stack = allocateStack(trace, snap, thread, program, 0x4000, programPc); } catch (EmulatorOutOfMemoryException e) { Msg.warn(ProgramEmulationUtils.class, 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 a8ab65ddc9..0898a65a92 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 @@ -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. @@ -723,7 +723,42 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe } @Test - public void testCustomStack() throws Exception { + public void testCustomStackByContext() 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); + + program.getProgramContext() + .setRegisterValue(addrText, addrText, + new RegisterValue(regSP, BigInteger.valueOf(0x00200000))); + } + + 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("00200000", 16), + regs.getViewValue(0, regSP).getUnsignedValue()); + } + + @Test + public void testCustomStackByBlock() throws Exception { createProgram(); intoProject(program); Memory memory = program.getMemory(); @@ -733,7 +768,8 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000, (byte) 0, TaskMonitor.DUMMY, false); blockText.setExecute(true); - memory.createUninitializedBlock("STACK", addr(program, 0x00001234), 0x1000, false); + memory.createUninitializedBlock(ProgramEmulationUtils.BLOCK_NAME_STACK, + addr(program, 0x00001234), 0x1000, false); } programManager.openProgram(program);