GP-1231: Added 'Map by Regions' action to Debugger.

This commit is contained in:
Dan 2021-11-23 10:50:16 -05:00
parent 513c9beb9d
commit 108ea044cc
46 changed files with 3091 additions and 1569 deletions

View file

@ -52,9 +52,9 @@ public class GdbModelTargetMemoryRegion
protected static String computeDisplay(GdbMemoryMapping mapping) {
// NOTE: This deviates from GDB's table display, as it'd be confusing in isolation
if (mapping.getObjfile() == null || mapping.getObjfile().length() == 0) {
return String.format("?? [0x%x-0x%x]", mapping.getStart(), mapping.getEnd());
return String.format("?? (0x%x-0x%x)", mapping.getStart(), mapping.getEnd());
}
return String.format("%s [0x%x-0x%x] (0x%x)", mapping.getObjfile(), mapping.getStart(),
return String.format("%s (0x%x-0x%x,0x%x)", mapping.getObjfile(), mapping.getStart(),
mapping.getEnd(), mapping.getOffset());
}

View file

@ -95,6 +95,7 @@ src/main/help/help/topics/DebuggerObjectsPlugin/images/stop.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerPcodeStepperPlugin/images/DebuggerPcodeStepperPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionMapProposalDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionsPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerRegistersPlugin/images/DebuggerAvailableRegistersDialog.png||GHIDRA||||END|

View file

@ -22,7 +22,6 @@ import ghidra.program.model.address.AddressSpace;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Trace;
import ghidra.util.database.UndoableTransaction;
public class AddMapping extends GhidraScript {
@Override
@ -35,8 +34,6 @@ public class AddMapping extends GhidraScript {
AddressSpace dynRam = currentTrace.getBaseAddressFactory().getDefaultAddressSpace();
AddressSpace statRam = currentProgram.getAddressFactory().getDefaultAddressSpace();
try (UndoableTransaction tid =
UndoableTransaction.start(currentTrace, "Add Mapping", true)) {
mappings.addMapping(
new DefaultTraceLocation(currentTrace, null, Range.atLeast(0L),
dynRam.getAddress(0x00400000)),
@ -44,4 +41,3 @@ public class AddMapping extends GhidraScript {
0x10000, false);
}
}
}

View file

@ -59,5 +59,15 @@
performed manually using the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#map_sections">Map Sections</A>
action in the Modules and Sections window.</P>
<H2><A name="map_regions"></A>Map Regions</H2>
<P>This bot automatically maps trace regions to memory blocks of programs opened in the same
tool. Its operation is analogous to that of the Map Modules Bot, except that it creates the
mapped ranges by region. It is not commonly used, as it's less efficient than the Map Modules
Bot, but it is required whenever a target fails to present modules. This action can be
performed manually using the <A href=
"help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html#map_regions">Map Regions</A>
action in the Regions window.</P>
</BODY>
</HTML>

View file

@ -55,6 +55,41 @@
<P>Other than modifications enabled by the table, the Regions window provides the following
actions:</P>
<H3><A name="map_regions"></A>Map Regions</H3>
<P>This action is analogous to the Map Modules and Map Sections actions from the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html">Modules window</A>. It searches
the tool's open programs for blocks matching the selected regions and proposes new mappings.
Users who prefer this should also consider using the <A href=
"help/topics/DebuggerBots/DebuggerBots.html#map_regions">Map Regions</A> debugger bot. For the
best result, the selection regions should comprise a complete module. In particular, it should
include the region containing the module's image base, as the offset from this base is used in
scoring the best-matched blocks. Additionally, the region names must include the module's file
name, otherwise the matcher has no means to identify a corresponding program.</P>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" src=
"images/DebuggerRegionMapProposalDialog.png"></TD>
</TR>
</TBODY>
</TABLE>
<H3><A name="map_regions_to"></A>Map Regions to Current Program</H3>
<P>This action is available from the pop-up menu, when there is a selection of regions and
there is an open program. It behaves like Map Regions, except that it will attempt to map the
selected regions to blocks in the current program only. This is useful if the regions are not
named according to the module filename. The selected regions should still comprise a complete
module for best results.</P>
<H3><A name="map_region_to"></A>Map Region to Current Block</H3>
<P>This action is available from a single region's pop-up menu, when there is an open program.
It behaves like Map Regions, except that it will propose the selected region be mapped to the
block containing the cursor in the static listing.</P>
<H3><A name="select_addresses"></A>Select Addresses</H3>
<P>This action is available when at least one region is selected. It selects all addresses in

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.modules;
package ghidra.app.plugin.core.debug.gui;
import java.awt.BorderLayout;
import java.util.Collection;
@ -59,6 +59,16 @@ public abstract class AbstractDebuggerMapProposalDialog<R> extends DialogCompone
createActions();
}
/* testing */
public GTable getTable() {
return table;
}
/* testing */
public EnumeratedColumnTableModel<R> getTableModel() {
return tableModel;
}
protected void removeEntry(R entry) {
tableModel.delete(entry);
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.modules;
package ghidra.app.plugin.core.debug.gui;
import java.awt.BorderLayout;
import java.util.*;
@ -34,16 +34,17 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceSection;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerBlockChooserDialog extends DialogComponentProvider {
static class MemoryBlockRow {
public static class MemoryBlockRow {
private final Program program;
private final MemoryBlock block;
private double score;
public MemoryBlockRow(Program program, MemoryBlock block) {
protected MemoryBlockRow(Program program, MemoryBlock block) {
this.program = program;
this.block = block;
}
@ -87,6 +88,13 @@ public class DebuggerBlockChooserDialog extends DialogComponentProvider {
return score = service.proposeSectionMap(section, program, block).computeScore();
}
public double score(TraceMemoryRegion region, DebuggerStaticMappingService service) {
if (region == null) {
return score = 0;
}
return score = service.proposeRegionMap(region, program, block).computeScore();
}
public ProgramLocation getProgramLocation() {
return new ProgramLocation(program, block.getStart());
}
@ -143,11 +151,21 @@ public class DebuggerBlockChooserDialog extends DialogComponentProvider {
private Entry<Program, MemoryBlock> chosen;
protected DebuggerBlockChooserDialog() {
public DebuggerBlockChooserDialog() {
super("Memory Blocks", true, true, true, false);
populateComponents();
}
/* testing */
public EnumeratedColumnTableModel<MemoryBlockRow> getTableModel() {
return tableModel;
}
/* testing */
public GhidraTableFilterPanel<MemoryBlockRow> getTableFilterPanel() {
return filterPanel;
}
protected void populateComponents() {
JPanel panel = new JPanel(new BorderLayout());
@ -183,16 +201,28 @@ public class DebuggerBlockChooserDialog extends DialogComponentProvider {
public Map.Entry<Program, MemoryBlock> chooseBlock(PluginTool tool, TraceSection section,
Collection<Program> programs) {
DebuggerStaticMappingService service = tool.getService(DebuggerStaticMappingService.class);
return chooseBlock(tool, programs, rec -> rec.score(section, service));
}
public Map.Entry<Program, MemoryBlock> chooseBlock(PluginTool tool, TraceMemoryRegion region,
Collection<Program> programs) {
DebuggerStaticMappingService service = tool.getService(DebuggerStaticMappingService.class);
return chooseBlock(tool, programs, rec -> rec.score(region, service));
}
protected Map.Entry<Program, MemoryBlock> chooseBlock(PluginTool tool,
Collection<Program> programs, Function<MemoryBlockRow, Double> scorer) {
setBlocksFromPrograms(programs);
computeScores(section, tool.getService(DebuggerStaticMappingService.class));
computeScores(scorer);
selectHighestScoringBlock();
tool.showDialog(this);
return getChosen();
}
protected void computeScores(TraceSection section, DebuggerStaticMappingService service) {
protected void computeScores(Function<MemoryBlockRow, Double> scorer) {
for (MemoryBlockRow rec : tableModel.getModelData()) {
rec.score(section, service);
scorer.apply(rec);
}
}

View file

@ -149,6 +149,7 @@ public interface DebuggerResources {
ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png");
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png");
ImageIcon ICON_MAP_SECTIONS = ICON_MAP_MODULES; // TODO
ImageIcon ICON_MAP_REGIONS = ICON_MAP_MODULES; // TODO
ImageIcon ICON_BLOCK = ICON_MAP_SECTIONS; // TODO
// TODO: Draw an icon
ImageIcon ICON_SELECT_ADDRESSES = ResourceManager.loadImage("images/NextSelectionBlock16.gif");
@ -1344,7 +1345,7 @@ public interface DebuggerResources {
interface MapSectionToAction {
String NAME_PREFIX = "Map Section to ";
String DESCRIPTION = "Map the selected module to the current program";
String DESCRIPTION = "Map the selected section to the current program";
Icon ICON = ICON_MAP_SECTIONS; // TODO: Probably no icon
String GROUP = GROUP_MAPPING;
String HELP_ANCHOR = "map_section_to";
@ -1374,6 +1375,57 @@ public interface DebuggerResources {
}
}
interface MapRegionsAction {
String NAME = "Map Regions";
String DESCRIPTION = "Map selected regions to program memory blocks";
Icon ICON = ICON_MAP_REGIONS; // TODO: Probably no icon
String GROUP = GROUP_MAPPING;
String HELP_ANCHOR = "map_regions";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName).description(DESCRIPTION)
//.toolBarIcon(ICON)
//.toolBarGroup(GROUP)
//.popupMenuIcon(ICON)
.popupMenuPath(NAME)
.popupMenuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface MapRegionToAction {
String NAME_PREFIX = "Map Region to ";
String DESCRIPTION = "Map the selected region to the current program";
Icon ICON = ICON_MAP_SECTIONS; // TODO: Probably no icon
String GROUP = GROUP_MAPPING;
String HELP_ANCHOR = "map_region_to";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME_PREFIX, ownerName).description(DESCRIPTION)
.popupMenuPath(NAME_PREFIX + "...")
.popupMenuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface MapRegionsToAction {
String NAME_PREFIX = "Map Regions to ";
String DESCRIPTION = "Map the selected (module) regions to the current program";
Icon ICON = ICON_MAP_SECTIONS;
String GROUP = GROUP_MAPPING;
String HELP_ANCHOR = "map_regions_to";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME_PREFIX, ownerName).description(DESCRIPTION)
.popupMenuPath(NAME_PREFIX + "...")
.popupMenuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
/*interface SelectAddressesAction { // TODO: Finish this conversion
String NAME = "Select Addresses";
Icon ICON = ICON_SELECT_ADDRESSES;

View file

@ -0,0 +1,164 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.memory;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapRegionsAction;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.Swing;
public class DebuggerRegionMapProposalDialog
extends AbstractDebuggerMapProposalDialog<RegionMapEntry> {
static final int BUTTON_SIZE = 32;
protected enum RegionMapTableColumns
implements EnumeratedTableColumn<RegionMapTableColumns, RegionMapEntry> {
REMOVE("Remove", String.class, e -> "Remove Proposed Entry", (e, v) -> nop()),
REGION_NAME("Region", String.class, e -> e.getRegion().getName()),
DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getRegion().getMinAddress()),
CHOOSE("Choose", String.class, e -> "Choose Block", (e, s) -> nop()),
PROGRAM_NAME("Program", String.class, e -> e.getToProgram().getName()),
BLOCK_NAME("Block", String.class, e -> e.getBlock().getName()),
STATIC_BASE("Static Base", Address.class, e -> e.getBlock().getStart()),
SIZE("Size", Long.class, e -> e.getMappingLength());
private final String header;
private final Class<?> cls;
private final Function<RegionMapEntry, ?> getter;
private final BiConsumer<RegionMapEntry, Object> setter;
private static void nop() {
}
@SuppressWarnings("unchecked")
<T> RegionMapTableColumns(String header, Class<T> cls, Function<RegionMapEntry, T> getter,
BiConsumer<RegionMapEntry, T> setter) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.setter = (BiConsumer<RegionMapEntry, Object>) setter;
}
<T> RegionMapTableColumns(String header, Class<T> cls,
Function<RegionMapEntry, T> getter) {
this(header, cls, getter, null);
}
@Override
public String getHeader() {
return header;
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public Object getValueOf(RegionMapEntry row) {
return getter.apply(row);
}
@Override
public boolean isEditable(RegionMapEntry row) {
return setter != null;
}
@Override
public void setValueOf(RegionMapEntry row, Object value) {
setter.accept(row, value);
}
}
protected static class RegionMapPropsalTableModel extends
DefaultEnumeratedColumnTableModel<RegionMapTableColumns, RegionMapEntry> {
public RegionMapPropsalTableModel() {
super("Region Map", RegionMapTableColumns.class);
}
@Override
public List<RegionMapTableColumns> defaultSortOrder() {
return List.of(RegionMapTableColumns.REGION_NAME);
}
}
private final DebuggerRegionsProvider provider;
public DebuggerRegionMapProposalDialog(DebuggerRegionsProvider provider) {
super(MapRegionsAction.NAME);
this.provider = provider;
}
@Override
protected RegionMapPropsalTableModel createTableModel() {
return new RegionMapPropsalTableModel();
}
@Override
protected void populateComponents() {
super.populateComponents();
setPreferredSize(600, 300);
TableColumnModel columnModel = table.getColumnModel();
TableColumn removeCol = columnModel.getColumn(RegionMapTableColumns.REMOVE.ordinal());
CellEditorUtils.installButton(table, filterPanel, removeCol,
DebuggerResources.ICON_DELETE, BUTTON_SIZE, this::removeEntry);
TableColumn dynBaseCol =
columnModel.getColumn(RegionMapTableColumns.DYNAMIC_BASE.ordinal());
dynBaseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn chooseCol = columnModel.getColumn(RegionMapTableColumns.CHOOSE.ordinal());
CellEditorUtils.installButton(table, filterPanel, chooseCol, DebuggerResources.ICON_PROGRAM,
BUTTON_SIZE, this::chooseAndSetBlock);
TableColumn stBaseCol = columnModel.getColumn(RegionMapTableColumns.STATIC_BASE.ordinal());
stBaseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn sizeCol = columnModel.getColumn(RegionMapTableColumns.SIZE.ordinal());
sizeCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
}
private void chooseAndSetBlock(RegionMapEntry entry) {
Map.Entry<Program, MemoryBlock> choice =
provider.askBlock(entry.getRegion(), entry.getToProgram(), entry.getBlock());
if (choice == null) {
return;
}
Swing.runIfSwingOrRunLater(() -> {
entry.setBlock(choice.getKey(), choice.getValue());
tableModel.notifyUpdated(entry);
});
}
}

View file

@ -15,32 +15,33 @@
*/
package ghidra.app.plugin.core.debug.gui.memory;
import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.services.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( //
shortDescription = "Debugger regions manager", //
description = "GUI to manage memory regions", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
@PluginInfo(
shortDescription = "Debugger regions manager",
description = "GUI to manage memory regions",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceActivatedPluginEvent.class, //
TraceClosedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerModelService.class, //
DebuggerStaticMappingService.class, //
DebuggerTraceManagerService.class, //
ProgramManager.class, //
} //
)
ProgramActivatedPluginEvent.class,
ProgramLocationPluginEvent.class,
ProgramClosedPluginEvent.class,
TraceActivatedPluginEvent.class,
},
servicesRequired = {
DebuggerModelService.class,
DebuggerStaticMappingService.class,
DebuggerTraceManagerService.class,
ProgramManager.class,
})
public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin {
protected DebuggerRegionsProvider provider;
@ -63,7 +64,19 @@ public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin {
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent) {
if (event instanceof ProgramActivatedPluginEvent) {
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
provider.setProgram(ev.getActiveProgram());
}
else if (event instanceof ProgramLocationPluginEvent) {
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
provider.setLocation(ev.getLocation());
}
else if (event instanceof ProgramClosedPluginEvent) {
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
provider.programClosed(ev.getProgram());
}
else if (event instanceof TraceActivatedPluginEvent) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.setTrace(ev.getActiveCoordinates().getTrace());
}

View file

@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.gui.memory;
import java.awt.BorderLayout;
import java.awt.event.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -34,17 +35,21 @@ import docking.action.*;
import docking.widgets.table.CustomToStringCellRenderer;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSelectAddressesAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectRowsAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider;
import ghidra.app.plugin.core.debug.service.modules.MapRegionsBackgroundCommand;
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.*;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace;
@ -52,6 +57,7 @@ import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.util.Msg;
import ghidra.util.database.ObjectKey;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
@ -213,9 +219,13 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
private final DebuggerRegionsPlugin plugin;
@AutoServiceConsumed
private DebuggerListingService listingService;
private DebuggerStaticMappingService staticMappingService;
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerListingService listingService;
@AutoServiceConsumed
ProgramManager programManager;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@ -229,7 +239,17 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
private final JPanel mainPanel = new JPanel(new BorderLayout());
// TODO: Lazy construction of these dialogs?
private final DebuggerBlockChooserDialog blockChooserDialog;
private final DebuggerRegionMapProposalDialog regionProposalDialog;
private DebuggerRegionActionContext myActionContext;
private Program currentProgram;
private ProgramLocation currentLocation;
DockingAction actionMapRegions;
DockingAction actionMapRegionTo;
DockingAction actionMapRegionsTo;
SelectAddressesAction actionSelectAddresses;
DockingAction actionSelectRows;
@ -247,6 +267,9 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
blockChooserDialog = new DebuggerBlockChooserDialog();
regionProposalDialog = new DebuggerRegionMapProposalDialog(this);
setDefaultWindowPosition(WindowPosition.BOTTOM);
setVisible(true);
createActions();
@ -335,12 +358,119 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
}
protected void createActions() {
actionMapRegions = MapRegionsAction.builder(plugin)
.withContext(DebuggerRegionActionContext.class)
.enabledWhen(this::isContextNonEmpty)
.popupWhen(this::isContextNonEmpty)
.onAction(this::activatedMapRegions)
.buildAndInstallLocal(this);
actionMapRegionTo = MapRegionToAction.builder(plugin)
.withContext(DebuggerRegionActionContext.class)
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedRegions().size() == 1)
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedRegions().size() == 1)
.onAction(this::activatedMapRegionTo)
.buildAndInstallLocal(this);
actionMapRegionsTo = MapRegionsToAction.builder(plugin)
.withContext(DebuggerRegionActionContext.class)
.enabledWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx))
.popupWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx))
.onAction(this::activatedMapRegionsTo)
.buildAndInstallLocal(this);
actionSelectAddresses = new SelectAddressesAction();
actionSelectRows = SelectRowsAction.builder(plugin)
.description("Select regions by trace selection")
.enabledWhen(ctx -> currentTrace != null)
.onAction(this::activatedSelectCurrent)
.buildAndInstallLocal(this);
contextChanged();
}
private boolean isContextNonEmpty(DebuggerRegionActionContext ctx) {
return !ctx.getSelectedRegions().isEmpty();
}
private static Set<TraceMemoryRegion> getSelectedRegions(DebuggerRegionActionContext ctx) {
if (ctx == null) {
return null;
}
return ctx.getSelectedRegions()
.stream()
.map(r -> r.getRegion())
.collect(Collectors.toSet());
}
private void activatedMapRegions(DebuggerRegionActionContext ignored) {
mapRegions(getSelectedRegions(myActionContext));
}
private void activatedMapRegionsTo(DebuggerRegionActionContext ignored) {
Set<TraceMemoryRegion> sel = getSelectedRegions(myActionContext);
if (sel == null || sel.isEmpty()) {
return;
}
mapRegionsTo(sel);
}
private void activatedMapRegionTo(DebuggerRegionActionContext ignored) {
Set<TraceMemoryRegion> sel = getSelectedRegions(myActionContext);
if (sel == null || sel.size() != 1) {
return;
}
mapRegionTo(sel.iterator().next());
}
protected void promptRegionProposal(Collection<RegionMapEntry> proposal) {
if (proposal.isEmpty()) {
Msg.showInfo(this, getComponent(), "Map Regions",
"Could not formulate a propsal for any selection region." +
" You may need to import and/or open the destination images first.");
return;
}
Collection<RegionMapEntry> adjusted =
regionProposalDialog.adjustCollection(getTool(), proposal);
if (adjusted == null || staticMappingService == null) {
return;
}
tool.executeBackgroundCommand(
new MapRegionsBackgroundCommand(staticMappingService, adjusted), currentTrace);
}
protected void mapRegions(Set<TraceMemoryRegion> regions) {
if (staticMappingService == null) {
return;
}
Map<?, RegionMapProposal> map = staticMappingService.proposeRegionMaps(regions,
List.of(programManager.getAllOpenPrograms()));
Collection<RegionMapEntry> proposal = MapProposal.flatten(map.values());
promptRegionProposal(proposal);
}
protected void mapRegionsTo(Set<TraceMemoryRegion> regions) {
if (staticMappingService == null) {
return;
}
Program program = currentProgram;
if (program == null) {
return;
}
RegionMapProposal map = staticMappingService.proposeRegionMap(regions, program);
Collection<RegionMapEntry> proposal = map.computeMap().values();
promptRegionProposal(proposal);
}
protected void mapRegionTo(TraceMemoryRegion region) {
if (staticMappingService == null) {
return;
}
ProgramLocation location = currentLocation;
MemoryBlock block = computeBlock(location);
if (block == null) {
return;
}
RegionMapProposal map =
staticMappingService.proposeRegionMap(region, location.getProgram(), block);
promptRegionProposal(map.computeMap().values());
}
private void activatedSelectCurrent(ActionContext ignored) {
@ -385,6 +515,34 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
return mainPanel;
}
public void setProgram(Program program) {
currentProgram = program;
String name = (program == null ? "..." : program.getName());
actionMapRegionTo.getPopupMenuData().setMenuItemName(MapRegionToAction.NAME_PREFIX + name);
actionMapRegionsTo.getPopupMenuData()
.setMenuItemName(MapRegionsToAction.NAME_PREFIX + name);
}
public static MemoryBlock computeBlock(ProgramLocation location) {
return DebuggerModulesProvider.computeBlock(location);
}
public static String computeBlockName(ProgramLocation location) {
return DebuggerModulesProvider.computeBlockName(location);
}
public void setLocation(ProgramLocation location) {
currentLocation = location;
String name = MapRegionToAction.NAME_PREFIX + computeBlockName(location);
actionMapRegionTo.getPopupMenuData().setMenuItemName(name);
}
public void programClosed(Program program) {
if (currentProgram == program) {
currentProgram = null;
}
}
public void setTrace(Trace trace) {
if (currentTrace == trace) {
return;
@ -409,4 +567,14 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
}
currentTrace.addListener(regionsListener);
}
public Entry<Program, MemoryBlock> askBlock(TraceMemoryRegion region, Program program,
MemoryBlock block) {
if (programManager == null) {
Msg.warn(this, "No program manager!");
return null;
}
return blockChooserDialog.chooseBlock(getTool(), region,
List.of(programManager.getAllOpenPrograms()));
}
}

View file

@ -35,7 +35,6 @@ import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.modules.TraceConflictedMappingException;
import ghidra.util.MathUtilities;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.layout.PairLayout;
public class DebuggerAddMappingDialog extends DialogComponentProvider {
@ -296,10 +295,8 @@ public class DebuggerAddMappingDialog extends DialogComponentProvider {
ProgramLocation to = new ProgramLocation(program,
fieldProgRange.getRange().getMinAddress());
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Add Static Mapping", false)) {
try {
mappingService.addMapping(from, to, getLength(), true);
tid.commit();
}
catch (TraceConflictedMappingException e) {
throw new AssertionError(e); // I said truncateExisting

View file

@ -24,9 +24,10 @@ import javax.swing.table.TableColumnModel;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapModulesAction;
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapEntry;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
@ -43,8 +44,8 @@ public class DebuggerModuleMapProposalDialog
MODULE_NAME("Module", String.class, e -> e.getModule().getName()),
DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getModule().getBase()),
CHOOSE("Choose", String.class, e -> "Choose Program", (e, v) -> nop()),
PROGRAM_NAME("Program", String.class, e -> e.getProgram().getName()),
STATIC_BASE("Static Base", Address.class, e -> e.getProgram().getImageBase()),
PROGRAM_NAME("Program", String.class, e -> e.getToProgram().getName()),
STATIC_BASE("Static Base", Address.class, e -> e.getToProgram().getImageBase()),
SIZE("Size", Long.class, e -> e.getModuleRange().getLength());
private final String header;
@ -146,7 +147,7 @@ public class DebuggerModuleMapProposalDialog
}
private void chooseAndSetProgram(ModuleMapEntry entry) {
DomainFile file = provider.askProgram(entry.getProgram());
DomainFile file = provider.askProgram(entry.getToProgram());
if (file == null) {
return;
}

View file

@ -24,25 +24,24 @@ import ghidra.app.services.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( //
shortDescription = "Debugger module and section manager", //
description = "GUI to manage modules and sections", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
@PluginInfo(
shortDescription = "Debugger module and section manager",
description = "GUI to manage modules and sections",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
ProgramActivatedPluginEvent.class, //
ProgramLocationPluginEvent.class, //
ProgramClosedPluginEvent.class, //
TraceActivatedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerModelService.class, //
DebuggerStaticMappingService.class, //
DebuggerTraceManagerService.class, //
ProgramManager.class, //
} //
)
ProgramActivatedPluginEvent.class,
ProgramLocationPluginEvent.class,
ProgramClosedPluginEvent.class,
TraceActivatedPluginEvent.class,
},
servicesRequired = {
DebuggerModelService.class,
DebuggerStaticMappingService.class,
DebuggerTraceManagerService.class,
ProgramManager.class,
})
public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
protected DebuggerModulesProvider provider;

View file

@ -38,6 +38,7 @@ import docking.widgets.table.CustomToStringCellRenderer;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.TableFilter;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.service.modules.MapModulesBackgroundCommand;
@ -45,7 +46,8 @@ import ghidra.app.plugin.core.debug.service.modules.MapSectionsBackgroundCommand
import ghidra.app.plugin.core.debug.utils.BackgroundUtils;
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStaticMappingService.*;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
import ghidra.framework.main.AppInfo;
@ -634,7 +636,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
private void loadModules() {
moduleTable.getSelectionModel().clearSelection();
moduleTableModel.clear();
sectionTable.getSelectionModel().clearSelection();
sectionTableModel.clear();
if (currentTrace == null) {
@ -758,8 +762,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
.onAction(this::activatedMapModules)
.buildAndInstallLocal(this);
actionMapModuleTo = MapModuleToAction.builder(plugin)
.enabledWhen(ctx -> currentProgram != null)
.withContext(DebuggerModuleActionContext.class)
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedModules().size() == 1)
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedModules().size() == 1)
.onAction(this::activatedMapModuleTo)
.buildAndInstallLocal(this);
@ -769,13 +773,13 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
.onAction(this::activatedMapSections)
.buildAndInstallLocal(this);
actionMapSectionTo = MapSectionToAction.builder(plugin)
.enabledWhen(ctx -> currentProgram != null)
.withContext(DebuggerSectionActionContext.class)
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedSections().size() == 1)
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedSections().size() == 1)
.onAction(this::activatedMapSectionTo)
.buildAndInstallLocal(this);
actionMapSectionsTo = MapSectionsToAction.builder(plugin)
.enabledWhen(ctx -> currentProgram != null)
.enabledWhen(ctx -> currentProgram != null && isContextSectionsOfOneModule(ctx))
.popupWhen(ctx -> currentProgram != null && isContextSectionsOfOneModule(ctx))
.onAction(this::activatedMapSectionsTo)
.buildAndInstallLocal(this);
@ -1006,7 +1010,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
Map<TraceModule, ModuleMapProposal> map = staticMappingService.proposeModuleMaps(modules,
List.of(programManager.getAllOpenPrograms()));
Collection<ModuleMapEntry> proposal = ModuleMapProposal.flatten(map.values());
Collection<ModuleMapEntry> proposal = MapProposal.flatten(map.values());
promptModuleProposal(proposal);
}
@ -1045,9 +1049,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
Set<TraceModule> modules =
sections.stream().map(TraceSection::getModule).collect(Collectors.toSet());
Map<TraceModule, SectionMapProposal> map = staticMappingService.proposeSectionMaps(modules,
Map<?, SectionMapProposal> map = staticMappingService.proposeSectionMaps(modules,
List.of(programManager.getAllOpenPrograms()));
Collection<SectionMapEntry> proposal = SectionMapProposal.flatten(map.values());
Collection<SectionMapEntry> proposal = MapProposal.flatten(map.values());
Collection<SectionMapEntry> filtered = proposal.stream()
.filter(e -> sections.contains(e.getSection()))
.collect(Collectors.toSet());
@ -1085,7 +1089,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
if (block == null) {
return;
}
promptSectionProposal(List.of(new SectionMapEntry(section, location.getProgram(), block)));
SectionMapProposal map =
staticMappingService.proposeSectionMap(section, location.getProgram(), block);
promptSectionProposal(map.computeMap().values());
}
protected Set<MemoryBlock> collectBlocksInOpenPrograms() {

View file

@ -25,9 +25,10 @@ import javax.swing.table.TableColumnModel;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapSectionsAction;
import ghidra.app.services.DebuggerStaticMappingService.SectionMapEntry;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
@ -45,10 +46,10 @@ public class DebuggerSectionMapProposalDialog
SECTION_NAME("Section", String.class, e -> e.getSection().getName()),
DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getSection().getStart()),
CHOOSE("Choose", String.class, e -> "Choose Block", (e, s) -> nop()),
PROGRAM_NAME("Program", String.class, e -> e.getProgram().getName()),
PROGRAM_NAME("Program", String.class, e -> e.getToProgram().getName()),
BLOCK_NAME("Block", String.class, e -> e.getBlock().getName()),
STATIC_BASE("Static Base", Address.class, e -> e.getBlock().getStart()),
SIZE("Size", Long.class, e -> e.getLength());
SIZE("Size", Long.class, e -> e.getMappingLength());
private final String header;
private final Class<?> cls;
@ -151,7 +152,7 @@ public class DebuggerSectionMapProposalDialog
private void chooseAndSetBlock(SectionMapEntry entry) {
Map.Entry<Program, MemoryBlock> choice =
provider.askBlock(entry.getSection(), entry.getProgram(), entry.getBlock());
provider.askBlock(entry.getSection(), entry.getToProgram(), entry.getBlock());
if (choice == null) {
return;
}

View file

@ -0,0 +1,99 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.modules;
import java.util.Objects;
import ghidra.app.services.MapEntry;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
public abstract class AbstractMapEntry<T, P> implements MapEntry<T, P> {
protected final Trace fromTrace;
protected final T fromObject;
protected Program toProgram;
protected P toObject;
public AbstractMapEntry(Trace fromTrace, T fromObject, Program toProgram, P toObject) {
this.fromTrace = fromTrace;
this.fromObject = fromObject;
this.toProgram = toProgram;
this.toObject = toObject;
}
@Override
public boolean equals(Object obj) {
// TODO: I guess comparing only the "from" object is sufficient....
if (!(obj instanceof AbstractMapEntry<?, ?>)) {
return false;
}
AbstractMapEntry<?, ?> that = (AbstractMapEntry<?, ?>) obj;
return this.fromObject == that.fromObject;
}
@Override
public int hashCode() {
return Objects.hash(fromObject);
}
@Override
public Trace getFromTrace() {
return fromTrace;
}
@Override
public T getFromObject() {
return fromObject;
}
@Override
public TraceLocation getFromTraceLocation() {
return new DefaultTraceLocation(fromTrace, null, getFromLifespan(),
getFromRange().getMinAddress());
}
protected void setToObject(Program toProgram, P toObject) {
this.toProgram = toProgram;
this.toObject = toObject;
}
@Override
public Program getToProgram() {
return toProgram;
}
@Override
public P getToObject() {
return toObject;
}
@Override
public ProgramLocation getToProgramLocation() {
return new ProgramLocation(toProgram, getToRange().getMinAddress());
}
/**
* {@inheritDoc}
*
* @implNote Ideally the "from" and "to" objects have exactly the same length. If they don't,
* take the minimum.
*/
@Override
public long getMappingLength() {
return Math.min(getFromRange().getLength(), getToRange().getLength());
}
}

View file

@ -0,0 +1,145 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.modules;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import ghidra.app.services.MapEntry;
import ghidra.app.services.MapProposal;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
public abstract class AbstractMapProposal<T, P, E extends MapEntry<T, P>>
implements MapProposal<T, P, E> {
protected abstract static class Matcher<T, P> {
protected final T fromObject;
protected final P toObject;
protected final AddressRange fromRange;
protected final AddressRange toRange;
protected final double score;
protected Matcher(T fromObject, P toObject) {
this.fromObject = fromObject;
this.toObject = toObject;
this.fromRange = fromObject == null ? null : getFromRange();
this.toRange = toObject == null ? null : getToRange();
this.score = fromObject == null || toObject == null ? 0 : computeScore();
}
protected abstract AddressRange getFromRange();
protected abstract AddressRange getToRange();
protected double computeScore() {
return computeKeyMatchScore() + computeLengthScore();
}
protected int computeKeyMatchScore() {
return 3;
}
protected long shiftRight1RoundUp(long val) {
if ((val & 1) == 1) {
return (val >>> 1) + 1;
}
return val >>> 1;
}
protected double computeLengthScore() {
long fLen = fromRange.getLength();
long tLen = toRange.getLength();
for (int bitsmatched = 64; bitsmatched > 0; bitsmatched--) {
if ((fLen == tLen)) {
return bitsmatched / 6.4d;
}
fLen = shiftRight1RoundUp(fLen);
tLen = shiftRight1RoundUp(tLen);
}
return 0;
}
}
protected static abstract class MatcherMap<K, T, P, M extends Matcher<T, P>> {
protected Map<K, Set<T>> fromsByJoin = new LinkedHashMap<>();
protected Map<T, M> map = new LinkedHashMap<>();
protected abstract M newMatcher(T fromObject, P toObject);
protected abstract K getFromJoinKey(T fromObject);
protected abstract K getToJoinKey(P toObject);
protected void processFromObject(T fromObject) {
fromsByJoin.computeIfAbsent(getFromJoinKey(fromObject), k -> new LinkedHashSet<>())
.add(fromObject);
}
protected void processToObject(P toObject) {
Set<T> froms = fromsByJoin.get(getToJoinKey(toObject));
if (froms == null) {
return;
}
for (T f : froms) {
M bestM = map.get(f);
M candM = newMatcher(f, toObject);
if (bestM == null || candM.score > bestM.score) {
map.put(f, candM);
}
}
}
protected double averageScore() {
return map.values()
.stream()
.reduce(0d, (s, m) -> s + m.score, Double::sum) /
map.size();
}
protected <E> Map<T, E> computeMap(Function<M, E> newEntry) {
return map.values()
.stream()
.filter(m -> m.fromObject != null && m.toObject != null)
.collect(Collectors.toMap(m -> m.fromObject, newEntry));
}
protected P getToObject(T fromObject) {
M m = map.get(fromObject);
return m == null ? null : m.toObject;
}
}
protected final Trace trace;
protected final Program program;
public AbstractMapProposal(Trace trace, Program program) {
this.trace = trace;
this.program = program;
}
@Override
public Trace getTrace() {
return trace;
}
@Override
public Program getProgram() {
return program;
}
}

View file

@ -0,0 +1,287 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.modules;
import java.io.File;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ghidra.app.services.*;
import ghidra.dbg.util.PathUtils;
import ghidra.framework.model.DomainFile;
import ghidra.graph.*;
import ghidra.graph.jung.JungDirectedGraph;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.util.Msg;
public enum DebuggerStaticMappingProposals {
;
protected static String getLastLower(String path) {
return new File(path).getName().toLowerCase();
}
/**
* Check if either the program's name, its executable path, or its domain file name contains the
* given module name
*
* @param program the program whose names to check
* @param moduleLowerName the module name to check for in lower case
* @return true if matched, false if not
*/
protected static boolean namesContain(Program program, String moduleLowerName) {
DomainFile df = program.getDomainFile();
if (df == null || df.getProjectLocator() == null) {
return false;
}
String programName = getLastLower(program.getName());
if (programName.contains(moduleLowerName)) {
return true;
}
String exePath = program.getExecutablePath();
if (exePath != null) {
String execName = getLastLower(exePath);
if (execName.contains(moduleLowerName)) {
return true;
}
}
String fileName = df.getName().toLowerCase();
if (fileName.contains(moduleLowerName)) {
return true;
}
return false;
}
protected abstract static class ProposalGenerator<F, T, J, MP extends MapProposal<?, ?, ?>> {
protected abstract MP proposeMap(F from, T to);
protected abstract J computeFromJoinKey(F from);
protected abstract boolean isJoined(J key, T to);
protected MP proposeBestMap(F from, Collection<? extends T> tos) {
double bestScore = -1;
MP bestMap = null;
for (T t : tos) {
MP map = proposeMap(from, t);
double score = map.computeScore();
// NOTE: Ties prefer first in candidate collection
if (score > bestScore) {
bestScore = score;
bestMap = map;
}
}
return bestMap;
}
protected Map<F, MP> proposeBestMaps(Collection<? extends F> froms,
Collection<? extends T> tos) {
Map<F, MP> result = new LinkedHashMap<>();
for (F f : froms) {
J joinKey = computeFromJoinKey(f);
Set<T> joined = tos.stream()
.filter(t -> isJoined(joinKey, t))
// Need to preserve order here
.collect(Collectors.toCollection(LinkedHashSet::new));
MP map = proposeBestMap(f, joined);
if (map != null) {
result.put(f, map);
}
}
return result;
}
}
protected static class ModuleMapProposalGenerator
extends ProposalGenerator<TraceModule, Program, String, ModuleMapProposal> {
@Override
protected ModuleMapProposal proposeMap(TraceModule from, Program to) {
return new DefaultModuleMapProposal(from, to);
}
@Override
protected String computeFromJoinKey(TraceModule from) {
return getLastLower(from.getName());
}
@Override
protected boolean isJoined(String key, Program to) {
return namesContain(to, key);
}
}
protected static final ModuleMapProposalGenerator MODULES = new ModuleMapProposalGenerator();
public static ModuleMapProposal proposeModuleMap(TraceModule module, Program program) {
return MODULES.proposeMap(module, program);
}
public static ModuleMapProposal proposeModuleMap(TraceModule module,
Collection<? extends Program> programs) {
return MODULES.proposeBestMap(module, programs);
}
public static Map<TraceModule, ModuleMapProposal> proposeModuleMaps(
Collection<? extends TraceModule> modules, Collection<? extends Program> programs) {
return MODULES.proposeBestMaps(modules, programs);
}
protected static class SectionMapProposalGenerator
extends ProposalGenerator<TraceModule, Program, String, SectionMapProposal> {
@Override
protected SectionMapProposal proposeMap(TraceModule from, Program to) {
return new DefaultSectionMapProposal(from, to);
}
@Override
protected String computeFromJoinKey(TraceModule from) {
return getLastLower(from.getName());
}
@Override
protected boolean isJoined(String key, Program to) {
return namesContain(to, key);
}
}
protected static final SectionMapProposalGenerator SECTIONS = new SectionMapProposalGenerator();
public static SectionMapProposal proposeSectionMap(TraceSection section, Program program,
MemoryBlock block) {
return new DefaultSectionMapProposal(section, program, block);
}
public static SectionMapProposal proposeSectionMap(TraceModule module, Program program) {
return SECTIONS.proposeMap(module, program);
}
public static SectionMapProposal proposeSectionMap(TraceModule module,
Collection<? extends Program> programs) {
return SECTIONS.proposeBestMap(module, programs);
}
public static Map<TraceModule, SectionMapProposal> proposeSectionMaps(
Collection<? extends TraceModule> modules, Collection<? extends Program> programs) {
return SECTIONS.proposeBestMaps(modules, programs);
}
protected static class RegionMapProposalGenerator extends
ProposalGenerator<Collection<TraceMemoryRegion>, Program, Set<String>, //
RegionMapProposal> {
@Override
protected RegionMapProposal proposeMap(Collection<TraceMemoryRegion> from,
Program to) {
return new DefaultRegionMapProposal(from, to);
}
@Override
protected Set<String> computeFromJoinKey(Collection<TraceMemoryRegion> from) {
return from.stream()
.flatMap(r -> getLikelyModulesFromName(r).stream())
.map(n -> getLastLower(n))
.collect(Collectors.toSet());
}
@Override
protected boolean isJoined(Set<String> key, Program to) {
return key.stream().anyMatch(n -> namesContain(to, n));
}
}
protected static final RegionMapProposalGenerator REGIONS = new RegionMapProposalGenerator();
public static RegionMapProposal proposeRegionMap(TraceMemoryRegion region, Program program,
MemoryBlock block) {
return new DefaultRegionMapProposal(region, program, block);
}
public static RegionMapProposal proposeRegionMap(
Collection<? extends TraceMemoryRegion> regions,
Program program) {
return REGIONS.proposeMap(Collections.unmodifiableCollection(regions), program);
}
public static RegionMapProposal proposeRegionMap(
Collection<? extends TraceMemoryRegion> regions,
Collection<? extends Program> programs) {
return REGIONS.proposeBestMap(Collections.unmodifiableCollection(regions), programs);
}
public static <V, J> Set<Set<V>> groupByComponents(Collection<? extends V> vertices,
Function<V, J> precompute, BiPredicate<J, J> areConnected) {
List<V> vs = List.copyOf(vertices);
List<J> pres = vs.stream().map(precompute).collect(Collectors.toList());
GDirectedGraph<V, GEdge<V>> graph = new JungDirectedGraph<>();
for (V v : vs) {
graph.addVertex(v); // Lone regions should still be considered
}
int size = vs.size();
for (int i = 0; i < size; i++) {
V v1 = vs.get(i);
J j1 = pres.get(i);
for (int j = i + 1; j < size; j++) {
V v2 = vs.get(j);
J j2 = pres.get(j);
if (areConnected.test(j1, j2)) {
graph.addEdge(new DefaultGEdge<>(v1, v2));
graph.addEdge(new DefaultGEdge<>(v2, v1));
}
}
}
return GraphAlgorithms.getStronglyConnectedComponents(graph);
}
protected static Set<String> getLikelyModulesFromName(TraceMemoryRegion region) {
String key;
try {
List<String> path = PathUtils.parse(region.getName());
key = PathUtils.getKey(path);
if (PathUtils.isIndex(key)) {
key = PathUtils.parseIndex(key);
}
}
catch (IllegalArgumentException e) { // Parse error
Msg.error(DebuggerStaticMappingProposals.class,
"Encountered unparsable path: " + region.getName());
key = region.getName(); // Not a great fallback, but it'll have to do
}
return Stream.of(key.split("\\s+"))
.filter(n -> n.replaceAll("[0-9A-Fa-f]+", "").length() >= 5)
.collect(Collectors.toSet());
}
public static Set<Set<TraceMemoryRegion>> groupRegionsByLikelyModule(
Collection<? extends TraceMemoryRegion> regions) {
return groupByComponents(regions, r -> getLikelyModulesFromName(r), (m1, m2) -> {
return m1.stream().anyMatch(m2::contains);
});
}
public static Map<Collection<TraceMemoryRegion>, RegionMapProposal> proposeRegionMaps(
Collection<? extends TraceMemoryRegion> regions,
Collection<? extends Program> programs) {
Set<Set<TraceMemoryRegion>> groups = groupRegionsByLikelyModule(regions);
return REGIONS.proposeBestMaps(groups, programs);
}
}

View file

@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.debug.service.modules;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
@ -34,23 +32,21 @@ import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
import ghidra.app.plugin.core.debug.utils.*;
import ghidra.app.services.*;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.framework.data.OpenedDomainFile;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.framework.store.FileSystem;
import ghidra.generic.util.datastruct.TreeValueSortedMap;
import ghidra.generic.util.datastruct.ValueSortedMap;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Library;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.ExternalManager;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceStaticMappingChangeType;
import ghidra.trace.model.memory.TraceMemoryRegion;
@ -60,7 +56,6 @@ import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
@PluginInfo(
@ -85,199 +80,6 @@ import ghidra.util.task.TaskMonitor;
public class DebuggerStaticMappingServicePlugin extends Plugin
implements DebuggerStaticMappingService, DomainFolderChangeAdapter {
protected static class PluginModuleMapProposal implements ModuleMapProposal {
private final TraceModule module;
private final Program program;
private final NavigableMap<Long, RegionMatcher> matchers = new TreeMap<>();
private Address imageBase;
private Address moduleBase;
private long imageSize;
private AddressRange moduleRange; // TODO: This is now in the trace schema. Use it.
public PluginModuleMapProposal(TraceModule module, Program program) {
this.module = module;
this.program = program;
processProgram();
processModule();
}
@Override
public TraceModule getModule() {
return module;
}
@Override
public Program getProgram() {
return program;
}
private RegionMatcher getMatcher(long baseOffset) {
return matchers.computeIfAbsent(baseOffset, RegionMatcher::new);
}
private void processProgram() {
imageBase = program.getImageBase();
imageSize = ModuleMapEntry.computeImageSize(program);
// TODO: How to handle Harvard architectures?
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (!ModuleMapEntry.includeBlock(program, block)) {
continue;
}
getMatcher(block.getStart().subtract(imageBase)).block = block;
}
}
/**
* Must be called after processProgram, so that image size is known
*/
private void processModule() {
moduleBase = module.getBase();
try {
moduleRange = new AddressRangeImpl(moduleBase, imageSize);
}
catch (AddressOverflowException e) {
return; // Just score it as having no matches?
}
for (TraceMemoryRegion region : module.getTrace()
.getMemoryManager()
.getRegionsIntersecting(module.getLifespan(), moduleRange)) {
getMatcher(region.getMinAddress().subtract(moduleBase)).region = region;
}
}
@Override
public double computeScore() {
return ((double) matchers.values()
.stream()
.reduce(0, (s, m) -> s + m.score(), Integer::sum)) /
matchers.size();
}
@Override
public Map<TraceModule, ModuleMapEntry> computeMap() {
return Map.of(module, new ModuleMapEntry(module, program, moduleRange));
}
}
protected static class RegionMatcher {
private MemoryBlock block;
private TraceMemoryRegion region;
public RegionMatcher(long baseOffset) {
}
private int score() {
if (block == null || region == null) {
return 0; // Unmatched
}
int score = 3; // For the matching offset
if (block.getSize() == region.getLength()) {
score += 10;
}
return score;
}
}
protected static class PluginSectionMapProposal implements SectionMapProposal {
private final TraceModule module;
private final Program program;
private final Map<String, SectionMatcher> matchers = new LinkedHashMap<>();
public PluginSectionMapProposal(TraceModule module, Program program) {
this.module = module;
this.program = program;
processModule();
processProgram();
}
public PluginSectionMapProposal(TraceSection section, Program program, MemoryBlock block) {
this.module = section.getModule();
this.program = program;
processSection(section);
processBlock(block);
}
@Override
public TraceModule getModule() {
return module;
}
@Override
public Program getProgram() {
return program;
}
private void processSection(TraceSection section) {
matchers.put(section.getName(), new SectionMatcher(section));
}
private void processBlock(MemoryBlock block) {
SectionMatcher m =
matchers.computeIfAbsent(block.getName(), n -> new SectionMatcher(null));
m.block = block;
}
private void processModule() {
for (TraceSection section : module.getSections()) {
processSection(section);
}
}
private void processProgram() {
for (MemoryBlock block : program.getMemory().getBlocks()) {
processBlock(block);
}
}
@Override
public double computeScore() {
return ((double) matchers.values()
.stream()
.reduce(0, (s, m) -> s + m.score(), Integer::sum)) /
matchers.size();
}
@Override
public Map<TraceSection, SectionMapEntry> computeMap() {
return matchers.values()
.stream()
.filter(m -> m.section != null && m.block != null)
.collect(Collectors.toMap(m -> m.section,
m -> new SectionMapEntry(m.section, program, m.block)));
}
@Override
public MemoryBlock getDestination(TraceSection section) {
SectionMatcher m = matchers.get(section.getName());
return m == null ? null : m.block;
}
}
protected static class SectionMatcher {
private final TraceSection section;
private MemoryBlock block;
public SectionMatcher(TraceSection section) {
this.section = section;
}
public int score() {
if (section == null || block == null) {
return 0; // Unmatched
}
int score = 3; // For the matching name
if (section.getRange().getLength() == block.getSize()) {
score += 10;
}
if ((section.getStart().getOffset() & 0xfff) == (block.getStart().getOffset() &
0xfff)) {
score += 20;
}
return score;
}
}
protected class MappingEntry {
private final TraceStaticMapping mapping;
@ -937,184 +739,75 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
@Override
public void addMapping(TraceLocation from, ProgramLocation to, long length,
boolean truncateExisting) throws TraceConflictedMappingException {
Program tp = to.getProgram();
if (tp instanceof TraceProgramView) {
throw new IllegalArgumentException(
"Mapping destination cannot be a " + TraceProgramView.class.getSimpleName());
try (UndoableTransaction tid =
UndoableTransaction.start(from.getTrace(), "Add mapping", true)) {
DebuggerStaticMappingUtils.addMapping(from, to, length, truncateExisting);
}
TraceStaticMappingManager manager = from.getTrace().getStaticMappingManager();
URL toURL = ProgramURLUtils.getUrlFromProgram(tp);
if (toURL == null) {
noProject();
}
Address fromAddress = from.getAddress();
Address toAddress = to.getByteAddress();
long maxFromLengthMinus1 =
fromAddress.getAddressSpace().getMaxAddress().subtract(fromAddress);
long maxToLengthMinus1 =
toAddress.getAddressSpace().getMaxAddress().subtract(toAddress);
if (Long.compareUnsigned(length - 1, maxFromLengthMinus1) > 0) {
throw new IllegalArgumentException("Length would cause address overflow in trace");
}
if (Long.compareUnsigned(length - 1, maxToLengthMinus1) > 0) {
throw new IllegalArgumentException("Length would cause address overflow in program");
}
Address end = fromAddress.addWrap(length - 1);
// Also check end in the destination
AddressRangeImpl range = new AddressRangeImpl(fromAddress, end);
Range<Long> fromLifespan = from.getLifespan();
if (truncateExisting) {
long truncEnd = DBTraceUtils.lowerEndpoint(fromLifespan) - 1;
for (TraceStaticMapping existing : List
.copyOf(manager.findAllOverlapping(range, fromLifespan))) {
existing.delete();
if (fromLifespan.hasLowerBound() &&
Long.compare(existing.getStartSnap(), truncEnd) <= 0) {
manager.add(existing.getTraceAddressRange(),
Range.closed(existing.getStartSnap(), truncEnd),
existing.getStaticProgramURL(), existing.getStaticAddress());
}
}
}
manager.add(range, fromLifespan, toURL, toAddress.toString(true));
}
static protected AddressRange clippedRange(Trace trace, String spaceName, long min,
long max) {
AddressSpace space = trace.getBaseAddressFactory().getAddressSpace(spaceName);
if (space == null) {
return null;
@Override
public void addMapping(MapEntry<?, ?> entry, boolean truncateExisting)
throws TraceConflictedMappingException {
try (UndoableTransaction tid =
UndoableTransaction.start(entry.getFromTrace(), "Add mapping", true)) {
DebuggerStaticMappingUtils.addMapping(entry, truncateExisting);
}
Address spaceMax = space.getMaxAddress();
if (Long.compareUnsigned(min, spaceMax.getOffset()) > 0) {
return null;
}
if (Long.compareUnsigned(max, spaceMax.getOffset()) > 0) {
return new AddressRangeImpl(space.getAddress(min), spaceMax);
@Override
public void addMappings(Collection<? extends MapEntry<?, ?>> entries, TaskMonitor monitor,
boolean truncateExisting, String description) throws CancelledException {
Map<Trace, List<MapEntry<?, ?>>> byTrace =
entries.stream().collect(Collectors.groupingBy(ent -> ent.getFromTrace()));
for (Map.Entry<Trace, List<MapEntry<?, ?>>> ent : byTrace.entrySet()) {
Trace trace = ent.getKey();
try (UndoableTransaction tid =
UndoableTransaction.start(trace, description, true)) {
doAddMappings(trace, ent.getValue(), monitor, truncateExisting);
}
}
}
protected static void doAddMappings(Trace trace, Collection<MapEntry<?, ?>> entries,
TaskMonitor monitor, boolean truncateExisting) throws CancelledException {
for (MapEntry<?, ?> ent : entries) {
monitor.checkCanceled();
try {
DebuggerStaticMappingUtils.addMapping(ent, truncateExisting);
}
catch (Exception e) {
Msg.error(DebuggerStaticMappingService.class,
"Could not add mapping " + ent + ": " + e.getMessage());
}
}
return new AddressRangeImpl(space.getAddress(min), space.getAddress(max));
}
@Override
public void addIdentityMapping(Trace from, Program toProgram, Range<Long> lifespan,
boolean truncateExisting) {
try (UndoableTransaction tid =
UndoableTransaction.start(from, "Add identity mappings", false)) {
doAddIdentityMapping(from, toProgram, lifespan, truncateExisting);
tid.commit();
}
}
protected void doAddIdentityMapping(Trace from, Program toProgram, Range<Long> lifespan,
boolean truncateExisting) {
Map<String, Address> mins = new HashMap<>();
Map<String, Address> maxs = new HashMap<>();
for (AddressRange range : toProgram.getMemory().getAddressRanges()) {
mins.compute(range.getAddressSpace().getName(), (n, min) -> {
Address can = range.getMinAddress();
if (min == null || can.compareTo(min) < 0) {
return can;
}
return min;
});
maxs.compute(range.getAddressSpace().getName(), (n, max) -> {
Address can = range.getMaxAddress();
if (max == null || can.compareTo(max) > 0) {
return can;
}
return max;
});
}
for (String name : mins.keySet()) {
AddressRange range = clippedRange(from, name, mins.get(name).getOffset(),
maxs.get(name).getOffset());
if (range == null) {
continue;
}
try {
addMapping(new DefaultTraceLocation(from, null, lifespan, range.getMinAddress()),
new ProgramLocation(toProgram, mins.get(name)), range.getLength(),
UndoableTransaction.start(from, "Add identity mappings", true)) {
DebuggerStaticMappingUtils.addIdentityMapping(from, toProgram, lifespan,
truncateExisting);
}
catch (TraceConflictedMappingException e) {
Msg.error(this, "Could not add identity mapping " + range + ": " + e.getMessage());
}
}
}
@Override
public void addModuleMapping(TraceModule from, long length, Program toProgram,
boolean truncateExisting) throws TraceConflictedMappingException {
TraceLocation fromLoc =
new DefaultTraceLocation(from.getTrace(), null, from.getLifespan(), from.getBase());
ProgramLocation toLoc = new ProgramLocation(toProgram, toProgram.getImageBase());
addMapping(fromLoc, toLoc, length, truncateExisting);
}
@Override
public void addModuleMappings(Collection<ModuleMapEntry> entries, TaskMonitor monitor,
boolean truncateExisting) throws CancelledException {
Map<Trace, Set<ModuleMapEntry>> byTrace = new LinkedHashMap<>();
for (ModuleMapEntry ent : entries) {
Set<ModuleMapEntry> subCol =
byTrace.computeIfAbsent(ent.getModule().getTrace(), t -> new LinkedHashSet<>());
subCol.add(ent);
}
for (Map.Entry<Trace, Set<ModuleMapEntry>> ent : byTrace.entrySet()) {
Trace trace = ent.getKey();
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Add module mappings", false)) {
doAddModuleMappings(trace, ent.getValue(), monitor, truncateExisting);
tid.commit();
}
}
}
protected void doAddModuleMappings(Trace trace, Collection<ModuleMapEntry> entries,
TaskMonitor monitor, boolean truncateExisting) throws CancelledException {
for (ModuleMapEntry ent : entries) {
monitor.checkCanceled();
try {
DebuggerStaticMappingUtils.addModuleMapping(ent.getModule(),
ent.getModuleRange().getLength(), ent.getProgram(), truncateExisting);
}
catch (Exception e) {
Msg.error(this, "Could not add mapping " + ent + ": " + e.getMessage());
}
}
addMappings(entries, monitor, truncateExisting, "Add module mappings");
}
@Override
public void addSectionMappings(Collection<SectionMapEntry> entries,
TaskMonitor monitor, boolean truncateExisting) throws CancelledException {
Map<Trace, Set<SectionMapEntry>> byTrace = new LinkedHashMap<>();
for (SectionMapEntry ent : entries) {
Set<SectionMapEntry> subCol =
byTrace.computeIfAbsent(ent.getSection().getTrace(), t -> new LinkedHashSet<>());
subCol.add(ent);
}
for (Map.Entry<Trace, Set<SectionMapEntry>> ent : byTrace.entrySet()) {
Trace trace = ent.getKey();
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Add section mappings", false)) {
doAddSectionMappings(trace, ent.getValue(), monitor, truncateExisting);
tid.commit();
}
}
public void addSectionMappings(Collection<SectionMapEntry> entries, TaskMonitor monitor,
boolean truncateExisting) throws CancelledException {
addMappings(entries, monitor, truncateExisting, "Add sections mappings");
}
protected void doAddSectionMappings(Trace trace, Collection<SectionMapEntry> entries,
TaskMonitor monitor, boolean truncateExisting) throws CancelledException {
for (SectionMapEntry ent : entries) {
monitor.checkCanceled();
try {
DebuggerStaticMappingUtils.addSectionMapping(ent.getSection(), ent.getProgram(),
ent.getBlock(), truncateExisting);
}
catch (Exception e) {
Msg.error(this, "Could not add mapping " + ent + ": " + e.getMessage());
}
}
@Override
public void addRegionMappings(Collection<RegionMapEntry> entries, TaskMonitor monitor,
boolean truncateExisting) throws CancelledException {
addMappings(entries, monitor, truncateExisting, "Add regions mappings");
}
protected <T> T noTraceInfo() {
@ -1249,237 +942,85 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
return info.openMappedProgramsInView(set, Range.singleton(snap), failures);
}
protected String normalizePath(String path) {
path = path.replace('\\', FileSystem.SEPARATOR_CHAR);
while (path.startsWith(FileSystem.SEPARATOR)) {
path = path.substring(1);
protected Collection<? extends Program> orderCurrentFirst(
Collection<? extends Program> programs) {
if (programManager == null) {
return programs;
}
return path;
Program currentProgram = programManager.getCurrentProgram();
if (!programs.contains(currentProgram)) {
return programs;
}
protected DomainFile resolve(DomainFolder folder, String path) {
StringBuilder fullPath = new StringBuilder(folder.getPathname());
if (!fullPath.toString().endsWith(FileSystem.SEPARATOR)) {
// Only root should end with /, anyway
fullPath.append(FileSystem.SEPARATOR_CHAR);
}
fullPath.append(path);
return folder.getProjectData().getFile(fullPath.toString());
}
public Set<DomainFile> doFindPrograms(String modulePath, DomainFolder folder) {
// TODO: If not found, consider filenames with space + extra info
while (folder != null) {
DomainFile found = resolve(folder, modulePath);
if (found != null) {
return Set.of(found);
}
folder = folder.getParent();
}
return Set.of();
}
public Set<DomainFile> doFindProgramsByPathOrName(String modulePath, DomainFolder folder) {
Set<DomainFile> found = doFindPrograms(modulePath, folder);
if (!found.isEmpty()) {
return found;
}
int idx = modulePath.lastIndexOf(FileSystem.SEPARATOR);
if (idx == -1) {
return Set.of();
}
found = doFindPrograms(modulePath.substring(idx + 1), folder);
if (!found.isEmpty()) {
return found;
}
return Set.of();
}
public Set<DomainFile> doFindProgramsByPathOrName(String modulePath, Project project) {
return doFindProgramsByPathOrName(modulePath, project.getProjectData().getRootFolder());
Set<Program> reordered = new LinkedHashSet<>(programs.size());
reordered.add(currentProgram);
reordered.addAll(programs);
return reordered;
}
@Override
public Set<DomainFile> findProbableModulePrograms(TraceModule module) {
// TODO: Consider folders containing existing mapping destinations
DomainFile df = module.getTrace().getDomainFile();
String modulePath = normalizePath(module.getName());
if (df == null) {
return doFindProgramsByPathOrName(modulePath, tool.getProject());
}
DomainFolder parent = df.getParent();
if (parent == null) {
return doFindProgramsByPathOrName(modulePath, tool.getProject());
}
return doFindProgramsByPathOrName(modulePath, parent);
}
protected void doCollectLibraries(ProjectData project, Program cur, Set<Program> col,
TaskMonitor monitor) throws CancelledException {
if (!col.add(cur)) {
return;
}
ExternalManager externs = cur.getExternalManager();
for (String extName : externs.getExternalLibraryNames()) {
monitor.checkCanceled();
Library lib = externs.getExternalLibrary(extName);
String libPath = lib.getAssociatedProgramPath();
if (libPath == null) {
continue;
}
DomainFile libFile = project.getFile(libPath);
if (libFile == null) {
Msg.info(this, "Referenced external program not found: " + libPath);
continue;
}
try (OpenedDomainFile<Program> program =
OpenedDomainFile.open(Program.class, libFile, monitor)) {
doCollectLibraries(project, program.content, col, monitor);
}
catch (ClassCastException e) {
Msg.info(this,
"Referenced external program is not a program: " + libPath + " is " +
libFile.getDomainObjectClass());
continue;
}
catch (VersionException | CancelledException | IOException e) {
Msg.info(this, "Referenced external program could not be opened: " + e);
continue;
}
}
return DebuggerStaticMappingUtils.findProbableModulePrograms(module, tool.getProject());
}
@Override
public Set<Program> collectLibraries(Program seed, TaskMonitor monitor)
throws CancelledException {
Set<Program> result = new LinkedHashSet<>();
doCollectLibraries(seed.getDomainFile().getParent().getProjectData(), seed, result,
monitor);
return result;
public ModuleMapProposal proposeModuleMap(TraceModule module, Program program) {
return DebuggerStaticMappingProposals.proposeModuleMap(module, program);
}
@Override
public PluginModuleMapProposal proposeModuleMap(TraceModule module, Program program) {
return new PluginModuleMapProposal(module, program);
}
@Override
public PluginModuleMapProposal proposeModuleMap(TraceModule module,
public ModuleMapProposal proposeModuleMap(TraceModule module,
Collection<? extends Program> programs) {
double bestScore = -1;
PluginModuleMapProposal bestMap = null;
for (Program program : programs) {
PluginModuleMapProposal map = proposeModuleMap(module, program);
double score = map.computeScore();
if (score == bestScore && programManager != null) {
// Prefer the current program in ties
if (programManager.getCurrentProgram() == program) {
bestMap = map;
}
}
if (score > bestScore) {
bestScore = score;
bestMap = map;
}
}
return bestMap;
return DebuggerStaticMappingProposals.proposeModuleMap(module, orderCurrentFirst(programs));
}
@Override
public Map<TraceModule, ModuleMapProposal> proposeModuleMaps(
Collection<? extends TraceModule> modules, Collection<? extends Program> programs) {
Map<TraceModule, ModuleMapProposal> result = new LinkedHashMap<>();
for (TraceModule module : modules) {
String moduleName = getLastLower(module.getName());
Set<Program> probable = programs.stream()
.filter(p -> namesContain(p, moduleName))
.collect(Collectors.toSet());
PluginModuleMapProposal map = proposeModuleMap(module, probable);
if (map == null) {
continue;
}
result.put(module, map);
}
return result;
return DebuggerStaticMappingProposals.proposeModuleMaps(modules,
orderCurrentFirst(programs));
}
@Override
public PluginSectionMapProposal proposeSectionMap(TraceSection section, Program program,
public SectionMapProposal proposeSectionMap(TraceSection section, Program program,
MemoryBlock block) {
return new PluginSectionMapProposal(section, program, block);
return DebuggerStaticMappingProposals.proposeSectionMap(section, program, block);
}
@Override
public PluginSectionMapProposal proposeSectionMap(TraceModule module, Program program) {
return new PluginSectionMapProposal(module, program);
public SectionMapProposal proposeSectionMap(TraceModule module, Program program) {
return DebuggerStaticMappingProposals.proposeSectionMap(module, program);
}
@Override
public PluginSectionMapProposal proposeSectionMap(TraceModule module,
public SectionMapProposal proposeSectionMap(TraceModule module,
Collection<? extends Program> programs) {
double bestScore = -1;
PluginSectionMapProposal bestMap = null;
for (Program program : programs) {
PluginSectionMapProposal map = proposeSectionMap(module, program);
double score = map.computeScore();
if (score > bestScore) {
bestScore = score;
bestMap = map;
}
}
return bestMap;
}
protected static String getLastLower(String path) {
return new File(path).getName().toLowerCase();
}
/**
* Check if either the program's name, its executable path, or its domain file name contains the
* given module name
*
* @param program the program whose names to check
* @param moduleLowerName the module name to check for in lower case
* @return true if matched, false if not
*/
protected boolean namesContain(Program program, String moduleLowerName) {
DomainFile df = program.getDomainFile();
if (df == null || df.getProjectLocator() == null) {
return false;
}
String programName = getLastLower(program.getName());
if (programName.contains(moduleLowerName)) {
return true;
}
String exePath = program.getExecutablePath();
if (exePath != null) {
String execName = getLastLower(exePath);
if (execName.contains(moduleLowerName)) {
return true;
}
}
String fileName = df.getName().toLowerCase();
if (fileName.contains(moduleLowerName)) {
return true;
}
return false;
return DebuggerStaticMappingProposals.proposeSectionMap(module,
orderCurrentFirst(programs));
}
@Override
public Map<TraceModule, SectionMapProposal> proposeSectionMaps(
Collection<? extends TraceModule> modules, Collection<? extends Program> programs) {
Map<TraceModule, SectionMapProposal> result = new LinkedHashMap<>();
for (TraceModule module : modules) {
String moduleName = getLastLower(module.getName());
Set<Program> probable = programs.stream()
.filter(p -> namesContain(p, moduleName))
.collect(Collectors.toSet());
PluginSectionMapProposal map = proposeSectionMap(module, probable);
if (map == null) {
continue;
return DebuggerStaticMappingProposals.proposeSectionMaps(modules,
orderCurrentFirst(programs));
}
result.put(module, map);
@Override
public RegionMapProposal proposeRegionMap(TraceMemoryRegion region, Program program,
MemoryBlock block) {
return DebuggerStaticMappingProposals.proposeRegionMap(region, program, block);
}
return result;
@Override
public RegionMapProposal proposeRegionMap(Collection<? extends TraceMemoryRegion> regions,
Program program) {
return DebuggerStaticMappingProposals.proposeRegionMap(regions, program);
}
@Override
public Map<Collection<TraceMemoryRegion>, RegionMapProposal> proposeRegionMaps(
Collection<? extends TraceMemoryRegion> regions,
Collection<? extends Program> programs) {
return DebuggerStaticMappingProposals.proposeRegionMaps(regions, programs);
}
}

View file

@ -15,23 +15,30 @@
*/
package ghidra.app.plugin.core.debug.service.modules;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.*;
import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.MapEntry;
import ghidra.framework.data.OpenedDomainFile;
import ghidra.framework.model.*;
import ghidra.framework.store.FileSystem;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Library;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.ExternalManager;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.TraceLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public enum DebuggerStaticMappingUtils {
;
@ -41,6 +48,124 @@ public enum DebuggerStaticMappingUtils {
return null;
}
public static DomainFile resolve(DomainFolder folder, String path) {
StringBuilder fullPath = new StringBuilder(folder.getPathname());
if (!fullPath.toString().endsWith(FileSystem.SEPARATOR)) {
// Only root should end with /, anyway
fullPath.append(FileSystem.SEPARATOR_CHAR);
}
fullPath.append(path);
return folder.getProjectData().getFile(fullPath.toString());
}
public static Set<DomainFile> findPrograms(String modulePath, DomainFolder folder) {
// TODO: If not found, consider filenames with space + extra info
while (folder != null) {
DomainFile found = resolve(folder, modulePath);
if (found != null) {
return Set.of(found);
}
folder = folder.getParent();
}
return Set.of();
}
public static Set<DomainFile> findProgramsByPathOrName(String modulePath,
DomainFolder folder) {
Set<DomainFile> found = findPrograms(modulePath, folder);
if (!found.isEmpty()) {
return found;
}
int idx = modulePath.lastIndexOf(FileSystem.SEPARATOR);
if (idx == -1) {
return Set.of();
}
found = findPrograms(modulePath.substring(idx + 1), folder);
if (!found.isEmpty()) {
return found;
}
return Set.of();
}
public static Set<DomainFile> findProgramsByPathOrName(String modulePath, Project project) {
return findProgramsByPathOrName(modulePath, project.getProjectData().getRootFolder());
}
protected static String normalizePath(String path) {
path = path.replace('\\', FileSystem.SEPARATOR_CHAR);
while (path.startsWith(FileSystem.SEPARATOR)) {
path = path.substring(1);
}
return path;
}
public static Set<DomainFile> findProbableModulePrograms(TraceModule module, Project project) {
// TODO: Consider folders containing existing mapping destinations
DomainFile df = module.getTrace().getDomainFile();
String modulePath = normalizePath(module.getName());
if (df == null) {
return findProgramsByPathOrName(modulePath, project);
}
DomainFolder parent = df.getParent();
if (parent == null) {
return findProgramsByPathOrName(modulePath, project);
}
return findProgramsByPathOrName(modulePath, parent);
}
protected static void collectLibraries(ProjectData project, Program cur, Set<Program> col,
TaskMonitor monitor) throws CancelledException {
if (!col.add(cur)) {
return;
}
ExternalManager externs = cur.getExternalManager();
for (String extName : externs.getExternalLibraryNames()) {
monitor.checkCanceled();
Library lib = externs.getExternalLibrary(extName);
String libPath = lib.getAssociatedProgramPath();
if (libPath == null) {
continue;
}
DomainFile libFile = project.getFile(libPath);
if (libFile == null) {
Msg.info(DebuggerStaticMappingUtils.class,
"Referenced external program not found: " + libPath);
continue;
}
try (OpenedDomainFile<Program> program =
OpenedDomainFile.open(Program.class, libFile, monitor)) {
collectLibraries(project, program.content, col, monitor);
}
catch (ClassCastException e) {
Msg.info(DebuggerStaticMappingUtils.class,
"Referenced external program is not a program: " + libPath + " is " +
libFile.getDomainObjectClass());
continue;
}
catch (VersionException | CancelledException | IOException e) {
Msg.info(DebuggerStaticMappingUtils.class,
"Referenced external program could not be opened: " + e);
continue;
}
}
}
/**
* Recursively collect external programs, i.e., libraries, starting at the given seed
*
* @param seed the seed, usually the executable
* @param monitor a monitor to cancel the process
* @return the set of found programs, including the seed
* @throws CancelledException if cancelled by the monitor
*/
public static Set<Program> collectLibraries(Program seed, TaskMonitor monitor)
throws CancelledException {
Set<Program> result = new LinkedHashSet<>();
collectLibraries(seed.getDomainFile().getParent().getProjectData(), seed, result,
monitor);
return result;
}
/**
* Add a static mapping (relocation) from the given trace to the given program
*
@ -48,7 +173,6 @@ public enum DebuggerStaticMappingUtils {
* Note if the trace is backed by a Ghidra database, the caller must already have started a
* transaction on the relevant domain object.
*
*
* @param from the source trace location, including lifespan
* @param to the destination program location
* @param length the length of the mapped region
@ -57,8 +181,7 @@ public enum DebuggerStaticMappingUtils {
* {@code truncateExisting} is false.
*/
public static void addMapping(TraceLocation from, ProgramLocation to, long length,
boolean truncateExisting)
throws TraceConflictedMappingException {
boolean truncateExisting) throws TraceConflictedMappingException {
Program tp = to.getProgram();
if (tp instanceof TraceProgramView) {
throw new IllegalArgumentException(
@ -67,75 +190,99 @@ public enum DebuggerStaticMappingUtils {
TraceStaticMappingManager manager = from.getTrace().getStaticMappingManager();
URL toURL = ProgramURLUtils.getUrlFromProgram(tp);
if (toURL == null) {
noProject(DebuggerStaticMappingService.class);
noProject(DebuggerStaticMappingUtils.class);
}
try {
Address start = from.getAddress();
Address end = start.addNoWrap(length - 1);
Address fromAddress = from.getAddress();
Address toAddress = to.getByteAddress();
long maxFromLengthMinus1 =
fromAddress.getAddressSpace().getMaxAddress().subtract(fromAddress);
long maxToLengthMinus1 =
toAddress.getAddressSpace().getMaxAddress().subtract(toAddress);
if (Long.compareUnsigned(length - 1, maxFromLengthMinus1) > 0) {
throw new IllegalArgumentException("Length would cause address overflow in trace");
}
if (Long.compareUnsigned(length - 1, maxToLengthMinus1) > 0) {
throw new IllegalArgumentException("Length would cause address overflow in program");
}
Address end = fromAddress.addWrap(length - 1);
// Also check end in the destination
Address toAddress = to.getAddress();
toAddress.addNoWrap(length - 1); // Anticipate possible AddressOverflow
AddressRangeImpl range = new AddressRangeImpl(start, end);
AddressRangeImpl range = new AddressRangeImpl(fromAddress, end);
Range<Long> fromLifespan = from.getLifespan();
if (truncateExisting) {
long truncEnd = DBTraceUtils.lowerEndpoint(from.getLifespan()) - 1;
long truncEnd = DBTraceUtils.lowerEndpoint(fromLifespan) - 1;
for (TraceStaticMapping existing : List
.copyOf(manager.findAllOverlapping(range, from.getLifespan()))) {
.copyOf(manager.findAllOverlapping(range, fromLifespan))) {
existing.delete();
if (Long.compareUnsigned(existing.getStartSnap(), truncEnd) < 0) {
if (fromLifespan.hasLowerBound() &&
Long.compare(existing.getStartSnap(), truncEnd) <= 0) {
manager.add(existing.getTraceAddressRange(),
Range.closed(existing.getStartSnap(), truncEnd),
existing.getStaticProgramURL(), existing.getStaticAddress());
}
}
}
manager.add(range, from.getLifespan(), toURL,
toAddress.toString(true));
}
catch (AddressOverflowException e) {
throw new IllegalArgumentException("Length would cause address overflow", e);
}
manager.add(range, fromLifespan, toURL, toAddress.toString(true));
}
/**
* Add a static mapping (relocation) from the given module to the given program
*
* <p>
* This is simply a shortcut and does not mean to imply that all mappings must represent module
* relocations. The lifespan is that of the module's.
*
* @param from the source module
* @param length the "size" of the module -- {@code max-min+1} as loaded/mapped in memory
* @param toProgram the destination program
* @see #addMapping(TraceLocation, ProgramLocation, long, boolean)
*/
public static void addModuleMapping(TraceModule from, long length, Program toProgram,
boolean truncateExisting) throws TraceConflictedMappingException {
TraceLocation fromLoc =
new DefaultTraceLocation(from.getTrace(), null, from.getLifespan(), from.getBase());
ProgramLocation toLoc = new ProgramLocation(toProgram, toProgram.getImageBase());
public static void addMapping(MapEntry<?, ?> entry, boolean truncateExisting)
throws TraceConflictedMappingException {
TraceLocation fromLoc = entry.getFromTraceLocation();
ProgramLocation toLoc = entry.getToProgramLocation();
long length = entry.getMappingLength();
addMapping(fromLoc, toLoc, length, truncateExisting);
}
/**
* Add a static mapping (relocation) from the given section to the given program memory block
*
* <p>
* This is simply a shortcut and does not mean to imply that all mappings must represent section
* relocations. In most cases the lengths of the from and to objects match exactly, but this may
* not be the case. Whatever the case, the minimum length is computed, and the start addresses
* are used as the location. The lifespan is that of the section's containing module.
*
* @param from the source section
* @param toProgram the destination program
* @param to the destination memory block
* @see #addMapping(TraceLocation, ProgramLocation, long, boolean)
*/
public static void addSectionMapping(TraceSection from, Program toProgram, MemoryBlock to,
boolean truncateExisting) throws TraceConflictedMappingException {
TraceLocation fromLoc = new DefaultTraceLocation(from.getTrace(), null,
from.getModule().getLifespan(), from.getStart());
ProgramLocation toLoc = new ProgramLocation(toProgram, to.getStart());
long length = Math.min(from.getRange().getLength(), to.getSize());
addMapping(fromLoc, toLoc, length, truncateExisting);
public static void addIdentityMapping(Trace from, Program toProgram, Range<Long> lifespan,
boolean truncateExisting) {
Map<String, Address> mins = new HashMap<>();
Map<String, Address> maxs = new HashMap<>();
for (AddressRange range : toProgram.getMemory().getAddressRanges()) {
mins.compute(range.getAddressSpace().getName(), (n, min) -> {
Address can = range.getMinAddress();
if (min == null || can.compareTo(min) < 0) {
return can;
}
return min;
});
maxs.compute(range.getAddressSpace().getName(), (n, max) -> {
Address can = range.getMaxAddress();
if (max == null || can.compareTo(max) > 0) {
return can;
}
return max;
});
}
for (String name : mins.keySet()) {
AddressRange range = clippedRange(from, name, mins.get(name).getOffset(),
maxs.get(name).getOffset());
if (range == null) {
continue;
}
try {
addMapping(new DefaultTraceLocation(from, null, lifespan, range.getMinAddress()),
new ProgramLocation(toProgram, mins.get(name)), range.getLength(),
truncateExisting);
}
catch (TraceConflictedMappingException e) {
Msg.error(DebuggerStaticMappingUtils.class,
"Could not add identity mapping " + range + ": " + e.getMessage());
}
}
}
protected static AddressRange clippedRange(Trace trace, String spaceName, long min,
long max) {
AddressSpace space = trace.getBaseAddressFactory().getAddressSpace(spaceName);
if (space == null) {
return null;
}
Address spaceMax = space.getMaxAddress();
if (Long.compareUnsigned(min, spaceMax.getOffset()) > 0) {
return null;
}
if (Long.compareUnsigned(max, spaceMax.getOffset()) > 0) {
return new AddressRangeImpl(space.getAddress(min), spaceMax);
}
return new AddressRangeImpl(space.getAddress(min), space.getAddress(max));
}
}

View file

@ -0,0 +1,233 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.modules;
import java.util.*;
import com.google.common.collect.Range;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.ModuleMapProposal;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceModule;
public class DefaultModuleMapProposal
extends AbstractMapProposal<TraceModule, Program, ModuleMapEntry>
implements ModuleMapProposal {
/**
* A module-program entry in a proposed module map
*/
public static class DefaultModuleMapEntry extends AbstractMapEntry<TraceModule, Program>
implements ModuleMapEntry {
/**
* Check if a block should be included in size computations or analyzed for proposals
*
* @param program the program containing the block
* @param block the block
* @return true if included, false otherwise
*/
public static boolean includeBlock(Program program, MemoryBlock block) {
if (program.getImageBase().getAddressSpace() != block.getStart().getAddressSpace()) {
return false;
}
if (!block.isLoaded()) {
return false;
}
if (block.isMapped()) {
// TODO: Determine how to handle these.
return false;
}
if (MemoryBlock.EXTERNAL_BLOCK_NAME.equals(block.getName())) {
return false;
}
return true;
}
/**
* Compute the "size" of an image
*
* <p>
* This is considered the maximum loaded address as mapped in memory, minus the image base.
*
* @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;
// 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);
}
return imageSize;
}
protected AddressRange moduleRange;
/**
* Construct a module map entry
*
* <p>
* Generally, only the service implementation should construct an entry. See
* {@link DebuggerStaticMappingService#proposeModuleMap(TraceModule, Program)} and related
* to obtain these.
*
* @param module the module
* @param program the matched program
* @param moduleRange a range from the module base the size of the program's image
*/
protected DefaultModuleMapEntry(TraceModule module, Program program,
AddressRange moduleRange) {
super(module.getTrace(), module, program, program);
this.moduleRange = moduleRange;
}
@Override
public TraceModule getModule() {
return getFromObject();
}
@Override
public Range<Long> getFromLifespan() {
return getModule().getLifespan();
}
@Override
public AddressRange getFromRange() {
return moduleRange;
}
@Override
public AddressRange getModuleRange() {
return moduleRange;
}
@Override
public void setProgram(Program program) {
setToObject(program, program);
try {
this.moduleRange =
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");
}
}
@Override
public AddressRange getToRange() {
try {
return new AddressRangeImpl(getToProgram().getImageBase(), moduleRange.getLength());
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
}
protected final TraceModule module;
// indexed by region's offset from module base
protected final NavigableMap<Long, ModuleRegionMatcher> 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 DefaultModuleMapProposal(TraceModule module, Program program) {
super(module.getTrace(), program);
this.module = module;
processProgram();
processModule();
}
@Override
public TraceModule getModule() {
return module;
}
private ModuleRegionMatcher getMatcher(long baseOffset) {
return matchers.computeIfAbsent(baseOffset, ModuleRegionMatcher::new);
}
private void processProgram() {
imageBase = program.getImageBase();
imageSize = DefaultModuleMapEntry.computeImageSize(program);
// TODO: How to handle Harvard architectures?
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (!DefaultModuleMapEntry.includeBlock(program, block)) {
continue;
}
getMatcher(block.getStart().subtract(imageBase)).block = block;
}
}
/**
* Must be called after processProgram, so that image size is known
*/
private void processModule() {
moduleBase = module.getBase();
try {
moduleRange = new AddressRangeImpl(moduleBase, imageSize);
}
catch (AddressOverflowException e) {
return; // Just score it as having no matches?
}
for (TraceMemoryRegion region : module.getTrace()
.getMemoryManager()
.getRegionsIntersecting(module.getLifespan(), moduleRange)) {
getMatcher(region.getMinAddress().subtract(moduleBase)).region = region;
}
}
/**
* {@inheritDoc}
*
* @implNote some information to consider: length and case of matched image and module names,
* alignment of program memory blocks to trace memory regions, etc.
*/
@Override
public double computeScore() {
return ((double) matchers.values()
.stream()
.reduce(0, (s, m) -> s + m.score(), Integer::sum)) /
matchers.size();
}
@Override
public Map<TraceModule, ModuleMapEntry> computeMap() {
return Map.of(module, new DefaultModuleMapEntry(module, program, moduleRange));
}
@Override
public Program getToObject(TraceModule from) {
if (from != module) {
return null;
}
return program;
}
}

View file

@ -0,0 +1,192 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.modules;
import java.util.*;
import java.util.stream.Collectors;
import com.google.common.collect.Range;
import ghidra.app.services.RegionMapProposal;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegion;
public class DefaultRegionMapProposal
extends AbstractMapProposal<TraceMemoryRegion, MemoryBlock, RegionMapEntry>
implements RegionMapProposal {
public static class DefaultRegionMapEntry
extends AbstractMapEntry<TraceMemoryRegion, MemoryBlock>
implements RegionMapEntry {
public DefaultRegionMapEntry(TraceMemoryRegion region,
Program program, MemoryBlock block) {
super(region.getTrace(), region, program, block);
}
@Override
public TraceMemoryRegion getRegion() {
return getFromObject();
}
@Override
public AddressRange getFromRange() {
return getRegion().getRange();
}
@Override
public Range<Long> getFromLifespan() {
return getRegion().getLifespan();
}
@Override
public MemoryBlock getBlock() {
return getToObject();
}
@Override
public AddressRange getToRange() {
return new AddressRangeImpl(getBlock().getStart(), getBlock().getEnd());
}
@Override
public void setBlock(Program program, MemoryBlock block) {
setToObject(program, block);
}
}
protected class RegionMatcher extends Matcher<TraceMemoryRegion, MemoryBlock> {
public RegionMatcher(TraceMemoryRegion region, MemoryBlock block) {
super(region, block);
}
@Override
protected AddressRange getFromRange() {
return fromObject == null ? null : fromObject.getRange();
}
@Override
protected AddressRange getToRange() {
return toObject == null ? null
: new AddressRangeImpl(toObject.getStart(), toObject.getEnd());
}
@Override
protected double computeScore() {
return computeLengthScore() + computeOffsetScore();
}
protected int computeOffsetScore() {
long fOff = fromRange.getMinAddress().subtract(fromBase);
long tOff = toRange.getMinAddress().subtract(toBase);
if (fOff == tOff) {
return 10;
}
return 0;
}
}
protected class RegionMatcherMap
extends MatcherMap<Void, TraceMemoryRegion, MemoryBlock, RegionMatcher> {
@Override
protected RegionMatcher newMatcher(TraceMemoryRegion region, MemoryBlock block) {
return new RegionMatcher(region, block);
}
@Override
protected Void getFromJoinKey(TraceMemoryRegion region) {
return null;
}
@Override
protected Void getToJoinKey(MemoryBlock block) {
return null;
}
}
protected static Trace getTrace(Collection<? extends TraceMemoryRegion> regions) {
if (regions == null || regions.isEmpty()) {
return null;
}
return regions.iterator().next().getTrace();
}
protected final List<TraceMemoryRegion> regions;
protected final Address fromBase;
protected final Address toBase;
protected final RegionMatcherMap matchers = new RegionMatcherMap();
protected DefaultRegionMapProposal(Collection<? extends TraceMemoryRegion> regions,
Program program) {
super(getTrace(regions), program);
this.regions = Collections.unmodifiableList(regions.stream()
.sorted(Comparator.comparing(r -> r.getMinAddress()))
.collect(Collectors.toList()));
this.fromBase = computeFromBase();
this.toBase = program.getImageBase();
processRegions();
processProgram();
}
protected DefaultRegionMapProposal(TraceMemoryRegion region, Program program,
MemoryBlock block) {
super(region.getTrace(), program);
this.regions = List.of(region);
this.fromBase = region.getMinAddress();
this.toBase = program.getImageBase();
processRegions();
matchers.processToObject(block);
}
protected Address computeFromBase() {
if (regions.isEmpty()) {
return null;
}
return regions.get(0).getMinAddress();
}
private void processRegions() {
for (TraceMemoryRegion region : regions) {
matchers.processFromObject(region);
}
}
private void processProgram() {
for (MemoryBlock block : program.getMemory().getBlocks()) {
matchers.processToObject(block);
}
}
@Override
public double computeScore() {
return matchers.averageScore();
}
@Override
public Map<TraceMemoryRegion, RegionMapEntry> computeMap() {
return matchers
.computeMap(m -> new DefaultRegionMapEntry(m.fromObject, program, m.toObject));
}
@Override
public MemoryBlock getToObject(TraceMemoryRegion from) {
return matchers.getToObject(from);
}
}

View file

@ -0,0 +1,178 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.modules;
import java.util.Map;
import com.google.common.collect.Range;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.SectionMapProposal;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
public class DefaultSectionMapProposal
extends AbstractMapProposal<TraceSection, MemoryBlock, SectionMapEntry>
implements SectionMapProposal {
/**
* A section-block entry in a proposed section map
*/
public static class DefaultSectionMapEntry extends AbstractMapEntry<TraceSection, MemoryBlock>
implements SectionMapEntry {
/**
* Construct a section map entry
*
* <p>
* Generally, only the service implementation should construct an entry. See
* {@link DebuggerStaticMappingService#proposeSectionMap(TraceSection, Program, MemoryBlock)}
* and related to obtain these.
*
* @param section the section
* @param program the program containing the matched block
* @param block the matched memory block
*/
protected DefaultSectionMapEntry(TraceSection section, Program program, MemoryBlock block) {
super(section.getTrace(), section, program, block);
}
@Override
public TraceModule getModule() {
return getFromObject().getModule();
}
@Override
public TraceSection getSection() {
return getFromObject();
}
@Override
public Range<Long> getFromLifespan() {
return getModule().getLifespan();
}
@Override
public AddressRange getFromRange() {
return getSection().getRange();
}
@Override
public MemoryBlock getBlock() {
return getToObject();
}
@Override
public AddressRange getToRange() {
return new AddressRangeImpl(getBlock().getStart(), getBlock().getEnd());
}
@Override
public void setBlock(Program program, MemoryBlock block) {
setToObject(program, block);
}
}
protected static class SectionMatcher extends Matcher<TraceSection, MemoryBlock> {
public SectionMatcher(TraceSection section, MemoryBlock block) {
super(section, block);
}
@Override
protected AddressRange getFromRange() {
return fromObject == null ? null : fromObject.getRange();
}
@Override
protected AddressRange getToRange() {
return toObject == null ? null
: new AddressRangeImpl(toObject.getStart(), toObject.getEnd());
}
}
protected static class SectionMatcherMap
extends MatcherMap<String, TraceSection, MemoryBlock, SectionMatcher> {
@Override
protected SectionMatcher newMatcher(TraceSection section, MemoryBlock block) {
return new SectionMatcher(section, block);
}
@Override
protected String getFromJoinKey(TraceSection section) {
return section.getName();
}
@Override
protected String getToJoinKey(MemoryBlock block) {
return block.getName();
}
}
protected final TraceModule module;
protected final SectionMatcherMap matchers = new SectionMatcherMap();
protected DefaultSectionMapProposal(TraceModule module, Program program) {
super(module.getTrace(), program);
this.module = module;
processModule();
processProgram();
}
protected DefaultSectionMapProposal(TraceSection section, Program program, MemoryBlock block) {
super(section.getTrace(), program);
this.module = section.getModule();
matchers.processFromObject(section);
matchers.processToObject(block);
}
@Override
public TraceModule getModule() {
return module;
}
private void processModule() {
for (TraceSection section : module.getSections()) {
matchers.processFromObject(section);
}
}
private void processProgram() {
for (MemoryBlock block : program.getMemory().getBlocks()) {
matchers.processToObject(block);
}
}
@Override
public double computeScore() {
return matchers.averageScore();
}
@Override
public Map<TraceSection, SectionMapEntry> computeMap() {
return matchers
.computeMap(m -> new DefaultSectionMapEntry(m.fromObject, program, m.toObject));
}
@Override
public MemoryBlock getToObject(TraceSection from) {
return matchers.getToObject(from);
}
}

View file

@ -18,7 +18,7 @@ package ghidra.app.plugin.core.debug.service.modules;
import java.util.Collection;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapEntry;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject;
import ghidra.util.exception.CancelledException;

View file

@ -0,0 +1,48 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.modules;
import java.util.Collection;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class MapRegionsBackgroundCommand extends BackgroundCommand {
private final DebuggerStaticMappingService service;
private final Collection<RegionMapEntry> entries;
public MapRegionsBackgroundCommand(DebuggerStaticMappingService service,
Collection<RegionMapEntry> entries) {
super("Map regions", true, true, true);
this.service = service;
this.entries = entries;
}
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
try {
service.addRegionMappings(entries, monitor, true);
}
catch (CancelledException e) {
return false;
}
return true;
}
}

View file

@ -18,7 +18,7 @@ package ghidra.app.plugin.core.debug.service.modules;
import java.util.Collection;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.SectionMapEntry;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject;
import ghidra.util.exception.CancelledException;

View file

@ -0,0 +1,38 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.modules;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.memory.TraceMemoryRegion;
class ModuleRegionMatcher {
MemoryBlock block;
TraceMemoryRegion region;
public ModuleRegionMatcher(long baseOffset) {
}
int score() {
if (block == null || region == null) {
return 0; // Unmatched
}
int score = 3; // For the matching offset
if (block.getSize() == region.getLength()) {
score += 10;
}
return score;
}
}

View file

@ -0,0 +1,180 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.workflow;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.service.workflow.*;
import ghidra.app.services.*;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.util.TraceChangeType;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public abstract class AbstractMapDebuggerBot implements DebuggerBot {
protected class ForChangesTraceListener extends AbstractMultiToolTraceListener {
public ForChangesTraceListener(Trace trace) {
super(trace);
for (TraceChangeType<?, ?> type : getChangeTypes()) {
listenFor(type, this::changed);
}
}
private void changed() {
queueTrace(trace);
}
}
protected abstract Collection<TraceChangeType<?, ?>> getChangeTypes();
private DebuggerWorkflowServicePlugin plugin;
private final MultiToolTraceListenerManager<ForChangesTraceListener> listeners =
new MultiToolTraceListenerManager<>(ForChangesTraceListener::new);
private final Set<Trace> traceQueue = new HashSet<>();
// Debounce to ensure we don't get too eager if manager is still opening stuff
private final AsyncDebouncer<Void> debouncer =
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 500);
{
debouncer.addListener(this::queueSettled);
}
@Override
public void enable(DebuggerWorkflowServicePlugin wp) {
this.plugin = wp;
listeners.enable(wp);
for (PluginTool t : plugin.getProxyingPluginTools()) {
DebuggerTraceManagerService traceManager =
t.getService(DebuggerTraceManagerService.class);
if (traceManager == null) {
continue;
}
queueTraces(traceManager.getOpenTraces());
}
}
@Override
public void disable() {
plugin = null;
listeners.disable();
}
@Override
public boolean isEnabled() {
return plugin != null;
}
@Override
public void traceOpened(PluginTool tool, Trace trace) {
listeners.traceOpened(tool, trace);
queueTrace(trace);
}
@Override
public void traceClosed(PluginTool tool, Trace trace) {
listeners.traceClosed(tool, trace);
}
@Override
public void programOpened(PluginTool t, Program program) {
DebuggerTraceManagerService traceManager = t.getService(DebuggerTraceManagerService.class);
if (traceManager == null) {
return;
}
queueTraces(traceManager.getOpenTraces());
}
private void queueTrace(Trace trace) {
synchronized (traceQueue) {
traceQueue.add(trace);
}
debouncer.contact(null);
}
private void queueTraces(Collection<Trace> traces) {
synchronized (traceQueue) {
traceQueue.addAll(traces);
}
debouncer.contact(null);
}
private void queueSettled(Void __) {
Set<Trace> traces;
synchronized (traceQueue) {
traces = Set.copyOf(traceQueue);
traceQueue.clear();
}
Map<Trace, Pair<PluginTool, Set<Program>>> toAnalyze = new HashMap<>();
for (Trace trace : traces) {
for (PluginTool tool : plugin.getProxyingPluginTools()) {
DebuggerTraceManagerService traceManager =
tool.getService(DebuggerTraceManagerService.class);
if (traceManager == null) {
continue;
}
ProgramManager programManager = tool.getService(ProgramManager.class);
if (programManager == null) {
continue;
}
if (!traceManager.getOpenTraces().contains(trace)) {
continue;
}
Pair<PluginTool, Set<Program>> programs =
toAnalyze.computeIfAbsent(trace, t -> Pair.of(tool, new HashSet<>()));
programs.getRight().addAll(List.of(programManager.getAllOpenPrograms()));
}
}
for (Map.Entry<Trace, Pair<PluginTool, Set<Program>>> ent : toAnalyze.entrySet()) {
PluginTool tool = ent.getValue().getLeft();
Trace trace = ent.getKey();
Set<Program> programs = ent.getValue().getRight();
analyzeTrace(tool, trace, programs);
}
}
private void analyzeTrace(PluginTool tool, Trace trace, Set<Program> programs) {
BackgroundCommand cmd = new BackgroundCommand(getDescription(), true, true, false) {
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
try {
doAnalysis(tool, trace, programs, monitor);
return true;
}
catch (CancelledException e) {
return false;
}
}
};
tool.executeBackgroundCommand(cmd, trace);
}
protected abstract void doAnalysis(PluginTool tool, Trace trace, Set<Program> programs,
TaskMonitor monitor) throws CancelledException;
}

View file

@ -17,24 +17,15 @@ package ghidra.app.plugin.core.debug.workflow;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.service.workflow.*;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapEntry;
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapProposal;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.framework.options.annotation.HelpInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
import ghidra.trace.model.Trace.TraceModuleChangeType;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.util.TraceChangeType;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -44,174 +35,25 @@ import ghidra.util.task.TaskMonitor;
help = @HelpInfo(anchor = "map_modules"), //
enabledByDefault = true //
)
public class MapModulesDebuggerBot implements DebuggerBot {
protected class ForMapNewModulesTraceListener extends AbstractMultiToolTraceListener {
public class MapModulesDebuggerBot extends AbstractMapDebuggerBot {
public ForMapNewModulesTraceListener(Trace trace) {
super(trace);
/**
* NB. Not reacting to LIFESPAN_CHANGED. Once something else is added or changed, we can
* knock collisions out of the way.
*/
listenFor(TraceModuleChangeType.ADDED, this::moduleAdded);
listenFor(TraceModuleChangeType.CHANGED, this::moduleChanged);
listenFor(TraceMemoryRegionChangeType.ADDED, this::regionAdded);
listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged);
}
private void moduleAdded(TraceModule module) {
queueTrace(trace);
}
private void moduleChanged(TraceModule module) {
queueTrace(trace);
}
private void regionAdded(TraceMemoryRegion region) {
queueTrace(trace);
}
private void regionChanged(TraceMemoryRegion region) {
queueTrace(trace);
}
}
private DebuggerWorkflowServicePlugin plugin;
private final MultiToolTraceListenerManager<ForMapNewModulesTraceListener> listeners =
new MultiToolTraceListenerManager<>(ForMapNewModulesTraceListener::new);
private final Set<Trace> traceQueue = new HashSet<>();
// Debounce to ensure we don't get too eager if manager is still opening stuff
private final AsyncDebouncer<Void> debouncer =
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 500);
{
debouncer.addListener(this::queueSettled);
@Override
protected Collection<TraceChangeType<?, ?>> getChangeTypes() {
return List.of(TraceModuleChangeType.ADDED, TraceModuleChangeType.CHANGED,
TraceMemoryRegionChangeType.ADDED, TraceMemoryRegionChangeType.CHANGED);
}
@Override
public void enable(DebuggerWorkflowServicePlugin wp) {
this.plugin = wp;
listeners.enable(wp);
for (PluginTool t : plugin.getProxyingPluginTools()) {
DebuggerTraceManagerService traceManager =
t.getService(DebuggerTraceManagerService.class);
if (traceManager == null) {
continue;
}
queueTraces(traceManager.getOpenTraces());
}
}
@Override
public void disable() {
plugin = null;
listeners.disable();
}
@Override
public boolean isEnabled() {
return plugin != null;
}
@Override
public void traceOpened(PluginTool tool, Trace trace) {
listeners.traceOpened(tool, trace);
queueTrace(trace);
}
@Override
public void traceClosed(PluginTool tool, Trace trace) {
listeners.traceClosed(tool, trace);
}
@Override
public void programOpened(PluginTool t, Program program) {
DebuggerTraceManagerService traceManager = t.getService(DebuggerTraceManagerService.class);
if (traceManager == null) {
return;
}
queueTraces(traceManager.getOpenTraces());
}
private void queueTrace(Trace trace) {
synchronized (traceQueue) {
traceQueue.add(trace);
}
debouncer.contact(null);
}
private void queueTraces(Collection<Trace> traces) {
synchronized (traceQueue) {
traceQueue.addAll(traces);
}
debouncer.contact(null);
}
private void queueSettled(Void __) {
Set<Trace> traces;
synchronized (traceQueue) {
traces = Set.copyOf(traceQueue);
traceQueue.clear();
}
Map<Trace, Pair<PluginTool, Set<Program>>> toAnalyze = new HashMap<>();
for (Trace trace : traces) {
for (PluginTool tool : plugin.getProxyingPluginTools()) {
DebuggerTraceManagerService traceManager =
tool.getService(DebuggerTraceManagerService.class);
if (traceManager == null) {
continue;
}
ProgramManager programManager = tool.getService(ProgramManager.class);
if (programManager == null) {
continue;
}
if (!traceManager.getOpenTraces().contains(trace)) {
continue;
}
Pair<PluginTool, Set<Program>> programs =
toAnalyze.computeIfAbsent(trace, t -> Pair.of(tool, new HashSet<>()));
programs.getRight().addAll(List.of(programManager.getAllOpenPrograms()));
}
}
for (Map.Entry<Trace, Pair<PluginTool, Set<Program>>> ent : toAnalyze.entrySet()) {
PluginTool tool = ent.getValue().getLeft();
Trace trace = ent.getKey();
Set<Program> programs = ent.getValue().getRight();
analyzeTrace(tool, trace, programs);
}
}
private void analyzeTrace(PluginTool t, Trace trace, Set<Program> programs) {
BackgroundCommand cmd = new BackgroundCommand("Auto-map modules", true, true, false) {
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
try {
protected void doAnalysis(PluginTool tool, Trace trace, Set<Program> programs,
TaskMonitor monitor) throws CancelledException {
DebuggerStaticMappingService mappingService =
t.getService(DebuggerStaticMappingService.class);
tool.getService(DebuggerStaticMappingService.class);
if (mappingService != null) {
Map<TraceModule, ModuleMapProposal> maps =
mappingService.proposeModuleMaps(
trace.getModuleManager().getAllModules(),
programs);
Collection<ModuleMapEntry> entries =
ModuleMapProposal.flatten(maps.values());
entries = ModuleMapProposal.removeOverlapping(entries);
Map<?, ModuleMapProposal> maps = mappingService
.proposeModuleMaps(trace.getModuleManager().getAllModules(), programs);
Collection<ModuleMapEntry> entries = MapProposal.flatten(maps.values());
entries = MapProposal.removeOverlapping(entries);
mappingService.addModuleMappings(entries, monitor, false);
}
return true;
}
catch (CancelledException e) {
return false;
}
}
};
t.executeBackgroundCommand(cmd, trace);
}
}

View file

@ -0,0 +1,57 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.workflow;
import java.util.*;
import ghidra.app.services.*;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.framework.options.annotation.HelpInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
import ghidra.trace.util.TraceChangeType;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@DebuggerBotInfo( //
description = "Map regions to open programs", //
details = "Monitors open traces and programs, attempting to map regions by \"best\" match.", //
help = @HelpInfo(anchor = "map_regions"), //
enabledByDefault = false //
)
public class MapRegionsDebuggerBot extends AbstractMapDebuggerBot {
@Override
protected Collection<TraceChangeType<?, ?>> getChangeTypes() {
return List.of(TraceMemoryRegionChangeType.ADDED);
}
@Override
protected void doAnalysis(PluginTool tool, Trace trace, Set<Program> programs,
TaskMonitor monitor) throws CancelledException {
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
if (mappingService != null) {
Map<?, RegionMapProposal> maps = mappingService
.proposeRegionMaps(trace.getMemoryManager().getAllRegions(), programs);
Collection<RegionMapEntry> entries = MapProposal.flatten(maps.values());
entries = MapProposal.removeOverlapping(entries);
mappingService.addRegionMappings(entries, monitor, false);
}
}
}

View file

@ -17,24 +17,14 @@ package ghidra.app.plugin.core.debug.workflow;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.service.workflow.*;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStaticMappingService.SectionMapEntry;
import ghidra.app.services.DebuggerStaticMappingService.SectionMapProposal;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.framework.options.annotation.HelpInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSectionChangeType;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.trace.util.TraceChangeType;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -44,150 +34,24 @@ import ghidra.util.task.TaskMonitor;
help = @HelpInfo(anchor = "map_sections"), //
enabledByDefault = false //
)
public class MapSectionsDebuggerBot implements DebuggerBot {
public class MapSectionsDebuggerBot extends AbstractMapDebuggerBot {
protected class ForMapNewSectionsTraceListener extends AbstractMultiToolTraceListener {
public ForMapNewSectionsTraceListener(Trace trace) {
super(trace);
listenFor(TraceSectionChangeType.ADDED, this::sectionAdded);
}
private void sectionAdded(TraceAddressSpace space, TraceSection section) {
queueTrace(trace);
}
}
private DebuggerWorkflowServicePlugin plugin;
private final MultiToolTraceListenerManager<ForMapNewSectionsTraceListener> listeners =
new MultiToolTraceListenerManager<>(ForMapNewSectionsTraceListener::new);
private final Set<Trace> traceQueue = new HashSet<>();
// Debounce to ensure we don't get too eager if manager is still opening stuff
private final AsyncDebouncer<Void> debouncer =
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 500);
{
debouncer.addListener(this::queueSettled);
@Override
protected Collection<TraceChangeType<?, ?>> getChangeTypes() {
return List.of(TraceSectionChangeType.ADDED);
}
@Override
public void enable(DebuggerWorkflowServicePlugin wp) {
this.plugin = wp;
listeners.enable(wp);
for (PluginTool t : plugin.getProxyingPluginTools()) {
DebuggerTraceManagerService traceManager =
t.getService(DebuggerTraceManagerService.class);
if (traceManager == null) {
continue;
}
queueTraces(traceManager.getOpenTraces());
}
}
@Override
public void disable() {
plugin = null;
listeners.disable();
}
@Override
public boolean isEnabled() {
return plugin != null;
}
@Override
public void traceOpened(PluginTool tool, Trace trace) {
listeners.traceOpened(tool, trace);
queueTrace(trace);
}
@Override
public void traceClosed(PluginTool tool, Trace trace) {
listeners.traceClosed(tool, trace);
}
@Override
public void programOpened(PluginTool t, Program program) {
DebuggerTraceManagerService traceManager = t.getService(DebuggerTraceManagerService.class);
if (traceManager == null) {
return;
}
queueTraces(traceManager.getOpenTraces());
}
private void queueTrace(Trace trace) {
synchronized (traceQueue) {
traceQueue.add(trace);
}
debouncer.contact(null);
}
private void queueTraces(Collection<Trace> traces) {
synchronized (traceQueue) {
traceQueue.addAll(traces);
}
debouncer.contact(null);
}
private void queueSettled(Void __) {
Set<Trace> traces;
synchronized (traceQueue) {
traces = Set.copyOf(traceQueue);
traceQueue.clear();
}
Map<Trace, Pair<PluginTool, Set<Program>>> toAnalyze = new HashMap<>();
for (Trace trace : traces) {
for (PluginTool tool : plugin.getProxyingPluginTools()) {
DebuggerTraceManagerService traceManager =
tool.getService(DebuggerTraceManagerService.class);
if (traceManager == null) {
continue;
}
ProgramManager programManager = tool.getService(ProgramManager.class);
if (programManager == null) {
continue;
}
if (!traceManager.getOpenTraces().contains(trace)) {
continue;
}
Pair<PluginTool, Set<Program>> programs =
toAnalyze.computeIfAbsent(trace, t -> Pair.of(tool, new HashSet<>()));
programs.getRight().addAll(List.of(programManager.getAllOpenPrograms()));
}
}
for (Map.Entry<Trace, Pair<PluginTool, Set<Program>>> ent : toAnalyze.entrySet()) {
PluginTool tool = ent.getValue().getLeft();
Trace trace = ent.getKey();
Set<Program> programs = ent.getValue().getRight();
analyzeTrace(tool, trace, programs);
}
}
private void analyzeTrace(PluginTool t, Trace trace, Set<Program> programs) {
BackgroundCommand cmd = new BackgroundCommand("Auto-map sections", true, true, false) {
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
try {
protected void doAnalysis(PluginTool tool, Trace trace, Set<Program> programs,
TaskMonitor monitor) throws CancelledException {
DebuggerStaticMappingService mappingService =
t.getService(DebuggerStaticMappingService.class);
Map<TraceModule, SectionMapProposal> maps =
mappingService.proposeSectionMaps(trace.getModuleManager().getAllModules(),
programs);
Collection<SectionMapEntry> entries = SectionMapProposal.flatten(maps.values());
entries = SectionMapProposal.removeOverlapping(entries);
tool.getService(DebuggerStaticMappingService.class);
if (mappingService != null) {
Map<?, SectionMapProposal> maps = mappingService
.proposeSectionMaps(trace.getModuleManager().getAllModules(), programs);
Collection<SectionMapEntry> entries = MapProposal.flatten(maps.values());
entries = MapProposal.removeOverlapping(entries);
mappingService.addSectionMappings(entries, monitor, false);
return true;
}
catch (CancelledException e) {
return false;
}
}
};
t.executeBackgroundCommand(cmd, trace);
}
}

View file

@ -16,19 +16,21 @@
package ghidra.app.services;
import java.util.*;
import java.util.stream.Collectors;
import com.google.common.collect.Range;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.MathUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -49,445 +51,7 @@ import ghidra.util.task.TaskMonitor;
public interface DebuggerStaticMappingService {
/**
* A proposed mapping of module to program
*/
public interface ModuleMapProposal {
/**
* Flatten proposals into a single collection of entries
*
* <p>
* The output is suitable for use in
* {@link DebuggerStaticMappingService#addModuleMappings(Collection, TaskMonitor, boolean)}.
* In some contexts, the user should be permitted to see and optionally adjust the
* collection first.
*
* <p>
* Note, a suitable parameter to this method is derived by invoking {@link Map#values()} on
* the result of
* {@link DebuggerStaticMappingService#proposeModuleMaps(Collection, Collection)}.
*
* <p>
* Note, it is advisable to filter the returned collection using
* {@link DebuggerStaticMappingService#removeOverlappingModuleEntries(Collection)} to avoid
* errors from adding overlapped mappings. Alternatively, you can set
* {@code truncateExisting} to true when calling
* {@link DebuggerStaticMappingService#addModuleMappings(Collection, TaskMonitor, boolean)}.
*
* @param proposals the collection of proposed maps
* @return the flattened, filtered collection
*/
static Collection<ModuleMapEntry> flatten(Collection<ModuleMapProposal> proposals) {
Collection<ModuleMapEntry> result = new LinkedHashSet<>();
for (ModuleMapProposal map : proposals) {
result.addAll(map.computeMap().values());
}
return result;
}
/**
* Remove entries from a collection which overlap existing entries in the trace
*
* @param entries the entries to filter
* @return the filtered entries
*/
public static Set<ModuleMapEntry> removeOverlapping(Collection<ModuleMapEntry> entries) {
return entries.stream().filter(e -> {
TraceStaticMappingManager manager = e.module.getTrace().getStaticMappingManager();
return manager.findAllOverlapping(e.moduleRange, e.module.getLifespan()).isEmpty();
}).collect(Collectors.toSet());
}
/**
* Get the trace module of this proposal
*
* @return the module
*/
TraceModule getModule();
/**
* Get the corresponding program image of this proposal
*
* @return the program
*/
Program getProgram();
/**
* Compute a notional "score" of the proposal
*
* <p>
* This may examine the module and program names, but must consider the likelihood of the
* match based on this proposal. The implementation need not assign meaning to any
* particular score, but a higher score must imply a more likely match.
*
* @implNote some information to consider: length and case of matched image and module
* names, alignment of program memory blocks to trace memory regions, etc.
*
* @return a score of the proposed pair
*/
double computeScore();
/**
* Compute the overall module map given by this proposal
*
* @return the map
*/
Map<TraceModule, ModuleMapEntry> computeMap();
}
/**
* A proposed map of sections to program memory blocks
*/
public interface SectionMapProposal {
/**
* Flatten proposals into a single collection of entries
*
* <p>
* The output is suitable for use in
* {@link DebuggerStaticMappingService#addSectionMappings(Collection, TaskMonitor, boolean)}.
* In some contexts, the user should be permitted to see and optionally adjust the
* collection first.
*
* <p>
* Note, a suitable parameter to this method is derived by invoking {@link Map#values()} on
* the result of
* {@link DebuggerStaticMappingService#proposeSectionMaps(Collection, Collection)}.
*
* <p>
* Note, it is advisable to filter the returned collection using
* {@link DebuggerStaticMappingService#removeOverlappingSectionEntries(Collection)} to avoid
* errors from adding overlapped mappings. Alternatively, you can set
* {@code truncateExisting} to true when calling
* {@link DebuggerStaticMappingService#addSectionMappings(Collection, TaskMonitor, boolean)}.
*
* @param proposals the collection of proposed maps
* @return the flattened, filtered collection
*/
static Collection<SectionMapEntry> flatten(Collection<SectionMapProposal> proposals) {
Collection<SectionMapEntry> result = new LinkedHashSet<>();
for (SectionMapProposal map : proposals) {
result.addAll(map.computeMap().values());
}
return result;
}
/**
* Remove entries from a collection which overlap existing entries in the trace
*
* @param entries the entries to filter
* @return the filtered entries
*/
public static Set<SectionMapEntry> removeOverlapping(Collection<SectionMapEntry> entries) {
return entries.stream().filter(e -> {
TraceStaticMappingManager manager = e.section.getTrace().getStaticMappingManager();
Range<Long> moduleLifespan = e.section.getModule().getLifespan();
return manager.findAllOverlapping(e.section.getRange(), moduleLifespan).isEmpty();
}).collect(Collectors.toSet());
}
/**
* Get the trace module of this proposal
*
* @return the module
*/
TraceModule getModule();
/**
* Get the corresponding program image of this proposal
*
* @return the program
*/
Program getProgram();
/**
* Compute a notional "score" of the proposal
*
* <p>
* This may examine the module and program names, but must consider the likelihood of the
* match based on this proposal. The implementation need not assign meaning to any
* particular score, but a higher score must imply a more likely match.
*
* @implNote some attributes of sections and blocks to consider: matched names vs. total
* names, sizes, addresses (last n hexidecimal digits, to account for relocation),
* consistency of relocation offset, etc.
*
* @return a score of the proposed pair
*/
double computeScore();
/**
* Get the program block proposed for a given trace section
*
* @param section the trace section
* @return the proposed program block
*/
MemoryBlock getDestination(TraceSection section);
/**
* Compute the overall section map given by this proposal
*
* @return the map
*/
Map<TraceSection, SectionMapEntry> computeMap();
}
/**
* A module-program entry in a proposed module map
*/
public static class ModuleMapEntry {
/**
* Check if a block should be included in size computations or analyzed for proposals
*
* @param program the program containing the block
* @param block the block
* @return true if included, false otherwise
*/
public static boolean includeBlock(Program program, MemoryBlock block) {
if (program.getImageBase().getAddressSpace() != block.getStart().getAddressSpace()) {
return false;
}
if (!block.isLoaded()) {
return false;
}
if (block.isMapped()) {
// TODO: Determine how to handle these.
return false;
}
if (MemoryBlock.EXTERNAL_BLOCK_NAME.equals(block.getName())) {
return false;
}
return true;
}
/**
* Compute the "size" of an image
*
* <p>
* This is considered the maximum loaded address as mapped in memory, minus the image base.
*
* @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;
// 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);
}
return imageSize;
}
private final TraceModule module;
private Program program;
private AddressRange moduleRange;
/**
* Construct a module map entry
*
* <p>
* Generally, only the service implementation should construct an entry. See
* {@link DebuggerStaticMappingService#proposeModuleMap(TraceModule, Program)} and related
* to obtain these.
*
* @param module the module
* @param program the matched program
* @param moduleRange a range from the module base the size of the program's image
*/
public ModuleMapEntry(TraceModule module, Program program, AddressRange moduleRange) {
this.module = module;
this.program = program;
this.moduleRange = moduleRange;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ModuleMapEntry)) {
return false;
}
ModuleMapEntry that = (ModuleMapEntry) obj;
if (this.module != that.module) {
return false;
}
/*if (this.program != that.program) {
return false;
}*/
// imageSize is derived
return true;
}
@Override
public int hashCode() {
return Objects.hash(module/*, program*/);
}
/**
* Get the module for this entry
*
* @return the module
*/
public TraceModule getModule() {
return module;
}
/**
* Get the address range of the module in the trace, as computed from the matched program's
* image size
*
* @return the module range
*/
public AddressRange getModuleRange() {
return moduleRange;
}
/**
* Get the matched program
*
* @return the program
*/
public Program getProgram() {
return program;
}
/**
* Set the matched program
*
* <p>
* This is generally used in UIs to let the user tweak and reassign, if desired. This will
* also re-compute the module range based on the new program's image size.
*
* @param program the program
*/
public void setProgram(Program program) {
this.program = program;
try {
this.moduleRange =
new AddressRangeImpl(module.getBase(), computeImageSize(program));
}
catch (AddressOverflowException e) {
// This is terribly unlikely
throw new IllegalArgumentException(
"Specified program is too large for module's memory space");
}
}
}
/**
* A section-block entry in a proposed section map
*/
public static class SectionMapEntry {
private final TraceSection section;
private Program program;
private MemoryBlock block;
/**
* Construct a section map entry
*
* <p>
* Generally, only the service implementation should construct an entry. See
* {@link DebuggerStaticMappingService#proposeSectionMap(TraceSection, Program, MemoryBlock)}
* and related to obtain these.
*
* @param section the section
* @param program the program containing the matched block
* @param block the matched memory block
*/
public SectionMapEntry(TraceSection section, Program program, MemoryBlock block) {
this.section = section;
this.program = program;
this.block = block;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof SectionMapEntry)) {
return false;
}
SectionMapEntry that = (SectionMapEntry) obj;
if (this.section != that.section) {
return false;
}
/*if (this.program != that.program) {
return false;
}
if (this.block != that.block) {
return false;
}*/
return true;
}
@Override
public int hashCode() {
return Objects.hash(section/*, program, block*/);
}
/**
* Get the module containing the section
*
* @return the module
*/
public TraceModule getModule() {
return section.getModule();
}
/**
* Get the section
*
* @return the section
*/
public TraceSection getSection() {
return section;
}
/**
* Get the program containing the matched memory block
*
* @return the program
*/
public Program getProgram() {
return program;
}
/**
* Get the matched memory block
*
* @return the block
*/
public MemoryBlock getBlock() {
return block;
}
/**
* Set the matched memory block
*
* @param program the program containing the block
* @param block the block
*/
public void setBlock(Program program, MemoryBlock block) {
this.program = program;
this.block = block;
}
/**
* Get the length of the match
*
* <p>
* Ideally, the section and block have <em>exactly</em> the same length. If they do not, the
* (unsigned) minimum of the two is used.
*
* @return the length
*/
public long getLength() {
return MathUtilities.unsignedMin(section.getRange().getLength(), block.getSize());
}
}
/**
* <<<<<<< HEAD A {@code (shift,view)} pair for describing sets of mapped addresses
* A {@code (shift,view)} pair for describing sets of mapped addresses
*/
public class ShiftAndAddressSetView {
private final long shift;
@ -528,10 +92,6 @@ public interface DebuggerStaticMappingService {
/**
* Add a static mapping (relocation) from the given trace to the given program
*
* <p>
* Note if the trace is backed by a Ghidra database, the caller must already have started a
* transaction on the relevant domain object.
*
* @param from the source trace location, including lifespan
* @param to the destination program location
* @param length the length of the mapped region, where 0 indicates {@code 1 << 64}.
@ -554,24 +114,14 @@ public interface DebuggerStaticMappingService {
void addIdentityMapping(Trace from, Program toProgram, Range<Long> lifespan,
boolean truncateExisting);
/**
* Add a static mapping (relocation) from the given module to the given program
*
* <p>
* This is simply a shortcut and does not mean to imply that all mappings must represent module
* relocations. The lifespan is that of the module's.
*
* @param from the source module
* @param length the "size" of the module -- {@code max-min+1} as loaded/mapped in memory
* @param toProgram the destination program
* @see #addMapping(TraceLocation, ProgramLocation, long, boolean)
*/
void addModuleMapping(TraceModule from, long length, Program toProgram,
boolean truncateExisting) throws TraceConflictedMappingException;
void addMapping(MapEntry<?, ?> entry, boolean truncateExisting)
throws TraceConflictedMappingException;
void addMappings(Collection<? extends MapEntry<?, ?>> entries, TaskMonitor monitor,
boolean truncateExisting, String description) throws CancelledException;
/**
* ======= >>>>>>> d694542c5 (GP-660: Put program filler back in. Need to performance test.) Add
* several static mappings (relocations)
* Add several static mappings (relocations)
*
* <p>
* This will group the entries by trace and add each's entries in a single transaction. If any
@ -582,6 +132,8 @@ public interface DebuggerStaticMappingService {
* @param monitor a monitor to cancel the operation
* @param truncateExisting true to delete or truncate the lifespan of overlapping entries
* @see #addMapping(TraceLocation, ProgramLocation, long, boolean)
* @throws TraceConflictedMappingException if a conflicting mapping overlaps the source and
* {@code truncateExisting} is false.
*/
void addModuleMappings(Collection<ModuleMapEntry> entries, TaskMonitor monitor,
boolean truncateExisting) throws CancelledException;
@ -602,6 +154,22 @@ public interface DebuggerStaticMappingService {
void addSectionMappings(Collection<SectionMapEntry> entries, TaskMonitor monitor,
boolean truncateExisting) throws CancelledException;
/**
* Add several static mappings (relocations)
*
* <p>
* This will group the entries by trace and add each's entries in a single transaction. If any
* entry fails, including due to conflicts, that failure is logged but ignored, and the
* remaining entries are processed.
*
* @param entries the entries to add
* @param monitor a monitor to cancel the operation
* @param truncateExisting true to delete or truncate the lifespan of overlapping entries
* @see #addMapping(TraceLocation, ProgramLocation, long, boolean)
*/
void addRegionMappings(Collection<RegionMapEntry> entries, TaskMonitor monitor,
boolean truncateExisting) throws CancelledException;
/**
* Collect all the open destination programs relevant for the given trace and snap
*
@ -741,16 +309,6 @@ public interface DebuggerStaticMappingService {
*/
Set<DomainFile> findProbableModulePrograms(TraceModule module);
/**
* Recursively collect external programs, i.e., libraries, starting at the given seed
*
* @param seed the seed, usually the executable
* @param monitor a monitor to cancel the process
* @return the set of found programs, including the seed
* @throws CancelledException if cancelled by the monitor
*/
Set<Program> collectLibraries(Program seed, TaskMonitor monitor) throws CancelledException;
/**
* Propose a module map for the given module to the given program
*
@ -866,4 +424,60 @@ public interface DebuggerStaticMappingService {
*/
Map<TraceModule, SectionMapProposal> proposeSectionMaps(
Collection<? extends TraceModule> modules, Collection<? extends Program> programs);
/**
* Propose a singleton region map from the given region to the given program memory block
*
* <p>
* Note, no sanity check is performed on the given parameters. This will simply give a singleton
* map of the given entry. It is strongly advised to use
* {@link RegionMapProposal#computeScore()} to assess the proposal. Alternatively, use
* {@link #proposeRegionMap(Collection, Collection)} to have the service select the best-scored
* mapping from a collection of proposed programs.
*
* @param region the region to map
* @param program the destination program
* @param block the memory block in the destination program
* @return the proposed map
*/
RegionMapProposal proposeRegionMap(TraceMemoryRegion region, Program program,
MemoryBlock block);
/**
* Propose a region map for the given regions to the given program
*
* <p>
* Note, no sanity check is performed on the given parameters. This will do its best to map
* regions to memory blocks in the given program. For the best results, regions should all
* comprise the same module, and the minimum address among the regions should be the module's
* base address. It is strongly advised to use {@link RegionMapProposal#computeScore()} to
* assess the proposal. Alternatively, use {@link #proposeRegionMap(Collection, Collection)} to
* have the service select the best-scored mapping from a collection of proposed programs.
*
* @param region the region to map
* @param program the destination program whose blocks to consider
* @return the proposed map
*/
RegionMapProposal proposeRegionMap(Collection<? extends TraceMemoryRegion> regions,
Program program);
/**
* Propose the best-scored maps of trace regions to program memory blocks for each given
* "module" given a collection of proposed programs.
*
* <p>
* Note, this method will first group regions into likely modules by parsing their names, then
* compare to program names in order to cull unlikely pairs. It then takes the best-scored
* proposal for each module. If a module has no likely paired program, then it is omitted from
* the result. For informational purposes, the keys in the returned map reflect the grouping of
* regions into likely modules. For the best results, the minimum address of each module should
* be among the regions.
*
* @param modules the modules to map
* @param programs a set of proposed destination programs
* @return the composite proposal
*/
Map<Collection<TraceMemoryRegion>, RegionMapProposal> proposeRegionMaps(
Collection<? extends TraceMemoryRegion> regions,
Collection<? extends Program> programs);
}

View file

@ -0,0 +1,46 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.services;
import com.google.common.collect.Range;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceLocation;
public interface MapEntry<T, P> {
Trace getFromTrace();
T getFromObject();
AddressRange getFromRange();
Range<Long> getFromLifespan();
TraceLocation getFromTraceLocation();
Program getToProgram();
P getToObject();
AddressRange getToRange();
ProgramLocation getToProgramLocation();
long getMappingLength();
}

View file

@ -0,0 +1,107 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.services;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.TraceStaticMappingManager;
import ghidra.util.task.TaskMonitor;
public interface MapProposal<T, P, E extends MapEntry<T, P>> {
/**
* Flatten proposals into a single collection of entries
*
* <p>
* The output is suitable for use in
* {@link DebuggerStaticMappingService#addMappings(Collection, TaskMonitor, boolean, String)}.
* In some contexts, the user should be permitted to see and optionally adjust the collection
* first.
*
* <p>
* Note, it is advisable to filter the returned collection using
* {@link #removeOverlapping(Collection)} to avoid errors from adding overlapped mappings.
* Alternatively, you can set {@code truncateExisting} to true when calling
* {@link DebuggerStaticMappingService#addMappings(Collection, TaskMonitor, boolean, String)}.
*
* @param proposals the collection of proposed maps
* @return the flattened, filtered collection
*/
static <T, P, E extends MapEntry<T, P>, M extends MapProposal<T, P, E>> Collection<E> flatten(
Collection<M> proposals) {
Collection<E> result = new LinkedHashSet<>();
for (M map : proposals) {
result.addAll(map.computeMap().values());
}
return result;
}
/**
* Remove entries from a collection which overlap existing entries in the trace
*
* @param entries the entries to filter
* @return the filtered entries
*/
static <E extends MapEntry<?, ?>> Set<E> removeOverlapping(Collection<E> entries) {
return entries.stream().filter(e -> {
TraceStaticMappingManager manager = e.getFromTrace().getStaticMappingManager();
return manager.findAllOverlapping(e.getFromRange(), e.getFromLifespan()).isEmpty();
}).collect(Collectors.toSet());
}
/**
* Get the trace containing the trace objects in this proposal
*
* @return the trace
*/
Trace getTrace();
/**
* Get the corresponding program image of this proposal
*
* @return the program
*/
Program getProgram();
/**
* Get the destination (program) object for a given source (trace) object
*
* @param from the trace object
* @return the proposed program object
*/
P getToObject(T from);
/**
* Compute a notional "score" of the proposal
*
* <p>
* This may examine attributes of the "from" and "to" objects, in order to determine the
* likelihood of the match based on this proposal. The implementation need not assign meaning to
* any particular score, but a higher score must imply a more likely match.
*
* @return a score of the proposed pair
*/
double computeScore();
/**
* Compute the overall map given by this proposal
*
* @return the map
*/
Map<T, E> computeMap();
}

View file

@ -0,0 +1,62 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.services;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.modules.TraceModule;
/**
* A proposed mapping of module to program
*/
public interface ModuleMapProposal extends MapProposal<TraceModule, Program, ModuleMapEntry> {
interface ModuleMapEntry extends MapEntry<TraceModule, Program> {
/**
* Get the module for this entry
*
* @return the module
*/
TraceModule getModule();
/**
* Get the address range of the module in the trace, as computed from the matched program's
* image size
*
* @return the module range
*/
AddressRange getModuleRange();
/**
* Set the matched program
*
* <p>
* This is generally used in UIs to let the user tweak and reassign, if desired. This will
* also re-compute the module range based on the new program's image size.
*
* @param program the program
*/
void setProgram(Program program);
}
/**
* Get the trace module of this proposal
*
* @return the module
*/
TraceModule getModule();
}

View file

@ -0,0 +1,52 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.services;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.memory.TraceMemoryRegion;
/**
* A proposed map of regions to program memory blocks
*/
public interface RegionMapProposal
extends MapProposal<TraceMemoryRegion, MemoryBlock, RegionMapEntry> {
interface RegionMapEntry extends MapEntry<TraceMemoryRegion, MemoryBlock> {
/**
* Get the region
*
* @return the region
*/
TraceMemoryRegion getRegion();
/**
* Get the matched memory block
*
* @return the block
*/
MemoryBlock getBlock();
/**
* Set the matched memory block
*
* @param program the program containing the block
* @param block the block
*/
void setBlock(Program program, MemoryBlock block);
}
}

View file

@ -0,0 +1,75 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.services;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
/**
* A proposed map of sections to program memory blocks
*/
public interface SectionMapProposal
extends MapProposal<TraceSection, MemoryBlock, SectionMapEntry> {
interface SectionMapEntry extends MapEntry<TraceSection, MemoryBlock> {
/**
* Get the section
*
* @return the section
*/
TraceSection getSection();
/**
* Get the module containing the section
*
* @return the module
*/
TraceModule getModule();
/**
* Get the matched memory block
*
* @return the block
*/
MemoryBlock getBlock();
/**
* Set the matched memory block
*
* @param program the program containing the block
* @param block the block
*/
void setBlock(Program program, MemoryBlock block);
}
/**
* Get the trace module of this proposal
*
* @return the module
*/
TraceModule getModule();
/**
* Get the corresponding program image of this proposal
*
* @return the program
*/
Program getProgram();
}

View file

@ -22,58 +22,136 @@ import org.junit.*;
import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.ProgramManager;
import ghidra.framework.model.DomainFolder;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.task.TaskMonitor;
import help.screenshot.GhidraScreenShotGenerator;
public class DebuggerRegionsPluginScreenShots extends GhidraScreenShotGenerator {
ProgramManager programManager;
DebuggerTraceManagerService traceManager;
DebuggerRegionsPlugin regionsPlugin;
DebuggerRegionsProvider regionsProvider;
ToyDBTraceBuilder tb;
Program progBash;
Program progLibC;
@Before
public void setUpMine() throws Throwable {
programManager = addPlugin(tool, ProgramManagerPlugin.class);
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
regionsPlugin = addPlugin(tool, DebuggerRegionsPlugin.class);
regionsProvider = waitForComponentProvider(DebuggerRegionsProvider.class);
tb = new ToyDBTraceBuilder("echo", ToyProgramBuilder._X64);
}
@After
public void tearDownMine() {
tb.close();
if (progBash != null) {
progBash.release(this);
}
if (progLibC != null) {
progLibC.release(this);
}
}
private static Address addr(Program program, long offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
private void populateTrace() throws Exception {
try (UndoableTransaction tid = tb.startTransaction()) {
long snap = tb.trace.getTimeManager().createSnapshot("First").getKey();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
mm.addRegion("/bin/bash (400000:40ffff)", Range.atLeast(snap),
tb.range(0x00400000, 0x0040ffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
mm.addRegion("/bin/bash (600000:60ffff)", Range.atLeast(snap),
tb.range(0x00600000, 0x0060ffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.WRITE));
mm.addRegion("/lib/libc (7fac0000:7facffff)", Range.atLeast(snap),
tb.range(0x7fac0000, 0x7facffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
mm.addRegion("/lib/libc (7fcc0000:7fccffff)", Range.atLeast(snap),
tb.range(0x7fcc0000, 0x7fccffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.WRITE));
}
}
private void populateTraceAndPrograms() throws Exception {
DomainFolder root = tool.getProject().getProjectData().getRootFolder();
populateTrace();
progBash = createDefaultProgram("bash", ProgramBuilder._X64, this);
progLibC = createDefaultProgram("libc.so.6", ProgramBuilder._X64, this);
try (UndoableTransaction tid = UndoableTransaction.start(progBash, "Add memory", true)) {
progBash.setImageBase(addr(progBash, 0x00400000), true);
progBash.getMemory()
.createInitializedBlock(".text", addr(progBash, 0x00400000), 0x10000, (byte) 0,
TaskMonitor.DUMMY, false);
progBash.getMemory()
.createInitializedBlock(".data", addr(progBash, 0x00600000), 0x10000, (byte) 0,
TaskMonitor.DUMMY, false);
}
try (UndoableTransaction tid = UndoableTransaction.start(progLibC, "Add memory", true)) {
progLibC.setImageBase(addr(progLibC, 0x00400000), true);
progLibC.getMemory()
.createInitializedBlock(".text", addr(progLibC, 0x00400000), 0x10000, (byte) 0,
TaskMonitor.DUMMY, false);
progLibC.getMemory()
.createInitializedBlock(".data", addr(progLibC, 0x00600000), 0x10000, (byte) 0,
TaskMonitor.DUMMY, false);
}
root.createFile("trace", tb.trace, TaskMonitor.DUMMY);
root.createFile("bash", progBash, TaskMonitor.DUMMY);
root.createFile("libc.so.6", progLibC, TaskMonitor.DUMMY);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
programManager.openProgram(progBash);
programManager.openProgram(progLibC);
}
@Test
public void testCaptureDebuggerRegionsPlugin() throws Throwable {
try (UndoableTransaction tid = tb.startTransaction()) {
long snap = tb.trace.getTimeManager().createSnapshot("First").getKey();
tb.trace.getMemoryManager()
.addRegion("[400000:40ffff]", Range.atLeast(snap),
tb.range(0x00400000, 0x0040ffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
tb.trace.getMemoryManager()
.addRegion("[600000:60ffff]", Range.atLeast(snap),
tb.range(0x00600000, 0x0060ffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.WRITE));
tb.trace.getMemoryManager()
.addRegion("[7fac0000:7facffff]", Range.atLeast(snap),
tb.range(0x7fac0000, 0x7facffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
tb.trace.getMemoryManager()
.addRegion("[7fae0000:7faeffff]", Range.atLeast(snap),
tb.range(0x7fae0000, 0x7faeffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.WRITE));
populateTrace();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
captureIsolatedProvider(DebuggerRegionsProvider.class, 900, 300);
}
@Test
public void testCaptureDebuggerRegionMapProposalDialog() throws Throwable {
populateTraceAndPrograms();
regionsProvider
.setSelectedRegions(Set.copyOf(tb.trace.getMemoryManager().getAllRegions()));
performAction(regionsProvider.actionMapRegions, false);
captureDialog(DebuggerRegionMapProposalDialog.class);
}
}

View file

@ -41,7 +41,7 @@ public class DebuggerModulesPluginScreenShots extends GhidraScreenShotGenerator
DebuggerModulesPlugin modulesPlugin;
DebuggerModulesProvider modulesProvider;
ToyDBTraceBuilder tb;
Program progEcho;
Program progBash;
Program progLibC;
@Before
@ -59,8 +59,8 @@ public class DebuggerModulesPluginScreenShots extends GhidraScreenShotGenerator
public void tearDownMine() {
tb.close();
if (progEcho != null) {
progEcho.release(this);
if (progBash != null) {
progBash.release(this);
}
if (progLibC != null) {
progLibC.release(this);
@ -111,16 +111,16 @@ public class DebuggerModulesPluginScreenShots extends GhidraScreenShotGenerator
lib.addSection("libc[.data]", ".data", tb.range(0x7fae0000, 0x7faeffff));
}
progEcho = createDefaultProgram("bash", ProgramBuilder._X64, this);
progBash = createDefaultProgram("bash", ProgramBuilder._X64, this);
progLibC = createDefaultProgram("libc.so.6", ProgramBuilder._X64, this);
try (UndoableTransaction tid = UndoableTransaction.start(progEcho, "Add memory", true)) {
progEcho.setImageBase(addr(progEcho, 0x00400000), true);
progEcho.getMemory()
.createInitializedBlock(".text", addr(progEcho, 0x00400000), 0x10000, (byte) 0,
try (UndoableTransaction tid = UndoableTransaction.start(progBash, "Add memory", true)) {
progBash.setImageBase(addr(progBash, 0x00400000), true);
progBash.getMemory()
.createInitializedBlock(".text", addr(progBash, 0x00400000), 0x10000, (byte) 0,
TaskMonitor.DUMMY, false);
progEcho.getMemory()
.createInitializedBlock(".data", addr(progEcho, 0x00600000), 0x10000, (byte) 0,
progBash.getMemory()
.createInitializedBlock(".data", addr(progBash, 0x00600000), 0x10000, (byte) 0,
TaskMonitor.DUMMY, false);
}
@ -135,13 +135,13 @@ public class DebuggerModulesPluginScreenShots extends GhidraScreenShotGenerator
}
root.createFile("trace", tb.trace, TaskMonitor.DUMMY);
root.createFile("echo", progEcho, TaskMonitor.DUMMY);
root.createFile("bash", progBash, TaskMonitor.DUMMY);
root.createFile("libc.so.6", progLibC, TaskMonitor.DUMMY);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
programManager.openProgram(progEcho);
programManager.openProgram(progBash);
programManager.openProgram(progLibC);
}

View file

@ -22,10 +22,8 @@ import org.junit.*;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapEntry;
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapProposal;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.ProgramManager;
import ghidra.app.services.*;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.framework.model.DomainFolder;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
@ -130,7 +128,7 @@ public class DebuggerStaticMappingPluginScreenShots extends GhidraScreenShotGene
Map<TraceModule, ModuleMapProposal> proposal =
mappingService.proposeModuleMaps(tb.trace.getModuleManager().getAllModules(),
List.of(programManager.getAllOpenPrograms()));
Collection<ModuleMapEntry> entries = ModuleMapProposal.flatten(proposal.values());
Collection<ModuleMapEntry> entries = MapProposal.flatten(proposal.values());
mappingService.addModuleMappings(entries, TaskMonitor.DUMMY, false);
}

View file

@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.memory;
import static org.junit.Assert.*;
import java.util.Set;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
@ -26,24 +26,63 @@ import com.google.common.collect.Range;
import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionMapProposalDialog.RegionMapTableColumns;
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsProvider.RegionTableColumns;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.util.database.UndoableTransaction;
public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
DebuggerRegionsProvider provider;
protected TraceMemoryRegion regionExeText;
protected TraceMemoryRegion regionExeData;
protected TraceMemoryRegion regionLibText;
protected TraceMemoryRegion regionLibData;
protected MemoryBlock blockExeText;
protected MemoryBlock blockExeData;
@Before
public void setUpRegionsTest() throws Exception {
addPlugin(tool, DebuggerRegionsPlugin.class);
provider = waitForComponentProvider(DebuggerRegionsProvider.class);
}
protected void addRegions() throws Exception {
TraceMemoryManager mm = tb.trace.getMemoryManager();
try (UndoableTransaction tid = tb.startTransaction()) {
regionExeText = mm.createRegion("Regions[/bin/echo 0x55550000]", 0,
tb.range(0x55550000, 0x555500ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
regionExeData = mm.createRegion("Regions[/bin/echo 0x55750000]", 0,
tb.range(0x55750000, 0x5575007f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
regionLibText = mm.createRegion("Regions[/lib/libc.so 0x7f000000]", 0,
tb.range(0x7f000000, 0x7f0003ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
regionLibData = mm.createRegion("Regions[/lib/libc.so 0x7f100000]", 0,
tb.range(0x7f100000, 0x7f10003f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
}
}
protected void addBlocks() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
Memory mem = program.getMemory();
blockExeText = mem.createInitializedBlock(".text", tb.addr(0x00400000), 0x100, (byte) 0,
monitor, false);
blockExeData = mem.createInitializedBlock(".data", tb.addr(0x00600000), 0x80, (byte) 0,
monitor, false);
}
}
@Test
public void testNoTraceEmpty() throws Exception {
assertEquals(0, provider.regionTableModel.getModelData().size());
@ -198,6 +237,92 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress()));
}
@Test
public void testActionMapRegions() throws Exception {
assertFalse(provider.actionMapRegions.isEnabled());
createAndOpenTrace();
createAndOpenProgramFromTrace();
intoProject(tb.trace);
intoProject(program);
addRegions();
traceManager.activateTrace(tb.trace);
waitForSwing();
// Still
assertFalse(provider.actionMapRegions.isEnabled());
addBlocks();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name", true)) {
program.setName("echo");
}
waitForDomainObject(program);
waitForPass(() -> assertEquals(4, provider.regionTable.getRowCount()));
// NB. Feature works "best" when all regions of modules are selected
// TODO: Test cases where feature works "worst"?
provider.setSelectedRegions(Set.of(regionExeText, regionExeData));
waitForSwing();
assertTrue(provider.actionMapRegions.isEnabled());
performAction(provider.actionMapRegions, false);
DebuggerRegionMapProposalDialog propDialog =
waitForDialogComponent(DebuggerRegionMapProposalDialog.class);
List<RegionMapEntry> proposal = new ArrayList<>(propDialog.getTableModel().getModelData());
assertEquals(2, proposal.size());
RegionMapEntry entry;
// Table sorts by name by default.
// Names are file name followed by min address, so .text is first.
entry = proposal.get(0);
assertEquals(regionExeText, entry.getRegion());
assertEquals(blockExeText, entry.getBlock());
entry = proposal.get(1);
assertEquals(regionExeData, entry.getRegion());
assertEquals(blockExeData, entry.getBlock());
clickTableCell(propDialog.getTable(), 0, RegionMapTableColumns.CHOOSE.ordinal(), 1);
DebuggerBlockChooserDialog blockDialog =
waitForDialogComponent(DebuggerBlockChooserDialog.class);
MemoryBlockRow row = blockDialog.getTableFilterPanel().getSelectedItem();
assertEquals(blockExeText, row.getBlock());
pressButtonByText(blockDialog, "OK", true);
assertEquals(blockExeData, entry.getBlock()); // Unchanged
// TODO: Test the changed case
Collection<? extends TraceStaticMapping> mappings =
tb.trace.getStaticMappingManager().getAllEntries();
assertEquals(0, mappings.size());
pressButtonByText(propDialog, "OK", true);
waitForDomainObject(tb.trace);
assertEquals(2, mappings.size());
Iterator<? extends TraceStaticMapping> mit = mappings.iterator();
TraceStaticMapping sm;
sm = mit.next();
assertEquals(Range.atLeast(0L), sm.getLifespan());
assertEquals("ram:00400000", sm.getStaticAddress());
assertEquals(0x100, sm.getLength());
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
sm = mit.next();
assertEquals(Range.atLeast(0L), sm.getLifespan());
assertEquals("ram:00600000", sm.getStaticAddress());
assertEquals(0x80, sm.getLength());
assertEquals(tb.addr(0x55750000), sm.getMinTraceAddress());
assertFalse(mit.hasNext());
}
// TODO: testActionMapRegionsTo
// TODO: testActionMapRegionTo
@Test
public void testActionSelectAddresses() throws Exception {
addPlugin(tool, DebuggerListingPlugin.class);

View file

@ -30,15 +30,16 @@ import docking.widgets.filechooser.GhidraFileChooser;
import generic.Unique;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerBlockChooserDialog.MemoryBlockRow;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModuleMapProposalDialog.ModuleMapTableColumns;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapEntry;
import ghidra.app.services.DebuggerStaticMappingService.SectionMapEntry;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.attributes.TargetPrimitiveDataType.DefaultTargetPrimitiveDataType;
import ghidra.dbg.attributes.TargetPrimitiveDataType.PrimitiveKind;
@ -46,7 +47,6 @@ import ghidra.dbg.model.TestTargetModule;
import ghidra.dbg.model.TestTargetTypedefDataType;
import ghidra.dbg.util.TargetDataTypeConverter;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.store.LockException;
import ghidra.plugin.importer.ImporterPlugin;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.address.AddressSet;
@ -126,7 +126,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
}
}
protected MemoryBlock addBlock() throws LockException, DuplicateNameException,
protected MemoryBlock addBlock() throws Exception,
MemoryConflictException, AddressOverflowException, CancelledException {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
return program.getMemory()
@ -232,13 +232,13 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
DebuggerSectionMapProposalDialog propDialog =
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
clickTableCell(propDialog.table, 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
clickTableCell(propDialog.getTable(), 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
DebuggerBlockChooserDialog blockDialog =
waitForDialogComponent(DebuggerBlockChooserDialog.class);
assertEquals(1, blockDialog.tableModel.getRowCount());
MemoryBlockRow row = blockDialog.tableModel.getModelData().get(0);
assertEquals(1, blockDialog.getTableModel().getRowCount());
MemoryBlockRow row = blockDialog.getTableModel().getModelData().get(0);
assertEquals(program, row.getProgram());
assertEquals(block, row.getBlock());
// NOTE: Other getters should be tested in a separate MemoryBlockRowTest
@ -399,19 +399,19 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
DebuggerModuleMapProposalDialog propDialog =
waitForDialogComponent(DebuggerModuleMapProposalDialog.class);
List<ModuleMapEntry> proposal = propDialog.tableModel.getModelData();
List<ModuleMapEntry> proposal = propDialog.getTableModel().getModelData();
ModuleMapEntry entry = Unique.assertOne(proposal);
assertEquals(modExe, entry.getModule());
assertEquals(program, entry.getProgram());
assertEquals(program, entry.getToProgram());
clickTableCell(propDialog.table, 0, ModuleMapTableColumns.CHOOSE.ordinal(), 1);
clickTableCell(propDialog.getTable(), 0, ModuleMapTableColumns.CHOOSE.ordinal(), 1);
DataTreeDialog programDialog = waitForDialogComponent(DataTreeDialog.class);
assertEquals(program.getDomainFile(), programDialog.getDomainFile());
pressButtonByText(programDialog, "OK", true);
assertEquals(program, entry.getProgram());
assertEquals(program, entry.getToProgram());
// TODO: Test the changed case
Collection<? extends TraceStaticMapping> mappings =
@ -429,6 +429,9 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
}
// TODO: testActionMapModulesTo
// TODO: testActionMapModuleTo
@Test
public void testActionMapSections() throws Exception {
assertFalse(modulesProvider.actionMapSections.isEnabled());
@ -461,16 +464,16 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
DebuggerSectionMapProposalDialog propDialog =
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
List<SectionMapEntry> proposal = propDialog.tableModel.getModelData();
List<SectionMapEntry> proposal = propDialog.getTableModel().getModelData();
SectionMapEntry entry = Unique.assertOne(proposal);
assertEquals(secExeText, entry.getSection());
assertEquals(block, entry.getBlock());
clickTableCell(propDialog.table, 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
clickTableCell(propDialog.getTable(), 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
DebuggerBlockChooserDialog blockDialog =
waitForDialogComponent(DebuggerBlockChooserDialog.class);
MemoryBlockRow row = Unique.assertOne(blockDialog.tableModel.getModelData());
MemoryBlockRow row = Unique.assertOne(blockDialog.getTableModel().getModelData());
assertEquals(block, row.getBlock());
pressButtonByText(blockDialog, "OK", true);
@ -492,6 +495,9 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
}
// TODO: testActionMapSectionsTo
// TODO: testActionMapSectionTo
@Test
public void testActionSelectAddresses() throws Exception {
assertFalse(modulesProvider.actionSelectAddresses.isEnabled());

View file

@ -33,7 +33,10 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.*;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
@ -579,4 +582,26 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
}
// TODO: open trace, add mapping to closed program, then open that program
// TODO: The various mapping proposals
@Test
public void testGroupRegionsByLikelyModule() throws Exception {
TraceMemoryRegion echoText, echoData, libText, libData;
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
try (UndoableTransaction tid = tb.startTransaction()) {
echoText = mm.createRegion("Memory.Regions[/bin/echo (0x00400000)]",
0, tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
echoData = mm.createRegion("Memory.Regions[/bin/echo (0x00600000)]",
0, tb.range(0x00600000, 0x00600fff), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
libText = mm.createRegion("Memory.Regions[/lib/libc.so (0x7ff00000)]",
0, tb.range(0x7ff00000, 0x7ff0ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
libData = mm.createRegion("Memory.Regions[/lib/libc.so (0x7ff20000)]",
0, tb.range(0x7ff20000, 0x7ff20fff), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
}
Set<Set<TraceMemoryRegion>> actual =
DebuggerStaticMappingProposals.groupRegionsByLikelyModule(mm.getAllRegions());
assertEquals(Set.of(Set.of(echoText, echoData), Set.of(libText, libData)), actual);
}
}