mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-06-30 23:04:52 +00:00
Compare commits
38 Commits
127ec8d1e7
...
29b4ae3034
Author | SHA1 | Date | |
---|---|---|---|
|
29b4ae3034 | ||
|
28846ef279 | ||
|
e7595341c4 | ||
|
bf71142709 | ||
|
36a707471e | ||
|
4b30e484b0 | ||
|
ae3f6feb70 | ||
|
2b73a6157f | ||
|
34272fd3ff | ||
|
3b6d5e43ce | ||
|
b86ad84c04 | ||
|
72d4a342a6 | ||
|
b68fa6c745 | ||
|
62f41a7179 | ||
|
a977a35f5f | ||
|
d5cbda1e21 | ||
|
4d8ec78908 | ||
|
02aba11104 | ||
|
184c657cfd | ||
|
13821930da | ||
|
e9e4ee48ce | ||
|
21a3896018 | ||
|
eb5e6a323a | ||
|
ea785546cf | ||
|
008a4ef948 | ||
|
5ab72bf4f2 | ||
|
627e3f14fa | ||
|
b4ef357e53 | ||
|
6b94d4b69b | ||
|
2fc70183e5 | ||
|
625df03c15 | ||
|
8336bdde74 | ||
|
0e33958c76 | ||
|
20702592dd | ||
|
1314f21613 | ||
|
42710d014d | ||
|
9f8b03a90f | ||
|
cf9319e321 |
|
@ -104,7 +104,7 @@ def get_arch():
|
|||
|
||||
def get_endian():
|
||||
parm = gdb.parameter('endian')
|
||||
if parm != 'auto':
|
||||
if not parm in ['', 'auto', 'default']:
|
||||
return parm
|
||||
# Once again, we have to hack using the human-readable 'show'
|
||||
show = gdb.execute('show endian', to_string=True)
|
||||
|
@ -132,7 +132,7 @@ def get_osabi():
|
|||
def compute_ghidra_language():
|
||||
# First, check if the parameter is set
|
||||
lang = gdb.parameter('ghidra-language')
|
||||
if lang != 'auto':
|
||||
if not lang in ['', 'auto', 'default']:
|
||||
return lang
|
||||
|
||||
# Get the list of possible languages for the arch. We'll need to sift
|
||||
|
@ -157,7 +157,7 @@ def compute_ghidra_language():
|
|||
def compute_ghidra_compiler(lang):
|
||||
# First, check if the parameter is set
|
||||
comp = gdb.parameter('ghidra-compiler')
|
||||
if comp != 'auto':
|
||||
if not comp in ['', 'auto', 'default']:
|
||||
return comp
|
||||
|
||||
# Check if the selected lang has specific compiler recommendations
|
||||
|
|
|
@ -381,7 +381,10 @@ class RegisterDesc(namedtuple('BaseRegisterDesc', ['name'])):
|
|||
|
||||
def get_register_descs(arch, group='all'):
|
||||
if hasattr(arch, "registers"):
|
||||
return arch.registers(group)
|
||||
try:
|
||||
return arch.registers(group)
|
||||
except ValueError: # No such group, or version too old
|
||||
return arch.registers()
|
||||
else:
|
||||
descs = []
|
||||
regset = gdb.execute(
|
||||
|
|
|
@ -33,15 +33,17 @@ public class PrivatelyQueuedListener<P> {
|
|||
DataStructureErrorHandlerFactory.createListenerErrorHandler();
|
||||
|
||||
protected class ListenerHandler implements InvocationHandler {
|
||||
private static final Method OBJECT_HASHCODE;
|
||||
static {
|
||||
private static final Method OBJECT_HASHCODE = initObjectHashCode();
|
||||
|
||||
private static Method initObjectHashCode() {
|
||||
try {
|
||||
OBJECT_HASHCODE = Object.class.getMethod("hashCode");
|
||||
return Object.class.getMethod("hashCode");
|
||||
}
|
||||
catch (NoSuchMethodException | SecurityException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected final Class<P> iface;
|
||||
|
||||
public ListenerHandler(Class<P> iface) {
|
||||
|
|
|
@ -609,6 +609,13 @@ xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-
|
|||
<P><A name="h"></A><FONT size="7"><B>H</B></FONT></P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H2><A name="HexShort"></A>Hex Short</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>A display format in the <A href="#ByteViewer">Byte Viewer</A> used to display short
|
||||
values in hex.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="HexInteger"></A>Hex Integer</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
@ -616,6 +623,20 @@ xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-
|
|||
values in hex.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="HexLong"></A>Hex Long</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>A display format in the <A href="#ByteViewer">Byte Viewer</A> used to display long
|
||||
values in hex.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="HexLongLong"></A>Hex Long Long</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>A display format in the <A href="#ByteViewer">Byte Viewer</A> used to display longlong
|
||||
values in hex.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="HijackedFile"></A>Hijacked File</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
|
|
@ -42,11 +42,10 @@ public class SetRegisterCmd implements Command<Program> {
|
|||
* A null value indicates that no value should be associated over the range.
|
||||
*/
|
||||
public SetRegisterCmd(Register register, Address start, Address end, BigInteger value) {
|
||||
if (start.getAddressSpace() != end.getAddressSpace()) {
|
||||
if (!start.getAddressSpace().equals(end.getAddressSpace())) {
|
||||
throw new IllegalArgumentException(
|
||||
"start and end address must be in the same address space");
|
||||
"start and end address must be within the same address space");
|
||||
}
|
||||
|
||||
this.register = register;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
|
|
|
@ -15,6 +15,14 @@
|
|||
*/
|
||||
package ghidra.app.merge.listing;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import ghidra.app.merge.MergeConstants;
|
||||
import ghidra.app.merge.ProgramMultiUserMergeManager;
|
||||
import ghidra.app.merge.tool.ListingMergePanel;
|
||||
|
@ -24,17 +32,10 @@ import ghidra.program.model.lang.Register;
|
|||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.util.*;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
/**
|
||||
* <code>RegisterMergeManager</code> handles the merge for a single named register.
|
||||
*/
|
||||
|
@ -98,6 +99,17 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
* @param latestChanges the address set of changes between original and latest versioned program.
|
||||
* @param myChanges the address set of changes between original and my modified program.
|
||||
*/
|
||||
/**
|
||||
* Creates a RegisterMergeManager.
|
||||
* @param registerName
|
||||
* @param mergeManager
|
||||
* @param resultPgm the program to be updated with the result of the merge.
|
||||
* @param originalPgm the program that was checked out.
|
||||
* @param latestPgm the latest checked-in version of the program.
|
||||
* @param myPgm the program requesting to be checked in.
|
||||
* @param latestChanges the address set of changes between original and latest versioned program.
|
||||
* @param myChanges the address set of changes between original and my modified program.
|
||||
*/
|
||||
RegisterMergeManager(String registerName, ProgramMultiUserMergeManager mergeManager,
|
||||
Program resultPgm, Program originalPgm, Program latestPgm, Program myPgm,
|
||||
ProgramChangeSet latestChanges, ProgramChangeSet myChanges) {
|
||||
|
@ -123,10 +135,7 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.app.merge.MergeResolver#apply()
|
||||
*/
|
||||
public void apply() {
|
||||
void apply() {
|
||||
conflictOption = conflictPanel.getSelectedOptions();
|
||||
|
||||
// If the "Use For All" check box is selected
|
||||
|
@ -138,27 +147,10 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
merge(min, max, resultReg);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.app.merge.MergeResolver#cancel()
|
||||
*/
|
||||
public void cancel() {
|
||||
void cancel() {
|
||||
conflictOption = CANCELED;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.app.merge.MergeResolver#getDescription()
|
||||
*/
|
||||
public String getDescription() {
|
||||
return "Merge Register";
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.app.merge.MergeResolver#getName()
|
||||
*/
|
||||
public String getName() {
|
||||
return "Register Merger";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param monitor
|
||||
|
@ -167,9 +159,8 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
if (conflictSet != null) {
|
||||
return; //This method only needs to be called once.
|
||||
}
|
||||
RegisterConflicts rc =
|
||||
new RegisterConflicts(registerName, originalContext, latestContext, myContext,
|
||||
resultContext);
|
||||
RegisterConflicts rc = new RegisterConflicts(registerName, originalContext, latestContext,
|
||||
myContext, resultContext);
|
||||
Memory resultMem = resultPgm.getMemory();
|
||||
AddressSetView myDiffs =
|
||||
rc.getRegisterDifferences(registerName, originalContext, myContext, mySet, monitor);
|
||||
|
@ -177,8 +168,8 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
conflictSet = new AddressSet();
|
||||
rvrs = rc.getConflicts(setToCheck, monitor);
|
||||
if (rvrs.length > 0) {
|
||||
for (int j = 0; j < rvrs.length; j++) {
|
||||
conflictSet.add(rvrs[j]);
|
||||
for (AddressRange rvr : rvrs) {
|
||||
conflictSet.add(rvr);
|
||||
}
|
||||
}
|
||||
autoSet = setToCheck.subtract(conflictSet);
|
||||
|
@ -187,20 +178,19 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
/**
|
||||
* Merges all the register values for the named register being managed by this merge manager.
|
||||
* @param monitor the monitor that provides feedback to the user.
|
||||
* @throws ProgramConflictException
|
||||
* @throws CancelledException if the user cancels
|
||||
*/
|
||||
public void merge(TaskMonitor monitor) throws CancelledException {
|
||||
monitor.setMessage("Auto-merging " + registerName +
|
||||
" Register Values and determining conflicts.");
|
||||
monitor.setMessage(
|
||||
"Auto-merging " + registerName + " Register Values and determining conflicts.");
|
||||
|
||||
determineConflicts(monitor);
|
||||
|
||||
// Auto merge any program context changes from my program where the
|
||||
// resulting program has the mem addresses but the latest doesn't.
|
||||
AddressRangeIterator arIter = autoSet.getAddressRanges();
|
||||
try {
|
||||
while (arIter.hasNext() && !monitor.isCancelled()) {
|
||||
while (arIter.hasNext() && !monitor.isCancelled()) {
|
||||
try {
|
||||
AddressRange range = arIter.next();
|
||||
Address rangeMin = range.getMinAddress();
|
||||
Address rangeMax = range.getMaxAddress();
|
||||
|
@ -215,9 +205,10 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
valueRange.getMaxAddress(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ContextChangeException e) {
|
||||
// ignore since processor context-register is not handled by this merge manager
|
||||
catch (ContextChangeException e) {
|
||||
// processor context-register is not handled here and should not occur
|
||||
throw new AssertException(e);
|
||||
}
|
||||
}
|
||||
|
||||
int totalConflicts = rvrs.length;
|
||||
|
@ -298,7 +289,8 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
resultContext.setValue(resultRegister, minAddress, maxAddress, myValue);
|
||||
}
|
||||
catch (ContextChangeException e) {
|
||||
// ignore since this merge manager does not handle the processor context register
|
||||
// processor context-register is not handled here and should not occur
|
||||
throw new AssertException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,7 +308,8 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
}
|
||||
|
||||
private void showMergePanel(final Address minAddress, final Address maxAddress,
|
||||
final BigInteger latestValue, final BigInteger myValue, final BigInteger originalValue) {
|
||||
final BigInteger latestValue, final BigInteger myValue,
|
||||
final BigInteger originalValue) {
|
||||
this.min = minAddress;
|
||||
this.max = maxAddress;
|
||||
try {
|
||||
|
@ -339,9 +332,8 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
VerticalChoicesPanel panel =
|
||||
getConflictsPanel(minAddress, maxAddress, latestValue, myValue,
|
||||
originalValue, changeListener);
|
||||
VerticalChoicesPanel panel = getConflictsPanel(minAddress, maxAddress,
|
||||
latestValue, myValue, originalValue, changeListener);
|
||||
listingMergePanel.setBottomComponent(panel);
|
||||
}
|
||||
});
|
||||
|
@ -377,12 +369,11 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
conflictPanel.clear();
|
||||
}
|
||||
conflictPanel.setTitle("\"" + registerName + "\" Register Value");
|
||||
String text =
|
||||
"Register: " + ConflictUtility.getEmphasizeString(registerName) +
|
||||
ConflictUtility.spaces(4) + "Address Range: " +
|
||||
ConflictUtility.getAddressString(minAddress) + " - " +
|
||||
ConflictUtility.getAddressString(maxAddress) +
|
||||
"<br>Select the desired register value for the address range.";
|
||||
String text = "Register: " + ConflictUtility.getEmphasizeString(registerName) +
|
||||
ConflictUtility.spaces(4) + "Address Range: " +
|
||||
ConflictUtility.getAddressString(minAddress) + " - " +
|
||||
ConflictUtility.getAddressString(maxAddress) +
|
||||
"<br>Select the desired register value for the address range.";
|
||||
conflictPanel.setHeader(text);
|
||||
conflictPanel.setRowHeader(getRegisterInfo(-1, null));
|
||||
conflictPanel.addRadioButtonRow(getRegisterInfo(MergeConstants.LATEST, latestValue),
|
||||
|
@ -431,7 +422,8 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
Register conflictResultReg;
|
||||
|
||||
RegisterConflicts(String registerName, ProgramContext originalContext,
|
||||
ProgramContext latestContext, ProgramContext myContext, ProgramContext resultContext) {
|
||||
ProgramContext latestContext, ProgramContext myContext,
|
||||
ProgramContext resultContext) {
|
||||
this.conflictRegisterName = registerName;
|
||||
this.conflictOriginalContext = originalContext;
|
||||
this.conflictLatestContext = latestContext;
|
||||
|
@ -496,13 +488,11 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
|
||||
ArrayList<AddressRange> conflicts = new ArrayList<AddressRange>();
|
||||
|
||||
AddressSet tempLatestChanges =
|
||||
getRegisterDifferences(conflictRegisterName, conflictOriginalContext,
|
||||
conflictLatestContext, addressSet, monitor);
|
||||
AddressSet tempLatestChanges = getRegisterDifferences(conflictRegisterName,
|
||||
conflictOriginalContext, conflictLatestContext, addressSet, monitor);
|
||||
|
||||
AddressSet tempMyChanges =
|
||||
getRegisterDifferences(conflictRegisterName, conflictOriginalContext,
|
||||
conflictMyContext, addressSet, monitor);
|
||||
AddressSet tempMyChanges = getRegisterDifferences(conflictRegisterName,
|
||||
conflictOriginalContext, conflictMyContext, addressSet, monitor);
|
||||
|
||||
AddressSet bothChanged = tempMyChanges.intersect(tempLatestChanges);
|
||||
|
||||
|
@ -513,19 +503,16 @@ class RegisterMergeManager implements ListingMergeConstants {
|
|||
Address rangeMin = range.getMinAddress();
|
||||
Address rangeMax = range.getMaxAddress();
|
||||
|
||||
AddressRangeIterator it1 =
|
||||
conflictLatestContext.getRegisterValueAddressRanges(conflictLatestReg,
|
||||
rangeMin, rangeMax);
|
||||
AddressRangeIterator it2 =
|
||||
conflictMyContext.getRegisterValueAddressRanges(conflictMyReg, rangeMin,
|
||||
rangeMax);
|
||||
AddressRangeIterator it1 = conflictLatestContext
|
||||
.getRegisterValueAddressRanges(conflictLatestReg, rangeMin, rangeMax);
|
||||
AddressRangeIterator it2 = conflictMyContext
|
||||
.getRegisterValueAddressRanges(conflictMyReg, rangeMin, rangeMax);
|
||||
AddressRangeIterator it = new CombinedAddressRangeIterator(it1, it2);
|
||||
|
||||
while (it.hasNext()) {
|
||||
AddressRange addrRange = it.next();
|
||||
BigInteger lastestValue =
|
||||
conflictLatestContext.getValue(conflictLatestReg,
|
||||
addrRange.getMinAddress(), false);
|
||||
BigInteger lastestValue = conflictLatestContext.getValue(conflictLatestReg,
|
||||
addrRange.getMinAddress(), false);
|
||||
BigInteger myValue =
|
||||
conflictMyContext.getValue(conflictMyReg, addrRange.getMinAddress(), false);
|
||||
boolean sameValue =
|
||||
|
|
|
@ -67,8 +67,7 @@ public class FindPossibleReferencesPlugin extends Plugin {
|
|||
final static String RESTORE_SELECTION_ACTION_NAME = "Restore Direct Refs Search Selection";
|
||||
final static String SEARCH_DIRECT_REFS_ACTION_NAME = "Search for Direct References";
|
||||
final static String SEARCH_DIRECT_REFS_ACTION_HELP = "Direct_Refs_Search_Alignment";
|
||||
private DockingAction action;
|
||||
private ArrayList<TableComponentProvider<ReferenceAddressPair>> providerList;
|
||||
private List<TableComponentProvider<ReferenceAddressPair>> providerList;
|
||||
|
||||
public FindPossibleReferencesPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
@ -98,22 +97,21 @@ public class FindPossibleReferencesPlugin extends Plugin {
|
|||
}
|
||||
|
||||
private void createActions() {
|
||||
action = new ActionBuilder(SEARCH_DIRECT_REFS_ACTION_NAME, getName())
|
||||
.menuPath(ToolConstants.MENU_SEARCH, "For Direct References")
|
||||
.menuGroup("search for", "DirectReferences")
|
||||
.helpLocation(new HelpLocation(HelpTopics.SEARCH, SEARCH_DIRECT_REFS_ACTION_NAME))
|
||||
.description(getPluginDescription().getDescription())
|
||||
.withContext(ListingActionContext.class, true)
|
||||
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
|
||||
.onAction(this::findReferences)
|
||||
.enabledWhen(this::hasCorrectAddressSize)
|
||||
.buildAndInstall(tool);
|
||||
new ActionBuilder(SEARCH_DIRECT_REFS_ACTION_NAME, getName())
|
||||
.menuPath(ToolConstants.MENU_SEARCH, "For Direct References")
|
||||
.menuGroup("search for", "DirectReferences")
|
||||
.helpLocation(new HelpLocation(HelpTopics.SEARCH, SEARCH_DIRECT_REFS_ACTION_NAME))
|
||||
.description(getPluginDescription().getDescription())
|
||||
.withContext(ListingActionContext.class, true)
|
||||
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
|
||||
.onAction(this::findReferences)
|
||||
.enabledWhen(this::hasCorrectAddressSize)
|
||||
.buildAndInstall(tool);
|
||||
|
||||
}
|
||||
|
||||
private boolean hasCorrectAddressSize(NavigatableActionContext context) {
|
||||
int size =
|
||||
context.getProgram().getAddressFactory().getDefaultAddressSpace().getSize();
|
||||
int size = context.getProgram().getAddressFactory().getDefaultAddressSpace().getSize();
|
||||
if ((size == 64) || (size == 32) || (size == 24) || (size == 16) || (size == 20) ||
|
||||
(size == 21)) {
|
||||
return true;
|
||||
|
@ -142,10 +140,10 @@ public class FindPossibleReferencesPlugin extends Plugin {
|
|||
localAction.setHelpLocation(
|
||||
new HelpLocation(HelpTopics.SEARCH, RESTORE_SELECTION_ACTION_NAME));
|
||||
String group = "selection";
|
||||
localAction.setMenuBarData(
|
||||
new MenuData(new String[] { "Restore Search Selection" }, group));
|
||||
localAction.setPopupMenuData(
|
||||
new MenuData(new String[] { "Restore Search Selection" }, group));
|
||||
localAction
|
||||
.setMenuBarData(new MenuData(new String[] { "Restore Search Selection" }, group));
|
||||
localAction
|
||||
.setPopupMenuData(new MenuData(new String[] { "Restore Search Selection" }, group));
|
||||
|
||||
localAction.setDescription(
|
||||
"Sets the program selection back to the selection this search was based upon.");
|
||||
|
@ -194,9 +192,8 @@ public class FindPossibleReferencesPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
if (currentProgram.getMemory()
|
||||
.getBlock(
|
||||
fromAddr)
|
||||
.getType() == MemoryBlockType.BIT_MAPPED) {
|
||||
.getBlock(fromAddr)
|
||||
.getType() == MemoryBlockType.BIT_MAPPED) {
|
||||
Msg.showWarn(getClass(), null, "Search For Direct References",
|
||||
"Cannot search for direct references on bit memory!");
|
||||
return;
|
||||
|
|
|
@ -45,6 +45,8 @@ import ghidra.util.HelpLocation;
|
|||
import ghidra.util.table.*;
|
||||
import ghidra.util.table.actions.DeleteTableRowAction;
|
||||
import ghidra.util.table.actions.MakeProgramSelectionAction;
|
||||
import utility.function.Callback;
|
||||
import utility.function.Dummy;
|
||||
|
||||
public class TableComponentProvider<T> extends ComponentProviderAdapter
|
||||
implements TableModelListener, NavigatableRemovalListener {
|
||||
|
@ -60,11 +62,13 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||
private String programName;
|
||||
private String windowSubMenu;
|
||||
private List<ComponentProviderActivationListener> activationListenerList = new ArrayList<>();
|
||||
private Callback closedCallback = Dummy.callback();
|
||||
|
||||
private Navigatable navigatable;
|
||||
private SelectionNavigationAction selectionNavigationAction;
|
||||
private DockingAction selectAction;
|
||||
private DockingAction removeItemsAction;
|
||||
private DockingAction externalGotoAction;
|
||||
|
||||
private Function<MouseEvent, ActionContext> contextProvider = null;
|
||||
|
||||
|
@ -170,7 +174,7 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||
selectionNavigationAction
|
||||
.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation"));
|
||||
|
||||
DockingAction externalGotoAction = new DockingAction("Go to External Location", getName()) {
|
||||
externalGotoAction = new DockingAction("Go to External Location", getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
gotoExternalAddress(getSelectedExternalAddress());
|
||||
|
@ -203,9 +207,21 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||
new MenuData(new String[] { "GoTo External Location" }, icon, null));
|
||||
externalGotoAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Navigation"));
|
||||
|
||||
plugin.getTool().addLocalAction(this, selectAction);
|
||||
plugin.getTool().addLocalAction(this, selectionNavigationAction);
|
||||
plugin.getTool().addLocalAction(this, externalGotoAction);
|
||||
tool.addLocalAction(this, selectAction);
|
||||
tool.addLocalAction(this, selectionNavigationAction);
|
||||
tool.addLocalAction(this, externalGotoAction);
|
||||
}
|
||||
|
||||
public void removeAllActions() {
|
||||
tool.removeLocalAction(this, externalGotoAction);
|
||||
tool.removeLocalAction(this, selectAction);
|
||||
tool.removeLocalAction(this, selectionNavigationAction);
|
||||
|
||||
// this action is conditionally added
|
||||
if (removeItemsAction != null) {
|
||||
tool.removeAction(removeItemsAction);
|
||||
removeItemsAction = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void installRemoveItemsAction() {
|
||||
|
@ -295,6 +311,8 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||
|
||||
@Override
|
||||
public void closeComponent() {
|
||||
this.closedCallback.call();
|
||||
|
||||
if (navigatable != null) {
|
||||
navigatable.removeNavigatableListener(this);
|
||||
}
|
||||
|
@ -419,4 +437,12 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||
public void setActionContextProvider(Function<MouseEvent, ActionContext> contextProvider) {
|
||||
this.contextProvider = contextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener to know when this provider is closed.
|
||||
* @param c the callback
|
||||
*/
|
||||
public void setClosedCallback(Callback c) {
|
||||
this.closedCallback = Dummy.ifNull(c);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.util.bin.format.omf;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class Omf2or4 implements StructConverter {
|
||||
|
||||
private int length;
|
||||
private long value;
|
||||
|
||||
public Omf2or4(int length, long value) {
|
||||
this.length = length;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public long value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
return length == 2 ? WORD : DWORD;
|
||||
}
|
||||
}
|
|
@ -32,9 +32,9 @@ public class OmfComdatExternalSymbol extends OmfExternalSymbol {
|
|||
|
||||
long max = reader.getPointerIndex() + getRecordLength() - 1;
|
||||
while (reader.getPointerIndex() < max) {
|
||||
int nameIndex = OmfRecord.readIndex(reader);
|
||||
int type = OmfRecord.readIndex(reader);
|
||||
externalLookups.add(new ExternalLookup(nameIndex, type));
|
||||
OmfIndex nameIndex = OmfRecord.readIndex(reader);
|
||||
OmfIndex type = OmfRecord.readIndex(reader);
|
||||
externalLookups.add(new ExternalLookup(nameIndex.value(), type.value()));
|
||||
}
|
||||
|
||||
readCheckSumByte(reader);
|
||||
|
|
|
@ -27,8 +27,8 @@ public class OmfComdefRecord extends OmfExternalSymbol {
|
|||
|
||||
long max = reader.getPointerIndex() + getRecordLength() - 1;
|
||||
while (reader.getPointerIndex() < max) {
|
||||
String name = OmfRecord.readString(reader);
|
||||
int typeIndex = OmfRecord.readIndex(reader);
|
||||
OmfString name = OmfRecord.readString(reader);
|
||||
OmfIndex typeIndex = OmfRecord.readIndex(reader);
|
||||
byte dataType = reader.readNextByte();
|
||||
int byteLength = 0;
|
||||
if (dataType == 0x61) { // FAR data, reads numElements and elSize
|
||||
|
@ -40,7 +40,7 @@ public class OmfComdefRecord extends OmfExternalSymbol {
|
|||
// Values 1 thru 5f plus 61, read the byte length
|
||||
byteLength = readCommunalLength(reader);
|
||||
}
|
||||
symbols.add(new OmfSymbol(name, typeIndex, 0, dataType, byteLength));
|
||||
symbols.add(new OmfSymbol(name.str(), typeIndex.value(), 0, dataType, byteLength));
|
||||
}
|
||||
readCheckSumByte(reader);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ import java.io.IOException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class OmfCommentRecord extends OmfRecord {
|
||||
// Language translator comment
|
||||
|
@ -34,7 +36,7 @@ public class OmfCommentRecord extends OmfRecord {
|
|||
|
||||
private byte commentType;
|
||||
private byte commentClass;
|
||||
private String value;
|
||||
private OmfString value;
|
||||
|
||||
public OmfCommentRecord(BinaryReader reader) throws IOException {
|
||||
readRecordHeader(reader);
|
||||
|
@ -46,7 +48,7 @@ public class OmfCommentRecord extends OmfRecord {
|
|||
case COMMENT_CLASS_DEFAULT_LIBRARY:
|
||||
byte[] bytes = reader.readNextByteArray(getRecordLength() -
|
||||
3 /* 3 = sizeof(commentType+commentClass+trailing_crcbyte*/);
|
||||
value = new String(bytes, StandardCharsets.US_ASCII); // assuming ASCII
|
||||
value = new OmfString(bytes.length, new String(bytes, StandardCharsets.US_ASCII)); // assuming ASCII
|
||||
break;
|
||||
case COMMENT_CLASS_LIBMOD:
|
||||
value = readString(reader);
|
||||
|
@ -67,6 +69,24 @@ public class OmfCommentRecord extends OmfRecord {
|
|||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
return value.str();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
int strlen = getRecordLength() - 3;
|
||||
|
||||
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
|
||||
struct.add(BYTE, "type", null);
|
||||
struct.add(WORD, "length", "");
|
||||
struct.add(BYTE, "comment_type", null);
|
||||
struct.add(BYTE, "comment_class", null);
|
||||
if (strlen > 0) {
|
||||
struct.add(new StringDataType(), strlen, "str", null);
|
||||
}
|
||||
struct.add(BYTE, "checksum", null);
|
||||
|
||||
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
|
||||
return struct;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,21 +23,21 @@ import ghidra.app.util.bin.BinaryReader;
|
|||
* Object representing data loaded directly into the final image.
|
||||
*/
|
||||
public abstract class OmfData extends OmfRecord implements Comparable<OmfData> {
|
||||
protected int segmentIndex;
|
||||
protected long dataOffset;
|
||||
protected OmfIndex segmentIndex;
|
||||
protected Omf2or4 dataOffset;
|
||||
|
||||
/**
|
||||
* @return get the segments index for this datablock
|
||||
*/
|
||||
public int getSegmentIndex() {
|
||||
return segmentIndex;
|
||||
return segmentIndex.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the starting offset, within the loaded image, of this data
|
||||
*/
|
||||
public long getDataOffset() {
|
||||
return dataOffset;
|
||||
return dataOffset.value();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,7 +47,7 @@ public abstract class OmfData extends OmfRecord implements Comparable<OmfData> {
|
|||
*/
|
||||
@Override
|
||||
public int compareTo(OmfData o) {
|
||||
return Long.compare(dataOffset, o.dataOffset);
|
||||
return Long.compare(dataOffset.value(), o.dataOffset.value());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,7 +27,7 @@ public class OmfEnumeratedData extends OmfData {
|
|||
readRecordHeader(reader);
|
||||
long start = reader.getPointerIndex();
|
||||
segmentIndex = OmfRecord.readIndex(reader);
|
||||
dataOffset = OmfRecord.readInt2Or4(reader, hasBigFields()) & 0xffffffffL;
|
||||
dataOffset = OmfRecord.readInt2Or4(reader, hasBigFields());
|
||||
streamOffset = reader.getPointerIndex();
|
||||
streamLength = getRecordLength() - 1 - (int) (streamOffset - start);
|
||||
reader.setPointerIndex(streamOffset + streamLength); // Skip over the data when reading header
|
||||
|
|
|
@ -20,11 +20,17 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class OmfExternalSymbol extends OmfRecord {
|
||||
|
||||
private boolean isStatic;
|
||||
protected List<OmfSymbol> symbols = new ArrayList<>();
|
||||
|
||||
private record Reference(OmfString name, OmfIndex type) {}
|
||||
|
||||
private List<Reference> refs = new ArrayList<>();
|
||||
|
||||
protected OmfExternalSymbol(boolean isStatic) {
|
||||
this.isStatic = isStatic;
|
||||
|
@ -36,9 +42,10 @@ public class OmfExternalSymbol extends OmfRecord {
|
|||
|
||||
long max = reader.getPointerIndex() + getRecordLength() - 1;
|
||||
while (reader.getPointerIndex() < max) {
|
||||
String name = OmfRecord.readString(reader);
|
||||
int type = OmfRecord.readIndex(reader);
|
||||
symbols.add(new OmfSymbol(name, type, 0, 0, 0));
|
||||
OmfString name = OmfRecord.readString(reader);
|
||||
OmfIndex type = OmfRecord.readIndex(reader);
|
||||
refs.add(new Reference(name, type));
|
||||
symbols.add(new OmfSymbol(name.str(), type.value(), 0, 0, 0));
|
||||
}
|
||||
|
||||
readCheckSumByte(reader);
|
||||
|
@ -51,4 +58,19 @@ public class OmfExternalSymbol extends OmfRecord {
|
|||
public boolean isStatic() {
|
||||
return isStatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
|
||||
struct.add(BYTE, "type", null);
|
||||
struct.add(WORD, "length", null);
|
||||
for (Reference ref : refs) {
|
||||
struct.add(ref.name.toDataType(), "name", null);
|
||||
struct.add(ref.type.toDataType(), "type", null);
|
||||
}
|
||||
struct.add(BYTE, "checksum", null);
|
||||
|
||||
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
|
||||
return struct;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,41 +17,52 @@ package ghidra.app.util.bin.format.omf;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class OmfFileHeader extends OmfRecord {
|
||||
|
||||
private String objectName; // Name of the object module
|
||||
private String libModuleName = null; // Name of the module (within a library)
|
||||
private String translator = null; // Usually the compiler/linker used to produce this object
|
||||
private OmfString objectName; // Name of the object module
|
||||
private String libModuleName = null; // Name of the module (within a library)
|
||||
private String translator = null; // Usually the compiler/linker used to produce this object
|
||||
private boolean isLittleEndian;
|
||||
private ArrayList<String> nameList = new ArrayList<String>(); // Indexable List of segment, group, ... names
|
||||
private ArrayList<OmfSegmentHeader> segments = new ArrayList<OmfSegmentHeader>();
|
||||
private ArrayList<OmfGroupRecord> groups = new ArrayList<OmfGroupRecord>();
|
||||
private ArrayList<OmfExternalSymbol> externsymbols = new ArrayList<OmfExternalSymbol>();
|
||||
private ArrayList<OmfSymbolRecord> symbols = new ArrayList<OmfSymbolRecord>();
|
||||
private ArrayList<OmfFixupRecord> fixup = new ArrayList<OmfFixupRecord>();
|
||||
private ArrayList<OmfSegmentHeader> extraSeg = null; // Holds implied segments that don't have official header record
|
||||
private List<OmfRecord> records = new ArrayList<>();
|
||||
private List<String> nameList = new ArrayList<>(); // Indexable List of segment, group, ... names
|
||||
private List<OmfSegmentHeader> segments = new ArrayList<>();
|
||||
private List<OmfGroupRecord> groups = new ArrayList<>();
|
||||
private List<OmfExternalSymbol> externsymbols = new ArrayList<>();
|
||||
private List<OmfSymbolRecord> symbols = new ArrayList<>();
|
||||
private List<OmfFixupRecord> fixup = new ArrayList<>();
|
||||
private List<OmfSegmentHeader> extraSeg = null; // Holds implied segments that don't have official header record
|
||||
// private OmfModuleEnd endModule = null;
|
||||
private boolean format16bit;
|
||||
|
||||
public OmfFileHeader(BinaryReader reader) throws IOException {
|
||||
readRecordHeader(reader);
|
||||
objectName = readString(reader); // This is usually the source code filename
|
||||
objectName = readString(reader); // This is usually the source code filename
|
||||
readCheckSumByte(reader);
|
||||
isLittleEndian = reader.isLittleEndian();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the list of records}
|
||||
*/
|
||||
public List<OmfRecord> getRecords() {
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is usually the original source filename
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return objectName;
|
||||
return objectName.str();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,42 +99,42 @@ public class OmfFileHeader extends OmfRecord {
|
|||
/**
|
||||
* @return the list of segments in this file
|
||||
*/
|
||||
public ArrayList<OmfSegmentHeader> getSegments() {
|
||||
public List<OmfSegmentHeader> getSegments() {
|
||||
return segments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of segments which are Borland extensions
|
||||
*/
|
||||
public ArrayList<OmfSegmentHeader> getExtraSegments() {
|
||||
public List<OmfSegmentHeader> getExtraSegments() {
|
||||
return extraSeg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of group records for this file
|
||||
*/
|
||||
public ArrayList<OmfGroupRecord> getGroups() {
|
||||
public List<OmfGroupRecord> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of symbols that are external to this file
|
||||
*/
|
||||
public ArrayList<OmfExternalSymbol> getExternalSymbols() {
|
||||
public List<OmfExternalSymbol> getExternalSymbols() {
|
||||
return externsymbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of symbols exported by this file
|
||||
*/
|
||||
public ArrayList<OmfSymbolRecord> getPublicSymbols() {
|
||||
public List<OmfSymbolRecord> getPublicSymbols() {
|
||||
return symbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of relocation records for this file
|
||||
*/
|
||||
public ArrayList<OmfFixupRecord> getFixups() {
|
||||
public List<OmfFixupRecord> getFixups() {
|
||||
return fixup;
|
||||
}
|
||||
|
||||
|
@ -313,14 +324,15 @@ public class OmfFileHeader extends OmfRecord {
|
|||
public static OmfFileHeader parse(BinaryReader reader, TaskMonitor monitor, MessageLog log)
|
||||
throws IOException, OmfException {
|
||||
OmfRecord record = OmfRecord.readRecord(reader);
|
||||
if (!(record instanceof OmfFileHeader)) {
|
||||
if (!(record instanceof OmfFileHeader header)) {
|
||||
throw new OmfException("Object file does not start with proper header");
|
||||
}
|
||||
OmfFileHeader header = (OmfFileHeader) record;
|
||||
header.records.add(header);
|
||||
OmfData lastDataBlock = null;
|
||||
|
||||
while (true) {
|
||||
record = OmfRecord.readRecord(reader);
|
||||
header.records.add(record);
|
||||
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
|
@ -403,8 +415,8 @@ public class OmfFileHeader extends OmfRecord {
|
|||
* @param groups is the list of specific segments that are grouped together in memory
|
||||
* @throws OmfException for malformed index/alignment/combining fields
|
||||
*/
|
||||
public static void doLinking(long startAddress, ArrayList<OmfSegmentHeader> segments,
|
||||
ArrayList<OmfGroupRecord> groups) throws OmfException {
|
||||
public static void doLinking(long startAddress, List<OmfSegmentHeader> segments,
|
||||
List<OmfGroupRecord> groups) throws OmfException {
|
||||
// Link anything in groups first
|
||||
for (int i = 0; i < groups.size(); ++i) {
|
||||
OmfGroupRecord group = groups.get(i);
|
||||
|
@ -489,4 +501,16 @@ public class OmfFileHeader extends OmfRecord {
|
|||
private static void logRecord(String description, OmfRecord record, MessageLog log) {
|
||||
log.appendMsg(description + " (" + record + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
|
||||
struct.add(BYTE, "type", null);
|
||||
struct.add(WORD, "length", null);
|
||||
struct.add(objectName.toDataType(), "name", null);
|
||||
struct.add(BYTE, "checksum", null);
|
||||
|
||||
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
|
||||
return struct;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,10 +67,10 @@ public class OmfFixupRecord extends OmfRecord {
|
|||
private byte first;
|
||||
private byte hiFixup;
|
||||
private byte fixData;
|
||||
private int index;
|
||||
private int frameDatum;
|
||||
private int targetDatum;
|
||||
private int targetDisplacement;
|
||||
private OmfIndex index;
|
||||
private OmfIndex frameDatum;
|
||||
private OmfIndex targetDatum;
|
||||
private Omf2or4 targetDisplacement;
|
||||
|
||||
/**
|
||||
* Read the next subrecord from the input reader
|
||||
|
@ -85,7 +85,7 @@ public class OmfFixupRecord extends OmfRecord {
|
|||
int method;
|
||||
final var rec = new Subrecord();
|
||||
rec.first = reader.readNextByte();
|
||||
rec.index = -1;
|
||||
rec.index = new OmfIndex(1, -1);
|
||||
if (rec.isThreadSubrecord()) {
|
||||
method = rec.getThreadMethod();
|
||||
if (method < 4) {
|
||||
|
@ -93,8 +93,8 @@ public class OmfFixupRecord extends OmfRecord {
|
|||
}
|
||||
return rec;
|
||||
}
|
||||
rec.targetDisplacement = 0;
|
||||
rec.targetDatum = 0;
|
||||
rec.targetDisplacement = new Omf2or4(2, 0);
|
||||
rec.targetDatum = new OmfIndex(1, 0);
|
||||
rec.hiFixup = reader.readNextByte();
|
||||
rec.fixData = reader.readNextByte();
|
||||
method = rec.getFrameMethod();
|
||||
|
@ -135,7 +135,7 @@ public class OmfFixupRecord extends OmfRecord {
|
|||
* @return Get the index for explicit thread or frame
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
return index.value();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,11 +170,11 @@ public class OmfFixupRecord extends OmfRecord {
|
|||
}
|
||||
|
||||
public int getTargetDatum() {
|
||||
return targetDatum;
|
||||
return targetDatum.value();
|
||||
}
|
||||
|
||||
public int getTargetDisplacement() {
|
||||
return targetDisplacement;
|
||||
return (int) targetDisplacement.value();
|
||||
}
|
||||
|
||||
public int getLocationType() {
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.app.util.bin.format.omf;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
@ -24,7 +25,7 @@ import ghidra.program.model.address.AddressSpace;
|
|||
import ghidra.program.model.lang.Language;
|
||||
|
||||
public class OmfGroupRecord extends OmfRecord {
|
||||
private int groupNameIndex;
|
||||
private OmfIndex groupNameIndex;
|
||||
private String groupName;
|
||||
private long vma = -1; // Assigned (by linker) starting address of the whole group
|
||||
private GroupSubrecord[] group;
|
||||
|
@ -72,7 +73,7 @@ public class OmfGroupRecord extends OmfRecord {
|
|||
}
|
||||
|
||||
public int getSegmentIndex(int i) {
|
||||
return group[i].segmentIndex;
|
||||
return group[i].segmentIndex.value();
|
||||
}
|
||||
|
||||
public Address getAddress(Language language) {
|
||||
|
@ -80,19 +81,19 @@ public class OmfGroupRecord extends OmfRecord {
|
|||
return addrSpace.getAddress(vma);
|
||||
}
|
||||
|
||||
public void resolveNames(ArrayList<String> nameList) throws OmfException {
|
||||
if (groupNameIndex <= 0) {
|
||||
public void resolveNames(List<String> nameList) throws OmfException {
|
||||
if (groupNameIndex.value() <= 0) {
|
||||
throw new OmfException("Cannot have unused group name");
|
||||
}
|
||||
if (groupNameIndex > nameList.size()) {
|
||||
if (groupNameIndex.value() > nameList.size()) {
|
||||
throw new OmfException("Group name index out of bounds");
|
||||
}
|
||||
groupName = nameList.get(groupNameIndex - 1);
|
||||
groupName = nameList.get(groupNameIndex.value() - 1);
|
||||
}
|
||||
|
||||
public static class GroupSubrecord {
|
||||
private byte componentType;
|
||||
private int segmentIndex;
|
||||
private OmfIndex segmentIndex;
|
||||
|
||||
public static GroupSubrecord read(BinaryReader reader) throws IOException {
|
||||
GroupSubrecord subrec = new GroupSubrecord();
|
||||
|
|
|
@ -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.util.bin.format.omf;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class OmfIndex implements StructConverter {
|
||||
|
||||
private int length;
|
||||
private int value;
|
||||
|
||||
public OmfIndex(int length, int value) {
|
||||
this.length = length;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public int value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
return length == 2 ? WORD : BYTE;
|
||||
}
|
||||
}
|
|
@ -78,7 +78,7 @@ public class OmfIteratedData extends OmfData {
|
|||
* Contain the definition of one part of a datablock with possible recursion
|
||||
*/
|
||||
public static class DataBlock {
|
||||
private int repeatCount;
|
||||
private Omf2or4 repeatCount;
|
||||
private int blockCount;
|
||||
private byte[] simpleBlock = null;
|
||||
private DataBlock[] nestedBlock = null;
|
||||
|
@ -86,7 +86,7 @@ public class OmfIteratedData extends OmfData {
|
|||
public static DataBlock read(BinaryReader reader, boolean hasBigFields) throws IOException {
|
||||
DataBlock subblock = new DataBlock();
|
||||
subblock.repeatCount = OmfRecord.readInt2Or4(reader, hasBigFields);
|
||||
subblock.blockCount = reader.readNextShort() & 0xffff;
|
||||
subblock.blockCount = reader.readNextUnsignedShort();
|
||||
if (subblock.blockCount == 0) {
|
||||
int size = reader.readNextByte() & 0xff;
|
||||
subblock.simpleBlock = new byte[size];
|
||||
|
@ -110,7 +110,7 @@ public class OmfIteratedData extends OmfData {
|
|||
* @return The position after the block
|
||||
*/
|
||||
public int fillBuffer(byte[] buffer, int pos) {
|
||||
for (int i = 0; i < repeatCount; ++i) {
|
||||
for (int i = 0; i < (int) repeatCount.value(); ++i) {
|
||||
if (simpleBlock != null) {
|
||||
for (byte element : simpleBlock) {
|
||||
buffer[pos] = element;
|
||||
|
@ -139,7 +139,7 @@ public class OmfIteratedData extends OmfData {
|
|||
length += block.getLength();
|
||||
}
|
||||
}
|
||||
return length * repeatCount;
|
||||
return length * (int) repeatCount.value();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,24 +17,39 @@ package ghidra.app.util.bin.format.omf;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class OmfNamesRecord extends OmfRecord {
|
||||
private ArrayList<String> name;
|
||||
private List<OmfString> names = new ArrayList<>();
|
||||
|
||||
public OmfNamesRecord(BinaryReader reader) throws IOException {
|
||||
readRecordHeader(reader);
|
||||
long max = reader.getPointerIndex() + getRecordLength() - 1;
|
||||
name = new ArrayList<String>();
|
||||
while (reader.getPointerIndex() < max) {
|
||||
String nm = OmfRecord.readString(reader);
|
||||
name.add(nm);
|
||||
names.add(OmfRecord.readString(reader));
|
||||
}
|
||||
readCheckSumByte(reader);
|
||||
}
|
||||
|
||||
public void appendNames(ArrayList<String> namelist) {
|
||||
namelist.addAll(name);
|
||||
public void appendNames(List<String> namelist) {
|
||||
namelist.addAll(names.stream().map(name -> name.str()).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
|
||||
struct.add(BYTE, "type", null);
|
||||
struct.add(WORD, "length", null);
|
||||
for (OmfString name : names) {
|
||||
struct.add(name.toDataType(), -1, "name", null);
|
||||
}
|
||||
struct.add(BYTE, "checksum", null);
|
||||
|
||||
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
|
||||
return struct;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,11 @@ import java.lang.reflect.Field;
|
|||
import java.lang.reflect.Modifier;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public abstract class OmfRecord {
|
||||
public abstract class OmfRecord implements StructConverter {
|
||||
public final static byte RHEADR = (byte) 0x6E; // Obsolete
|
||||
public final static byte REGINT = (byte) 0x70; // Obsolete
|
||||
public final static byte REDATA = (byte) 0x72; // Obsolete
|
||||
|
@ -68,6 +71,8 @@ public abstract class OmfRecord {
|
|||
public final static byte START = (byte) 0xF0;
|
||||
public final static byte END = (byte) 0xF1;
|
||||
|
||||
protected static final String CATEGORY_PATH = "/OMF";
|
||||
|
||||
protected byte recordType;
|
||||
protected int recordLength;
|
||||
protected long recordOffset;
|
||||
|
@ -120,22 +125,26 @@ public abstract class OmfRecord {
|
|||
return (reader.readNextByte() & 0xff);
|
||||
}
|
||||
|
||||
public static int readInt2Or4(BinaryReader reader, boolean isBig) throws IOException {
|
||||
if (isBig)
|
||||
return reader.readNextInt();
|
||||
return (reader.readNextShort() & 0xffff);
|
||||
public static Omf2or4 readInt2Or4(BinaryReader reader, boolean isBig) throws IOException {
|
||||
if (isBig) {
|
||||
return new Omf2or4(4, reader.readNextInt());
|
||||
}
|
||||
return new Omf2or4(2, reader.readNextUnsignedShort());
|
||||
}
|
||||
|
||||
public static int readIndex(BinaryReader reader) throws IOException {
|
||||
public static OmfIndex readIndex(BinaryReader reader) throws IOException {
|
||||
int length;
|
||||
int indexWord;
|
||||
byte firstByte = reader.readNextByte();
|
||||
if ((firstByte & 0x80) != 0) {
|
||||
indexWord = (firstByte & 0x7f) * 0x100 + (reader.readNextByte() & 0xff);
|
||||
length = 2;
|
||||
}
|
||||
else {
|
||||
indexWord = firstByte;
|
||||
length = 1;
|
||||
}
|
||||
return indexWord;
|
||||
return new OmfIndex(length, indexWord);
|
||||
}
|
||||
|
||||
public static OmfRecord readRecord(BinaryReader reader) throws IOException, OmfException {
|
||||
|
@ -215,9 +224,9 @@ public abstract class OmfRecord {
|
|||
* @return the read OMF string
|
||||
* @throws IOException if an IO-related error occurred
|
||||
*/
|
||||
public static String readString(BinaryReader reader) throws IOException {
|
||||
public static OmfString readString(BinaryReader reader) throws IOException {
|
||||
int count = reader.readNextByte() & 0xff;
|
||||
return reader.readNextAsciiString(count);
|
||||
return new OmfString(count, reader.readNextAsciiString(count));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -249,4 +258,16 @@ public abstract class OmfRecord {
|
|||
return String.format("name: %s, type: 0x%x, offset: 0x%x, length: 0x%x",
|
||||
getRecordName(recordType & (byte) 0xfe), recordType, recordOffset, recordLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
|
||||
struct.add(BYTE, "type", null);
|
||||
struct.add(WORD, "length", null);
|
||||
struct.add(new ArrayDataType(BYTE, getRecordLength() - 1, 1), "contents", null);
|
||||
struct.add(BYTE, "checksum", null);
|
||||
|
||||
struct.setCategoryPath(new CategoryPath(CATEGORY_PATH));
|
||||
return struct;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,24 +17,25 @@ package ghidra.app.util.bin.format.omf;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.app.util.opinion.OmfLoader;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class OmfSegmentHeader extends OmfRecord {
|
||||
private byte segAttr; // first byte of Segment Attributes
|
||||
private int frameNumber;
|
||||
private int offset;
|
||||
private long segmentLength;
|
||||
private int segmentNameIndex;
|
||||
private int classNameIndex;
|
||||
private int overlayNameIndex;
|
||||
private Omf2or4 segmentLength;
|
||||
private OmfIndex segmentNameIndex;
|
||||
private OmfIndex classNameIndex;
|
||||
private OmfIndex overlayNameIndex;
|
||||
private String segmentName;
|
||||
private String className;
|
||||
private String overlayName;
|
||||
|
@ -48,10 +49,10 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
OmfSegmentHeader(int num, int datatype) {
|
||||
// generate a special Borland header
|
||||
segAttr = (byte) 0xa9;
|
||||
segmentLength = 0;
|
||||
segmentNameIndex = 0;
|
||||
classNameIndex = 0;
|
||||
overlayNameIndex = 0;
|
||||
segmentLength = new Omf2or4(2,0);
|
||||
segmentNameIndex = new OmfIndex(1, 0);
|
||||
classNameIndex = new OmfIndex(1, 0);
|
||||
overlayNameIndex = new OmfIndex(1, 0);
|
||||
overlayName = "";
|
||||
if (datatype == 1) {
|
||||
segmentName = "EXTRATEXT_";
|
||||
|
@ -90,7 +91,7 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
offset = reader.readNextByte() & 0xff;
|
||||
vma = (long) frameNumber + offset;
|
||||
}
|
||||
segmentLength = OmfRecord.readInt2Or4(reader, hasBigFields) & 0xffffffffL;
|
||||
segmentLength = OmfRecord.readInt2Or4(reader, hasBigFields);
|
||||
segmentNameIndex = OmfRecord.readIndex(reader);
|
||||
classNameIndex = OmfRecord.readIndex(reader);
|
||||
overlayNameIndex = OmfRecord.readIndex(reader);
|
||||
|
@ -98,10 +99,10 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
int B = (segAttr >> 1) & 1;
|
||||
if (B == 1) { // Ignore the segmentLength field
|
||||
if (getRecordType() == OmfRecord.SEGDEF) {
|
||||
segmentLength = 0x10000L; // Exactly 64K segment
|
||||
segmentLength = new Omf2or4(segmentLength.length(), 0x10000L); // Exactly 64K segment
|
||||
}
|
||||
else {
|
||||
segmentLength = 0x100000000L; // Exactly 4G segment
|
||||
segmentLength = new Omf2or4(segmentLength.length(), 0x100000000L); // Exactly 4G segment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +190,7 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
* @return the length of the segment in bytes
|
||||
*/
|
||||
public long getSegmentLength() {
|
||||
return segmentLength;
|
||||
return segmentLength.value();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -277,7 +278,7 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
throw new OmfException("Unsupported alignment type");
|
||||
}
|
||||
vma = firstValidAddress;
|
||||
firstValidAddress = vma + segmentLength;
|
||||
firstValidAddress = vma + segmentLength.value();
|
||||
return firstValidAddress;
|
||||
}
|
||||
|
||||
|
@ -288,33 +289,33 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
* @param nameList is the array of names associated with the file
|
||||
* @throws OmfException for improper name indices
|
||||
*/
|
||||
protected void resolveNames(ArrayList<String> nameList) throws OmfException {
|
||||
if (segmentNameIndex == 0) {
|
||||
protected void resolveNames(List<String> nameList) throws OmfException {
|
||||
if (segmentNameIndex.value() == 0) {
|
||||
segmentName = ""; // Name is unused
|
||||
}
|
||||
else {
|
||||
if (segmentNameIndex > nameList.size()) {
|
||||
if (segmentNameIndex.value() > nameList.size()) {
|
||||
throw new OmfException("Segment name index out of bounds");
|
||||
}
|
||||
segmentName = nameList.get(segmentNameIndex - 1);
|
||||
segmentName = nameList.get(segmentNameIndex.value() - 1);
|
||||
}
|
||||
if (classNameIndex == 0) {
|
||||
if (classNameIndex.value() == 0) {
|
||||
className = "";
|
||||
}
|
||||
else {
|
||||
if (classNameIndex > nameList.size()) {
|
||||
if (classNameIndex.value() > nameList.size()) {
|
||||
throw new OmfException("Class name index out of bounds");
|
||||
}
|
||||
className = nameList.get(classNameIndex - 1);
|
||||
className = nameList.get(classNameIndex.value() - 1);
|
||||
}
|
||||
if (overlayNameIndex == 0) {
|
||||
if (overlayNameIndex.value() == 0) {
|
||||
overlayName = "";
|
||||
}
|
||||
else {
|
||||
if (overlayNameIndex > nameList.size()) {
|
||||
if (overlayNameIndex.value() > nameList.size()) {
|
||||
throw new OmfException("Overlay name index out of bounds");
|
||||
}
|
||||
overlayName = nameList.get(overlayNameIndex - 1);
|
||||
overlayName = nameList.get(overlayNameIndex.value() - 1);
|
||||
}
|
||||
|
||||
// Once we know the class name, we can make some educated guesses about read/write/exec permissions
|
||||
|
@ -347,8 +348,8 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
*/
|
||||
protected void appendEnumeratedData(OmfEnumeratedData rec) {
|
||||
long blockend = rec.getDataOffset() + rec.getLength();
|
||||
if (blockend > segmentLength) {
|
||||
segmentLength = blockend;
|
||||
if (blockend > segmentLength.value()) {
|
||||
segmentLength = new Omf2or4(segmentLength.length(), blockend);
|
||||
}
|
||||
dataBlocks.add(rec);
|
||||
}
|
||||
|
@ -380,7 +381,7 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
this.log = log;
|
||||
pointer = 0;
|
||||
dataUpNext = 0;
|
||||
if (pointer < segmentLength) {
|
||||
if (pointer < segmentLength.value()) {
|
||||
establishNextBuffer();
|
||||
}
|
||||
}
|
||||
|
@ -423,7 +424,7 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
}
|
||||
}
|
||||
// We may have filler after the last block
|
||||
long size = segmentLength - pointer;
|
||||
long size = segmentLength.value() - pointer;
|
||||
if (size > OmfLoader.MAX_UNINITIALIZED_FILL) {
|
||||
throw new IOException("Large hole at the end of OMF segment: " + segmentName);
|
||||
}
|
||||
|
@ -436,7 +437,7 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (pointer < segmentLength) {
|
||||
if (pointer < segmentLength.value()) {
|
||||
if (bufferpointer < buffer.length) {
|
||||
pointer++;
|
||||
return buffer[bufferpointer++] & 0xff;
|
||||
|
@ -454,4 +455,25 @@ public class OmfSegmentHeader extends OmfRecord {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
|
||||
struct.add(BYTE, "type", null);
|
||||
struct.add(WORD, "length", null);
|
||||
struct.add(BYTE, "segment_attr", null);
|
||||
int A = (segAttr >> 5) & 7;
|
||||
if (A == 0) {
|
||||
struct.add(WORD, "frame_number", null);
|
||||
struct.add(BYTE, "offset", null);
|
||||
}
|
||||
struct.add(segmentLength.toDataType(), "segment_length", null);
|
||||
struct.add(segmentNameIndex.toDataType(), "segment_name_index", null);
|
||||
struct.add(classNameIndex.toDataType(), "class_name_index", null);
|
||||
struct.add(overlayNameIndex.toDataType(), "overlay_name_index", null);
|
||||
struct.add(BYTE, "checksum", null);
|
||||
|
||||
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
|
||||
return struct;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/* ###
|
||||
* 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.util.bin.format.omf;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class OmfString implements StructConverter {
|
||||
|
||||
private int length;
|
||||
private String str;
|
||||
|
||||
public OmfString(int length, String str) {
|
||||
this.length = length;
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public String str() {
|
||||
return str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
if (length == 0) {
|
||||
return BYTE;
|
||||
}
|
||||
|
||||
StructureDataType struct = new StructureDataType("OmfString", 0);
|
||||
struct.add(BYTE, "length", "");
|
||||
struct.add(new StringDataType(), length, "str", null);
|
||||
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
|
||||
return struct;
|
||||
}
|
||||
}
|
|
@ -20,13 +20,18 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class OmfSymbolRecord extends OmfRecord {
|
||||
private int baseGroupIndex;
|
||||
private int baseSegmentIndex;
|
||||
private OmfIndex baseGroupIndex;
|
||||
private OmfIndex baseSegmentIndex;
|
||||
private int baseFrame;
|
||||
private boolean isStatic;
|
||||
private OmfSymbol[] symbol;
|
||||
private List<Reference> refs = new ArrayList<>();
|
||||
|
||||
private record Reference(OmfString name, Omf2or4 offset, OmfIndex type) {}
|
||||
|
||||
public OmfSymbolRecord(BinaryReader reader, boolean isStatic) throws IOException {
|
||||
this.isStatic = isStatic;
|
||||
|
@ -35,17 +40,18 @@ public class OmfSymbolRecord extends OmfRecord {
|
|||
boolean hasBigFields = hasBigFields();
|
||||
baseGroupIndex = OmfRecord.readIndex(reader);
|
||||
baseSegmentIndex = OmfRecord.readIndex(reader);
|
||||
if (baseSegmentIndex == 0) {
|
||||
baseFrame = reader.readNextShort() & 0xffff;
|
||||
if (baseSegmentIndex.value() == 0) {
|
||||
baseFrame = reader.readNextUnsignedShort();
|
||||
}
|
||||
|
||||
ArrayList<OmfSymbol> symbollist = new ArrayList<OmfSymbol>();
|
||||
while (reader.getPointerIndex() < max) {
|
||||
String name = OmfRecord.readString(reader);
|
||||
long offset = OmfRecord.readInt2Or4(reader, hasBigFields) & 0xffffffffL;
|
||||
int type = OmfRecord.readIndex(reader);
|
||||
OmfSymbol subrec = new OmfSymbol(name, type, offset, 0, 0);
|
||||
OmfString name = OmfRecord.readString(reader);
|
||||
Omf2or4 offset = OmfRecord.readInt2Or4(reader, hasBigFields);
|
||||
OmfIndex type = OmfRecord.readIndex(reader);
|
||||
OmfSymbol subrec = new OmfSymbol(name.str(), type.value(), offset.value(), 0, 0);
|
||||
symbollist.add(subrec);
|
||||
refs.add(new Reference(name, offset, type));
|
||||
}
|
||||
readCheckSumByte(reader);
|
||||
symbol = new OmfSymbol[symbollist.size()];
|
||||
|
@ -57,11 +63,11 @@ public class OmfSymbolRecord extends OmfRecord {
|
|||
}
|
||||
|
||||
public int getGroupIndex() {
|
||||
return baseGroupIndex;
|
||||
return baseGroupIndex.value();
|
||||
}
|
||||
|
||||
public int getSegmentIndex() {
|
||||
return baseSegmentIndex;
|
||||
return baseSegmentIndex.value();
|
||||
}
|
||||
|
||||
public int numSymbols() {
|
||||
|
@ -76,4 +82,29 @@ public class OmfSymbolRecord extends OmfRecord {
|
|||
return List.of(symbol);
|
||||
}
|
||||
|
||||
public int getBaseFrame() {
|
||||
return baseFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
StructureDataType struct = new StructureDataType(getRecordName(getRecordType()), 0);
|
||||
struct.add(BYTE, "type", null);
|
||||
struct.add(WORD, "length", null);
|
||||
struct.add(baseGroupIndex.toDataType(), "base_group_index", null);
|
||||
struct.add(baseSegmentIndex.toDataType(), "base_segment_index", null);
|
||||
if (baseSegmentIndex.value() == 0) {
|
||||
struct.add(WORD, "base_frame", null);
|
||||
}
|
||||
for (Reference ref : refs) {
|
||||
struct.add(ref.name.toDataType(), "name", null);
|
||||
struct.add(ref.offset.toDataType(), "offset", null);
|
||||
struct.add(ref.type.toDataType(), "type", null);
|
||||
}
|
||||
struct.add(BYTE, "checksum", null);
|
||||
|
||||
struct.setCategoryPath(new CategoryPath(OmfRecord.CATEGORY_PATH));
|
||||
return struct;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ package ghidra.app.util.headless;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import generic.stl.Pair;
|
||||
import ghidra.*;
|
||||
|
@ -37,7 +37,76 @@ import ghidra.util.exception.InvalidInputException;
|
|||
*/
|
||||
public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
|
||||
/**
|
||||
* Headless command line arguments.
|
||||
* <p>
|
||||
* NOTE: Please update 'analyzeHeadlessREADME.html' if changing command line parameters
|
||||
*/
|
||||
private enum Arg {
|
||||
//@formatter:off
|
||||
IMPORT("-import", true, "[<directory>|<file>]+"),
|
||||
PROCESS("-process", true, "[<project_file>]"),
|
||||
PRE_SCRIPT("-prescript", true, "<ScriptName>"),
|
||||
POST_SCRIPT("-postscript", true, "<ScriptName>"),
|
||||
SCRIPT_PATH("-scriptPath", true, "\"<path1>[;<path2>...]\""),
|
||||
PROPERTIES_PATH("-propertiesPath", true, "\"<path1>[;<path2>...]\""),
|
||||
SCRIPT_LOG("-scriptlog", true, "<path to script log file>"),
|
||||
LOG("-log", true, "<path to log file>"),
|
||||
OVERWRITE("-overwrite", false),
|
||||
RECURSIVE("-recursive", false),
|
||||
READ_ONLY("-readOnly", false),
|
||||
DELETE_PROJECT("-deleteproject", false),
|
||||
NO_ANALYSIS("-noanalysis", false),
|
||||
PROCESSOR("-processor", true, "<languageID>"),
|
||||
CSPEC("-cspec", true, "<compilerSpecID>"),
|
||||
ANALYSIS_TIMEOUT_PER_FILE("-analysisTimeoutPerFile", true, "<timeout in seconds>"),
|
||||
KEYSTORE("-keystore", true, "<KeystorePath>"),
|
||||
CONNECT("-connect", false, "[<userID>]"),
|
||||
PASSWORD("-p", false),
|
||||
COMMIT("-commit", false, "[\"<comment>\"]]"),
|
||||
OK_TO_DELETE("-okToDelete", false),
|
||||
MAX_CPU("-max-cpu", true, "<max cpu cores to use>"),
|
||||
LIBRARY_SEARCH_PATHS("-librarySearchPaths", true, "<path1>[;<path2>...]"),
|
||||
LOADER("-loader", true, "<desired loader name>"),
|
||||
LOADER_ARGS(Loader.COMMAND_LINE_ARG_PREFIX + "-", true, "<loader argument value>") {
|
||||
@Override
|
||||
public boolean matches(String arg) {
|
||||
return arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX + "-");
|
||||
}
|
||||
};
|
||||
//@formatter:on
|
||||
|
||||
private String name;
|
||||
private boolean requiresSubArgs;
|
||||
private String subArgFormat;
|
||||
|
||||
private Arg(String name, boolean requiresSubArgs, String subArgFormat) {
|
||||
this.name = name;
|
||||
this.requiresSubArgs = requiresSubArgs;
|
||||
this.subArgFormat = subArgFormat;
|
||||
}
|
||||
|
||||
private Arg(String name, boolean requiresSubArgs) {
|
||||
this(name, requiresSubArgs, "");
|
||||
}
|
||||
|
||||
public String usage() {
|
||||
return "%s%s%s".formatted(name, subArgFormat.isEmpty() ? "" : " ", subArgFormat);
|
||||
}
|
||||
|
||||
public boolean matches(String arg) {
|
||||
return arg.equalsIgnoreCase(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int EXIT_CODE_ERROR = 1;
|
||||
private static final Set<String> ALL_ARG_NAMES =
|
||||
Arrays.stream(Arg.values()).map(a -> a.name).collect(Collectors.toSet());
|
||||
|
||||
/**
|
||||
* The entry point of 'analyzeHeadless.bat'. Parses the command line arguments to the script
|
||||
|
@ -64,7 +133,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
if (args[0].startsWith("ghidra:")) {
|
||||
optionStartIndex = 1;
|
||||
try {
|
||||
ghidraURL = new URL(args[0]);
|
||||
ghidraURL = new URI(args[0]).toURL();
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
System.err.println("Invalid Ghidra URL: " + args[0]);
|
||||
|
@ -98,10 +167,10 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
File logFile = null;
|
||||
File scriptLogFile = null;
|
||||
for (int argi = optionStartIndex; argi < args.length; argi++) {
|
||||
if (checkArgument("-log", args, argi)) {
|
||||
if (checkArgument(Arg.LOG, args, argi)) {
|
||||
logFile = new File(args[++argi]);
|
||||
}
|
||||
else if (checkArgument("-scriptlog", args, argi)) {
|
||||
else if (checkArgument(Arg.SCRIPT_LOG, args, argi)) {
|
||||
scriptLogFile = new File(args[++argi]);
|
||||
}
|
||||
}
|
||||
|
@ -158,7 +227,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
String languageId = null;
|
||||
String compilerSpecId = null;
|
||||
String keystorePath = null;
|
||||
String serverUID = null;
|
||||
String userId = null;
|
||||
boolean allowPasswordPrompt = false;
|
||||
List<Pair<String, String[]>> preScripts = new LinkedList<>();
|
||||
List<Pair<String, String[]>> postScripts = new LinkedList<>();
|
||||
|
@ -166,57 +235,57 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
for (int argi = startIndex; argi < args.length; argi++) {
|
||||
|
||||
String arg = args[argi];
|
||||
if (checkArgument("-log", args, argi)) {
|
||||
if (checkArgument(Arg.LOG, args, argi)) {
|
||||
// Already processed
|
||||
argi++;
|
||||
}
|
||||
else if (checkArgument("-scriptlog", args, argi)) {
|
||||
else if (checkArgument(Arg.SCRIPT_LOG, args, argi)) {
|
||||
// Already processed
|
||||
argi++;
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("-overwrite")) {
|
||||
else if (checkArgument(Arg.OVERWRITE, args, argi)) {
|
||||
options.enableOverwriteOnConflict(true);
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("-noanalysis")) {
|
||||
else if (checkArgument(Arg.NO_ANALYSIS, args, argi)) {
|
||||
options.enableAnalysis(false);
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("-deleteproject")) {
|
||||
else if (checkArgument(Arg.DELETE_PROJECT, args, argi)) {
|
||||
options.setDeleteCreatedProjectOnClose(true);
|
||||
}
|
||||
else if (checkArgument("-loader", args, argi)) {
|
||||
else if (checkArgument(Arg.LOADER, args, argi)) {
|
||||
loaderName = args[++argi];
|
||||
}
|
||||
else if (arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX)) {
|
||||
if (args[argi + 1].startsWith("-")) {
|
||||
else if (checkArgument(Arg.LOADER_ARGS, args, argi)) {
|
||||
if (ALL_ARG_NAMES.contains(args[argi + 1])) {
|
||||
throw new InvalidInputException(args[argi] + " expects value to follow.");
|
||||
}
|
||||
loaderArgs.add(new Pair<>(arg, args[++argi]));
|
||||
}
|
||||
else if (checkArgument("-processor", args, argi)) {
|
||||
else if (checkArgument(Arg.PROCESSOR, args, argi)) {
|
||||
languageId = args[++argi];
|
||||
}
|
||||
else if (checkArgument("-cspec", args, argi)) {
|
||||
else if (checkArgument(Arg.CSPEC, args, argi)) {
|
||||
compilerSpecId = args[++argi];
|
||||
}
|
||||
else if (checkArgument("-prescript", args, argi)) {
|
||||
else if (checkArgument(Arg.PRE_SCRIPT, args, argi)) {
|
||||
String scriptName = args[++argi];
|
||||
String[] scriptArgs = getSubArguments(args, argi);
|
||||
String[] scriptArgs = getSubArguments(args, argi, ALL_ARG_NAMES);
|
||||
argi += scriptArgs.length;
|
||||
preScripts.add(new Pair<>(scriptName, scriptArgs));
|
||||
}
|
||||
else if (checkArgument("-postscript", args, argi)) {
|
||||
else if (checkArgument(Arg.POST_SCRIPT, args, argi)) {
|
||||
String scriptName = args[++argi];
|
||||
String[] scriptArgs = getSubArguments(args, argi);
|
||||
String[] scriptArgs = getSubArguments(args, argi, ALL_ARG_NAMES);
|
||||
argi += scriptArgs.length;
|
||||
postScripts.add(new Pair<>(scriptName, scriptArgs));
|
||||
}
|
||||
else if (checkArgument("-scriptPath", args, argi)) {
|
||||
else if (checkArgument(Arg.SCRIPT_PATH, args, argi)) {
|
||||
options.setScriptDirectories(args[++argi]);
|
||||
}
|
||||
else if (checkArgument("-propertiesPath", args, argi)) {
|
||||
else if (checkArgument(Arg.PROPERTIES_PATH, args, argi)) {
|
||||
options.setPropertiesFileDirectories(args[++argi]);
|
||||
}
|
||||
else if (checkArgument("-import", args, argi)) {
|
||||
else if (checkArgument(Arg.IMPORT, args, argi)) {
|
||||
File inputFile = null;
|
||||
try {
|
||||
inputFile = new File(args[++argi]);
|
||||
|
@ -242,7 +311,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
nextArg = args[++argi];
|
||||
|
||||
// Check if next argument is a parameter
|
||||
if (nextArg.charAt(0) == '-') {
|
||||
if (ALL_ARG_NAMES.contains(nextArg)) {
|
||||
argi--;
|
||||
break;
|
||||
}
|
||||
|
@ -258,29 +327,29 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
filesToImport.add(otherFile);
|
||||
}
|
||||
}
|
||||
else if ("-connect".equals(args[argi])) {
|
||||
else if (checkArgument(Arg.CONNECT, args, argi)) {
|
||||
if ((argi + 1) < args.length) {
|
||||
arg = args[argi + 1];
|
||||
if (!arg.startsWith("-")) {
|
||||
if (!ALL_ARG_NAMES.contains(arg)) {
|
||||
// serverUID is optional argument after -connect
|
||||
serverUID = arg;
|
||||
userId = arg;
|
||||
++argi;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("-commit".equals(args[argi])) {
|
||||
else if (checkArgument(Arg.COMMIT, args, argi)) {
|
||||
String comment = null;
|
||||
if ((argi + 1) < args.length) {
|
||||
arg = args[argi + 1];
|
||||
if (!arg.startsWith("-")) {
|
||||
// comment is optional argument after -commit
|
||||
if (!ALL_ARG_NAMES.contains(arg)) {
|
||||
// commit is optional argument after -commit
|
||||
comment = arg;
|
||||
++argi;
|
||||
}
|
||||
}
|
||||
options.setCommitFiles(true, comment);
|
||||
}
|
||||
else if (checkArgument("-keystore", args, argi)) {
|
||||
else if (checkArgument(Arg.KEYSTORE, args, argi)) {
|
||||
keystorePath = args[++argi];
|
||||
File keystore = new File(keystorePath);
|
||||
if (!keystore.isFile()) {
|
||||
|
@ -288,13 +357,13 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
keystore.getAbsolutePath() + " is not a valid keystore file.");
|
||||
}
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("-p")) {
|
||||
else if (checkArgument(Arg.PASSWORD, args, argi)) {
|
||||
allowPasswordPrompt = true;
|
||||
}
|
||||
else if ("-analysisTimeoutPerFile".equalsIgnoreCase(args[argi])) {
|
||||
else if (checkArgument(Arg.ANALYSIS_TIMEOUT_PER_FILE, args, argi)) {
|
||||
options.setPerFileAnalysisTimeout(args[++argi]);
|
||||
}
|
||||
else if ("-process".equals(args[argi])) {
|
||||
else if (checkArgument(Arg.PROCESS, args, argi)) {
|
||||
if (options.runScriptsNoImport) {
|
||||
throw new InvalidInputException(
|
||||
"The -process option may only be specified once.");
|
||||
|
@ -302,7 +371,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
String processBinary = null;
|
||||
if ((argi + 1) < args.length) {
|
||||
arg = args[argi + 1];
|
||||
if (!arg.startsWith("-")) {
|
||||
if (!ALL_ARG_NAMES.contains(arg)) {
|
||||
// processBinary is optional argument after -process
|
||||
processBinary = arg;
|
||||
++argi;
|
||||
|
@ -310,11 +379,11 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
}
|
||||
options.setRunScriptsNoImport(true, processBinary);
|
||||
}
|
||||
else if ("-recursive".equals(args[argi])) {
|
||||
else if (checkArgument(Arg.RECURSIVE, args, argi)) {
|
||||
Integer depth = null;
|
||||
if ((argi + 1) < args.length) {
|
||||
arg = args[argi + 1];
|
||||
if (!arg.startsWith("-")) {
|
||||
if (!ALL_ARG_NAMES.contains(arg)) {
|
||||
// depth is optional argument after -recursive
|
||||
try {
|
||||
depth = Integer.parseInt(arg);
|
||||
|
@ -327,10 +396,10 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
}
|
||||
options.enableRecursiveProcessing(true, depth);
|
||||
}
|
||||
else if ("-readOnly".equalsIgnoreCase(args[argi])) {
|
||||
else if (checkArgument(Arg.READ_ONLY, args, argi)) {
|
||||
options.enableReadOnlyProcessing(true);
|
||||
}
|
||||
else if (checkArgument("-max-cpu", args, argi)) {
|
||||
else if (checkArgument(Arg.MAX_CPU, args, argi)) {
|
||||
String cpuVal = args[++argi];
|
||||
try {
|
||||
options.setMaxCpu(Integer.parseInt(cpuVal));
|
||||
|
@ -339,12 +408,15 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
throw new InvalidInputException("Invalid value for max-cpu: " + cpuVal);
|
||||
}
|
||||
}
|
||||
else if ("-okToDelete".equalsIgnoreCase(args[argi])) {
|
||||
else if (checkArgument(Arg.OK_TO_DELETE, args, argi)) {
|
||||
options.setOkToDelete(true);
|
||||
}
|
||||
else if (checkArgument("-librarySearchPaths", args, argi)) {
|
||||
else if (checkArgument(Arg.LIBRARY_SEARCH_PATHS, args, argi)) {
|
||||
LibrarySearchPathManager.setLibraryPaths(args[++argi].split(";"));
|
||||
}
|
||||
else if (ALL_ARG_NAMES.contains(args[argi])) {
|
||||
throw new AssertionError("Valid option was not processed: " + args[argi]);
|
||||
}
|
||||
else {
|
||||
throw new InvalidInputException("Bad argument: " + arg);
|
||||
}
|
||||
|
@ -362,7 +434,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
|
||||
// Set up optional Ghidra Server authenticator
|
||||
try {
|
||||
options.setClientCredentials(serverUID, keystorePath, allowPasswordPrompt);
|
||||
options.setClientCredentials(userId, keystorePath, allowPasswordPrompt);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new InvalidInputException(
|
||||
|
@ -438,47 +510,48 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
* @param execCmd the command used to run the headless analyzer from the calling method.
|
||||
*/
|
||||
public static void usage(String execCmd) {
|
||||
System.out.println("Headless Analyzer Usage: " + execCmd);
|
||||
System.out.println(" <project_location> <project_name>[/<folder_path>]");
|
||||
System.out.println(
|
||||
" | ghidra://<server>[:<port>]/<repository_name>[/<folder_path>]");
|
||||
System.out.println(
|
||||
" [[-import [<directory>|<file>]+] | [-process [<project_file>]]]");
|
||||
System.out.println(" [-preScript <ScriptName>]");
|
||||
System.out.println(" [-postScript <ScriptName>]");
|
||||
System.out.println(" [-scriptPath \"<path1>[;<path2>...]\"]");
|
||||
System.out.println(" [-propertiesPath \"<path1>[;<path2>...]\"]");
|
||||
System.out.println(" [-scriptlog <path to script log file>]");
|
||||
System.out.println(" [-log <path to log file>]");
|
||||
System.out.println(" [-overwrite]");
|
||||
System.out.println(" [-recursive]");
|
||||
System.out.println(" [-readOnly]");
|
||||
System.out.println(" [-deleteProject]");
|
||||
System.out.println(" [-noanalysis]");
|
||||
System.out.println(" [-processor <languageID>]");
|
||||
System.out.println(" [-cspec <compilerSpecID>]");
|
||||
System.out.println(" [-analysisTimeoutPerFile <timeout in seconds>]");
|
||||
System.out.println(" [-keystore <KeystorePath>]");
|
||||
System.out.println(" [-connect <userID>]");
|
||||
System.out.println(" [-p]");
|
||||
System.out.println(" [-commit [\"<comment>\"]]");
|
||||
System.out.println(" [-okToDelete]");
|
||||
System.out.println(" [-max-cpu <max cpu cores to use>]");
|
||||
System.out.println(" [-loader <desired loader name>]");
|
||||
// ** NOTE: please update 'analyzeHeadlessREADME.html' if changing command line parameters **
|
||||
StringBuilder sb = new StringBuilder();
|
||||
final String INDENT = " ";
|
||||
|
||||
sb.append("Headless Analyzer Usage: %s\n".formatted(execCmd));
|
||||
sb.append(INDENT + "<project_location> <project_name>[/<folder_path>]\n");
|
||||
sb.append(INDENT + " | ghidra://<server>[:<port>]/<repository_name>[/<folder_path>]\n");
|
||||
for (Arg arg : Arg.values()) {
|
||||
switch (arg) {
|
||||
case IMPORT -> {
|
||||
// Can't use both IMPORT and PROCESS, so must handle the usage a little
|
||||
// differently
|
||||
sb.append(
|
||||
INDENT + "[[%s] | [%s]]\n".formatted(arg.usage(), Arg.PROCESS.usage()));
|
||||
}
|
||||
case PROCESS -> {
|
||||
// Handled above by IMPORT
|
||||
}
|
||||
case LOADER_ARGS -> {
|
||||
// Loader args are a little different because we don't know the full
|
||||
// argument name ahead of time...just what it starts with
|
||||
sb.append(INDENT + "[%s<loader argument name> %s]\n"
|
||||
.formatted(Arg.LOADER_ARGS.name, Arg.LOADER_ARGS.subArgFormat));
|
||||
}
|
||||
default -> {
|
||||
sb.append(INDENT + "[%s]\n".formatted(arg.usage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform.CURRENT_PLATFORM.getOperatingSystem() != OperatingSystem.WINDOWS) {
|
||||
System.out.println();
|
||||
System.out.println(
|
||||
sb.append("\n");
|
||||
sb.append(
|
||||
" - All uses of $GHIDRA_HOME or $USER_HOME in script path must be" +
|
||||
" preceded by '\\'");
|
||||
" preceded by '\\'\n");
|
||||
}
|
||||
System.out.println();
|
||||
System.out.println(
|
||||
sb.append("\n");
|
||||
sb.append(
|
||||
"Please refer to 'analyzeHeadlessREADME.html' for detailed usage examples " +
|
||||
"and notes.");
|
||||
"and notes.\n");
|
||||
|
||||
System.out.println();
|
||||
sb.append("\n");
|
||||
System.out.println(sb);
|
||||
System.exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
|
||||
|
@ -486,23 +559,22 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
|||
usage("analyzeHeadless");
|
||||
}
|
||||
|
||||
private String[] getSubArguments(String[] args, int argi) {
|
||||
List<String> subArgs = new LinkedList<>();
|
||||
private String[] getSubArguments(String[] args, int argi, Set<String> argNames) {
|
||||
List<String> subArgs = new ArrayList<>();
|
||||
int i = argi + 1;
|
||||
while (i < args.length && !args[i].startsWith("-")) {
|
||||
while (i < args.length && !argNames.contains(args[i])) {
|
||||
subArgs.add(args[i++]);
|
||||
}
|
||||
return subArgs.toArray(new String[0]);
|
||||
return subArgs.toArray(new String[subArgs.size()]);
|
||||
}
|
||||
|
||||
private boolean checkArgument(String optionName, String[] args, int argi)
|
||||
private boolean checkArgument(Arg arg, String[] args, int argi)
|
||||
throws InvalidInputException {
|
||||
// everything after this requires an argument
|
||||
if (!optionName.equalsIgnoreCase(args[argi])) {
|
||||
if (!arg.matches(args[argi])) {
|
||||
return false;
|
||||
}
|
||||
if (argi + 1 == args.length) {
|
||||
throw new InvalidInputException(optionName + " requires an argument");
|
||||
if (arg.requiresSubArgs && argi + 1 == args.length) {
|
||||
throw new InvalidInputException(args[argi] + " requires an argument");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -879,7 +879,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
|
|||
}
|
||||
monitor.initialize(totalCount);
|
||||
|
||||
ElfRelocationContext context = ElfRelocationContext.getRelocationContext(this, symbolMap);
|
||||
ElfRelocationContext<?> context = ElfRelocationContext.getRelocationContext(this, symbolMap);
|
||||
try {
|
||||
for (ElfRelocationTable relocationTable : relocationTables) {
|
||||
monitor.checkCancelled();
|
||||
|
@ -894,7 +894,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
|
|||
}
|
||||
|
||||
private void processRelocationTable(ElfRelocationTable relocationTable,
|
||||
ElfRelocationContext context, TaskMonitor monitor) throws CancelledException {
|
||||
ElfRelocationContext<?> context, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
Address defaultBase = getDefaultAddress(elf.adjustAddressForPrelink(0));
|
||||
AddressSpace defaultSpace = defaultBase.getAddressSpace();
|
||||
|
@ -953,7 +953,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
|
|||
}
|
||||
|
||||
private void processRelocationTableEntries(ElfRelocationTable relocationTable,
|
||||
ElfRelocationContext context, AddressSpace relocationSpace, long baseWordOffset,
|
||||
ElfRelocationContext<?> context, AddressSpace relocationSpace, long baseWordOffset,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
if (context != null) {
|
||||
|
|
|
@ -244,22 +244,25 @@ public class MachoPrelinkUtils {
|
|||
* @param offset The offset within the provider to check.
|
||||
* @return True A valid {@link LoadSpec} for the Mach-O at the given provider's offset, or null
|
||||
* if it is not a Mach-O or a valid {@link LoadSpec} could not be found.
|
||||
* @throws IOException if there was an IO-related problem.
|
||||
*/
|
||||
private static LoadSpec getMachoLoadSpec(ByteProvider provider, long offset)
|
||||
throws IOException {
|
||||
Collection<LoadSpec> loadSpecs = new MachoLoader().findSupportedLoadSpecs(
|
||||
new ByteProviderWrapper(provider, offset, provider.length() - offset));
|
||||
private static LoadSpec getMachoLoadSpec(ByteProvider provider, long offset) {
|
||||
try {
|
||||
Collection<LoadSpec> loadSpecs = new MachoLoader().findSupportedLoadSpecs(
|
||||
new ByteProviderWrapper(provider, offset, provider.length() - offset));
|
||||
|
||||
// Getting a LoadSpec back means it's a Mach-O we can load. We also need to make sure
|
||||
// the LoadSpec has a language/compiler spec defined to know we support the processor the
|
||||
// loader detected.
|
||||
if (!loadSpecs.isEmpty()) {
|
||||
LoadSpec loadSpec = loadSpecs.iterator().next();
|
||||
if (loadSpec.getLanguageCompilerSpec() != null) {
|
||||
return loadSpec;
|
||||
// Getting a LoadSpec back means it's a Mach-O we can load. We also need to make sure
|
||||
// the LoadSpec has a language/compiler spec defined to know we support the processor the
|
||||
// loader detected.
|
||||
if (!loadSpecs.isEmpty()) {
|
||||
LoadSpec loadSpec = loadSpecs.iterator().next();
|
||||
if (loadSpec.getLanguageCompilerSpec() != null) {
|
||||
return loadSpec;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -412,7 +412,9 @@ public class MachoProgramBuilder {
|
|||
}
|
||||
}
|
||||
if (segmentFragment == null) {
|
||||
log.appendMsg("Could not find/fixup segment in Program Tree: " + segmentName);
|
||||
if (segment.getVMsize() != 0 || segment.getFileSize() != 0) {
|
||||
log.appendMsg("Could not find/fixup segment in Program Tree: " + segmentName);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ProgramModule segmentModule = rootModule.createModule(segmentName + suffix);
|
||||
|
@ -830,8 +832,10 @@ public class MachoProgramBuilder {
|
|||
List<DyldChainedFixupsCommand> loadCommands =
|
||||
machoHeader.getLoadCommands(DyldChainedFixupsCommand.class);
|
||||
if (!loadCommands.isEmpty()) {
|
||||
BinaryReader memReader = new BinaryReader(new MemoryByteProvider(memory, imagebase),
|
||||
!memory.isBigEndian());
|
||||
for (DyldChainedFixupsCommand loadCommand : loadCommands) {
|
||||
fixups.addAll(loadCommand.getChainedFixups(reader, imagebase.getOffset(),
|
||||
fixups.addAll(loadCommand.getChainedFixups(memReader, imagebase.getOffset(),
|
||||
symbolTable, log, monitor));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,9 @@ import ghidra.app.util.bin.format.omf.*;
|
|||
import ghidra.app.util.bin.format.omf.OmfFixupRecord.Subrecord;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.program.database.function.OverlappingFunctionException;
|
||||
import ghidra.program.database.mem.FileBytes;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.Undefined;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.*;
|
||||
|
@ -47,7 +47,7 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
|
|||
public final static long IMAGE_BASE = 0x2000; // Base offset to start loading segments
|
||||
public final static long MAX_UNINITIALIZED_FILL = 0x2000; // Maximum zero bytes added to pad initialized segments
|
||||
|
||||
private ArrayList<OmfSymbol> externsyms = new ArrayList<>();
|
||||
private List<OmfSymbol> externsyms = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* OMF usually stores a string describing the compiler that produced it in a
|
||||
|
@ -137,19 +137,42 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
|
|||
// We don't use the file bytes to create block because the bytes are manipulated before
|
||||
// forming the block. Creating the FileBytes anyway in case later we want access to all
|
||||
// the original bytes.
|
||||
MemoryBlockUtils.createFileBytes(program, provider, monitor);
|
||||
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
|
||||
|
||||
try {
|
||||
processSegmentHeaders(reader, header, program, monitor, log);
|
||||
processPublicSymbols(header, program, monitor, log);
|
||||
processExternalSymbols(header, program, monitor, log);
|
||||
processRelocations(header, program, monitor, log);
|
||||
markupRecords(program, fileBytes, header, log, monitor);
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void markupRecords(Program program, FileBytes fileBytes, OmfFileHeader fileHeader,
|
||||
MessageLog log, TaskMonitor monitor) {
|
||||
monitor.setMessage("Marking up records...");
|
||||
int size =
|
||||
fileHeader.getRecords().stream().mapToInt(r -> r.getRecordLength() + 3).sum();
|
||||
try {
|
||||
Address recordSpaceAddr = AddressSpace.OTHER_SPACE.getAddress(0);
|
||||
MemoryBlock headerBlock = MemoryBlockUtils.createInitializedBlock(program, true,
|
||||
"RECORDS", recordSpaceAddr, fileBytes, 0, size, "", "", false,
|
||||
false, false, log);
|
||||
Address start = headerBlock.getStart();
|
||||
|
||||
for (OmfRecord record : fileHeader.getRecords()) {
|
||||
DataUtilities.createData(program, start.add(record.getRecordOffset()),
|
||||
record.toDataType(), -1, DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.appendMsg("Failed to markup records");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a (hopefully) descriptive error, if we can't process a specific relocation
|
||||
* @param program is the Program
|
||||
|
@ -181,7 +204,7 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
|
|||
MessageLog log) {
|
||||
Language language = program.getLanguage();
|
||||
OmfFixupRecord.Subrecord[] targetThreads = new Subrecord[4];
|
||||
ArrayList<OmfGroupRecord> groups = header.getGroups();
|
||||
List<OmfGroupRecord> groups = header.getGroups();
|
||||
long targetAddr; // Address of item being referred to
|
||||
Address locAddress; // Location of data to be patched
|
||||
DataConverter converter = DataConverter.getInstance(!header.isLittleEndian());
|
||||
|
@ -364,7 +387,7 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
|
|||
|
||||
final Language language = program.getLanguage();
|
||||
|
||||
ArrayList<OmfSegmentHeader> segments = header.getSegments();
|
||||
List<OmfSegmentHeader> segments = header.getSegments();
|
||||
for (OmfSegmentHeader segment : segments) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
|
@ -432,9 +455,9 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
|
|||
MessageLog log) {
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
|
||||
ArrayList<OmfSymbolRecord> symbols = header.getPublicSymbols();
|
||||
ArrayList<OmfSegmentHeader> segments = header.getSegments();
|
||||
ArrayList<OmfGroupRecord> groups = header.getGroups();
|
||||
List<OmfSymbolRecord> symbols = header.getPublicSymbols();
|
||||
List<OmfSegmentHeader> segments = header.getSegments();
|
||||
List<OmfGroupRecord> groups = header.getGroups();
|
||||
Language language = program.getLanguage();
|
||||
|
||||
monitor.setMessage("Creating Public Symbols");
|
||||
|
@ -536,7 +559,7 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
|
|||
private void processExternalSymbols(OmfFileHeader header, Program program, TaskMonitor monitor,
|
||||
MessageLog log) {
|
||||
|
||||
ArrayList<OmfExternalSymbol> symbolrecs = header.getExternalSymbols();
|
||||
List<OmfExternalSymbol> symbolrecs = header.getExternalSymbols();
|
||||
if (symbolrecs.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -93,34 +93,28 @@ public class ProgramOpener {
|
|||
*/
|
||||
public Program openProgram(ProgramLocator locator, TaskMonitor monitor) {
|
||||
if (locator.isURL()) {
|
||||
try {
|
||||
return openURL(locator, monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "Program Open Failed",
|
||||
"Failed to open Ghidra URL: " + locator.getURL());
|
||||
}
|
||||
return null;
|
||||
return openURL(locator, monitor);
|
||||
}
|
||||
return openProgram(locator, locator.getDomainFile(), monitor);
|
||||
}
|
||||
|
||||
private Program openURL(ProgramLocator locator, TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
private Program openURL(ProgramLocator locator, TaskMonitor monitor) {
|
||||
URL ghidraUrl = locator.getURL();
|
||||
|
||||
AtomicReference<Program> openedProgram = new AtomicReference<>();
|
||||
GhidraURLQuery.queryUrl(ghidraUrl, new GhidraURLResultHandlerAdapter() {
|
||||
@Override
|
||||
public void processResult(DomainFile domainFile, URL url, TaskMonitor m) {
|
||||
Program p = openProgram(locator, domainFile, m); // may return null
|
||||
openedProgram.set(p);
|
||||
}
|
||||
}, monitor);
|
||||
|
||||
try {
|
||||
GhidraURLQuery.queryUrl(ghidraUrl, new GhidraURLResultHandlerAdapter() {
|
||||
@Override
|
||||
public void processResult(DomainFile domainFile, URL url, TaskMonitor m) {
|
||||
Program p = openProgram(locator, domainFile, m); // may return null
|
||||
openedProgram.set(p);
|
||||
}
|
||||
}, monitor);
|
||||
}
|
||||
catch (IOException | CancelledException e) {
|
||||
// IOException reported to user by GhidraURLResultHandlerAdapter
|
||||
return null;
|
||||
}
|
||||
return openedProgram.get();
|
||||
}
|
||||
|
||||
|
|
|
@ -53,8 +53,10 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
|
|||
ProgramContext pc = program.getProgramContext();
|
||||
Register regDR0 = pc.getRegister(regNameDR0);
|
||||
|
||||
// Initially Direction was 0x1e240
|
||||
setRegValue(pc, addr("1002085"), addr("1002100"), regDR0, 0x5L);
|
||||
|
||||
setRegValue(pc, addr("TextOverlay:1001700"), addr("TextOverlay:1001780"), regDR0,
|
||||
0x5L);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,6 +65,9 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
|
|||
Register regDR0 = pc.getRegister(regNameDR0);
|
||||
|
||||
setRegValue(pc, addr("1002000"), addr("1002074"), regDR0, 0x22L);
|
||||
|
||||
setRegValue(pc, addr("TextOverlay:1001630"), addr("TextOverlay:1001680"), regDR0,
|
||||
0x22L);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -89,6 +94,34 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
|
|||
for (Address a = addr("1002101"); a.compareTo(addr("1002150")) <= 0; a = a.add(0x1L)) {
|
||||
assertUndefinedRegValue("DR0", a);
|
||||
}
|
||||
|
||||
// Check Overlay
|
||||
|
||||
// Neither set it
|
||||
for (Address a = addr("TextOverlay:1001629"); a
|
||||
.compareTo(addr("TextOverlay:1001629")) <= 0; a = a.add(0x1L)) {
|
||||
assertUndefinedRegValue("DR0", a);
|
||||
}
|
||||
// From MY
|
||||
for (Address a = addr("TextOverlay:1001630"); a
|
||||
.compareTo(addr("TextOverlay:1001680")) <= 0; a = a.add(0x1L)) {
|
||||
assertRegValue("DR0", a, 0x22L);
|
||||
}
|
||||
// Neither set it
|
||||
for (Address a = addr("TextOverlay:1001681"); a
|
||||
.compareTo(addr("TextOverlay:10016ff")) <= 0; a = a.add(0x1L)) {
|
||||
assertUndefinedRegValue("DR0", a);
|
||||
}
|
||||
// From LATEST
|
||||
for (Address a = addr("TextOverlay:1001700"); a
|
||||
.compareTo(addr("TextOverlay:1001780")) <= 0; a = a.add(0x1L)) {
|
||||
assertRegValue("DR0", a, 0x5L);
|
||||
}
|
||||
// Neither set it
|
||||
for (Address a = addr("TextOverlay:1001781"); a
|
||||
.compareTo(addr("TextOverlay:100182f")) <= 0; a = a.add(0x1L)) {
|
||||
assertUndefinedRegValue("DR0", a);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -100,7 +133,6 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
|
|||
ProgramContext pc = program.getProgramContext();
|
||||
Register regDR0 = pc.getRegister(regNameDR0);
|
||||
|
||||
// Initially Direction was 0x1e240
|
||||
setRegValue(pc, addr("10022d4"), addr("10022d9"), regDR0, 0x66L);
|
||||
}
|
||||
|
||||
|
@ -807,6 +839,9 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
|
|||
|
||||
setRegValue(pc, addr("10022d4"), addr("10022e5"), reg1, 0x66L);
|
||||
setRegValue(pc, addr("10022ee"), addr("10022fc"), reg1, 0x44L);
|
||||
|
||||
setRegValue(pc, addr("TextOverlay:1001700"), addr("TextOverlay:1001700"), reg1,
|
||||
0x44L);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -816,6 +851,9 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
|
|||
|
||||
setRegValue(pc, addr("10022d4"), addr("10022e5"), reg1, 0x7L);
|
||||
setRegValue(pc, addr("10022ee"), addr("10022fc"), reg1, 0x5L);
|
||||
|
||||
setRegValue(pc, addr("TextOverlay:1001700"), addr("TextOverlay:1001700"), reg1,
|
||||
0x5L);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -824,6 +862,8 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
|
|||
chooseRadioButton(CHECKED_OUT_BUTTON_NAME);
|
||||
checkDisplayValues(Long.valueOf(0x44L), Long.valueOf(0x5L), (Long) null);
|
||||
chooseRadioButton(CHECKED_OUT_BUTTON_NAME);
|
||||
checkDisplayValues(Long.valueOf(0x44L), Long.valueOf(0x5L), (Long) null);
|
||||
chooseRadioButton(CHECKED_OUT_BUTTON_NAME);
|
||||
waitForMergeCompletion();
|
||||
|
||||
for (Address a = addr("10022d4"); a.compareTo(addr("10022e5")) <= 0; a = a.add(0x1L)) {
|
||||
|
@ -832,6 +872,7 @@ public class ProgramContextMergeManagerTest extends AbstractListingMergeManagerT
|
|||
for (Address a = addr("10022ee"); a.compareTo(addr("10022fc")) <= 0; a = a.add(0x1L)) {
|
||||
assertRegValue("DR0", a, 0x5L);
|
||||
}
|
||||
assertRegValue("DR0", addr("TextOverlay:1001700"), 0x5L);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -372,6 +372,11 @@ public abstract class AbstractEditorTest extends AbstractGhidraHeadedIntegration
|
|||
return (dtc != null) ? dtc.getLength() : -1;
|
||||
}
|
||||
|
||||
protected DataType getDataType(Composite c, int index) {
|
||||
DataTypeComponent dtc = c.getComponent(index);
|
||||
return (dtc != null) ? dtc.getDataType() : null;
|
||||
}
|
||||
|
||||
protected DataType getDataType(int index) {
|
||||
DataTypeComponent dtc = getComponent(index);
|
||||
return (dtc != null) ? dtc.getDataType() : null;
|
||||
|
|
|
@ -89,7 +89,7 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest {
|
|||
|
||||
programDTM.remove(complexStructure, TaskMonitor.DUMMY);
|
||||
programDTM.getCategory(pgmRootCat.getCategoryPath())
|
||||
.removeCategory("Temp", TaskMonitor.DUMMY);
|
||||
.removeCategory("Temp", TaskMonitor.DUMMY);
|
||||
|
||||
waitForSwing();
|
||||
|
||||
|
@ -486,15 +486,17 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest {
|
|||
|
||||
@Test
|
||||
public void testComponentDataTypeRemoved() {
|
||||
|
||||
// Get the data types we want to hold onto for comparison later
|
||||
DataType dt3 = getDataType(complexStructure, 3);
|
||||
DataType dt5 = getDataType(complexStructure, 5);
|
||||
DataType dt8 = getDataType(complexStructure, 8);
|
||||
DataType dt10 = getDataType(complexStructure, 10);
|
||||
|
||||
init(complexStructure, pgmTestCat);
|
||||
DataType undef = DataType.DEFAULT;
|
||||
|
||||
assertEquals(23, model.getNumComponents());
|
||||
// Clone the data types we want to hold onto for comparison later, since reload can close the viewDTM.
|
||||
DataType dt3 = getDataType(3).clone(programDTM);
|
||||
DataType dt5 = getDataType(5).clone(programDTM);
|
||||
DataType dt8 = getDataType(8).clone(programDTM);
|
||||
DataType dt10 = getDataType(10).clone(programDTM);
|
||||
|
||||
runSwing(
|
||||
() -> complexStructure.getDataTypeManager().remove(simpleUnion, TaskMonitor.DUMMY));
|
||||
|
@ -521,9 +523,8 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest {
|
|||
waitForSwing();
|
||||
assertTrue(simpleStructure.isEquivalent(getDataType(0)));
|
||||
|
||||
runSwing(() -> simpleStructure.getDataTypeManager()
|
||||
.remove(
|
||||
simpleStructure, TaskMonitor.DUMMY));
|
||||
runSwing(
|
||||
() -> simpleStructure.getDataTypeManager().remove(simpleStructure, TaskMonitor.DUMMY));
|
||||
waitForSwing();
|
||||
assertEquals(29, model.getNumComponents());// becomes undefined bytes
|
||||
}
|
||||
|
@ -581,18 +582,21 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest {
|
|||
|
||||
@Test
|
||||
public void testComponentDataTypeReplaced() throws Exception {
|
||||
|
||||
// Get the data types we want to hold onto for comparison later
|
||||
DataType dt15 = getDataType(complexStructure, 15);
|
||||
DataType dt16 = getDataType(complexStructure, 16);
|
||||
DataType dt18 = getDataType(complexStructure, 18);
|
||||
DataType dt19 = getDataType(complexStructure, 19);
|
||||
DataType dt20 = getDataType(complexStructure, 20);
|
||||
String dt21Name = getDataType(complexStructure, 21).getName();
|
||||
DataType dt22 = getDataType(complexStructure, 22);
|
||||
|
||||
init(complexStructure, pgmTestCat);
|
||||
|
||||
int numComps = model.getNumComponents();
|
||||
int len = model.getLength();
|
||||
// Clone the data types we want to hold onto for comparison later, since reload can close the viewDTM.
|
||||
DataType dt15 = getDataType(15).clone(programDTM);
|
||||
DataType dt16 = getDataType(16).clone(programDTM);
|
||||
DataType dt18 = getDataType(18).clone(programDTM);
|
||||
DataType dt19 = getDataType(19).clone(programDTM);
|
||||
DataType dt20 = getDataType(20).clone(programDTM);
|
||||
String dt21Name = getDataType(21).getName();
|
||||
DataType dt22 = getDataType(22).clone(programDTM);
|
||||
|
||||
assertEquals(87, complexStructure.getComponent(16).getDataType().getLength());
|
||||
assertEquals(29, complexStructure.getComponent(19).getDataType().getLength());
|
||||
assertEquals(24, complexStructure.getComponent(20).getDataType().getLength());
|
||||
|
|
|
@ -502,8 +502,9 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest {
|
|||
@Test
|
||||
public void testCloseEditorProviderAndSave() throws Exception {
|
||||
Window dialog;
|
||||
DataType oldDt = complexStructure.clone(null);
|
||||
|
||||
init(complexStructure, pgmTestCat, false);
|
||||
DataType oldDt = model.viewComposite.clone(null);
|
||||
|
||||
// Change the structure
|
||||
runSwingLater(() -> {
|
||||
|
@ -538,8 +539,9 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest {
|
|||
@Test
|
||||
public void testCloseEditorAndNoSave() throws Exception {
|
||||
|
||||
DataType oldDt = complexStructure.clone(null);
|
||||
|
||||
init(complexStructure, pgmTestCat, false);
|
||||
DataType oldDt = model.viewComposite.clone(null);
|
||||
|
||||
// Change the structure
|
||||
runSwing(() -> {
|
||||
|
|
|
@ -779,15 +779,18 @@ public class UnionEditorActions1Test extends AbstractUnionEditorTest {
|
|||
|
||||
@Test
|
||||
public void testApplyNameChange() throws Exception {
|
||||
|
||||
DataType viewCopy = complexUnion.clone(null);
|
||||
|
||||
init(complexUnion, pgmTestCat, false);
|
||||
|
||||
model.setName("FooBarUnion");
|
||||
DataType viewCopy = model.viewComposite.clone(null);
|
||||
|
||||
assertTrue(complexUnion.isEquivalent(model.viewComposite));
|
||||
assertTrue(viewCopy.isEquivalent(complexUnion));
|
||||
|
||||
assertEquals("FooBarUnion", model.getCompositeName());
|
||||
assertEquals("complexUnion", complexUnion.getName());
|
||||
assertTrue(viewCopy.isEquivalent(model.viewComposite));
|
||||
|
||||
invoke(applyAction);
|
||||
assertTrue(viewCopy.isEquivalent(complexUnion));
|
||||
assertTrue(viewCopy.isEquivalent(model.viewComposite));
|
||||
|
|
|
@ -200,8 +200,9 @@ public class UnionEditorProviderTest extends AbstractUnionEditorTest {
|
|||
@Test
|
||||
public void testCloseEditorProviderAndSave() throws Exception {
|
||||
Window dialog;
|
||||
DataType oldDt = complexUnion.clone(null);
|
||||
|
||||
init(complexUnion, pgmTestCat, false);
|
||||
DataType oldDt = model.viewComposite.clone(null);
|
||||
|
||||
// Change the union.
|
||||
Swing.runLater(() -> {
|
||||
|
@ -236,8 +237,9 @@ public class UnionEditorProviderTest extends AbstractUnionEditorTest {
|
|||
@Test
|
||||
public void testCloseEditorAndNoSave() throws Exception {
|
||||
Window dialog;
|
||||
DataType oldDt = complexUnion.clone(null);
|
||||
|
||||
init(complexUnion, pgmTestCat, false);
|
||||
DataType oldDt = model.viewComposite.clone(null);
|
||||
|
||||
// Change the union.
|
||||
Swing.runLater(() -> {
|
||||
|
|
|
@ -465,12 +465,6 @@ public class FakeSharedProject {
|
|||
|
||||
void refresh() {
|
||||
DefaultProjectData projectData = getProjectData();
|
||||
try {
|
||||
projectData.refresh(true);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// shouldn't happen
|
||||
throw new AssertionFailedError("Unable to refresh project " + this);
|
||||
}
|
||||
projectData.refresh(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -202,6 +202,7 @@ class MergeProgramGenerator_DiffTestPrograms implements MergeProgramGenerator {
|
|||
builder.createMemory(".data", "0x1008000", 0x600);
|
||||
builder.createMemory(".datau", "0x1008600", 0x1344);
|
||||
builder.createMemory(".rsrc", "0x100a000", 0x5400);
|
||||
builder.createOverlayMemory("TextOverlay", "0x01001630", 0x200);
|
||||
|
||||
// for FunctionMergeManager2Test
|
||||
//
|
||||
|
|
|
@ -87,17 +87,41 @@
|
|||
<p>This view does not support <a href="#EditBytes">editing</a>.</p>
|
||||
</blockquote>
|
||||
|
||||
<h3><a name="Add_Byteviewer_HexInteger_Panel"></a><a name="HexInteger"></a>HexInteger </h3>
|
||||
<h3><a name="Add_Byteviewer_HexShortPanel"></a><a name="HexShort"></a>Hex Short</h3>
|
||||
<blockquote>
|
||||
<p>This format shows four byte numbers represented as an eight digit hex number. </p>
|
||||
<p>This format shows two-byte numbers represented as an four-digit hex number. </p>
|
||||
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
|
||||
is changed, both bytes associated with this address are rendered in
|
||||
<font color="#ff0000"> red</font> to denote the change.</p>
|
||||
</blockquote>
|
||||
|
||||
<h3><a name="Add_Byteviewer_HexInteger_Panel"></a><a name="HexInteger"></a>Hex Integer</h3>
|
||||
<blockquote>
|
||||
<p>This format shows four-byte numbers represented as an eight-digit hex number. </p>
|
||||
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
|
||||
is changed, all four bytes associated with this address are rendered in
|
||||
<font color="#ff0000"> red</font> to denote the change.</p>
|
||||
</blockquote>
|
||||
|
||||
<h3><a name="Add_Byteviewer_HexLong_Panel"></a><a name="HexLongr"></a>Hex Long</h3>
|
||||
<blockquote>
|
||||
<p>This format shows eight-byte numbers represented as an 16-digit hex number. </p>
|
||||
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
|
||||
is changed, all eight bytes associated with this address are rendered in
|
||||
<font color="#ff0000"> red</font> to denote the change.</p>
|
||||
</blockquote>
|
||||
|
||||
<h3><a name="Add_Byteviewer_HexLongLong_Panel"></a><a name="HexLongLong"></a>Hex Long Long</h3>
|
||||
<blockquote>
|
||||
<p>This format shows 16-byte numbers represented as an 32-digit hex number. </p>
|
||||
<p> This view supports <a href="#EditBytes">editing</a>. When a byte
|
||||
is changed, all 16 bytes associated with this address are rendered in
|
||||
<font color="#ff0000"> red</font> to denote the change.</p>
|
||||
</blockquote>
|
||||
|
||||
<h3><a name="Add_Byteviewer_Integer_Panel"></a><a name="Integer"></a>Integer </h3>
|
||||
<blockquote>
|
||||
<p>This view shows four byte numbers represented in decimal format. </p>
|
||||
<p>This view shows four-byte numbers represented in decimal format. </p>
|
||||
<p> This view does not support <a href="#EditBytes">editing</a>.</p>
|
||||
</blockquote>
|
||||
|
||||
|
|
|
@ -912,9 +912,8 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
|||
|
||||
ByteField getField(BigInteger index, int fieldNum) {
|
||||
if (indexMap != null) {
|
||||
int fieldOffset = indexMap.getFieldOffset(index, fieldNum, fieldFactories);
|
||||
if (fieldNum < fieldFactories.length) {
|
||||
return (ByteField) fieldFactories[fieldOffset].getField(index);
|
||||
return (ByteField) fieldFactories[fieldNum].getField(index);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -78,6 +78,19 @@ class FileByteBlock implements ByteBlock {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.app.plugin.core.format.ByteBlock#getShort(int)
|
||||
*/
|
||||
public short getShort(BigInteger bigIndex) throws ByteBlockAccessException {
|
||||
int index = bigIndex.intValue();
|
||||
if (index < buf.length) {
|
||||
byte[] b = new byte[2];
|
||||
System.arraycopy(buf, index, b, 0, b.length);
|
||||
return converter.getShort(b);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.app.plugin.core.format.ByteBlock#getInt(int)
|
||||
*/
|
||||
|
@ -114,6 +127,18 @@ class FileByteBlock implements ByteBlock {
|
|||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.app.plugin.core.format.ByteBlock#setShort(int, short)
|
||||
*/
|
||||
public void setShort(BigInteger bigIndex, short value) throws ByteBlockAccessException {
|
||||
int index = bigIndex.intValue();
|
||||
if (index < buf.length) {
|
||||
byte[] b = new byte[2];
|
||||
converter.putShort(b, 0, value);
|
||||
System.arraycopy(b, 0, buf, index, b.length);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.app.plugin.core.format.ByteBlock#setInt(int, int)
|
||||
*/
|
||||
|
|
|
@ -225,6 +225,44 @@ public class MemoryByteBlock implements ByteBlock {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the short value at the given index.
|
||||
*
|
||||
* @param index byte index
|
||||
* @throws ByteBlockAccessException if the block cannot be read
|
||||
* @throws IndexOutOfBoundsException if the given index is not in this block.
|
||||
*/
|
||||
@Override
|
||||
public short getShort(BigInteger index) throws ByteBlockAccessException {
|
||||
Address addr = getAddress(index);
|
||||
try {
|
||||
return memory.getShort(addr, bigEndian);
|
||||
}
|
||||
catch (MemoryAccessException e) {
|
||||
throw new ByteBlockAccessException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the short at the given index.
|
||||
*
|
||||
* @param index byte index
|
||||
* @param value value to set
|
||||
* @throws ByteBlockAccessException if the block cannot be updated
|
||||
* @throws IndexOutOfBoundsException if the given index is not in this block.
|
||||
*/
|
||||
@Override
|
||||
public void setShort(BigInteger index, short value) throws ByteBlockAccessException {
|
||||
Address addr = getAddress(index);
|
||||
checkEditsAllowed(addr, 2);
|
||||
try {
|
||||
memory.setShort(addr, value, bigEndian);
|
||||
}
|
||||
catch (MemoryAccessException e) {
|
||||
throw new ByteBlockAccessException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this block can be modified.
|
||||
*/
|
||||
|
|
|
@ -61,6 +61,15 @@ public interface ByteBlock {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the short value at the given index.
|
||||
* @param index byte index
|
||||
* @throws ByteBlockAccessException if the block cannot be read
|
||||
* @throws IndexOutOfBoundsException if the given index is not in this
|
||||
* block.
|
||||
*/
|
||||
public short getShort(BigInteger index) throws ByteBlockAccessException;
|
||||
|
||||
/**
|
||||
* Get the int value at the given index.
|
||||
* @param index byte index
|
||||
|
@ -89,6 +98,16 @@ public interface ByteBlock {
|
|||
*/
|
||||
public void setByte(BigInteger index, byte value) throws ByteBlockAccessException;
|
||||
|
||||
/**
|
||||
* Set the short at the given index.
|
||||
* @param index byte index
|
||||
* @param value value to set
|
||||
* @throws ByteBlockAccessException if the block cannot be updated
|
||||
* @throws IndexOutOfBoundsException if the given index is not in this
|
||||
* block.
|
||||
*/
|
||||
public void setShort(BigInteger index, short value) throws ByteBlockAccessException;
|
||||
|
||||
/**
|
||||
* Set the int at the given index.
|
||||
* @param index byte index
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,236 +15,22 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.format;
|
||||
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Converts byte values to Integer represented as an 8 digit hex number.
|
||||
* Converts byte values to Integer represented as an 4-byte/8-digit hex number.
|
||||
*/
|
||||
public class HexIntegerFormatModel implements UniversalDataFormatModel {
|
||||
|
||||
private int symbolSize;
|
||||
public class HexIntegerFormatModel extends HexValueFormatModel {
|
||||
|
||||
public HexIntegerFormatModel() {
|
||||
|
||||
symbolSize = 8;
|
||||
super("Hex Integer", 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this formatter.
|
||||
*/
|
||||
public String getName() {
|
||||
return "HexInteger";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes to make a unit; in this case,
|
||||
* returns 4.
|
||||
*/
|
||||
public int getUnitByteSize() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte used to generate the character at a given
|
||||
* position.
|
||||
* @param position number in the range 0 to 7
|
||||
*/
|
||||
public int getByteOffset(ByteBlock block, int position) {
|
||||
|
||||
int o = position / 2;
|
||||
|
||||
if (block.isBigEndian()) {
|
||||
return o;
|
||||
}
|
||||
return 3 - o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the byte offset into a unit, get the column position.
|
||||
*/
|
||||
public int getColumnPosition(ByteBlock block, int byteOffset) {
|
||||
if (byteOffset > 3) {
|
||||
throw new IllegalArgumentException("invalid byteOffset: " + byteOffset);
|
||||
}
|
||||
if (block.isBigEndian()) {
|
||||
return byteOffset * 2;
|
||||
}
|
||||
return (3 - byteOffset) * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of characters required to display a
|
||||
* unit.
|
||||
* @return 4 for number of characters in the integer representation.
|
||||
*/
|
||||
public int getDataUnitSymbolSize() {
|
||||
return symbolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string representation at the given index in the block.
|
||||
* @param block block to change
|
||||
* @param index byte index into the block
|
||||
* @throws ByteBlockAccessException if the block cannot be read
|
||||
* @throws IndexOutOfBoundsException if index is not valid for the
|
||||
* block
|
||||
*/
|
||||
@Override
|
||||
public String getDataRepresentation(ByteBlock block, BigInteger index)
|
||||
throws ByteBlockAccessException {
|
||||
|
||||
int i = block.getInt(index);
|
||||
|
||||
String str = Integer.toHexString(i);
|
||||
|
||||
return pad(str);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true to allow values to be changed.
|
||||
*/
|
||||
public boolean isEditable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite a value in a ByteBlock.
|
||||
* @param block block to change
|
||||
* @param index byte index into the block
|
||||
* @param pos The position within the unit where c will be the
|
||||
* new character.
|
||||
* @param c new character to put at pos param
|
||||
* @return true if the replacement is legal, false if the
|
||||
* replacement value would not make sense for this format, e.g.
|
||||
* attempt to put a 'z' in a hex unit.
|
||||
* @throws ByteBlockAccessException if the block cannot be updated
|
||||
* @throws IndexOutOfBoundsException if index is not valid for the
|
||||
* block
|
||||
*/
|
||||
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c)
|
||||
throws ByteBlockAccessException {
|
||||
|
||||
if (charPosition < 0 || charPosition > symbolSize - 1) {
|
||||
return false;
|
||||
}
|
||||
char[] charArray = { c };
|
||||
String s = new String(charArray);
|
||||
try {
|
||||
Integer.parseInt(s, 16);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte cb = Byte.parseByte(new String(charArray), 16);
|
||||
// get the correct byte offset based on position
|
||||
int byteOffset = getByteOffset(block, charPosition);
|
||||
BigInteger saveIndex = index;
|
||||
index = index.add(BigInteger.valueOf(byteOffset));
|
||||
byte b = block.getByte(index);
|
||||
b = adjustByte(b, cb, charPosition);
|
||||
int intValue = getInt(block, saveIndex, b, byteOffset);
|
||||
block.setInt(saveIndex, intValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of units in a group. A group may represent
|
||||
* multiple units shown as one entity. This format does not
|
||||
* support groups.
|
||||
*/
|
||||
public int getGroupSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of units in a group. This format does not
|
||||
* support groups.
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
public void setGroupSize(int groupSize) {
|
||||
throw new UnsupportedOperationException("groups are not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of characters separating units.
|
||||
*/
|
||||
public int getUnitDelimiterSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.app.plugin.core.format.DataFormatModel#validateBytesPerLine(int)
|
||||
*/
|
||||
public boolean validateBytesPerLine(int bytesPerLine) {
|
||||
return bytesPerLine % 4 == 0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// *** private methods ***
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Returns value with leading zeros.
|
||||
*/
|
||||
private String pad(String value) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
int len = symbolSize - value.length();
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
sb.append("0");
|
||||
}
|
||||
sb.append(value);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* adjust byte b to use either the upper 4 bits or
|
||||
* the lower 4 bits of newb according to charPosition.
|
||||
*/
|
||||
private byte adjustByte(byte b, byte newb, int charPosition) {
|
||||
if (charPosition % 2 == 0) {
|
||||
// its the high order byte
|
||||
b &= 0x0f;
|
||||
newb <<= 4;
|
||||
}
|
||||
else {
|
||||
b &= 0xf0;
|
||||
}
|
||||
b += newb;
|
||||
return b;
|
||||
}
|
||||
|
||||
private int getInt(ByteBlock block, BigInteger offset, byte newb, int byteOffset) {
|
||||
byte[] b = new byte[4];
|
||||
try {
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
b[i] = block.getByte(offset.add(BigInteger.valueOf(i)));
|
||||
}
|
||||
b[byteOffset] = newb;
|
||||
|
||||
if (block.isBigEndian()) {
|
||||
return (b[0] << 24) | ((b[1] << 16) & 0x00FF0000) | ((b[2] << 8) & 0x0000FF00) |
|
||||
(b[3] & 0x000000FF);
|
||||
}
|
||||
return (b[3] << 24) | ((b[2] << 16) & 0x00FF0000) | ((b[1] << 8) & 0x0000FF00) |
|
||||
(b[0] & 0x000000FF);
|
||||
|
||||
}
|
||||
catch (ByteBlockAccessException e) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.app.plugin.format.DataFormatModel#getHelpLocation()
|
||||
*/
|
||||
public HelpLocation getHelpLocation() {
|
||||
return new HelpLocation("ByteViewerPlugin", "HexInteger");
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/* ###
|
||||
* 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.format;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Converts byte values to Long represented as an 8-byte/16--digit hex number.
|
||||
*/
|
||||
public class HexLongFormatModel extends HexValueFormatModel {
|
||||
|
||||
public HexLongFormatModel() {
|
||||
super("Hex Long", 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDataRepresentation(ByteBlock block, BigInteger index)
|
||||
throws ByteBlockAccessException {
|
||||
long l = block.getLong(index);
|
||||
String str = Long.toHexString(l);
|
||||
return pad(str);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* ###
|
||||
* 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.format;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Converts byte values to LongLong represented as an 16-byte/32-digit hex number.
|
||||
*/
|
||||
public class HexLongLongFormatModel extends HexValueFormatModel {
|
||||
|
||||
public HexLongLongFormatModel() {
|
||||
super("Hex Long Long", 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDataRepresentation(ByteBlock block, BigInteger index)
|
||||
throws ByteBlockAccessException {
|
||||
long l0 = block.getLong(index);
|
||||
String str0 = pad(Long.toHexString(l0));
|
||||
long l1 = block.getLong(index.add(BigInteger.valueOf(8)));
|
||||
String str1 = pad(Long.toHexString(l1));
|
||||
String str = str1.substring(16) + str0.substring(16);
|
||||
return pad(str);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* ###
|
||||
* 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.format;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Converts byte values to Short represented as an 2-byte/4-digit hex number.
|
||||
*/
|
||||
public class HexShortFormatModel extends HexValueFormatModel {
|
||||
|
||||
public HexShortFormatModel() {
|
||||
super("Hex Short", 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDataRepresentation(ByteBlock block, BigInteger index)
|
||||
throws ByteBlockAccessException {
|
||||
short s = block.getShort(index);
|
||||
String str = Integer.toHexString(s & 0xFFFF);
|
||||
return pad(str);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/* ###
|
||||
* 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.format;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
/**
|
||||
* Converts byte values to value represented as a 2, 4, 8, or 16-byte hex number.
|
||||
*/
|
||||
public abstract class HexValueFormatModel implements UniversalDataFormatModel {
|
||||
|
||||
protected String name;
|
||||
|
||||
private int symbolSize;
|
||||
protected int nbytes;
|
||||
|
||||
public HexValueFormatModel(String name, int nbytes) {
|
||||
this.name = name;
|
||||
this.nbytes = nbytes;
|
||||
symbolSize = nbytes * 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnitByteSize() {
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getByteOffset(ByteBlock block, int position) {
|
||||
|
||||
int o = position / 2;
|
||||
|
||||
if (block.isBigEndian()) {
|
||||
return o;
|
||||
}
|
||||
return nbytes - 1 - o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPosition(ByteBlock block, int byteOffset) {
|
||||
if (byteOffset > nbytes - 1) {
|
||||
throw new IllegalArgumentException("invalid byteOffset: " + byteOffset);
|
||||
}
|
||||
if (block.isBigEndian()) {
|
||||
return byteOffset * 2;
|
||||
}
|
||||
return (nbytes - 1 - byteOffset) * 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataUnitSymbolSize() {
|
||||
return symbolSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String getDataRepresentation(ByteBlock block, BigInteger index)
|
||||
throws ByteBlockAccessException;
|
||||
|
||||
@Override
|
||||
public boolean isEditable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceValue(ByteBlock block, BigInteger index, int charPosition, char c)
|
||||
throws ByteBlockAccessException {
|
||||
|
||||
if (charPosition < 0 || charPosition > symbolSize - 1) {
|
||||
// Not sure how this is possible, but...
|
||||
return false;
|
||||
}
|
||||
char[] charArray = { c };
|
||||
byte cb = Byte.parseByte(new String(charArray), 16);
|
||||
// get the correct byte offset based on position
|
||||
int byteOffset = getByteOffset(block, charPosition);
|
||||
index = index.add(BigInteger.valueOf(byteOffset));
|
||||
byte b = block.getByte(index);
|
||||
b = adjustByte(b, cb, charPosition);
|
||||
block.setByte(index, b);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGroupSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of units in a group. This format does not
|
||||
* support groups.
|
||||
*/
|
||||
@Override
|
||||
public void setGroupSize(int groupSize) {
|
||||
throw new UnsupportedOperationException("groups are not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnitDelimiterSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateBytesPerLine(int bytesPerLine) {
|
||||
return bytesPerLine % nbytes == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value with leading zeros.
|
||||
*/
|
||||
protected String pad(String value) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
int len = symbolSize - value.length();
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
sb.append("0");
|
||||
}
|
||||
sb.append(value);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* adjust byte b to use either the upper 4 bits or
|
||||
* the lower 4 bits of newb according to charPosition.
|
||||
*/
|
||||
private byte adjustByte(byte b, byte newb, int charPosition) {
|
||||
if (charPosition % 2 == 0) {
|
||||
// its the high order byte
|
||||
b &= 0x0f;
|
||||
newb <<= 4;
|
||||
}
|
||||
else {
|
||||
b &= 0xf0;
|
||||
}
|
||||
b += newb;
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HelpLocation getHelpLocation() {
|
||||
return new HelpLocation("ByteViewerPlugin", "HexValue");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
}
|
|
@ -305,7 +305,7 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
field = asciiC.getField(loc.getIndex(), loc.getFieldNum());
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, field.getForeground());
|
||||
|
||||
final ByteViewerComponent hexIntC = findComponent(panel, "HexInteger");
|
||||
final ByteViewerComponent hexIntC = findComponent(panel, "Hex Integer");
|
||||
SwingUtilities.invokeAndWait(() -> panel.setCurrentView(hexIntC));
|
||||
|
||||
loc = getFieldLocation(addr);
|
||||
|
@ -456,14 +456,14 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testUndoRedoHexInteger() throws Exception {
|
||||
public void testUndoRedoHexShort() throws Exception {
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final Address addr = getAddr(0x01001003);
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
ByteViewerComponent c = findComponent(panel, "HexInteger");
|
||||
ByteViewerComponent c = findComponent(panel, "Hex Short");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
|
@ -504,6 +504,228 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndoRedoHexInteger() throws Exception {
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final Address addr = getAddr(0x01001003);
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
ByteViewerComponent c = findComponent(panel, "Hex Integer");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol());
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
byte value = program.getMemory().getByte(addr);
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
KeyEvent ev =
|
||||
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_A, 'a');
|
||||
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol(), currentComponent.getCurrentField());
|
||||
});
|
||||
program.flushEvents();
|
||||
assertEquals((byte) 0xa0, memory.getByte(addr));
|
||||
|
||||
undo(program);
|
||||
|
||||
assertEquals(value, memory.getByte(addr));
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
Color fg = field.getForeground();
|
||||
assertTrue(fg == null ||
|
||||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
|
||||
|
||||
redo(program);
|
||||
|
||||
// field color should show edit color
|
||||
loc = getFieldLocation(addr);
|
||||
field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, field.getForeground());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndoRedoHexLong() throws Exception {
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final Address addr = getAddr(0x01001003);
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
ByteViewerComponent c = findComponent(panel, "Hex Long");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol());
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
byte value = program.getMemory().getByte(addr);
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
KeyEvent ev =
|
||||
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_A, 'a');
|
||||
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol(), currentComponent.getCurrentField());
|
||||
});
|
||||
program.flushEvents();
|
||||
assertEquals((byte) 0xa0, memory.getByte(addr));
|
||||
|
||||
undo(program);
|
||||
|
||||
assertEquals(value, memory.getByte(addr));
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
Color fg = field.getForeground();
|
||||
assertTrue(fg == null ||
|
||||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
|
||||
|
||||
redo(program);
|
||||
|
||||
// field color should show edit color
|
||||
loc = getFieldLocation(addr);
|
||||
field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, field.getForeground());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndoRedoHexLongLong() throws Exception {
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final Address addr = getAddr(0x01001003);
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
ByteViewerComponent c = findComponent(panel, "Hex Long Long");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol());
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
byte value = program.getMemory().getByte(addr);
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
KeyEvent ev =
|
||||
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_A, 'a');
|
||||
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol(), currentComponent.getCurrentField());
|
||||
});
|
||||
program.flushEvents();
|
||||
assertEquals((byte) 0xa0, memory.getByte(addr));
|
||||
|
||||
undo(program);
|
||||
|
||||
assertEquals(value, memory.getByte(addr));
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
Color fg = field.getForeground();
|
||||
assertTrue(fg == null ||
|
||||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
|
||||
|
||||
redo(program);
|
||||
|
||||
// field color should show edit color
|
||||
loc = getFieldLocation(addr);
|
||||
field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, field.getForeground());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndoRedoHexShort2() throws Exception {
|
||||
// remove code browser plugin so the cursor position does not
|
||||
// get changed because of location events that the code browser
|
||||
// generates.
|
||||
tool.removePlugins(new Plugin[] { cbPlugin });
|
||||
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
ByteViewerComponent c = findComponent(panel, "Hex Short");
|
||||
panel.setCurrentView(c);
|
||||
// make 3 changes
|
||||
// verify that the Undo button is enabled and undo can be done 5 times
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
Address addr = getAddr(0x01001003);
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
char[] values = { 'a', '1', '2' };
|
||||
int[] keyCodes =
|
||||
{ KeyEvent.VK_P, KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_B, KeyEvent.VK_3 };
|
||||
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
FieldLocation loc = currentComponent.getCursorLocation();
|
||||
KeyEvent ev = new KeyEvent(currentComponent, 0, new Date().getTime(), 0,
|
||||
keyCodes[i], values[i]);
|
||||
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol(), currentComponent.getCurrentField());
|
||||
}
|
||||
|
||||
});
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
assertTrue(program.canUndo());
|
||||
|
||||
undo(program);
|
||||
|
||||
FieldLocation loc = c.getCursorLocation();
|
||||
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
Color fg = field.getForeground();
|
||||
if (i == 2) {
|
||||
assertTrue(fg == null ||
|
||||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
|
||||
}
|
||||
else {
|
||||
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
|
||||
}
|
||||
}
|
||||
assertTrue(!program.canUndo());
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
|
||||
redo(program);
|
||||
|
||||
FieldLocation loc = c.getCursorLocation();
|
||||
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
Color fg = field.getForeground();
|
||||
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
|
||||
}
|
||||
|
||||
assertTrue(program.canUndo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndoRedoHexInteger2() throws Exception {
|
||||
// remove code browser plugin so the cursor position does not
|
||||
|
@ -514,7 +736,7 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
env.showTool();
|
||||
addViews();
|
||||
|
||||
ByteViewerComponent c = findComponent(panel, "HexInteger");
|
||||
ByteViewerComponent c = findComponent(panel, "Hex Integer");
|
||||
panel.setCurrentView(c);
|
||||
// make 5 changes
|
||||
// verify that the Undo button is enabled and undo can be done 5 times
|
||||
|
@ -579,6 +801,160 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
assertTrue(program.canUndo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndoRedoHexLong2() throws Exception {
|
||||
// remove code browser plugin so the cursor position does not
|
||||
// get changed because of location events that the code browser
|
||||
// generates.
|
||||
tool.removePlugins(new Plugin[] { cbPlugin });
|
||||
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
ByteViewerComponent c = findComponent(panel, "Hex Long");
|
||||
panel.setCurrentView(c);
|
||||
// make 9 changes
|
||||
// verify that the Undo button is enabled and undo can be done 5 times
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
Address addr = getAddr(0x01001003);
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
char[] values = { 'a', '1', '2', 'b', '3', 'c', '4', 'd', '5' };
|
||||
int[] keyCodes =
|
||||
{ KeyEvent.VK_P, KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_B, KeyEvent.VK_3,
|
||||
KeyEvent.VK_C, KeyEvent.VK_4, KeyEvent.VK_D, KeyEvent.VK_5 };
|
||||
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
for (int i = 0; i < 9; i++) {
|
||||
FieldLocation loc = currentComponent.getCursorLocation();
|
||||
KeyEvent ev = new KeyEvent(currentComponent, 0, new Date().getTime(), 0,
|
||||
keyCodes[i], values[i]);
|
||||
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol(), currentComponent.getCurrentField());
|
||||
}
|
||||
|
||||
});
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
assertTrue(program.canUndo());
|
||||
|
||||
undo(program);
|
||||
|
||||
FieldLocation loc = c.getCursorLocation();
|
||||
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
Color fg = field.getForeground();
|
||||
if (i == 8) {
|
||||
assertTrue(fg == null ||
|
||||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
|
||||
}
|
||||
else {
|
||||
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
|
||||
}
|
||||
}
|
||||
assertTrue(!program.canUndo());
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
|
||||
redo(program);
|
||||
|
||||
FieldLocation loc = c.getCursorLocation();
|
||||
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
Color fg = field.getForeground();
|
||||
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
|
||||
}
|
||||
|
||||
assertTrue(program.canUndo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndoRedoHexLongLong2() throws Exception {
|
||||
// remove code browser plugin so the cursor position does not
|
||||
// get changed because of location events that the code browser
|
||||
// generates.
|
||||
tool.removePlugins(new Plugin[] { cbPlugin });
|
||||
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
ByteViewerComponent c = findComponent(panel, "Hex Long Long");
|
||||
panel.setCurrentView(c);
|
||||
// make 9 changes
|
||||
// verify that the Undo button is enabled and undo can be done 5 times
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
Address addr = getAddr(0x01001003);
|
||||
FieldLocation loc = getFieldLocation(addr);
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
char[] values = { 'a', '1', '2', 'b', '3', 'c', '4', 'd', '5', 'e', '6', 'f', '7', 'a',
|
||||
'8', 'b', '9' };
|
||||
int[] keyCodes =
|
||||
{ KeyEvent.VK_P, KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_B, KeyEvent.VK_3,
|
||||
KeyEvent.VK_C, KeyEvent.VK_4, KeyEvent.VK_D, KeyEvent.VK_5, KeyEvent.VK_E, KeyEvent.VK_6,
|
||||
KeyEvent.VK_F, KeyEvent.VK_7, KeyEvent.VK_G, KeyEvent.VK_8, KeyEvent.VK_H, KeyEvent.VK_9 };
|
||||
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
for (int i = 0; i < 17; i++) {
|
||||
FieldLocation loc = currentComponent.getCursorLocation();
|
||||
KeyEvent ev = new KeyEvent(currentComponent, 0, new Date().getTime(), 0,
|
||||
keyCodes[i], values[i]);
|
||||
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol(), currentComponent.getCurrentField());
|
||||
}
|
||||
|
||||
});
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
|
||||
for (int i = 0; i < 17; i++) {
|
||||
assertTrue(program.canUndo());
|
||||
|
||||
undo(program);
|
||||
|
||||
FieldLocation loc = c.getCursorLocation();
|
||||
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
Color fg = field.getForeground();
|
||||
if (i == 16) {
|
||||
assertTrue(fg == null ||
|
||||
ByteViewerComponentProvider.CURSOR_NON_ACTIVE_COLOR == field.getForeground());
|
||||
}
|
||||
else {
|
||||
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
|
||||
}
|
||||
}
|
||||
assertTrue(!program.canUndo());
|
||||
|
||||
for (int i = 0; i < 17; i++) {
|
||||
|
||||
redo(program);
|
||||
|
||||
FieldLocation loc = c.getCursorLocation();
|
||||
ByteField field = c.getField(loc.getIndex(), loc.getFieldNum());
|
||||
Color fg = field.getForeground();
|
||||
assertEquals(fg, ByteViewerComponentProvider.CHANGED_VALUE_COLOR);
|
||||
}
|
||||
|
||||
assertTrue(program.canUndo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditInputHex() throws Exception {
|
||||
env.showTool();
|
||||
|
@ -717,12 +1093,129 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
assertEquals(value, program.getMemory().getByte(getAddr(0x01001000)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditModeHexShort() throws Exception {
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "Hex Short");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
final FieldLocation loc = getFieldLocation(getAddr(0x01001003));
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
assertTrue(action.isSelected());
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, c.getFocusedCursorColor());
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
KeyEvent ev = new KeyEvent(c, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
|
||||
c.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol(),
|
||||
c.getCurrentField());
|
||||
});
|
||||
program.flushEvents();
|
||||
assertEquals((byte) 0x10, program.getMemory().getByte(getAddr(0x01001003)));
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
|
||||
((ByteField) c.getCurrentField()).getForeground());
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
action.setSelected(false);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
assertTrue(!action.isSelected());
|
||||
assertEquals(ByteViewerComponentProvider.CURSOR_ACTIVE_COLOR,
|
||||
c.getFocusedCursorColor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditModeHexInteger() throws Exception {
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "HexInteger");
|
||||
final ByteViewerComponent c = findComponent(panel, "Hex Integer");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
final FieldLocation loc = getFieldLocation(getAddr(0x01001003));
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
assertTrue(action.isSelected());
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, c.getFocusedCursorColor());
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
KeyEvent ev = new KeyEvent(c, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
|
||||
c.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol(),
|
||||
c.getCurrentField());
|
||||
});
|
||||
program.flushEvents();
|
||||
assertEquals((byte) 0x10, program.getMemory().getByte(getAddr(0x01001003)));
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
|
||||
((ByteField) c.getCurrentField()).getForeground());
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
action.setSelected(false);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
assertTrue(!action.isSelected());
|
||||
assertEquals(ByteViewerComponentProvider.CURSOR_ACTIVE_COLOR,
|
||||
c.getFocusedCursorColor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditModeHexLong() throws Exception {
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "Hex Long");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
final FieldLocation loc = getFieldLocation(getAddr(0x01001003));
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
assertTrue(action.isSelected());
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR, c.getFocusedCursorColor());
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
KeyEvent ev = new KeyEvent(c, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
|
||||
c.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol(),
|
||||
c.getCurrentField());
|
||||
});
|
||||
program.flushEvents();
|
||||
assertEquals((byte) 0x10, program.getMemory().getByte(getAddr(0x01001003)));
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
|
||||
((ByteField) c.getCurrentField()).getForeground());
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
action.setSelected(false);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
});
|
||||
assertTrue(!action.isSelected());
|
||||
assertEquals(ByteViewerComponentProvider.CURSOR_ACTIVE_COLOR,
|
||||
c.getFocusedCursorColor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditModeHexLongLong() throws Exception {
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "Hex Long Long");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
final ToggleDockingAction action =
|
||||
|
@ -1563,7 +2056,10 @@ public class ByteViewerPlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
|
||||
setViewSelected(dialog, "Ascii", true);
|
||||
setViewSelected(dialog, "Octal", true);
|
||||
setViewSelected(dialog, "HexInteger", true);
|
||||
setViewSelected(dialog, "Hex Short", true);
|
||||
setViewSelected(dialog, "Hex Integer", true);
|
||||
setViewSelected(dialog, "Hex Long", true);
|
||||
setViewSelected(dialog, "Hex Long Long", true);
|
||||
pressButtonByText(dialog.getComponent(), "OK");
|
||||
}
|
||||
|
||||
|
|
|
@ -237,13 +237,72 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
|
|||
assertEquals(insertionStr, findLabelStr(panel, "Insertion"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHexShortView() throws Exception {
|
||||
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "Hex Short");
|
||||
panel.setCurrentView(c);
|
||||
assertEquals(8, c.getNumberOfFields());
|
||||
assertEquals(2, c.getDataModel().getUnitByteSize());
|
||||
|
||||
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
|
||||
runSwing(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
});
|
||||
// verify that the 2 bytes are represented as an 4 digit hex number
|
||||
assertEquals(4, c.getCurrentField().getNumCols(loc.getRow()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherEditsHexShort() throws Exception {
|
||||
// verify that the 4 byte string is rendered in red when a byte
|
||||
// is changed from another view, e.g. Ascii or Hex
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "Ascii");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
|
||||
runSwing(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
KeyEvent ev =
|
||||
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
|
||||
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol(), currentComponent.getCurrentField());
|
||||
});
|
||||
program.flushEvents();
|
||||
|
||||
final ByteViewerComponent hexComp = findComponent(panel, "Hex Short");
|
||||
|
||||
runSwing(() -> {
|
||||
ProgramByteBlockSet blockset =
|
||||
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
|
||||
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
|
||||
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
|
||||
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
|
||||
});
|
||||
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
|
||||
((ByteField) hexComp.getCurrentField()).getForeground());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHexIntegerView() throws Exception {
|
||||
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "HexInteger");
|
||||
final ByteViewerComponent c = findComponent(panel, "Hex Integer");
|
||||
panel.setCurrentView(c);
|
||||
assertEquals(4, c.getNumberOfFields());
|
||||
assertEquals(4, c.getDataModel().getUnitByteSize());
|
||||
|
@ -282,7 +341,125 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
|
|||
});
|
||||
program.flushEvents();
|
||||
|
||||
final ByteViewerComponent hexComp = findComponent(panel, "HexInteger");
|
||||
final ByteViewerComponent hexComp = findComponent(panel, "Hex Integer");
|
||||
|
||||
runSwing(() -> {
|
||||
ProgramByteBlockSet blockset =
|
||||
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
|
||||
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
|
||||
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
|
||||
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
|
||||
});
|
||||
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
|
||||
((ByteField) hexComp.getCurrentField()).getForeground());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHexLongView() throws Exception {
|
||||
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "Hex Long");
|
||||
panel.setCurrentView(c);
|
||||
assertEquals(2, c.getNumberOfFields());
|
||||
assertEquals(8, c.getDataModel().getUnitByteSize());
|
||||
|
||||
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
|
||||
runSwing(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
});
|
||||
// verify that the 8 bytes are represented as an 8 digit hex number
|
||||
assertEquals(16, c.getCurrentField().getNumCols(loc.getRow()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherEditsHexLong() throws Exception {
|
||||
// verify that the 4 byte string is rendered in red when a byte
|
||||
// is changed from another view, e.g. Ascii or Hex
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "Ascii");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
|
||||
runSwing(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
KeyEvent ev =
|
||||
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
|
||||
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol(), currentComponent.getCurrentField());
|
||||
});
|
||||
program.flushEvents();
|
||||
|
||||
final ByteViewerComponent hexComp = findComponent(panel, "Hex Long");
|
||||
|
||||
runSwing(() -> {
|
||||
ProgramByteBlockSet blockset =
|
||||
(ProgramByteBlockSet) plugin.getProvider().getByteBlockSet();
|
||||
ByteBlockInfo bbInfo = blockset.getByteBlockInfo(getAddr(0x01001000));
|
||||
FieldLocation l = hexComp.getFieldLocation(bbInfo.getBlock(), bbInfo.getOffset());
|
||||
hexComp.setCursorPosition(l.getIndex(), l.getFieldNum(), 0, 0);
|
||||
});
|
||||
|
||||
assertEquals(ByteViewerComponentProvider.CHANGED_VALUE_COLOR,
|
||||
((ByteField) hexComp.getCurrentField()).getForeground());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHexLongLongView() throws Exception {
|
||||
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "Hex Long Long");
|
||||
panel.setCurrentView(c);
|
||||
assertEquals(1, c.getNumberOfFields());
|
||||
assertEquals(16, c.getDataModel().getUnitByteSize());
|
||||
|
||||
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
|
||||
runSwing(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
});
|
||||
// verify that the 16 bytes are represented as an 32 digit hex number
|
||||
assertEquals(32, c.getCurrentField().getNumCols(loc.getRow()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherEditsHexLongLong() throws Exception {
|
||||
// verify that the 4 byte string is rendered in red when a byte
|
||||
// is changed from another view, e.g. Ascii or Hex
|
||||
env.showTool();
|
||||
addViews();
|
||||
|
||||
final ByteViewerComponent c = findComponent(panel, "Ascii");
|
||||
panel.setCurrentView(c);
|
||||
|
||||
final ToggleDockingAction action =
|
||||
(ToggleDockingAction) getAction(plugin, "Enable/Disable Byteviewer Editing");
|
||||
final FieldLocation loc = getFieldLocation(getAddr(0x01001000));
|
||||
runSwing(() -> {
|
||||
ByteViewerComponent currentComponent = panel.getCurrentComponent();
|
||||
currentComponent.setCursorPosition(loc.getIndex(), loc.getFieldNum(), 0, 0);
|
||||
action.setSelected(true);
|
||||
action.actionPerformed(new DefaultActionContext());
|
||||
KeyEvent ev =
|
||||
new KeyEvent(currentComponent, 0, new Date().getTime(), 0, KeyEvent.VK_1, '1');
|
||||
currentComponent.keyPressed(ev, loc.getIndex(), loc.getFieldNum(), loc.getRow(),
|
||||
loc.getCol(), currentComponent.getCurrentField());
|
||||
});
|
||||
program.flushEvents();
|
||||
|
||||
final ByteViewerComponent hexComp = findComponent(panel, "Hex Long Long");
|
||||
|
||||
runSwing(() -> {
|
||||
ProgramByteBlockSet blockset =
|
||||
|
@ -402,7 +579,7 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
|
|||
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
|
||||
setViewSelected(dialog, "Ascii", true);
|
||||
setViewSelected(dialog, "Octal", true);
|
||||
setViewSelected(dialog, "HexInteger", true);
|
||||
setViewSelected(dialog, "Hex Integer", true);
|
||||
setViewSelected(dialog, "Integer", true);
|
||||
pressButtonByText(dialog.getComponent(), "OK");
|
||||
waitForSwing();
|
||||
|
@ -805,7 +982,10 @@ public class ByteViewerPluginFormatsTest extends AbstractGhidraHeadedIntegration
|
|||
ByteViewerOptionsDialog dialog = launchByteViewerOptions();
|
||||
setViewSelected(dialog, "Ascii", true);
|
||||
setViewSelected(dialog, "Octal", true);
|
||||
setViewSelected(dialog, "HexInteger", true);
|
||||
setViewSelected(dialog, "Hex Short", true);
|
||||
setViewSelected(dialog, "Hex Integer", true);
|
||||
setViewSelected(dialog, "Hex Long", true);
|
||||
setViewSelected(dialog, "Hex Long Long", true);
|
||||
pressButtonByText(dialog.getComponent(), "OK");
|
||||
waitForSwing();
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.util.stream.Collectors;
|
|||
import docking.widgets.EventTrigger;
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import generic.theme.GColor;
|
||||
import ghidra.app.decompiler.ClangSyntaxToken;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.component.*;
|
||||
|
@ -120,7 +119,7 @@ public class DiffClangHighlightController extends LocationClangHighlightControll
|
|||
List<ClangToken> tokens = addPrimaryHighlightToTokensForParenthesis(
|
||||
(ClangSyntaxToken) tok, defaultParenColor);
|
||||
reHighlightDiffs(tokens);
|
||||
addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor);
|
||||
addPrimaryHighlightToTokensForBrace((ClangSyntaxToken) tok, defaultParenColor);
|
||||
}
|
||||
|
||||
TokenBin tokenBin = null;
|
||||
|
|
|
@ -18,8 +18,6 @@ package classrecovery;
|
|||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.decompiler.DecompileOptions;
|
||||
import ghidra.app.decompiler.component.DecompilerUtils;
|
||||
import ghidra.app.decompiler.util.FillOutStructureHelper;
|
||||
import ghidra.app.decompiler.util.FillOutStructureHelper.OffsetPcodeOpPair;
|
||||
import ghidra.app.util.opinion.PeLoader;
|
||||
|
@ -1480,10 +1478,7 @@ public class RTTIWindowsClassRecoverer extends RTTIClassRecoverer {
|
|||
}
|
||||
}
|
||||
|
||||
DecompileOptions decompileOptions =
|
||||
DecompilerUtils.getDecompileOptions(serviceProvider, program);
|
||||
FillOutStructureHelper fillStructHelper =
|
||||
new FillOutStructureHelper(program, decompileOptions, monitor);
|
||||
FillOutStructureHelper fillStructHelper = new FillOutStructureHelper(program, monitor);
|
||||
|
||||
for (Function constructor : constructorList) {
|
||||
|
||||
|
@ -1568,7 +1563,7 @@ public class RTTIWindowsClassRecoverer extends RTTIClassRecoverer {
|
|||
|
||||
monitor.checkCancelled();
|
||||
|
||||
fillStructHelper.processStructure(highVariable, function, true, false);
|
||||
fillStructHelper.processStructure(highVariable, function, true, false, null);
|
||||
List<OffsetPcodeOpPair> stores = fillStructHelper.getStorePcodeOps();
|
||||
stores = removePcodeOpsNotInFunction(function, stores);
|
||||
|
||||
|
@ -2484,7 +2479,7 @@ public class RTTIWindowsClassRecoverer extends RTTIClassRecoverer {
|
|||
baseClassDescriptorAddress.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Continue if the class has mult inh but base class is not on the parent list
|
||||
if (!recoveredClass.getParentList().contains(baseClass)) {
|
||||
continue;
|
||||
|
|
|
@ -498,16 +498,6 @@ public class RecoveredClass {
|
|||
String fieldName = newComponent.getFieldName();
|
||||
String comment = newComponent.getComment();
|
||||
|
||||
// if it is any empty placeholder structure - replace with
|
||||
// undefined1 dt
|
||||
if (newComponentDataType instanceof Structure &&
|
||||
newComponentDataType.isNotYetDefined()) {
|
||||
|
||||
computedClassStructure.replaceAtOffset(offset, new Undefined1DataType(), 1,
|
||||
fieldName, comment);
|
||||
continue;
|
||||
}
|
||||
|
||||
// if new component is an existing class data type pointer then replace current item
|
||||
// with a void pointer of same size if there is room
|
||||
if (newComponentDataType instanceof Pointer &&
|
||||
|
@ -529,9 +519,19 @@ public class RecoveredClass {
|
|||
// if the new component is a non-empty structure, check to see if the current
|
||||
// structure has undefined or equivalent components and replace with new struct if so
|
||||
if (newComponentDataType instanceof Structure) {
|
||||
|
||||
// if new component is any empty placeholder structure AND if the existing component
|
||||
// is undefined then replace with undefined1 dt
|
||||
if (newComponentDataType.isNotYetDefined()) {
|
||||
if (Undefined.isUndefined(currentComponentDataType)) {
|
||||
computedClassStructure.replaceAtOffset(offset, new Undefined1DataType(), 1,
|
||||
fieldName, comment);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (EditStructureUtils.hasReplaceableComponentsAtOffset(computedClassStructure,
|
||||
offset, (Structure) newComponentDataType, monitor)) {
|
||||
|
||||
offset, (Structure)newComponentDataType, monitor)) {
|
||||
|
||||
boolean successfulClear =
|
||||
EditStructureUtils.clearLengthAtOffset(computedClassStructure, offset,
|
||||
length, monitor);
|
||||
|
|
|
@ -22,8 +22,6 @@ import java.util.stream.Collectors;
|
|||
import ghidra.app.cmd.function.ApplyFunctionSignatureCmd;
|
||||
import ghidra.app.cmd.label.AddLabelCmd;
|
||||
import ghidra.app.cmd.label.SetLabelPrimaryCmd;
|
||||
import ghidra.app.decompiler.DecompileOptions;
|
||||
import ghidra.app.decompiler.component.DecompilerUtils;
|
||||
import ghidra.app.decompiler.util.FillOutStructureHelper;
|
||||
import ghidra.app.decompiler.util.FillOutStructureHelper.OffsetPcodeOpPair;
|
||||
import ghidra.app.plugin.core.navigation.locationreferences.LocationReference;
|
||||
|
@ -1135,10 +1133,7 @@ public class RecoveredClassHelper {
|
|||
highVariables
|
||||
.addAll(getVariableThatStoresVftablePointer(highFunction, firstVftableReference));
|
||||
|
||||
DecompileOptions decompileOptions =
|
||||
DecompilerUtils.getDecompileOptions(serviceProvider, program);
|
||||
FillOutStructureHelper fillStructHelper =
|
||||
new FillOutStructureHelper(program, decompileOptions, monitor);
|
||||
FillOutStructureHelper fillStructHelper = new FillOutStructureHelper(program, monitor);
|
||||
|
||||
Address vftableAddress = null;
|
||||
for (HighVariable highVariable : highVariables) {
|
||||
|
@ -1146,7 +1141,8 @@ public class RecoveredClassHelper {
|
|||
monitor.checkCancelled();
|
||||
|
||||
Structure structure =
|
||||
fillStructHelper.processStructure(highVariable, function, true, false);
|
||||
fillStructHelper.processStructure(highVariable, function, true, false,
|
||||
decompilerUtils.getDecompilerInterface());
|
||||
|
||||
NoisyStructureBuilder componentMap = fillStructHelper.getComponentMap();
|
||||
|
||||
|
@ -1202,13 +1198,12 @@ public class RecoveredClassHelper {
|
|||
|
||||
Address address = getTargetAddressFromPcodeOp(pcodeOp);
|
||||
if (address.equals(vftableReference)) {
|
||||
|
||||
Varnode[] inputs = pcodeOp.getInputs();
|
||||
for (Varnode input : inputs) {
|
||||
monitor.checkCancelled();
|
||||
if (input.getHigh() != null) {
|
||||
highVars.add(input.getHigh());
|
||||
}
|
||||
Varnode input = pcodeOp.getInput(1);
|
||||
if (input.getDef() != null && input.getDef().getOpcode() == PcodeOp.CAST) {
|
||||
input = input.getDef().getInput(0);
|
||||
}
|
||||
if (input.getHigh() != null) {
|
||||
highVars.add(input.getHigh());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5855,16 +5850,13 @@ public class RecoveredClassHelper {
|
|||
highVariables
|
||||
.addAll(getVariableThatStoresVftablePointer(highFunction, firstVftableReference));
|
||||
|
||||
DecompileOptions decompileOptions =
|
||||
DecompilerUtils.getDecompileOptions(serviceProvider, program);
|
||||
FillOutStructureHelper fillStructHelper =
|
||||
new FillOutStructureHelper(program, decompileOptions, monitor);
|
||||
FillOutStructureHelper fillStructHelper = new FillOutStructureHelper(program, monitor);
|
||||
|
||||
for (HighVariable highVariable : highVariables) {
|
||||
|
||||
monitor.checkCancelled();
|
||||
|
||||
fillStructHelper.processStructure(highVariable, function, true, false);
|
||||
fillStructHelper.processStructure(highVariable, function, true, false, null);
|
||||
List<OffsetPcodeOpPair> stores = fillStructHelper.getStorePcodeOps();
|
||||
stores = removePcodeOpsNotInFunction(function, stores);
|
||||
|
||||
|
|
|
@ -4370,7 +4370,8 @@
|
|||
<title>Middle-Click</title>
|
||||
<para>
|
||||
Highlights every occurrence of a variable, constant, or operator represented by the selected
|
||||
token, within the Decompiler window.
|
||||
token, within the Decompiler window. There are actions available from the popup menu and from
|
||||
the keyboard to navigate to each highlighted token.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
|
@ -4770,6 +4771,17 @@
|
|||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="GoToMiddleMouseHighlight">
|
||||
<title>Go To Next/Previous Highlight</title>
|
||||
<para>
|
||||
These actions are available from the popup menu and keyboard. Only tokens highlighted from the
|
||||
middle-mouse will be navigated. <emphasis role="bold">Shift-Comma</emphasis> will go to the
|
||||
previous highlighted token. <emphasis role="bold">Shift-Period</emphasis> will go to the
|
||||
next highlighted token. These key bindings can be changed via the
|
||||
<link xlink:href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option">Tool Options Dialog</link>.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="ActionHighlight">
|
||||
<title>Highlight</title>
|
||||
<para>
|
||||
|
|
|
@ -533,8 +533,10 @@
|
|||
|
||||
<p>
|
||||
Highlights every occurrence of a variable, constant, or operator represented by the selected
|
||||
token, within the Decompiler window.
|
||||
token, within the Decompiler window. There are actions available from the popup menu and from
|
||||
the keyboard to navigate to each highlighted token.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -958,6 +960,19 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="sect2">
|
||||
<div class="titlepage"><div><div><h3 class="title">
|
||||
<a name="GoToMiddleMouseHighlight"></a>Go To Next/Previous Highlight</h3></div></div></div>
|
||||
|
||||
<p>
|
||||
These actions are available from the popup menu and keyboard. Only tokens highlighted from the
|
||||
middle-mouse will be navigated. <span class="bold"><strong>Shift-Comma</strong></span> will
|
||||
go to the previous highlighted token. <span class="bold"><strong>Shift-Period</strong></span>
|
||||
will go to the next highlighted token. These key bindings can be changed via the
|
||||
<a class="ulink" href="help/topics/Tool/ToolOptions_Dialog.htm#KeyBindings_Option" target="_top">Tool Options Dialog</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="sect2">
|
||||
<div class="titlepage"><div><div><h3 class="title">
|
||||
<a name="ActionHighlight"></a>Highlight</h3></div></div></div>
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.util.*;
|
|||
*/
|
||||
public class ClangLine {
|
||||
private int indent_level;
|
||||
private ArrayList<ClangToken> tokens;
|
||||
private List<ClangToken> tokens;
|
||||
private int lineNumber;
|
||||
|
||||
public ClangLine(int lineNumber, int indent) {
|
||||
|
@ -35,7 +35,7 @@ public class ClangLine {
|
|||
}
|
||||
|
||||
public String getIndentString() {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
for (int i = 0; i < indent_level; i++) {
|
||||
buffer.append(PrettyPrinter.INDENT_STRING);
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ public class ClangLine {
|
|||
tok.setLineParent(this);
|
||||
}
|
||||
|
||||
public ArrayList<ClangToken> getAllTokens() {
|
||||
public List<ClangToken> getAllTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,6 @@ public class ClangLine {
|
|||
if (isCallout) {
|
||||
buffy.append(end);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return buffy.toString();
|
||||
|
|
|
@ -37,7 +37,7 @@ public class PrettyPrinter {
|
|||
|
||||
private Function function;
|
||||
private ClangTokenGroup tokgroup;
|
||||
private ArrayList<ClangLine> lines = new ArrayList<>();
|
||||
private List<ClangLine> lines = new ArrayList<>();
|
||||
private NameTransformer transformer;
|
||||
|
||||
/**
|
||||
|
@ -58,7 +58,7 @@ public class PrettyPrinter {
|
|||
|
||||
private void padEmptyLines() {
|
||||
for (ClangLine line : lines) {
|
||||
ArrayList<ClangToken> tokenList = line.getAllTokens();
|
||||
List<ClangToken> tokenList = line.getAllTokens();
|
||||
if (tokenList.size() == 0) {
|
||||
ClangToken spacer = ClangToken.buildSpacer(null, line.getIndent(), INDENT_STRING);
|
||||
spacer.setLineParent(line);
|
||||
|
@ -72,11 +72,11 @@ public class PrettyPrinter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns an array list of the C language lines contained in the
|
||||
* Returns a list of the C language lines contained in the
|
||||
* C language token group.
|
||||
* @return an array list of the C language lines
|
||||
* @return a list of the C language lines
|
||||
*/
|
||||
public ArrayList<ClangLine> getLines() {
|
||||
public List<ClangLine> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
@ -86,8 +86,7 @@ public class PrettyPrinter {
|
|||
* @return a string of readable C code
|
||||
*/
|
||||
public DecompiledFunction print() {
|
||||
StringBuffer buff = new StringBuffer();
|
||||
|
||||
StringBuilder buff = new StringBuilder();
|
||||
for (ClangLine line : lines) {
|
||||
buff.append(line.getIndentString());
|
||||
List<ClangToken> tokens = line.getAllTokens();
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.decompiler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* An iterator over ClangToken objects. The iterator walks a tree of ClangNode objects based on
|
||||
|
@ -126,7 +125,7 @@ public class TokenIterator implements Iterator<ClangToken> {
|
|||
* @param forward is true for a forward iterator, false for a backward iterator
|
||||
*/
|
||||
public TokenIterator(ClangToken token, boolean forward) {
|
||||
ArrayList<ClangTokenGroup> groupList = new ArrayList<>();
|
||||
List<ClangTokenGroup> groupList = new ArrayList<>();
|
||||
ClangNode node = token.Parent();
|
||||
while (node != null) {
|
||||
groupList.add((ClangTokenGroup) node);
|
||||
|
@ -154,7 +153,7 @@ public class TokenIterator implements Iterator<ClangToken> {
|
|||
* @param forward is true for a forward iterator, false for a backward iterator
|
||||
*/
|
||||
public TokenIterator(ClangTokenGroup group, boolean forward) {
|
||||
ArrayList<ClangTokenGroup> groupList = new ArrayList<>();
|
||||
List<ClangTokenGroup> groupList = new ArrayList<>();
|
||||
ClangNode node = group;
|
||||
while (node instanceof ClangTokenGroup) {
|
||||
ClangTokenGroup curGroup = (ClangTokenGroup) node;
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.awt.Color;
|
|||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import generic.json.Json;
|
||||
import ghidra.app.decompiler.*;
|
||||
|
||||
/**
|
||||
|
@ -95,7 +96,7 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter {
|
|||
}
|
||||
|
||||
Supplier<? extends Collection<ClangToken>> tokens = () -> highlights.keySet();
|
||||
ColorProvider colorProvider = t -> highlights.get(t);
|
||||
ColorProvider colorProvider = new MappedTokenColorProvider(highlights);
|
||||
decompilerPanel.addHighlighterHighlights(this, tokens, colorProvider);
|
||||
|
||||
clones.forEach(c -> c.applyHighlights());
|
||||
|
@ -155,6 +156,25 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ' ' + id;
|
||||
return Json.toString(this, "matcher", "id");
|
||||
}
|
||||
|
||||
private class MappedTokenColorProvider implements ColorProvider {
|
||||
|
||||
private Map<ClangToken, Color> highlights;
|
||||
|
||||
MappedTokenColorProvider(Map<ClangToken, Color> highlights) {
|
||||
this.highlights = highlights;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor(ClangToken token) {
|
||||
return highlights.get(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Token Matcher Color " + matcher.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ import java.util.*;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.collections4.map.LazyMap;
|
||||
|
||||
import docking.widgets.EventTrigger;
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
|
@ -42,7 +40,7 @@ import util.CollectionUtils;
|
|||
*
|
||||
* <p>This class maintains the following types of highlights:
|
||||
* <UL>
|
||||
* <LI>Primary Highlights - triggered by user clicking and some user actions; considered transient
|
||||
* <LI> Context Highlights - triggered by user clicking and some user actions; considered transient
|
||||
* and get cleared whenever the location changes. These highlights show state such as the
|
||||
* current field, impact of a variable (via a slicing action), or related syntax (such as
|
||||
* matching braces)
|
||||
|
@ -77,21 +75,8 @@ public abstract class ClangHighlightController {
|
|||
protected Color defaultHighlightColor = DEFAULT_HIGHLIGHT_COLOR;
|
||||
protected Color defaultParenColor = DEFAULT_HIGHLIGHT_COLOR;
|
||||
|
||||
private TokenHighlights primaryHighlightTokens = new TokenHighlights();
|
||||
|
||||
private Map<Function, List<ClangDecompilerHighlighter>> secondaryHighlightersbyFunction =
|
||||
LazyMap.lazyMap(new HashMap<>(), f -> new ArrayList<>());
|
||||
|
||||
// store the secondary highlighters here in addition to the map below so that we may discern
|
||||
// between secondary highlights and highlight service highlights
|
||||
private Set<ClangDecompilerHighlighter> secondaryHighlighters = new HashSet<>();
|
||||
|
||||
// all highlighters, including secondary and highlight service highlighters
|
||||
private Map<ClangDecompilerHighlighter, TokenHighlights> highlighterHighlights =
|
||||
new HashMap<>();
|
||||
|
||||
// color supplier for secondary highlights
|
||||
private TokenHighlightColors secondaryHighlightColors = new TokenHighlightColors();
|
||||
private TokenHighlights contextHighlightTokens = new TokenHighlights();
|
||||
private UserHighlights userHighlights = new UserHighlights();
|
||||
|
||||
/**
|
||||
* A counter to track updates so that clients know when a buffered highlight request is invalid
|
||||
|
@ -113,21 +98,8 @@ public abstract class ClangHighlightController {
|
|||
* color.
|
||||
* @return the color provider
|
||||
*/
|
||||
public ColorProvider getRandomColorProvider() {
|
||||
return token -> secondaryHighlightColors.getColor(token.getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token that has the primary highlight applied, if any. If multiple tokens are
|
||||
* highlighted, then the return value is arbitrary.
|
||||
* @return the highlighted text
|
||||
*/
|
||||
public String getPrimaryHighlightedText() {
|
||||
ClangToken highlightedToken = getHighlightedToken();
|
||||
if (highlightedToken != null) {
|
||||
return highlightedToken.getText();
|
||||
}
|
||||
return null;
|
||||
public ColorProvider getGeneratedColorProvider() {
|
||||
return new GeneratedColorProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,8 +111,8 @@ public abstract class ClangHighlightController {
|
|||
return updateId;
|
||||
}
|
||||
|
||||
public boolean hasPrimaryHighlight(ClangToken token) {
|
||||
return primaryHighlightTokens.contains(token);
|
||||
public boolean hasContextHighlight(ClangToken token) {
|
||||
return contextHighlightTokens.contains(token);
|
||||
}
|
||||
|
||||
public boolean hasSecondaryHighlight(ClangToken token) {
|
||||
|
@ -148,26 +120,19 @@ public abstract class ClangHighlightController {
|
|||
}
|
||||
|
||||
public boolean hasSecondaryHighlights(Function function) {
|
||||
return !secondaryHighlightersbyFunction.get(function).isEmpty();
|
||||
return userHighlights.hasSecondaryHighlights(function);
|
||||
}
|
||||
|
||||
public Color getSecondaryHighlight(ClangToken token) {
|
||||
DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
|
||||
if (highlighter != null) {
|
||||
TokenHighlights highlights = highlighterHighlights.get(highlighter);
|
||||
HighlightToken hlToken = highlights.get(token);
|
||||
return hlToken.getColor();
|
||||
}
|
||||
|
||||
return null;
|
||||
return userHighlights.getSecondaryHighlight(token);
|
||||
}
|
||||
|
||||
public TokenHighlightColors getSecondaryHighlightColors() {
|
||||
return secondaryHighlightColors;
|
||||
return userHighlights.getSecondaryHighlightColors();
|
||||
}
|
||||
|
||||
public TokenHighlights getPrimaryHighlights() {
|
||||
return primaryHighlightTokens;
|
||||
return contextHighlightTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,8 +142,8 @@ public abstract class ClangHighlightController {
|
|||
* @param function the function
|
||||
* @return the highlighters
|
||||
*/
|
||||
public Set<ClangDecompilerHighlighter> getSecondaryHighlighters(Function function) {
|
||||
return new HashSet<>(secondaryHighlightersbyFunction.get(function));
|
||||
public Set<DecompilerHighlighter> getSecondaryHighlighters(Function function) {
|
||||
return userHighlights.getSecondaryHighlighters(function);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -187,11 +152,8 @@ public abstract class ClangHighlightController {
|
|||
* function-specific.
|
||||
* @return the highlighters
|
||||
*/
|
||||
public Set<ClangDecompilerHighlighter> getGlobalHighlighters() {
|
||||
Set<ClangDecompilerHighlighter> allHighlighters = highlighterHighlights.keySet();
|
||||
Set<ClangDecompilerHighlighter> results = new HashSet<>(allHighlighters);
|
||||
results.removeAll(secondaryHighlighters);
|
||||
return results;
|
||||
public Set<DecompilerHighlighter> getGlobalHighlighters() {
|
||||
return userHighlights.getGlobalHighlighters();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,7 +163,7 @@ public abstract class ClangHighlightController {
|
|||
* @see #getPrimaryHighlights()
|
||||
*/
|
||||
public TokenHighlights getHighlighterHighlights(DecompilerHighlighter highlighter) {
|
||||
return highlighterHighlights.get(highlighter);
|
||||
return userHighlights.getHighlights(highlighter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,8 +171,8 @@ public abstract class ClangHighlightController {
|
|||
* @return token or null
|
||||
*/
|
||||
public ClangToken getHighlightedToken() {
|
||||
if (primaryHighlightTokens.size() == 1) {
|
||||
HighlightToken hlToken = CollectionUtils.any(primaryHighlightTokens);
|
||||
if (contextHighlightTokens.size() == 1) {
|
||||
HighlightToken hlToken = CollectionUtils.any(contextHighlightTokens);
|
||||
return hlToken.getToken();
|
||||
}
|
||||
return null;
|
||||
|
@ -236,7 +198,7 @@ public abstract class ClangHighlightController {
|
|||
updateHighlightColor(token);
|
||||
};
|
||||
|
||||
doClearHighlights(primaryHighlightTokens, clearAll);
|
||||
doClearHighlights(contextHighlightTokens, clearAll);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
@ -262,10 +224,9 @@ public abstract class ClangHighlightController {
|
|||
* @param tokens the tokens
|
||||
*/
|
||||
public void togglePrimaryHighlights(Color hlColor, Supplier<List<ClangToken>> tokens) {
|
||||
|
||||
boolean isAllHighlighted = true;
|
||||
for (ClangToken otherToken : tokens.get()) {
|
||||
if (!hasPrimaryHighlight(otherToken)) {
|
||||
for (ClangToken token : tokens.get()) {
|
||||
if (!hasContextHighlight(token)) {
|
||||
isAllHighlighted = false;
|
||||
break;
|
||||
}
|
||||
|
@ -276,8 +237,7 @@ public abstract class ClangHighlightController {
|
|||
clearPrimaryHighlights();
|
||||
|
||||
if (isAllHighlighted) {
|
||||
// nothing to do; we toggled from 'all on' to 'all off'
|
||||
return;
|
||||
return; // nothing to do; we toggled from 'all on' to 'all off'
|
||||
}
|
||||
|
||||
addPrimaryHighlights(tokens, hlColor);
|
||||
|
@ -289,9 +249,11 @@ public abstract class ClangHighlightController {
|
|||
*/
|
||||
public void removeSecondaryHighlights(Function f) {
|
||||
|
||||
List<ClangDecompilerHighlighter> highlighters = secondaryHighlightersbyFunction.get(f);
|
||||
for (ClangDecompilerHighlighter highlighter : highlighters) {
|
||||
TokenHighlights highlights = highlighterHighlights.get(highlighter);
|
||||
List<DecompilerHighlighter> highlighters =
|
||||
userHighlights.getSecondaryHighlightersByFunction(f);
|
||||
|
||||
for (DecompilerHighlighter highlighter : highlighters) {
|
||||
TokenHighlights highlights = userHighlights.getHighlights(highlighter);
|
||||
Consumer<ClangToken> clearHighlight = token -> updateHighlightColor(token);
|
||||
doClearHighlights(highlights, clearHighlight);
|
||||
}
|
||||
|
@ -305,38 +267,16 @@ public abstract class ClangHighlightController {
|
|||
* @see #removeSecondaryHighlights(Function)
|
||||
*/
|
||||
public void removeSecondaryHighlights(ClangToken token) {
|
||||
DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
|
||||
DecompilerHighlighter highlighter = userHighlights.getSecondaryHighlighter(token);
|
||||
if (highlighter != null) {
|
||||
highlighter.dispose(); // this will call removeHighlighterHighlights()
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
private DecompilerHighlighter getSecondaryHighlighter(ClangToken token) {
|
||||
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
|
||||
TokenHighlights highlights = highlighterHighlights.get(highlighter);
|
||||
HighlightToken hlToken = highlights.get(token);
|
||||
if (hlToken != null) {
|
||||
return highlighter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeHighlighter(DecompilerHighlighter highlighter) {
|
||||
|
||||
removeHighlighterHighlights(highlighter);
|
||||
highlighterHighlights.remove(highlighter);
|
||||
secondaryHighlighters.remove(highlighter);
|
||||
|
||||
Collection<List<ClangDecompilerHighlighter>> lists =
|
||||
secondaryHighlightersbyFunction.values();
|
||||
for (List<ClangDecompilerHighlighter> highlighters : lists) {
|
||||
if (highlighters.remove(highlighter)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
userHighlights.remove(highlighter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -345,7 +285,7 @@ public abstract class ClangHighlightController {
|
|||
*/
|
||||
public void removeHighlighterHighlights(DecompilerHighlighter highlighter) {
|
||||
|
||||
TokenHighlights highlighterTokens = highlighterHighlights.get(highlighter);
|
||||
TokenHighlights highlighterTokens = userHighlights.get(highlighter);
|
||||
if (highlighterTokens == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -361,59 +301,56 @@ public abstract class ClangHighlightController {
|
|||
* @param function the function
|
||||
* @param highlighter the highlighter
|
||||
*/
|
||||
public void addSecondaryHighlighter(Function function, ClangDecompilerHighlighter highlighter) {
|
||||
|
||||
// Note: this highlighter has likely already been added the the this class, but has not
|
||||
// yet been bound to the given function.
|
||||
secondaryHighlightersbyFunction.get(function).add(highlighter);
|
||||
secondaryHighlighters.add(highlighter);
|
||||
highlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
|
||||
public void addSecondaryHighlighter(Function function, DecompilerHighlighter highlighter) {
|
||||
userHighlights.addSecondaryHighlighter(function, highlighter);
|
||||
}
|
||||
|
||||
// Note: this is used for all highlight types, secondary and highlighter service highlighters
|
||||
public void addHighlighter(ClangDecompilerHighlighter highlighter) {
|
||||
highlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
|
||||
userHighlights.add(highlighter);
|
||||
}
|
||||
|
||||
// Note: this is used for all highlight types, secondary and highlighter service highlights
|
||||
public void addHighlighterHighlights(ClangDecompilerHighlighter highlighter,
|
||||
Supplier<? extends Collection<ClangToken>> tokens,
|
||||
ColorProvider colorProvider) {
|
||||
public void addHighlighterHighlights(DecompilerHighlighter highlighter,
|
||||
Supplier<? extends Collection<ClangToken>> tokens, ColorProvider colorProvider) {
|
||||
|
||||
Objects.requireNonNull(highlighter);
|
||||
TokenHighlights highlighterTokens =
|
||||
highlighterHighlights.computeIfAbsent(highlighter, k -> new TokenHighlights());
|
||||
TokenHighlights highlighterTokens = userHighlights.add(highlighter);
|
||||
addTokensToHighlights(tokens.get(), colorProvider, highlighterTokens);
|
||||
}
|
||||
|
||||
private void addPrimaryHighlights(Supplier<? extends Collection<ClangToken>> tokens,
|
||||
Color hlColor) {
|
||||
ColorProvider colorProvider = token -> hlColor;
|
||||
addTokensToHighlights(tokens.get(), colorProvider, primaryHighlightTokens);
|
||||
addPrimaryHighlights(tokens.get(), hlColor);
|
||||
}
|
||||
|
||||
private void addPrimaryHighlights(Collection<ClangToken> tokens, Color hlColor) {
|
||||
ColorProvider colorProvider = new DefaultColorProvider("Tokens Highlight Color", hlColor);
|
||||
addTokensToHighlights(tokens, colorProvider, contextHighlightTokens);
|
||||
}
|
||||
|
||||
public void addPrimaryHighlights(ClangNode parentNode, Set<PcodeOp> ops, Color hlColor) {
|
||||
|
||||
addPrimaryHighlights(parentNode, token -> {
|
||||
PcodeOp op = token.getPcodeOp();
|
||||
return ops.contains(op) ? hlColor : null;
|
||||
});
|
||||
ColorProvider colorProvider = new DefaultColorProvider("PcodeOp Highlight Color", hlColor) {
|
||||
@Override
|
||||
public Color getColor(ClangToken token) {
|
||||
PcodeOp op = token.getPcodeOp();
|
||||
return ops.contains(op) ? hlColor : null;
|
||||
}
|
||||
};
|
||||
|
||||
addPrimaryHighlights(parentNode, colorProvider);
|
||||
}
|
||||
|
||||
public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) {
|
||||
|
||||
Set<ClangToken> tokens = new HashSet<>();
|
||||
gatherAllTokens(parentNode, tokens);
|
||||
addTokensToHighlights(tokens, colorProvider::getColor, primaryHighlightTokens);
|
||||
addTokensToHighlights(tokens, colorProvider, contextHighlightTokens);
|
||||
}
|
||||
|
||||
private void addPrimaryHighlights(Collection<ClangToken> tokens, Color hlColor) {
|
||||
ColorProvider colorProvider = token -> hlColor;
|
||||
addTokensToHighlights(tokens, colorProvider, primaryHighlightTokens);
|
||||
}
|
||||
|
||||
private void addTokensToHighlights(Collection<ClangToken> tokens,
|
||||
ColorProvider colorProvider, TokenHighlights currentHighlights) {
|
||||
private void addTokensToHighlights(Collection<ClangToken> tokens, ColorProvider colorProvider,
|
||||
TokenHighlights currentHighlights) {
|
||||
|
||||
updateId++;
|
||||
|
||||
|
@ -441,7 +378,7 @@ public abstract class ClangHighlightController {
|
|||
}
|
||||
|
||||
private void updateHighlightColor(ClangToken t) {
|
||||
// set the color to the current combined value of both highlight types
|
||||
// set the color to the current combined value of all highlight types
|
||||
Color combinedColor = getCombinedColor(t);
|
||||
t.setHighlight(combinedColor);
|
||||
}
|
||||
|
@ -469,7 +406,7 @@ public abstract class ClangHighlightController {
|
|||
// note: not sure whether we should always blend all colors or decide to allow some
|
||||
// highlighters have precedence for highlighting
|
||||
|
||||
HighlightToken primaryHl = primaryHighlightTokens.get(t);
|
||||
HighlightToken primaryHl = contextHighlightTokens.get(t);
|
||||
Color blendedHlColor = blendHighlighterColors(t);
|
||||
|
||||
List<Color> allColors = new ArrayList<>();
|
||||
|
@ -506,12 +443,12 @@ public abstract class ClangHighlightController {
|
|||
return null; // not sure if this can happen
|
||||
}
|
||||
|
||||
Set<ClangDecompilerHighlighter> global = getGlobalHighlighters();
|
||||
Set<ClangDecompilerHighlighter> secondary = getSecondaryHighlighters(function);
|
||||
Iterable<ClangDecompilerHighlighter> it = CollectionUtils.asIterable(global, secondary);
|
||||
Set<DecompilerHighlighter> global = getGlobalHighlighters();
|
||||
Set<DecompilerHighlighter> secondary = getSecondaryHighlighters(function);
|
||||
Iterable<DecompilerHighlighter> it = CollectionUtils.asIterable(global, secondary);
|
||||
Color lastColor = null;
|
||||
for (ClangDecompilerHighlighter highlighter : it) {
|
||||
TokenHighlights highlights = highlighterHighlights.get(highlighter);
|
||||
for (DecompilerHighlighter highlighter : it) {
|
||||
TokenHighlights highlights = userHighlights.get(highlighter);
|
||||
HighlightToken hlToken = highlights.get(token);
|
||||
if (hlToken == null) {
|
||||
continue;
|
||||
|
@ -542,6 +479,24 @@ public abstract class ClangHighlightController {
|
|||
return highFunction.getFunction();
|
||||
}
|
||||
|
||||
protected void addPrimaryHighlightToTokensForBrace(ClangSyntaxToken token,
|
||||
Color highlightColor) {
|
||||
|
||||
if (DecompilerUtils.isBrace(token)) {
|
||||
highlightBrace(token, highlightColor);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
private void highlightBrace(ClangSyntaxToken startToken, Color highlightColor) {
|
||||
|
||||
ClangSyntaxToken matchingBrace = DecompilerUtils.getMatchingBrace(startToken);
|
||||
if (matchingBrace != null) {
|
||||
matchingBrace.setMatchingToken(true); // this is a signal to the painter
|
||||
addPrimaryHighlights(Set.of(matchingBrace), highlightColor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If input token is a parenthesis, highlight all tokens between it and its match
|
||||
* @param tok potential parenthesis token
|
||||
|
@ -609,23 +564,6 @@ public abstract class ClangHighlightController {
|
|||
return results;
|
||||
}
|
||||
|
||||
public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) {
|
||||
|
||||
if (DecompilerUtils.isBrace(token)) {
|
||||
highlightBrace(token, highlightColor);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
private void highlightBrace(ClangSyntaxToken startToken, Color highlightColor) {
|
||||
|
||||
ClangSyntaxToken matchingBrace = DecompilerUtils.getMatchingBrace(startToken);
|
||||
if (matchingBrace != null) {
|
||||
matchingBrace.setMatchingToken(true); // this is a signal to the painter
|
||||
addPrimaryHighlights(Set.of(matchingBrace), highlightColor);
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(ClangHighlightListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
@ -642,9 +580,21 @@ public abstract class ClangHighlightController {
|
|||
|
||||
public void dispose() {
|
||||
listeners.clear();
|
||||
primaryHighlightTokens.clear();
|
||||
secondaryHighlighters.clear();
|
||||
secondaryHighlightersbyFunction.clear();
|
||||
highlighterHighlights.clear();
|
||||
contextHighlightTokens.clear();
|
||||
userHighlights.dispose();
|
||||
}
|
||||
|
||||
private class GeneratedColorProvider implements ColorProvider {
|
||||
|
||||
@Override
|
||||
public Color getColor(ClangToken token) {
|
||||
return userHighlights.getSecondaryColor(token.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Generated Color Provider " + userHighlights.getAppliedColorsString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,11 +19,7 @@ import java.awt.*;
|
|||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.widgets.SearchLocation;
|
||||
import docking.widgets.fieldpanel.Layout;
|
||||
import docking.widgets.fieldpanel.LayoutModel;
|
||||
import docking.widgets.fieldpanel.field.*;
|
||||
|
@ -31,16 +27,12 @@ import docking.widgets.fieldpanel.listener.IndexMapper;
|
|||
import docking.widgets.fieldpanel.listener.LayoutModelListener;
|
||||
import docking.widgets.fieldpanel.support.*;
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
|
||||
import ghidra.app.util.viewer.field.CommentUtils;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Control the GUI layout for displaying tokenized C code
|
||||
*/
|
||||
public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
||||
|
@ -53,10 +45,10 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
|||
private Field[] fieldList; // Array of fields comprising layout
|
||||
private FontMetrics metrics;
|
||||
private FieldHighlightFactory hlFactory;
|
||||
private ArrayList<LayoutModelListener> listeners;
|
||||
private List<LayoutModelListener> listeners;
|
||||
private Color[] syntaxColor; // Foreground colors.
|
||||
private BigInteger numIndexes = BigInteger.ZERO;
|
||||
private ArrayList<ClangLine> lines = new ArrayList<>();
|
||||
private List<ClangLine> lines = new ArrayList<>();
|
||||
|
||||
private boolean showLineNumbers = true;
|
||||
|
||||
|
@ -71,7 +63,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
|||
buildLayouts(null, null, null, false);
|
||||
}
|
||||
|
||||
public ArrayList<ClangLine> getLines() {
|
||||
public List<ClangLine> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
@ -248,7 +240,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
|||
}
|
||||
}
|
||||
|
||||
private void splitToMaxWidthLines(ArrayList<String> res, String line) {
|
||||
private void splitToMaxWidthLines(List<String> res, String line) {
|
||||
int maxchar;
|
||||
if ((maxWidth == 0) || (indentWidth == 0)) {
|
||||
maxchar = 40;
|
||||
|
@ -257,7 +249,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
|||
maxchar = maxWidth / indentWidth;
|
||||
}
|
||||
String[] toklist = line.split("[ \t]+");
|
||||
StringBuffer buf = new StringBuffer();
|
||||
StringBuilder buf = new StringBuilder();
|
||||
int cursize = 0;
|
||||
boolean atleastone = false;
|
||||
int i = 0;
|
||||
|
@ -275,7 +267,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
|||
res.add(finishLine);
|
||||
cursize = 5;
|
||||
atleastone = false;
|
||||
buf = new StringBuffer();
|
||||
buf = new StringBuilder();
|
||||
buf.append(" ");
|
||||
}
|
||||
else {
|
||||
|
@ -302,7 +294,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
|||
return false; // No error message to add
|
||||
}
|
||||
String[] errlines_init = errmsg.split("[\n\r]+");
|
||||
ArrayList<String> errlines = new ArrayList<>();
|
||||
List<String> errlines = new ArrayList<>();
|
||||
for (String element : errlines_init) {
|
||||
splitToMaxWidthLines(errlines, element);
|
||||
}
|
||||
|
@ -337,255 +329,13 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
|||
return null;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Search Related Methods
|
||||
//==================================================================================================
|
||||
|
||||
private SearchLocation findNextTokenGoingForward(
|
||||
java.util.function.Function<String, SearchMatch> matcher, String searchString,
|
||||
FieldLocation currentLocation) {
|
||||
|
||||
int row = currentLocation.getIndex().intValue();
|
||||
for (int i = row; i < fieldList.length; i++) {
|
||||
ClangTextField field = (ClangTextField) fieldList[i];
|
||||
String partialLine =
|
||||
getTextLineFromOffset((i == row) ? currentLocation : null, field, true);
|
||||
SearchMatch match = matcher.apply(partialLine);
|
||||
if (match == SearchMatch.NO_MATCH) {
|
||||
continue;
|
||||
}
|
||||
if (i == row) { // cursor is on this line
|
||||
//
|
||||
// The match start for all lines without the cursor will be relative to the start
|
||||
// of the line, which is 0. However, when searching on the row with the cursor,
|
||||
// the match start is relative to the cursor position. Update the start to
|
||||
// compensate for the difference between the start of the line and the cursor.
|
||||
//
|
||||
String fullLine = field.getText();
|
||||
int cursorOffset = fullLine.length() - partialLine.length();
|
||||
match.start += cursorOffset;
|
||||
match.end += cursorOffset;
|
||||
}
|
||||
FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation =
|
||||
new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn());
|
||||
|
||||
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||
searchString, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SearchLocation findNextTokenGoingBackward(
|
||||
java.util.function.Function<String, SearchMatch> matcher, String searchString,
|
||||
FieldLocation currentLocation) {
|
||||
|
||||
int row = currentLocation.getIndex().intValue();
|
||||
for (int i = row; i >= 0; i--) {
|
||||
ClangTextField field = (ClangTextField) fieldList[i];
|
||||
String textLine =
|
||||
getTextLineFromOffset((i == row) ? currentLocation : null, field, false);
|
||||
|
||||
SearchMatch match = matcher.apply(textLine);
|
||||
if (match != SearchMatch.NO_MATCH) {
|
||||
FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation =
|
||||
new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn());
|
||||
|
||||
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||
searchString, false);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public SearchLocation findNextTokenForSearchRegex(String searchString,
|
||||
FieldLocation currentLocation, boolean forwardSearch) {
|
||||
|
||||
Pattern pattern = null;
|
||||
try {
|
||||
pattern = Pattern.compile(searchString, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
|
||||
}
|
||||
catch (PatternSyntaxException e) {
|
||||
Msg.showError(this, decompilerPanel, "Regular Expression Syntax Error", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
Pattern finalPattern = pattern;
|
||||
if (forwardSearch) {
|
||||
|
||||
java.util.function.Function<String, SearchMatch> function = textLine -> {
|
||||
|
||||
Matcher matcher = finalPattern.matcher(textLine);
|
||||
if (matcher.find()) {
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
return new SearchMatch(start, end, textLine);
|
||||
}
|
||||
|
||||
return SearchMatch.NO_MATCH;
|
||||
};
|
||||
|
||||
return findNextTokenGoingForward(function, searchString, currentLocation);
|
||||
}
|
||||
|
||||
java.util.function.Function<String, SearchMatch> reverse = textLine -> {
|
||||
|
||||
Matcher matcher = finalPattern.matcher(textLine);
|
||||
|
||||
if (!matcher.find()) {
|
||||
return SearchMatch.NO_MATCH;
|
||||
}
|
||||
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
|
||||
// Since the matcher can only match from the start to end of line, we need
|
||||
// to find all matches and then take the last match
|
||||
while (matcher.find()) {
|
||||
start = matcher.start();
|
||||
end = matcher.end();
|
||||
}
|
||||
|
||||
return new SearchMatch(start, end, textLine);
|
||||
};
|
||||
|
||||
return findNextTokenGoingBackward(reverse, searchString, currentLocation);
|
||||
}
|
||||
|
||||
public SearchLocation findNextTokenForSearch(String searchString, FieldLocation currentLocation,
|
||||
boolean forwardSearch) {
|
||||
|
||||
if (forwardSearch) {
|
||||
|
||||
java.util.function.Function<String, SearchMatch> function = textLine -> {
|
||||
|
||||
int index = StringUtils.indexOfIgnoreCase(textLine, searchString);
|
||||
if (index == -1) {
|
||||
return SearchMatch.NO_MATCH;
|
||||
}
|
||||
|
||||
return new SearchMatch(index, index + searchString.length(), textLine);
|
||||
};
|
||||
|
||||
return findNextTokenGoingForward(function, searchString, currentLocation);
|
||||
}
|
||||
|
||||
java.util.function.Function<String, SearchMatch> function = textLine -> {
|
||||
|
||||
int index = StringUtils.lastIndexOfIgnoreCase(textLine, searchString);
|
||||
if (index == -1) {
|
||||
return SearchMatch.NO_MATCH;
|
||||
}
|
||||
return new SearchMatch(index, index + searchString.length(), textLine);
|
||||
};
|
||||
|
||||
return findNextTokenGoingBackward(function, searchString, currentLocation);
|
||||
}
|
||||
|
||||
private String getTextLineFromOffset(FieldLocation location, ClangTextField textField,
|
||||
boolean forwardSearch) {
|
||||
|
||||
if (location == null) { // the cursor location is not on this line; use all of the text
|
||||
return textField.getText();
|
||||
}
|
||||
|
||||
if (textField.getText().isEmpty()) { // the cursor is on blank line
|
||||
return "";
|
||||
}
|
||||
|
||||
String partialText = textField.getText();
|
||||
|
||||
if (forwardSearch) {
|
||||
|
||||
int nextCol = location.getCol();
|
||||
|
||||
// protects against the location column being out of range (this can happen if we're
|
||||
// searching forward and the cursor is past the last token)
|
||||
if (nextCol >= partialText.length()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// skip a character to start the next search; this prevents matching the previous match
|
||||
return partialText.substring(nextCol);
|
||||
}
|
||||
|
||||
// backwards search
|
||||
return partialText.substring(0, location.getCol());
|
||||
}
|
||||
|
||||
private FieldNumberColumnPair getFieldIndexFromOffset(int screenOffset,
|
||||
ClangTextField textField) {
|
||||
RowColLocation rowColLocation = textField.textOffsetToScreenLocation(screenOffset);
|
||||
|
||||
// we use 0 here because currently there is only one field, which is the entire line
|
||||
return new FieldNumberColumnPair(0, rowColLocation.col());
|
||||
}
|
||||
|
||||
private static class SearchMatch {
|
||||
private static SearchMatch NO_MATCH = new SearchMatch(-1, -1, null);
|
||||
private int start;
|
||||
private int end;
|
||||
private String textLine;
|
||||
|
||||
SearchMatch(int start, int end, String textLine) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.textLine = textLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == NO_MATCH) {
|
||||
return "NO MATCH";
|
||||
}
|
||||
return "[start=" + start + ",end=" + end + "]: " + textLine;
|
||||
}
|
||||
}
|
||||
//==================================================================================================
|
||||
// End Search Related Methods
|
||||
//==================================================================================================
|
||||
|
||||
ClangToken getTokenForLocation(FieldLocation fieldLocation) {
|
||||
int row = fieldLocation.getIndex().intValue();
|
||||
ClangTextField field = (ClangTextField) fieldList[row];
|
||||
return field.getToken(fieldLocation);
|
||||
}
|
||||
|
||||
public void locationChanged(FieldLocation loc, Field field, Color locationColor,
|
||||
Color parenColor) {
|
||||
// Highlighting is now handled through the decompiler panel's highlight controller.
|
||||
}
|
||||
|
||||
public boolean changePending() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushChanges() {
|
||||
// nothing to do
|
||||
}
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
private class FieldNumberColumnPair {
|
||||
private final int fieldNumber;
|
||||
private final int column;
|
||||
|
||||
FieldNumberColumnPair(int fieldNumber, int column) {
|
||||
this.fieldNumber = fieldNumber;
|
||||
this.column = column;
|
||||
|
||||
}
|
||||
|
||||
int getFieldNumber() {
|
||||
return fieldNumber;
|
||||
}
|
||||
|
||||
int getColumn() {
|
||||
return column;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ package ghidra.app.decompiler.component;
|
|||
import java.util.List;
|
||||
|
||||
import docking.widgets.fieldpanel.field.*;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import docking.widgets.fieldpanel.support.FieldHighlightFactory;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
|
||||
public class ClangTextField extends WrappingVerticalLayoutTextField {
|
||||
|
@ -123,4 +123,12 @@ public class ClangTextField extends WrappingVerticalLayoutTextField {
|
|||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
public ClangToken getFirstToken() {
|
||||
return tokenList.get(0);
|
||||
}
|
||||
|
||||
public ClangToken getLastToken() {
|
||||
return tokenList.get(tokenList.size() - 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
/* ###
|
||||
* 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.decompiler.component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.ListSelectionModel;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.Tool;
|
||||
import docking.widgets.FindDialog;
|
||||
import docking.widgets.SearchLocation;
|
||||
import docking.widgets.button.GButton;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import docking.widgets.table.AbstractDynamicTableColumnStub;
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
|
||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearcher;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.app.util.query.TableService;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.table.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DecompilerFindDialog extends FindDialog {
|
||||
|
||||
private DecompilerPanel decompilerPanel;
|
||||
|
||||
public DecompilerFindDialog(DecompilerPanel decompilerPanel) {
|
||||
super("Decompiler Find Text", new DecompilerSearcher(decompilerPanel));
|
||||
this.decompilerPanel = decompilerPanel;
|
||||
|
||||
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
||||
|
||||
GButton showAllButton = new GButton("Search All");
|
||||
showAllButton.addActionListener(e -> showAll());
|
||||
|
||||
// move this button to the end
|
||||
removeButton(dismissButton);
|
||||
|
||||
addButton(showAllButton);
|
||||
addButton(dismissButton);
|
||||
}
|
||||
|
||||
private void showAll() {
|
||||
close();
|
||||
|
||||
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
|
||||
Tool tool = dwm.getTool();
|
||||
TableService tableService = tool.getService(TableService.class);
|
||||
if (tableService == null) {
|
||||
Msg.error(this,
|
||||
"Cannot use the Decompiler Search All action without having a TableService " +
|
||||
"installed");
|
||||
return;
|
||||
}
|
||||
|
||||
List<SearchLocation> results = searcher.searchAll(getSearchText(), useRegex());
|
||||
if (!results.isEmpty()) {
|
||||
// save off searches that find results so users can reuse them later
|
||||
storeSearchText(getSearchText());
|
||||
}
|
||||
|
||||
Program program = decompilerPanel.getProgram();
|
||||
DecompilerFindResultsModel model = new DecompilerFindResultsModel(tool, program, results);
|
||||
|
||||
String title = "Decompiler Search '%s'".formatted(getSearchText());
|
||||
String type = "Decompiler Search ";
|
||||
String subMenuName = "Search";
|
||||
TableComponentProvider<DecompilerSearchLocation> provider =
|
||||
tableService.showTable(title, type, model, subMenuName, null);
|
||||
|
||||
// The Decompiler does not support some of the table's basic actions, such as making
|
||||
// selections for a given row, so remove them.
|
||||
provider.removeAllActions();
|
||||
provider.installRemoveItemsAction();
|
||||
|
||||
GhidraThreadedTablePanel<DecompilerSearchLocation> panel = provider.getThreadedTablePanel();
|
||||
GhidraTable table = panel.getTable();
|
||||
|
||||
// add row listener to go to the field for that row
|
||||
ListSelectionModel selectionModel = table.getSelectionModel();
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
selectionModel.addListSelectionListener(lse -> {
|
||||
if (lse.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int row = table.getSelectedRow();
|
||||
if (row == -1) {
|
||||
searcher.highlightSearchResults(null);
|
||||
return;
|
||||
}
|
||||
|
||||
DecompilerSearchLocation location = model.getRowObject(row);
|
||||
|
||||
notifySearchHit(location);
|
||||
});
|
||||
|
||||
// add listener to table closed to clear highlights
|
||||
provider.setClosedCallback(() -> decompilerPanel.setSearchResults(null));
|
||||
|
||||
// set the tab text to the short and descriptive search term
|
||||
provider.setTabText("'%s'".formatted(getSearchText()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dialogClosed() {
|
||||
// clear the search results when the dialog is closed
|
||||
decompilerPanel.setSearchResults(null);
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Inner Classes
|
||||
//=================================================================================================
|
||||
|
||||
private class DecompilerFindResultsModel
|
||||
extends GhidraProgramTableModel<DecompilerSearchLocation> {
|
||||
|
||||
private List<DecompilerSearchLocation> searchLocations;
|
||||
|
||||
DecompilerFindResultsModel(ServiceProvider sp, Program program,
|
||||
List<SearchLocation> searchLocations) {
|
||||
super("Decompiler Search All Results", sp, program, null);
|
||||
this.searchLocations = searchLocations.stream()
|
||||
.map(l -> (DecompilerSearchLocation) l)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<DecompilerSearchLocation> createTableColumnDescriptor() {
|
||||
|
||||
TableColumnDescriptor<DecompilerSearchLocation> descriptor =
|
||||
new TableColumnDescriptor<>();
|
||||
descriptor.addVisibleColumn(new LineNumberColumn(), 1, true);
|
||||
descriptor.addVisibleColumn(new ContextColumn());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<DecompilerSearchLocation> accumulator,
|
||||
TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
for (DecompilerSearchLocation location : searchLocations) {
|
||||
accumulator.add(location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramLocation getProgramLocation(int modelRow, int modelColumn) {
|
||||
return null; // This doesn't really make sense for this model
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramSelection getProgramSelection(int[] modelRows) {
|
||||
return new ProgramSelection(); // This doesn't really make sense for this model
|
||||
}
|
||||
|
||||
private class LineNumberColumn
|
||||
extends AbstractDynamicTableColumnStub<DecompilerSearchLocation, Integer> {
|
||||
|
||||
@Override
|
||||
public Integer getValue(DecompilerSearchLocation rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
FieldLocation fieldLocation = rowObject.getFieldLocation();
|
||||
return fieldLocation.getIndex().intValue() + 1; // +1 for 1-based lines
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Line";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 75;
|
||||
}
|
||||
}
|
||||
|
||||
private class ContextColumn
|
||||
extends AbstractDynamicTableColumnStub<DecompilerSearchLocation, String> {
|
||||
|
||||
@Override
|
||||
public String getValue(DecompilerSearchLocation rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getTextLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Context";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ import ghidra.app.decompiler.*;
|
|||
import ghidra.app.decompiler.component.hover.DecompilerHoverService;
|
||||
import ghidra.app.decompiler.component.margin.*;
|
||||
import ghidra.app.plugin.core.decompile.DecompilerClipboardProvider;
|
||||
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
|
||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
|
||||
import ghidra.app.util.viewer.util.ScrollpaneAlignedHorizontalLayout;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
@ -80,7 +80,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
|
||||
private FieldHighlightFactory hlFactory;
|
||||
private ClangHighlightController highlightController;
|
||||
private Map<String, ClangDecompilerHighlighter> highlightersById = new HashMap<>();
|
||||
private Map<String, DecompilerHighlighter> highlightersById = new HashMap<>();
|
||||
private PendingHighlightUpdate pendingHighlightUpdate;
|
||||
private SwingUpdateManager highlighCursorUpdater = new SwingUpdateManager(() -> {
|
||||
if (pendingHighlightUpdate != null) {
|
||||
|
@ -89,6 +89,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
}
|
||||
});
|
||||
|
||||
private ActiveMiddleMouse activeMiddleMouse;
|
||||
private int middleMouseHighlightButton;
|
||||
private Color middleMouseHighlightColor;
|
||||
private Color currentVariableHighlightColor;
|
||||
|
@ -194,7 +195,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
return highlightController.getHighlighterHighlights(highligter);
|
||||
}
|
||||
|
||||
private Set<ClangDecompilerHighlighter> getSecondaryHighlihgtersByFunction(Function function) {
|
||||
public TokenHighlights getMiddleMouseHighlights() {
|
||||
if (activeMiddleMouse != null) {
|
||||
return activeMiddleMouse.getHighlights();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Set<DecompilerHighlighter> getSecondaryHighlihgtersByFunction(Function function) {
|
||||
return highlightController.getSecondaryHighlighters(function);
|
||||
}
|
||||
|
||||
|
@ -212,31 +220,50 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
}
|
||||
|
||||
public void addSecondaryHighlight(ClangToken token) {
|
||||
ColorProvider cp = highlightController.getRandomColorProvider();
|
||||
ColorProvider cp = highlightController.getGeneratedColorProvider();
|
||||
addSecondaryHighlight(token.getText(), cp);
|
||||
}
|
||||
|
||||
public void addSecondaryHighlight(ClangToken token, Color color) {
|
||||
ColorProvider cp = t -> color;
|
||||
ColorProvider cp = new DefaultColorProvider("User Secondary Highlight", color);
|
||||
addSecondaryHighlight(token.getText(), cp);
|
||||
}
|
||||
|
||||
private void addSecondaryHighlight(String tokenText, ColorProvider colorProvider) {
|
||||
NameTokenMatcher matcher = new NameTokenMatcher(tokenText, colorProvider);
|
||||
ClangDecompilerHighlighter highlighter = createHighlighter(matcher);
|
||||
DecompilerHighlighter highlighter = createHighlighter(matcher);
|
||||
applySecondaryHighlights(highlighter);
|
||||
}
|
||||
|
||||
private void applySecondaryHighlights(ClangDecompilerHighlighter highlighter) {
|
||||
private void applySecondaryHighlights(DecompilerHighlighter highlighter) {
|
||||
Function function = decompileData.getFunction();
|
||||
highlightController.addSecondaryHighlighter(function, highlighter);
|
||||
highlighter.applyHighlights();
|
||||
}
|
||||
|
||||
private void togglePrimaryHighlight(FieldLocation location, Field field, Color highlightColor) {
|
||||
private void toggleMiddleMouseHighlight(FieldLocation location, Field field) {
|
||||
ClangToken token = ((ClangTextField) field).getToken(location);
|
||||
Supplier<List<ClangToken>> lazyTokens = () -> findTokensByName(token.getText());
|
||||
highlightController.togglePrimaryHighlights(middleMouseHighlightColor, lazyTokens);
|
||||
ColorProvider cp = new MiddleMouseColorProvider();
|
||||
NameTokenMatcher matcher = new NameTokenMatcher(token.getText(), cp);
|
||||
|
||||
ActiveMiddleMouse previousMiddleMouse = activeMiddleMouse;
|
||||
activeMiddleMouse = null;
|
||||
|
||||
if (previousMiddleMouse != null) {
|
||||
|
||||
// middle mousing always clears the last middle-mouse highlight
|
||||
previousMiddleMouse.clear();
|
||||
|
||||
if (previousMiddleMouse.matches(token)) {
|
||||
// middle mousing on the same token clears, but does not create a new highlight
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DecompilerHighlighter newMiddleMouseHighlighter = createHighlighter(matcher);
|
||||
ActiveMiddleMouse newMiddleMouse = new ActiveMiddleMouse(token, newMiddleMouseHighlighter);
|
||||
newMiddleMouse.apply();
|
||||
activeMiddleMouse = newMiddleMouse;
|
||||
}
|
||||
|
||||
void addHighlighterHighlights(ClangDecompilerHighlighter highlighter,
|
||||
|
@ -248,14 +275,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
highlightController.removeHighlighterHighlights(highlighter);
|
||||
}
|
||||
|
||||
public ClangDecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm) {
|
||||
public DecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm) {
|
||||
UUID uuId = UUID.randomUUID();
|
||||
String id = uuId.toString();
|
||||
return createHighlighter(id, tm);
|
||||
}
|
||||
|
||||
public ClangDecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm) {
|
||||
ClangDecompilerHighlighter currentHighlighter = highlightersById.get(id);
|
||||
public DecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm) {
|
||||
DecompilerHighlighter currentHighlighter = highlightersById.get(id);
|
||||
if (currentHighlighter != null) {
|
||||
currentHighlighter.dispose();
|
||||
}
|
||||
|
@ -271,7 +298,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
}
|
||||
|
||||
void removeHighlighter(String id) {
|
||||
ClangDecompilerHighlighter highlighter = highlightersById.remove(id);
|
||||
DecompilerHighlighter highlighter = highlightersById.remove(id);
|
||||
highlightController.removeHighlighter(highlighter);
|
||||
}
|
||||
|
||||
|
@ -339,11 +366,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
|
||||
private void cloneGlobalHighlighters(DecompilerPanel sourcePanel) {
|
||||
|
||||
Set<ClangDecompilerHighlighter> globalHighlighters =
|
||||
Set<DecompilerHighlighter> globalHighlighters =
|
||||
sourcePanel.highlightController.getGlobalHighlighters();
|
||||
for (ClangDecompilerHighlighter otherHighlighter : globalHighlighters) {
|
||||
for (DecompilerHighlighter otherHighlighter : globalHighlighters) {
|
||||
|
||||
ClangDecompilerHighlighter newHighlighter = otherHighlighter.clone(this);
|
||||
if (!(otherHighlighter instanceof ClangDecompilerHighlighter clangHighlighter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DecompilerHighlighter newHighlighter = clangHighlighter.clone(this);
|
||||
highlightersById.put(newHighlighter.getId(), newHighlighter);
|
||||
|
||||
TokenHighlights otherHighlighterTokens =
|
||||
|
@ -374,15 +405,20 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
// clone will match the cloned decompiler.
|
||||
//
|
||||
Function function = decompileData.getFunction();
|
||||
Set<ClangDecompilerHighlighter> secondaryHighlighters =
|
||||
Set<DecompilerHighlighter> secondaryHighlighters =
|
||||
sourcePanel.getSecondaryHighlihgtersByFunction(function);
|
||||
|
||||
//
|
||||
// We do NOT clone the secondary highlighters. This allows the user the remove them
|
||||
// from the primary provider without effecting the cloned provider and vice versa.
|
||||
//
|
||||
for (ClangDecompilerHighlighter highlighter : secondaryHighlighters) {
|
||||
ClangDecompilerHighlighter newHighlighter = highlighter.copy(this);
|
||||
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
|
||||
|
||||
if (!(highlighter instanceof ClangDecompilerHighlighter clangHighlighter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DecompilerHighlighter newHighlighter = clangHighlighter.copy(this);
|
||||
highlightersById.put(newHighlighter.getId(), newHighlighter);
|
||||
applySecondaryHighlights(newHighlighter);
|
||||
}
|
||||
|
@ -455,9 +491,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
return;
|
||||
}
|
||||
|
||||
Set<ClangDecompilerHighlighter> globalHighlighters =
|
||||
highlightController.getGlobalHighlighters();
|
||||
for (ClangDecompilerHighlighter highlighter : globalHighlighters) {
|
||||
Set<DecompilerHighlighter> globalHighlighters = highlightController.getGlobalHighlighters();
|
||||
for (DecompilerHighlighter highlighter : globalHighlighters) {
|
||||
highlighter.clearHighlights();
|
||||
highlighter.applyHighlights();
|
||||
}
|
||||
|
@ -470,9 +505,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
return;
|
||||
}
|
||||
|
||||
Set<ClangDecompilerHighlighter> secondaryHighlighters =
|
||||
Set<DecompilerHighlighter> secondaryHighlighters =
|
||||
getSecondaryHighlihgtersByFunction(function);
|
||||
for (ClangDecompilerHighlighter highlighter : secondaryHighlighters) {
|
||||
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
|
||||
highlighter.clearHighlights();
|
||||
highlighter.applyHighlights();
|
||||
}
|
||||
|
@ -719,7 +754,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
}
|
||||
|
||||
if (buttonState == middleMouseHighlightButton && clickCount == 1) {
|
||||
togglePrimaryHighlight(location, field, middleMouseHighlightColor);
|
||||
toggleMiddleMouseHighlight(location, field);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -770,19 +805,6 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
controller.goToFunction(function, newWindow);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO no idea what this is supposed to be handling...someone doc this please
|
||||
// String labelName = functionToken.getText();
|
||||
// if (labelName.startsWith("func_0x")) {
|
||||
// try {
|
||||
// Address addr =
|
||||
// decompileData.getFunction().getEntryPoint().getAddress(labelName.substring(7));
|
||||
// controller.goToAddress(addr, newWindow);
|
||||
// }
|
||||
// catch (AddressFormatException e) {
|
||||
// controller.goToLabel(labelName, newWindow);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private void tryGoToLabel(ClangLabelToken token, boolean newWindow) {
|
||||
|
@ -966,33 +988,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
location.getIndex().intValue(), location.col);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Search Methods
|
||||
//==================================================================================================
|
||||
|
||||
public SearchLocation searchText(String text, FieldLocation startLocation,
|
||||
boolean forwardDirection) {
|
||||
return layoutController.findNextTokenForSearch(text, startLocation, forwardDirection);
|
||||
}
|
||||
|
||||
public SearchLocation searchTextRegex(String text, FieldLocation startLocation,
|
||||
boolean forwardDirection) {
|
||||
return layoutController.findNextTokenForSearchRegex(text, startLocation, forwardDirection);
|
||||
}
|
||||
|
||||
public void setSearchResults(SearchLocation searchLocation) {
|
||||
currentSearchLocation = searchLocation;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public FieldBasedSearchLocation getSearchResults() {
|
||||
return (FieldBasedSearchLocation) currentSearchLocation;
|
||||
public DecompilerSearchLocation getSearchResults() {
|
||||
return (DecompilerSearchLocation) currentSearchLocation;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// End Search Methods
|
||||
//==================================================================================================
|
||||
|
||||
public Color getCurrentVariableHighlightColor() {
|
||||
return currentVariableHighlightColor;
|
||||
}
|
||||
|
@ -1257,7 +1261,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
int highlightLine = cField.getLineNumber();
|
||||
|
||||
FieldLocation searchCursorLocation =
|
||||
((FieldBasedSearchLocation) currentSearchLocation).getFieldLocation();
|
||||
((DecompilerSearchLocation) currentSearchLocation).getFieldLocation();
|
||||
int searchLineNumber = searchCursorLocation.getIndex().intValue() + 1;
|
||||
if (highlightLine != searchLineNumber) {
|
||||
// only highlight the match on the actual line
|
||||
|
@ -1377,4 +1381,57 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MiddleMouseColorProvider implements ColorProvider {
|
||||
|
||||
@Override
|
||||
public Color getColor(ClangToken token) {
|
||||
return middleMouseHighlightColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Middle Mouse Color Provider " + middleMouseHighlightColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to track the current middle moused token.
|
||||
*/
|
||||
private class ActiveMiddleMouse {
|
||||
|
||||
private ClangToken token;
|
||||
private DecompilerHighlighter highlighter;
|
||||
|
||||
ActiveMiddleMouse(ClangToken token, DecompilerHighlighter highlighter) {
|
||||
this.token = token;
|
||||
this.highlighter = highlighter;
|
||||
}
|
||||
|
||||
TokenHighlights getHighlights() {
|
||||
return highlightController.getHighlighterHighlights(highlighter);
|
||||
}
|
||||
|
||||
DecompilerHighlighter getHighlighter() {
|
||||
return highlighter;
|
||||
}
|
||||
|
||||
boolean matches(ClangToken other) {
|
||||
return token.getText().equals(other.getText());
|
||||
}
|
||||
|
||||
void clear() {
|
||||
highlightController.removeHighlighter(highlighter);
|
||||
}
|
||||
|
||||
void apply() {
|
||||
applySecondaryHighlights(highlighter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Middle Mouse Token " + token;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,9 +33,10 @@ import ghidra.program.model.pcode.*;
|
|||
public class DecompilerUtils {
|
||||
|
||||
/**
|
||||
* Gaither decompiler options from tool and program. If tool is null or does not provide
|
||||
* Gather decompiler options from tool and program. If tool is null or does not provide
|
||||
* a {@link OptionsService} provider only options stored within the program will be consumed.
|
||||
* @param serviceProvider plugin tool or service provider providing access to {@link OptionsService}
|
||||
* @param serviceProvider plugin tool or service provider providing access to
|
||||
* {@link OptionsService}
|
||||
* @param program program
|
||||
* @return decompiler options
|
||||
*/
|
||||
|
@ -281,7 +282,7 @@ public class DecompilerUtils {
|
|||
* @param function decompiled function
|
||||
* @return true if {@code var} corresponds to existing auto {@code this} parameter, else false
|
||||
*/
|
||||
public static boolean testForAutoParameterThis(HighVariable var, Function function) {
|
||||
public static boolean isThisParameter(HighVariable var, Function function) {
|
||||
if (var instanceof HighParam) {
|
||||
int slot = ((HighParam) var).getSlot();
|
||||
Parameter parameter = function.getParameter(slot);
|
||||
|
@ -626,7 +627,7 @@ public class DecompilerUtils {
|
|||
|
||||
String destinationStart = label.getText() + ':';
|
||||
Address address = label.getMinAddress();
|
||||
List<ClangToken> tokens = DecompilerUtils.getTokens(root, address);
|
||||
List<ClangToken> tokens = getTokens(root, address);
|
||||
for (ClangToken token : tokens) {
|
||||
if (isGoToStatement(token)) {
|
||||
continue; // ignore any goto statements
|
||||
|
@ -791,7 +792,7 @@ public class DecompilerUtils {
|
|||
* @param group is the token hierarchy
|
||||
* @return the array of ClangLine objects
|
||||
*/
|
||||
public static ArrayList<ClangLine> toLines(ClangTokenGroup group) {
|
||||
public static List<ClangLine> toLines(ClangTokenGroup group) {
|
||||
|
||||
List<ClangNode> alltoks = new ArrayList<>();
|
||||
group.flatten(alltoks);
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/* ###
|
||||
* 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.decompiler.component;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
|
||||
/**
|
||||
* A color provider that returns a specific color.
|
||||
*/
|
||||
public class DefaultColorProvider implements ColorProvider {
|
||||
|
||||
private Color color;
|
||||
private String prefix;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param prefix a descriptive prefix used in the {@link #toString()} method
|
||||
* @param color the color
|
||||
*/
|
||||
DefaultColorProvider(String prefix, Color color) {
|
||||
this.prefix = prefix;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor(ClangToken token) {
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return prefix + ' ' + color;
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ public class LocationClangHighlightController extends ClangHighlightController {
|
|||
addPrimaryHighlight(tok, defaultHighlightColor);
|
||||
if (tok instanceof ClangSyntaxToken) {
|
||||
addPrimaryHighlightToTokensForParenthesis((ClangSyntaxToken) tok, defaultParenColor);
|
||||
addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor);
|
||||
addPrimaryHighlightToTokensForBrace((ClangSyntaxToken) tok, defaultParenColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.app.decompiler.component;
|
|||
|
||||
import java.awt.Color;
|
||||
|
||||
import generic.json.Json;
|
||||
import ghidra.app.decompiler.CTokenHighlightMatcher;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
|
||||
|
@ -40,4 +41,9 @@ class NameTokenMatcher implements CTokenHighlightMatcher {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,11 +35,6 @@ public class NullClangHighlightController extends ClangHighlightController {
|
|||
// stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrimaryHighlightedText() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) {
|
||||
// stub
|
||||
|
@ -51,7 +46,7 @@ public class NullClangHighlightController extends ClangHighlightController {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) {
|
||||
public void addPrimaryHighlightToTokensForBrace(ClangSyntaxToken token, Color highlightColor) {
|
||||
// stub
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import generic.theme.Gui;
|
|||
*/
|
||||
public class TokenHighlightColors {
|
||||
|
||||
private Map<String, Color> colorsByName = new HashMap<>();
|
||||
private Map<String, Color> colorsByText = new HashMap<>();
|
||||
private List<Color> recentColors = new ArrayList<>();
|
||||
|
||||
private Color generateColor() {
|
||||
|
@ -42,15 +42,27 @@ public class TokenHighlightColors {
|
|||
}
|
||||
|
||||
public Color getColor(String text) {
|
||||
return colorsByName.computeIfAbsent(text, t -> generateColor());
|
||||
return colorsByText.computeIfAbsent(text, t -> generateColor());
|
||||
}
|
||||
|
||||
public void setColor(String text, Color color) {
|
||||
colorsByName.put(text, color);
|
||||
colorsByText.put(text, color);
|
||||
recentColors.add(color);
|
||||
}
|
||||
|
||||
public List<Color> getRecentColors() {
|
||||
return recentColors;
|
||||
}
|
||||
|
||||
public String getAppliedColorsString() {
|
||||
if (colorsByText.isEmpty()) {
|
||||
return "No tokens highlighted";
|
||||
}
|
||||
return colorsByText.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getAppliedColorsString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/* ###
|
||||
* 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.decompiler.component;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.collections4.map.LazyMap;
|
||||
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.DecompilerHighlighter;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
/**
|
||||
* A class to manage and track Decompiler highlights created by the user via the UI or from a
|
||||
* script. This class manages secondary and global highlights. For a description of these terms,
|
||||
* see {@link ClangHighlightController}.
|
||||
* <p>
|
||||
* These highlights will remain until cleared explicitly by the user or a client API call.
|
||||
* Contrastingly, context highlights are cleared as the user moves the cursor around the Decompiler
|
||||
* display.
|
||||
*/
|
||||
public class UserHighlights {
|
||||
|
||||
private Map<Function, List<DecompilerHighlighter>> secondaryHighlightersByFunction =
|
||||
LazyMap.lazyMap(new HashMap<>(), f -> new ArrayList<>());
|
||||
|
||||
// store the secondary highlighters here in addition to the map below so that we may discern
|
||||
// between secondary highlights and highlight service highlights
|
||||
private Set<DecompilerHighlighter> secondaryHighlighters = new HashSet<>();
|
||||
|
||||
// all highlighters, including secondary and global highlight service highlighters
|
||||
private Map<DecompilerHighlighter, TokenHighlights> allHighlighterHighlights = new HashMap<>();
|
||||
|
||||
// color supplier for secondary highlights
|
||||
private TokenHighlightColors secondaryHighlightColors = new TokenHighlightColors();
|
||||
|
||||
Color getSecondaryColor(String text) {
|
||||
// Note: this call is used to generate colors for secondary highlighters that this API
|
||||
// creates. Client highlighters will create their own colors.
|
||||
return secondaryHighlightColors.getColor(text);
|
||||
}
|
||||
|
||||
String getAppliedColorsString() {
|
||||
return secondaryHighlightColors.getAppliedColorsString();
|
||||
}
|
||||
|
||||
boolean hasSecondaryHighlights(Function function) {
|
||||
return !secondaryHighlightersByFunction.get(function).isEmpty();
|
||||
}
|
||||
|
||||
Color getSecondaryHighlight(ClangToken token) {
|
||||
DecompilerHighlighter highlighter = getSecondaryHighlighter(token);
|
||||
if (highlighter != null) {
|
||||
TokenHighlights highlights = allHighlighterHighlights.get(highlighter);
|
||||
HighlightToken hlToken = highlights.get(token);
|
||||
return hlToken.getColor();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
TokenHighlightColors getSecondaryHighlightColors() {
|
||||
return secondaryHighlightColors;
|
||||
}
|
||||
|
||||
Set<DecompilerHighlighter> getSecondaryHighlighters(Function function) {
|
||||
return new HashSet<>(secondaryHighlightersByFunction.get(function));
|
||||
}
|
||||
|
||||
Set<DecompilerHighlighter> getGlobalHighlighters() {
|
||||
Set<DecompilerHighlighter> allHighlighters = allHighlighterHighlights.keySet();
|
||||
Set<DecompilerHighlighter> results = new HashSet<>(allHighlighters);
|
||||
results.removeAll(secondaryHighlighters);
|
||||
return results;
|
||||
}
|
||||
|
||||
List<DecompilerHighlighter> getSecondaryHighlightersByFunction(Function f) {
|
||||
return secondaryHighlightersByFunction.get(f);
|
||||
}
|
||||
|
||||
TokenHighlights getHighlights(DecompilerHighlighter highlighter) {
|
||||
return allHighlighterHighlights.get(highlighter);
|
||||
}
|
||||
|
||||
DecompilerHighlighter getSecondaryHighlighter(ClangToken token) {
|
||||
for (DecompilerHighlighter highlighter : secondaryHighlighters) {
|
||||
TokenHighlights highlights = allHighlighterHighlights.get(highlighter);
|
||||
HighlightToken hlToken = highlights.get(token);
|
||||
if (hlToken != null) {
|
||||
return highlighter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void addSecondaryHighlighter(Function function, DecompilerHighlighter highlighter) {
|
||||
|
||||
// Note: this highlighter has likely already been added the the this class, but has not
|
||||
// yet been bound to the given function.
|
||||
secondaryHighlightersByFunction.get(function).add(highlighter);
|
||||
secondaryHighlighters.add(highlighter);
|
||||
allHighlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
|
||||
}
|
||||
|
||||
// This adds the given highlighter. This is for global and secondary highlights. Secondary
|
||||
// highlights will be later registered to this class for the function they apply to.
|
||||
TokenHighlights add(DecompilerHighlighter highlighter) {
|
||||
allHighlighterHighlights.putIfAbsent(highlighter, new TokenHighlights());
|
||||
return allHighlighterHighlights.get(highlighter);
|
||||
}
|
||||
|
||||
void remove(DecompilerHighlighter highlighter) {
|
||||
allHighlighterHighlights.remove(highlighter);
|
||||
secondaryHighlighters.remove(highlighter);
|
||||
|
||||
Collection<List<DecompilerHighlighter>> lists = secondaryHighlightersByFunction.values();
|
||||
for (List<DecompilerHighlighter> highlighters : lists) {
|
||||
if (highlighters.remove(highlighter)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TokenHighlights get(DecompilerHighlighter highlighter) {
|
||||
return allHighlighterHighlights.get(highlighter);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
secondaryHighlighters.clear();
|
||||
secondaryHighlightersByFunction.clear();
|
||||
allHighlighterHighlights.clear();
|
||||
}
|
||||
}
|
|
@ -66,23 +66,24 @@ public class FillOutStructureCmd extends BackgroundCommand<Program> {
|
|||
throw new AssertionError("program does not match location");
|
||||
}
|
||||
|
||||
Function function =
|
||||
program.getFunctionManager().getFunctionContaining(location.getAddress());
|
||||
if (function == null) {
|
||||
setStatusMsg("Function not found at " + location.getAddress());
|
||||
return false;
|
||||
}
|
||||
|
||||
FillOutStructureHelper fillStructureHelper =
|
||||
new FillOutStructureHelper(program, monitor);
|
||||
DecompInterface decompInterface = fillStructureHelper.setUpDecompiler(decompileOptions);
|
||||
try {
|
||||
Function function =
|
||||
program.getFunctionManager().getFunctionContaining(location.getAddress());
|
||||
if (function == null) {
|
||||
setStatusMsg("Function not found at " + location.getAddress());
|
||||
return false;
|
||||
}
|
||||
|
||||
FillOutStructureHelper fillStructureHelper =
|
||||
new FillOutStructureHelper(program, decompileOptions, monitor);
|
||||
|
||||
HighVariable var = null;
|
||||
|
||||
if (!(location instanceof DecompilerLocation dloc)) {
|
||||
// if we don't have one, make one, and map variable to a varnode
|
||||
Address storageAddr = computeStorageAddress(function);
|
||||
var = fillStructureHelper.computeHighVariable(storageAddr, function);
|
||||
var =
|
||||
fillStructureHelper.computeHighVariable(storageAddr, function, decompInterface);
|
||||
}
|
||||
else {
|
||||
|
||||
|
@ -108,7 +109,8 @@ public class FillOutStructureCmd extends BackgroundCommand<Program> {
|
|||
}
|
||||
}
|
||||
|
||||
Structure structDT = fillStructureHelper.processStructure(var, function, false, true);
|
||||
Structure structDT =
|
||||
fillStructureHelper.processStructure(var, function, false, true, decompInterface);
|
||||
if (structDT == null) {
|
||||
setStatusMsg("Failed to fill-out structure");
|
||||
return false;
|
||||
|
@ -120,7 +122,7 @@ public class FillOutStructureCmd extends BackgroundCommand<Program> {
|
|||
pointerDT = program.getDataTypeManager()
|
||||
.addDataType(pointerDT, DataTypeConflictHandler.DEFAULT_HANDLER);
|
||||
|
||||
boolean isThisParam = DecompilerUtils.testForAutoParameterThis(var, function);
|
||||
boolean isThisParam = DecompilerUtils.isThisParameter(var, function);
|
||||
if (!isThisParam) {
|
||||
commitVariable(var, pointerDT, isThisParam);
|
||||
}
|
||||
|
@ -131,6 +133,9 @@ public class FillOutStructureCmd extends BackgroundCommand<Program> {
|
|||
Msg.showError(this, null, "Auto Create Structure Failed",
|
||||
"Failed to create Structure variable", e);
|
||||
}
|
||||
finally {
|
||||
decompInterface.dispose();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import java.util.Map.Entry;
|
|||
import ghidra.app.cmd.label.RenameLabelCmd;
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.decompiler.component.DecompilerUtils;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.data.*;
|
||||
|
@ -35,11 +34,16 @@ import ghidra.util.exception.InvalidInputException;
|
|||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Automatically creates a structure definition based on the references found by the decompiler.
|
||||
*
|
||||
* If the parameter is already a structure pointer, any new references found will be added
|
||||
* to the structure, even if the structure must grow.
|
||||
* Automatically create a Structure data-type based on references found by the decompiler to a
|
||||
* root parameter or other variable.
|
||||
*
|
||||
* If the parameter is already a Structure pointer, any new references found can optionally be added
|
||||
* to the existing Structure data-type.
|
||||
* {@link #processStructure(HighVariable, Function, boolean, boolean, DecompInterface)} is the primary
|
||||
* entry point to the helper, which computes the new or updated Structure based on an existing
|
||||
* decompiled function. Decompilation, if not provided externally, can be performed by calling
|
||||
* {@link #computeHighVariable(Address, Function, DecompInterface)}. A decompiler process,
|
||||
* if not provided externally, can be started by calling {@link #setUpDecompiler(DecompileOptions)}.
|
||||
*/
|
||||
public class FillOutStructureHelper {
|
||||
|
||||
|
@ -61,7 +65,6 @@ public class FillOutStructureHelper {
|
|||
|
||||
private Program currentProgram;
|
||||
private TaskMonitor monitor;
|
||||
private DecompileOptions decompileOptions;
|
||||
|
||||
private static final int maxCallDepth = 1;
|
||||
|
||||
|
@ -75,36 +78,35 @@ public class FillOutStructureHelper {
|
|||
* Constructor.
|
||||
*
|
||||
* @param program the current program
|
||||
* @param decompileOptions decompiler options
|
||||
* (see {@link DecompilerUtils#getDecompileOptions(ServiceProvider, Program)})
|
||||
* @param monitor task monitor
|
||||
*/
|
||||
public FillOutStructureHelper(Program program, DecompileOptions decompileOptions,
|
||||
TaskMonitor monitor) {
|
||||
public FillOutStructureHelper(Program program, TaskMonitor monitor) {
|
||||
this.currentProgram = program;
|
||||
this.decompileOptions = decompileOptions;
|
||||
this.monitor = monitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create a structure data type for a variable in the given function.
|
||||
* Unlike the applyTo() action, this method will not modify the function, its variables,
|
||||
* or any existing data-types. A new structure is always created.
|
||||
* @param var a parameter, local variable, or global variable used in the given function
|
||||
* @param function the function to process
|
||||
* @param createNewStructure if true a new structure with a unique name will always be generated,
|
||||
* if false and variable corresponds to a structure pointer the existing structure will be
|
||||
* Create or update a Structure data-type given a function and a root pointer variable.
|
||||
* The function must already be decompiled, but if a decompiler interface is provided, this
|
||||
* method will recursively follow variable references into CALLs, possibly triggering additional
|
||||
* decompilation.
|
||||
* @param var is the pointer variable
|
||||
* @param function is the function to process
|
||||
* @param createNewStructure if true a new Structure with a unique name will always be generated,
|
||||
* if false and the variable corresponds to a Structure pointer, the existing Structure will be
|
||||
* updated instead.
|
||||
* @param createClassIfNeeded if true and variable corresponds to a <B>this</B> pointer without
|
||||
* an assigned Ghidra Class (i.e., {@code void * this}), the function will be assigned to a
|
||||
* new unique Ghidra Class namespace with a new identically named structure returned. If false,
|
||||
* a new uniquely structure will be created.
|
||||
* @return a filled-in structure or null if one could not be created
|
||||
* new unique Ghidra Class namespace with a new identically named Structure returned. If false,
|
||||
* a new unique Structure will be created.
|
||||
* @param decomplib is the (optional) decompiler interface, which can be used to recursively
|
||||
* decompile into CALLs.
|
||||
* @return a filled-in Structure or null if one could not be created
|
||||
*/
|
||||
public Structure processStructure(HighVariable var, Function function,
|
||||
boolean createNewStructure, boolean createClassIfNeeded) {
|
||||
boolean createNewStructure, boolean createClassIfNeeded, DecompInterface decomplib) {
|
||||
|
||||
if (var == null || var.getSymbol() == null || var.getOffset() >= 0) {
|
||||
if (var == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -120,7 +122,9 @@ public class FillOutStructureHelper {
|
|||
}
|
||||
|
||||
fillOutStructureDef(var);
|
||||
pushIntoCalls();
|
||||
if (decomplib != null) {
|
||||
pushIntoCalls(decomplib);
|
||||
}
|
||||
|
||||
long size = componentMap.getSize();
|
||||
if (size == 0) {
|
||||
|
@ -132,7 +136,7 @@ public class FillOutStructureHelper {
|
|||
}
|
||||
|
||||
if (structDT == null) {
|
||||
if (createClassIfNeeded && DecompilerUtils.testForAutoParameterThis(var, function)) {
|
||||
if (createClassIfNeeded && DecompilerUtils.isThisParameter(var, function)) {
|
||||
structDT = createUniqueClassNamespaceAndStructure(var, (int) size, function);
|
||||
}
|
||||
else {
|
||||
|
@ -168,7 +172,7 @@ public class FillOutStructureHelper {
|
|||
/**
|
||||
* Retrieve the component map that was generated when structure was created using decompiler
|
||||
* info. Results are not valid until
|
||||
* {@link #processStructure(HighVariable, Function, boolean, boolean)} is invoked.
|
||||
* {@link #processStructure(HighVariable, Function, boolean, boolean, DecompInterface)} is invoked.
|
||||
* @return componentMap
|
||||
*/
|
||||
public NoisyStructureBuilder getComponentMap() {
|
||||
|
@ -179,7 +183,7 @@ public class FillOutStructureHelper {
|
|||
* Retrieve the offset/pcodeOp pairs that are used to store data into the variable
|
||||
* used to fill-out structure.
|
||||
* Results are not valid until
|
||||
* {@link #processStructure(HighVariable, Function, boolean, boolean)} is invoked.
|
||||
* {@link #processStructure(HighVariable, Function, boolean, boolean, DecompInterface)} is invoked.
|
||||
* @return the pcodeOps doing the storing to the associated variable
|
||||
*/
|
||||
public List<OffsetPcodeOpPair> getStorePcodeOps() {
|
||||
|
@ -190,7 +194,7 @@ public class FillOutStructureHelper {
|
|||
* Retrieve the offset/pcodeOp pairs that are used to load data from the variable
|
||||
* used to fill-out structure.
|
||||
* Results are not valid until
|
||||
* {@link #processStructure(HighVariable, Function, boolean, boolean)} is invoked.
|
||||
* {@link #processStructure(HighVariable, Function, boolean, boolean, DecompInterface)} is invoked.
|
||||
* @return the pcodeOps doing the loading from the associated variable
|
||||
*/
|
||||
public List<OffsetPcodeOpPair> getLoadPcodeOps() {
|
||||
|
@ -237,8 +241,9 @@ public class FillOutStructureHelper {
|
|||
/**
|
||||
* Recursively visit calls that take the structure pointer as a parameter.
|
||||
* Add any new references to the offsetToDataTypeMap.
|
||||
* @param decomplib is the active interface for decompiling
|
||||
*/
|
||||
private void pushIntoCalls() {
|
||||
private void pushIntoCalls(DecompInterface decomplib) {
|
||||
AddressSet doneSet = new AddressSet();
|
||||
|
||||
while (addressToCallInputMap.size() > 0) {
|
||||
|
@ -256,7 +261,7 @@ public class FillOutStructureHelper {
|
|||
doneSet.addRange(addr, addr);
|
||||
Function func = currentProgram.getFunctionManager().getFunctionAt(addr);
|
||||
Address storageAddr = savedList.get(addr);
|
||||
HighVariable paramHighVar = computeHighVariable(storageAddr, func);
|
||||
HighVariable paramHighVar = computeHighVariable(storageAddr, func, decomplib);
|
||||
if (paramHighVar != null) {
|
||||
fillOutStructureDef(paramHighVar);
|
||||
}
|
||||
|
@ -268,77 +273,73 @@ public class FillOutStructureHelper {
|
|||
* Decompile a function and return the resulting HighVariable associated with a storage address
|
||||
* @param storageAddress the storage address of the variable
|
||||
* @param function is the function
|
||||
* @param decomplib is the active interface to use for decompiling
|
||||
* @return the corresponding HighVariable or null
|
||||
*/
|
||||
public HighVariable computeHighVariable(Address storageAddress, Function function) {
|
||||
public HighVariable computeHighVariable(Address storageAddress, Function function,
|
||||
DecompInterface decomplib) {
|
||||
if (storageAddress == null) {
|
||||
return null;
|
||||
}
|
||||
DecompInterface decomplib = setUpDecompiler();
|
||||
HighVariable highVar = null;
|
||||
|
||||
// call decompiler to get syntax tree
|
||||
try {
|
||||
if (!decomplib.openProgram(currentProgram)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DecompileResults results = decomplib.decompileFunction(function,
|
||||
decomplib.getOptions().getDefaultTimeout(), monitor);
|
||||
if (monitor.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HighFunction highFunc = results.getHighFunction();
|
||||
|
||||
// no decompile...
|
||||
if (highFunc == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// try to map the variable
|
||||
HighSymbol sym =
|
||||
highFunc.getMappedSymbol(storageAddress, function.getEntryPoint().subtractWrap(1L));
|
||||
if (sym == null) {
|
||||
sym = highFunc.getMappedSymbol(storageAddress, null);
|
||||
}
|
||||
if (sym == null) {
|
||||
sym = highFunc.getMappedSymbol(storageAddress, function.getEntryPoint());
|
||||
}
|
||||
if (sym == null) {
|
||||
sym = highFunc.getLocalSymbolMap()
|
||||
.findLocal(storageAddress, function.getEntryPoint().subtractWrap(1L));
|
||||
}
|
||||
if (sym == null) {
|
||||
sym = highFunc.getLocalSymbolMap().findLocal(storageAddress, null);
|
||||
}
|
||||
if (sym == null) {
|
||||
sym = highFunc.getLocalSymbolMap()
|
||||
.findLocal(storageAddress, function.getEntryPoint());
|
||||
}
|
||||
if (sym == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
highVar = sym.getHighVariable();
|
||||
}
|
||||
finally {
|
||||
decomplib.dispose();
|
||||
DecompileResults results = decomplib.decompileFunction(function,
|
||||
decomplib.getOptions().getDefaultTimeout(), monitor);
|
||||
if (monitor.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HighFunction highFunc = results.getHighFunction();
|
||||
|
||||
// no decompile...
|
||||
if (highFunc == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// try to map the variable
|
||||
HighSymbol sym =
|
||||
highFunc.getMappedSymbol(storageAddress, function.getEntryPoint().subtractWrap(1L));
|
||||
if (sym == null) {
|
||||
sym = highFunc.getMappedSymbol(storageAddress, null);
|
||||
}
|
||||
if (sym == null) {
|
||||
sym = highFunc.getMappedSymbol(storageAddress, function.getEntryPoint());
|
||||
}
|
||||
if (sym == null) {
|
||||
sym = highFunc.getLocalSymbolMap()
|
||||
.findLocal(storageAddress, function.getEntryPoint().subtractWrap(1L));
|
||||
}
|
||||
if (sym == null) {
|
||||
sym = highFunc.getLocalSymbolMap().findLocal(storageAddress, null);
|
||||
}
|
||||
if (sym == null) {
|
||||
sym = highFunc.getLocalSymbolMap()
|
||||
.findLocal(storageAddress, function.getEntryPoint());
|
||||
}
|
||||
if (sym == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
highVar = sym.getHighVariable();
|
||||
return highVar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a decompiler interface for recovering data-flow
|
||||
* Set up a decompiler interface and prepare for decompiling on the currentProgram.
|
||||
* The interface can be used to pass to computeHighVariable or to processStructure.
|
||||
* @param options are the options to pass to the decompiler
|
||||
* @return the decompiler interface
|
||||
*/
|
||||
private DecompInterface setUpDecompiler() {
|
||||
public DecompInterface setUpDecompiler(DecompileOptions options) {
|
||||
DecompInterface decomplib = new DecompInterface();
|
||||
decomplib.setOptions(decompileOptions);
|
||||
decomplib.setOptions(options);
|
||||
decomplib.toggleCCode(true);
|
||||
decomplib.toggleSyntaxTree(true);
|
||||
decomplib.setSimplificationStyle("decompile");
|
||||
if (!decomplib.openProgram(currentProgram)) {
|
||||
return null;
|
||||
}
|
||||
return decomplib;
|
||||
}
|
||||
|
||||
|
|
|
@ -1016,6 +1016,13 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
|
|||
new RemoveAllSecondaryHighlightsAction();
|
||||
setGroupInfo(removeAllSecondadryHighlightsAction, highlightGroup, subGroupPosition++);
|
||||
|
||||
PreviousHighlightedTokenAction previousHighlightedTokenAction =
|
||||
new PreviousHighlightedTokenAction();
|
||||
setGroupInfo(previousHighlightedTokenAction, highlightGroup, subGroupPosition++);
|
||||
|
||||
NextHighlightedTokenAction nextHighlightedTokenAction = new NextHighlightedTokenAction();
|
||||
setGroupInfo(nextHighlightedTokenAction, highlightGroup, subGroupPosition++);
|
||||
|
||||
String convertGroup = "7 - Convert Group";
|
||||
subGroupPosition = 0;
|
||||
RemoveEquateAction removeEquateAction = new RemoveEquateAction();
|
||||
|
@ -1122,6 +1129,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
|
|||
addLocalAction(setSecondaryHighlightColorChooserAction);
|
||||
addLocalAction(removeSecondaryHighlightAction);
|
||||
addLocalAction(removeAllSecondadryHighlightsAction);
|
||||
addLocalAction(nextHighlightedTokenAction);
|
||||
addLocalAction(previousHighlightedTokenAction);
|
||||
addLocalAction(convertBinaryAction);
|
||||
addLocalAction(convertDecAction);
|
||||
addLocalAction(convertFloatAction);
|
||||
|
@ -1165,7 +1174,14 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
|
|||
private void setGroupInfo(DockingAction action, String group, int subGroupPosition) {
|
||||
MenuData popupMenuData = action.getPopupMenuData();
|
||||
popupMenuData.setMenuGroup(group);
|
||||
popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition));
|
||||
|
||||
// Some groups have numbers reach double-digits. These will not compare correctly unless
|
||||
// padded. Ensure all string numbers are at least 2 digits.
|
||||
String numberString = Integer.toString(subGroupPosition);
|
||||
if (numberString.length() == 1) {
|
||||
numberString = '0' + numberString;
|
||||
}
|
||||
popupMenuData.setMenuSubGroup(numberString);
|
||||
}
|
||||
|
||||
private void graphServiceRemoved() {
|
||||
|
|
|
@ -19,21 +19,27 @@ import docking.widgets.CursorPosition;
|
|||
import docking.widgets.SearchLocation;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
|
||||
public class FieldBasedSearchLocation extends SearchLocation {
|
||||
public class DecompilerSearchLocation extends SearchLocation {
|
||||
|
||||
private final FieldLocation fieldLocation;
|
||||
private String textLine;
|
||||
|
||||
public FieldBasedSearchLocation(FieldLocation fieldLocation, int startIndexInclusive,
|
||||
int endIndexInclusive, String searchText, boolean forwardDirection) {
|
||||
public DecompilerSearchLocation(FieldLocation fieldLocation, int startIndexInclusive,
|
||||
int endIndexInclusive, String searchText, boolean forwardDirection, String textLine) {
|
||||
|
||||
super(startIndexInclusive, endIndexInclusive, searchText, forwardDirection);
|
||||
this.fieldLocation = fieldLocation;
|
||||
this.textLine = textLine;
|
||||
}
|
||||
|
||||
public FieldLocation getFieldLocation() {
|
||||
return fieldLocation;
|
||||
}
|
||||
|
||||
public String getTextLine() {
|
||||
return textLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorPosition getCursorPosition() {
|
||||
return new DecompilerCursorPosition(fieldLocation);
|
|
@ -15,14 +15,18 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.decompile.actions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.*;
|
||||
|
||||
import docking.widgets.*;
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import docking.widgets.fieldpanel.support.RowColLocation;
|
||||
import ghidra.app.decompiler.component.ClangTextField;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.UserSearchUtils;
|
||||
|
||||
/**
|
||||
* A {@link FindDialogSearcher} for searching the text of the decompiler window.
|
||||
|
@ -86,15 +90,14 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
|||
DecompilerCursorPosition decompilerCursorPosition = (DecompilerCursorPosition) position;
|
||||
FieldLocation startLocation =
|
||||
getNextSearchStartLocation(decompilerCursorPosition, searchForward);
|
||||
return useRegex ? decompilerPanel.searchTextRegex(text, startLocation, searchForward)
|
||||
: decompilerPanel.searchText(text, startLocation, searchForward);
|
||||
return doFind(text, startLocation, searchForward, useRegex);
|
||||
}
|
||||
|
||||
private FieldLocation getNextSearchStartLocation(
|
||||
DecompilerCursorPosition decompilerCursorPosition, boolean searchForward) {
|
||||
|
||||
FieldLocation startLocation = decompilerCursorPosition.getFieldLocation();
|
||||
FieldBasedSearchLocation currentSearchLocation = decompilerPanel.getSearchResults();
|
||||
DecompilerSearchLocation currentSearchLocation = decompilerPanel.getSearchResults();
|
||||
if (currentSearchLocation == null) {
|
||||
return startLocation; // nothing to do; no prior search hit
|
||||
}
|
||||
|
@ -139,4 +142,229 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
|||
|
||||
return startLocation;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Search Methods
|
||||
//=================================================================================================
|
||||
|
||||
@Override
|
||||
public List<SearchLocation> searchAll(String searchString, boolean isRegex) {
|
||||
|
||||
Pattern pattern = createPattern(searchString, isRegex);
|
||||
Function<String, SearchMatch> function = createForwardMatchFunction(pattern);
|
||||
FieldLocation start = new FieldLocation();
|
||||
|
||||
List<SearchLocation> results = new ArrayList<>();
|
||||
DecompilerSearchLocation searchLocation = findNext(function, searchString, start);
|
||||
while (searchLocation != null) {
|
||||
results.add(searchLocation);
|
||||
|
||||
FieldLocation last = searchLocation.getFieldLocation();
|
||||
|
||||
int line = last.getIndex().intValue();
|
||||
int field = 0; // there is only 1 field
|
||||
int row = 0; // there is only 1 row
|
||||
int col = last.getCol() + 1; // move over one char to handle sub-matches
|
||||
start = new FieldLocation(line, field, row, col);
|
||||
searchLocation = findNext(function, searchString, start);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private DecompilerSearchLocation doFind(String searchString, FieldLocation currentLocation,
|
||||
boolean forwardSearch, boolean isRegex) {
|
||||
|
||||
Pattern pattern = createPattern(searchString, isRegex);
|
||||
|
||||
if (forwardSearch) {
|
||||
Function<String, SearchMatch> function = createForwardMatchFunction(pattern);
|
||||
return findNext(function, searchString, currentLocation);
|
||||
}
|
||||
|
||||
Function<String, SearchMatch> reverse = createReverseMatchFunction(pattern);
|
||||
return findPrevious(reverse, searchString, currentLocation);
|
||||
}
|
||||
|
||||
private Pattern createPattern(String searchString, boolean isRegex) {
|
||||
|
||||
int options = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
|
||||
if (isRegex) {
|
||||
try {
|
||||
return Pattern.compile(searchString, options);
|
||||
}
|
||||
catch (PatternSyntaxException e) {
|
||||
Msg.showError(this, decompilerPanel, "Regular Expression Syntax Error",
|
||||
e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return UserSearchUtils.createPattern(searchString, false, options);
|
||||
}
|
||||
|
||||
private Function<String, SearchMatch> createForwardMatchFunction(Pattern pattern) {
|
||||
|
||||
return textLine -> {
|
||||
|
||||
Matcher matcher = pattern.matcher(textLine);
|
||||
if (matcher.find()) {
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
return new SearchMatch(start, end, textLine);
|
||||
}
|
||||
|
||||
return SearchMatch.NO_MATCH;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private Function<String, SearchMatch> createReverseMatchFunction(Pattern pattern) {
|
||||
|
||||
return textLine -> {
|
||||
|
||||
Matcher matcher = pattern.matcher(textLine);
|
||||
if (!matcher.find()) {
|
||||
return SearchMatch.NO_MATCH;
|
||||
}
|
||||
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
|
||||
// Since the matcher can only match from the start to end of line, we need to find all
|
||||
// matches and then take the last match
|
||||
|
||||
// Setting the region to one character past the previous match allows repeated matches
|
||||
// within a match. The default behavior of the matcher is to start the match after
|
||||
// the previous match found by find().
|
||||
matcher.region(start + 1, textLine.length());
|
||||
while (matcher.find()) {
|
||||
start = matcher.start();
|
||||
end = matcher.end();
|
||||
matcher.region(start + 1, textLine.length());
|
||||
}
|
||||
|
||||
return new SearchMatch(start, end, textLine);
|
||||
};
|
||||
}
|
||||
|
||||
private DecompilerSearchLocation findNext(Function<String, SearchMatch> matcher,
|
||||
String searchString, FieldLocation currentLocation) {
|
||||
|
||||
List<Field> fields = decompilerPanel.getFields();
|
||||
int line = currentLocation.getIndex().intValue();
|
||||
for (int i = line; i < fields.size(); i++) {
|
||||
ClangTextField field = (ClangTextField) fields.get(i);
|
||||
String partialLine = substring(field, (i == line) ? currentLocation : null, true);
|
||||
SearchMatch match = matcher.apply(partialLine);
|
||||
if (match == SearchMatch.NO_MATCH) {
|
||||
continue;
|
||||
}
|
||||
if (i == line) { // cursor is on this line
|
||||
//
|
||||
// The match start for all lines without the cursor will be relative to the start
|
||||
// of the line, which is 0. However, when searching on the row with the cursor,
|
||||
// the match start is relative to the cursor position. Update the start to
|
||||
// compensate for the difference between the start of the line and the cursor.
|
||||
//
|
||||
String fullLine = field.getText();
|
||||
int cursorOffset = fullLine.length() - partialLine.length();
|
||||
match.start += cursorOffset;
|
||||
match.end += cursorOffset;
|
||||
}
|
||||
|
||||
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation =
|
||||
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
|
||||
|
||||
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||
searchString, true, field.getText());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private DecompilerSearchLocation findPrevious(Function<String, SearchMatch> matcher,
|
||||
String searchString, FieldLocation currentLocation) {
|
||||
|
||||
List<Field> fields = decompilerPanel.getFields();
|
||||
int line = currentLocation.getIndex().intValue();
|
||||
for (int i = line; i >= 0; i--) {
|
||||
ClangTextField field = (ClangTextField) fields.get(i);
|
||||
String textLine = substring(field, (i == line) ? currentLocation : null, false);
|
||||
|
||||
SearchMatch match = matcher.apply(textLine);
|
||||
if (match != SearchMatch.NO_MATCH) {
|
||||
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation =
|
||||
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
|
||||
|
||||
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||
searchString, false, field.getText());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String substring(ClangTextField textField, FieldLocation location,
|
||||
boolean forwardSearch) {
|
||||
|
||||
if (location == null) { // the cursor location is not on this line; use all of the text
|
||||
return textField.getText();
|
||||
}
|
||||
|
||||
if (textField.getText().isEmpty()) { // the cursor is on blank line
|
||||
return "";
|
||||
}
|
||||
|
||||
String partialText = textField.getText();
|
||||
|
||||
if (forwardSearch) {
|
||||
|
||||
int nextCol = location.getCol();
|
||||
|
||||
// protects against the location column being out of range (this can happen if we're
|
||||
// searching forward and the cursor is past the last token)
|
||||
if (nextCol >= partialText.length()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// skip a character to start the next search; this prevents matching the previous match
|
||||
return partialText.substring(nextCol);
|
||||
}
|
||||
|
||||
// backwards search
|
||||
return partialText.substring(0, location.getCol());
|
||||
}
|
||||
|
||||
private FieldLineLocation getFieldIndexFromOffset(int screenOffset, ClangTextField textField) {
|
||||
|
||||
RowColLocation rowColLocation = textField.textOffsetToScreenLocation(screenOffset);
|
||||
|
||||
// we use 0 here because currently there is only one field, which is the entire line
|
||||
return new FieldLineLocation(0, rowColLocation.col());
|
||||
}
|
||||
|
||||
private static class SearchMatch {
|
||||
private static SearchMatch NO_MATCH = new SearchMatch(-1, -1, null);
|
||||
private int start;
|
||||
private int end;
|
||||
private String textLine;
|
||||
|
||||
SearchMatch(int start, int end, String textLine) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.textLine = textLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == NO_MATCH) {
|
||||
return "NO MATCH";
|
||||
}
|
||||
return "[start=" + start + ",end=" + end + "]: " + textLine;
|
||||
}
|
||||
}
|
||||
|
||||
private record FieldLineLocation(int fieldNumber, int column) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ public class DecompilerStructureVariableAction extends CreateStructureVariableAc
|
|||
HighVariable var = tokenAtCursor.getHighVariable();
|
||||
if (var != null && !(var instanceof HighConstant)) {
|
||||
dt = var.getDataType();
|
||||
isThisParam = DecompilerUtils.testForAutoParameterThis(var, function);
|
||||
isThisParam = DecompilerUtils.isThisParameter(var, function);
|
||||
}
|
||||
|
||||
if (dt == null || dt.getLength() > maxPointerSize) {
|
||||
|
|
|
@ -23,13 +23,14 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import docking.action.KeyBindingData;
|
||||
import docking.action.MenuData;
|
||||
import docking.widgets.FindDialog;
|
||||
import ghidra.app.decompiler.component.DecompilerFindDialog;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
public class FindAction extends AbstractDecompilerAction {
|
||||
private FindDialog findDialog;
|
||||
private DecompilerFindDialog findDialog;
|
||||
|
||||
public FindAction() {
|
||||
super("Find");
|
||||
|
@ -49,15 +50,7 @@ public class FindAction extends AbstractDecompilerAction {
|
|||
|
||||
protected FindDialog getFindDialog(DecompilerPanel decompilerPanel) {
|
||||
if (findDialog == null) {
|
||||
findDialog =
|
||||
new FindDialog("Decompiler Find Text", new DecompilerSearcher(decompilerPanel)) {
|
||||
@Override
|
||||
protected void dialogClosed() {
|
||||
// clear the search results when the dialog is closed
|
||||
decompilerPanel.setSearchResults(null);
|
||||
}
|
||||
};
|
||||
findDialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
||||
findDialog = new DecompilerFindDialog(decompilerPanel);
|
||||
}
|
||||
return findDialog;
|
||||
}
|
||||
|
|
|
@ -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.decompile.actions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.KeyBindingData;
|
||||
import docking.action.MenuData;
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.TokenIterator;
|
||||
import ghidra.app.decompiler.component.*;
|
||||
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
/**
|
||||
* An action to navigate to the next token highlighted by the user via the middle-mouse.
|
||||
*/
|
||||
public class NextHighlightedTokenAction extends AbstractDecompilerAction {
|
||||
|
||||
public NextHighlightedTokenAction() {
|
||||
super("Next Highlihted Token");
|
||||
|
||||
setPopupMenuData(new MenuData(new String[] { "Next Highlight" }, "Decompile"));
|
||||
setKeyBindingData(new KeyBindingData("Ctrl period"));
|
||||
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "GoToMiddleMouseHighlight"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
|
||||
if (!context.hasRealFunction()) {
|
||||
return false;
|
||||
}
|
||||
DecompilerPanel panel = context.getDecompilerPanel();
|
||||
TokenHighlights highlights = panel.getMiddleMouseHighlights();
|
||||
if (highlights != null) {
|
||||
return highlights.size() > 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decompilerActionPerformed(DecompilerActionContext context) {
|
||||
|
||||
DecompilerPanel panel = context.getDecompilerPanel();
|
||||
TokenHighlights highlights = panel.getMiddleMouseHighlights();
|
||||
ClangToken cursorToken = context.getTokenAtCursor();
|
||||
TokenIterator it = new TokenIterator(cursorToken, true);
|
||||
it.next(); // ignore the current token
|
||||
|
||||
if (goToNexToken(panel, it, highlights)) {
|
||||
return; // found another token in the current direction
|
||||
}
|
||||
|
||||
// this means there are no more occurrences in the current direction; wrap the search
|
||||
ClangToken firstToken = getFirstToken(panel);
|
||||
it = new TokenIterator(firstToken, true);
|
||||
goToNexToken(panel, it, highlights);
|
||||
}
|
||||
|
||||
private ClangToken getFirstToken(DecompilerPanel panel) {
|
||||
List<Field> fields = panel.getFields();
|
||||
Field line = fields.get(0);
|
||||
ClangTextField tf = (ClangTextField) line;
|
||||
return tf.getFirstToken();
|
||||
}
|
||||
|
||||
private boolean goToNexToken(DecompilerPanel panel, TokenIterator it,
|
||||
TokenHighlights highlights) {
|
||||
|
||||
while (it.hasNext()) {
|
||||
ClangToken nextToken = it.next();
|
||||
HighlightToken hlToken = highlights.get(nextToken);
|
||||
if (hlToken == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ClangToken token = hlToken.getToken();
|
||||
panel.goToToken(token);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/* ###
|
||||
* 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.decompile.actions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.KeyBindingData;
|
||||
import docking.action.MenuData;
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.TokenIterator;
|
||||
import ghidra.app.decompiler.component.*;
|
||||
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
/**
|
||||
* An action to navigate to the previous token highlighted by the user via the middle-mouse.
|
||||
*/
|
||||
public class PreviousHighlightedTokenAction extends AbstractDecompilerAction {
|
||||
|
||||
public PreviousHighlightedTokenAction() {
|
||||
super("Previous Highlighted Token");
|
||||
|
||||
setPopupMenuData(new MenuData(new String[] { "Previous Highlight" }, "Decompile"));
|
||||
setKeyBindingData(new KeyBindingData("Ctrl comma"));
|
||||
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "GoToMiddleMouseHighlight"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
|
||||
if (!context.hasRealFunction()) {
|
||||
return false;
|
||||
}
|
||||
DecompilerPanel panel = context.getDecompilerPanel();
|
||||
TokenHighlights highlights = panel.getMiddleMouseHighlights();
|
||||
if (highlights != null) {
|
||||
return highlights.size() > 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decompilerActionPerformed(DecompilerActionContext context) {
|
||||
|
||||
DecompilerPanel panel = context.getDecompilerPanel();
|
||||
TokenHighlights highlights = panel.getMiddleMouseHighlights();
|
||||
ClangToken cursorToken = context.getTokenAtCursor();
|
||||
TokenIterator it = new TokenIterator(cursorToken, false);
|
||||
it.next(); // ignore the current token
|
||||
|
||||
if (goToNexToken(panel, it, highlights)) {
|
||||
return; // found another token in the current direction
|
||||
}
|
||||
|
||||
// this means there are no more occurrences in the current direction; wrap the search
|
||||
ClangToken lastToken = getLastToken(panel);
|
||||
it = new TokenIterator(lastToken, false);
|
||||
goToNexToken(panel, it, highlights);
|
||||
}
|
||||
|
||||
private ClangToken getLastToken(DecompilerPanel panel) {
|
||||
List<Field> fields = panel.getFields();
|
||||
int lastLine = fields.size();
|
||||
Field line = fields.get(lastLine - 1);
|
||||
ClangTextField tf = (ClangTextField) line;
|
||||
return tf.getLastToken();
|
||||
}
|
||||
|
||||
private boolean goToNexToken(DecompilerPanel panel, TokenIterator it,
|
||||
TokenHighlights highlights) {
|
||||
|
||||
while (it.hasNext()) {
|
||||
ClangToken nextToken = it.next();
|
||||
HighlightToken hlToken = highlights.get(nextToken);
|
||||
if (hlToken == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ClangToken token = hlToken.getToken();
|
||||
panel.goToToken(token);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -70,4 +70,9 @@ public class SliceHighlightColorProvider implements ColorProvider {
|
|||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Slice Color Provider " + hlColor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ import ghidra.app.plugin.core.decompile.actions.*;
|
|||
import ghidra.app.util.AddEditDialog;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class DecompilerClangTest extends AbstractDecompilerTest {
|
||||
|
||||
|
@ -383,29 +384,32 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||
setDecompilerLocation(line, charPosition);
|
||||
|
||||
ClangToken token = getToken();
|
||||
String text = token.getText();
|
||||
assertEquals("_printf", text);
|
||||
String printfText = token.getText();
|
||||
assertEquals("_printf", printfText);
|
||||
|
||||
highlight();
|
||||
highlight(); // "printf" is secondary highlighted
|
||||
|
||||
// 5:30 "a->name"
|
||||
line = 5;
|
||||
charPosition = 38;
|
||||
setDecompilerLocation(line, charPosition);
|
||||
ClangToken token2 = getToken();
|
||||
String text2 = token2.getText();
|
||||
assertEquals("name", text2);
|
||||
String nameText = token2.getText();
|
||||
assertEquals("name", nameText);
|
||||
|
||||
Color color2 = highlight();
|
||||
Color color2 = highlight(); // "name" is secondary highlighted
|
||||
|
||||
// 5:2 "_printf"
|
||||
line = 5;
|
||||
charPosition = 2;
|
||||
setDecompilerLocation(line, charPosition);
|
||||
removeSecondaryHighlight();
|
||||
|
||||
assertNoFieldsSecondaryHighlighted(text);
|
||||
assertAllFieldsHighlighted(text2, color2);
|
||||
Msg.debug(this, "test - remove");
|
||||
|
||||
removeSecondaryHighlight(); // remove "printf" highlight
|
||||
|
||||
assertNoFieldsSecondaryHighlighted(printfText);
|
||||
assertAllFieldsHighlighted(nameText, color2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -812,7 +816,106 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSecondaryHighlighting_MiddleMouseDoesNotClearSecondaryHighlight() {
|
||||
public void testSecondaryHighlighting_MiddleMouse_SecondaryHighlight() {
|
||||
|
||||
/*
|
||||
|
||||
The middle mouse is a secondary highlight so that it persists as the user clicks around.
|
||||
|
||||
Middle mousing on an already middle moused highlight should clear the highlight. Middle
|
||||
mousing on a new token should clear the original highlight and highlight the new token.
|
||||
|
||||
Decomp of '_call_structure_A':
|
||||
|
||||
1|
|
||||
2| void _call_structure_A(A *a)
|
||||
3|
|
||||
4| {
|
||||
5| _printf("call_structure_A: %s\n",a->name);
|
||||
6| _printf("call_structure_A: %s\n",(a->b).name);
|
||||
7| _printf("call_structure_A: %s\n",(a->b).c.name);
|
||||
8| _printf("call_structure_A: %s\n",(a->b).c.d.name);
|
||||
9| _printf("call_structure_A: %s\n",(a->b).c.d.e.name);
|
||||
10| _call_structure_B(&a->b);
|
||||
11| return;
|
||||
12| }
|
||||
|
||||
*/
|
||||
|
||||
decompile("100000d60"); // '_call_structure_A'
|
||||
|
||||
// 5:2 "_printf"
|
||||
int line = 5;
|
||||
int charPosition = 2;
|
||||
setDecompilerLocation(line, charPosition);
|
||||
|
||||
ClangToken token = getToken();
|
||||
String tokenText = token.getText();
|
||||
assertEquals("_printf", tokenText);
|
||||
|
||||
middleMouse();
|
||||
assertCombinedHighlightColor(token);
|
||||
|
||||
middleMouse();
|
||||
assertNoFieldsSecondaryHighlighted(tokenText);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondaryHighlighting_MiddleMouse_SecondaryHighlight_NewToken() {
|
||||
|
||||
/*
|
||||
|
||||
The middle mouse is a secondary highlight so that it persists as the user clicks around.
|
||||
|
||||
Middle mousing on an already middle moused highlight should clear the highlight. Middle
|
||||
mousing on a new token should clear the original highlight and highlight the new token.
|
||||
|
||||
Decomp of '_call_structure_A':
|
||||
|
||||
1|
|
||||
2| void _call_structure_A(A *a)
|
||||
3|
|
||||
4| {
|
||||
5| _printf("call_structure_A: %s\n",a->name);
|
||||
6| _printf("call_structure_A: %s\n",(a->b).name);
|
||||
7| _printf("call_structure_A: %s\n",(a->b).c.name);
|
||||
8| _printf("call_structure_A: %s\n",(a->b).c.d.name);
|
||||
9| _printf("call_structure_A: %s\n",(a->b).c.d.e.name);
|
||||
10| _call_structure_B(&a->b);
|
||||
11| return;
|
||||
12| }
|
||||
|
||||
*/
|
||||
|
||||
decompile("100000d60"); // '_call_structure_A'
|
||||
|
||||
// 5:2 "_printf"
|
||||
int line = 5;
|
||||
int charPosition = 2;
|
||||
setDecompilerLocation(line, charPosition);
|
||||
|
||||
ClangToken token = getToken();
|
||||
String tokenText = token.getText();
|
||||
assertEquals("_printf", tokenText);
|
||||
|
||||
middleMouse();
|
||||
assertCombinedHighlightColor(token);
|
||||
|
||||
// 5:30 "a->name"
|
||||
line = 5;
|
||||
charPosition = 38;
|
||||
setDecompilerLocation(line, charPosition);
|
||||
ClangToken token2 = getToken();
|
||||
String text2 = token2.getText();
|
||||
assertEquals("name", text2);
|
||||
|
||||
middleMouse();
|
||||
assertNoFieldsSecondaryHighlighted(tokenText);
|
||||
assertCombinedHighlightColor(token2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondaryHighlighting_MiddleMouseDoesNotClearSecondaryHighlight_ExistingHighlight() {
|
||||
|
||||
/*
|
||||
|
||||
|
@ -850,7 +953,9 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||
assertCombinedHighlightColor(token);
|
||||
|
||||
middleMouse();
|
||||
assertAllFieldsHighlighted(tokenText, color);
|
||||
ClangToken cursorToken = getToken(provider);
|
||||
Predicate<ClangToken> ignores = t -> t == cursorToken;
|
||||
assertAllFieldsHighlighted(tokenText, color, ignores);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -17,21 +17,28 @@ package ghidra.app.plugin.core.decompile;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.widgets.FindDialog;
|
||||
import docking.widgets.dialogs.InputDialog;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import docking.widgets.table.GTable;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.component.DecompilerFindDialog;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
|
||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.ClassicSampleX86ProgramBuilder;
|
||||
import ghidra.util.table.GhidraProgramTableModel;
|
||||
import ghidra.util.table.GhidraThreadedTablePanel;
|
||||
|
||||
public class DecompilerFindDialogTest extends AbstractDecompilerTest {
|
||||
|
||||
private FindDialog findDialog;
|
||||
private DecompilerFindDialog findDialog;
|
||||
|
||||
@Override
|
||||
@After
|
||||
|
@ -243,10 +250,78 @@ public class DecompilerFindDialogTest extends AbstractDecompilerTest {
|
|||
assertSearchHit(line, column, length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchAll() {
|
||||
|
||||
/*
|
||||
|
||||
bool FUN_01002239(int param_1)
|
||||
|
||||
{
|
||||
undefined4 uVar1;
|
||||
int iVar2;
|
||||
undefined4 *puVar3;
|
||||
bool bVar4;
|
||||
undefined *puVar5;
|
||||
undefined2 local_210;
|
||||
undefined4 local_20e [129];
|
||||
int local_8;
|
||||
|
||||
local_210 = 0;
|
||||
puVar3 = local_20e;
|
||||
...
|
||||
...
|
||||
...
|
||||
*/
|
||||
|
||||
decompile("1002239");
|
||||
|
||||
String text = "puVar";
|
||||
showFind(text);
|
||||
|
||||
searchAll();
|
||||
|
||||
GTable table = getResultsTable();
|
||||
List<DecompilerSearchLocation> results = getResults(table);
|
||||
assertEquals(10, results.size());
|
||||
|
||||
// click some rows and verify the cursor location
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
clickAndVerify(i, table, results);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private void clickAndVerify(int row, GTable table, List<DecompilerSearchLocation> results) {
|
||||
|
||||
runSwing(() -> table.selectRow(row));
|
||||
DecompilerSearchLocation searchLocation = results.get(row);
|
||||
FieldLocation fieldLocation = searchLocation.getFieldLocation();
|
||||
ClangToken expectedToken = getToken(fieldLocation);
|
||||
ClangToken cursorToken = getToken();
|
||||
assertEquals(expectedToken, cursorToken);
|
||||
}
|
||||
|
||||
private GTable getResultsTable() {
|
||||
@SuppressWarnings("unchecked")
|
||||
TableComponentProvider<DecompilerSearchLocation> tableProvider =
|
||||
waitForComponentProvider(TableComponentProvider.class);
|
||||
GhidraThreadedTablePanel<DecompilerSearchLocation> panel =
|
||||
tableProvider.getThreadedTablePanel();
|
||||
return panel.getTable();
|
||||
}
|
||||
|
||||
private List<DecompilerSearchLocation> getResults(GTable table) {
|
||||
@SuppressWarnings("unchecked")
|
||||
GhidraProgramTableModel<DecompilerSearchLocation> model =
|
||||
(GhidraProgramTableModel<DecompilerSearchLocation>) table.getModel();
|
||||
waitForTableModel(model);
|
||||
return model.getModelData();
|
||||
}
|
||||
|
||||
private void next() {
|
||||
runSwing(() -> findDialog.next());
|
||||
}
|
||||
|
@ -255,13 +330,17 @@ public class DecompilerFindDialogTest extends AbstractDecompilerTest {
|
|||
runSwing(() -> findDialog.previous());
|
||||
}
|
||||
|
||||
private void searchAll() {
|
||||
pressButtonByText(findDialog, "Search All");
|
||||
}
|
||||
|
||||
private void assertSearchHit(int line, int column, int length) {
|
||||
|
||||
waitForSwing();
|
||||
assertCurrentLocation(line, column);
|
||||
|
||||
DecompilerPanel panel = getDecompilerPanel();
|
||||
FieldBasedSearchLocation searchResults = panel.getSearchResults();
|
||||
DecompilerSearchLocation searchResults = panel.getSearchResults();
|
||||
FieldLocation searchCursorLocation = searchResults.getFieldLocation();
|
||||
int searchLineNumber = searchCursorLocation.getIndex().intValue() + 1;
|
||||
assertEquals("Search result is on the wrong line", line, searchLineNumber);
|
||||
|
@ -285,7 +364,7 @@ public class DecompilerFindDialogTest extends AbstractDecompilerTest {
|
|||
private void showFind(String text) {
|
||||
DockingActionIf findAction = getAction(decompiler, "Find");
|
||||
performAction(findAction, provider, true);
|
||||
findDialog = waitForDialogComponent(FindDialog.class);
|
||||
findDialog = waitForDialogComponent(DecompilerFindDialog.class);
|
||||
runSwing(() -> findDialog.setSearchText(text));
|
||||
}
|
||||
|
||||
|
|
|
@ -162,22 +162,10 @@ public class MDMang {
|
|||
* @throws MDException upon parsing error
|
||||
*/
|
||||
public MDDataType demangleType(boolean errorOnRemainingChars) throws MDException {
|
||||
if (mangled == null) {
|
||||
throw new MDException("MDMang: Mangled string is null.");
|
||||
}
|
||||
initState();
|
||||
pushContext();
|
||||
if (peek() != '.') {
|
||||
throw new MDException("MDMang: Mangled string is not that of a type.");
|
||||
}
|
||||
increment();
|
||||
MDDataType mdDataType = MDDataTypeParser.parseDataType(this, false);
|
||||
MDDataType mdDataType = MDDataTypeParser.determineAndParseDataType(this, false);
|
||||
item = mdDataType;
|
||||
if (mdDataType != null) {
|
||||
mdDataType.parse();
|
||||
}
|
||||
int numCharsRemaining = getNumCharsRemaining();
|
||||
popContext();
|
||||
if (errorOnRemainingChars && (numCharsRemaining > 0)) {
|
||||
throw new MDException(
|
||||
"MDMang: characters remain after demangling: " + numCharsRemaining + ".");
|
||||
|
|
|
@ -17,6 +17,7 @@ package mdemangler.datatype;
|
|||
|
||||
import mdemangler.MDException;
|
||||
import mdemangler.MDMang;
|
||||
import mdemangler.MDMang.ProcessingMode;
|
||||
import mdemangler.datatype.complex.*;
|
||||
import mdemangler.datatype.extended.*;
|
||||
import mdemangler.datatype.modifier.*;
|
||||
|
@ -31,6 +32,52 @@ import mdemangler.object.MDObjectCPP;
|
|||
* by calling the appropriate parser at the appropriate place in the code.
|
||||
*/
|
||||
public class MDDataTypeParser {
|
||||
/**
|
||||
* This method is only to be used by MDMang itself for the highest level type parsing where
|
||||
* there is not already a multi-retry. This method checks for the '.' starting character,
|
||||
* determines the type by calling the {@link #parseDataType(MDMang, boolean)}, parses the type,
|
||||
* and does the multi-mode retry if there is an exception on the first pass as
|
||||
* MDMangObjectParser does for generic mangled objects
|
||||
* @param dmang - the MDMang driver
|
||||
* @param isHighest - boolean indicating whether something else modifies or names the data
|
||||
* type to be parsed, which impacts when certain overloaded CV modifiers can be applied.
|
||||
* @return - a type derived from MDDataType
|
||||
* @throws MDException on parsing error
|
||||
*/
|
||||
public static MDDataType determineAndParseDataType(MDMang dmang, boolean isHighest)
|
||||
throws MDException {
|
||||
|
||||
MDDataType dt = null;
|
||||
if (dmang.peek() != '.') {
|
||||
throw new MDException("MDMang: Mangled string is not that of a type.");
|
||||
}
|
||||
|
||||
dmang.setProcessingMode(ProcessingMode.DEFAULT_STANDARD);
|
||||
try {
|
||||
dmang.pushContext();
|
||||
dmang.increment(); // skip the '.'
|
||||
dt = parseDataType(dmang, isHighest);
|
||||
dt.parse();
|
||||
dmang.popContext();
|
||||
}
|
||||
catch (MDException e1) {
|
||||
dmang.resetState();
|
||||
dmang.setProcessingMode(ProcessingMode.LLVM);
|
||||
try {
|
||||
dmang.pushContext();
|
||||
dmang.increment(); // skip the '.'
|
||||
dt = parseDataType(dmang, isHighest);
|
||||
dt.parse();
|
||||
dmang.popContext();
|
||||
}
|
||||
catch (MDException e2) {
|
||||
throw new MDException(
|
||||
"Reason1: " + e1.getMessage().trim() + "; Reason2: " + e2.getMessage().trim());
|
||||
}
|
||||
}
|
||||
return dt;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses all data types. Specifically, it parses void, data indirect types,
|
||||
* function indirect types, and all types parsed by parsePrimaryDataType().
|
||||
|
|
|
@ -14992,6 +14992,19 @@ public class MDMangBaseTest extends AbstractGenericTest {
|
|||
demangleAndTest();
|
||||
}
|
||||
|
||||
// Note the suffix seems like an already or partially demangled name. Note that name0
|
||||
// seems like a plain tag (no closing '@'), there is a regular namespace delimiter "::",
|
||||
// the suffix "3@" is almost like a backreference tag with the '@' closing the full
|
||||
// qualified name... except... we've seen numbers that are beyond the backref range as
|
||||
// here, but also have seen numbers like 18.
|
||||
@Ignore
|
||||
public void testMangledTypeWithNamespaceSuffix() throws Exception {
|
||||
mangled = ".?AT<unnamed-tag>@name0::3@";
|
||||
msTruth = "";
|
||||
mdTruth = msTruth;
|
||||
demangleAndTest();
|
||||
}
|
||||
|
||||
//=====================
|
||||
|
||||
@Test
|
||||
|
|
|
@ -80,4 +80,18 @@ public class MDMangExtraTest extends AbstractGenericTest {
|
|||
assertEquals("k::j::i", qualifications.get(2).toString());
|
||||
}
|
||||
|
||||
// Need to test the demangleType() method to make sure it does the retry with LLVM mode
|
||||
@Test
|
||||
public void testDemangleTypeWithRetry() throws Exception {
|
||||
// Test string taken from MDMangBaseTest
|
||||
String mangled = ".?AW4name0@?name1@name2@@YAX_N@Z@";
|
||||
String truth = "enum `void __cdecl name2::name1(bool)'::name0";
|
||||
|
||||
MDMangGhidra demangler = new MDMangGhidra();
|
||||
MDParsableItem item = demangler.demangleType(mangled, true); // note demangleType()
|
||||
|
||||
String demangled = item.toString();
|
||||
assertEquals(truth, demangled);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -216,7 +216,7 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel<IconValue, Obj
|
|||
private class ThemeIconRenderer extends AbstractGColumnRenderer<ResolvedIcon> {
|
||||
|
||||
public ThemeIconRenderer() {
|
||||
setFont(Gui.getFont("font.monospaced"));
|
||||
setBaseFontId("font.monospaced");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -32,7 +32,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
|||
|
||||
private GhidraComboBox<String> comboBox;
|
||||
|
||||
private FindDialogSearcher searcher;
|
||||
protected FindDialogSearcher searcher;
|
||||
private JButton nextButton;
|
||||
private JButton previousButton;
|
||||
private JRadioButton stringRadioButton;
|
||||
|
@ -140,6 +140,10 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
|||
doSearch(false);
|
||||
}
|
||||
|
||||
protected boolean useRegex() {
|
||||
return regexRadioButton.isSelected();
|
||||
}
|
||||
|
||||
private void doSearch(boolean forward) {
|
||||
|
||||
if (!nextButton.isEnabled()) {
|
||||
|
@ -190,7 +194,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
|||
notifyUser("Not found");
|
||||
}
|
||||
|
||||
private void notifySearchHit(SearchLocation location) {
|
||||
protected void notifySearchHit(SearchLocation location) {
|
||||
searcher.setCursorPosition(location.getCursorPosition());
|
||||
storeSearchText(location.getSearchText());
|
||||
searcher.highlightSearchResults(location);
|
||||
|
@ -234,7 +238,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
|||
history.forEach(comboBox::addToModel);
|
||||
}
|
||||
|
||||
private void storeSearchText(String text) {
|
||||
protected void storeSearchText(String text) {
|
||||
|
||||
MutableComboBoxModel<String> model = (MutableComboBoxModel<String>) comboBox.getModel();
|
||||
model.insertElementAt(text, 0);
|
||||
|
|
|
@ -15,21 +15,72 @@
|
|||
*/
|
||||
package docking.widgets;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
/**
|
||||
* A simple interface for the {@link FindDialog} so that it can work for different search clients.
|
||||
* <p>
|
||||
* The {@link CursorPosition} object used by this interface is one that implementations can extend
|
||||
* to add extra context to use when searching. The implementation is responsible for creating the
|
||||
* locations and these locations will later be handed back to the searcher.
|
||||
*/
|
||||
public interface FindDialogSearcher {
|
||||
|
||||
/**
|
||||
* The current cursor position. Used to search for the next item.
|
||||
* @return the cursor position.
|
||||
*/
|
||||
public CursorPosition getCursorPosition();
|
||||
|
||||
/**
|
||||
* Sets the cursor position after a successful search.
|
||||
* @param position the cursor position.
|
||||
*/
|
||||
public void setCursorPosition(CursorPosition position);
|
||||
|
||||
/**
|
||||
* Returns the start cursor position. This is used when a search is wrapped to start at the
|
||||
* beginning of the search range.
|
||||
* @return the start position.
|
||||
*/
|
||||
public CursorPosition getStart();
|
||||
|
||||
/**
|
||||
* The end cursor position. This is used when a search is wrapped while searching backwards to
|
||||
* start at the end position.
|
||||
* @return the end position.
|
||||
*/
|
||||
public CursorPosition getEnd();
|
||||
|
||||
/**
|
||||
* Called to signal the implementor should highlight the given search location.
|
||||
* @param location the search result location.
|
||||
*/
|
||||
public void highlightSearchResults(SearchLocation location);
|
||||
|
||||
/**
|
||||
* Perform a search for the next item in the given direction starting at the given cursor
|
||||
* position.
|
||||
* @param text the search text.
|
||||
* @param cursorPosition the current cursor position.
|
||||
* @param searchForward true if searching forward.
|
||||
* @param useRegex useRegex true if the search text is a regular expression; false if the texts is
|
||||
* literal text.
|
||||
* @return the search result or null if no match was found.
|
||||
*/
|
||||
public SearchLocation search(String text, CursorPosition cursorPosition, boolean searchForward,
|
||||
boolean useRegex);
|
||||
|
||||
/**
|
||||
* Search for all matches.
|
||||
* @param text the search text.
|
||||
* @param useRegex true if the search text is a regular expression; false if the texts is
|
||||
* literal text.
|
||||
* @return all search results or an empty list.
|
||||
*/
|
||||
public default List<SearchLocation> searchAll(String text, boolean useRegex) {
|
||||
throw new UnsupportedOperationException("Search All is not defined for this searcher");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import javax.swing.JButton;
|
|||
import resources.ResourceManager;
|
||||
|
||||
/**
|
||||
* A drop-in replacement for {@link JButton} that correctly installs a disable icon.
|
||||
* A drop-in replacement for {@link JButton} that correctly installs a disabled icon.
|
||||
*/
|
||||
public class GButton extends JButton {
|
||||
|
||||
|
|
|
@ -742,7 +742,7 @@ public class DefaultProjectData implements ProjectData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void refresh(boolean force) throws IOException {
|
||||
public void refresh(boolean force) {
|
||||
try {
|
||||
rootFolderData.refresh(true, true, projectDisposalMonitor);
|
||||
}
|
||||
|
|
|
@ -17,14 +17,12 @@ package ghidra.framework.main.projectdata.actions;
|
|||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.action.*;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.main.datatable.ProjectDataContext;
|
||||
import ghidra.framework.main.datatable.FrontendProjectTreeAction;
|
||||
import ghidra.framework.main.datatable.ProjectDataContext;
|
||||
import ghidra.framework.model.ProjectData;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.task.*;
|
||||
|
@ -53,13 +51,7 @@ public class ProjectDataRefreshAction extends FrontendProjectTreeAction {
|
|||
TaskLauncher.launch(new Task("Refresh folders and files", false, false, true) {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
try {
|
||||
projectData.refresh(false);
|
||||
}
|
||||
catch (IOException e) {
|
||||
ClientUtil.handleException(projectData.getRepository(), e,
|
||||
"Refresh Project Data", false, comp);
|
||||
}
|
||||
projectData.refresh(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -143,9 +143,8 @@ public interface ProjectData {
|
|||
* Sync the Domain folder/file structure with the underlying file structure.
|
||||
* @param force if true all folders will be be visited and refreshed, if false
|
||||
* only those folders previously visited will be refreshed.
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
public void refresh(boolean force) throws IOException;
|
||||
public void refresh(boolean force);
|
||||
|
||||
/**
|
||||
* Returns User object associated with remote repository or null if a remote repository
|
||||
|
|
|
@ -119,7 +119,7 @@ public class TestDummyProjectData implements ProjectData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void refresh(boolean force) throws IOException {
|
||||
public void refresh(boolean force) {
|
||||
// stub
|
||||
}
|
||||
|
||||
|
|
|
@ -1249,8 +1249,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
|
|||
resolvedDataType = resolveDataTypeNoSource(dataType);
|
||||
}
|
||||
else if (!sourceArchive.getSourceArchiveID().equals(getUniversalID()) &&
|
||||
sourceArchive.getArchiveType() == ArchiveType.PROGRAM) {
|
||||
// dataTypes from a different program don't carry over their identity.
|
||||
(sourceArchive.getArchiveType() == ArchiveType.PROGRAM ||
|
||||
sourceArchive.getArchiveType() == ArchiveType.TEMPORARY)) {
|
||||
// dataTypes from a program or temporary archive don't carry over their identity
|
||||
resolvedDataType = resolveDataTypeNoSource(dataType);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -220,10 +220,13 @@ public class FunctionManagerDB implements FunctionManager {
|
|||
}
|
||||
}
|
||||
|
||||
static void checkSingleAddressSpaceOnly(AddressSetView set) {
|
||||
if (set.getMinAddress().getAddressSpace() != set.getMaxAddress().getAddressSpace()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Function body must contain single address space only");
|
||||
static void checkSingleAddressSpaceOnly(AddressSetView set) throws IllegalArgumentException {
|
||||
AddressSpace addressSpace = set.getMinAddress().getAddressSpace();
|
||||
for (AddressRange range : set.getAddressRanges()) {
|
||||
if (range.getMinAddress().getAddressSpace() != addressSpace) {
|
||||
throw new IllegalArgumentException(
|
||||
"Function body must contain single address space only");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ public enum ArchiveType {
|
|||
FILE,
|
||||
PROJECT,
|
||||
PROGRAM,
|
||||
TEST;
|
||||
TEMPORARY;
|
||||
//@formatter:on
|
||||
|
||||
public boolean isBuiltIn() {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user