mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-1231: Added 'Map by Regions' action to Debugger.
This commit is contained in:
parent
513c9beb9d
commit
108ea044cc
46 changed files with 3091 additions and 1569 deletions
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 |
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue