diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py index 21db9c8f19..07920515cc 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py @@ -1224,6 +1224,7 @@ def put_regions(): regobj.set_value('_executable', r.perms == None or 'x' in r.perms) regobj.set_value('_offset', r.offset) regobj.set_value('_objfile', r.objfile) + regobj.set_value('_display', f'{r.objfile} (0x{r.start:x}-0x{r.end:x})') regobj.insert() STATE.trace.proxy_object_path( MEMORY_PATTERN.format(infnum=inf.num)).retain_values(keys) diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/util.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/util.py index 47d141936f..f883d39c54 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/util.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/util.py @@ -57,6 +57,7 @@ GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for " class Module(namedtuple('BaseModule', ['name', 'base', 'max', 'sections'])): pass + class Index: def __init__(self, regions): self.regions = {} @@ -64,6 +65,7 @@ class Index: for r in regions: self.regions[r.start] = r self.bases.append(r.start) + def compute_base(self, address): index = bisect.bisect_right(self.bases, address) - 1 if index == -1: @@ -78,6 +80,7 @@ class Index: else: return region.start + class Section(namedtuple('BaseSection', ['name', 'start', 'end', 'offset', 'attrs'])): def better(self, other): start = self.start if self.start != 0 else other.start @@ -103,8 +106,6 @@ class ModuleInfoReader(object): if mat is None: return None n = mat['name'] - if n.startswith(GNU_DEBUGDATA_PREFIX): - return None return None if mat is None else mat['name'] def section_from_line(self, line): @@ -135,7 +136,7 @@ class ModuleInfoReader(object): for line in out.split('\n'): n = self.name_from_line(line) if n is not None: - if name is not None: + if name is not None and not name.startswith(GNU_DEBUGDATA_PREFIX): modules[name] = self.finish_module(name, sections, index) name = n sections = {} @@ -148,7 +149,7 @@ class ModuleInfoReader(object): if s.name in sections: s = s.better(sections[s.name]) sections[s.name] = s - if name is not None: + if name is not None and not name.startswith(GNU_DEBUGDATA_PREFIX): modules[name] = self.finish_module(name, sections, index) return modules @@ -292,8 +293,9 @@ class BreakpointLocationInfoReaderV8(object): inf = gdb.selected_inferior() thread_groups = [inf.num] if breakpoint.location is not None and breakpoint.location.startswith("*0x"): - address = int(breakpoint.location[1:],16) - loc = BreakpointLocation(address, breakpoint.enabled, thread_groups) + address = int(breakpoint.location[1:], 16) + loc = BreakpointLocation( + address, breakpoint.enabled, thread_groups) return [loc] return [] @@ -312,7 +314,8 @@ class BreakpointLocationInfoReaderV9(object): return [] try: address = gdb.parse_and_eval(breakpoint.location).address - loc = BreakpointLocation(address, breakpoint.enabled, thread_groups) + loc = BreakpointLocation( + address, breakpoint.enabled, thread_groups) return [loc] except Exception as e: print(f"Error parsing bpt location = {breakpoint.location}") @@ -338,6 +341,7 @@ def _choose_breakpoint_location_info_reader(): BREAKPOINT_LOCATION_INFO_READER = _choose_breakpoint_location_info_reader() + def set_bool_param_by_api(name, value): gdb.set_parameter(name, value) @@ -353,6 +357,7 @@ def choose_set_parameter(): else: return set_bool_param_by_cmd + set_bool_param = choose_set_parameter() @@ -360,7 +365,7 @@ def get_level(frame): if hasattr(frame, "level"): return frame.level() else: - level = -1; + level = -1 f = frame while f is not None: level += 1 @@ -371,16 +376,16 @@ def get_level(frame): class RegisterDesc(namedtuple('BaseRegisterDesc', ['name'])): pass + def get_register_descs(arch, group='all'): if hasattr(arch, "registers"): return arch.registers(group) else: descs = [] - regset = gdb.execute(f"info registers {group}", to_string=True).strip().split('\n') + regset = gdb.execute( + f"info registers {group}", to_string=True).strip().split('\n') for line in regset: if not line.startswith(" "): tokens = line.strip().split() - descs.append(RegisterDesc(tokens[0])) + descs.append(RegisterDesc(tokens[0])) return descs - - diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchAction.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchAction.java index 8ec4b59839..4db42408e7 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchAction.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchAction.java @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.debug.gui.tracermi.launcher; +import static ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin.getProgramName; + import java.util.*; import java.util.stream.Stream; @@ -52,7 +54,7 @@ public class LaunchAction extends MultiActionDockingAction { Program program = plugin.currentProgram; String title = program == null ? "Configure and Launch ..." - : "Configure and Launch %s using...".formatted(program.getName()); + : "Configure and Launch %s using...".formatted(getProgramName(program)); return Stream.concat(Stream.of(title), menuPath.stream()).toArray(String[]::new); } @@ -77,12 +79,12 @@ public class LaunchAction extends MultiActionDockingAction { Long last = saved.get(offer.getConfigName()); if (last == null) { // NB. If program == null, this will always happen. - // Thus, no worries about program.getName() below. + // Thus, no worries about getProgramName(program) below. continue; } String title = program == null ? "Re-launch " + offer.getTitle() - : "Re-launch %s using %s".formatted(program.getName(), offer.getTitle()); + : "Re-launch %s using %s".formatted(getProgramName(program), offer.getTitle()); actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName()) .popupMenuPath(title) .popupMenuGroup("0", "%016x".formatted(Long.MAX_VALUE - last)) @@ -158,11 +160,11 @@ public class LaunchAction extends MultiActionDockingAction { return "Configure and launch"; } if (offer == null) { - return "Configure and launch " + program.getName(); + return "Configure and launch " + getProgramName(program); } if (program == null) { return "Re-launch " + offer.getTitle(); } - return "Re-launch %s using %s".formatted(program.getName(), offer.getTitle()); + return "Re-launch %s using %s".formatted(getProgramName(program), offer.getTitle()); } } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java index 3f15ebe1cb..9a32f67f59 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java @@ -38,6 +38,7 @@ import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchConfigurator; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.PromptMode; import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion; import ghidra.formats.gfilesystem.FSRL; +import ghidra.framework.model.DomainFile; import ghidra.framework.options.*; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @@ -236,10 +237,18 @@ public class TraceRmiLauncherServicePlugin extends Plugin executeTask(new ConfigureAndLaunchTask(offer)); } + protected static String getProgramName(Program program) { + DomainFile df = program.getDomainFile(); + if (df != null) { + return df.getName(); + } + return program.getName(); + } + protected String[] constructLaunchMenuPrefix() { return new String[] { DebuggerPluginPackage.NAME, - "Configure and Launch " + currentProgram.getName() + " using..." }; + "Configure and Launch " + getProgramName(currentProgram) + " using..." }; } protected String[] prependConfigAndLaunch(List menuPath) { diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java index 49bd096cd9..e1c1252a49 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java @@ -810,6 +810,11 @@ public class TraceRmiHandler implements TraceRmiConnection { } DebuggerCoordinates finalCoords = object == null ? coords : coords.object(object); Swing.runLater(() -> { + DebuggerTraceManagerService traceManager = this.traceManager; + if (traceManager == null) { + // Can happen during tear down. + return; + } if (!traceManager.getOpenTraces().contains(open.trace)) { traceManager.openTrace(open.trace); traceManager.activate(finalCoords, ActivationCause.SYNC_MODEL); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoMapSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoMapSpec.java index b46c2526c6..466fe92371 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoMapSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/AutoMapSpec.java @@ -26,7 +26,6 @@ import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.ProgramManager; import ghidra.framework.cmd.BackgroundCommand; -import ghidra.framework.model.DomainObject; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.AutoConfigState.ConfigFieldCodec; import ghidra.framework.plugintool.PluginTool; @@ -104,9 +103,9 @@ public interface AutoMapSpec extends ExtensionPoint { if (mappingService == null || programManager == null) { return; } - BackgroundCommand cmd = new BackgroundCommand(getTaskTitle(), true, true, false) { + BackgroundCommand cmd = new BackgroundCommand<>(getTaskTitle(), true, true, false) { @Override - public boolean applyTo(DomainObject obj, TaskMonitor monitor) { + public boolean applyTo(Trace trace, TaskMonitor monitor) { try { performMapping(mappingService, trace, programManager, monitor); return true; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java index b08a143fd4..2b5f608ac6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java @@ -799,7 +799,7 @@ public class DebuggerCopyIntoProgramDialog extends ReusableDialogComponentProvid protected void executeCapture(AddressRange range, Target target, TaskMonitor monitor) throws Exception { - target.readMemory(new AddressSet(range), monitor); + target.readMemoryAsync(new AddressSet(range), monitor).get(); } protected void executePlan(TaskMonitor monitor) throws Exception { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DisplaysObjectValues.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DisplaysObjectValues.java index 9502d5ae4a..008e216a1d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DisplaysObjectValues.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DisplaysObjectValues.java @@ -64,6 +64,11 @@ public interface DisplaysObjectValues { .collect(Collectors.joining(":")); } + default String getStringsDisplay(String[] strings) { + return Stream.of(strings) + .collect(Collectors.joining(":")); + } + default String getPrimitiveValueDisplay(Object value) { assert !(value instanceof TraceObject); assert !(value instanceof TraceObjectValue); @@ -89,6 +94,9 @@ public interface DisplaysObjectValues { if (value instanceof long[] longs) { return getLongsDisplay(longs); } + if (value instanceof String[] strings) { + return getStringsDisplay(strings); + } return value.toString(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java index f86003460d..d39e427883 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java @@ -42,13 +42,13 @@ public class DebuggerModuleMapProposalDialog implements EnumeratedTableColumn { REMOVE("Remove", String.class, e -> "Remove Proposed Entry", (e, v) -> nop()), MODULE_NAME("Module", String.class, e -> e.getModule().getName()), - DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getModule().getBase()), + DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getFromRange().getMinAddress()), CHOOSE("Choose", String.class, e -> "Choose Program", (e, v) -> nop()), PROGRAM_NAME("Program", String.class, e -> (e.getToProgram().getDomainFile() == null ? e.getToProgram().getName() : e.getToProgram().getDomainFile().getName())), - STATIC_BASE("Static Base", Address.class, e -> e.getToProgram().getImageBase()), - SIZE("Size", Long.class, e -> e.getModuleRange().getLength()), + STATIC_BASE("Static Base", Address.class, e -> e.getToRange().getMinAddress()), + SIZE("Size", Long.class, e -> e.getFromRange().getLength()), MEMORIZE("Memorize", Boolean.class, ModuleMapEntry::isMemorize, ModuleMapEntry::setMemorize); private final String header; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java index 224d361b88..a63bfa59b2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java @@ -32,6 +32,7 @@ import ghidra.framework.plugintool.util.PluginStatus; packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, eventsConsumed = { + ProgramOpenedPluginEvent.class, ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class, @@ -65,7 +66,10 @@ public class DebuggerModulesPlugin extends AbstractDebuggerPlugin { @Override public void processEvent(PluginEvent event) { super.processEvent(event); - if (event instanceof ProgramActivatedPluginEvent ev) { + if (event instanceof ProgramOpenedPluginEvent ev) { + provider.programOpened(ev.getProgram()); + } + else if (event instanceof ProgramActivatedPluginEvent ev) { provider.setProgram(ev.getActiveProgram()); } else if (event instanceof ProgramLocationPluginEvent ev) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java index 6a65bdea8b..4b20b4498d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java @@ -1078,6 +1078,12 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { actionMapSectionTo.getPopupMenuData().setMenuItemName(name); } + public void programOpened(Program program) { + // TODO: Debounce this? + cueAutoMap = true; + doCuedAutoMap(); + } + public void programClosed(Program program) { if (currentProgram == program) { currentProgram = null; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java index 3dde790938..24795e3966 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java @@ -614,7 +614,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { pcodeTable.setTableHeader(null); pcodeTable.setBackground(COLOR_BACKGROUND); pcodeTable.setSelectionBackground(COLOR_BACKGROUND_CURSOR); - pcodeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + pcodeTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); pcodeTable.getSelectionModel().addListSelectionListener(evt -> { if (evt.getValueIsAdjusting()) { return; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/UniqueRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/UniqueRow.java index d891dd909e..4f6c78ccd1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/UniqueRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/UniqueRow.java @@ -106,6 +106,9 @@ public class UniqueRow { } public RefType getRefType() { + if (provider.pcodeTable.getSelectedRowCount() != 1) { + return RefType.NONE; + } int index = provider.pcodeTable.getSelectedRow(); if (index == -1) { return RefType.NONE; 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 808c307444..54bca75719 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 @@ -42,8 +42,8 @@ import ghidra.trace.database.DBTrace; import ghidra.trace.model.*; import ghidra.trace.model.memory.*; import ghidra.trace.model.modules.TraceConflictedMappingException; -import ghidra.trace.model.target.TraceObject; -import ghidra.trace.model.target.TraceObjectKeyPath; +import ghidra.trace.model.target.*; +import ghidra.trace.model.target.TraceObject.ConflictResolution; import ghidra.trace.model.thread.*; import ghidra.trace.model.time.TraceSnapshot; import ghidra.util.*; @@ -70,7 +70,8 @@ public class ProgramEmulationUtils { - + @@ -494,6 +495,20 @@ public class ProgramEmulationUtils { throw new EmulatorOutOfMemoryException(); } + protected static void createObjects(Trace trace) { + TraceObjectManager om = trace.getObjectManager(); + om.createRootObject(EMU_SESSION_SCHEMA); + + om.createObject(TraceObjectKeyPath.parse("Breakpoints")) + .insert(Lifespan.ALL, ConflictResolution.DENY); + om.createObject(TraceObjectKeyPath.parse("Memory")) + .insert(Lifespan.ALL, ConflictResolution.DENY); + om.createObject(TraceObjectKeyPath.parse("Modules")) + .insert(Lifespan.ALL, ConflictResolution.DENY); + om.createObject(TraceObjectKeyPath.parse("Threads")) + .insert(Lifespan.ALL, ConflictResolution.DENY); + } + /** * Create a new trace with a single thread, ready for emulation of the given program * @@ -510,7 +525,8 @@ public class ProgramEmulationUtils { try { trace = new DBTrace(getTraceName(program), program.getCompilerSpec(), consumer); try (Transaction tx = trace.openTransaction("Emulate")) { - trace.getObjectManager().createRootObject(EMU_SESSION_SCHEMA); + createObjects(trace); + TraceSnapshot initial = trace.getTimeManager().createSnapshot(EMULATION_STARTED_AT + pc); long snap = initial.getKey(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java index 395e2df02d..c4d4739199 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java @@ -34,7 +34,6 @@ import ghidra.program.util.ProgramLocation; import ghidra.trace.model.*; import ghidra.trace.model.modules.*; import ghidra.trace.model.program.TraceProgramView; -import ghidra.trace.model.thread.TraceThread; import ghidra.util.ComparatorMath; import ghidra.util.Msg; @@ -163,11 +162,17 @@ public enum DebuggerStaticMappingUtils { private Address min = null; private Address max = null; + public void consider(Address min, Address max) { + this.min = this.min == null ? min : ComparatorMath.cmin(this.min, min); + this.max = this.max == null ? max : ComparatorMath.cmax(this.max, max); + } + public void consider(AddressRange range) { - min = min == null ? range.getMinAddress() - : ComparatorMath.cmin(min, range.getMinAddress()); - max = max == null ? range.getMaxAddress() - : ComparatorMath.cmax(max, range.getMaxAddress()); + consider(range.getMinAddress(), range.getMaxAddress()); + } + + public void consider(Address address) { + consider(address, address); } public Address getMin() { @@ -181,6 +186,10 @@ public enum DebuggerStaticMappingUtils { public long getLength() { return max.subtract(min) + 1; } + + public AddressRange getRange() { + return new AddressRangeImpl(getMin(), getMax()); + } } public static boolean isReal(MemoryBlock block) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DefaultModuleMapProposal.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DefaultModuleMapProposal.java index 405bcf0d06..768849246f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DefaultModuleMapProposal.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DefaultModuleMapProposal.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.service.modules; import java.util.*; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils.Extrema; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.debug.api.modules.ModuleMapProposal; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; @@ -27,6 +28,7 @@ import ghidra.trace.model.Lifespan; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceObjectMemoryRegion; import ghidra.trace.model.modules.TraceModule; +import ghidra.util.MathUtilities; public class DefaultModuleMapProposal extends AbstractMapProposal @@ -81,20 +83,24 @@ public class DefaultModuleMapProposal * @param program the program image whose size to compute * @return the size */ - public static long computeImageSize(Program program) { - Address imageBase = program.getImageBase(); - long imageSize = 0; + public static AddressRange computeImageRange(Program program) { + Extrema extrema = new Extrema(); // TODO: How to handle Harvard architectures? for (MemoryBlock block : program.getMemory().getBlocks()) { if (!includeBlock(program, block)) { continue; } - imageSize = Math.max(imageSize, block.getEnd().subtract(imageBase) + 1); + // includeBlock checks address space is same as image base + extrema.consider(block.getAddressRange()); } - return imageSize; + if (program.getImageBase().getOffset() != 0) { + extrema.consider(program.getImageBase()); + } + return extrema.getRange(); } protected AddressRange moduleRange; + protected AddressRange imageRange; protected boolean memorize = false; /** @@ -113,6 +119,7 @@ public class DefaultModuleMapProposal AddressRange moduleRange) { super(module.getTrace(), module, program, program); this.moduleRange = moduleRange; + this.imageRange = quantize(computeImageRange(program)); } @Override @@ -125,9 +132,18 @@ public class DefaultModuleMapProposal return getModule().getLifespan(); } + private long getLength() { + return MathUtilities.unsignedMin(moduleRange.getLength(), imageRange.getLength()); + } + @Override public AddressRange getFromRange() { - return moduleRange; + try { + return new AddressRangeImpl(moduleRange.getMinAddress(), getLength()); + } + catch (AddressOverflowException e) { + throw new AssertionError(e); + } } @Override @@ -138,21 +154,13 @@ public class DefaultModuleMapProposal @Override public void setProgram(Program program) { setToObject(program, program); - try { - this.moduleRange = quantize( - new AddressRangeImpl(getModule().getBase(), computeImageSize(program))); - } - catch (AddressOverflowException e) { - // This is terribly unlikely - throw new IllegalArgumentException( - "Specified program is too large for module's memory space"); - } + this.imageRange = quantize(computeImageRange(program)); } @Override public AddressRange getToRange() { try { - return new AddressRangeImpl(getToProgram().getImageBase(), moduleRange.getLength()); + return new AddressRangeImpl(imageRange.getMinAddress(), getLength()); } catch (AddressOverflowException e) { throw new AssertionError(e); @@ -174,10 +182,8 @@ public class DefaultModuleMapProposal // indexed by region's offset from module base protected final NavigableMap matchers = new TreeMap<>(); - protected Address imageBase; - protected Address moduleBase; - protected long imageSize; - protected AddressRange moduleRange; // TODO: This is now in the trace schema. Use it. + protected AddressRange imageRange; + protected AddressRange moduleRange; protected DefaultModuleMapProposal(TraceModule module, Program program) { super(module.getTrace(), program); @@ -196,8 +202,8 @@ public class DefaultModuleMapProposal } private void processProgram() { - imageBase = program.getImageBase(); - imageSize = DefaultModuleMapEntry.computeImageSize(program); + imageRange = quantize(DefaultModuleMapEntry.computeImageRange(program)); + Address imageBase = imageRange.getMinAddress(); // not precisely, but good enough // TODO: How to handle Harvard architectures? for (MemoryBlock block : program.getMemory().getBlocks()) { if (!DefaultModuleMapEntry.includeBlock(program, block)) { @@ -211,13 +217,8 @@ public class DefaultModuleMapProposal * Must be called after processProgram, so that image size is known */ private void processModule() { - moduleBase = module.getBase(); - try { - moduleRange = quantize(new AddressRangeImpl(moduleBase, imageSize)); - } - catch (AddressOverflowException e) { - return; // Just score it as having no matches? - } + moduleRange = quantize(module.getRange()); + Address moduleBase = moduleRange.getMinAddress(); Lifespan lifespan = module.getLifespan(); for (TraceMemoryRegion region : module.getTrace() .getMemoryManager() diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathUtils.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathUtils.java index f147a57c4e..8315c8dd82 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathUtils.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathUtils.java @@ -499,6 +499,21 @@ public enum PathUtils { return Objects.equals(ancestor, successor.subList(0, ancestor.size())); } + /** + * Assuming the first path is an ancestor of the second, compute the relative path from the + * first to the second. + * + * @param ancestor the ancestor (from) + * @param successor the successor (to) + * @return the relative path + */ + public static List relativize(List ancestor, List successor) { + if (!isAncestor(ancestor, successor)) { + throw new IllegalArgumentException("First must be an ancestor of the second"); + } + return successor.subList(ancestor.size(), successor.size()); + } + /** * Check whether a given object-valued attribute is a link. * @@ -538,6 +553,7 @@ public enum PathUtils { * Check whether a given attribute should be displayed. * * @param key the key of the given attribute + * @return true if hidden */ public static boolean isHidden(String key) { return key.startsWith(TargetObject.PREFIX_INVISIBLE); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java index a64c98b708..18152312b4 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java @@ -121,8 +121,19 @@ public class DBTraceObjectBreakpointLocation @Override public String getName() { try (LockHold hold = object.getTrace().lockRead()) { - return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), - TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); + String display = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), + TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, null); + if (display != null) { + return display; + } + TraceObject container = + object.queryCanonicalAncestorsTargetInterface(TargetBreakpointSpecContainer.class) + .findFirst() + .orElse(null); + if (container == null) { + return ""; // Should be impossible, but maybe not a sane schema + } + return container.getCanonicalPath().relativize(object.getCanonicalPath()).toString(); } } @@ -312,8 +323,16 @@ public class DBTraceObjectBreakpointLocation @Override public String getComment() { try (LockHold hold = object.getTrace().lockRead()) { - return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_COMMENT, - String.class, ""); + String comment = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), + KEY_COMMENT, String.class, ""); + if (!comment.isBlank()) { + return comment; + } + TraceObjectBreakpointSpec spec = getSpecification(); + if (spec == null) { + return ""; + } + return spec.getExpression(); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java index 570d5b5fd8..f762d0e017 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java @@ -181,6 +181,12 @@ public class DBTraceObjectBreakpointSpec } } + @Override + public String getExpression() { + return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), + TargetBreakpointSpec.EXPRESSION_ATTRIBUTE_NAME, String.class, null); + } + @Override public Set getThreads() { throw new UnsupportedOperationException("Ask a location instead"); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java index 479963e416..d37e4f5df6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java @@ -18,7 +18,12 @@ package ghidra.trace.database.time; import java.io.IOException; import db.DBRecord; +import ghidra.dbg.target.TargetEventScope; +import ghidra.trace.database.target.DBTraceObject; import ghidra.trace.model.Trace; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectValue; +import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.TraceSchedule; @@ -143,7 +148,26 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot @Override public TraceThread getEventThread() { try (LockHold hold = LockHold.lock(manager.lock.readLock())) { - return eventThread; + if (eventThread != null) { + return eventThread; + } + // TODO: Can it be something other than root? + DBTraceObject root = manager.trace.getObjectManager().getRootObject(); + if (root == null) { + return null; + } + if (!root.getTargetSchema().getInterfaces().contains(TargetEventScope.class)) { + return null; + } + TraceObjectValue eventAttr = + root.getAttribute(getKey(), TargetEventScope.EVENT_OBJECT_ATTRIBUTE_NAME); + if (eventAttr == null) { + return null; + } + if (!(eventAttr.getValue() instanceof TraceObject eventObj)) { + return null; + } + return eventObj.queryInterface(TraceObjectThread.class); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java index 7ff66e1783..294ff11ba0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java @@ -43,5 +43,7 @@ public interface TraceObjectBreakpointSpec extends TraceBreakpoint, TraceObjectI Collection getLocations(); + String getExpression(); + void setKinds(Lifespan lifespan, Collection kinds); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java index ece0f8b69c..1de27134a0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java @@ -224,8 +224,8 @@ public final class TraceObjectKeyPath implements Comparable /** * Stream, starting with the longer paths, paths that match the given predicates * - * @param matcher - * @return + * @param predicates the predicates to filter the ancestor paths + * @return the stream of matching paths, longest to shortest */ public Stream streamMatchingAncestry(PathPredicates predicates) { if (!predicates.ancestorMatches(keyList, false)) { @@ -253,4 +253,15 @@ public final class TraceObjectKeyPath implements Comparable public boolean isAncestor(TraceObjectKeyPath that) { return PathUtils.isAncestor(keyList, that.keyList); } + + /** + * Assuming this is an ancestor of the given successor, compute the relative path from here to + * there + * + * @param successor the successor + * @return the relative path + */ + public TraceObjectKeyPath relativize(TraceObjectKeyPath successor) { + return TraceObjectKeyPath.of(PathUtils.relativize(keyList, successor.keyList)); + } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java index b7e62a3d40..168d6078de 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertTrue; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.io.*; +import java.nio.charset.Charset; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.util.*; @@ -50,23 +51,24 @@ import ghidra.app.plugin.core.debug.gui.memory.DebuggerMemoryBytesProvider; import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsProvider; import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider; import ghidra.app.plugin.core.debug.gui.modules.DebuggerStaticMappingProvider; +import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog; import ghidra.app.plugin.core.debug.gui.pcode.DebuggerPcodeStepperPlugin; import ghidra.app.plugin.core.debug.gui.pcode.DebuggerPcodeStepperProvider; import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider; import ghidra.app.plugin.core.debug.gui.stack.DebuggerStackProvider; import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueHoverPlugin; -import ghidra.app.plugin.core.debug.gui.target.DebuggerTargetsPlugin; import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsProvider; import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeProvider; import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog; +import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerPlugin; import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesProvider; import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin.EmulateProgramAction; -import ghidra.app.plugin.core.debug.service.model.DebuggerConnectDialog; import ghidra.app.plugin.core.debug.stack.StackUnwinderTest; import ghidra.app.plugin.core.debug.stack.StackUnwinderTest.HoverLocation; import ghidra.app.plugin.core.debug.stack.UnwindStackCommand; import ghidra.app.plugin.core.decompile.DecompilerProvider; +import ghidra.app.plugin.core.terminal.TerminalProvider; import ghidra.app.script.GhidraState; import ghidra.app.services.*; import ghidra.app.services.DebuggerEmulationService.EmulationResult; @@ -74,19 +76,17 @@ import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.MessageLog; import ghidra.app.util.opinion.LoadResults; import ghidra.async.AsyncTestUtils; -import ghidra.dbg.DebuggerModelFactory; -import ghidra.dbg.target.TargetLauncher; -import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher; -import ghidra.dbg.testutil.DummyProc; -import ghidra.dbg.util.ConfigurableFactory.Property; -import ghidra.debug.api.model.DebuggerProgramLaunchOffer; -import ghidra.debug.api.model.DebuggerProgramLaunchOffer.*; +import ghidra.debug.api.modules.ModuleMapProposal; +import ghidra.debug.api.tracermi.RemoteMethod; +import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; +import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*; import ghidra.debug.api.watch.WatchRow; -import ghidra.debug.flatapi.FlatDebuggerAPI; +import ghidra.debug.flatapi.FlatDebuggerRmiAPI; import ghidra.framework.Application; import ghidra.framework.TestApplicationUtils; import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainObject; +import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginUtils; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSet; @@ -96,11 +96,11 @@ import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.Symbol; import ghidra.program.util.GhidraProgramUtilities; import ghidra.program.util.ProgramSelection; +import ghidra.pty.*; import ghidra.test.TestEnv; -import ghidra.trace.model.breakpoint.TraceBreakpoint; +import ghidra.trace.model.breakpoint.*; import ghidra.trace.model.guest.TracePlatform; -import ghidra.trace.model.modules.TraceModule; -import ghidra.trace.model.modules.TraceSection; +import ghidra.trace.model.modules.*; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.time.schedule.*; import ghidra.util.InvalidNameException; @@ -117,26 +117,36 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator protected static final String TERMMINES_PATH = "/tmp/termmines"; + static class MyTestEnv extends TestEnv { + public MyTestEnv(String projectName) throws IOException { + super(projectName); + } + + @Override + protected PluginTool launchDefaultToolByName(String toolName) { + return super.launchDefaultToolByName(toolName); + } + } + protected final ConsoleTaskMonitor monitor = new ConsoleTaskMonitor(); + protected TerminalService terminalService; protected ProgramManager programManager; protected CodeViewerService staticListingService; - protected DebuggerModelService modelService; + protected MyTestEnv env; - protected DebuggerModelFactory gdbFactory; - - protected final FlatDebuggerAPI flatDbg = new FlatDebuggerAPI() { + protected final FlatDebuggerRmiAPI flatDbg = new FlatDebuggerRmiAPI() { @Override public GhidraState getState() { Navigatable nav = staticListingService.getNavigatable(); - return new GhidraState(tool, env.getProject(), - nav.getProgram(), nav.getLocation(), nav.getSelection(), nav.getHighlight()); + return new GhidraState(tool, env.getProject(), nav.getProgram(), nav.getLocation(), + nav.getSelection(), nav.getHighlight()); } }; @Override protected TestEnv newTestEnv() throws Exception { - return new TestEnv("DebuggerCourse"); + return env = new MyTestEnv("DebuggerCourse"); } // TODO: Propose this replace waitForProgram @@ -167,7 +177,7 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator @Override public void prepareTool() { - tool = env.launchTool("Debugger"); + tool = env.launchDefaultToolByName("Debugger"); } @Override @@ -214,20 +224,23 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator } termmines.setExecutable(true); + terminalService = tool.getService(TerminalService.class); programManager = tool.getService(ProgramManager.class); staticListingService = getStaticListingService(); + } - modelService = tool.getService(DebuggerModelService.class); - gdbFactory = modelService.getModelFactories() - .stream() - .filter(f -> "gdb".equals(f.getBrief())) - .findAny() - .get(); + @Test + public void testGettingStarted_Termmines() throws Throwable { + Pty pty = PtyFactory.local().openpty(); + pty.getChild().session(new String[] { TERMMINES_PATH }, Map.of("TERM", "xterm-256color")); + PtyParent parent = pty.getParent(); + try (Terminal terminal = + terminalService.createWithStreams(Charset.forName("utf8"), parent.getInputStream(), + parent.getOutputStream())) { - @SuppressWarnings("unchecked") - Property gdbPathProp = - (Property) gdbFactory.getOptions().get("GDB launch command"); - gdbPathProp.setValue(DummyProc.which("gdb")); + TerminalProvider provider = waitForComponentProvider(TerminalProvider.class); + captureIsolatedProvider(provider, 600, 600); + } } @Test @@ -235,32 +248,46 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator captureToolWindow(1920, 1080); } - protected void launchProgramInGdb(String extraArgs) throws Throwable { - DebuggerProgramLaunchOffer offer = modelService.getProgramLaunchOffers(program) - .filter(o -> "IN-VM GDB".equals(o.getConfigName())) - .findFirst() - .get(); - LaunchConfigurator config = new LaunchConfigurator() { + protected void captureLaunchDialog(String title) { + TraceRmiLaunchOffer offer = flatDbg.getLaunchOffers(program) + .stream() + .filter(o -> title.equals(o.getTitle())) + .findAny() + .orElseThrow(); + + runSwingLater(() -> offer.launchProgram(monitor, new LaunchConfigurator() { @Override - public Map configureLauncher(TargetLauncher launcher, - Map arguments, RelPrompt relPrompt) { - if (extraArgs.length() == 0) { - return arguments; - } - Map adjusted = new HashMap<>(arguments); - TargetCmdLineLauncher.PARAMETER_CMDLINE_ARGS.adjust(adjusted, - c -> c + " " + extraArgs); - return adjusted; + public PromptMode getPromptMode() { + return PromptMode.ALWAYS; } - }; - LaunchResult result = waitOn(offer.launchProgram(monitor, PromptMode.NEVER, config)); + })); + + captureDialog(DebuggerMethodInvocationDialog.class); + } + + @Test + public void testGettingStarted_LaunchGDBDialog() { + captureLaunchDialog("gdb"); + } + + protected LaunchResult launchProgramInGdb(String extraArgs) throws Throwable { + TraceRmiLaunchOffer offer = flatDbg.getLaunchOffers(program) + .stream() + .filter(o -> "gdb".equals(o.getTitle())) + .findAny() + .orElseThrow(); + LaunchResult result = flatDbg.launch(offer, Map.ofEntries( + Map.entry("env:OPT_START_CMD", "start"), + Map.entry("args", extraArgs)), + monitor); if (result.exception() != null) { throw result.exception(); } + return result; } - protected void launchProgramInGdb() throws Throwable { - launchProgramInGdb(""); + protected LaunchResult launchProgramInGdb() throws Throwable { + return launchProgramInGdb(""); } @Test @@ -278,15 +305,33 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator captureProvider(DebuggerBreakpointsProvider.class); } - protected void placeBreakpointsSRandRand() throws Throwable { + protected void waitBreakSpecExists(String expression) { + waitForCondition(() -> flatDbg.getAllBreakpoints() + .stream() + .flatMap(lb -> lb.getTraceBreakpoints().stream()) + . mapMulti((loc, down) -> { + if (loc instanceof TraceObjectBreakpointLocation oloc) { + down.accept(oloc.getSpecification()); + } + }) + .distinct() + .filter(l -> expression.equals(l.getExpression())) + .count() == 1); + } + + protected void placeBreakpointsSRand() throws Throwable { assertTrue(flatDbg.execute("break srand")); - assertTrue(flatDbg.execute("break rand")); - waitForCondition(() -> flatDbg.getAllBreakpoints().size() == 2); + waitBreakSpecExists("srand"); } protected void placeBreakpointsRand() throws Throwable { assertTrue(flatDbg.execute("break rand")); - waitForCondition(() -> flatDbg.getAllBreakpoints().size() == 1); + waitBreakSpecExists("rand"); + } + + protected void placeBreakpointsSRandRand() throws Throwable { + placeBreakpointsSRand(); + placeBreakpointsRand(); } @Test @@ -298,11 +343,16 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator captureProvider(DebuggerBreakpointsProvider.class); } - protected Address navigateToBreakpoint(String comment) { + protected Address navigateToBreakpoint(String expression) { TraceBreakpoint bp = flatDbg.getAllBreakpoints() .stream() .flatMap(l -> l.getTraceBreakpoints().stream()) - .filter(l -> comment.equals(l.getComment())) + . mapMulti((loc, down) -> { + if (loc instanceof TraceObjectBreakpointLocation oloc) { + down.accept(oloc); + } + }) + .filter(l -> expression.equals(l.getSpecification().getExpression())) .findAny() .get(); Address dynAddr = bp.getMinAddress(); @@ -367,6 +417,14 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator Address dynAddr = navigateToBreakpoint("srand"); TraceModule modLibC = getModuleContaining(dynAddr); Program progLibC = importModule(modLibC); + + // This module might be symlinked, so module name and file name may not match. + DebuggerStaticMappingService mappings = tool.getService(DebuggerStaticMappingService.class); + ModuleMapProposal proposal = mappings.proposeModuleMap(modLibC, progLibC); + try (Transaction tx = modLibC.getTrace().openTransaction("Map")) { + mappings.addModuleMappings(proposal.computeMap().values(), monitor, true); + } + waitForCondition(() -> flatDbg.translateDynamicToStatic(dynAddr) != null); disassembleSymbol(progLibC, "srand"); // Just to be sure. @@ -385,9 +443,16 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator Address dynAddr = navigateToBreakpoint("srand"); TraceModule modLibC = getModuleContaining(dynAddr); Program progLibC = importModule(modLibC); - waitForCondition(() -> flatDbg.translateDynamicToStatic(dynAddr) != null); + + // This module might be symlinked, so module name and file name may not match. + DebuggerStaticMappingService mappings = tool.getService(DebuggerStaticMappingService.class); + ModuleMapProposal proposal = mappings.proposeModuleMap(modLibC, progLibC); + try (Transaction tx = modLibC.getTrace().openTransaction("Map")) { + mappings.addModuleMappings(proposal.computeMap().values(), monitor, true); + } + + Address stAddr = waitForValue(() -> flatDbg.translateDynamicToStatic(dynAddr)); disassembleSymbol(progLibC, "srand"); - Address stAddr = flatDbg.translateDynamicToStatic(dynAddr); // Just to be sure. goTo(tool, progLibC, stAddr); flatDbg.resume(); @@ -488,6 +553,18 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator public void testNavigation_ThreadsInCallRand() throws Throwable { launchProgramInGdb(); placeBreakpointsRand(); + Address dynAddr = navigateToBreakpoint("rand"); + TraceModule modLibC = getModuleContaining(dynAddr); + Program progLibC = importModule(modLibC); + + // This module might be symlinked, so module name and file name may not match. + DebuggerStaticMappingService mappings = tool.getService(DebuggerStaticMappingService.class); + ModuleMapProposal proposal = mappings.proposeModuleMap(modLibC, progLibC); + try (Transaction tx = modLibC.getTrace().openTransaction("Map")) { + mappings.addModuleMappings(proposal.computeMap().values(), monitor, true); + } + + waitForCondition(() -> flatDbg.translateDynamicToStatic(dynAddr) != null); flatDbg.resume(); runSwing(() -> tool.setSize(1920, 1080)); @@ -500,7 +577,15 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator placeBreakpointsRand(); Address dynAddr = navigateToBreakpoint("rand"); TraceModule modLibC = getModuleContaining(dynAddr); - importModule(modLibC); + Program progLibC = importModule(modLibC); + + // This module might be symlinked, so module name and file name may not match. + DebuggerStaticMappingService mappings = tool.getService(DebuggerStaticMappingService.class); + ModuleMapProposal proposal = mappings.proposeModuleMap(modLibC, progLibC); + try (Transaction tx = modLibC.getTrace().openTransaction("Map")) { + mappings.addModuleMappings(proposal.computeMap().values(), monitor, true); + } + waitForCondition(() -> flatDbg.translateDynamicToStatic(dynAddr) != null); flatDbg.resume(); @@ -513,8 +598,11 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator launchProgramInGdb(); placeBreakpointsSRandRand(); flatDbg.resume(); // srand + Thread.sleep(500); flatDbg.resume(); // rand.1 + Thread.sleep(500); flatDbg.stepOut(); + Thread.sleep(500); runSwing(() -> tool.setSize(1920, 1080)); captureProvider(DebuggerTimeProvider.class); @@ -522,18 +610,29 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator @Test public void testNavigation_DialogCompareTimes() throws Throwable { - launchProgramInGdb(); // main + LaunchResult result = launchProgramInGdb(); // main placeBreakpointsRand(); Address pc = flatDbg.getProgramCounter(); long snapA = flatDbg.getCurrentSnap(); - TraceModule modTermmines = Unique.assertOne(flatDbg.getCurrentTrace() - .getModuleManager() - .getModulesAt(snapA, pc)); + try (Transaction tx = result.trace().openTransaction("Name snapshot")) { + result.trace() + .getTimeManager() + .getSnapshot(snapA, false) + .setDescription("Initial snapshot"); + } + TraceObjectModule modTermmines = + (TraceObjectModule) Unique.assertOne(flatDbg.getCurrentTrace() + .getModuleManager() + .getModulesAt(snapA, pc)); + + RemoteMethod refreshSections = result.connection().getMethods().get("refresh_sections"); + refreshSections.invoke(Map.of("node", modTermmines.getObject())); TraceSection secTermminesData = modTermmines.getSectionByName(".data"); flatDbg.readMemory(secTermminesData.getStart(), (int) secTermminesData.getRange().getLength(), monitor); flatDbg.resume(); // rand.1 + Thread.sleep(500); flatDbg.readMemory(secTermminesData.getStart(), (int) secTermminesData.getRange().getLength(), monitor); @@ -547,13 +646,23 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator @Test public void testNavigation_CompareTimes() throws Throwable { - launchProgramInGdb("-M 15"); // main + LaunchResult result = launchProgramInGdb("-M 15"); // main placeBreakpointsRand(); Address pc = flatDbg.getProgramCounter(); long snapA = flatDbg.getCurrentSnap(); - TraceModule modTermmines = Unique.assertOne(flatDbg.getCurrentTrace() - .getModuleManager() - .getModulesAt(snapA, pc)); + try (Transaction tx = result.trace().openTransaction("Name snapshot")) { + result.trace() + .getTimeManager() + .getSnapshot(snapA, false) + .setDescription("Initial snapshot"); + } + TraceObjectModule modTermmines = + (TraceObjectModule) Unique.assertOne(flatDbg.getCurrentTrace() + .getModuleManager() + .getModulesAt(snapA, pc)); + + RemoteMethod refreshSections = result.connection().getMethods().get("refresh_sections"); + refreshSections.invoke(Map.of("node", modTermmines.getObject())); TraceSection secTermminesData = modTermmines.getSectionByName(".data"); flatDbg.readMemory(secTermminesData.getStart(), (int) secTermminesData.getRange().getLength(), monitor); @@ -582,6 +691,10 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator }); waitForCondition(() -> actionNextDiff.isEnabled()); flatDbg.goToDynamic(secTermminesData.getStart()); + // Because auto-strack is a little broken right now + Thread.sleep(500); + flatDbg.goToDynamic(secTermminesData.getStart()); + performAction(actionNextDiff); runSwing(() -> tool.setSize(1920, 1080)); @@ -611,7 +724,15 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator showProvider(DebuggerStaticMappingProvider.class); Address dynAddr = navigateToBreakpoint("srand"); TraceModule modLibC = getModuleContaining(dynAddr); - importModule(modLibC); + Program progLibC = importModule(modLibC); + + // This module might be symlinked, so module name and file name may not match. + DebuggerStaticMappingService mappings = tool.getService(DebuggerStaticMappingService.class); + ModuleMapProposal proposal = mappings.proposeModuleMap(modLibC, progLibC); + try (Transaction tx = modLibC.getTrace().openTransaction("Map")) { + mappings.addModuleMappings(proposal.computeMap().values(), monitor, true); + } + waitForCondition(() -> flatDbg.translateDynamicToStatic(dynAddr) != null); runSwing(() -> tool.setSize(1920, 1080)); @@ -631,29 +752,30 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator DebuggerListingService listings = tool.getService(DebuggerListingService.class); runSwing(() -> listings .setCurrentSelection(new ProgramSelection(new AddressSet(modNcurses.getRange())))); + DebuggerListingProvider listingProvider = + waitForComponentProvider(DebuggerListingProvider.class); performAction("Copy Into New Program", - PluginUtils.getPluginNameFromClass(DebuggerCopyActionsPlugin.class), false); + PluginUtils.getPluginNameFromClass(DebuggerCopyActionsPlugin.class), listingProvider, + false); captureDialog(DebuggerCopyIntoProgramDialog.class); } @Test - public void testRemoteTargets_GdbOverSsh() throws Throwable { - performAction("Connect", PluginUtils.getPluginNameFromClass(DebuggerTargetsPlugin.class), - false); - DebuggerConnectDialog dialog = waitForDialogComponent(DebuggerConnectDialog.class); - runSwing(() -> dialog.setFactoryByBrief("gdb via SSH")); - - captureDialog(dialog); + public void testRemoteTargets_GdbPlusGdbserverViaSsh() throws Throwable { + captureLaunchDialog("gdb + gdbserver via ssh"); } @Test - public void testRemoteTargets_Gadp() throws Throwable { - performAction("Connect", PluginUtils.getPluginNameFromClass(DebuggerTargetsPlugin.class), - false); - DebuggerConnectDialog dialog = waitForDialogComponent(DebuggerConnectDialog.class); - runSwing(() -> dialog.setFactoryByBrief("Ghidra debug agent (GADP)")); + public void testRemoteTargets_GdbViaSsh() throws Throwable { + captureLaunchDialog("gdb via ssh"); + } - captureDialog(dialog); + @Test + public void testRemoteTargets_AcceptTraceRmi() throws Throwable { + performAction("Connect by Accept", + PluginUtils.getPluginNameFromClass(TraceRmiConnectionManagerPlugin.class), + false); + captureDialog(DebuggerMethodInvocationDialog.class); } protected Function findCommandLineParser() throws Throwable { diff --git a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html index 19ddc8a8cd..77af0e12a4 100644 --- a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html +++ b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html @@ -128,16 +128,14 @@ icon in my Tool Chest
  • There is no Debug / Launch icon in the global toolbar
  • +
  • There is no +gdb option in the launch drop-down
  • There -is no “Debug termmines in GDB locally IN-VM” option in the launch -drop-down
  • -
  • The -launch hangs for several seconds and then prompt for a -“recorder”
  • +href="#the-launch-hangs-for-several-seconds-and-then-i-get-prompted-with-a-wall-of-text" +id="toc-the-launch-hangs-for-several-seconds-and-then-i-get-prompted-with-a-wall-of-text">The +launch hangs for several seconds and then I get prompted with a wall of +text
  • The Dynamic Listing is empty
  • @@ -192,8 +190,16 @@ trust. For termmines, the risk is negligible. Run it:

    ./termmines

    You should see a 9x9 grid and a cursor you can move with the arrow -keys. Hit Ctrl-C to exit. Probe it for help. Most Linux -programs accept a -h argument for help:

    +keys.

    +
    + + +
    +

    Hit CTRL-C to exit. Probe +it for help. Most Linux programs accept a -h argument for +help:

    ./termmines -h

    You should now have all the information you need to understand how @@ -223,7 +229,18 @@ open

  • In the Debugger tool, click the dropdown ▾ for the debug debug button icon in the global tool -bar, and select “Debug termmines in GDB locally IN-VM.”

  • +bar, and select Configure and Launch termmines using… → +gdb.

    +
    + + +
    +
  • Change the Run Command to “start” (not +“starti”). NOTE: In practice, this is rarely +recommended, because most targets do not export their main +function.

  • +
  • Click the Launch button in the dialog.

  • Wait a bit then verify the Dynamic Listing window (top) is displaying disassembly code.

    @@ -236,20 +253,18 @@ termmines

    Launching on Windows

    -

    On Windows, we will use dbgeng to debug the specimen. This is the -engine that backs WinDbg. You may choose an alternative Minesweeper, -since terminal applications are less representative of Windows -executables. Follow the same process as for Linux, except import -termmines.exe and select “Debug termmines.exe in dbgeng -locally IN-VM.”

    +

    On Windows, we will use the Windows Debugger dbgeng.dll to debug the +specimen. This is the engine that backs WinDbg. You may choose an +alternative Minesweeper, since terminal applications are less +representative of Windows executables. Follow the same process as for +Linux, except import termmines.exe and select +Configure and Launch termmines.exe using… → dbgeng.

    Launching on macOS

    -

    Unfortunately, things are not so simple on macOS. See the -instructions for Building -LLDB-Java Bindings. Once built, follow the same process as for -Linux, except select “Debug termmines in LLDB locally IN-VM.”

    +

    On macOS, we will use LLDB to debug the specimen. This is the +debugger included with Xcode. Follow the same process as for Linux, +except choose lldb in the last menu.

    Troubleshooting

    @@ -280,73 +295,71 @@ tool. If it is still not there, then you may need to re-import the default Debugger tool as under the previous heading. If it is still not there, your installation may be corrupt.

    -
    -

    There is no “Debug termmines in GDB locally IN-VM” option in the -launch drop-down

    -

    You may need to install GDB and/or configure Ghidra with its -location. If you have a copy or custom build of GDB in a non-system -path, note its full path. If you intend to use the system’s copy of GDB, -then in a terminal:

    -
    which gdb
    -

    Note the path given. (If you get an error, then you need to install -GDB.) In a terminal, type the full path of GDB to ensure it executes -properly. Type q to quit GDB.

    -
      -
    1. From the Debugger Targets window, click the Connect connect button button.
    2. -
    3. In the Connect dialog, select “gdb” from the dropdown at the -top.
    4. -
    5. Enter the full path, e.g., /usr/bin/gdb, in the “GDB -launch command” field.
    6. -
    7. Click “Connect”
    8. -
    9. If you get an Interpreter window, then things have gone well.
    10. -
    11. Type echo test into it to verify it’s responsive, then -type q to disconnect.
    12. -
    13. Close the Debugger tool, then retry.
    14. -
    +

    There is no gdb option in the launch drop-down

    +

    You may have an older Debugger tool still configured for +Recorder-based targets. We are transitioning to TraceRmi-based targets. +Delete your Debugger tool and re-import the default one using the +instructions above. If it is still not there, it’s possible your +installation is corrupt. Search for a file called +local-gdb.sh in your installation. Unlike the previous +system, Trace RMI will not probe your system for dependencies nor hide +incompatible launchers. All installed launchers should be present in the +menus, even though some may not work on your configuration.

    -

    The launch hangs for several seconds and then prompt for a -“recorder”

    -

    You probably have a stale GDB connection, so when you launched you -now have multiple connections. For the prompt, select the option with -the highest score. Examine the Targets window to confirm you have -multiple GDB connections. If you know which is the stale connection, you -can right-click it and choose Disconnect. Otherwise, -use Disconnect All from the drop-down menu and -re-launch.

    +

    The launch hangs for several seconds and then I get prompted with a +wall of text

    +

    Read the wall of text. The first line should tell you the exception +that it encountered. Often this is a timeout. Press the +Keep button and then find the Terminal, usually in the +bottom right. If you do not see it there, check the Window → +Terminals menu. Once you have found the Terminal, check its +output starting at the top for diagnostic messages. If you have +something like bash: gdb: command not found, it is because +you are missing gdb, or you need to tell Ghidra where to +find it.

    +

    If it is just missing, then install it and try again. If you need to +tell Ghidra where it is, then in the launcher drop-down, select +Configure and Launch termmines using… → gdb. DO NOT +select Re-launch termmines using gdb, since this will +not allow you to correct the configuration.

    The Dynamic Listing is empty

    Check for an actual connection. You should see an entry in the -Debugger Targets window, a populated Object window, and there should be -an Interpreter window. If not, then your GDB connector may not be -configured properly. Try the steps under the previous heading.

    -

    If you have an Interpreter window, there are several +Connection Manager window, a populated +Model window, and there should be a +Terminal window. If not, then your GDB connector may +not be configured properly. Try the steps under the previous +heading.

    +

    If you have a Terminal window, there are several possibilities:

    Ghidra or GDB failed to launch the target:

    -

    Check that the original termmines exists and is -executable. It must be at the path from where it was originally -imported. If you imported from a share, consider copying it locally, -setting its permissions, then re-importing.

    +

    If this is the case, you should see an error message in the Terminal, +e.g.: termmines: no such file or directory. Check that the +original termmines exists and is executable. You may also +need to adjust the Image option when configuring the +launch.

    The target was launched, but immediately terminated:

    -

    Check that the specimen has a main symbol. NOTE: It is +

    If this is the case, you should see a message in the Terminal, e.g.: +[Inferior 1 (process 1234) exited normally]. Check that the +specimen has a main symbol. NOTE: It is not sufficient to place a main label in Ghidra. The original file must have a main symbol.

    -

    Alternatively, in the menus try Debugger → Debug termmines → -in GDB locally IN-VM, and select “Use starti.” This will break -at the system entry point. If you have labeled main in -Ghidra, then you can place a breakpoint there and continue — these -features are covered later in the course.

    +

    Alternatively, in the menus try Debugger → Configure and +Launch termmines using → gdb, and select “starti” for +Run Command. This will break at the system entry point. +If you have labeled main in Ghidra, then you can place a +breakpoint there and continue — these features are covered later in the +course.

    Alternatively, try debugging the target in GDB from a separate terminal completely outside of Ghidra to see if things work as expected.

    @@ -356,37 +369,37 @@ class="level4">

    The target was launched, but has not stopped, yet

    Try pressing the Interrupt interrupt button button. If that doesn’t work or is -unsatisfactory, try the remedies under the previous heading — for an -immediately terminating target.

    +unsatisfactory, try the remedies under the previous heading.

    You hit an uncommon bug where the memory map is not applied properly

    -

    This is the case if the Dynamic Listing is completely blank but the -Regions window is replete. The Dynamic Listing just needs to be kicked a -little. The easiest way is to step once, using the step into Step Into button in the -main toolbar. If this is not desirable, then you can toggle -Force Full View back and forth. In the Regions window, -use the drop-down menu to toggle it on, then toggle it off. The Dynamic -Listing should now be populated. To go to the program counter, -double-click the “pc = …” label in the top right.

    +

    This is the case if the Dynamic Listing is +completely blank but the Regions window is replete. The +Dynamic Listing just needs to be kicked a little. The +easiest way is to step once, using the Step Into button in the main +toolbar. If this is not desirable, then you can toggle Force +Full View back and forth. In the Regions +window, use the drop-down menu to toggle it on, then toggle it off. The +Dynamic Listing should now be populated. To go to the +program counter, double-click the “pc = …” label in the top right.

    Something else has gone wrong

    Try typing info inferiors and similar GDB diagnostic -commands into the Interpreter.

    +commands into the Terminal.

    The listings are in sync, but the Dynamic Listing is grey 00s

    -

    Check the Auto-Read drop-down near the top right of the Dynamic -Listing. It should be set to Read Visible Memory, RO -Once.

    +

    Check the Auto-Read drop-down near the top right of +the Dynamic Listing. It should be set to Read +Visible Memory, RO Once.

    @@ -395,63 +408,66 @@ Once.

    termmines and/or start a new Ghidra Project. Starting from the beginning, import termmines and launch it in the Ghidra Debugger with GDB. When your tool looks like the screenshot with a -populated Dynamic Listing, you have completed the exercise. Disconnect -before proceeding to the next exercise.

    +populated Dynamic Listing, you have completed the +exercise. Disconnect before proceeding to the next exercise.

    Customized Launching

    For this specimen, you may occasionally need to provide custom command-line parameters. By default, Ghidra attempts to launch the -target without any parameters. In the menus, use Debugger → -Debug termmmines → in GDB locally IN-VM to launch with -customizations. Ghidra will remember these customizations the next time -you launch using the drop-down button from the toolbar. The first dialog -allows you to customize the connection to the back-end debugger. Unless -you have a special copy of GDB, you should probably just click Connect. -The second dialog allows you to customize how the back-end debugger -launches the target. This is where you tweak the command line. You can -also change the actual image, in case it has moved or you want to -experiment with a patched version.

    +target without any parameters. In the Debugger menu, or +the Launch button’s drop-down menu, use +Configure and Launch termmmines → gdb to adjust your +configuration. This is where you can specify the image path and +command-line parameters of your target. Ghidra will remember this +configuration the next time you launch using the drop-down button from +the toolbar. Launchers with memorized configurations are presented as +Re-launch termmines using… options. Using one of those +entries will re-launch with the saved configuration rather than +prompting.

    Exercise: Launch with Command-line Help

    Launch the specimen so that it prints its usage. When successful, you -will see the usage info in the Debugger’s Interpreter window. -NOTE: The process will terminate after printing its -usage, and as a result, the rest of the UI will be mostly empty.

    +will see the usage info in the Debugger’s Terminal +window. NOTE: The process will terminate after printing +its usage, and as a result, the rest of the UI will be mostly empty.

    Attaching

    -

    Attaching is slightly more advanced, but because the target will need -to read from stdin, and Ghidra does not properly attach the Interpreter -to stdin, we will need to launch the target in a terminal and attach to -it instead. Note this technique is only possible because the target -waits for input. Depending on the task for future exercises, you may -still need to launch from the Debugger instead of attaching.

    +

    Attaching is slightly more advanced, but can be useful if the target +is part of a larger system, and it needs to be running in situ. +For this section, we will just run termmines in a separate +terminal and then attach to it from Ghidra. This used to be required, +because the older Recorder-based system did not provide target I/O, but +this limitation is overcome by the new Terminal window +when using Trace RMI. Note this technique is only possible because the +target waits for input.

      -
    1. Run termmines in a proper terminal with the desired -command-line parameters.
    2. -
    3. In the Ghidra Debugger, find the Targets window, and click the connect Connect button.
    4. -
    5. Select “gdb” from the drop-down box.
    6. -
    7. This dialog should look familiar from the Customized Launching -section. Just click the Connect button.
    8. -
    9. In the Objects window (below the Targets window), expand the node -labeled “Available.”
    10. +
    11. Run termmines in a terminal outside of Ghidra with the +desired command-line parameters.
    12. +
    13. In the Ghidra Debugger, use the Launch button +drop-down and select Configured and Launch termmines using… → +raw gdb. The “raw” connector will give us a GDB session without +a target.
    14. +
    15. Ghidra needs to know the location of gdb and the architecture of the +intended target. The defaults are correct for 64-bit x86 targets using +the system’s copy of GDB. Probably, you can just click +Launch.
    16. +
    17. In the Model window (to the left), expand the +Available node.
    18. In the filter box, type termmines.
    19. -
    20. Right-click on the termmines process and select Attach. If this -fails, select Available again, and click the -refresh Refresh -button.
    21. +
    22. Note the PID, e.g. 1234, then in the Terminal type, +e.g., attach 1234.

    Exercise: Attach

    Try attaching on your own, if you have not already. Check your work -by typing bt into the Interpreter. If you are in -read you have completed this exercise. Disconnect before -proceeding to the next module: A Tour of the -UI

    +by typing bt into the Terminal. If you are +in read you have completed this exercise. Quit GDB from the +Terminal before proceeding to the next module: A Tour of the UI

    Troubleshooting

    @@ -464,14 +480,15 @@ be traced by any other process and then executes a shell command. Using specimen in the permissive process, and thus you can attach to it as if ptrace_scope=0, but without reducing the security of the rest of the system. For example:

    -
    ./anyptracer 'exec ./termmines'
    +
    ./anyptracer 'exec ./termmines'

    Alternatively, if you have root access, you can rectify the issue using the relevant documentation available online. -Beware! You should not modify this setting on your -daily driver, as this substantially reduces the security of your system. -Any compromised process would be allowed to attach to and steal data, -e.g., credentials, from any other process owned by the same user.

    +Beware! You should not set ptrace_scope=0 +globally, except on a system set aside for debugging, as this +substantially reduces the security of that system. Any compromised +process would be allowed to attach to and steal data, e.g., credentials, +from any other process owned by the same user.

    diff --git a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.md b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.md index cf9bc1bbfa..6085fd1d9c 100644 --- a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.md +++ b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.md @@ -30,7 +30,10 @@ Run it: ``` You should see a 9x9 grid and a cursor you can move with the arrow keys. -Hit **Ctrl-C** to exit. + +![Termmines running in a Terminal](images/GettingStarted_Termmines.png) + +Hit **`CTRL`-`C`** to exit. Probe it for help. Most Linux programs accept a `-h` argument for help: @@ -55,23 +58,29 @@ There are many ways to do this, but for the sake of simplicity, import and launc ![Debugger tool with termmines open](images/GettingStarted_ToolWSpecimen.png) -1. In the Debugger tool, click the dropdown ▾ for the debug ![debug button](images/debugger.png) icon in the global tool bar, and select "Debug termmines in GDB locally IN-VM." +1. In the Debugger tool, click the dropdown ▾ for the debug ![debug button](images/debugger.png) icon in the global tool bar, and select **Configure and Launch termmines using... → gdb**. + + ![Launch GDB Dialog](images/GettingStarted_LaunchGDBDialog.png) + +1. Change the **Run Command** to "start" (not "starti"). + **NOTE**: In practice, this is rarely recommended, because most targets do not export their `main` function. +1. Click the **Launch** button in the dialog. 1. Wait a bit then verify the Dynamic Listing window (top) is displaying disassembly code. ![Debugger tool after launching termmines](images/GettingStarted_DisassemblyAfterLaunch.png) ## Launching on Windows -On Windows, we will use dbgeng to debug the specimen. +On Windows, we will use the Windows Debugger dbgeng.dll to debug the specimen. This is the engine that backs WinDbg. You may choose an alternative Minesweeper, since terminal applications are less representative of Windows executables. -Follow the same process as for Linux, except import `termmines.exe` and select "Debug termmines.exe in dbgeng locally IN-VM." +Follow the same process as for Linux, except import `termmines.exe` and select **Configure and Launch termmines.exe using... → dbgeng**. ## Launching on macOS -Unfortunately, things are not so simple on macOS. -See the instructions for [Building LLDB-Java Bindings](../../../Ghidra/Debug/Debugger-swig-lldb/InstructionsForBuildingLLDBInterface.txt). -Once built, follow the same process as for Linux, except select "Debug termmines in LLDB locally IN-VM." +On macOS, we will use LLDB to debug the specimen. +This is the debugger included with Xcode. +Follow the same process as for Linux, except choose **lldb** in the last menu. ## Troubleshooting @@ -97,59 +106,53 @@ Double-check that you are in the Debugger tool, not the CodeBrowser tool. If it is still not there, then you may need to re-import the default Debugger tool as under the previous heading. If it is still not there, your installation may be corrupt. -### There is no "Debug termmines in GDB locally IN-VM" option in the launch drop-down +### There is no **gdb** option in the launch drop-down -You may need to install GDB and/or configure Ghidra with its location. -If you have a copy or custom build of GDB in a non-system path, note its full path. -If you intend to use the system's copy of GDB, then in a terminal: +You may have an older Debugger tool still configured for Recorder-based targets. +We are transitioning to TraceRmi-based targets. +Delete your Debugger tool and re-import the default one using the instructions above. +If it is still not there, it's possible your installation is corrupt. +Search for a file called `local-gdb.sh` in your installation. +Unlike the previous system, Trace RMI will not probe your system for dependencies nor hide incompatible launchers. +All installed launchers should be present in the menus, even though some may not work on your configuration. -```bash -which gdb -``` +### The launch hangs for several seconds and then I get prompted with a wall of text -Note the path given. -(If you get an error, then you need to install GDB.) -In a terminal, type the full path of GDB to ensure it executes properly. -Type `q` to quit GDB. +Read the wall of text. +The first line should tell you the exception that it encountered. +Often this is a timeout. +Press the **Keep** button and then find the Terminal, usually in the bottom right. +If you do not see it there, check the **Window → Terminals** menu. +Once you have found the Terminal, check its output *starting at the top* for diagnostic messages. +If you have something like `bash: gdb: command not found`, it is because you are missing `gdb`, or you need to tell Ghidra where to find it. -1. From the Debugger Targets window, click the Connect ![connect button](images/connect.png) button. -1. In the Connect dialog, select "gdb" from the dropdown at the top. -1. Enter the full path, e.g., `/usr/bin/gdb`, in the "GDB launch command" field. -1. Click "Connect" -1. If you get an Interpreter window, then things have gone well. -1. Type `echo test` into it to verify it's responsive, then type `q` to disconnect. -1. Close the Debugger tool, then retry. - -### The launch hangs for several seconds and then prompt for a "recorder" - -You probably have a stale GDB connection, so when you launched you now have multiple connections. -For the prompt, select the option with the highest score. -Examine the Targets window to confirm you have multiple GDB connections. -If you know which is the stale connection, you can right-click it and choose **Disconnect**. -Otherwise, use **Disconnect All** from the drop-down menu and re-launch. +If it is just missing, then install it and try again. +If you need to tell Ghidra where it is, then in the launcher drop-down, select **Configure and Launch termmines using... → gdb**. +DO NOT select **Re-launch termmines using gdb**, since this will not allow you to correct the configuration. ### The Dynamic Listing is empty Check for an actual connection. -You should see an entry in the Debugger Targets window, a populated Object window, and there should be an Interpreter window. +You should see an entry in the **Connection Manager** window, a populated **Model** window, and there should be a **Terminal** window. If not, then your GDB connector may not be configured properly. Try the steps under the previous heading. -If you have an Interpreter window, there are several possibilities: +If you have a **Terminal** window, there are several possibilities: #### Ghidra or GDB failed to launch the target: +If this is the case, you should see an error message in the Terminal, e.g.: `termmines: no such file or directory`. Check that the original `termmines` exists and is executable. -It must be at the path from where it was originally imported. -If you imported from a share, consider copying it locally, setting its permissions, then re-importing. +You may also need to adjust the **Image** option when configuring the launch. #### The target was launched, but immediately terminated: +If this is the case, you should see a message in the Terminal, e.g.: `[Inferior 1 (process 1234) exited normally]`. Check that the specimen has a `main` symbol. -NOTE: It is not sufficient to place a `main` label in Ghidra. +**NOTE**: It is not sufficient to place a `main` label in Ghidra. The original file must have a `main` symbol. -Alternatively, in the menus try **Debugger → Debug termmines → in GDB locally IN-VM**, and select "Use starti." +Alternatively, in the menus try **Debugger → Configure and Launch termmines using → gdb**, and select "starti" for **Run Command**. This will break at the system entry point. If you have labeled `main` in Ghidra, then you can place a breakpoint there and continue — these features are covered later in the course. @@ -158,75 +161,74 @@ Alternatively, try debugging the target in GDB from a separate terminal complete #### The target was launched, but has not stopped, yet Try pressing the Interrupt ![interrupt button](images/interrupt.png) button. -If that doesn't work or is unsatisfactory, try the remedies under the previous heading — for an immediately terminating target. +If that doesn't work or is unsatisfactory, try the remedies under the previous heading. #### You hit an uncommon bug where the memory map is not applied properly -This is the case if the Dynamic Listing is completely blank but the Regions window is replete. -The Dynamic Listing just needs to be kicked a little. -The easiest way is to step once, using the ![step into](images/stepinto.png) Step Into button in the main toolbar. +This is the case if the **Dynamic Listing** is completely blank but the **Regions** window is replete. +The **Dynamic Listing** just needs to be kicked a little. +The easiest way is to step once, using the ![step into](images/stepinto.png) **Step Into** button in the main toolbar. If this is not desirable, then you can toggle **Force Full View** back and forth. -In the Regions window, use the drop-down menu to toggle it on, then toggle it off. -The Dynamic Listing should now be populated. +In the **Regions** window, use the drop-down menu to toggle it on, then toggle it off. +The **Dynamic Listing** should now be populated. To go to the program counter, double-click the "pc = ..." label in the top right. #### Something else has gone wrong -Try typing `info inferiors` and similar GDB diagnostic commands into the Interpreter. +Try typing `info inferiors` and similar GDB diagnostic commands into the **Terminal**. ### The listings are in sync, but the Dynamic Listing is grey 00s -Check the Auto-Read drop-down near the top right of the Dynamic Listing. +Check the **Auto-Read** drop-down near the top right of the **Dynamic Listing**. It should be set to **Read Visible Memory, RO Once**. ## Exercise: Launch `termmines` If you were following along with an instructor, delete your import of `termmines` and/or start a new Ghidra Project. Starting from the beginning, import `termmines` and launch it in the Ghidra Debugger with GDB. -When your tool looks like the screenshot with a populated Dynamic Listing, you have completed the exercise. +When your tool looks like the screenshot with a populated **Dynamic Listing**, you have completed the exercise. Disconnect before proceeding to the next exercise. ## Customized Launching For this specimen, you may occasionally need to provide custom command-line parameters. By default, Ghidra attempts to launch the target without any parameters. -In the menus, use **Debugger → Debug termmmines → in GDB locally IN-VM** to launch with customizations. -Ghidra will remember these customizations the next time you launch using the drop-down button from the toolbar. -The first dialog allows you to customize the connection to the back-end debugger. -Unless you have a special copy of GDB, you should probably just click Connect. -The second dialog allows you to customize how the back-end debugger launches the target. -This is where you tweak the command line. -You can also change the actual image, in case it has moved or you want to experiment with a patched version. +In the **Debugger** menu, or the **Launch** button's drop-down menu, use **Configure and Launch termmmines → gdb** to adjust your configuration. +This is where you can specify the image path and command-line parameters of your target. +Ghidra will remember this configuration the next time you launch using the drop-down button from the toolbar. +Launchers with memorized configurations are presented as **Re-launch termmines using...** options. +Using one of those entries will re-launch with the saved configuration rather than prompting. ## Exercise: Launch with Command-line Help Launch the specimen so that it prints its usage. -When successful, you will see the usage info in the Debugger's Interpreter window. +When successful, you will see the usage info in the Debugger's **Terminal** window. **NOTE**: The process will terminate after printing its usage, and as a result, the rest of the UI will be mostly empty. ## Attaching -Attaching is slightly more advanced, but because the target will need to read from stdin, and Ghidra does not properly attach the Interpreter to stdin, we will need to launch the target in a terminal and attach to it instead. +Attaching is slightly more advanced, but can be useful if the target is part of a larger system, and it needs to be running *in situ*. +For this section, we will just run `termmines` in a separate terminal and then attach to it from Ghidra. +This used to be required, because the older Recorder-based system did not provide target I/O, but this limitation is overcome by the new **Terminal** window +when using Trace RMI. Note this technique is only possible because the target waits for input. -Depending on the task for future exercises, you may still need to launch from the Debugger instead of attaching. -1. Run `termmines` in a proper terminal with the desired command-line parameters. -1. In the Ghidra Debugger, find the Targets window, and click the ![connect](images/connect.png) Connect button. -1. Select "gdb" from the drop-down box. -1. This dialog should look familiar from the Customized Launching section. - Just click the Connect button. -1. In the Objects window (below the Targets window), expand the node labeled "Available." +1. Run `termmines` in a terminal outside of Ghidra with the desired command-line parameters. +1. In the Ghidra Debugger, use the **Launch** button drop-down and select **Configured and Launch termmines using... → raw gdb**. + The "raw" connector will give us a GDB session without a target. +1. Ghidra needs to know the location of gdb and the architecture of the intended target. + The defaults are correct for 64-bit x86 targets using the system's copy of GDB. + Probably, you can just click **Launch**. +1. In the **Model** window (to the left), expand the *Available* node. 1. In the filter box, type `termmines`. -1. Right-click on the termmines process and select Attach. - If this fails, select Available again, and click the refresh Refresh button. - +1. Note the PID, e.g. 1234, then in the **Terminal** type, e.g., `attach 1234`. ## Exercise: Attach Try attaching on your own, if you have not already. -Check your work by typing `bt` into the Interpreter. +Check your work by typing `bt` into the **Terminal**. If you are in `read` you have completed this exercise. -Disconnect before proceeding to the next module: [A Tour of the UI](A2-UITour.md) +Quit GDB from the **Terminal** before proceeding to the next module: [A Tour of the UI](A2-UITour.md) ## Troubleshooting @@ -241,5 +243,5 @@ For example: ``` Alternatively, if you have root access, you can rectify the issue using the relevant documentation available online. -**Beware!** You should not modify this setting on your daily driver, as this substantially reduces the security of your system. +**Beware!** You should not set `ptrace_scope=0` globally, except on a system set aside for debugging, as this substantially reduces the security of that system. Any compromised process would be allowed to attach to and steal data, e.g., credentials, from any other process owned by the same user. diff --git a/GhidraDocs/GhidraClass/Debugger/A2-UITour.html b/GhidraDocs/GhidraClass/Debugger/A2-UITour.html index fe67e44143..797c932b50 100644 --- a/GhidraDocs/GhidraClass/Debugger/A2-UITour.html +++ b/GhidraDocs/GhidraClass/Debugger/A2-UITour.html @@ -18,6 +18,70 @@ vertical-align: middle; } .display.math{display: block; text-align: center; margin: 0.5rem auto;} + /* CSS for syntax highlighting */ + pre > code.sourceCode { white-space: pre; position: relative; } + pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } + pre > code.sourceCode > span:empty { height: 1.2em; } + .sourceCode { overflow: visible; } + code.sourceCode > span { color: inherit; text-decoration: inherit; } + div.sourceCode { margin: 1em 0; } + pre.sourceCode { margin: 0; } + @media screen { + div.sourceCode { overflow: auto; } + } + @media print { + pre > code.sourceCode { white-space: pre-wrap; } + pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; } + } + pre.numberSource code + { counter-reset: source-line 0; } + pre.numberSource code > span + { position: relative; left: -4em; counter-increment: source-line; } + pre.numberSource code > span > a:first-child::before + { content: counter(source-line); + position: relative; left: -1em; text-align: right; vertical-align: baseline; + border: none; display: inline-block; + -webkit-touch-callout: none; -webkit-user-select: none; + -khtml-user-select: none; -moz-user-select: none; + -ms-user-select: none; user-select: none; + padding: 0 4px; width: 4em; + color: #aaaaaa; + } + pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; } + div.sourceCode + { } + @media screen { + pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; } + } + code span.al { color: #ff0000; font-weight: bold; } /* Alert */ + code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */ + code span.at { color: #7d9029; } /* Attribute */ + code span.bn { color: #40a070; } /* BaseN */ + code span.bu { color: #008000; } /* BuiltIn */ + code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */ + code span.ch { color: #4070a0; } /* Char */ + code span.cn { color: #880000; } /* Constant */ + code span.co { color: #60a0b0; font-style: italic; } /* Comment */ + code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */ + code span.do { color: #ba2121; font-style: italic; } /* Documentation */ + code span.dt { color: #902000; } /* DataType */ + code span.dv { color: #40a070; } /* DecVal */ + code span.er { color: #ff0000; font-weight: bold; } /* Error */ + code span.ex { } /* Extension */ + code span.fl { color: #40a070; } /* Float */ + code span.fu { color: #06287e; } /* Function */ + code span.im { color: #008000; font-weight: bold; } /* Import */ + code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */ + code span.kw { color: #007020; font-weight: bold; } /* Keyword */ + code span.op { color: #666666; } /* Operator */ + code span.ot { color: #007020; } /* Other */ + code span.pp { color: #bc7a00; } /* Preprocessor */ + code span.sc { color: #4070a0; } /* SpecialChar */ + code span.ss { color: #bb6688; } /* SpecialString */ + code span.st { color: #4070a0; } /* String */ + code span.va { color: #19177c; } /* Variable */ + code span.vs { color: #4070a0; } /* VerbatimString */ + code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */