mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +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) {
|
protected static String computeDisplay(GdbMemoryMapping mapping) {
|
||||||
// NOTE: This deviates from GDB's table display, as it'd be confusing in isolation
|
// NOTE: This deviates from GDB's table display, as it'd be confusing in isolation
|
||||||
if (mapping.getObjfile() == null || mapping.getObjfile().length() == 0) {
|
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());
|
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/DebuggerPcodeStepperPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerPcodeStepperPlugin/images/DebuggerPcodeStepperPlugin.png||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/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/DebuggerRegionsPlugin/images/DebuggerRegionsPlugin.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerRegistersPlugin/images/DebuggerAvailableRegistersDialog.png||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.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.DefaultTraceLocation;
|
import ghidra.trace.model.DefaultTraceLocation;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
|
||||||
|
|
||||||
public class AddMapping extends GhidraScript {
|
public class AddMapping extends GhidraScript {
|
||||||
@Override
|
@Override
|
||||||
|
@ -35,13 +34,10 @@ public class AddMapping extends GhidraScript {
|
||||||
AddressSpace dynRam = currentTrace.getBaseAddressFactory().getDefaultAddressSpace();
|
AddressSpace dynRam = currentTrace.getBaseAddressFactory().getDefaultAddressSpace();
|
||||||
AddressSpace statRam = currentProgram.getAddressFactory().getDefaultAddressSpace();
|
AddressSpace statRam = currentProgram.getAddressFactory().getDefaultAddressSpace();
|
||||||
|
|
||||||
try (UndoableTransaction tid =
|
mappings.addMapping(
|
||||||
UndoableTransaction.start(currentTrace, "Add Mapping", true)) {
|
new DefaultTraceLocation(currentTrace, null, Range.atLeast(0L),
|
||||||
mappings.addMapping(
|
dynRam.getAddress(0x00400000)),
|
||||||
new DefaultTraceLocation(currentTrace, null, Range.atLeast(0L),
|
new ProgramLocation(currentProgram, statRam.getAddress(0x00400000)),
|
||||||
dynRam.getAddress(0x00400000)),
|
0x10000, false);
|
||||||
new ProgramLocation(currentProgram, statRam.getAddress(0x00400000)),
|
|
||||||
0x10000, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,5 +59,15 @@
|
||||||
performed manually using the <A href=
|
performed manually using the <A href=
|
||||||
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#map_sections">Map Sections</A>
|
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html#map_sections">Map Sections</A>
|
||||||
action in the Modules and Sections window.</P>
|
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>
|
</BODY>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
|
|
@ -55,6 +55,41 @@
|
||||||
<P>Other than modifications enabled by the table, the Regions window provides the following
|
<P>Other than modifications enabled by the table, the Regions window provides the following
|
||||||
actions:</P>
|
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>
|
<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
|
<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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.awt.BorderLayout;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -59,6 +59,16 @@ public abstract class AbstractDebuggerMapProposalDialog<R> extends DialogCompone
|
||||||
createActions();
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* testing */
|
||||||
|
public GTable getTable() {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* testing */
|
||||||
|
public EnumeratedColumnTableModel<R> getTableModel() {
|
||||||
|
return tableModel;
|
||||||
|
}
|
||||||
|
|
||||||
protected void removeEntry(R entry) {
|
protected void removeEntry(R entry) {
|
||||||
tableModel.delete(entry);
|
tableModel.delete(entry);
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.awt.BorderLayout;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -34,16 +34,17 @@ import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
import ghidra.trace.model.modules.TraceSection;
|
import ghidra.trace.model.modules.TraceSection;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
|
||||||
public class DebuggerBlockChooserDialog extends DialogComponentProvider {
|
public class DebuggerBlockChooserDialog extends DialogComponentProvider {
|
||||||
static class MemoryBlockRow {
|
public static class MemoryBlockRow {
|
||||||
private final Program program;
|
private final Program program;
|
||||||
private final MemoryBlock block;
|
private final MemoryBlock block;
|
||||||
private double score;
|
private double score;
|
||||||
|
|
||||||
public MemoryBlockRow(Program program, MemoryBlock block) {
|
protected MemoryBlockRow(Program program, MemoryBlock block) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
this.block = block;
|
this.block = block;
|
||||||
}
|
}
|
||||||
|
@ -87,6 +88,13 @@ public class DebuggerBlockChooserDialog extends DialogComponentProvider {
|
||||||
return score = service.proposeSectionMap(section, program, block).computeScore();
|
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() {
|
public ProgramLocation getProgramLocation() {
|
||||||
return new ProgramLocation(program, block.getStart());
|
return new ProgramLocation(program, block.getStart());
|
||||||
}
|
}
|
||||||
|
@ -143,11 +151,21 @@ public class DebuggerBlockChooserDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
private Entry<Program, MemoryBlock> chosen;
|
private Entry<Program, MemoryBlock> chosen;
|
||||||
|
|
||||||
protected DebuggerBlockChooserDialog() {
|
public DebuggerBlockChooserDialog() {
|
||||||
super("Memory Blocks", true, true, true, false);
|
super("Memory Blocks", true, true, true, false);
|
||||||
populateComponents();
|
populateComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* testing */
|
||||||
|
public EnumeratedColumnTableModel<MemoryBlockRow> getTableModel() {
|
||||||
|
return tableModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* testing */
|
||||||
|
public GhidraTableFilterPanel<MemoryBlockRow> getTableFilterPanel() {
|
||||||
|
return filterPanel;
|
||||||
|
}
|
||||||
|
|
||||||
protected void populateComponents() {
|
protected void populateComponents() {
|
||||||
JPanel panel = new JPanel(new BorderLayout());
|
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,
|
public Map.Entry<Program, MemoryBlock> chooseBlock(PluginTool tool, TraceSection section,
|
||||||
Collection<Program> programs) {
|
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);
|
setBlocksFromPrograms(programs);
|
||||||
computeScores(section, tool.getService(DebuggerStaticMappingService.class));
|
computeScores(scorer);
|
||||||
selectHighestScoringBlock();
|
selectHighestScoringBlock();
|
||||||
tool.showDialog(this);
|
tool.showDialog(this);
|
||||||
return getChosen();
|
return getChosen();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void computeScores(TraceSection section, DebuggerStaticMappingService service) {
|
protected void computeScores(Function<MemoryBlockRow, Double> scorer) {
|
||||||
for (MemoryBlockRow rec : tableModel.getModelData()) {
|
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_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png");
|
||||||
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png");
|
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png");
|
||||||
ImageIcon ICON_MAP_SECTIONS = ICON_MAP_MODULES; // TODO
|
ImageIcon ICON_MAP_SECTIONS = ICON_MAP_MODULES; // TODO
|
||||||
|
ImageIcon ICON_MAP_REGIONS = ICON_MAP_MODULES; // TODO
|
||||||
ImageIcon ICON_BLOCK = ICON_MAP_SECTIONS; // TODO
|
ImageIcon ICON_BLOCK = ICON_MAP_SECTIONS; // TODO
|
||||||
// TODO: Draw an icon
|
// TODO: Draw an icon
|
||||||
ImageIcon ICON_SELECT_ADDRESSES = ResourceManager.loadImage("images/NextSelectionBlock16.gif");
|
ImageIcon ICON_SELECT_ADDRESSES = ResourceManager.loadImage("images/NextSelectionBlock16.gif");
|
||||||
|
@ -1344,7 +1345,7 @@ public interface DebuggerResources {
|
||||||
|
|
||||||
interface MapSectionToAction {
|
interface MapSectionToAction {
|
||||||
String NAME_PREFIX = "Map Section to ";
|
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
|
Icon ICON = ICON_MAP_SECTIONS; // TODO: Probably no icon
|
||||||
String GROUP = GROUP_MAPPING;
|
String GROUP = GROUP_MAPPING;
|
||||||
String HELP_ANCHOR = "map_section_to";
|
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
|
/*interface SelectAddressesAction { // TODO: Finish this conversion
|
||||||
String NAME = "Select Addresses";
|
String NAME = "Select Addresses";
|
||||||
Icon ICON = ICON_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;
|
package ghidra.app.plugin.core.debug.gui.memory;
|
||||||
|
|
||||||
|
import ghidra.app.events.*;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
|
||||||
@PluginInfo( //
|
@PluginInfo(
|
||||||
shortDescription = "Debugger regions manager", //
|
shortDescription = "Debugger regions manager",
|
||||||
description = "GUI to manage memory regions", //
|
description = "GUI to manage memory regions",
|
||||||
category = PluginCategoryNames.DEBUGGER, //
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME, //
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.RELEASED, //
|
status = PluginStatus.RELEASED,
|
||||||
eventsConsumed = {
|
eventsConsumed = {
|
||||||
TraceActivatedPluginEvent.class, //
|
ProgramActivatedPluginEvent.class,
|
||||||
TraceClosedPluginEvent.class, //
|
ProgramLocationPluginEvent.class,
|
||||||
}, //
|
ProgramClosedPluginEvent.class,
|
||||||
servicesRequired = { //
|
TraceActivatedPluginEvent.class,
|
||||||
DebuggerModelService.class, //
|
},
|
||||||
DebuggerStaticMappingService.class, //
|
servicesRequired = {
|
||||||
DebuggerTraceManagerService.class, //
|
DebuggerModelService.class,
|
||||||
ProgramManager.class, //
|
DebuggerStaticMappingService.class,
|
||||||
} //
|
DebuggerTraceManagerService.class,
|
||||||
)
|
ProgramManager.class,
|
||||||
|
})
|
||||||
public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin {
|
public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin {
|
||||||
protected DebuggerRegionsProvider provider;
|
protected DebuggerRegionsProvider provider;
|
||||||
|
|
||||||
|
@ -63,7 +64,19 @@ public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin {
|
||||||
@Override
|
@Override
|
||||||
public void processEvent(PluginEvent event) {
|
public void processEvent(PluginEvent event) {
|
||||||
super.processEvent(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;
|
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||||
provider.setTrace(ev.getActiveCoordinates().getTrace());
|
provider.setTrace(ev.getActiveCoordinates().getTrace());
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.gui.memory;
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -34,17 +35,21 @@ import docking.action.*;
|
||||||
import docking.widgets.table.CustomToStringCellRenderer;
|
import docking.widgets.table.CustomToStringCellRenderer;
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
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.gui.DebuggerResources.AbstractSelectAddressesAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectRowsAction;
|
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.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
||||||
import ghidra.app.services.DebuggerListingService;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
import ghidra.framework.plugintool.AutoService;
|
import ghidra.framework.plugintool.AutoService;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.program.model.address.*;
|
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.ProgramLocation;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
@ -52,6 +57,7 @@ import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
|
||||||
import ghidra.trace.model.TraceDomainObjectListener;
|
import ghidra.trace.model.TraceDomainObjectListener;
|
||||||
import ghidra.trace.model.memory.TraceMemoryManager;
|
import ghidra.trace.model.memory.TraceMemoryManager;
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.database.ObjectKey;
|
import ghidra.util.database.ObjectKey;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
@ -213,9 +219,13 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
private final DebuggerRegionsPlugin plugin;
|
private final DebuggerRegionsPlugin plugin;
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerListingService listingService;
|
private DebuggerStaticMappingService staticMappingService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerTraceManagerService traceManager;
|
private DebuggerTraceManagerService traceManager;
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private DebuggerListingService listingService;
|
||||||
|
@AutoServiceConsumed
|
||||||
|
ProgramManager programManager;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
@ -229,7 +239,17 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
private final JPanel mainPanel = new JPanel(new BorderLayout());
|
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 DebuggerRegionActionContext myActionContext;
|
||||||
|
private Program currentProgram;
|
||||||
|
private ProgramLocation currentLocation;
|
||||||
|
|
||||||
|
DockingAction actionMapRegions;
|
||||||
|
DockingAction actionMapRegionTo;
|
||||||
|
DockingAction actionMapRegionsTo;
|
||||||
|
|
||||||
SelectAddressesAction actionSelectAddresses;
|
SelectAddressesAction actionSelectAddresses;
|
||||||
DockingAction actionSelectRows;
|
DockingAction actionSelectRows;
|
||||||
|
@ -247,6 +267,9 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
|
||||||
|
blockChooserDialog = new DebuggerBlockChooserDialog();
|
||||||
|
regionProposalDialog = new DebuggerRegionMapProposalDialog(this);
|
||||||
|
|
||||||
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
createActions();
|
createActions();
|
||||||
|
@ -335,12 +358,119 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
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();
|
actionSelectAddresses = new SelectAddressesAction();
|
||||||
actionSelectRows = SelectRowsAction.builder(plugin)
|
actionSelectRows = SelectRowsAction.builder(plugin)
|
||||||
.description("Select regions by trace selection")
|
.description("Select regions by trace selection")
|
||||||
.enabledWhen(ctx -> currentTrace != null)
|
.enabledWhen(ctx -> currentTrace != null)
|
||||||
.onAction(this::activatedSelectCurrent)
|
.onAction(this::activatedSelectCurrent)
|
||||||
.buildAndInstallLocal(this);
|
.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) {
|
private void activatedSelectCurrent(ActionContext ignored) {
|
||||||
|
@ -385,6 +515,34 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
return mainPanel;
|
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) {
|
public void setTrace(Trace trace) {
|
||||||
if (currentTrace == trace) {
|
if (currentTrace == trace) {
|
||||||
return;
|
return;
|
||||||
|
@ -409,4 +567,14 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
currentTrace.addListener(regionsListener);
|
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.*;
|
||||||
import ghidra.trace.model.modules.TraceConflictedMappingException;
|
import ghidra.trace.model.modules.TraceConflictedMappingException;
|
||||||
import ghidra.util.MathUtilities;
|
import ghidra.util.MathUtilities;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
|
||||||
import ghidra.util.layout.PairLayout;
|
import ghidra.util.layout.PairLayout;
|
||||||
|
|
||||||
public class DebuggerAddMappingDialog extends DialogComponentProvider {
|
public class DebuggerAddMappingDialog extends DialogComponentProvider {
|
||||||
|
@ -296,10 +295,8 @@ public class DebuggerAddMappingDialog extends DialogComponentProvider {
|
||||||
ProgramLocation to = new ProgramLocation(program,
|
ProgramLocation to = new ProgramLocation(program,
|
||||||
fieldProgRange.getRange().getMinAddress());
|
fieldProgRange.getRange().getMinAddress());
|
||||||
|
|
||||||
try (UndoableTransaction tid =
|
try {
|
||||||
UndoableTransaction.start(trace, "Add Static Mapping", false)) {
|
|
||||||
mappingService.addMapping(from, to, getLength(), true);
|
mappingService.addMapping(from, to, getLength(), true);
|
||||||
tid.commit();
|
|
||||||
}
|
}
|
||||||
catch (TraceConflictedMappingException e) {
|
catch (TraceConflictedMappingException e) {
|
||||||
throw new AssertionError(e); // I said truncateExisting
|
throw new AssertionError(e); // I said truncateExisting
|
||||||
|
|
|
@ -24,9 +24,10 @@ import javax.swing.table.TableColumnModel;
|
||||||
|
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
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;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapModulesAction;
|
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.framework.model.DomainFile;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
@ -43,8 +44,8 @@ public class DebuggerModuleMapProposalDialog
|
||||||
MODULE_NAME("Module", String.class, e -> e.getModule().getName()),
|
MODULE_NAME("Module", String.class, e -> e.getModule().getName()),
|
||||||
DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getModule().getBase()),
|
DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getModule().getBase()),
|
||||||
CHOOSE("Choose", String.class, e -> "Choose Program", (e, v) -> nop()),
|
CHOOSE("Choose", String.class, e -> "Choose Program", (e, v) -> nop()),
|
||||||
PROGRAM_NAME("Program", String.class, e -> e.getProgram().getName()),
|
PROGRAM_NAME("Program", String.class, e -> e.getToProgram().getName()),
|
||||||
STATIC_BASE("Static Base", Address.class, e -> e.getProgram().getImageBase()),
|
STATIC_BASE("Static Base", Address.class, e -> e.getToProgram().getImageBase()),
|
||||||
SIZE("Size", Long.class, e -> e.getModuleRange().getLength());
|
SIZE("Size", Long.class, e -> e.getModuleRange().getLength());
|
||||||
|
|
||||||
private final String header;
|
private final String header;
|
||||||
|
@ -146,7 +147,7 @@ public class DebuggerModuleMapProposalDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
private void chooseAndSetProgram(ModuleMapEntry entry) {
|
private void chooseAndSetProgram(ModuleMapEntry entry) {
|
||||||
DomainFile file = provider.askProgram(entry.getProgram());
|
DomainFile file = provider.askProgram(entry.getToProgram());
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,25 +24,24 @@ import ghidra.app.services.*;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
|
||||||
@PluginInfo( //
|
@PluginInfo(
|
||||||
shortDescription = "Debugger module and section manager", //
|
shortDescription = "Debugger module and section manager",
|
||||||
description = "GUI to manage modules and sections", //
|
description = "GUI to manage modules and sections",
|
||||||
category = PluginCategoryNames.DEBUGGER, //
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME, //
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.RELEASED, //
|
status = PluginStatus.RELEASED,
|
||||||
eventsConsumed = {
|
eventsConsumed = {
|
||||||
ProgramActivatedPluginEvent.class, //
|
ProgramActivatedPluginEvent.class,
|
||||||
ProgramLocationPluginEvent.class, //
|
ProgramLocationPluginEvent.class,
|
||||||
ProgramClosedPluginEvent.class, //
|
ProgramClosedPluginEvent.class,
|
||||||
TraceActivatedPluginEvent.class, //
|
TraceActivatedPluginEvent.class,
|
||||||
}, //
|
},
|
||||||
servicesRequired = { //
|
servicesRequired = {
|
||||||
DebuggerModelService.class, //
|
DebuggerModelService.class,
|
||||||
DebuggerStaticMappingService.class, //
|
DebuggerStaticMappingService.class,
|
||||||
DebuggerTraceManagerService.class, //
|
DebuggerTraceManagerService.class,
|
||||||
ProgramManager.class, //
|
ProgramManager.class,
|
||||||
} //
|
})
|
||||||
)
|
|
||||||
public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
|
public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
|
||||||
protected DebuggerModulesProvider provider;
|
protected DebuggerModulesProvider provider;
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ import docking.widgets.table.CustomToStringCellRenderer;
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
import docking.widgets.table.TableFilter;
|
import docking.widgets.table.TableFilter;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
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.gui.DebuggerResources.*;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.MapModulesBackgroundCommand;
|
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.BackgroundUtils;
|
||||||
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
||||||
import ghidra.app.services.*;
|
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.AsyncUtils;
|
||||||
import ghidra.async.TypeSpec;
|
import ghidra.async.TypeSpec;
|
||||||
import ghidra.framework.main.AppInfo;
|
import ghidra.framework.main.AppInfo;
|
||||||
|
@ -634,7 +636,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadModules() {
|
private void loadModules() {
|
||||||
|
moduleTable.getSelectionModel().clearSelection();
|
||||||
moduleTableModel.clear();
|
moduleTableModel.clear();
|
||||||
|
sectionTable.getSelectionModel().clearSelection();
|
||||||
sectionTableModel.clear();
|
sectionTableModel.clear();
|
||||||
|
|
||||||
if (currentTrace == null) {
|
if (currentTrace == null) {
|
||||||
|
@ -758,8 +762,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
.onAction(this::activatedMapModules)
|
.onAction(this::activatedMapModules)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
actionMapModuleTo = MapModuleToAction.builder(plugin)
|
actionMapModuleTo = MapModuleToAction.builder(plugin)
|
||||||
.enabledWhen(ctx -> currentProgram != null)
|
|
||||||
.withContext(DebuggerModuleActionContext.class)
|
.withContext(DebuggerModuleActionContext.class)
|
||||||
|
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedModules().size() == 1)
|
||||||
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedModules().size() == 1)
|
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedModules().size() == 1)
|
||||||
.onAction(this::activatedMapModuleTo)
|
.onAction(this::activatedMapModuleTo)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
|
@ -769,13 +773,13 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
.onAction(this::activatedMapSections)
|
.onAction(this::activatedMapSections)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
actionMapSectionTo = MapSectionToAction.builder(plugin)
|
actionMapSectionTo = MapSectionToAction.builder(plugin)
|
||||||
.enabledWhen(ctx -> currentProgram != null)
|
|
||||||
.withContext(DebuggerSectionActionContext.class)
|
.withContext(DebuggerSectionActionContext.class)
|
||||||
|
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedSections().size() == 1)
|
||||||
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedSections().size() == 1)
|
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedSections().size() == 1)
|
||||||
.onAction(this::activatedMapSectionTo)
|
.onAction(this::activatedMapSectionTo)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
actionMapSectionsTo = MapSectionsToAction.builder(plugin)
|
actionMapSectionsTo = MapSectionsToAction.builder(plugin)
|
||||||
.enabledWhen(ctx -> currentProgram != null)
|
.enabledWhen(ctx -> currentProgram != null && isContextSectionsOfOneModule(ctx))
|
||||||
.popupWhen(ctx -> currentProgram != null && isContextSectionsOfOneModule(ctx))
|
.popupWhen(ctx -> currentProgram != null && isContextSectionsOfOneModule(ctx))
|
||||||
.onAction(this::activatedMapSectionsTo)
|
.onAction(this::activatedMapSectionsTo)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
|
@ -1006,7 +1010,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
Map<TraceModule, ModuleMapProposal> map = staticMappingService.proposeModuleMaps(modules,
|
Map<TraceModule, ModuleMapProposal> map = staticMappingService.proposeModuleMaps(modules,
|
||||||
List.of(programManager.getAllOpenPrograms()));
|
List.of(programManager.getAllOpenPrograms()));
|
||||||
Collection<ModuleMapEntry> proposal = ModuleMapProposal.flatten(map.values());
|
Collection<ModuleMapEntry> proposal = MapProposal.flatten(map.values());
|
||||||
promptModuleProposal(proposal);
|
promptModuleProposal(proposal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1045,9 +1049,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
Set<TraceModule> modules =
|
Set<TraceModule> modules =
|
||||||
sections.stream().map(TraceSection::getModule).collect(Collectors.toSet());
|
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()));
|
List.of(programManager.getAllOpenPrograms()));
|
||||||
Collection<SectionMapEntry> proposal = SectionMapProposal.flatten(map.values());
|
Collection<SectionMapEntry> proposal = MapProposal.flatten(map.values());
|
||||||
Collection<SectionMapEntry> filtered = proposal.stream()
|
Collection<SectionMapEntry> filtered = proposal.stream()
|
||||||
.filter(e -> sections.contains(e.getSection()))
|
.filter(e -> sections.contains(e.getSection()))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
@ -1085,7 +1089,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
if (block == null) {
|
if (block == null) {
|
||||||
return;
|
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() {
|
protected Set<MemoryBlock> collectBlocksInOpenPrograms() {
|
||||||
|
|
|
@ -25,9 +25,10 @@ import javax.swing.table.TableColumnModel;
|
||||||
|
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
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;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapSectionsAction;
|
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.address.Address;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
|
@ -45,10 +46,10 @@ public class DebuggerSectionMapProposalDialog
|
||||||
SECTION_NAME("Section", String.class, e -> e.getSection().getName()),
|
SECTION_NAME("Section", String.class, e -> e.getSection().getName()),
|
||||||
DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getSection().getStart()),
|
DYNAMIC_BASE("Dynamic Base", Address.class, e -> e.getSection().getStart()),
|
||||||
CHOOSE("Choose", String.class, e -> "Choose Block", (e, s) -> nop()),
|
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()),
|
BLOCK_NAME("Block", String.class, e -> e.getBlock().getName()),
|
||||||
STATIC_BASE("Static Base", Address.class, e -> e.getBlock().getStart()),
|
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 String header;
|
||||||
private final Class<?> cls;
|
private final Class<?> cls;
|
||||||
|
@ -151,7 +152,7 @@ public class DebuggerSectionMapProposalDialog
|
||||||
|
|
||||||
private void chooseAndSetBlock(SectionMapEntry entry) {
|
private void chooseAndSetBlock(SectionMapEntry entry) {
|
||||||
Map.Entry<Program, MemoryBlock> choice =
|
Map.Entry<Program, MemoryBlock> choice =
|
||||||
provider.askBlock(entry.getSection(), entry.getProgram(), entry.getBlock());
|
provider.askBlock(entry.getSection(), entry.getToProgram(), entry.getBlock());
|
||||||
if (choice == null) {
|
if (choice == null) {
|
||||||
return;
|
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;
|
package ghidra.app.plugin.core.debug.service.modules;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
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.event.TraceOpenedPluginEvent;
|
||||||
import ghidra.app.plugin.core.debug.utils.*;
|
import ghidra.app.plugin.core.debug.utils.*;
|
||||||
import ghidra.app.services.*;
|
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.AsyncDebouncer;
|
||||||
import ghidra.async.AsyncTimer;
|
import ghidra.async.AsyncTimer;
|
||||||
import ghidra.framework.data.OpenedDomainFile;
|
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.framework.store.FileSystem;
|
|
||||||
import ghidra.generic.util.datastruct.TreeValueSortedMap;
|
import ghidra.generic.util.datastruct.TreeValueSortedMap;
|
||||||
import ghidra.generic.util.datastruct.ValueSortedMap;
|
import ghidra.generic.util.datastruct.ValueSortedMap;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.listing.Library;
|
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
import ghidra.program.model.symbol.ExternalManager;
|
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.Trace.TraceStaticMappingChangeType;
|
import ghidra.trace.model.Trace.TraceStaticMappingChangeType;
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
|
@ -60,7 +56,6 @@ import ghidra.util.Msg;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
import ghidra.util.datastruct.ListenerSet;
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.VersionException;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
|
@ -85,199 +80,6 @@ import ghidra.util.task.TaskMonitor;
|
||||||
public class DebuggerStaticMappingServicePlugin extends Plugin
|
public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||||
implements DebuggerStaticMappingService, DomainFolderChangeAdapter {
|
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 {
|
protected class MappingEntry {
|
||||||
private final TraceStaticMapping mapping;
|
private final TraceStaticMapping mapping;
|
||||||
|
|
||||||
|
@ -937,184 +739,75 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||||
@Override
|
@Override
|
||||||
public void addMapping(TraceLocation from, ProgramLocation to, long length,
|
public void addMapping(TraceLocation from, ProgramLocation to, long length,
|
||||||
boolean truncateExisting) throws TraceConflictedMappingException {
|
boolean truncateExisting) throws TraceConflictedMappingException {
|
||||||
Program tp = to.getProgram();
|
try (UndoableTransaction tid =
|
||||||
if (tp instanceof TraceProgramView) {
|
UndoableTransaction.start(from.getTrace(), "Add mapping", true)) {
|
||||||
throw new IllegalArgumentException(
|
DebuggerStaticMappingUtils.addMapping(from, to, length, truncateExisting);
|
||||||
"Mapping destination cannot be a " + TraceProgramView.class.getSimpleName());
|
|
||||||
}
|
}
|
||||||
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,
|
@Override
|
||||||
long max) {
|
public void addMapping(MapEntry<?, ?> entry, boolean truncateExisting)
|
||||||
AddressSpace space = trace.getBaseAddressFactory().getAddressSpace(spaceName);
|
throws TraceConflictedMappingException {
|
||||||
if (space == null) {
|
try (UndoableTransaction tid =
|
||||||
return null;
|
UndoableTransaction.start(entry.getFromTrace(), "Add mapping", true)) {
|
||||||
|
DebuggerStaticMappingUtils.addMapping(entry, truncateExisting);
|
||||||
}
|
}
|
||||||
Address spaceMax = space.getMaxAddress();
|
}
|
||||||
if (Long.compareUnsigned(min, spaceMax.getOffset()) > 0) {
|
|
||||||
return null;
|
@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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (Long.compareUnsigned(max, spaceMax.getOffset()) > 0) {
|
}
|
||||||
return new AddressRangeImpl(space.getAddress(min), spaceMax);
|
|
||||||
|
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
|
@Override
|
||||||
public void addIdentityMapping(Trace from, Program toProgram, Range<Long> lifespan,
|
public void addIdentityMapping(Trace from, Program toProgram, Range<Long> lifespan,
|
||||||
boolean truncateExisting) {
|
boolean truncateExisting) {
|
||||||
try (UndoableTransaction tid =
|
try (UndoableTransaction tid =
|
||||||
UndoableTransaction.start(from, "Add identity mappings", false)) {
|
UndoableTransaction.start(from, "Add identity mappings", true)) {
|
||||||
doAddIdentityMapping(from, toProgram, lifespan, truncateExisting);
|
DebuggerStaticMappingUtils.addIdentityMapping(from, toProgram, lifespan,
|
||||||
tid.commit();
|
truncateExisting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(),
|
|
||||||
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
|
@Override
|
||||||
public void addModuleMappings(Collection<ModuleMapEntry> entries, TaskMonitor monitor,
|
public void addModuleMappings(Collection<ModuleMapEntry> entries, TaskMonitor monitor,
|
||||||
boolean truncateExisting) throws CancelledException {
|
boolean truncateExisting) throws CancelledException {
|
||||||
Map<Trace, Set<ModuleMapEntry>> byTrace = new LinkedHashMap<>();
|
addMappings(entries, monitor, truncateExisting, "Add module mappings");
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addSectionMappings(Collection<SectionMapEntry> entries,
|
public void addSectionMappings(Collection<SectionMapEntry> entries, TaskMonitor monitor,
|
||||||
TaskMonitor monitor, boolean truncateExisting) throws CancelledException {
|
boolean truncateExisting) throws CancelledException {
|
||||||
Map<Trace, Set<SectionMapEntry>> byTrace = new LinkedHashMap<>();
|
addMappings(entries, monitor, truncateExisting, "Add sections mappings");
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doAddSectionMappings(Trace trace, Collection<SectionMapEntry> entries,
|
@Override
|
||||||
TaskMonitor monitor, boolean truncateExisting) throws CancelledException {
|
public void addRegionMappings(Collection<RegionMapEntry> entries, TaskMonitor monitor,
|
||||||
for (SectionMapEntry ent : entries) {
|
boolean truncateExisting) throws CancelledException {
|
||||||
monitor.checkCanceled();
|
addMappings(entries, monitor, truncateExisting, "Add regions mappings");
|
||||||
try {
|
|
||||||
DebuggerStaticMappingUtils.addSectionMapping(ent.getSection(), ent.getProgram(),
|
|
||||||
ent.getBlock(), truncateExisting);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
Msg.error(this, "Could not add mapping " + ent + ": " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> T noTraceInfo() {
|
protected <T> T noTraceInfo() {
|
||||||
|
@ -1249,237 +942,85 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||||
return info.openMappedProgramsInView(set, Range.singleton(snap), failures);
|
return info.openMappedProgramsInView(set, Range.singleton(snap), failures);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String normalizePath(String path) {
|
protected Collection<? extends Program> orderCurrentFirst(
|
||||||
path = path.replace('\\', FileSystem.SEPARATOR_CHAR);
|
Collection<? extends Program> programs) {
|
||||||
while (path.startsWith(FileSystem.SEPARATOR)) {
|
if (programManager == null) {
|
||||||
path = path.substring(1);
|
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);
|
Set<Program> reordered = new LinkedHashSet<>(programs.size());
|
||||||
return folder.getProjectData().getFile(fullPath.toString());
|
reordered.add(currentProgram);
|
||||||
}
|
reordered.addAll(programs);
|
||||||
|
return reordered;
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<DomainFile> findProbableModulePrograms(TraceModule module) {
|
public Set<DomainFile> findProbableModulePrograms(TraceModule module) {
|
||||||
// TODO: Consider folders containing existing mapping destinations
|
return DebuggerStaticMappingUtils.findProbableModulePrograms(module, tool.getProject());
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Program> collectLibraries(Program seed, TaskMonitor monitor)
|
public ModuleMapProposal proposeModuleMap(TraceModule module, Program program) {
|
||||||
throws CancelledException {
|
return DebuggerStaticMappingProposals.proposeModuleMap(module, program);
|
||||||
Set<Program> result = new LinkedHashSet<>();
|
|
||||||
doCollectLibraries(seed.getDomainFile().getParent().getProjectData(), seed, result,
|
|
||||||
monitor);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PluginModuleMapProposal proposeModuleMap(TraceModule module, Program program) {
|
public ModuleMapProposal proposeModuleMap(TraceModule module,
|
||||||
return new PluginModuleMapProposal(module, program);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PluginModuleMapProposal proposeModuleMap(TraceModule module,
|
|
||||||
Collection<? extends Program> programs) {
|
Collection<? extends Program> programs) {
|
||||||
double bestScore = -1;
|
return DebuggerStaticMappingProposals.proposeModuleMap(module, orderCurrentFirst(programs));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<TraceModule, ModuleMapProposal> proposeModuleMaps(
|
public Map<TraceModule, ModuleMapProposal> proposeModuleMaps(
|
||||||
Collection<? extends TraceModule> modules, Collection<? extends Program> programs) {
|
Collection<? extends TraceModule> modules, Collection<? extends Program> programs) {
|
||||||
Map<TraceModule, ModuleMapProposal> result = new LinkedHashMap<>();
|
return DebuggerStaticMappingProposals.proposeModuleMaps(modules,
|
||||||
for (TraceModule module : modules) {
|
orderCurrentFirst(programs));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PluginSectionMapProposal proposeSectionMap(TraceSection section, Program program,
|
public SectionMapProposal proposeSectionMap(TraceSection section, Program program,
|
||||||
MemoryBlock block) {
|
MemoryBlock block) {
|
||||||
return new PluginSectionMapProposal(section, program, block);
|
return DebuggerStaticMappingProposals.proposeSectionMap(section, program, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PluginSectionMapProposal proposeSectionMap(TraceModule module, Program program) {
|
public SectionMapProposal proposeSectionMap(TraceModule module, Program program) {
|
||||||
return new PluginSectionMapProposal(module, program);
|
return DebuggerStaticMappingProposals.proposeSectionMap(module, program);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PluginSectionMapProposal proposeSectionMap(TraceModule module,
|
public SectionMapProposal proposeSectionMap(TraceModule module,
|
||||||
Collection<? extends Program> programs) {
|
Collection<? extends Program> programs) {
|
||||||
double bestScore = -1;
|
return DebuggerStaticMappingProposals.proposeSectionMap(module,
|
||||||
PluginSectionMapProposal bestMap = null;
|
orderCurrentFirst(programs));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<TraceModule, SectionMapProposal> proposeSectionMaps(
|
public Map<TraceModule, SectionMapProposal> proposeSectionMaps(
|
||||||
Collection<? extends TraceModule> modules, Collection<? extends Program> programs) {
|
Collection<? extends TraceModule> modules, Collection<? extends Program> programs) {
|
||||||
Map<TraceModule, SectionMapProposal> result = new LinkedHashMap<>();
|
return DebuggerStaticMappingProposals.proposeSectionMaps(modules,
|
||||||
for (TraceModule module : modules) {
|
orderCurrentFirst(programs));
|
||||||
String moduleName = getLastLower(module.getName());
|
}
|
||||||
Set<Program> probable = programs.stream()
|
|
||||||
.filter(p -> namesContain(p, moduleName))
|
@Override
|
||||||
.collect(Collectors.toSet());
|
public RegionMapProposal proposeRegionMap(TraceMemoryRegion region, Program program,
|
||||||
PluginSectionMapProposal map = proposeSectionMap(module, probable);
|
MemoryBlock block) {
|
||||||
if (map == null) {
|
return DebuggerStaticMappingProposals.proposeRegionMap(region, program, block);
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
result.put(module, map);
|
@Override
|
||||||
}
|
public RegionMapProposal proposeRegionMap(Collection<? extends TraceMemoryRegion> regions,
|
||||||
return result;
|
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;
|
package ghidra.app.plugin.core.debug.service.modules;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
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.address.*;
|
||||||
|
import ghidra.program.model.listing.Library;
|
||||||
import ghidra.program.model.listing.Program;
|
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.program.util.ProgramLocation;
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.model.DefaultTraceLocation;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.TraceLocation;
|
|
||||||
import ghidra.trace.model.modules.*;
|
import ghidra.trace.model.modules.*;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.exception.VersionException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public enum DebuggerStaticMappingUtils {
|
public enum DebuggerStaticMappingUtils {
|
||||||
;
|
;
|
||||||
|
@ -41,6 +48,124 @@ public enum DebuggerStaticMappingUtils {
|
||||||
return null;
|
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
|
* 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
|
* Note if the trace is backed by a Ghidra database, the caller must already have started a
|
||||||
* transaction on the relevant domain object.
|
* transaction on the relevant domain object.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param from the source trace location, including lifespan
|
* @param from the source trace location, including lifespan
|
||||||
* @param to the destination program location
|
* @param to the destination program location
|
||||||
* @param length the length of the mapped region
|
* @param length the length of the mapped region
|
||||||
|
@ -57,8 +181,7 @@ public enum DebuggerStaticMappingUtils {
|
||||||
* {@code truncateExisting} is false.
|
* {@code truncateExisting} is false.
|
||||||
*/
|
*/
|
||||||
public static void addMapping(TraceLocation from, ProgramLocation to, long length,
|
public static void addMapping(TraceLocation from, ProgramLocation to, long length,
|
||||||
boolean truncateExisting)
|
boolean truncateExisting) throws TraceConflictedMappingException {
|
||||||
throws TraceConflictedMappingException {
|
|
||||||
Program tp = to.getProgram();
|
Program tp = to.getProgram();
|
||||||
if (tp instanceof TraceProgramView) {
|
if (tp instanceof TraceProgramView) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
|
@ -67,75 +190,99 @@ public enum DebuggerStaticMappingUtils {
|
||||||
TraceStaticMappingManager manager = from.getTrace().getStaticMappingManager();
|
TraceStaticMappingManager manager = from.getTrace().getStaticMappingManager();
|
||||||
URL toURL = ProgramURLUtils.getUrlFromProgram(tp);
|
URL toURL = ProgramURLUtils.getUrlFromProgram(tp);
|
||||||
if (toURL == null) {
|
if (toURL == null) {
|
||||||
noProject(DebuggerStaticMappingService.class);
|
noProject(DebuggerStaticMappingUtils.class);
|
||||||
}
|
}
|
||||||
try {
|
Address fromAddress = from.getAddress();
|
||||||
Address start = from.getAddress();
|
Address toAddress = to.getByteAddress();
|
||||||
Address end = start.addNoWrap(length - 1);
|
long maxFromLengthMinus1 =
|
||||||
// Also check end in the destination
|
fromAddress.getAddressSpace().getMaxAddress().subtract(fromAddress);
|
||||||
Address toAddress = to.getAddress();
|
long maxToLengthMinus1 =
|
||||||
toAddress.addNoWrap(length - 1); // Anticipate possible AddressOverflow
|
toAddress.getAddressSpace().getMaxAddress().subtract(toAddress);
|
||||||
AddressRangeImpl range = new AddressRangeImpl(start, end);
|
if (Long.compareUnsigned(length - 1, maxFromLengthMinus1) > 0) {
|
||||||
if (truncateExisting) {
|
throw new IllegalArgumentException("Length would cause address overflow in trace");
|
||||||
long truncEnd = DBTraceUtils.lowerEndpoint(from.getLifespan()) - 1;
|
}
|
||||||
for (TraceStaticMapping existing : List
|
if (Long.compareUnsigned(length - 1, maxToLengthMinus1) > 0) {
|
||||||
.copyOf(manager.findAllOverlapping(range, from.getLifespan()))) {
|
throw new IllegalArgumentException("Length would cause address overflow in program");
|
||||||
existing.delete();
|
}
|
||||||
if (Long.compareUnsigned(existing.getStartSnap(), truncEnd) < 0) {
|
Address end = fromAddress.addWrap(length - 1);
|
||||||
manager.add(existing.getTraceAddressRange(),
|
// Also check end in the destination
|
||||||
Range.closed(existing.getStartSnap(), truncEnd),
|
AddressRangeImpl range = new AddressRangeImpl(fromAddress, end);
|
||||||
existing.getStaticProgramURL(), existing.getStaticAddress());
|
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, 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static void addMapping(MapEntry<?, ?> entry, boolean truncateExisting)
|
||||||
* Add a static mapping (relocation) from the given module to the given program
|
throws TraceConflictedMappingException {
|
||||||
*
|
TraceLocation fromLoc = entry.getFromTraceLocation();
|
||||||
* <p>
|
ProgramLocation toLoc = entry.getToProgramLocation();
|
||||||
* This is simply a shortcut and does not mean to imply that all mappings must represent module
|
long length = entry.getMappingLength();
|
||||||
* 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());
|
|
||||||
addMapping(fromLoc, toLoc, length, truncateExisting);
|
addMapping(fromLoc, toLoc, length, truncateExisting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static void addIdentityMapping(Trace from, Program toProgram, Range<Long> lifespan,
|
||||||
* Add a static mapping (relocation) from the given section to the given program memory block
|
boolean truncateExisting) {
|
||||||
*
|
Map<String, Address> mins = new HashMap<>();
|
||||||
* <p>
|
Map<String, Address> maxs = new HashMap<>();
|
||||||
* This is simply a shortcut and does not mean to imply that all mappings must represent section
|
for (AddressRange range : toProgram.getMemory().getAddressRanges()) {
|
||||||
* relocations. In most cases the lengths of the from and to objects match exactly, but this may
|
mins.compute(range.getAddressSpace().getName(), (n, min) -> {
|
||||||
* not be the case. Whatever the case, the minimum length is computed, and the start addresses
|
Address can = range.getMinAddress();
|
||||||
* are used as the location. The lifespan is that of the section's containing module.
|
if (min == null || can.compareTo(min) < 0) {
|
||||||
*
|
return can;
|
||||||
* @param from the source section
|
}
|
||||||
* @param toProgram the destination program
|
return min;
|
||||||
* @param to the destination memory block
|
});
|
||||||
* @see #addMapping(TraceLocation, ProgramLocation, long, boolean)
|
maxs.compute(range.getAddressSpace().getName(), (n, max) -> {
|
||||||
*/
|
Address can = range.getMaxAddress();
|
||||||
public static void addSectionMapping(TraceSection from, Program toProgram, MemoryBlock to,
|
if (max == null || can.compareTo(max) > 0) {
|
||||||
boolean truncateExisting) throws TraceConflictedMappingException {
|
return can;
|
||||||
TraceLocation fromLoc = new DefaultTraceLocation(from.getTrace(), null,
|
}
|
||||||
from.getModule().getLifespan(), from.getStart());
|
return max;
|
||||||
ProgramLocation toLoc = new ProgramLocation(toProgram, to.getStart());
|
});
|
||||||
long length = Math.min(from.getRange().getLength(), to.getSize());
|
}
|
||||||
addMapping(fromLoc, toLoc, length, truncateExisting);
|
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 java.util.Collection;
|
||||||
|
|
||||||
import ghidra.app.services.DebuggerStaticMappingService;
|
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.cmd.BackgroundCommand;
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
import ghidra.util.exception.CancelledException;
|
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 java.util.Collection;
|
||||||
|
|
||||||
import ghidra.app.services.DebuggerStaticMappingService;
|
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.cmd.BackgroundCommand;
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
import ghidra.util.exception.CancelledException;
|
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,201 +17,43 @@ package ghidra.app.plugin.core.debug.workflow;
|
||||||
|
|
||||||
import java.util.*;
|
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.*;
|
||||||
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapEntry;
|
import ghidra.app.services.ModuleMapProposal.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.framework.options.annotation.HelpInfo;
|
import ghidra.framework.options.annotation.HelpInfo;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
|
import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
|
||||||
import ghidra.trace.model.Trace.TraceModuleChangeType;
|
import ghidra.trace.model.Trace.TraceModuleChangeType;
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
import ghidra.trace.util.TraceChangeType;
|
||||||
import ghidra.trace.model.modules.TraceModule;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
@DebuggerBotInfo( //
|
@DebuggerBotInfo( //
|
||||||
description = "Map modules to open programs", //
|
description = "Map modules to open programs", //
|
||||||
details = "Monitors open traces and programs, attempting to map modules by \"best\" match.", //
|
details = "Monitors open traces and programs, attempting to map modules by \"best\" match.", //
|
||||||
help = @HelpInfo(anchor = "map_modules"), //
|
help = @HelpInfo(anchor = "map_modules"), //
|
||||||
enabledByDefault = true //
|
enabledByDefault = true //
|
||||||
)
|
)
|
||||||
public class MapModulesDebuggerBot implements DebuggerBot {
|
public class MapModulesDebuggerBot extends AbstractMapDebuggerBot {
|
||||||
protected class ForMapNewModulesTraceListener extends AbstractMultiToolTraceListener {
|
|
||||||
|
|
||||||
public ForMapNewModulesTraceListener(Trace trace) {
|
@Override
|
||||||
super(trace);
|
protected Collection<TraceChangeType<?, ?>> getChangeTypes() {
|
||||||
|
return List.of(TraceModuleChangeType.ADDED, TraceModuleChangeType.CHANGED,
|
||||||
/**
|
TraceMemoryRegionChangeType.ADDED, TraceMemoryRegionChangeType.CHANGED);
|
||||||
* 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
|
@Override
|
||||||
public void enable(DebuggerWorkflowServicePlugin wp) {
|
protected void doAnalysis(PluginTool tool, Trace trace, Set<Program> programs,
|
||||||
this.plugin = wp;
|
TaskMonitor monitor) throws CancelledException {
|
||||||
|
DebuggerStaticMappingService mappingService =
|
||||||
listeners.enable(wp);
|
tool.getService(DebuggerStaticMappingService.class);
|
||||||
for (PluginTool t : plugin.getProxyingPluginTools()) {
|
if (mappingService != null) {
|
||||||
DebuggerTraceManagerService traceManager =
|
Map<?, ModuleMapProposal> maps = mappingService
|
||||||
t.getService(DebuggerTraceManagerService.class);
|
.proposeModuleMaps(trace.getModuleManager().getAllModules(), programs);
|
||||||
if (traceManager == null) {
|
Collection<ModuleMapEntry> entries = MapProposal.flatten(maps.values());
|
||||||
continue;
|
entries = MapProposal.removeOverlapping(entries);
|
||||||
}
|
mappingService.addModuleMappings(entries, monitor, false);
|
||||||
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 {
|
|
||||||
DebuggerStaticMappingService mappingService =
|
|
||||||
t.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);
|
|
||||||
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,177 +17,41 @@ package ghidra.app.plugin.core.debug.workflow;
|
||||||
|
|
||||||
import java.util.*;
|
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.*;
|
||||||
import ghidra.app.services.DebuggerStaticMappingService.SectionMapEntry;
|
import ghidra.app.services.SectionMapProposal.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.framework.options.annotation.HelpInfo;
|
import ghidra.framework.options.annotation.HelpInfo;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.Trace.TraceSectionChangeType;
|
import ghidra.trace.model.Trace.TraceSectionChangeType;
|
||||||
import ghidra.trace.model.modules.TraceModule;
|
import ghidra.trace.util.TraceChangeType;
|
||||||
import ghidra.trace.model.modules.TraceSection;
|
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
@DebuggerBotInfo( //
|
@DebuggerBotInfo( //
|
||||||
description = "Map sections to open programs", //
|
description = "Map sections to open programs", //
|
||||||
details = "Monitors open traces and programs, attempting to map sections by \"best\" match.", //
|
details = "Monitors open traces and programs, attempting to map sections by \"best\" match.", //
|
||||||
help = @HelpInfo(anchor = "map_sections"), //
|
help = @HelpInfo(anchor = "map_sections"), //
|
||||||
enabledByDefault = false //
|
enabledByDefault = false //
|
||||||
)
|
)
|
||||||
public class MapSectionsDebuggerBot implements DebuggerBot {
|
public class MapSectionsDebuggerBot extends AbstractMapDebuggerBot {
|
||||||
|
|
||||||
protected class ForMapNewSectionsTraceListener extends AbstractMultiToolTraceListener {
|
@Override
|
||||||
public ForMapNewSectionsTraceListener(Trace trace) {
|
protected Collection<TraceChangeType<?, ?>> getChangeTypes() {
|
||||||
super(trace);
|
return List.of(TraceSectionChangeType.ADDED);
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public void enable(DebuggerWorkflowServicePlugin wp) {
|
protected void doAnalysis(PluginTool tool, Trace trace, Set<Program> programs,
|
||||||
this.plugin = wp;
|
TaskMonitor monitor) throws CancelledException {
|
||||||
|
DebuggerStaticMappingService mappingService =
|
||||||
listeners.enable(wp);
|
tool.getService(DebuggerStaticMappingService.class);
|
||||||
for (PluginTool t : plugin.getProxyingPluginTools()) {
|
if (mappingService != null) {
|
||||||
DebuggerTraceManagerService traceManager =
|
Map<?, SectionMapProposal> maps = mappingService
|
||||||
t.getService(DebuggerTraceManagerService.class);
|
.proposeSectionMaps(trace.getModuleManager().getAllModules(), programs);
|
||||||
if (traceManager == null) {
|
Collection<SectionMapEntry> entries = MapProposal.flatten(maps.values());
|
||||||
continue;
|
entries = MapProposal.removeOverlapping(entries);
|
||||||
}
|
mappingService.addSectionMappings(entries, monitor, false);
|
||||||
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 {
|
|
||||||
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);
|
|
||||||
mappingService.addSectionMappings(entries, monitor, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (CancelledException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
t.executeBackgroundCommand(cmd, trace);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,19 +16,21 @@
|
||||||
package ghidra.app.services;
|
package ghidra.app.services;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
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.framework.model.DomainFile;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.AddressSetView;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
import ghidra.trace.model.modules.*;
|
import ghidra.trace.model.modules.*;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.util.MathUtilities;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
@ -49,445 +51,7 @@ import ghidra.util.task.TaskMonitor;
|
||||||
public interface DebuggerStaticMappingService {
|
public interface DebuggerStaticMappingService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A proposed mapping of module to program
|
* A {@code (shift,view)} pair for describing sets of mapped addresses
|
||||||
*/
|
|
||||||
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
|
|
||||||
*/
|
*/
|
||||||
public class ShiftAndAddressSetView {
|
public class ShiftAndAddressSetView {
|
||||||
private final long shift;
|
private final long shift;
|
||||||
|
@ -528,10 +92,6 @@ public interface DebuggerStaticMappingService {
|
||||||
/**
|
/**
|
||||||
* Add a static mapping (relocation) from the given trace to the given program
|
* 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 from the source trace location, including lifespan
|
||||||
* @param to the destination program location
|
* @param to the destination program location
|
||||||
* @param length the length of the mapped region, where 0 indicates {@code 1 << 64}.
|
* @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,
|
void addIdentityMapping(Trace from, Program toProgram, Range<Long> lifespan,
|
||||||
boolean truncateExisting);
|
boolean truncateExisting);
|
||||||
|
|
||||||
/**
|
void addMapping(MapEntry<?, ?> entry, boolean truncateExisting)
|
||||||
* Add a static mapping (relocation) from the given module to the given program
|
throws TraceConflictedMappingException;
|
||||||
*
|
|
||||||
* <p>
|
void addMappings(Collection<? extends MapEntry<?, ?>> entries, TaskMonitor monitor,
|
||||||
* This is simply a shortcut and does not mean to imply that all mappings must represent module
|
boolean truncateExisting, String description) throws CancelledException;
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ======= >>>>>>> d694542c5 (GP-660: Put program filler back in. Need to performance test.) Add
|
* Add several static mappings (relocations)
|
||||||
* several static mappings (relocations)
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This will group the entries by trace and add each's entries in a single transaction. If any
|
* 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 monitor a monitor to cancel the operation
|
||||||
* @param truncateExisting true to delete or truncate the lifespan of overlapping entries
|
* @param truncateExisting true to delete or truncate the lifespan of overlapping entries
|
||||||
* @see #addMapping(TraceLocation, ProgramLocation, long, boolean)
|
* @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,
|
void addModuleMappings(Collection<ModuleMapEntry> entries, TaskMonitor monitor,
|
||||||
boolean truncateExisting) throws CancelledException;
|
boolean truncateExisting) throws CancelledException;
|
||||||
|
@ -602,6 +154,22 @@ public interface DebuggerStaticMappingService {
|
||||||
void addSectionMappings(Collection<SectionMapEntry> entries, TaskMonitor monitor,
|
void addSectionMappings(Collection<SectionMapEntry> entries, TaskMonitor monitor,
|
||||||
boolean truncateExisting) throws CancelledException;
|
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
|
* 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);
|
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
|
* Propose a module map for the given module to the given program
|
||||||
*
|
*
|
||||||
|
@ -866,4 +424,60 @@ public interface DebuggerStaticMappingService {
|
||||||
*/
|
*/
|
||||||
Map<TraceModule, SectionMapProposal> proposeSectionMaps(
|
Map<TraceModule, SectionMapProposal> proposeSectionMaps(
|
||||||
Collection<? extends TraceModule> modules, Collection<? extends Program> programs);
|
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 com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
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.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.test.ToyProgramBuilder;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
|
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
import help.screenshot.GhidraScreenShotGenerator;
|
import help.screenshot.GhidraScreenShotGenerator;
|
||||||
|
|
||||||
public class DebuggerRegionsPluginScreenShots extends GhidraScreenShotGenerator {
|
public class DebuggerRegionsPluginScreenShots extends GhidraScreenShotGenerator {
|
||||||
|
|
||||||
|
ProgramManager programManager;
|
||||||
DebuggerTraceManagerService traceManager;
|
DebuggerTraceManagerService traceManager;
|
||||||
DebuggerRegionsPlugin regionsPlugin;
|
DebuggerRegionsPlugin regionsPlugin;
|
||||||
|
DebuggerRegionsProvider regionsProvider;
|
||||||
ToyDBTraceBuilder tb;
|
ToyDBTraceBuilder tb;
|
||||||
|
Program progBash;
|
||||||
|
Program progLibC;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUpMine() throws Throwable {
|
public void setUpMine() throws Throwable {
|
||||||
|
programManager = addPlugin(tool, ProgramManagerPlugin.class);
|
||||||
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
|
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
|
||||||
regionsPlugin = addPlugin(tool, DebuggerRegionsPlugin.class);
|
regionsPlugin = addPlugin(tool, DebuggerRegionsPlugin.class);
|
||||||
|
|
||||||
|
regionsProvider = waitForComponentProvider(DebuggerRegionsProvider.class);
|
||||||
|
|
||||||
tb = new ToyDBTraceBuilder("echo", ToyProgramBuilder._X64);
|
tb = new ToyDBTraceBuilder("echo", ToyProgramBuilder._X64);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDownMine() {
|
public void tearDownMine() {
|
||||||
tb.close();
|
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
|
@Test
|
||||||
public void testCaptureDebuggerRegionsPlugin() throws Throwable {
|
public void testCaptureDebuggerRegionsPlugin() throws Throwable {
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
populateTrace();
|
||||||
long snap = tb.trace.getTimeManager().createSnapshot("First").getKey();
|
|
||||||
|
|
||||||
tb.trace.getMemoryManager()
|
traceManager.openTrace(tb.trace);
|
||||||
.addRegion("[400000:40ffff]", Range.atLeast(snap),
|
traceManager.activateTrace(tb.trace);
|
||||||
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));
|
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
captureIsolatedProvider(DebuggerRegionsProvider.class, 900, 300);
|
||||||
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;
|
DebuggerModulesPlugin modulesPlugin;
|
||||||
DebuggerModulesProvider modulesProvider;
|
DebuggerModulesProvider modulesProvider;
|
||||||
ToyDBTraceBuilder tb;
|
ToyDBTraceBuilder tb;
|
||||||
Program progEcho;
|
Program progBash;
|
||||||
Program progLibC;
|
Program progLibC;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -59,8 +59,8 @@ public class DebuggerModulesPluginScreenShots extends GhidraScreenShotGenerator
|
||||||
public void tearDownMine() {
|
public void tearDownMine() {
|
||||||
tb.close();
|
tb.close();
|
||||||
|
|
||||||
if (progEcho != null) {
|
if (progBash != null) {
|
||||||
progEcho.release(this);
|
progBash.release(this);
|
||||||
}
|
}
|
||||||
if (progLibC != null) {
|
if (progLibC != null) {
|
||||||
progLibC.release(this);
|
progLibC.release(this);
|
||||||
|
@ -111,16 +111,16 @@ public class DebuggerModulesPluginScreenShots extends GhidraScreenShotGenerator
|
||||||
lib.addSection("libc[.data]", ".data", tb.range(0x7fae0000, 0x7faeffff));
|
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);
|
progLibC = createDefaultProgram("libc.so.6", ProgramBuilder._X64, this);
|
||||||
|
|
||||||
try (UndoableTransaction tid = UndoableTransaction.start(progEcho, "Add memory", true)) {
|
try (UndoableTransaction tid = UndoableTransaction.start(progBash, "Add memory", true)) {
|
||||||
progEcho.setImageBase(addr(progEcho, 0x00400000), true);
|
progBash.setImageBase(addr(progBash, 0x00400000), true);
|
||||||
progEcho.getMemory()
|
progBash.getMemory()
|
||||||
.createInitializedBlock(".text", addr(progEcho, 0x00400000), 0x10000, (byte) 0,
|
.createInitializedBlock(".text", addr(progBash, 0x00400000), 0x10000, (byte) 0,
|
||||||
TaskMonitor.DUMMY, false);
|
TaskMonitor.DUMMY, false);
|
||||||
progEcho.getMemory()
|
progBash.getMemory()
|
||||||
.createInitializedBlock(".data", addr(progEcho, 0x00600000), 0x10000, (byte) 0,
|
.createInitializedBlock(".data", addr(progBash, 0x00600000), 0x10000, (byte) 0,
|
||||||
TaskMonitor.DUMMY, false);
|
TaskMonitor.DUMMY, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,13 +135,13 @@ public class DebuggerModulesPluginScreenShots extends GhidraScreenShotGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
root.createFile("trace", tb.trace, TaskMonitor.DUMMY);
|
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);
|
root.createFile("libc.so.6", progLibC, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
|
|
||||||
programManager.openProgram(progEcho);
|
programManager.openProgram(progBash);
|
||||||
programManager.openProgram(progLibC);
|
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.modules.DebuggerStaticMappingServicePlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||||
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
||||||
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapEntry;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapProposal;
|
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
|
||||||
import ghidra.app.services.ProgramManager;
|
|
||||||
import ghidra.framework.model.DomainFolder;
|
import ghidra.framework.model.DomainFolder;
|
||||||
import ghidra.program.database.ProgramBuilder;
|
import ghidra.program.database.ProgramBuilder;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
@ -130,7 +128,7 @@ public class DebuggerStaticMappingPluginScreenShots extends GhidraScreenShotGene
|
||||||
Map<TraceModule, ModuleMapProposal> proposal =
|
Map<TraceModule, ModuleMapProposal> proposal =
|
||||||
mappingService.proposeModuleMaps(tb.trace.getModuleManager().getAllModules(),
|
mappingService.proposeModuleMaps(tb.trace.getModuleManager().getAllModules(),
|
||||||
List.of(programManager.getAllOpenPrograms()));
|
List.of(programManager.getAllOpenPrograms()));
|
||||||
Collection<ModuleMapEntry> entries = ModuleMapProposal.flatten(proposal.values());
|
Collection<ModuleMapEntry> entries = MapProposal.flatten(proposal.values());
|
||||||
mappingService.addModuleMappings(entries, TaskMonitor.DUMMY, false);
|
mappingService.addModuleMappings(entries, TaskMonitor.DUMMY, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.memory;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.*;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -26,24 +26,63 @@ import com.google.common.collect.Range;
|
||||||
|
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
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.DebuggerListingPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
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.plugin.core.debug.gui.memory.DebuggerRegionsProvider.RegionTableColumns;
|
||||||
|
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
|
||||||
import ghidra.program.model.address.AddressSet;
|
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.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.memory.*;
|
import ghidra.trace.model.memory.*;
|
||||||
|
import ghidra.trace.model.modules.TraceStaticMapping;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
|
|
||||||
DebuggerRegionsProvider provider;
|
DebuggerRegionsProvider provider;
|
||||||
|
|
||||||
|
protected TraceMemoryRegion regionExeText;
|
||||||
|
protected TraceMemoryRegion regionExeData;
|
||||||
|
protected TraceMemoryRegion regionLibText;
|
||||||
|
protected TraceMemoryRegion regionLibData;
|
||||||
|
|
||||||
|
protected MemoryBlock blockExeText;
|
||||||
|
protected MemoryBlock blockExeData;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUpRegionsTest() throws Exception {
|
public void setUpRegionsTest() throws Exception {
|
||||||
addPlugin(tool, DebuggerRegionsPlugin.class);
|
addPlugin(tool, DebuggerRegionsPlugin.class);
|
||||||
provider = waitForComponentProvider(DebuggerRegionsProvider.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
|
@Test
|
||||||
public void testNoTraceEmpty() throws Exception {
|
public void testNoTraceEmpty() throws Exception {
|
||||||
assertEquals(0, provider.regionTableModel.getModelData().size());
|
assertEquals(0, provider.regionTableModel.getModelData().size());
|
||||||
|
@ -198,6 +237,92 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress()));
|
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
|
@Test
|
||||||
public void testActionSelectAddresses() throws Exception {
|
public void testActionSelectAddresses() throws Exception {
|
||||||
addPlugin(tool, DebuggerListingPlugin.class);
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
|
|
@ -30,15 +30,16 @@ import docking.widgets.filechooser.GhidraFileChooser;
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import generic.test.category.NightlyCategory;
|
import generic.test.category.NightlyCategory;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
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.DebuggerResources.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
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.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.DebuggerModuleMapProposalDialog.ModuleMapTableColumns;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
||||||
import ghidra.app.services.DebuggerListingService;
|
import ghidra.app.services.DebuggerListingService;
|
||||||
import ghidra.app.services.DebuggerStaticMappingService.ModuleMapEntry;
|
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
|
||||||
import ghidra.app.services.DebuggerStaticMappingService.SectionMapEntry;
|
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
|
||||||
import ghidra.app.services.TraceRecorder;
|
import ghidra.app.services.TraceRecorder;
|
||||||
import ghidra.dbg.attributes.TargetPrimitiveDataType.DefaultTargetPrimitiveDataType;
|
import ghidra.dbg.attributes.TargetPrimitiveDataType.DefaultTargetPrimitiveDataType;
|
||||||
import ghidra.dbg.attributes.TargetPrimitiveDataType.PrimitiveKind;
|
import ghidra.dbg.attributes.TargetPrimitiveDataType.PrimitiveKind;
|
||||||
|
@ -46,7 +47,6 @@ import ghidra.dbg.model.TestTargetModule;
|
||||||
import ghidra.dbg.model.TestTargetTypedefDataType;
|
import ghidra.dbg.model.TestTargetTypedefDataType;
|
||||||
import ghidra.dbg.util.TargetDataTypeConverter;
|
import ghidra.dbg.util.TargetDataTypeConverter;
|
||||||
import ghidra.framework.main.DataTreeDialog;
|
import ghidra.framework.main.DataTreeDialog;
|
||||||
import ghidra.framework.store.LockException;
|
|
||||||
import ghidra.plugin.importer.ImporterPlugin;
|
import ghidra.plugin.importer.ImporterPlugin;
|
||||||
import ghidra.program.model.address.AddressOverflowException;
|
import ghidra.program.model.address.AddressOverflowException;
|
||||||
import ghidra.program.model.address.AddressSet;
|
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 {
|
MemoryConflictException, AddressOverflowException, CancelledException {
|
||||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
|
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
|
||||||
return program.getMemory()
|
return program.getMemory()
|
||||||
|
@ -232,13 +232,13 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
DebuggerSectionMapProposalDialog propDialog =
|
DebuggerSectionMapProposalDialog propDialog =
|
||||||
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
|
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
|
||||||
clickTableCell(propDialog.table, 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
|
clickTableCell(propDialog.getTable(), 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
|
||||||
|
|
||||||
DebuggerBlockChooserDialog blockDialog =
|
DebuggerBlockChooserDialog blockDialog =
|
||||||
waitForDialogComponent(DebuggerBlockChooserDialog.class);
|
waitForDialogComponent(DebuggerBlockChooserDialog.class);
|
||||||
|
|
||||||
assertEquals(1, blockDialog.tableModel.getRowCount());
|
assertEquals(1, blockDialog.getTableModel().getRowCount());
|
||||||
MemoryBlockRow row = blockDialog.tableModel.getModelData().get(0);
|
MemoryBlockRow row = blockDialog.getTableModel().getModelData().get(0);
|
||||||
assertEquals(program, row.getProgram());
|
assertEquals(program, row.getProgram());
|
||||||
assertEquals(block, row.getBlock());
|
assertEquals(block, row.getBlock());
|
||||||
// NOTE: Other getters should be tested in a separate MemoryBlockRowTest
|
// NOTE: Other getters should be tested in a separate MemoryBlockRowTest
|
||||||
|
@ -399,19 +399,19 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
DebuggerModuleMapProposalDialog propDialog =
|
DebuggerModuleMapProposalDialog propDialog =
|
||||||
waitForDialogComponent(DebuggerModuleMapProposalDialog.class);
|
waitForDialogComponent(DebuggerModuleMapProposalDialog.class);
|
||||||
|
|
||||||
List<ModuleMapEntry> proposal = propDialog.tableModel.getModelData();
|
List<ModuleMapEntry> proposal = propDialog.getTableModel().getModelData();
|
||||||
ModuleMapEntry entry = Unique.assertOne(proposal);
|
ModuleMapEntry entry = Unique.assertOne(proposal);
|
||||||
assertEquals(modExe, entry.getModule());
|
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);
|
DataTreeDialog programDialog = waitForDialogComponent(DataTreeDialog.class);
|
||||||
assertEquals(program.getDomainFile(), programDialog.getDomainFile());
|
assertEquals(program.getDomainFile(), programDialog.getDomainFile());
|
||||||
|
|
||||||
pressButtonByText(programDialog, "OK", true);
|
pressButtonByText(programDialog, "OK", true);
|
||||||
|
|
||||||
assertEquals(program, entry.getProgram());
|
assertEquals(program, entry.getToProgram());
|
||||||
// TODO: Test the changed case
|
// TODO: Test the changed case
|
||||||
|
|
||||||
Collection<? extends TraceStaticMapping> mappings =
|
Collection<? extends TraceStaticMapping> mappings =
|
||||||
|
@ -429,6 +429,9 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
|
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: testActionMapModulesTo
|
||||||
|
// TODO: testActionMapModuleTo
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActionMapSections() throws Exception {
|
public void testActionMapSections() throws Exception {
|
||||||
assertFalse(modulesProvider.actionMapSections.isEnabled());
|
assertFalse(modulesProvider.actionMapSections.isEnabled());
|
||||||
|
@ -461,16 +464,16 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
DebuggerSectionMapProposalDialog propDialog =
|
DebuggerSectionMapProposalDialog propDialog =
|
||||||
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
|
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
|
||||||
|
|
||||||
List<SectionMapEntry> proposal = propDialog.tableModel.getModelData();
|
List<SectionMapEntry> proposal = propDialog.getTableModel().getModelData();
|
||||||
SectionMapEntry entry = Unique.assertOne(proposal);
|
SectionMapEntry entry = Unique.assertOne(proposal);
|
||||||
assertEquals(secExeText, entry.getSection());
|
assertEquals(secExeText, entry.getSection());
|
||||||
assertEquals(block, entry.getBlock());
|
assertEquals(block, entry.getBlock());
|
||||||
|
|
||||||
clickTableCell(propDialog.table, 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
|
clickTableCell(propDialog.getTable(), 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
|
||||||
|
|
||||||
DebuggerBlockChooserDialog blockDialog =
|
DebuggerBlockChooserDialog blockDialog =
|
||||||
waitForDialogComponent(DebuggerBlockChooserDialog.class);
|
waitForDialogComponent(DebuggerBlockChooserDialog.class);
|
||||||
MemoryBlockRow row = Unique.assertOne(blockDialog.tableModel.getModelData());
|
MemoryBlockRow row = Unique.assertOne(blockDialog.getTableModel().getModelData());
|
||||||
assertEquals(block, row.getBlock());
|
assertEquals(block, row.getBlock());
|
||||||
|
|
||||||
pressButtonByText(blockDialog, "OK", true);
|
pressButtonByText(blockDialog, "OK", true);
|
||||||
|
@ -492,6 +495,9 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
|
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: testActionMapSectionsTo
|
||||||
|
// TODO: testActionMapSectionTo
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActionSelectAddresses() throws Exception {
|
public void testActionSelectAddresses() throws Exception {
|
||||||
assertFalse(modulesProvider.actionSelectAddresses.isEnabled());
|
assertFalse(modulesProvider.actionSelectAddresses.isEnabled());
|
||||||
|
|
|
@ -33,7 +33,10 @@ import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
|
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
import ghidra.trace.model.modules.*;
|
import ghidra.trace.model.modules.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
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: 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