Compare commits

...

27 Commits

Author SHA1 Message Date
mborgerson
38801cb23e
Merge dc8bebef47 into 28846ef279 2024-06-27 17:58:05 +02:00
ghidra1
28846ef279 GP-0 Corrected formatting issue 2024-06-26 16:55:02 -04:00
Ryan Kurtz
e7595341c4 Merge remote-tracking branch 'origin/GP-4722_ryanmkurtz_omf-markup' 2024-06-26 11:30:09 -04:00
Ryan Kurtz
bf71142709 Merge remote-tracking branch
'origin/GP-4709_d-millar_HexInteger_ext--SQUASHED' (Closes #6658,
Closes #6659)
2024-06-26 11:26:51 -04:00
Ryan Kurtz
36a707471e GP-4722: Marking up OMF records 2024-06-26 11:13:44 -04:00
ghidra1
4b30e484b0 GP-4508 revised address range space checks 2024-06-26 09:13:11 -04:00
d-millar
ae3f6feb70 GP-4709: post-post-post review
GP-4709: post=post review - test logic in error
GP-4709: post=post review - test logic in error
GP-4709: post-review fixes
GP-4709: going for simplicity
GP-4709: reverting most stuff
GP-4709: fix for replaceValue logic
GP-4709: extensions to HexInteger
2024-06-26 09:09:28 -04:00
Ryan Kurtz
2b73a6157f Merge remote-tracking branch
'origin/GP-4707_ryanmkurtz_headless--SQUASHED' (Closes #6639)
2024-06-25 13:41:24 -04:00
Ryan Kurtz
34272fd3ff GP-4707: Improved handling of custom headless command line arguments that start with dash 2024-06-25 13:38:53 -04:00
ghidra1
3b6d5e43ce GP-0 Corrected test failure fallout from GP-4682 datatype resolution
change related to temporary source archive
2024-06-25 13:21:27 -04:00
Ryan Kurtz
b86ad84c04 Merge remote-tracking branch 'origin/patch' 2024-06-25 12:16:37 -04:00
Ryan Kurtz
72d4a342a6 Merge remote-tracking branch
'origin/GP-4710_Dan_gdbGeneralRegistersFallback' into patch
(Closes #6635)
2024-06-25 12:14:02 -04:00
Ryan Kurtz
b68fa6c745 Merge remote-tracking branch
'origin/GP-3491-dragonmacher-decompiler-find-window--SQUASHED'
(Closes #5317, #538)
2024-06-25 12:03:04 -04:00
dragonmacher
62f41a7179 GP-3491 - Decompiler - Added a Search All button to the Find Dialog that will show a table of results 2024-06-25 11:59:05 -04:00
Ryan Kurtz
a977a35f5f Merge remote-tracking branch 'origin/GP-4703_ghizard_MDMang_multipass_on_demangleType_method' 2024-06-25 11:57:01 -04:00
Ryan Kurtz
d5cbda1e21 Merge remote-tracking branch 'origin/GP-4708_RecoverClassesFillOutStructure' 2024-06-25 06:34:53 -04:00
Ryan Kurtz
4d8ec78908 Merge remote-tracking branch
'origin/GP-4508_ghidra1_ProgramContextMergeForOverlays' (Closes #6403)
2024-06-25 06:01:35 -04:00
ghidra007
02aba11104 GP-4708 updated createStructure to fix logic error. 2024-06-24 23:11:26 +00:00
caheckman
184c657cfd GP-4708 Adjustments to RecoverClassesFromRTTIScript and
FillOutStructureHelper
2024-06-24 23:11:26 +00:00
ghidra1
13821930da GP-4508 Corrected program context mutli-user merge issue for overlays 2024-06-24 16:11:31 -04:00
Ryan Kurtz
e9e4ee48ce Merge remote-tracking branch 'origin/patch' 2024-06-24 13:59:30 -04:00
Ryan Kurtz
21a3896018 Merge remote-tracking branch 'origin/GP-4682_ghidra1_eBPFAnalysis--SQUASHED' 2024-06-24 13:50:03 -04:00
ghidra1
eb5e6a323a GP-4682 cleanup eBPF analyzers and BPF helper function identification 2024-06-24 12:39:52 -04:00
ghidra1
ea785546cf GP-0 Fix URL error handling 2024-06-24 11:46:51 -04:00
Dan
008a4ef948 GP-4710: Fallback to all registers if "general" is not a group.
Fixes #6635
2024-06-24 10:08:46 -04:00
ghizard
8336bdde74 GP-4703 - MDMang - retry on demangleType 2024-06-20 14:03:47 +00:00
Matt Borgerson
dc8bebef47 Encode SlaSpace wordsize as an unsigned integer
This patch corrects a mismatch between the encoder and decoder in
wordsize signedness. AddrSpace::getWordSize() returns an unsigned
integer, so use unsigned here for consistency.
2024-06-11 17:14:20 -07:00
82 changed files with 3321 additions and 2230 deletions

View File

@ -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(

View File

@ -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>

View File

@ -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;

View File

@ -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 =

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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());
}
/**

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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();
}
/**

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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

View File

@ -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;

View File

@ -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());

View File

@ -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(() -> {

View File

@ -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));

View File

@ -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(() -> {

View File

@ -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);
}
}

View File

@ -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
//

View File

@ -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.&nbsp;</p>
<p>This format shows two-byte numbers represented as an four-digit hex number.&nbsp;</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.&nbsp;</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.&nbsp;</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.&nbsp;</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&nbsp; </h3>
<blockquote>
<p>This view shows four byte numbers represented in decimal format.&nbsp;</p>
<p>This view shows four-byte numbers represented in decimal format.&nbsp;</p>
<p> This view does not support <a href="#EditBytes">editing</a>.</p>
</blockquote>

View File

@ -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;

View File

@ -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)
*/

View File

@ -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.
*/

View File

@ -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

View File

@ -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() {
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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() {
}
}

View File

@ -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");
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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";
}
}
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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) {
}
}

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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 + ".");

View File

@ -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().

View File

@ -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

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);

View File

@ -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");
}
}

View File

@ -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 {

View File

@ -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);
}

View File

@ -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);
}
});
}

View File

@ -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

View File

@ -119,7 +119,7 @@ public class TestDummyProjectData implements ProjectData {
}
@Override
public void refresh(boolean force) throws IOException {
public void refresh(boolean force) {
// stub
}

View File

@ -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 {

View File

@ -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");
}
}
}

View File

@ -21,7 +21,7 @@ public enum ArchiveType {
FILE,
PROJECT,
PROGRAM,
TEST;
TEMPORARY;
//@formatter:on
public boolean isBuiltIn() {

View File

@ -923,7 +923,7 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos
@Override
public ArchiveType getType() {
return ArchiveType.TEST;
return ArchiveType.TEMPORARY;
}
/**

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -132,6 +132,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
[<a href="#max-cpu">-max-cpu &lt;max cpu cores to use&gt;</a>]
[<a href="#librarySearchPaths">-librarySearchPaths &lt;path1&gt;[;&lt;path2&gt;...]</a>]
[<a href="#loader">-loader &lt;desired loader name&gt;</a>]
[<a href="#loader">-loader-&lt;loader argument name&gt; &lt;loader argument value&gt;</a>]
</PRE>