mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-06-30 23:04:52 +00:00
Compare commits
27 Commits
8f84e71a78
...
38801cb23e
Author | SHA1 | Date | |
---|---|---|---|
|
38801cb23e | ||
|
28846ef279 | ||
|
e7595341c4 | ||
|
bf71142709 | ||
|
36a707471e | ||
|
4b30e484b0 | ||
|
ae3f6feb70 | ||
|
2b73a6157f | ||
|
34272fd3ff | ||
|
3b6d5e43ce | ||
|
b86ad84c04 | ||
|
72d4a342a6 | ||
|
b68fa6c745 | ||
|
62f41a7179 | ||
|
a977a35f5f | ||
|
d5cbda1e21 | ||
|
4d8ec78908 | ||
|
02aba11104 | ||
|
184c657cfd | ||
|
13821930da | ||
|
e9e4ee48ce | ||
|
21a3896018 | ||
|
eb5e6a323a | ||
|
ea785546cf | ||
|
008a4ef948 | ||
|
8336bdde74 | ||
|
dc8bebef47 |
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -199,7 +199,7 @@ void SleighBase::encodeSlaSpace(Encoder &encoder,AddrSpace *spc) const
|
|||
// encoder.writeSignedInteger(sla::ATTRIB_DEADCODEDELAY, spc->getDeadcodeDelay());
|
||||
encoder.writeSignedInteger(sla::ATTRIB_SIZE, spc->getAddrSize());
|
||||
if (spc->getWordSize() > 1)
|
||||
encoder.writeSignedInteger(sla::ATTRIB_WORDSIZE, spc->getWordSize());
|
||||
encoder.writeUnsignedInteger(sla::ATTRIB_WORDSIZE, spc->getWordSize());
|
||||
encoder.writeBool(sla::ATTRIB_PHYSICAL, spc->hasPhysical());
|
||||
if (spc->getType() == IPTR_INTERNAL)
|
||||
encoder.closeElement(sla::ELEM_SPACE_UNIQUE);
|
||||
|
|
|
@ -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,12 +27,10 @@ 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
|
||||
|
@ -335,230 +329,11 @@ 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 startRow = currentLocation.getIndex().intValue();
|
||||
for (int row = startRow; row < fieldList.length; row++) {
|
||||
ClangTextField field = (ClangTextField) fieldList[row];
|
||||
FieldLocation location = (row == startRow) ? currentLocation : null;
|
||||
String lineText = getLineTextFromOffset(location, field, true);
|
||||
SearchMatch match = matcher.apply(lineText);
|
||||
if (match == SearchMatch.NO_MATCH) {
|
||||
continue;
|
||||
}
|
||||
if (row == startRow) { // 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() - lineText.length();
|
||||
match.start += cursorOffset;
|
||||
match.end += cursorOffset;
|
||||
}
|
||||
|
||||
// we use 0 here because currently there is only one field, which is the entire line
|
||||
int fieldNum = 0;
|
||||
int column = getScreenColumnFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation = new FieldLocation(row, fieldNum, 0, column);
|
||||
|
||||
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 startRow = currentLocation.getIndex().intValue();
|
||||
for (int row = startRow; row >= 0; row--) {
|
||||
ClangTextField field = (ClangTextField) fieldList[row];
|
||||
FieldLocation location = (row == startRow) ? currentLocation : null;
|
||||
String lineText = getLineTextFromOffset(location, field, false);
|
||||
|
||||
SearchMatch match = matcher.apply(lineText);
|
||||
if (match != SearchMatch.NO_MATCH) {
|
||||
|
||||
// we use 0 here because currently there is only one field, which is the entire line
|
||||
int fieldNum = 0;
|
||||
int column = getScreenColumnFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation = new FieldLocation(row, fieldNum, 0, column);
|
||||
|
||||
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 getLineTextFromOffset(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 lineText = 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 >= lineText.length()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// skip a character to start the next search; this prevents matching the previous match
|
||||
return lineText.substring(nextCol);
|
||||
}
|
||||
|
||||
// backwards search
|
||||
return lineText.substring(0, location.getCol());
|
||||
}
|
||||
|
||||
private int getScreenColumnFromOffset(int textOffset, ClangTextField textField) {
|
||||
RowColLocation rowColLocation = textField.textOffsetToScreenLocation(textOffset);
|
||||
return 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
|
||||
|
|
|
@ -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;
|
||||
|
@ -988,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;
|
||||
}
|
||||
|
@ -1279,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
|
||||
|
|
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -923,7 +923,7 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos
|
|||
|
||||
@Override
|
||||
public ArchiveType getType() {
|
||||
return ArchiveType.TEST;
|
||||
return ArchiveType.TEMPORARY;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,6 @@ import ghidra.program.database.register.InMemoryRangeMapAdapter;
|
|||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.ContextChangeException;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -248,8 +247,9 @@ abstract public class AbstractStoredProgramContext extends AbstractProgramContex
|
|||
@Override
|
||||
public void remove(Address start, Address end, Register register)
|
||||
throws ContextChangeException {
|
||||
if (start.getAddressSpace() != end.getAddressSpace()) {
|
||||
throw new AssertException("start and end address must be in the same address space");
|
||||
if (!start.getAddressSpace().equals(end.getAddressSpace())) {
|
||||
throw new IllegalArgumentException(
|
||||
"start and end address must be within the same address space");
|
||||
}
|
||||
RegisterValueStore values = registerValueMap.get(register.getBaseRegister());
|
||||
if (values != null) {
|
||||
|
@ -259,8 +259,9 @@ abstract public class AbstractStoredProgramContext extends AbstractProgramContex
|
|||
}
|
||||
|
||||
// public void removeDefault(Address start, Address end, Register register) {
|
||||
// if (start.getAddressSpace() != end.getAddressSpace()) {
|
||||
// throw new AssertException("start and end address must be in the same address space");
|
||||
// if (!start.getAddressSpace().equals(end.getAddressSpace())) {
|
||||
// throw new IllegalArgumentException(
|
||||
// "start and end address must be within the same address space");
|
||||
// }
|
||||
// invalidateCache();
|
||||
// RegisterValueStore values = defaultRegisterValueMap.get(register.getBaseRegister());
|
||||
|
@ -272,8 +273,9 @@ abstract public class AbstractStoredProgramContext extends AbstractProgramContex
|
|||
@Override
|
||||
public void setValue(Register register, Address start, Address end, BigInteger value)
|
||||
throws ContextChangeException {
|
||||
if (start.getAddressSpace() != end.getAddressSpace()) {
|
||||
throw new AssertException("start and end address must be in the same address space");
|
||||
if (!start.getAddressSpace().equals(end.getAddressSpace())) {
|
||||
throw new IllegalArgumentException(
|
||||
"start and end address must be within the same address space");
|
||||
}
|
||||
if (value == null) {
|
||||
remove(start, end, register);
|
||||
|
@ -284,8 +286,9 @@ abstract public class AbstractStoredProgramContext extends AbstractProgramContex
|
|||
|
||||
@Override
|
||||
public void setDefaultValue(RegisterValue registerValue, Address start, Address end) {
|
||||
if (start.getAddressSpace() != end.getAddressSpace()) {
|
||||
throw new AssertException("start and end address must be in the same address space");
|
||||
if (!start.getAddressSpace().equals(end.getAddressSpace())) {
|
||||
throw new IllegalArgumentException(
|
||||
"start and end address must be within the same address space");
|
||||
}
|
||||
Register baseRegister = registerValue.getRegister().getBaseRegister();
|
||||
RegisterValueStore store = defaultRegisterValueMap.get(baseRegister);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,432 @@
|
|||
/* ###
|
||||
* 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.analysis;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.plugin.core.analysis.TransientProgramProperties.SCOPE;
|
||||
import ghidra.app.util.cparser.C.CParser;
|
||||
import ghidra.app.util.cparser.C.ParseException;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class eBPFHelperDataTypes implements Closeable {
|
||||
|
||||
private static final String EBPF_DATATYPE_MGR_PROPERTY_KEY = "eBPFDataTypes";
|
||||
|
||||
/**
|
||||
* Ordered list of BPF helper functions. Array index corresponds to helper ID.
|
||||
* A null may be substituted for a missing/unknown function definition.
|
||||
*
|
||||
* References:
|
||||
* https://github.com/torvalds/linux/blob/master/include/uapi/linux/bpf.h
|
||||
* https://man7.org/linux/man-pages/man7/bpf-helpers.7.html
|
||||
*/
|
||||
//@formatter:off
|
||||
private static final String[] bpfHelperSignatures = new String[] {
|
||||
// Helper IDs: 0..9
|
||||
"void bpf_unspec()",
|
||||
"void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)",
|
||||
"int bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)",
|
||||
"int bpf_map_delete_elem(struct bpf_map *map, const void *key)",
|
||||
"int bpf_probe_read(void *dst, u32 size, const void *src)",
|
||||
"u64 bpf_ktime_get_ns(void)",
|
||||
"int bpf_trace_printk(const char *fmt, u32 fmt_size, ...)",
|
||||
"u32 bpf_get_prandom_u32(void)",
|
||||
"u32 bpf_get_smp_processor_id(void)",
|
||||
"int bpf_skb_store_bytes(struct sk_buff *skb, u32 offset, const void *from, u32 len, u64 flags)",
|
||||
|
||||
// Helper IDs: 10..19
|
||||
"int bpf_l3_csum_replace(struct sk_buff *skb, u32 offset, u64 from, u64 to, u64 size)",
|
||||
"int bpf_l4_csum_replace(struct sk_buff *skb, u32 offset, u64 from, u64 to, u64 flags)",
|
||||
"int bpf_tail_call(void *ctx, struct bpf_map *prog_array_map, u32 index)",
|
||||
"int bpf_clone_redirect(struct sk_buff *skb, u32 ifindex, u64 flags)",
|
||||
"u64 bpf_get_current_pid_tgid(void)",
|
||||
"u64 bpf_get_current_uid_gid(void)",
|
||||
"int bpf_get_current_comm(char *buf, u32 size_of_buf)",
|
||||
"u32 bpf_get_cgroup_classid(struct sk_buff *skb)",
|
||||
"int bpf_skb_vlan_push(struct sk_buff *skb, __be16 vlan_proto, u16 vlan_tci)",
|
||||
"int bpf_skb_vlan_pop(struct sk_buff *skb)",
|
||||
|
||||
// Helper IDs: 20..29
|
||||
"int bpf_skb_get_tunnel_key(struct sk_buff *skb, struct bpf_tunnel_key *key, u32 size, u64 flags)",
|
||||
"int bpf_skb_set_tunnel_key(struct sk_buff *skb, struct bpf_tunnel_key *key, u32 size, u64 flags)",
|
||||
"u64 bpf_perf_event_read(struct bpf_map *map, u64 flags)",
|
||||
"int bpf_redirect(u32 ifindex, u64 flags)",
|
||||
"u32 bpf_get_route_realm(struct sk_buff *skb)",
|
||||
"int bpf_perf_event_output(struct pt_reg *ctx, struct bpf_map *map, u64 flags, void *data, u64 size)",
|
||||
"int bpf_skb_load_bytes(const struct sk_buff *skb, u32 offset, void *to, u32 len)",
|
||||
"int bpf_get_stackid(struct pt_reg *ctx, struct bpf_map *map, u64 flags)",
|
||||
"s64 bpf_csum_diff(__be32 *from, u32 from_size, __be32 *to, u32 to_size, __wsum seed)",
|
||||
"int bpf_skb_get_tunnel_opt(struct sk_buff *skb, u8 *opt, u32 size)",
|
||||
|
||||
// Helper IDs: 30..39
|
||||
"int bpf_skb_set_tunnel_opt(struct sk_buff *skb, u8 *opt, u32 size)",
|
||||
"int bpf_skb_change_proto(struct sk_buff *skb, __be16 proto, u64 flags)",
|
||||
"int bpf_skb_change_type(struct sk_buff *skb, u32 type)",
|
||||
"int bpf_skb_under_cgroup(struct sk_buff *skb, struct bpf_map *map, u32 index)",
|
||||
"u32 bpf_get_hash_recalc(struct sk_buff *skb)",
|
||||
"u64 bpf_get_current_task(void)",
|
||||
"int bpf_probe_write_user(void *dst, const void *src, u32 len)",
|
||||
"int bpf_current_task_under_cgroup(struct bpf_map *map, u32 index)",
|
||||
"int bpf_skb_change_tail(struct sk_buff *skb, u32 len, u64 flags)",
|
||||
"int bpf_skb_pull_data(struct sk_buff *skb, u32 len)",
|
||||
|
||||
// Helper IDs: 40..49
|
||||
"s64 bpf_csum_update(struct sk_buff *skb, __wsum csum)",
|
||||
"void bpf_set_hash_invalid(struct sk_buff *skb)",
|
||||
"int bpf_get_numa_node_id(void)",
|
||||
"int bpf_skb_change_head(struct sk_buff *skb, u32 len, u64 flags)",
|
||||
"int bpf_xdp_adjust_head(struct xdp_buff *xdp_md, int delta)",
|
||||
"int bpf_probe_read_str(void *dst, u32 size, const void *unsafe_ptr)",
|
||||
// NOTE: bpf_get_socket_cookie function is overloaded based upon program type so
|
||||
// we define the argument as a void pointer within the generic function definition
|
||||
// u64 bpf_get_socket_cookie(struct sk_buff *skb)
|
||||
// u64 bpf_get_socket_cookie(struct bpf_sock_addr *ctx)
|
||||
// u64 bpf_get_socket_cookie(struct bpf_sock_ops *ctx)
|
||||
// u64 bpf_get_socket_cookie(struct sock *sk)
|
||||
"u64 bpf_get_socket_cookie(void *ctx)",
|
||||
"u32 bpf_get_socket_uid(struct sk_buff *skb)",
|
||||
"int bpf_set_hash(struct sk_buff *skb, u32 hash)",
|
||||
"int bpf_setsockopt(void *bpf_socket, int level, int optname, void *optval, int optlen)",
|
||||
|
||||
// Helper IDs: 50..59
|
||||
"int bpf_skb_adjust_room(struct sk_buff *skb, s32 len_diff, u32 mode, u64 flags)",
|
||||
"int bpf_redirect_map(struct bpf_map *map, u32 key, u64 flags)",
|
||||
"int bpf_sk_redirect_map(struct sk_buff *skb, struct bpf_map *map, u32 key, u64 flags)",
|
||||
"int bpf_sock_map_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)",
|
||||
"int bpf_xdp_adjust_meta(struct xdp_buff *xdp_md, int delta)",
|
||||
"int bpf_perf_event_read_value(struct bpf_map *map, u64 flags, struct bpf_perf_event_value *buf, u32 buf_size)",
|
||||
"int bpf_perf_prog_read_value(struct bpf_perf_event_data *ctx, struct bpf_perf_event_value *buf, u32 buf_size)",
|
||||
"int bpf_getsockopt(void *bpf_socket, int level, int optname, void *optval, int optlen)",
|
||||
"int bpf_override_return(struct pt_regs *regs, u64 rc)",
|
||||
"int bpf_sock_ops_cb_flags_set(struct bpf_sock_ops *bpf_sock, int argval)",
|
||||
|
||||
// Helper IDs: 60..69
|
||||
"int bpf_msg_redirect_map(struct sk_msg_buff *msg, struct bpf_map *map, u32 key, u64 flags)",
|
||||
"int bpf_msg_apply_bytes(struct sk_msg_buff *msg, u32 bytes)",
|
||||
"long bpf_msg_cork_bytes(struct sk_msg_buff *msg, u32 bytes)",
|
||||
"long bpf_msg_pull_data(struct sk_msg_buff *msg, u32 start, u32 end, u64 flags)",
|
||||
"long bpf_bind(struct bpf_sock_addr *ctx, struct sockaddr *addr, int addr_len)",
|
||||
"long bpf_xdp_adjust_tail(struct xdp_buff *xdp_md, int delta)",
|
||||
"long bpf_skb_get_xfrm_state(struct sk_buff *skb, u32 index, struct bpf_xfrm_state *xfrm_state, u32 size, u64 flags)",
|
||||
"long bpf_get_stack(void *ctx, void *buf, u32 size, u64 flags)",
|
||||
"long bpf_skb_load_bytes_relative(const void *skb, u32 offset, void *to, u32 len, u32 start_header)",
|
||||
"long bpf_fib_lookup(void *ctx, struct bpf_fib_lookup *params, int plen, u32 flags)",
|
||||
|
||||
// Helper IDs: 70..79
|
||||
"long bpf_sock_hash_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)",
|
||||
"long bpf_msg_redirect_hash(struct sk_msg_buff *msg, struct bpf_map *map, void *key, u64 flags)",
|
||||
"long bpf_sk_redirect_hash(struct sk_buff *skb, struct bpf_map *map, void *key, u64 flags)",
|
||||
"long bpf_lwt_push_encap(struct sk_buff *skb, u32 type, void *hdr, u32 len)",
|
||||
"long bpf_lwt_seg6_store_bytes(struct sk_buff *skb, u32 offset, const void *from, u32 len)",
|
||||
"long bpf_lwt_seg6_adjust_srh(struct sk_buff *skb, u32 offset, s32 delta)",
|
||||
"long bpf_lwt_seg6_action(struct sk_buff *skb, u32 action, void *param, u32 param_len)",
|
||||
"long bpf_rc_repeat(void *ctx)",
|
||||
"long bpf_rc_keydown(void *ctx, u32 protocol, u64 scancode, u32 toggle)",
|
||||
"u64 bpf_skb_cgroup_id(struct sk_buff *skb)",
|
||||
|
||||
// Helper IDs: 80..89
|
||||
"u64 bpf_get_current_cgroup_id(void)",
|
||||
"void *bpf_get_local_storage(void *map, u64 flags)",
|
||||
"long bpf_sk_select_reuseport(struct sk_reuseport_md *reuse, struct bpf_map *map, void *key, u64 flags)",
|
||||
"u64 bpf_skb_ancestor_cgroup_id(struct sk_buff *skb, int ancestor_level)",
|
||||
"struct bpf_sock *bpf_sk_lookup_tcp(void *ctx, struct bpf_sock_tuple *tuple, u32 tuple_size, u64 netns, u64 flags)",
|
||||
"struct bpf_sock *bpf_sk_lookup_udp(void *ctx, struct bpf_sock_tuple *tuple, u32 tuple_size, u64 netns, u64 flags)",
|
||||
"long bpf_sk_release(void *sock)",
|
||||
"long bpf_map_push_elem(struct bpf_map *map, const void *value, u64 flags)",
|
||||
"long bpf_map_pop_elem(struct bpf_map *map, void *value)",
|
||||
"long bpf_map_peek_elem(struct bpf_map *map, void *value)",
|
||||
|
||||
// Helper IDs: 90..99
|
||||
"long bpf_msg_push_data(struct sk_msg_buff *msg, u32 start, u32 len, u64 flags)",
|
||||
"long bpf_msg_pop_data(struct sk_msg_buff *msg, u32 start, u32 len, u64 flags)",
|
||||
"long bpf_rc_pointer_rel(void *ctx, s32 rel_x, s32 rel_y)",
|
||||
"long bpf_spin_lock(struct bpf_spin_lock *lock)",
|
||||
"long bpf_spin_unlock(struct bpf_spin_lock *lock)",
|
||||
"struct bpf_sock *bpf_sk_fullsock(struct bpf_sock *sk)",
|
||||
"struct bpf_tcp_sock *bpf_tcp_sock(struct bpf_sock *sk)",
|
||||
"long bpf_skb_ecn_set_ce(struct sk_buff *skb)",
|
||||
"struct bpf_sock *bpf_get_listener_sock(struct bpf_sock *sk)",
|
||||
"struct bpf_sock *bpf_skc_lookup_tcp(void *ctx, struct bpf_sock_tuple *tuple, u32 tuple_size, u64 netns, u64 flags)",
|
||||
|
||||
// Helper IDs: 100..109
|
||||
"long bpf_tcp_check_syncookie(void *sk, void *iph, u32 iph_len, struct tcphdr *th, u32 th_len)",
|
||||
"long bpf_sysctl_get_name(struct bpf_sysctl *ctx, char *buf, size_t buf_len, u64 flags)",
|
||||
"long bpf_sysctl_get_current_value(struct bpf_sysctl *ctx, char *buf, size_t buf_len)",
|
||||
"long bpf_sysctl_get_new_value(struct bpf_sysctl *ctx, char *buf, size_t buf_len)",
|
||||
"long bpf_sysctl_set_new_value(struct bpf_sysctl *ctx, const char *buf, size_t buf_len)",
|
||||
"long bpf_strtol(const char *buf, size_t buf_len, u64 flags, long *res)",
|
||||
"long bpf_strtoul(const char *buf, size_t buf_len, u64 flags, unsigned long *res)",
|
||||
"void *bpf_sk_storage_get(struct bpf_map *map, void *sk, void *value, u64 flags)",
|
||||
"long bpf_sk_storage_delete(struct bpf_map *map, void *sk)",
|
||||
"long bpf_send_signal(u32 sig)",
|
||||
|
||||
// Helper IDs: 110..119
|
||||
"s64 bpf_tcp_gen_syncookie(void *sk, void *iph, u32 iph_len, struct tcphdr *th, u32 th_len)",
|
||||
"long bpf_skb_output(void *ctx, struct bpf_map *map, u64 flags, void *data, u64 size)",
|
||||
"long bpf_probe_read_user(void *dst, u32 size, const void *unsafe_ptr)",
|
||||
"long bpf_probe_read_kernel(void *dst, u32 size, const void *unsafe_ptr)",
|
||||
"long bpf_probe_read_user_str(void *dst, u32 size, const void *unsafe_ptr)",
|
||||
"long bpf_probe_read_kernel_str(void *dst, u32 size, const void *unsafe_ptr)",
|
||||
"long bpf_tcp_send_ack(void *tp, u32 rcv_nxt)",
|
||||
"long bpf_send_signal_thread(u32 sig)",
|
||||
"u64 bpf_jiffies64(void)",
|
||||
"long bpf_read_branch_records(struct bpf_perf_event_data *ctx, void *buf, u32 size, u64 flags)",
|
||||
// Helper IDs: 120..129
|
||||
"long bpf_get_ns_current_pid_tgid(u64 dev, u64 ino, struct bpf_pidns_info *nsdata, u32 size)",
|
||||
"long bpf_xdp_output(void *ctx, struct bpf_map *map, u64 flags, void *data, u64 size)",
|
||||
"u64 bpf_get_netns_cookie(void *ctx)",
|
||||
"u64 bpf_get_current_ancestor_cgroup_id(int ancestor_level)",
|
||||
"long bpf_sk_assign(struct bpf_sk_lookup *ctx, struct bpf_sock *sk, u64 flags)",
|
||||
"u64 bpf_ktime_get_boot_ns(void)",
|
||||
"long bpf_seq_printf(struct seq_file *m, const char *fmt, u32 fmt_size, const void *data, u32 data_len)",
|
||||
"long bpf_seq_write(struct seq_file *m, const void *data, u32 len)",
|
||||
"u64 bpf_sk_cgroup_id(void *sk)",
|
||||
"u64 bpf_sk_ancestor_cgroup_id(void *sk, int ancestor_level)",
|
||||
|
||||
// Helper IDs: 130..139
|
||||
"long bpf_ringbuf_output(void *ringbuf, void *data, u64 size, u64 flags)",
|
||||
"void *bpf_ringbuf_reserve(void *ringbuf, u64 size, u64 flags)",
|
||||
"void bpf_ringbuf_submit(void *data, u64 flags)",
|
||||
"void bpf_ringbuf_discard(void *data, u64 flags)",
|
||||
"u64 bpf_ringbuf_query(void *ringbuf, u64 flags)",
|
||||
"long bpf_csum_level(struct sk_buff *skb, u64 level)",
|
||||
"struct tcp6_sock *bpf_skc_to_tcp6_sock(void *sk)",
|
||||
"struct tcp_sock *bpf_skc_to_tcp_sock(void *sk)",
|
||||
"struct tcp_timewait_sock *bpf_skc_to_tcp_timewait_sock(void *sk)",
|
||||
"struct tcp_request_sock *bpf_skc_to_tcp_request_sock(void *sk)",
|
||||
|
||||
// Helper IDs: 140..149
|
||||
"struct udp6_sock *bpf_skc_to_udp6_sock(void *sk)",
|
||||
"long bpf_get_task_stack(struct task_struct *task, void *buf, u32 size, u64 flags)",
|
||||
"long bpf_load_hdr_opt(struct bpf_sock_ops *skops, void *searchby_res, u32 len, u64 flags)",
|
||||
"long bpf_store_hdr_opt(struct bpf_sock_ops *skops, const void *from, u32 len, u64 flags)",
|
||||
"long bpf_reserve_hdr_opt(struct bpf_sock_ops *skops, u32 len, u64 flags)",
|
||||
"void *bpf_inode_storage_get(struct bpf_map *map, void *inode, void *value, u64 flags)",
|
||||
"int bpf_inode_storage_delete(struct bpf_map *map, void *inode)",
|
||||
"long bpf_d_path(struct path *path, char *buf, u32 sz)",
|
||||
"long bpf_copy_from_user(void *dst, u32 size, const void *user_ptr)",
|
||||
"long bpf_snprintf_btf(char *str, u32 str_size, struct btf_ptr *ptr, u32 btf_ptr_size, u64 flags)",
|
||||
|
||||
// Helper IDs: 150..159
|
||||
"long bpf_seq_printf_btf(struct seq_file *m, struct btf_ptr *ptr, u32 ptr_size, u64 flags)",
|
||||
"u64 bpf_skb_cgroup_classid(struct sk_buff *skb)",
|
||||
"long bpf_redirect_neigh(u32 ifindex, struct bpf_redir_neigh *params, int plen, u64 flags)",
|
||||
"void *bpf_per_cpu_ptr(const void *percpu_ptr, u32 cpu)",
|
||||
"void *bpf_this_cpu_ptr(const void *percpu_ptr)",
|
||||
"long bpf_redirect_peer(u32 ifindex, u64 flags)",
|
||||
"void *bpf_task_storage_get(struct bpf_map *map, struct task_struct *task, void *value, u64 flags)",
|
||||
"long bpf_task_storage_delete(struct bpf_map *map, struct task_struct *task)",
|
||||
"struct task_struct *bpf_get_current_task_btf(void)",
|
||||
"long bpf_bprm_opts_set(struct linux_binprm *bprm, u64 flags)",
|
||||
|
||||
// Helper IDs: 160..169
|
||||
"u64 bpf_ktime_get_coarse_ns(void)",
|
||||
"long bpf_ima_inode_hash(struct inode *inode, void *dst, u32 size)",
|
||||
"struct socket *bpf_sock_from_file(struct file *file)",
|
||||
"long bpf_check_mtu(void *ctx, u32 ifindex, u32 *mtu_len, s32 len_diff, u64 flags)",
|
||||
"long bpf_for_each_map_elem(struct bpf_map *map, void *callback_fn, void *callback_ctx, u64 flags)",
|
||||
"long bpf_snprintf(char *str, u32 str_size, const char *fmt, u64 *data, u32 data_len)",
|
||||
"long bpf_sys_bpf(u32 cmd, void *attr, u32 attr_size)",
|
||||
"long bpf_btf_find_by_name_kind(char *name, int name_sz, u32 kind, int flags)",
|
||||
"long bpf_sys_close(u32 fd)",
|
||||
"long bpf_timer_init(struct bpf_timer *timer, struct bpf_map *map, u64 flags)",
|
||||
|
||||
// Helper IDs: 170..179
|
||||
"long bpf_timer_set_callback(struct bpf_timer *timer, void *callback_fn)",
|
||||
"long bpf_timer_start(struct bpf_timer *timer, u64 nsecs, u64 flags)",
|
||||
"long bpf_timer_cancel(struct bpf_timer *timer)",
|
||||
"u64 bpf_get_func_ip(void *ctx)",
|
||||
"u64 bpf_get_attach_cookie(void *ctx)",
|
||||
"long bpf_task_pt_regs(struct task_struct *task)",
|
||||
"long bpf_get_branch_snapshot(void *entries, u32 size, u64 flags)",
|
||||
"long bpf_trace_vprintk(const char *fmt, u32 fmt_size, const void *data, u32 data_len)",
|
||||
"struct unix_sock *bpf_skc_to_unix_sock(void *sk)",
|
||||
"long bpf_kallsyms_lookup_name(const char *name, int name_sz, int flags, u64 *res)",
|
||||
|
||||
// Helper IDs: 180..189
|
||||
"long bpf_find_vma(struct task_struct *task, u64 addr, void *callback_fn, void *callback_ctx, u64 flags)",
|
||||
"long bpf_loop(u32 nr_loops, void *callback_fn, void *callback_ctx, u64 flags)",
|
||||
"long bpf_strncmp(const char *s1, u32 s1_sz, const char *s2)",
|
||||
"long bpf_get_func_arg(void *ctx, u32 n, u64 *value)",
|
||||
"long bpf_get_func_ret(void *ctx, u64 *value)",
|
||||
"long bpf_get_func_arg_cnt(void *ctx)",
|
||||
"int bpf_get_retval(void)",
|
||||
"int bpf_set_retval(int retval)",
|
||||
"u64 bpf_xdp_get_buff_len(struct xdp_buff *xdp_md)",
|
||||
"long bpf_xdp_load_bytes(struct xdp_buff *xdp_md, u32 offset, void *buf, u32 len)",
|
||||
|
||||
// Helper IDs: 190..199
|
||||
"long bpf_xdp_store_bytes(struct xdp_buff *xdp_md, u32 offset, void *buf, u32 len)",
|
||||
"long bpf_copy_from_user_task(void *dst, u32 size, const void *user_ptr, struct task_struct *tsk, u64 flags)",
|
||||
"long bpf_skb_set_tstamp(struct sk_buff *skb, u64 tstamp, u32 tstamp_type)",
|
||||
"long bpf_ima_file_hash(struct file *file, void *dst, u32 size)",
|
||||
"void *bpf_kptr_xchg(void *map_value, void *ptr)",
|
||||
"void *bpf_map_lookup_percpu_elem(struct bpf_map *map, const void *key, u32 cpu)",
|
||||
"struct mptcp_sock *bpf_skc_to_mptcp_sock(void *sk)",
|
||||
"long bpf_dynptr_from_mem(void *data, u32 size, u64 flags, struct bpf_dynptr *ptr)",
|
||||
"long bpf_ringbuf_reserve_dynptr(void *ringbuf, u32 size, u64 flags, struct bpf_dynptr *ptr)",
|
||||
"void bpf_ringbuf_submit_dynptr(struct bpf_dynptr *ptr, u64 flags)",
|
||||
|
||||
// Helper IDs: 200..209
|
||||
"void bpf_ringbuf_discard_dynptr(struct bpf_dynptr *ptr, u64 flags)",
|
||||
"long bpf_dynptr_read(void *dst, u32 len, const struct bpf_dynptr *src, u32 offset, u64 flags)",
|
||||
"long bpf_dynptr_write(const struct bpf_dynptr *dst, u32 offset, void *src, u32 len, u64 flags)",
|
||||
"void *bpf_dynptr_data(const struct bpf_dynptr *ptr, u32 offset, u32 len)",
|
||||
"s64 bpf_tcp_raw_gen_syncookie_ipv4(struct iphdr *iph, struct tcphdr *th, u32 th_len)",
|
||||
"s64 bpf_tcp_raw_gen_syncookie_ipv6(struct ipv6hdr *iph, struct tcphdr *th, u32 th_len)",
|
||||
"long bpf_tcp_raw_check_syncookie_ipv4(struct iphdr *iph, struct tcphdr *th)",
|
||||
"long bpf_tcp_raw_check_syncookie_ipv6(struct ipv6hdr *iph, struct tcphdr *th)",
|
||||
"u64 bpf_ktime_get_tai_ns(void)",
|
||||
"long bpf_user_ringbuf_drain(struct bpf_map *map, void *callback_fn, void *ctx, u64 flags)",
|
||||
|
||||
// Helper IDs: 210..
|
||||
"void *bpf_cgrp_storage_get(struct bpf_map *map, struct cgroup *cgroup, void *value, u64 flags)",
|
||||
"long bpf_cgrp_storage_delete(struct bpf_map *map, struct cgroup *cgroup)"
|
||||
};
|
||||
//@formatter:on
|
||||
|
||||
private DataTypeManager dtm;
|
||||
private FunctionDefinition[] helperFunctionDefs;
|
||||
|
||||
private eBPFHelperDataTypes(DataTypeManager dtm, FunctionDefinition[] helperFunctionDefs) {
|
||||
this.dtm = dtm;
|
||||
this.helperFunctionDefs = helperFunctionDefs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
helperFunctionDefs = null;
|
||||
dtm.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get eBPF helper function definition for the specified ID.
|
||||
*
|
||||
* @param id helper function ID
|
||||
* @return eBPF helper function definition or null
|
||||
*/
|
||||
FunctionDefinition getHelperFunctionDef(int id) {
|
||||
if (id >= 0 && id < helperFunctionDefs.length) {
|
||||
return helperFunctionDefs[id];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*******************
|
||||
* Static Methods
|
||||
*******************/
|
||||
|
||||
/**
|
||||
* Get the BPF helper datatypes which has been populated with helper function
|
||||
* definitions and related dependency datatypes. All structure dependencies are defined
|
||||
* as empty structures. In addition, the big-endian typedefs {@code __be16} and
|
||||
* {@code __be32} will be prepopulated within the program's datatype manager with
|
||||
* big-endian default setting enabled.
|
||||
*
|
||||
* @param program target program
|
||||
* @param log analysis message log
|
||||
* @return BPF helper datatype or null if failed to initialize.
|
||||
*/
|
||||
static synchronized eBPFHelperDataTypes get(Program program, MessageLog log) {
|
||||
|
||||
boolean previouslyParsed =
|
||||
TransientProgramProperties.hasProperty(program, EBPF_DATATYPE_MGR_PROPERTY_KEY);
|
||||
eBPFHelperDataTypes instance = TransientProgramProperties.getProperty(program,
|
||||
EBPF_DATATYPE_MGR_PROPERTY_KEY, SCOPE.ANALYSIS_SESSION, eBPFHelperDataTypes.class,
|
||||
() -> parseHelpFunctionDefs(program));
|
||||
if (instance == null && !previouslyParsed) {
|
||||
log.appendMsg("Failed to parse eBPF helper function definitions (see log for details)");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static eBPFHelperDataTypes parseHelpFunctionDefs(Program program) {
|
||||
|
||||
FunctionDefinition[] helperFunctionDefs =
|
||||
new FunctionDefinition[bpfHelperSignatures.length];
|
||||
|
||||
DataType be16;
|
||||
DataType be32;
|
||||
|
||||
boolean success = false;
|
||||
DataTypeManager dtm =
|
||||
new StandAloneDataTypeManager("BPF", DataOrganizationImpl.getDefaultOrganization());
|
||||
int txId = dtm.startTransaction("Parse Types");
|
||||
try {
|
||||
|
||||
// Populate typedef dependencies (based upon eBPF.cspec and little-endian)
|
||||
dtm.addDataType(new TypedefDataType("u8", UnsignedCharDataType.dataType), null);
|
||||
dtm.addDataType(new TypedefDataType("u16", UnsignedShortDataType.dataType), null);
|
||||
dtm.addDataType(new TypedefDataType("u32", UnsignedIntegerDataType.dataType), null);
|
||||
dtm.addDataType(new TypedefDataType("s32", IntegerDataType.dataType), null);
|
||||
dtm.addDataType(new TypedefDataType("u64", UnsignedLongDataType.dataType), null);
|
||||
dtm.addDataType(new TypedefDataType("s64", LongDataType.dataType), null);
|
||||
dtm.addDataType(new TypedefDataType("__wsum", IntegerDataType.dataType), null);
|
||||
dtm.addDataType(new TypedefDataType("__sum16", ShortDataType.dataType), null);
|
||||
dtm.addDataType(new TypedefDataType("size_t", UnsignedLongDataType.dataType), null);
|
||||
|
||||
// Define big-endian typedefs - limited support within little-endian program
|
||||
be16 = dtm.addDataType(new TypedefDataType("__be16", UnsignedShortDataType.dataType),
|
||||
null);
|
||||
be32 = dtm.addDataType(new TypedefDataType("__be32", UnsignedIntegerDataType.dataType),
|
||||
null);
|
||||
|
||||
CParser parser = new CParser(dtm, true, null);
|
||||
try {
|
||||
int id = 0;
|
||||
for (String def : bpfHelperSignatures) {
|
||||
helperFunctionDefs[id++] =
|
||||
def != null ? (FunctionDefinition) parser.parse(def + ";") : null;
|
||||
}
|
||||
}
|
||||
catch (ParseException e) {
|
||||
Msg.error(eBPFHelperDataTypes.class, "eBPF datatype parse error: " +
|
||||
e.getMessage() + "\n\n" + parser.getParseMessages());
|
||||
return null;
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
finally {
|
||||
dtm.endTransaction(txId, true);
|
||||
if (!success) {
|
||||
dtm.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Add big-endian datatypes to program and set default setting.
|
||||
// This is done since endian settings do not carry through resolve
|
||||
program.withTransaction("Add BPF big-endian typedefs", () -> {
|
||||
ProgramBasedDataTypeManager programDtm = program.getDataTypeManager();
|
||||
setBigEndianFormat(programDtm.addDataType(be16, null));
|
||||
setBigEndianFormat(programDtm.addDataType(be32, null));
|
||||
});
|
||||
|
||||
return new eBPFHelperDataTypes(dtm, helperFunctionDefs);
|
||||
}
|
||||
|
||||
private static void setBigEndianFormat(DataType beDt) {
|
||||
Settings defaultSettings = beDt.getDefaultSettings();
|
||||
EndianSettingsDefinition.DEF.setBigEndian(defaultSettings, true);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.analysis;
|
||||
|
||||
import ghidra.app.cmd.function.ApplyFunctionSignatureCmd;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.data.FunctionDefinition;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class eBPFSyscallAnalyzer extends AbstractAnalyzer {
|
||||
|
||||
private final static String PROCESSOR_NAME = "eBPF";
|
||||
private final static String SYSCALL_ADDRSPACE_NAME = "syscall";
|
||||
|
||||
private final static String NAME = "eBPF Syscall Functions";
|
||||
private final static String DESCRIPTION = "Apply eBPF syscall Functions";
|
||||
|
||||
public eBPFSyscallAnalyzer() {
|
||||
super(NAME, DESCRIPTION, AnalyzerType.FUNCTION_ANALYZER);
|
||||
setPriority(AnalysisPriority.FUNCTION_ID_ANALYSIS.before());
|
||||
setDefaultEnablement(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAnalyze(Program program) {
|
||||
return PROCESSOR_NAME.equals(program.getLanguage().getProcessor().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Following the creation of a function this analyzer applies a function signature to default
|
||||
* function if contains within the syscall space.
|
||||
* @throws CancelledException if analysis is cancelled
|
||||
*/
|
||||
@Override
|
||||
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
|
||||
throws CancelledException {
|
||||
|
||||
AddressSpace syscallSpace = program.getAddressFactory().getAddressSpace(SYSCALL_ADDRSPACE_NAME);
|
||||
|
||||
AddressSetView syscallSet = set.intersectRange(syscallSpace.getMinAddress(), syscallSpace.getMaxAddress());
|
||||
if (syscallSet.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clear disassembly errors within syscall space
|
||||
BookmarkManager bookmarkMgr = program.getBookmarkManager();
|
||||
bookmarkMgr.removeBookmarks(syscallSet, BookmarkType.ERROR, monitor);
|
||||
|
||||
eBPFHelperDataTypes helperDataTypes = eBPFHelperDataTypes.get(program, log);
|
||||
if (helperDataTypes == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Function f : program.getFunctionManager().getFunctions(syscallSet, true)) {
|
||||
monitor.checkCancelled();
|
||||
if (f.getSymbol().getSource() != SourceType.DEFAULT) {
|
||||
continue;
|
||||
}
|
||||
applySyscallSignature(f, helperDataTypes);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applySyscallSignature(Function func, eBPFHelperDataTypes helperDataTypes) {
|
||||
|
||||
Program program = func.getProgram();
|
||||
|
||||
int helperId = (int) func.getEntryPoint().getOffset();
|
||||
|
||||
FunctionDefinition helperDef = helperDataTypes.getHelperFunctionDef(helperId);
|
||||
|
||||
if (helperDef == null) {
|
||||
try {
|
||||
func.setName("bpf_undef_0x" + Integer.toHexString(helperId), SourceType.ANALYSIS);
|
||||
}
|
||||
catch (DuplicateNameException | InvalidInputException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
else {
|
||||
ApplyFunctionSignatureCmd cmd = new ApplyFunctionSignatureCmd(func.getEntryPoint(), helperDef, SourceType.ANALYSIS);
|
||||
cmd.applyTo(program);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -53,12 +53,12 @@ public class eBPF_ElfRelocationHandler
|
|||
return RelocationResult.SKIPPED;
|
||||
}
|
||||
|
||||
int symbolIndex = relocation.getSymbolIndex();
|
||||
long new_value = 0;
|
||||
int byteLength = 8;
|
||||
long new_value;
|
||||
int byteLength;
|
||||
|
||||
switch (type) {
|
||||
case R_BPF_64_64: {
|
||||
byteLength = 12;
|
||||
new_value = symbolAddr.getAddressableWordOffset();
|
||||
Byte dst = memory.getByte(relocationAddress.add(0x1));
|
||||
memory.setLong(relocationAddress.add(0x4), new_value);
|
||||
|
@ -66,7 +66,8 @@ public class eBPF_ElfRelocationHandler
|
|||
break;
|
||||
}
|
||||
case R_BPF_64_32: {
|
||||
|
||||
byteLength = 8;
|
||||
|
||||
// if we have, e.g, non-static function, it will be marked in the relocation table
|
||||
// and indexed in the symbol table and it's easy to calculate the pc-relative offset
|
||||
long instr_next = relocationAddress.add(0x8).getAddressableWordOffset();
|
||||
|
@ -92,14 +93,24 @@ public class eBPF_ElfRelocationHandler
|
|||
int offset = (int) (func_addr - instr_next);
|
||||
memory.setInt(relocationAddress.add(0x4), offset);
|
||||
}
|
||||
// else {
|
||||
// markAsUnhandled(program, relocationAddress, type, relocation.getSymbolIndex(),
|
||||
// symbolName, elfRelocationContext.getLog());
|
||||
// return RelocationResult.UNSUPPORTED;
|
||||
// }
|
||||
}
|
||||
// else {
|
||||
// markAsUnhandled(program, relocationAddress, type, relocation.getSymbolIndex(),
|
||||
// symbolName, elfRelocationContext.getLog());
|
||||
// return RelocationResult.UNSUPPORTED;
|
||||
// }
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (symbolIndex == 0) {
|
||||
markAsWarning(program, relocationAddress, type, symbolName, symbolIndex,
|
||||
"applied relocation with symbol-index of 0", elfRelocationContext.getLog());
|
||||
}
|
||||
// TODO: it may be appropriate to bookmark unsupported relocations
|
||||
// Relocation treatment for .BTF sections may differ
|
||||
// markAsUnhandled(program, relocationAddress, type, relocation.getSymbolIndex(),
|
||||
// symbolName, elfRelocationContext.getLog());
|
||||
return RelocationResult.UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,6 +132,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
|
|||
[<a href="#max-cpu">-max-cpu <max cpu cores to use></a>]
|
||||
[<a href="#librarySearchPaths">-librarySearchPaths <path1>[;<path2>...]</a>]
|
||||
[<a href="#loader">-loader <desired loader name></a>]
|
||||
[<a href="#loader">-loader-<loader argument name> <loader argument value></a>]
|
||||
|
||||
</PRE>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user