diff --git a/Ghidra/Debug/Debugger/developer_scripts/AllocationTracker.java b/Ghidra/Debug/Debugger/developer_scripts/AllocationTracker.java new file mode 100644 index 0000000000..33a23bafe2 --- /dev/null +++ b/Ghidra/Debug/Debugger/developer_scripts/AllocationTracker.java @@ -0,0 +1,412 @@ +/* ### + * 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. + */ +//EXPERIMENTAL script that tries to track memory allocations and frees in a trace. +//@category Debugger +//@keybinding +//@menupath +//@toolbar + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.*; + +import ghidra.app.script.GhidraScript; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.app.services.ProgramManager; +import ghidra.debug.api.modules.MappedAddressRange; +import ghidra.debug.flatapi.FlatDebuggerAPI; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.lang.RegisterValue; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.RefType; +import ghidra.trace.model.*; +import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.target.TraceObjectValue; +import ghidra.trace.model.thread.TraceThread; + +public class AllocationTracker extends GhidraScript implements FlatDebuggerAPI { + public record AllocFreeEvent(long snap, long size, Address allocedAddress, + Address freedAddress, + Function function, long returnStatus) { + } + + public record EntryExitEvent(long snap, AllocOrFreeFunc allocOrFreeFunc, boolean entry) { + } + + public record AllocatedBuffer(Lifespan lifespan, long size, Address startAddress) { + } + + public static class AllocFreeEventBuilder { + private long snap; + private long size; + private Address allocatedAddress; + private Address freedAddress; + private Function function; + private boolean clear = true; + private long returnStatus; + + public AllocFreeEventBuilder setSnap(long snap) { + clear = false; + this.snap = snap; + return this; + } + + public AllocFreeEventBuilder setSize(long size) { + clear = false; + this.size = size; + return this; + } + + public AllocFreeEventBuilder setAllocatedAddress(Address allocatedAddress) { + clear = false; + this.allocatedAddress = allocatedAddress; + return this; + } + + public AllocFreeEventBuilder setFreedAddress(Address freedAddress) { + clear = false; + this.freedAddress = freedAddress; + return this; + } + + public AllocFreeEventBuilder setReturnStatus(long returnStatus) { + clear = false; + this.returnStatus = returnStatus; + return this; + } + + public AllocFreeEventBuilder setFunction(Function function) { + clear = false; + this.function = function; + return this; + } + + public AllocFreeEvent createAllocFreeEvent() { + AllocFreeEvent event = + new AllocFreeEvent(snap, size, allocatedAddress, freedAddress, function, + returnStatus); + snap = 0; + size = 0; + returnStatus = 0; + allocatedAddress = null; + freedAddress = null; + function = null; + clear = true; + return event; + } + + public boolean isClear() { + return clear; + } + } + + public class AllocFreePairings { + private final List funcs; + + AllocFreePairings(List funcs) { + this.funcs = funcs; + } + + List getAllEvents() { + AddressSet addressSet = new AddressSet(); + funcs.forEach(e -> { + addressSet.add(e.dynFunctionStart); + addressSet.add(e.dynFunctionReturns); + }); + + return getCurrentTrace().getObjectManager() + .getRootSchema() + .getContext() + .getAllSchemas() + .stream() + .filter(e -> e.getInterfaces().contains(TraceStackFrame.class)) + .map(s -> s.checkAliasedAttribute(TraceStackFrame.KEY_PC)) + .flatMap(path -> funcs.stream() + .flatMap(func -> func.dynFunctionStartsAndReturns.stream() + .flatMap(addrRange -> getCurrentTrace().getObjectManager() + .getValuesIntersecting(Lifespan.ALL, addrRange, path) + .stream() + .sorted(Comparator.comparingLong( + TraceObjectValue::getMinSnap)) + .map(tov -> { + if (func.dynFunctionStart.intersects(addrRange)) { + return new EntryExitEvent(tov.getMinSnap(), + func, true); + } + else { + return new EntryExitEvent(tov.getMinSnap(), + func, false); + } + })))) + .sorted(Comparator.comparingLong(EntryExitEvent::snap)) + .map(e -> { + if (e.entry) { + e.allocOrFreeFunc.onEntry(e.snap()); + return null; + } + else { + return e.allocOrFreeFunc.onExit(e.snap()); + } + }) + .filter(Objects::nonNull) + .toList(); + } + } + + class AllocOrFreeFunc { + Function function; + AddressSet functionStarts; + AddressSet functionReturns; + AddressSet dynFunctionStart; + AddressSet dynFunctionReturns; + AddressSet dynFunctionStartsAndReturns; + AllocFreeEventBuilder allocFreeEventBuilder; + + AllocOrFreeFunc(Function function) { + this.function = function; + functionStarts = new AddressSet(function.getEntryPoint()); + functionReturns = new AddressSet(); + for (Instruction instruction : function.getProgram() + .getListing() + .getInstructions(function.getBody(), true)) { + if (instruction.getFlowType().equals(RefType.TERMINATOR)) { + functionReturns.add(instruction.getAddress()); + } + } + dynFunctionStart = getDynamicAddresses(functionStarts, function.getProgram()); + dynFunctionReturns = getDynamicAddresses(functionReturns, function.getProgram()); + dynFunctionStartsAndReturns = new AddressSet(); + dynFunctionStartsAndReturns.add(dynFunctionStart); + dynFunctionStartsAndReturns.add(dynFunctionReturns); + allocFreeEventBuilder = new AllocFreeEventBuilder(); + + } + + AddressSet getDynamicAddresses(AddressSet staticSet, Program program) { + final DebuggerStaticMappingService staticMappingService = + state.getTool().getService(DebuggerStaticMappingService.class); + final AddressSet dynamicSet = new AddressSet(); + + for (AddressRange address : staticSet) { + Map> openMappedViews = + staticMappingService.getOpenMappedViews(program, new AddressSet(address)); + for (Map.Entry> entry : + openMappedViews.entrySet()) { + if (entry.getKey().getTrace() != getCurrentTrace()) { + continue; + } + for (MappedAddressRange addressRange : entry.getValue()) { + dynamicSet.add(addressRange.getDestinationAddressRange().getMinAddress(), + addressRange.getDestinationAddressRange().getMaxAddress()); + } + } + } + return dynamicSet; + } + + BigInteger getVariableValue(Variable variable, long snap) { + Trace trace = getCurrentTrace(); + Address address = variable.getMinAddress(); + final ByteBuffer buf = ByteBuffer.allocate(variable.getLength()); + TraceThread eventThread = + trace.getTimeManager().getSnapshot(snap, false).getEventThread(); + if (address.isStackAddress()) { + final CompilerSpec spec = trace.getBaseCompilerSpec(); + + final RegisterValue value = trace.getMemoryManager() + .getMemoryRegisterSpace(eventThread, false) + .getValue(snap, spec.getStackPointer()); + + final Address stackPointer = + spec.getStackBaseSpace().getAddress(value.getUnsignedValue().longValue()); + + address = stackPointer.add(address.getOffset()); + trace.getMemoryManager().getBytes(snap, address, buf); + } + else if (address.isMemoryAddress()) { + trace.getMemoryManager().getBytes(snap, address, buf); + } + else if (address.isRegisterAddress()) { + final RegisterValue regVal = trace.getMemoryManager() + .getMemoryRegisterSpace(eventThread, false) + .getValue(snap, variable.getRegister()); + return regVal.getUnsignedValue(); + } + + byte[] bytes = new byte[buf.remaining()]; + buf.get(bytes); + return new BigInteger(bytes); + } + + void onEntry(long snap) { + if (!allocFreeEventBuilder.isClear()) { + printerr("Seems like we missed a function exit for %s".formatted(function)); + } + } + + AllocFreeEvent onExit(long snap) { + return allocFreeEventBuilder.createAllocFreeEvent(); + } + } + + class malloc extends AllocOrFreeFunc { + malloc(Function function) { + super(function); + } + + @Override + void onEntry(long snap) { + super.onEntry(snap); + allocFreeEventBuilder.setSize( + getVariableValue(function.getParameter(0), snap).longValue()) + .setFunction(function) + .setSnap(snap); + } + + @Override + AllocFreeEvent onExit(long snap) { + allocFreeEventBuilder.setAllocatedAddress( + toAddr(getVariableValue(function.getReturn(), snap).longValue())) + .setFunction(function); + return super.onExit(snap); + } + } + + class calloc extends AllocOrFreeFunc { + calloc(Function function) { + super(function); + } + + @Override + void onEntry(long snap) { + super.onEntry(snap); + allocFreeEventBuilder.setSize( + getVariableValue(function.getParameter(0), snap).longValue() * + getVariableValue(function.getParameter(1), snap).longValue()) + .setFunction(function) + .setSnap(snap); + } + + @Override + AllocFreeEvent onExit(long snap) { + allocFreeEventBuilder.setAllocatedAddress( + toAddr(getVariableValue(function.getReturn(), snap).longValue())) + .setFunction(function); + + return super.onExit(snap); + } + } + + class realloc extends AllocOrFreeFunc { + realloc(Function function) { + super(function); + } + + @Override + void onEntry(long snap) { + super.onEntry(snap); + allocFreeEventBuilder.setFreedAddress( + toAddr(getVariableValue(function.getParameter(0), snap).longValue())) + .setSize(getVariableValue(function.getParameter(1), snap).longValue()) + .setFunction(function) + .setSnap(snap); + } + + @Override + AllocFreeEvent onExit(long snap) { + allocFreeEventBuilder.setAllocatedAddress( + toAddr(getVariableValue(function.getReturn(), snap).longValue())) + .setFunction(function); + + return super.onExit(snap); + } + } + + class free extends AllocOrFreeFunc { + free(Function function) { + super(function); + } + + @Override + void onEntry(long snap) { + super.onEntry(snap); + allocFreeEventBuilder.setFreedAddress( + toAddr(getVariableValue(function.getParameter(0), snap).longValue())) + .setFunction(function) + .setSnap(snap); + } + +// @Override +// AllocFreeEvent onExit(long snap) { +// allocFreeEventBuilder.setReturnStatus( +// getVariableValue(function.getReturn(), snap).longValue()); +// return super.onExit(snap); +// } + } + + @Override + protected void run() throws Exception { + AllocFreePairings pairing = new AllocFreePairings( + List.of(new malloc(getMatchingFunction("malloc")), + new calloc(getMatchingFunction("calloc")), + new realloc(getMatchingFunction("realloc")), + new free(getMatchingFunction("free")))); + + Map allocationTracker = new HashMap<>(); + List allocatedBuffers = new ArrayList<>(); + + for (AllocFreeEvent event : pairing.getAllEvents()) { + if (event.freedAddress != null) { + if (!allocationTracker.containsKey(event.freedAddress)) { + println("Double free?? %x".formatted(event.freedAddress.getUnsignedOffset())); + } + else { + AllocFreeEvent allocEvent = allocationTracker.get(event.freedAddress); + allocatedBuffers.add( + new AllocatedBuffer(Lifespan.span(allocEvent.snap, event.snap), + allocEvent.size(), allocEvent.allocedAddress)); + allocationTracker.remove(event.freedAddress); + } + } + if (event.allocedAddress != null) { + if (allocationTracker.containsKey(event.allocedAddress)) { + println("Did we miss a free?? %x".formatted( + event.allocedAddress.getUnsignedOffset())); + } + else { + allocationTracker.put(event.allocedAddress, event); + } + } + } + + println("Unfreed memory: %s".formatted(allocationTracker.keySet())); + println("Allocated buffers: %s".formatted(allocatedBuffers)); + } + + public Function getMatchingFunction(String funcName) { + for (Program program : state.getTool() + .getService(ProgramManager.class) + .getAllOpenPrograms()) { + for (Function f : program.getFunctionManager().getFunctions(true)) { + if (!f.isThunk() && !f.isExternal() && funcName.equals(f.getName().toLowerCase())) { + return f; + } + } + } + return null; + } +} diff --git a/Ghidra/Debug/Debugger/developer_scripts/EmulateGapsInTrace.java b/Ghidra/Debug/Debugger/developer_scripts/EmulateGapsInTrace.java new file mode 100644 index 0000000000..bcd5c97e02 --- /dev/null +++ b/Ghidra/Debug/Debugger/developer_scripts/EmulateGapsInTrace.java @@ -0,0 +1,278 @@ +/* ### + * 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. + */ +//EXPERIMENTAL script that emulates instructions between snapshots in a sparse trace +//@category Debugger +//@keybinding +//@menupath +//@toolbar + +import java.nio.ByteBuffer; +import java.util.*; + +import db.Transaction; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationIntegration; +import ghidra.app.plugin.core.debug.service.emulation.Mode; +import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; +import ghidra.app.script.GhidraScript; +import ghidra.debug.flatapi.FlatDebuggerAPI; +import ghidra.pcode.emu.*; +import ghidra.pcode.exec.BytesPcodeArithmetic; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.trace.TraceEmulationIntegration; +import ghidra.pcode.exec.trace.data.PcodeTraceAccess; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.SourceType; +import ghidra.trace.database.DBTrace; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectManager; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; + +import static ghidra.pcode.exec.BytesPcodeArithmetic.forLanguage; + +public class EmulateGapsInTrace extends GhidraScript implements FlatDebuggerAPI { + private static class CustomWriter extends TraceEmulationIntegration.TraceWriter { + final Map readMap = new HashMap<>(); + final Map writeMap = new HashMap<>(); + final BytesPcodeArithmetic arithmetic; + SleighInstructionDecoder decoder = null; + + public CustomWriter(PcodeTraceAccess access, Language language) { + super(access); + arithmetic = forLanguage(language); + } + + @Override + public void beforeLoad(PcodeThread thread, PcodeOp op, AddressSpace space, + Object offset, int size) { + if (offset instanceof byte[] off) { + readMap.put(arithmetic.toAddress(off, space, PcodeArithmetic.Purpose.INSPECT), + size); + } + super.beforeLoad(thread, op, space, offset, size); + } + + @Override + public void beforeStore(PcodeThread thread, PcodeOp op, AddressSpace space, + Object offset, int size, Object value) { + if (offset instanceof byte[] off) { + writeMap.put(arithmetic.toAddress(off, space, PcodeArithmetic.Purpose.INSPECT), + size); + } + super.beforeStore(thread, op, space, offset, size, value); + } + } + + @Override + protected void run() throws Exception { + final Trace fromTrace = requireCurrentTrace(); + final TraceThread fromThread = requireCurrentThread(); + Iterator iterator = fromTrace.getTimeManager() + .getSnapshots(getCurrentSnap(), true, fromTrace.getTimeManager().getMaxSnap(), + true) + .iterator(); + + TraceSnapshot cur = iterator.next(); + Trace toTrace = new DBTrace("%s Emulated Gaps".formatted(fromTrace.getName()), + fromTrace.getBaseCompilerSpec(), this); + final TraceObjectManager om = toTrace.getObjectManager(); + + try (Transaction ignored = toTrace.openTransaction("Emulate the gaps"); + TraceObjectManager.BypassWriteCache ignored1 = om.withoutWriteCache()) { + om.createRootObject(fromTrace.getObjectManager().getRootSchema()); + + monitor.initialize(fromTrace.getTimeManager().getMaxSnap() - getCurrentSnap()); + while (iterator.hasNext()) { + monitor.increment(); + TraceSnapshot next = iterator.next(); + + TraceThread toThread = copySnapshotToTrace(cur, toTrace); + + if (emulateToNextSnapshot(cur, fromThread, fromTrace, toTrace, toThread, next)) { + break; + } + cur = next; + } + activateTrace(toTrace); + } + catch (Exception e) { + activateTrace(toTrace); + throw e; + } + } + + private TraceThread copySnapshotToTrace(TraceSnapshot fromSnapshot, Trace toTrace) { + TraceSnapshot snapshot = + toTrace.getTimeManager().createSnapshot(fromSnapshot.getDescription()); + Trace fromTrace = fromSnapshot.getTrace(); + TraceThread fromThread = fromSnapshot.getEventThread(); + + fromTrace.getObjectManager() + .getAllObjects() + .filter(o -> o.getLife().contains(fromSnapshot.getKey()) && !o.isRoot()) + .forEachOrdered(fromTraceObject -> { + TraceObject toTraceObject = toTrace.getObjectManager() + .createObject(fromTraceObject.getCanonicalPath()); + toTraceObject.insert(Lifespan.nowOn(snapshot.getKey()), + TraceObject.ConflictResolution.DENY); + fromTraceObject.getValues(Lifespan.at(fromSnapshot.getKey())) + .forEach(fromTraceObjectValue -> { + if (!(fromTraceObjectValue.getValue() instanceof TraceObject)) { + toTraceObject.setValue(Lifespan.nowOn(snapshot.getKey()), + fromTraceObjectValue.getEntryKey(), + fromTraceObjectValue.getValue()); + } + }); + }); + + TraceThread thread = toTrace.getThreadManager().getThread(fromThread.getKey()); + snapshot.setEventThread(thread); + + for (AddressRange addressRange : fromTrace.getMemoryManager() + .getAddressesWithState(fromSnapshot.getKey(), + s -> s != TraceMemoryState.UNKNOWN)) { + ByteBuffer buffer = ByteBuffer.allocate((int) addressRange.getLength()); + fromTrace.getMemoryManager() + .getBytes(fromSnapshot.getKey(), addressRange.getMinAddress(), buffer); + buffer.flip(); + if (addressRange.getMinAddress().isRegisterAddress()) { + Address regAddress = toTrace.getMemoryManager() + .getMemoryRegisterSpace(thread, true) + .getAddressSpace() + .getAddress(addressRange.getMinAddress().getOffset()); + toTrace.getMemoryManager() + .getMemoryRegisterSpace(thread, true) + .putBytes(snapshot.getKey(), regAddress, buffer); + } + else { + toTrace.getMemoryManager() + .putBytes(snapshot.getKey(), toTrace.getBaseAddressFactory() + .getDefaultAddressSpace() + .getAddress(addressRange.getMinAddress().getOffset()), buffer); + } + } + + return thread; + } + + private boolean emulateToNextSnapshot(TraceSnapshot curFromSnapshot, TraceThread fromThread, + Trace fromTrace, Trace toTrace, TraceThread toThread, TraceSnapshot nextFromSnapshot) { + Register programCounter = fromTrace.getBaseLanguage().getProgramCounter(); + + TraceMemorySpace registerSpace = + fromTrace.getMemoryManager().getMemoryRegisterSpace(fromThread, false); + + RegisterValue endPCValue = + registerSpace.getViewValue(nextFromSnapshot.getKey(), programCounter); + + int tickCount = 1; + TraceSchedule schedule = TraceSchedule.snap(curFromSnapshot.getKey()) + .steppedForward(fromThread, tickCount++); + +// TraceEmulationIntegration.Writer writer = +// DebuggerEmulationIntegration.bytesDelayedWriteTrace( +// new DefaultPcodeDebuggerAccess(getState().getTool(), null, +// fromTrace.getPlatformManager().getHostPlatform(), +// curFromSnapshot.getKey())); + + var writer = new CustomWriter(new DefaultPcodeDebuggerAccess(getState().getTool(), null, + fromTrace.getPlatformManager().getHostPlatform(), curFromSnapshot.getKey()), + fromTrace.getBaseLanguage()); + writer.putHandler(new DebuggerEmulationIntegration.TargetBytesPieceHandler(Mode.RO)); + + PcodeEmulator emulator = new PcodeEmulator(fromTrace.getBaseLanguage(), writer.callbacks()); + + PcodeThread pcodeThread = emulator.getThread(fromThread.getPath(), true); +// ThreadPcodeExecutorState state = pcodeThread.getState(); +// for (Register register : fromTrace.getBaseLanguage().getRegisters()) { +// if (!register.isBaseRegister()) { +// continue; +// } +// RegisterValue value = fromTrace.getMemoryManager() +// .getMemoryRegisterSpace(fromThread, true) +// .getValue(curFromSnapshot.getKey(), register); +// state.setRegisterValue(register, value); +// } + + try { + while (true) { + monitor.checkCancelled(); + pcodeThread.stepInstruction(); + if (pcodeThread.getCounter().getOffset() == + endPCValue.getUnsignedValue().longValue()) { + break; + } + + TraceSnapshot snapshot = + toTrace.getTimeManager().createSnapshot("Emulated " + schedule); + Msg.info(this, "Snapshot %d".formatted(snapshot.getKey())); + snapshot.setSchedule(schedule); + snapshot.setEventThread(toThread); + TraceStackFrame frame = toTrace.getStackManager() + .getStack(toThread, snapshot.getKey(), true) + .getFrame(snapshot.getKey(), 0, true); + frame.setProgramCounter(Lifespan.nowOn(snapshot.getKey()), + pcodeThread.getCounter()); + writer.writeDown(new DefaultPcodeDebuggerAccess(getState().getTool(), null, + toTrace.getPlatformManager().getHostPlatform(), snapshot.getKey())); + for (Map.Entry entry : writer.readMap.entrySet()) { + AddressRangeImpl range = new AddressRangeImpl(entry.getKey(), + entry.getValue()); + toTrace.getReferenceManager() + .addMemoryReference(Lifespan.at(snapshot.getKey() - 1), + addr(toTrace, pcodeThread.getCounter().getOffset()), range, + RefType.READ, SourceType.USER_DEFINED, -1); + } + + for (Map.Entry entry : writer.writeMap.entrySet()) { + AddressRangeImpl range = new AddressRangeImpl(entry.getKey(), + entry.getValue()); + toTrace.getReferenceManager() + .addMemoryReference(Lifespan.at(snapshot.getKey() - 1), + addr(toTrace, pcodeThread.getCounter().getOffset()), range, + RefType.WRITE, SourceType.USER_DEFINED, -1); + } + + writer.readMap.clear(); + writer.writeMap.clear(); + schedule = TraceSchedule.snap(curFromSnapshot.getKey()) + .steppedForward(fromThread, tickCount++); + } + } + catch (CancelledException _) { + return true; + } + catch (Exception e) { + printerr(e.getMessage()); + } + return false; + } + + Address addr(Trace trace, long addr) { + return trace.getBaseAddressFactory().getDefaultAddressSpace().getAddress(addr); + } +}