mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
Merge remote-tracking branch 'origin/Ghidra_9.2'
This commit is contained in:
commit
b605ad0327
25 changed files with 408 additions and 252 deletions
|
@ -35,6 +35,7 @@ public interface Navigatable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commands this navigatable to goto (display) the given program and location
|
* Commands this navigatable to goto (display) the given program and location
|
||||||
|
* @param program the program
|
||||||
*
|
*
|
||||||
* @param location the location in that program to display
|
* @param location the location in that program to display
|
||||||
* @return true if the goto was successful
|
* @return true if the goto was successful
|
||||||
|
|
|
@ -217,7 +217,7 @@ public class AddressTable {
|
||||||
* @param end end index (inclusive)
|
* @param end end index (inclusive)
|
||||||
* @param createIndex don't create index if false
|
* @param createIndex don't create index if false
|
||||||
* @param autoLabel true if labels should be created on the table
|
* @param autoLabel true if labels should be created on the table
|
||||||
* @return
|
* @return true if tablecreated else false
|
||||||
*/
|
*/
|
||||||
public boolean makeTable(Program program, int start, int end, boolean createIndex,
|
public boolean makeTable(Program program, int start, int end, boolean createIndex,
|
||||||
boolean autoLabel) {
|
boolean autoLabel) {
|
||||||
|
@ -236,13 +236,17 @@ public class AddressTable {
|
||||||
|
|
||||||
//TODO: Do I need to do something special for the 3 byte pointers or will it know
|
//TODO: Do I need to do something special for the 3 byte pointers or will it know
|
||||||
// how to make it automatically?
|
// how to make it automatically?
|
||||||
|
DataTypeManager dtm = program.getDataTypeManager();
|
||||||
if (shiftedAddr) {
|
if (shiftedAddr) {
|
||||||
adt = ShiftedAddressDataType.dataType;
|
adt = ShiftedAddressDataType.dataType;
|
||||||
}
|
}
|
||||||
else {
|
else if (addrSize == program.getDefaultPointerSize()) {
|
||||||
adt = new PointerDataType(DataType.DEFAULT, addrSize);
|
adt = new PointerDataType(DataType.DEFAULT, dtm);
|
||||||
}
|
}
|
||||||
adt = program.getDataTypeManager().resolve(adt, null);
|
else {
|
||||||
|
adt = new PointerDataType(DataType.DEFAULT, addrSize, dtm);
|
||||||
|
}
|
||||||
|
adt = dtm.resolve(adt, null);
|
||||||
|
|
||||||
Address newAddress = currentAddress;
|
Address newAddress = currentAddress;
|
||||||
|
|
||||||
|
|
|
@ -691,15 +691,9 @@ public class BundleHost {
|
||||||
public void activateAll(Collection<GhidraBundle> bundles, TaskMonitor monitor,
|
public void activateAll(Collection<GhidraBundle> bundles, TaskMonitor monitor,
|
||||||
PrintWriter console) {
|
PrintWriter console) {
|
||||||
|
|
||||||
List<GhidraBundle> availableBundles = getGhidraBundles().stream()
|
BundleDependencyGraph dependencyGraph = new BundleDependencyGraph(bundles, monitor);
|
||||||
.filter(GhidraBundle::isEnabled)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
BundleDependencyGraph dependencyGraph =
|
|
||||||
new BundleDependencyGraph(availableBundles, bundles, monitor);
|
|
||||||
|
|
||||||
monitor.setMaximum(dependencyGraph.vertexSet().size());
|
monitor.setMaximum(dependencyGraph.vertexSet().size());
|
||||||
|
|
||||||
for (GhidraBundle bundle : dependencyGraph.inTopologicalOrder()) {
|
for (GhidraBundle bundle : dependencyGraph.inTopologicalOrder()) {
|
||||||
if (monitor.isCancelled()) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
|
@ -728,21 +722,25 @@ public class BundleHost {
|
||||||
*/
|
*/
|
||||||
public void activateInStages(Collection<GhidraBundle> bundles, TaskMonitor monitor,
|
public void activateInStages(Collection<GhidraBundle> bundles, TaskMonitor monitor,
|
||||||
PrintWriter console) {
|
PrintWriter console) {
|
||||||
List<GhidraBundle> bundlesRemaining = new ArrayList<>(bundles);
|
Map<GhidraBundle, List<BundleRequirement>> requirementMap = new HashMap<>();
|
||||||
|
for (GhidraBundle bundle : bundles) {
|
||||||
|
try {
|
||||||
|
requirementMap.put(bundle, bundle.getAllRequirements());
|
||||||
|
}
|
||||||
|
catch (GhidraBundleException e) {
|
||||||
|
fireBundleException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<GhidraBundle> bundlesRemaining = new ArrayList<>(requirementMap.keySet());
|
||||||
|
|
||||||
monitor.setMaximum(bundlesRemaining.size());
|
monitor.setMaximum(bundlesRemaining.size());
|
||||||
while (!bundlesRemaining.isEmpty() && !monitor.isCancelled()) {
|
while (!bundlesRemaining.isEmpty() && !monitor.isCancelled()) {
|
||||||
List<GhidraBundle> resolvableBundles = bundlesRemaining.stream().filter(bundle -> {
|
List<GhidraBundle> resolvableBundles = bundlesRemaining.stream()
|
||||||
try {
|
.filter(bundle -> canResolveAll(requirementMap.get(bundle)))
|
||||||
return canResolveAll(bundle.getAllRequirements());
|
.collect(Collectors.toList());
|
||||||
}
|
|
||||||
catch (GhidraBundleException e) {
|
|
||||||
// failure in last round will set fireBundleException
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
if (resolvableBundles.isEmpty()) {
|
if (resolvableBundles.isEmpty()) {
|
||||||
// final round, try everything we couldn't resolve to generate errors
|
// final round, try everything that hasn't already been eliminated.
|
||||||
|
// this can generate helpful error messages
|
||||||
resolvableBundles = bundlesRemaining;
|
resolvableBundles = bundlesRemaining;
|
||||||
bundlesRemaining = Collections.emptyList();
|
bundlesRemaining = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
@ -927,18 +925,29 @@ public class BundleHost {
|
||||||
// exists only to be distinguished by id
|
// exists only to be distinguished by id
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class BundleDependencyGraph
|
/**
|
||||||
extends DirectedMultigraph<GhidraBundle, Dependency> {
|
* Utility class to build a dependency graph from bundles where capabilities map to requirements.
|
||||||
|
*/
|
||||||
|
private class BundleDependencyGraph extends DirectedMultigraph<GhidraBundle, Dependency> {
|
||||||
final Map<GhidraBundle, List<BundleCapability>> capabilityMap = new HashMap<>();
|
final Map<GhidraBundle, List<BundleCapability>> capabilityMap = new HashMap<>();
|
||||||
final List<GhidraBundle> availableBundles;
|
final List<GhidraBundle> availableBundles;
|
||||||
final TaskMonitor monitor;
|
final TaskMonitor monitor;
|
||||||
|
|
||||||
BundleDependencyGraph(List<GhidraBundle> availableBundles,
|
BundleDependencyGraph(Collection<GhidraBundle> startingBundles, TaskMonitor monitor) {
|
||||||
Collection<GhidraBundle> startingBundles, TaskMonitor monitor) {
|
|
||||||
super(null, null, false);
|
super(null, null, false);
|
||||||
this.availableBundles = availableBundles;
|
|
||||||
this.monitor = monitor;
|
this.monitor = monitor;
|
||||||
|
|
||||||
|
// maintain a list of bundles available for resolution, starting with all of the enabled bundles
|
||||||
|
this.availableBundles = new ArrayList<>();
|
||||||
|
for (GhidraBundle bundle : getGhidraBundles()) {
|
||||||
|
if (bundle.isEnabled()) {
|
||||||
|
addToAvailable(bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// An edge A->B indicates that the capabilities of A resolve some requirement(s) of B
|
||||||
|
|
||||||
|
// "front" accumulates bundles and links to bundles already in the graph that they provide capabilities for.
|
||||||
|
// e.g. if front[A]=[B,...] then A->B, B is already in the graph, and we will add A next iteration.
|
||||||
Map<GhidraBundle, Set<GhidraBundle>> front = new HashMap<>();
|
Map<GhidraBundle, Set<GhidraBundle>> front = new HashMap<>();
|
||||||
for (GhidraBundle bundle : startingBundles) {
|
for (GhidraBundle bundle : startingBundles) {
|
||||||
front.put(bundle, null);
|
front.put(bundle, null);
|
||||||
|
@ -951,6 +960,7 @@ public class BundleHost {
|
||||||
for (GhidraBundle bundle : front.keySet()) {
|
for (GhidraBundle bundle : front.keySet()) {
|
||||||
resolve(bundle, newFront);
|
resolve(bundle, newFront);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBackEdges(newFront);
|
handleBackEdges(newFront);
|
||||||
front = newFront;
|
front = newFront;
|
||||||
}
|
}
|
||||||
|
@ -969,7 +979,9 @@ public class BundleHost {
|
||||||
GhidraBundle source = entry.getKey();
|
GhidraBundle source = entry.getKey();
|
||||||
if (containsVertex(source)) {
|
if (containsVertex(source)) {
|
||||||
for (GhidraBundle destination : entry.getValue()) {
|
for (GhidraBundle destination : entry.getValue()) {
|
||||||
addEdge(source, destination, new Dependency());
|
if (source != destination) {
|
||||||
|
addEdge(source, destination, new Dependency());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
newFrontIter.remove();
|
newFrontIter.remove();
|
||||||
}
|
}
|
||||||
|
@ -979,31 +991,48 @@ public class BundleHost {
|
||||||
void addFront(Map<GhidraBundle, Set<GhidraBundle>> front) {
|
void addFront(Map<GhidraBundle, Set<GhidraBundle>> front) {
|
||||||
for (Entry<GhidraBundle, Set<GhidraBundle>> e : front.entrySet()) {
|
for (Entry<GhidraBundle, Set<GhidraBundle>> e : front.entrySet()) {
|
||||||
GhidraBundle source = e.getKey();
|
GhidraBundle source = e.getKey();
|
||||||
availableBundles.add(source);
|
if (addToAvailable(source)) {
|
||||||
addVertex(source);
|
addVertex(source);
|
||||||
Set<GhidraBundle> destinations = e.getValue();
|
Set<GhidraBundle> destinations = e.getValue();
|
||||||
if (destinations != null) {
|
if (destinations != null) {
|
||||||
for (GhidraBundle destination : destinations) {
|
for (GhidraBundle destination : destinations) {
|
||||||
addEdge(source, destination, new Dependency());
|
addEdge(source, destination, new Dependency());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean addToAvailable(GhidraBundle bundle) {
|
||||||
|
try {
|
||||||
|
capabilityMap.put(bundle, bundle.getAllCapabilities());
|
||||||
|
availableBundles.add(bundle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (GhidraBundleException ex) {
|
||||||
|
fireBundleException(ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate newFront with edges depBundle -> bundle,
|
||||||
|
// where depBundle has a capability that resolves a requirement of bundle
|
||||||
void resolve(GhidraBundle bundle, Map<GhidraBundle, Set<GhidraBundle>> newFront) {
|
void resolve(GhidraBundle bundle, Map<GhidraBundle, Set<GhidraBundle>> newFront) {
|
||||||
List<BundleRequirement> requirements;
|
List<BundleRequirement> requirements;
|
||||||
try {
|
try {
|
||||||
requirements = new ArrayList<>(bundle.getAllRequirements());
|
requirements = new ArrayList<>(bundle.getAllRequirements());
|
||||||
|
if (requirements.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (GhidraBundleException e) {
|
catch (GhidraBundleException e) {
|
||||||
throw new RuntimeException(e);
|
fireBundleException(e);
|
||||||
}
|
removeVertex(bundle);
|
||||||
if (requirements.isEmpty()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (GhidraBundle depBundle : availableBundles) {
|
for (GhidraBundle depBundle : availableBundles) {
|
||||||
for (BundleCapability capability : getCapabilities(depBundle)) {
|
for (BundleCapability capability : capabilityMap.get(depBundle)) {
|
||||||
if (monitor.isCancelled()) {
|
if (monitor.isCancelled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1020,18 +1049,8 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// if requirements remain, some will be resolved by system
|
||||||
|
// and others will generate helpful errors for the user during activation
|
||||||
List<BundleCapability> getCapabilities(GhidraBundle bundle) {
|
|
||||||
return capabilityMap.computeIfAbsent(bundle, b -> {
|
|
||||||
try {
|
|
||||||
return b.getAllCapabilities();
|
|
||||||
}
|
|
||||||
catch (GhidraBundleException e) {
|
|
||||||
Msg.error(this, "getting capabilities", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,7 @@ import ghidra.app.services.ConsoleService;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.preferences.Preferences;
|
import ghidra.framework.preferences.Preferences;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.*;
|
||||||
import ghidra.util.Msg;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.filechooser.GhidraFileChooserModel;
|
import ghidra.util.filechooser.GhidraFileChooserModel;
|
||||||
import ghidra.util.filechooser.GhidraFileFilter;
|
import ghidra.util.filechooser.GhidraFileFilter;
|
||||||
|
@ -321,14 +320,18 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyTableRowChanged(BundleStatus status) {
|
private void notifyTableRowChanged(BundleStatus status) {
|
||||||
int modelRowIndex = bundleStatusTableModel.getRowIndex(status);
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
int viewRowIndex = filterPanel.getViewRow(modelRowIndex);
|
int modelRowIndex = bundleStatusTableModel.getRowIndex(status);
|
||||||
bundleStatusTable
|
int viewRowIndex = filterPanel.getViewRow(modelRowIndex);
|
||||||
.notifyTableChanged(new TableModelEvent(bundleStatusTableModel, viewRowIndex));
|
bundleStatusTable
|
||||||
|
.notifyTableChanged(new TableModelEvent(bundleStatusTableModel, viewRowIndex));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyTableDataChanged() {
|
private void notifyTableDataChanged() {
|
||||||
bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusTableModel));
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
|
bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusTableModel));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -374,24 +377,22 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor monitor) throws CancelledException {
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
deactivateBundlesTask.run(monitor);
|
deactivateBundlesTask.run(monitor);
|
||||||
if (!monitor.isCancelled()) {
|
monitor.checkCanceled();
|
||||||
// partition bundles into system (bundles.get(true)) and non-system (bundles.get(false)).
|
// partition bundles into system (bundles.get(true)) and non-system (bundles.get(false)).
|
||||||
Map<Boolean, List<GhidraBundle>> bundles = statuses.stream()
|
Map<Boolean, List<GhidraBundle>> bundles = statuses.stream()
|
||||||
.map(bs -> bundleHost.getExistingGhidraBundle(bs.getFile()))
|
.map(bs -> bundleHost.getExistingGhidraBundle(bs.getFile()))
|
||||||
.collect(Collectors.partitioningBy(GhidraBundle::isSystemBundle));
|
.collect(Collectors.partitioningBy(GhidraBundle::isSystemBundle));
|
||||||
|
|
||||||
List<GhidraBundle> systemBundles = bundles.get(true);
|
List<GhidraBundle> systemBundles = bundles.get(true);
|
||||||
if (!systemBundles.isEmpty()) {
|
if (!systemBundles.isEmpty()) {
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
StringBuilder buff = new StringBuilder();
|
||||||
for (GhidraBundle bundle : systemBundles) {
|
for (GhidraBundle bundle : systemBundles) {
|
||||||
stringBuilder.append(Path.toPathString(bundle.getFile()) + "\n");
|
buff.append(Path.toPathString(bundle.getFile()) + "\n");
|
||||||
}
|
|
||||||
Msg.showWarn(this, BundleStatusComponentProvider.this.getComponent(),
|
|
||||||
"Unabled to remove",
|
|
||||||
"System bundles cannot be removed:\n" + stringBuilder.toString());
|
|
||||||
}
|
}
|
||||||
bundleHost.remove(bundles.get(false));
|
Msg.showWarn(this, BundleStatusComponentProvider.this.getComponent(),
|
||||||
|
"Unabled to remove", "System bundles cannot be removed:\n" + buff.toString());
|
||||||
}
|
}
|
||||||
|
bundleHost.remove(bundles.get(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,18 @@ public class GhidraBundleException extends OSGiException {
|
||||||
this.bundleLocation = bundleLocation;
|
this.bundleLocation = bundleLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new exception originating with the bundle having location identifier {@code bundleLocation}.
|
||||||
|
*
|
||||||
|
* @param bundleLocation the bundle location identifier (since no bundle is available)
|
||||||
|
* @param msg a contextual message
|
||||||
|
*/
|
||||||
|
public GhidraBundleException(String bundleLocation, String msg) {
|
||||||
|
super(msg);
|
||||||
|
this.bundle = null;
|
||||||
|
this.bundleLocation = bundleLocation;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the associated bundle, or null. If null, the bundle location identifier will be non-null
|
* @return the associated bundle, or null. If null, the bundle location identifier will be non-null
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,11 +23,13 @@ import java.util.jar.Manifest;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.felix.framework.util.manifestparser.ManifestParser;
|
import org.apache.felix.framework.util.manifestparser.ManifestParser;
|
||||||
|
import org.osgi.framework.BundleException;
|
||||||
import org.osgi.framework.wiring.BundleCapability;
|
import org.osgi.framework.wiring.BundleCapability;
|
||||||
import org.osgi.framework.wiring.BundleRequirement;
|
import org.osgi.framework.wiring.BundleRequirement;
|
||||||
|
|
||||||
import aQute.bnd.osgi.Jar;
|
import aQute.bnd.osgi.Jar;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
|
import ghidra.util.exception.AssertException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proxy to an ordinary OSGi Jar bundle. {@link GhidraJarBundle#build(PrintWriter)} does nothing.
|
* Proxy to an ordinary OSGi Jar bundle. {@link GhidraJarBundle#build(PrintWriter)} does nothing.
|
||||||
|
@ -64,9 +66,12 @@ public class GhidraJarBundle extends GhidraBundle {
|
||||||
return bundleLocation;
|
return bundleLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ManifestParser createManifestParser() {
|
protected ManifestParser createManifestParser() throws GhidraBundleException {
|
||||||
try (Jar jar = new Jar(file.getFile(true))) {
|
try (Jar jar = new Jar(file.getFile(true))) {
|
||||||
Manifest manifest = jar.getManifest();
|
Manifest manifest = jar.getManifest();
|
||||||
|
if (manifest == null) {
|
||||||
|
throw new GhidraBundleException(bundleLocation, "jar bundle with no manifest");
|
||||||
|
}
|
||||||
Attributes mainAttributes = manifest.getMainAttributes();
|
Attributes mainAttributes = manifest.getMainAttributes();
|
||||||
Map<String, Object> headerMap = mainAttributes.entrySet()
|
Map<String, Object> headerMap = mainAttributes.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -74,8 +79,14 @@ public class GhidraJarBundle extends GhidraBundle {
|
||||||
Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString()));
|
Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString()));
|
||||||
return new ManifestParser(null, null, null, headerMap);
|
return new ManifestParser(null, null, null, headerMap);
|
||||||
}
|
}
|
||||||
|
catch (BundleException e) {
|
||||||
|
throw new GhidraBundleException(bundleLocation, "parsing manifest", e);
|
||||||
|
}
|
||||||
|
catch (GhidraBundleException e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new AssertException("Unexpected exception while parsing manifest", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -713,11 +713,31 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable e) {
|
catch (Throwable e) {
|
||||||
Msg.error(this, "Exception searching ", e);
|
Msg.error(this, "Exception while searching for file system discrepancies ", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* requirements that resolve internally are never "missing", but will only resolve _after_ build/install */
|
||||||
|
private boolean resolveInternally(List<BundleRequirement> requirements)
|
||||||
|
throws GhidraBundleException {
|
||||||
|
if (requirements.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
List<BundleCapability> capabilities = getAllCapabilities();
|
||||||
|
Iterator<BundleRequirement> requirementIterator = requirements.iterator();
|
||||||
|
boolean anyMissing = false;
|
||||||
|
while (requirementIterator.hasNext()) {
|
||||||
|
BundleRequirement requirement = requirementIterator.next();
|
||||||
|
if (capabilities.stream().anyMatch(requirement::matches)) {
|
||||||
|
requirementIterator.remove();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
anyMissing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !anyMissing;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* when calling the java compiler programmatically, we map import requests to files with
|
* when calling the java compiler programmatically, we map import requests to files with
|
||||||
* a custom {@link JavaFileManager}. We wrap the system JavaFileManager with one that
|
* a custom {@link JavaFileManager}. We wrap the system JavaFileManager with one that
|
||||||
|
@ -740,7 +760,7 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
List<BundleRequirement> requirements = getAllRequirements();
|
List<BundleRequirement> requirements = getAllRequirements();
|
||||||
List<BundleWiring> bundleWirings = bundleHost.resolve(requirements);
|
List<BundleWiring> bundleWirings = bundleHost.resolve(requirements);
|
||||||
|
|
||||||
if (!requirements.isEmpty()) {
|
if (!resolveInternally(requirements)) {
|
||||||
writer.printf("%d import requirement%s remain%s unresolved:\n", requirements.size(),
|
writer.printf("%d import requirement%s remain%s unresolved:\n", requirements.size(),
|
||||||
requirements.size() > 1 ? "s" : "", requirements.size() > 1 ? "" : "s");
|
requirements.size() > 1 ? "s" : "", requirements.size() > 1 ? "" : "s");
|
||||||
for (BundleRequirement requirement : requirements) {
|
for (BundleRequirement requirement : requirements) {
|
||||||
|
@ -1097,8 +1117,7 @@ public class GhidraSourceBundle extends GhidraBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
try (Stream<Path> paths = Files.list(directory)) {
|
try (Stream<Path> paths = Files.list(directory)) {
|
||||||
classToClassFilesMap = paths
|
classToClassFilesMap = paths.filter(p -> Files.isRegularFile(p))
|
||||||
.filter(p -> Files.isRegularFile(p))
|
|
||||||
.filter(p -> p.getFileName().toString().endsWith(".class"))
|
.filter(p -> p.getFileName().toString().endsWith(".class"))
|
||||||
.collect(groupingBy(this::getClassName));
|
.collect(groupingBy(this::getClassName));
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,20 +122,37 @@ public class ElfDefaultGotPltMarkup {
|
||||||
if (relocationTable == null) {
|
if (relocationTable == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ElfRelocation[] relocations = relocationTable.getRelocations();
|
|
||||||
int count = relocations.length;
|
|
||||||
|
|
||||||
// First few entries of GOT do not correspond to dynamic symbols.
|
// External dynamic symbol entries in the PLTGOT, if any, will be placed
|
||||||
// First relocation address must be used to calculate GOT end address
|
// after any local symbol entries.
|
||||||
// based upon the total number of relocation entries.
|
|
||||||
long firstGotEntryOffset = relocations[0].getOffset() + imageBaseAdj;
|
// While DT_PLTGOT identifies the start of the PLTGOT it does not
|
||||||
|
// specify its length. If there are dynamic non-local entries in the
|
||||||
|
// PLTGOT they should have relocation entries in the table identified
|
||||||
|
// by DT_JMPREL. It is important to note that this relocation table
|
||||||
|
// can include entries which affect other processor-specific PLTGOT
|
||||||
|
// tables (e.g., MIPS_PLTGOT) so we must attempt to isolate the
|
||||||
|
// entries which correspond to DT_PLTGOT.
|
||||||
|
|
||||||
|
// WARNING: This implementation makes a potentially bad assumption that
|
||||||
|
// the last relocation entry will identify the endof the PLTGOT if its
|
||||||
|
// offset is beyond the start of the PLTGOT. This assumption could
|
||||||
|
// easily be violated by a processor-specific PLTGOT which falls after
|
||||||
|
// the standard PLTGOT in memory and shares the same relocation table.
|
||||||
|
|
||||||
long pltgot = elf.adjustAddressForPrelink(
|
long pltgot = elf.adjustAddressForPrelink(
|
||||||
dynamicTable.getDynamicValue(ElfDynamicType.DT_PLTGOT)) + imageBaseAdj;
|
dynamicTable.getDynamicValue(ElfDynamicType.DT_PLTGOT));
|
||||||
|
|
||||||
|
ElfRelocation[] relocations = relocationTable.getRelocations();
|
||||||
|
|
||||||
|
long lastGotOffset = relocations[relocations.length - 1].getOffset();
|
||||||
|
if (lastGotOffset < pltgot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Address gotStart = defaultSpace.getAddress(pltgot + imageBaseAdj);
|
||||||
|
Address gotEnd = defaultSpace.getAddress(lastGotOffset + imageBaseAdj);
|
||||||
|
|
||||||
Address gotStart = defaultSpace.getAddress(pltgot);
|
|
||||||
Address gotEnd = defaultSpace.getAddress(
|
|
||||||
firstGotEntryOffset + (count * defaultSpace.getPointerSize()) - 1);
|
|
||||||
processGOT(gotStart, gotEnd, monitor);
|
processGOT(gotStart, gotEnd, monitor);
|
||||||
processDynamicPLT(gotStart, gotEnd, monitor);
|
processDynamicPLT(gotStart, gotEnd, monitor);
|
||||||
}
|
}
|
||||||
|
@ -162,7 +179,7 @@ public class ElfDefaultGotPltMarkup {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Address newImageBase = null;
|
Address newImageBase = null;
|
||||||
while (gotStart.compareTo(gotEnd) < 0) {
|
while (gotStart.compareTo(gotEnd) <= 0) {
|
||||||
monitor.checkCanceled();
|
monitor.checkCanceled();
|
||||||
|
|
||||||
Data data = createPointer(gotStart, true);
|
Data data = createPointer(gotStart, true);
|
||||||
|
@ -350,7 +367,7 @@ public class ElfDefaultGotPltMarkup {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int pointerSize = program.getDataTypeManager().getDataOrganization().getPointerSize();
|
int pointerSize = program.getDataTypeManager().getDataOrganization().getPointerSize();
|
||||||
Pointer pointer = PointerDataType.dataType;
|
Pointer pointer = PointerDataType.dataType.clone(program.getDataTypeManager());
|
||||||
if (elf.is32Bit() && pointerSize != 4) {
|
if (elf.is32Bit() && pointerSize != 4) {
|
||||||
pointer = Pointer32DataType.dataType;
|
pointer = Pointer32DataType.dataType;
|
||||||
}
|
}
|
||||||
|
@ -364,20 +381,50 @@ public class ElfDefaultGotPltMarkup {
|
||||||
}
|
}
|
||||||
data = listing.createData(addr, pointer);
|
data = listing.createData(addr, pointer);
|
||||||
}
|
}
|
||||||
Address refAddr = (Address) data.getValue();
|
if (keepRefWhenValid && isValidPointer(data)) {
|
||||||
if (keepRefWhenValid) {
|
setConstant(data);
|
||||||
if (memory.contains(refAddr)) {
|
}
|
||||||
return data;
|
else {
|
||||||
}
|
removeMemRefs(data);
|
||||||
Symbol syms[] = program.getSymbolTable().getSymbols(refAddr);
|
|
||||||
if (syms != null && syms.length > 0 && syms[0].getSource() != SourceType.DEFAULT) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
removeMemRefs(data);
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set specified data as constant if contained within a writable block. It can be helpful
|
||||||
|
* to the decompiler results if constant pointers are marked as such (e.g., GOT entries)
|
||||||
|
* @param data program data
|
||||||
|
*/
|
||||||
|
public static void setConstant(Data data) {
|
||||||
|
Memory memory = data.getProgram().getMemory();
|
||||||
|
MemoryBlock block = memory.getBlock(data.getAddress());
|
||||||
|
if (!block.isWrite() || block.getName().startsWith(ElfSectionHeaderConstants.dot_got)) {
|
||||||
|
// .got blocks will be force to read-only by ElfDefaultGotPltMarkup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.setLong(MutabilitySettingsDefinition.MUTABILITY,
|
||||||
|
MutabilitySettingsDefinition.CONSTANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if pointerData refers to a valid memory address or symbol
|
||||||
|
* @param pointerData pointer data
|
||||||
|
* @return true if pointer data refers to valid memory address or symbol
|
||||||
|
*/
|
||||||
|
public static boolean isValidPointer(Data pointerData) {
|
||||||
|
Program program = pointerData.getProgram();
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
Address refAddr = (Address) pointerData.getValue();
|
||||||
|
if (memory.contains(refAddr)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Symbol syms[] = program.getSymbolTable().getSymbols(refAddr);
|
||||||
|
if (syms != null && syms.length > 0 && syms[0].getSource() != SourceType.DEFAULT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void removeMemRefs(Data data) {
|
private void removeMemRefs(Data data) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
Reference[] refs = data.getValueReferences();
|
Reference[] refs = data.getValueReferences();
|
||||||
|
|
|
@ -105,7 +105,7 @@ public class GoToServiceImpl implements GoToService {
|
||||||
programLocation = override.goTo(goToAddress);
|
programLocation = override.goTo(goToAddress);
|
||||||
}
|
}
|
||||||
if (programLocation == null) {
|
if (programLocation == null) {
|
||||||
programLocation = helper.getProgramLocationForAddress(goToAddress, program);
|
programLocation = GoToHelper.getProgramLocationForAddress(goToAddress, program);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
program = programLocation.getProgram();
|
program = programLocation.getProgram();
|
||||||
|
@ -116,7 +116,7 @@ public class GoToServiceImpl implements GoToService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean goTo(Address goToAddress, Program program) {
|
public boolean goTo(Address goToAddress, Program program) {
|
||||||
ProgramLocation location = helper.getProgramLocationForAddress(goToAddress, program);
|
ProgramLocation location = GoToHelper.getProgramLocationForAddress(goToAddress, program);
|
||||||
return helper.goTo(defaultNavigatable, location, program);
|
return helper.goTo(defaultNavigatable, location, program);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,9 +100,6 @@ public abstract class AbstractDecompilerAction extends DockingAction {
|
||||||
HighVariable variable = token.getHighVariable();
|
HighVariable variable = token.getHighVariable();
|
||||||
HighSymbol highSymbol = null;
|
HighSymbol highSymbol = null;
|
||||||
if (variable == null) {
|
if (variable == null) {
|
||||||
if (highFunction == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Token may be from a variable reference, in which case we have to dig to find the actual symbol
|
// Token may be from a variable reference, in which case we have to dig to find the actual symbol
|
||||||
Function function = highFunction.getFunction();
|
Function function = highFunction.getFunction();
|
||||||
if (function == null) {
|
if (function == null) {
|
||||||
|
|
|
@ -945,14 +945,14 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
|
|
||||||
DockingActionIf action = getAction(decompiler, "Refresh");
|
DockingActionIf action = getAction(decompiler, "Refresh");
|
||||||
performAction(action);
|
performAction(action, provider.getActionContext(null), true);
|
||||||
waitForDecompiler();
|
waitForDecompiler();
|
||||||
}
|
}
|
||||||
|
|
||||||
private DecompilerProvider cloneDecompiler() {
|
private DecompilerProvider cloneDecompiler() {
|
||||||
|
|
||||||
DockingActionIf action = getAction(decompiler, "Decompile Clone");
|
DockingActionIf action = getAction(decompiler, "Decompile Clone");
|
||||||
performAction(action);
|
performAction(action, provider.getActionContext(null), true);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -1035,7 +1035,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
||||||
|
|
||||||
private void backwardSlice() {
|
private void backwardSlice() {
|
||||||
DockingActionIf action = getAction(decompiler, BackwardsSliceAction.NAME);
|
DockingActionIf action = getAction(decompiler, BackwardsSliceAction.NAME);
|
||||||
performAction(action);
|
performAction(action, provider.getActionContext(null), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void middleMouse() {
|
private void middleMouse() {
|
||||||
|
@ -1064,7 +1064,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
||||||
|
|
||||||
private void rename(String newName) {
|
private void rename(String newName) {
|
||||||
DockingActionIf action = getAction(decompiler, "Rename Variable");
|
DockingActionIf action = getAction(decompiler, "Rename Variable");
|
||||||
performAction(action, false);
|
performAction(action, provider.getActionContext(null), false);
|
||||||
|
|
||||||
InputDialog dialog = waitForDialogComponent(InputDialog.class);
|
InputDialog dialog = waitForDialogComponent(InputDialog.class);
|
||||||
runSwing(() -> dialog.setValue(newName));
|
runSwing(() -> dialog.setValue(newName));
|
||||||
|
@ -1077,7 +1077,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
||||||
|
|
||||||
DockingActionIf highlightAction =
|
DockingActionIf highlightAction =
|
||||||
getAction(decompiler, RemoveAllSecondaryHighlightsAction.NAME);
|
getAction(decompiler, RemoveAllSecondaryHighlightsAction.NAME);
|
||||||
performAction(highlightAction);
|
performAction(highlightAction, provider.getActionContext(null), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color highlight() {
|
private Color highlight() {
|
||||||
|
@ -1085,7 +1085,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
||||||
ClangToken token = getToken();
|
ClangToken token = getToken();
|
||||||
|
|
||||||
DockingActionIf highlightAction = getAction(decompiler, SetSecondaryHighlightAction.NAME);
|
DockingActionIf highlightAction = getAction(decompiler, SetSecondaryHighlightAction.NAME);
|
||||||
performAction(highlightAction);
|
performAction(highlightAction, provider.getActionContext(null), true);
|
||||||
|
|
||||||
HighlightToken ht = getSecondaryHighlight(token);
|
HighlightToken ht = getSecondaryHighlight(token);
|
||||||
assertNotNull("No highlight for token: " + token, ht);
|
assertNotNull("No highlight for token: " + token, ht);
|
||||||
|
@ -1106,7 +1106,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
||||||
|
|
||||||
DockingActionIf highlightAction =
|
DockingActionIf highlightAction =
|
||||||
getAction(decompiler, SetSecondaryHighlightColorChooserAction.NAME);
|
getAction(decompiler, SetSecondaryHighlightColorChooserAction.NAME);
|
||||||
performAction(highlightAction, false);
|
performAction(highlightAction, provider.getActionContext(null), false);
|
||||||
|
|
||||||
Window w = waitForWindow("Please Choose a Color");
|
Window w = waitForWindow("Please Choose a Color");
|
||||||
GhidraColorChooser colorChooser = findComponent(w, GhidraColorChooser.class);
|
GhidraColorChooser colorChooser = findComponent(w, GhidraColorChooser.class);
|
||||||
|
@ -1238,7 +1238,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
||||||
for (DockingActionIf action : actions) {
|
for (DockingActionIf action : actions) {
|
||||||
Object service = getInstanceField("clipboardService", action);
|
Object service = getInstanceField("clipboardService", action);
|
||||||
if (service.getClass().toString().contains("Decomp")) {
|
if (service.getClass().toString().contains("Decomp")) {
|
||||||
performAction(action);
|
performAction(action, provider.getActionContext(null), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2166,12 +2166,6 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
|
||||||
Double scale = getGraphScale(getPrimaryGraphViewer());
|
Double scale = getGraphScale(getPrimaryGraphViewer());
|
||||||
int result = Double.compare(scale, 1.0);
|
int result = Double.compare(scale, 1.0);
|
||||||
assertEquals("Graph not fully zoomed-in; scale: " + scale, 0, result);
|
assertEquals("Graph not fully zoomed-in; scale: " + scale, 0, result);
|
||||||
|
|
||||||
FGVertex v = getFocusedVertex();
|
|
||||||
Rectangle cursorBounds = v.getCursorBounds();
|
|
||||||
Window graphWindow = windowForComponent(getPrimaryGraphViewer());
|
|
||||||
Rectangle windowBounds = graphWindow.getBounds();
|
|
||||||
assertTrue(windowBounds.contains(cursorBounds));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertZoomedOut() {
|
protected void assertZoomedOut() {
|
||||||
|
@ -2277,7 +2271,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
|
||||||
|
|
||||||
protected void goTo(Address address) {
|
protected void goTo(Address address) {
|
||||||
GoToService goToService = tool.getService(GoToService.class);
|
GoToService goToService = tool.getService(GoToService.class);
|
||||||
goToService.goTo(address);
|
runSwing(() -> goToService.goTo(address));
|
||||||
waitForBusyGraph();
|
waitForBusyGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -843,7 +843,7 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
|
||||||
private void setNavigationHistoryOption(NavigationHistoryChoices choice) throws Exception {
|
private void setNavigationHistoryOption(NavigationHistoryChoices choice) throws Exception {
|
||||||
FGController controller = getFunctionGraphController();
|
FGController controller = getFunctionGraphController();
|
||||||
FunctionGraphOptions options = controller.getFunctionGraphOptions();
|
FunctionGraphOptions options = controller.getFunctionGraphOptions();
|
||||||
setInstanceField("navigationHistoryChoice", options, choice);
|
runSwing(() -> setInstanceField("navigationHistoryChoice", options, choice));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ import ghidra.graph.job.GraphJobRunner;
|
||||||
import ghidra.service.graph.*;
|
import ghidra.service.graph.*;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskLauncher;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import resources.Icons;
|
import resources.Icons;
|
||||||
|
|
||||||
|
@ -128,10 +129,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||||
* provides graph displays for supplied graphs
|
* provides graph displays for supplied graphs
|
||||||
*/
|
*/
|
||||||
private final DefaultGraphDisplayProvider graphDisplayProvider;
|
private final DefaultGraphDisplayProvider graphDisplayProvider;
|
||||||
/**
|
|
||||||
* a 'busy' dialog to show while the layout algorithm is working
|
|
||||||
*/
|
|
||||||
private LayoutWorkingDialog layoutWorkingDialog;
|
|
||||||
/**
|
/**
|
||||||
* the vertex that has been nominated to be 'focused' in the graph display and listing
|
* the vertex that has been nominated to be 'focused' in the graph display and listing
|
||||||
*/
|
*/
|
||||||
|
@ -359,20 +356,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||||
.onActionStateChanged((s, t) -> layoutChanged(s.getName()))
|
.onActionStateChanged((s, t) -> layoutChanged(s.getName()))
|
||||||
.addStates(getLayoutActionStates())
|
.addStates(getLayoutActionStates())
|
||||||
.buildAndInstallLocal(componentProvider);
|
.buildAndInstallLocal(componentProvider);
|
||||||
|
|
||||||
// show a 'busy' dialog while the layout algorithm is computing vertex locations
|
|
||||||
viewer.getVisualizationModel()
|
|
||||||
.getLayoutModel()
|
|
||||||
.getLayoutStateChangeSupport()
|
|
||||||
.addLayoutStateChangeListener(
|
|
||||||
evt -> {
|
|
||||||
if (evt.active) {
|
|
||||||
Swing.runLater(this::showLayoutWorking);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Swing.runLater(this::hideLayoutWorking);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createPopupActions() {
|
private void createPopupActions() {
|
||||||
|
@ -569,7 +552,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||||
*/
|
*/
|
||||||
private void layoutChanged(String layoutName) {
|
private void layoutChanged(String layoutName) {
|
||||||
if (layoutTransitionManager != null) {
|
if (layoutTransitionManager != null) {
|
||||||
layoutTransitionManager.setLayout(layoutName);
|
new TaskLauncher(new SetLayoutTask(viewer, layoutTransitionManager, layoutName), null,
|
||||||
|
1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -587,27 +571,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||||
componentProvider.getTool().showDialog(filterDialog);
|
componentProvider.getTool().showDialog(filterDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* show the 'busy' dialog indicating that the layout algorithm is working
|
|
||||||
*/
|
|
||||||
protected void showLayoutWorking() {
|
|
||||||
if (this.layoutWorkingDialog != null) {
|
|
||||||
layoutWorkingDialog.close();
|
|
||||||
}
|
|
||||||
this.layoutWorkingDialog =
|
|
||||||
new LayoutWorkingDialog(viewer.getVisualizationModel().getLayoutAlgorithm());
|
|
||||||
componentProvider.getTool().showDialog(layoutWorkingDialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* hide the 'busy' dialog for the layout algorithm work
|
|
||||||
*/
|
|
||||||
protected void hideLayoutWorking() {
|
|
||||||
if (this.layoutWorkingDialog != null) {
|
|
||||||
layoutWorkingDialog.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add or remove the satellite viewer
|
* add or remove the satellite viewer
|
||||||
* @param context information about the event
|
* @param context information about the event
|
||||||
|
@ -1216,7 +1179,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addAction(DockingAction action) {
|
public void addAction(DockingAction action) {
|
||||||
componentProvider.addLocalAction(action);
|
Swing.runLater(() -> componentProvider.addLocalAction(action));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1286,4 +1249,19 @@ public class DefaultGraphDisplay implements GraphDisplay {
|
||||||
public AttributedGraph getGraph() {
|
public AttributedGraph getGraph() {
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all externally added actions. This is called before re-using the graph window for a
|
||||||
|
* new graph which may add its own set of actions for that particular graph.
|
||||||
|
*/
|
||||||
|
void restoreToDefaultSetOfActions() {
|
||||||
|
Swing.runLater(() -> {
|
||||||
|
// remove all actions
|
||||||
|
componentProvider.removeAllLocalActions();
|
||||||
|
// put the standard graph actions back
|
||||||
|
createToolbarActions();
|
||||||
|
createPopupActions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,4 +69,10 @@ public class DefaultGraphDisplayComponentProvider extends ComponentProviderAdapt
|
||||||
public ActionContext getActionContext(MouseEvent event) {
|
public ActionContext getActionContext(MouseEvent event) {
|
||||||
return display.getActionContext(event);
|
return display.getActionContext(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// overridden to make it accessible
|
||||||
|
@Override
|
||||||
|
public void removeAllLocalActions() {
|
||||||
|
super.removeAllLocalActions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,9 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
|
||||||
TaskMonitor monitor) {
|
TaskMonitor monitor) {
|
||||||
|
|
||||||
if (reuseGraph && !displays.isEmpty()) {
|
if (reuseGraph && !displays.isEmpty()) {
|
||||||
return getVisibleGraph();
|
DefaultGraphDisplay visibleGraph = getVisibleGraph();
|
||||||
|
visibleGraph.restoreToDefaultSetOfActions();
|
||||||
|
return visibleGraph;
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultGraphDisplay display =
|
DefaultGraphDisplay display =
|
||||||
|
@ -73,9 +75,11 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
|
||||||
* return one from the Set via its iterator
|
* return one from the Set via its iterator
|
||||||
* @return a display that is showing
|
* @return a display that is showing
|
||||||
*/
|
*/
|
||||||
private GraphDisplay getVisibleGraph() {
|
private DefaultGraphDisplay getVisibleGraph() {
|
||||||
return displays.stream().filter(d -> d.getComponent().isShowing())
|
return displays.stream()
|
||||||
.findAny().orElse(displays.iterator().next());
|
.filter(d -> d.getComponent().isShowing())
|
||||||
|
.findAny()
|
||||||
|
.orElse(displays.iterator().next());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -100,4 +104,5 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
|
||||||
public void remove(DefaultGraphDisplay defaultGraphDisplay) {
|
public void remove(DefaultGraphDisplay defaultGraphDisplay) {
|
||||||
displays.remove(defaultGraphDisplay);
|
displays.remove(defaultGraphDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
/* ###
|
|
||||||
* 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.graph.visualization;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
|
|
||||||
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
|
|
||||||
|
|
||||||
import docking.DialogComponentProvider;
|
|
||||||
import ghidra.service.graph.AttributedVertex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extends DialogComponentProvider to make a dialog with buttons to show that the
|
|
||||||
* layout arrangement algorithm is busy
|
|
||||||
*/
|
|
||||||
public class LayoutWorkingDialog extends DialogComponentProvider {
|
|
||||||
|
|
||||||
public LayoutWorkingDialog(LayoutAlgorithm<AttributedVertex> layoutAlgorithm) {
|
|
||||||
super("Working....", false);
|
|
||||||
super.addWorkPanel(createPanel(layoutAlgorithm));
|
|
||||||
setRememberSize(false);
|
|
||||||
addDismissButton();
|
|
||||||
setDefaultButton(dismissButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a layout-formatted JComponent holding 2 vertical lists
|
|
||||||
* of buttons, one list for vertex filter buttons and one list for
|
|
||||||
* edge filter buttons. Each list has a border and title.
|
|
||||||
* @return a formatted JComponent (container)
|
|
||||||
*/
|
|
||||||
JComponent createPanel(LayoutAlgorithm<AttributedVertex> layoutAlgorithm) {
|
|
||||||
JProgressBar progressBar = new JProgressBar();
|
|
||||||
progressBar.setIndeterminate(true);
|
|
||||||
JPanel panel = new JPanel(new BorderLayout());
|
|
||||||
panel.add(progressBar, BorderLayout.CENTER);
|
|
||||||
panel.add(new JLabel("Please wait......."), BorderLayout.NORTH);
|
|
||||||
addCancelButton();
|
|
||||||
cancelButton.addActionListener(evt -> layoutAlgorithm.cancel());
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
/* ###
|
||||||
|
* 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.graph.visualization;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import org.jungrapht.visualization.VisualizationModel;
|
||||||
|
import org.jungrapht.visualization.VisualizationViewer;
|
||||||
|
import org.jungrapht.visualization.layout.event.LayoutStateChange.*;
|
||||||
|
import org.jungrapht.visualization.layout.model.LayoutModel;
|
||||||
|
|
||||||
|
import ghidra.service.graph.AttributedEdge;
|
||||||
|
import ghidra.service.graph.AttributedVertex;
|
||||||
|
import ghidra.util.Swing;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task to change the layout of the graph
|
||||||
|
*/
|
||||||
|
public class SetLayoutTask extends Task {
|
||||||
|
|
||||||
|
private LayoutTransitionManager layoutTransitionManager;
|
||||||
|
private String layoutName;
|
||||||
|
private VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
|
||||||
|
private CountDownLatch taskDone = new CountDownLatch(1);
|
||||||
|
|
||||||
|
public SetLayoutTask(VisualizationViewer<AttributedVertex, AttributedEdge> viewer,
|
||||||
|
LayoutTransitionManager layoutTransitionManager, String layoutName) {
|
||||||
|
super("Changing Graph Layout to " + layoutName, true, false, true, false);
|
||||||
|
this.viewer = viewer;
|
||||||
|
this.layoutTransitionManager = layoutTransitionManager;
|
||||||
|
this.layoutName = layoutName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
|
// add a callback for when/if the user cancels the layout, use a variable cause
|
||||||
|
// monitor uses a weak listener list and it would othewise get garbage collected.
|
||||||
|
CancelledListener cancelListener = this::taskCancelled;
|
||||||
|
monitor.addCancelledListener(cancelListener);
|
||||||
|
|
||||||
|
// add a listener so we are notified when the layout starts and ends
|
||||||
|
VisualizationModel<AttributedVertex, AttributedEdge> model = viewer.getVisualizationModel();
|
||||||
|
LayoutModel<AttributedVertex> layoutModel = model.getLayoutModel();
|
||||||
|
Support support = layoutModel.getLayoutStateChangeSupport();
|
||||||
|
Listener listener = this::layoutStateChanged;
|
||||||
|
support.addLayoutStateChangeListener(listener);
|
||||||
|
|
||||||
|
// start the layout - needs to be done on swing thread to prevent issues and intermediate
|
||||||
|
// paints - should be changed in the future to not require it to be on the swing thread.
|
||||||
|
Swing.runNow(() -> layoutTransitionManager.setLayout(layoutName));
|
||||||
|
|
||||||
|
// some of the layouts are done on the calling thread and some aren't. If they are on
|
||||||
|
// the calling thread, then by now, we already got the "done" callback and the "taskDone"
|
||||||
|
// countdown latch has been triggered and won't wait. If, however, the layout has been
|
||||||
|
// diverted to another thread, we want to wait until the layout is completed
|
||||||
|
// There are two ways the latch will be triggered, the layout is completed or the user
|
||||||
|
// cancles the layout.
|
||||||
|
try {
|
||||||
|
taskDone.await();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
model.getLayoutAlgorithm().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up the listeners
|
||||||
|
support.removeLayoutStateChangeListener(listener);
|
||||||
|
monitor.removeCancelledListener(cancelListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notfication when the layout algorithm starts and stops.
|
||||||
|
* @param e the event. If the event.active is true, then the
|
||||||
|
* algorithm is starting, if false, the algorithm is done.
|
||||||
|
*/
|
||||||
|
private void layoutStateChanged(Event e) {
|
||||||
|
if (!e.active) {
|
||||||
|
// algorithm is done, release the latch
|
||||||
|
taskDone.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback if the user cancels the layout
|
||||||
|
*/
|
||||||
|
private void taskCancelled() {
|
||||||
|
// release the latch and tell the layout algorithm to cancel.
|
||||||
|
taskDone.countDown();
|
||||||
|
viewer.getVisualizationModel().getLayoutAlgorithm().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -267,7 +267,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the given action from the system.
|
* Removes the given action from this component provider.
|
||||||
* @param action The action to remove.
|
* @param action The action to remove.
|
||||||
*/
|
*/
|
||||||
protected void removeLocalAction(DockingAction action) {
|
protected void removeLocalAction(DockingAction action) {
|
||||||
|
@ -277,6 +277,16 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all local actions from this component provider
|
||||||
|
*/
|
||||||
|
protected void removeAllLocalActions() {
|
||||||
|
if (isInTool()) {
|
||||||
|
actionSet.forEach(action -> dockingTool.removeLocalAction(this, action));
|
||||||
|
}
|
||||||
|
actionSet.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to show or hide this provider.
|
* Convenience method to show or hide this provider.
|
||||||
* @param visible True shows the provider; false hides the provider
|
* @param visible True shows the provider; false hides the provider
|
||||||
|
|
|
@ -24,6 +24,7 @@ public interface HoverProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this service's popup window is currently visible
|
* Returns true if this service's popup window is currently visible
|
||||||
|
* @return true if this service's popup window is currently visible
|
||||||
*/
|
*/
|
||||||
public boolean isShowing();
|
public boolean isShowing();
|
||||||
|
|
||||||
|
|
|
@ -273,6 +273,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
|
||||||
* @return true if {@link #setIndeterminate(boolean)} with a value of <code>true</code> has
|
* @return true if {@link #setIndeterminate(boolean)} with a value of <code>true</code> has
|
||||||
* been called.
|
* been called.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean isIndeterminate() {
|
public boolean isIndeterminate() {
|
||||||
return isIndeterminate.get();
|
return isIndeterminate.get();
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,12 +125,19 @@ public final class DataUtilities {
|
||||||
existingDataLen = data.getLength();
|
existingDataLen = data.getLength();
|
||||||
existingDT = data.getDataType();
|
existingDT = data.getDataType();
|
||||||
|
|
||||||
|
if (data.isDefined() && newDataType.isEquivalent(existingDT)) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for external reference on pointer
|
// Check for external reference on pointer
|
||||||
Reference[] refs = refMgr.getReferencesFrom(addr);
|
if (existingDT instanceof Pointer) {
|
||||||
for (Reference ref : refs) {
|
// TODO: This can probably be eliminated
|
||||||
if (ref.getOperandIndex() == 0 && ref.isExternalReference()) {
|
Reference[] refs = refMgr.getReferencesFrom(addr);
|
||||||
extRef = ref;
|
for (Reference ref : refs) {
|
||||||
break;
|
if (ref.getOperandIndex() == 0 && ref.isExternalReference()) {
|
||||||
|
extRef = ref;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,11 +199,11 @@ public final class DataUtilities {
|
||||||
private static void checkEnoughSpace(Program program, Address addr, int existingDataLen,
|
private static void checkEnoughSpace(Program program, Address addr, int existingDataLen,
|
||||||
DataTypeInstance dti, ClearDataMode mode) throws CodeUnitInsertionException {
|
DataTypeInstance dti, ClearDataMode mode) throws CodeUnitInsertionException {
|
||||||
Listing listing = program.getListing();
|
Listing listing = program.getListing();
|
||||||
int newSize = dti.getLength();
|
// int newSize = dti.getLength();
|
||||||
if (newSize <= existingDataLen) {
|
// if (newSize <= existingDataLen) {
|
||||||
listing.clearCodeUnits(addr, addr, false);
|
// listing.clearCodeUnits(addr, addr, false);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
try {
|
try {
|
||||||
Address end = addr.addNoWrap(existingDataLen - 1);
|
Address end = addr.addNoWrap(existingDataLen - 1);
|
||||||
Address newEnd = addr.addNoWrap(dti.getLength() - 1);
|
Address newEnd = addr.addNoWrap(dti.getLength() - 1);
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class PointerDataType extends BuiltIn implements Pointer {
|
||||||
* organization when resolved.
|
* organization when resolved.
|
||||||
*/
|
*/
|
||||||
public PointerDataType() {
|
public PointerDataType() {
|
||||||
this(null, -1, null);
|
this(DataType.DEFAULT, -1, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +72,7 @@ public class PointerDataType extends BuiltIn implements Pointer {
|
||||||
* @param dtm data-type manager whose data organization should be used
|
* @param dtm data-type manager whose data organization should be used
|
||||||
*/
|
*/
|
||||||
public PointerDataType(DataTypeManager dtm) {
|
public PointerDataType(DataTypeManager dtm) {
|
||||||
this(null, -1, dtm);
|
this(DataType.DEFAULT, -1, dtm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -142,7 +142,7 @@ public class PointerDataType extends BuiltIn implements Pointer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final DataType clone(DataTypeManager dtm) {
|
public final Pointer clone(DataTypeManager dtm) {
|
||||||
if (dtm == getDataTypeManager()) {
|
if (dtm == getDataTypeManager()) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -744,7 +744,9 @@ public class MIPS_ElfExtension extends ElfExtension {
|
||||||
Address gotEntryAddr =
|
Address gotEntryAddr =
|
||||||
adjustTableEntryIfNonZero(gotBaseAddress, i, imageShift, elfLoadHelper);
|
adjustTableEntryIfNonZero(gotBaseAddress, i, imageShift, elfLoadHelper);
|
||||||
Data pointerData = elfLoadHelper.createData(gotEntryAddr, PointerDataType.dataType);
|
Data pointerData = elfLoadHelper.createData(gotEntryAddr, PointerDataType.dataType);
|
||||||
setConstant(pointerData);
|
if (ElfDefaultGotPltMarkup.isValidPointer(pointerData)) {
|
||||||
|
ElfDefaultGotPltMarkup.setConstant(pointerData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process global/external symbol got entries
|
// process global/external symbol got entries
|
||||||
|
@ -753,7 +755,7 @@ public class MIPS_ElfExtension extends ElfExtension {
|
||||||
Address gotEntryAddr = adjustTableEntryIfNonZero(gotBaseAddress, gotIndex++,
|
Address gotEntryAddr = adjustTableEntryIfNonZero(gotBaseAddress, gotIndex++,
|
||||||
imageShift, elfLoadHelper);
|
imageShift, elfLoadHelper);
|
||||||
Data pointerData = elfLoadHelper.createData(gotEntryAddr, PointerDataType.dataType);
|
Data pointerData = elfLoadHelper.createData(gotEntryAddr, PointerDataType.dataType);
|
||||||
setConstant(pointerData);
|
ElfDefaultGotPltMarkup.setConstant(pointerData);
|
||||||
if (elfSymbols[i].isFunction() && elfSymbols[i].getSectionHeaderIndex() == 0) {
|
if (elfSymbols[i].isFunction() && elfSymbols[i].getSectionHeaderIndex() == 0) {
|
||||||
// ensure that external function/thunk are created in absence of sections
|
// ensure that external function/thunk are created in absence of sections
|
||||||
Address refAddr = (Address) pointerData.getValue();
|
Address refAddr = (Address) pointerData.getValue();
|
||||||
|
@ -811,7 +813,7 @@ public class MIPS_ElfExtension extends ElfExtension {
|
||||||
Address gotEntryAddr = adjustTableEntryIfNonZero(mipsPltgotBase, ++gotEntryIndex,
|
Address gotEntryAddr = adjustTableEntryIfNonZero(mipsPltgotBase, ++gotEntryIndex,
|
||||||
imageShift, elfLoadHelper);
|
imageShift, elfLoadHelper);
|
||||||
Data pointerData = elfLoadHelper.createData(gotEntryAddr, PointerDataType.dataType);
|
Data pointerData = elfLoadHelper.createData(gotEntryAddr, PointerDataType.dataType);
|
||||||
setConstant(pointerData);
|
ElfDefaultGotPltMarkup.setConstant(pointerData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (NotFoundException e) {
|
catch (NotFoundException e) {
|
||||||
|
@ -822,17 +824,6 @@ public class MIPS_ElfExtension extends ElfExtension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setConstant(Data pointerData) {
|
|
||||||
Memory memory = pointerData.getProgram().getMemory();
|
|
||||||
MemoryBlock block = memory.getBlock(pointerData.getAddress());
|
|
||||||
if (!block.isWrite() || block.getName().startsWith(ElfSectionHeaderConstants.dot_got)) {
|
|
||||||
// .got blocks will be force to read-only by ElfDefaultGotPltMarkup
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pointerData.setLong(MutabilitySettingsDefinition.MUTABILITY,
|
|
||||||
MutabilitySettingsDefinition.CONSTANT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Address adjustTableEntryIfNonZero(Address tableBaseAddr, int entryIndex,
|
private Address adjustTableEntryIfNonZero(Address tableBaseAddr, int entryIndex,
|
||||||
long adjustment, ElfLoadHelper elfLoadHelper) throws MemoryAccessException {
|
long adjustment, ElfLoadHelper elfLoadHelper) throws MemoryAccessException {
|
||||||
boolean is64Bit = elfLoadHelper.getElfHeader().is64Bit();
|
boolean is64Bit = elfLoadHelper.getElfHeader().is64Bit();
|
||||||
|
|
|
@ -368,6 +368,9 @@ public class PowerPC64_ElfExtension extends ElfExtension {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ElfDefaultGotPltMarkup.setConstant(refPtr);
|
||||||
|
ElfDefaultGotPltMarkup.setConstant(tocPtr);
|
||||||
|
|
||||||
Function function = program.getListing().getFunctionAt(refAddr);
|
Function function = program.getListing().getFunctionAt(refAddr);
|
||||||
if (function == null) {
|
if (function == null) {
|
||||||
// Check for potential pointer table (unsure a non-function would be referenced by OPD section)
|
// Check for potential pointer table (unsure a non-function would be referenced by OPD section)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue