GP-4559 Creating new Memory Search Feature that include dynamic change

detection
This commit is contained in:
ghidragon 2024-06-12 15:50:20 -04:00
parent 86c126b7f6
commit 7f7559df56
134 changed files with 15580 additions and 3105 deletions

View File

@ -4,9 +4,9 @@
* 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.
@ -778,10 +778,18 @@ public class DebuggerCoordinates {
return coords;
}
public boolean isAlive() {
public static boolean isAlive(Target target) {
return target != null && target.isValid();
}
public boolean isAlive() {
return isAlive(target);
}
public static boolean isAliveAndPresent(TraceProgramView view, Target target) {
return isAlive(target) && target.getSnap() == view.getSnap();
}
protected boolean isPresent() {
TraceSchedule defaultedTime = getTime();
return target.getSnap() == defaultedTime.getSnap() && defaultedTime.isSnapOnly();

View File

@ -0,0 +1,112 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Stream;
import db.Transaction;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.debug.gui.action.DebuggerReadsMemoryTrait;
import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.program.TraceProgramView;
/**
* A byte source for searching the memory of a possibly-live target in the debugger.
*
* <p>
* Because we'd like the search to preserve its state over the lifetime of the target, and the
* target "changes" by navigating snapshots, we need to allow the view to move without requiring a
* new byte source to be constructed. We <em>cannot</em>, however, just blindly follow the
* {@link Navigatable} wherever it goes. This is roughly the equivalent of a {@link Program}, but
* with knowledge of the target to cause a refresh of actual target memory when necessary.
*/
public class DebuggerByteSource implements AddressableByteSource {
private final PluginTool tool;
private final TraceProgramView view;
private final Target target;
private final DebuggerReadsMemoryTrait readsMem;
public DebuggerByteSource(PluginTool tool, TraceProgramView view, Target target,
DebuggerReadsMemoryTrait readsMem) {
this.tool = tool;
this.view = view;
this.target = target;
this.readsMem = readsMem;
}
@Override
public int getBytes(Address address, byte[] bytes, int length) {
AddressSet set = new AddressSet(address, address.add(length - 1));
try {
readsMem.getAutoSpec()
.readMemory(tool, DebuggerCoordinates.NOWHERE.view(view).target(target), set)
.get(Target.TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
return view.getMemory().getBytes(address, bytes, 0, length);
}
catch (AddressOutOfBoundsException | MemoryAccessException | InterruptedException
| ExecutionException | TimeoutException e) {
return 0;
}
}
@Override
public List<SearchRegion> getSearchableRegions() {
AddressFactory factory = view.getTrace().getBaseAddressFactory();
List<AddressSpace> spaces = Stream.of(factory.getPhysicalSpaces())
.filter(s -> s.getType() != AddressSpace.TYPE_OTHER)
.toList();
if (spaces.size() == 1) {
return DebuggerSearchRegionFactory.ALL.stream()
.map(f -> f.createRegion(null))
.toList();
}
Stream<AddressSpace> concat =
Stream.concat(Stream.of((AddressSpace) null), spaces.stream());
return concat
.flatMap(s -> DebuggerSearchRegionFactory.ALL.stream().map(f -> f.createRegion(s)))
.toList();
}
@Override
public void invalidate() {
try (Transaction tx = view.getTrace().openTransaction("Invalidate memory")) {
TraceMemoryManager mm = view.getTrace().getMemoryManager();
for (AddressSpace space : view.getTrace().getBaseAddressFactory().getAddressSpaces()) {
if (!space.isMemorySpace()) {
continue;
}
TraceMemorySpace ms = mm.getMemorySpace(space, false);
if (ms == null) {
continue;
}
ms.setState(view.getSnap(), space.getMinAddress(), space.getMaxAddress(),
TraceMemoryState.UNKNOWN);
}
}
}
}

View File

@ -0,0 +1,130 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui;
import java.util.List;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
public enum DebuggerSearchRegionFactory {
FULL_SPACE("All Addresses", """
Searches all memory in the space, regardless of known validity.""") {
@Override
AddressSetView getAddresses(AddressSpace space, Program program) {
AddressSet set = new AddressSet();
if (space != null) {
set.add(space.getMinAddress(), space.getMaxAddress());
return set;
}
for (AddressSpace s : program.getAddressFactory().getAddressSpaces()) {
set.add(s.getMinAddress(), s.getMaxAddress());
}
return set;
}
},
VALID("Valid Addresses", """
Searches listed memory regions in the space.""") {
@Override
AddressSetView getAddresses(AddressSpace space, Program program) {
AddressSet set = new AddressSet();
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (space == null || space == block.getStart().getAddressSpace()) {
set.add(block.getAddressRange());
}
}
return set;
}
@Override
boolean isDefault(AddressSpace space) {
return space == null;
}
},
WRITABLE("Writable Addresses", """
Searches listed regions marked as writable in the space.""") {
@Override
AddressSetView getAddresses(AddressSpace space, Program program) {
AddressSet set = new AddressSet();
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (block.isWrite() &&
(space == null || space == block.getStart().getAddressSpace())) {
set.add(block.getAddressRange());
}
}
return set;
}
};
public static final List<DebuggerSearchRegionFactory> ALL = List.of(values());
record DebuggerSearchRegion(DebuggerSearchRegionFactory factory, AddressSpace spaces)
implements SearchRegion {
@Override
public String getName() {
return factory.getName(spaces);
}
@Override
public String getDescription() {
return factory.getDescription(spaces);
}
@Override
public AddressSetView getAddresses(Program program) {
return factory.getAddresses(spaces, program);
}
@Override
public boolean isDefault() {
return factory.isDefault(spaces);
}
}
private final String namePrefix;
private final String description;
private DebuggerSearchRegionFactory(String namePrefix, String description) {
this.namePrefix = namePrefix;
this.description = description;
}
public SearchRegion createRegion(AddressSpace space) {
return new DebuggerSearchRegion(this, space);
}
String getName(AddressSpace space) {
if (space == null) {
return namePrefix;
}
return "%s (%s)".formatted(namePrefix, space.getName());
}
String getDescription(AddressSpace spaces) {
return description;
}
abstract AddressSetView getAddresses(AddressSpace space, Program program);
boolean isDefault(AddressSpace space) {
return false;
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -51,8 +51,7 @@ import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand;
import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand.Reqs;
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin;
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.OpenProgramAction;
import ghidra.app.plugin.core.debug.gui.action.*;
@ -76,6 +75,8 @@ import ghidra.debug.api.listing.MultiBlendedListingBackgroundColorModel;
import ghidra.debug.api.modules.DebuggerMissingModuleActionContext;
import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.EmptyByteSource;
import ghidra.framework.model.DomainFile;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
@ -1358,4 +1359,12 @@ public class DebuggerListingProvider extends CodeViewerProvider {
.setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset());
});
}
@Override
public AddressableByteSource getByteSource() {
if (current == DebuggerCoordinates.NOWHERE) {
return EmptyByteSource.INSTANCE;
}
return new DebuggerByteSource(tool, current.getView(), current.getTarget(), readsMemTrait);
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -34,8 +34,7 @@ import docking.menu.MultiStateDockingAction;
import docking.widgets.fieldpanel.support.ViewerPosition;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.plugin.core.byteviewer.*;
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
@ -46,6 +45,8 @@ import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.action.GoToInput;
import ghidra.debug.api.action.LocationTrackingSpec;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.EmptyByteSource;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
@ -666,4 +667,12 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
newProvider.panel.setViewerPosition(vp);
});
}
@Override
public AddressableByteSource getByteSource() {
if (current == DebuggerCoordinates.NOWHERE) {
return EmptyByteSource.INSTANCE;
}
return new DebuggerByteSource(tool, current.getView(), current.getTarget(), readsMemTrait);
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -960,4 +960,35 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
assertArrayEquals(b.arr(1, 2, 3, 4), read.array());
}
}
@Test
public void testReplicateNpeScenario() throws Exception {
ByteBuffer buf4k = ByteBuffer.allocate(0x1000);
AddressSetView set = b.set(
b.range(0x00400000, 0x00404fff),
b.range(0x00605000, 0x00606fff),
b.range(0x7ffff7a2c000L, 0x7ffff7a33fffL));
Random random = new Random();
for (int i = 0; i < 30; i++) {
try (Transaction tx = b.startTransaction()) {
for (int j = 0; j < 3; j++) {
for (AddressRange r : set) {
for (AddressRange rc : new AddressRangeChunker(r, 0x1000)) {
if (random.nextInt(100) < 20) {
memory.setState(0, rc, TraceMemoryState.ERROR);
continue;
}
buf4k.position(0);
buf4k.limit(0x1000);
memory.putBytes(0, rc.getMinAddress(), buf4k);
}
}
}
}
try (Transaction tx = b.startTransaction()) {
memory.setState(0, b.range(0, -1), TraceMemoryState.UNKNOWN);
}
}
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -1097,6 +1097,23 @@ public class RStarTreeMapTest {
assertTrue(obj.map.isEmpty());
}
@Test
public void testAddThenRemove1() {
assertTrue(obj.map.isEmpty());
try (Transaction tx = obj.openTransaction("AddPoint")) {
obj.map.put(new ImmutableIntRect(0, 0, 0, 10), "Test");
}
assertFalse(obj.map.isEmpty());
try (Transaction tx = obj.openTransaction("RemovePoint")) {
assertTrue(obj.map.remove(new ImmutableIntRect(0, 0, 0, 10), "Test"));
}
assertTrue(obj.map.isEmpty());
}
@Test
public void testClear() {
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));

View File

@ -508,8 +508,10 @@ src/main/help/help/topics/RuntimeInfoPlugin/RuntimeInfo.htm||GHIDRA||||END|
src/main/help/help/topics/ScalarSearchPlugin/The_Scalar_Table.htm||GHIDRA||||END|
src/main/help/help/topics/ScalarSearchPlugin/images/ScalarWindow.png||GHIDRA||||END|
src/main/help/help/topics/ScalarSearchPlugin/images/SearchAllScalarsDialog.png||GHIDRA||||END|
src/main/help/help/topics/Search/Instruction_Mnemonic_Search.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Query_Results_Dialog.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Regular_Expressions.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Formats.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Instruction_Patterns.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Memory.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Program_Text.htm||GHIDRA||||END|
@ -520,6 +522,9 @@ src/main/help/help/topics/Search/Searching.htm||GHIDRA||||END|
src/main/help/help/topics/Search/images/DirectReferences.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/EncodedStringsDialog_advancedoptions.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/EncodedStringsDialog_initial.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MemorySearchProvider.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MultipleSelectionError.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/QueryResultsSearch.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchForAddressTables.png||GHIDRA||||END|
@ -536,11 +541,7 @@ src/main/help/help/topics/Search/images/SearchInstructionsIncludeOperands.png||G
src/main/help/help/topics/Search/images/SearchInstructionsIncludeOperandsNoConsts.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchInstructionsManualSearchDialog.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchLimitExceeded.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryBinary.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryDecimal.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryHex.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryRegex.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchMemoryString.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchText.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/StringSearchDialog.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/StringSearchResults.png||GHIDRA||||END|
@ -919,6 +920,11 @@ src/main/resources/images/unlock.gif||GHIDRA||||END|
src/main/resources/images/verticalSplit.png||GHIDRA||||END|
src/main/resources/images/view-sort-ascending.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/view-sort-descending.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/view_bottom.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/view_left_right.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/view_top_bottom.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/viewmag+.png||Crystal Clear Icons - LGPL 2.1||||END|
src/main/resources/images/viewmag.png||GHIDRA||||END|
src/main/resources/images/window.png||GHIDRA||||END|
src/main/resources/images/wizard.png||Nuvola Icons - LGPL 2.1|||nuvola|END|
src/main/resources/images/x-office-document-template.png||Tango Icons - Public Domain|||tango icon set|END|

View File

@ -395,6 +395,11 @@ icon.base.util.xml.functions.bookmark = imported_bookmark.gif
icon.base.util.datatree.version.control.archive.dt.checkout.undo = vcUndoCheckOut.png
icon.base.mem.search.panel.options = view_left_right.png
icon.base.mem.search.panel.scan = view_bottom.png
icon.base.mem.search.panel.search = view_top_bottom.png

View File

@ -0,0 +1,112 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Search Memory</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
<META name="generator" content="Microsoft FrontPage 4.0">
</HEAD>
<BODY lang="EN-US">
<BLOCKQUOTE>
<H1><A name="Mnemonic_Search"></A>Search for Matching Instructions</H1>
<BLOCKQUOTE>
<P>This action works only on a selection of code. It uses the selected instructions to build
a combined mask/value bit pattern that is then used to populate the search field in a
<A href="Search_Memory.htm">Memory Search Window</A>. This enables searching through memory
for a particular ordering of
instructions. There are three options available:&nbsp;</P>
<UL>
<LI><B>Include Operands</B> - All bits that make up the instruction and all bits that make
up the operands will be included in the search pattern.</LI>
<LI><B>Exclude Operands</B> - All bits that make up the instruction are included in the
search pattern but the bits that make up the operands will be masked off to enable wild
carding for those bits.</LI>
<LI><B>Include Operands (except constants)</B> - All bits that make up the instruction are
included in the search pattern and all bits that make up the operands, except constant
operands, which will be masked off to enable wild carding for those bits.</LI>
</UL>
<BLOCKQUOTE>
<P>Example:</P>
<P>A user first selects the following lines of code. Then, from the Search menu they choose
<B>Search for Matching Instructions</B> and one of the following options:</P>
<P align="center"><IMG border="1" src="images/SearchInstructions.png" alt=""></P>
<B>Option 1:</B>
<BLOCKQUOTE>
<P>If the <B>Include Operands</B> action is chosen then the search will find all
instances of the following instructions and operands.</P>
<P align="center"><IMG border="1" src="images/SearchInstructionsIncludeOperands.png" alt=
""></P>
<P>All of the bytes that make up the selected code will be searched for exactly, with no
wild carding. The bit pattern <B>10000101 11000000 01010110 01101010 00010100
01011110</B> which equates to the byte pattern <B>85 c0 56 6a 14 5e</B> is searched
for.<BR>
<BR>
</P>
</BLOCKQUOTE><B>Option 2:</B>
<BLOCKQUOTE>
<P>If the <B>Exclude Operands</B> option is chosen then the search will find all
instances of the following instructions only.</P>
<P align="center"><IMG border="1" src="images/SearchInstructionsExcludeOperands.png" alt=
""></P>
<P>Only the parts of the byte pattern that make up the instructions will be searched for
with the remaining bits used as wildcards. The bit pattern <B>10000101 11...... 01010...
01101010 ........ 01011...</B> is searched for where the .'s indicate the wild carded
values.<BR>
<BR>
</P>
</BLOCKQUOTE><B>Option 3:</B>
<BLOCKQUOTE>
<P>If the <B>Include Operands (except constants)</B> option is chosen then the search
will find all instances of the instruction and all operands except the 0x14 which is a
constant.</P>
<P align="center"><IMG border="1" src=
"images/SearchInstructionsIncludeOperandsNoConsts.png" alt=""></P>
<P>The bit pattern <B>10000101 11000000 01010110 01101010 ........ 01011110</B> which
equates to the byte pattern <B>85 c0 56 6a xx 5e</B> is searched for where xx can be any
number N between 0x0 and 0xff.<BR>
<BR>
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P><IMG alt="Note" src="help/shared/note.png">The previous operations can only work on a
<B>single</B> selected region. If multiple regions are selected, the following error dialog
will be shown and the operation will be cancelled.</P>
<P align="center"><IMG border="1" src="images/MultipleSelectionError.png" alt=""></P>
<BR>
<BR>
<P class="providedbyplugin">Provided by: <I>Mnemonic Search Plugin</I></P>
<P class="relatedtopic">Related Topics:</P>
<UL>
<LI><A href="help/topics/Search/Search_Memory.htm">Search Memory</A></LI>
</UL><BR>
<BR>
</BLOCKQUOTE>
</BODY>
</HTML>

View File

@ -19,17 +19,21 @@
same as a regular expression for any standard Java application. Because of restrictions on how
regular expressions are processed, regular expression searches can only be performed in the
forward direction. Unlike standard string searches, case sensitivity and unicode options do not
apply. The <I>Search Memory</I> dialog below shows a sample regular expression entered in the
apply. The <I>Search Memory</I> window below shows a sample regular expression entered in the
<I>Value</I> field.</P>
<TABLE border="0" width="100%">
<BLOCKQUOTE>
<BLOCKQUOTE>
<TABLE border="0" width="80%">
<TBODY>
<TR>
<TD width="100%" align="center"><IMG border="0" src="images/SearchMemoryRegex.png" alt=""></TD>
<TD align="left"><IMG border="0" src="images/SearchMemoryRegex.png" alt=""></TD>
</TR>
</TBODY>
</TABLE>
</BLOCKQUOTE>
<H3>Examples</H3>
<BLOCKQUOTE>

File diff suppressed because it is too large Load Diff

View File

@ -12,467 +12,525 @@
</HEAD>
<BODY lang="EN-US">
<H1>Search Memory</H1>
<P>Search Memory locates sequences of bytes in program memory. &nbsp;The search is based on a
value entered as hex numbers, decimal numbers or strings.&nbsp; The byte sequence may contain
"wildcards" that will match any byte (or possibly nibble). String searching also allows for the
use of <A href="#RegularExpression">regular expression</A> searches.</P>
<P>To Search Memory:</P>
<BLOCKQUOTE>
<OL>
<LI>From the Tool, select <B>Search</B><IMG alt="" border="0" src="help/shared/arrow.gif">
<B>Memory</B></LI>
<A name="Memory Search"></A>
<LI>Enter a Hex String in the Value field<BR>
This will create a Hex Sequence for searching.</LI>
<H1>Search Memory</H1>
<LI>Choose "Next" to find the next occurrence<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
- or -<BR>
Choose "Previous" to find the previous occurrence<BR>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
- or -<BR>
Choose "Search All" to find all occurrences.</LI>
</OL>
</BLOCKQUOTE>
<P>The memory search feature locates sequences of bytes in program memory. The search is
based on user entered values which can be specified in a variety of formats such as hex,
binary, string, and decimal values. The hex and binary formats support "wildcards" which can
match any bit or nibbles depending on the format. String search also supports the use of <A
href="Search_Formats.htm#RegularExpressions">regular expression</A> searches.</P>
<H2>Search Formats</H2>
<BLOCKQUOTE>
<UL type="disc">
<LI><A href="#Hex">Hex</A></LI>
<LI><A href="#String">String</A></LI>
<LI><A href="#Decimal">Decimal</A></LI>
<LI><A href="#Binary">Binary</A></LI>
<LI><A href="#Regular_Expression">Regular Expression</A></LI>
</UL>
</BLOCKQUOTE>
<P align="center"><IMG src="images/SearchMemoryHex.png" border="0" alt=""> &nbsp;</P>
<H2>Search Options</H2>
<BLOCKQUOTE>
<H3>Search</H3>
<P>To create a new memory search window, select <B>Search</B> <IMG alt="" border="0" src=
"help/shared/arrow.gif"><B>Memory</B> from the main tool menu or use the default keybinding
"S".</P>
<P><IMG alt="" border="0" src="help/shared/tip.png">By default, search windows and their
tabs display "Search Memory:" followed by the search string and the program name. This
can be changed by right-clicking on the title or table to change its name. (This is true
for all transient windows.)</P>
<H2>Contents</H2>
<BLOCKQUOTE>
<H4>Search Value</H4>
</BLOCKQUOTE>
<UL>
<LI>The value to search.&nbsp; The values entered will be interpreted based on the
<I>Format</I> options.</LI>
</UL>
<BLOCKQUOTE>
<H4>Hex Sequence</H4>
</BLOCKQUOTE>
<UL>
<LI>As the search value is entered, this field will display the exact hex byte sequence
that will be searched for in memory.</LI>
</UL>
<H3>Format</H3>
<BLOCKQUOTE>
<H4><A name="Hex"></A><B>Hex:</B></H4>
</BLOCKQUOTE>
<UL>
<LI>Value is interpreted as a sequence of hex numbers, separated by spaces.&nbsp; Wildcard
characters can be used to match any single hex digit (i.e. any 4 bit value). &nbsp;Either
the '.' or '?' character can be used for the wildcard character.</LI>
<LI>Each hex number (separated by spaces) will produce a sequence of bytes that may be
reversed depending on the Byte Order.</LI>
<LI>The byte search pattern is formed by concatenating the bytes from each hex number.</LI>
</UL>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P><B>Example:</B></P>
<TABLE border="1" width="526">
<TBODY>
<TR>
<TD width="260">
<P><B>Value:&nbsp;&nbsp;&nbsp;</B></P>
</TD>
<TD width="252">
<P>&nbsp;"1234 567 89ab"</P>
</TD>
</TR>
<TR>
<TD width="260">
<P><B><A href="help/topics/Glossary/glossary.htm#LittleEndian">Little Endian</A>
Hex Sequence&nbsp;&nbsp;&nbsp;</B></P>
</TD>
<TD width="252">
<P>34 12 67 05 ab 89</P>
</TD>
</TR>
<TR>
<TD width="260">
<P><B><A href="help/topics/Glossary/glossary.htm#BigEndian">Big Endian</A> Hex
Sequence&nbsp;&nbsp;&nbsp;</B></P>
</TD>
<TD width="252">
<P>12 34 05 67 89 ab</P>
</TD>
</TR>
</TBODY>
</TABLE>
<P><IMG alt="" border="0" src="help/shared/tip.png">
As a convenience, if a user enters a single wildcard value within the search text, then
the search string will be interpreted as if 2 consecutive wildcard characters were
entered, meaning to match any byte value.
</P>
<P>
Similarly, if the search string contains an odd number of characters, then a 0 is prepended
to the search string, based on the assumption that a single hex digit implies a leading
0 value.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P>&nbsp;</P>
<BLOCKQUOTE>
<H4><A name="String"></A><B>String:</B></H4>
<BLOCKQUOTE>
<P>Value is interpreted as the specified character encoding. The center panel of the
Search Memory dialog shows the <I>Format Options</I>, described below.</P>
<P align="center"><IMG border="0" src="images/SearchMemoryString.png" alt=""></P>
<UL>
<LI><I>Encoding</I> - Interprets strings by the specified encoding.&nbsp; Note that
byte ordering determines if the high order byte comes first or last.</LI>
<LI><I>Case Sensitive</I> - Turning off this option will search for the string
regardless of case using the specified character encoding. Only applicable for English
characters.</LI>
<LI><I>Escape Sequences</I> - Enabling this option allows escape sequences in the
search value (i.e., allows \n to be searched for).</LI>
</UL>
</BLOCKQUOTE>
<H4><A name="Decimal"></A><B>Decimal:</B></H4>
<BLOCKQUOTE>
<P>Value is interpreted as a sequence of decimal numbers, separated by spaces. The center
panel of the Search Memory dialog shows the <I>Decimal Options</I>, described below.</P>
</BLOCKQUOTE>
<P align="center"><IMG border="0" src="images/SearchMemoryDecimal.png" alt=""></P>
<UL>
<LI>Only numbers that fit the specified Decimal Options are allowed to be entered.</LI>
<LI>The byte search pattern is formed by concatenating the bytes from each&nbsp;
number.</LI>
<LI>
Valid decimal numbers are:
<A href="#Memory_Search_Window">Memory Search Window</A>
<UL>
<LI><B>Byte</B> - any fixed point 8 bit number&nbsp; (-128 to 255)</LI>
<LI><A href="#Search_Controls">Search Controls</A></LI>
<LI><B>Word</B> - any fixed point 16 bit number (-32768 to 65535)</LI>
<LI><A href="#Scan_Controls">Scan Controls</A></LI>
<LI><B>DWord</B> - any fixed point 32 bit number (you get the idea.....)</LI>
<LI><A href="#Results_Table">Results Table</A></LI>
<LI><B>QWord</B> - any fixed point 64 bit number</LI>
<LI><B>Float</B> - any 32 bit floating point number</LI>
<LI><B>Double</B> any 64 bit floating point number</LI>
<LI><A href="#Options">Options</A></LI>
</UL>
</LI>
<LI><A href="#Search_Formats">Search Formats</A></LI>
<LI><A href="#Actions">Actions</A></LI>
<LI><A href="#Combining_Searches">Combining Searches</A></LI>
<LI><A href="#Repeating_Searches">Repeating Last Search Forwards/Backwards</A></LI>
<LI><A href="#Highlight_Options">Highlight Search Options</A></LI>
</UL>
</BLOCKQUOTE><A name="Memory_Search_Window"></A>
<H4><A name="Binary"></A><B>Binary:</B></H4>
<H2>Memory Search Window</H2>
<BLOCKQUOTE>
<P>Value is interpreted as a sequence of binary numbers, separated by spaces.&nbsp;
Wildcard characters ('x' or '?' or '.') can be used to match any bit.</P>
</BLOCKQUOTE>
<P>The Memory Search Window provides controls and options for entering search values and
and a table for displaying the search results. It supports both bulk searching and
incremental searching forwards and backwards. Also, users can perform additional searches
and combine those results with the existing results using set operations. The window also
has a "scan" feature which will re-examine the bytes of the current results set, looking
for memory changes at those locations (this is most useful when debugging). Scanning has an
option for reducing the results to those that either changed, didn't change, were
incremented, or were decremented.</P>
<P align="center"><IMG border="0" src="images/SearchMemoryBinary.png" alt=""></P>
<P align="center"><IMG src="images/MemorySearchProvider.png" border="0" alt=""> &nbsp;</P>
<UL>
<LI>Only binary digits (0 or 1) or wildcard characters (*?.) are allowed to be
entered.</LI>
<P align="center"><I>Memory Search Window</I></P>
<A name="Search_Controls"></A>
<LI>The byte search pattern is formed by concatenating the bytes from each&nbsp;
number.</LI>
<LI>An additional Mask byte which is not shown, is generated for each search byte to handle
the wildcards.</LI>
</UL>
<H4><A name="Regular_Expression"></A><B>Regular Expression:</B></H4>
<BLOCKQUOTE>
<P>Value is interpreted as a Java <A name="RegularExpression"></A><I>Regular Expression</I>
that is matched against memory as if all memory was a string. Help on how to form regular
expressions is available on the <A href="Regular_Expressions.htm">Regular Expression
Help</A> page.</P>
</BLOCKQUOTE>
<P align="center"><IMG border="0" src="images/SearchMemoryRegex.png" alt=""></P>
<UL>
<LI>Regular Expressions can only be used to search forward in memory.</LI>
<LI>No Hex Sequence is displayed for regular expressions.</LI>
</UL>
</BLOCKQUOTE>
<H3>Memory Block Types</H3>
<UL>
<LI>Selects which initialized memory blocks are searched. Ghidra now stores external
information from the program's file header in special memory blocks. These blocks do not live
in the program's address space, but instead are stored in the "OTHER" address space. Memory
blocks which would be found in an actual running version of the program are referred to as
"Loaded Memory Blocks."</LI>
<LI style="list-style: none">
<UL>
<LI>Loaded Blocks - will search only "loaded" memory blocks (memory blocks that would
appear in an actual running instance of the program) and not "Other" information memory
blocks.</LI>
<LI>All Blocks - will search all memory blocks including "Other" blocks.</LI>
</UL>
</LI>
</UL>
<P>&nbsp;</P>
<H3>Selection Scope</H3>
<UL type="disc">
<LI><B>Search All</B> - If this option is selected, the search will search all memory in the
tool.</LI>
<LI><B>Search Selection</B> - If this option is selected, the search will be restricted to
the current selection in the tool. This option is only enabled if there is a current
selection in the tool.</LI>
</UL>
<P>&nbsp;</P>
<H3><B>Code Unit Scope</B></H3>
<BLOCKQUOTE>
<P>Filters the matches based upon the code unit containing a given address.</P>
<UL type="disc">
<LI><B>Instructions</B> - includes instruction code units in the search.</LI>
<LI><B>Defined Data</B> - includes defined data in the search.</LI>
<LI><B>Undefined Data</B> - includes undefined data in the search.</LI>
</UL>
</BLOCKQUOTE>
<H3><B>Byte Order</B></H3>
<BLOCKQUOTE>
<P>Sets the byte ordering for multi-byte values.&nbsp; Has no effect on non-Unicode Ascii
values, Binary, or regular expressions.</P>
<P><B>Little Endian</B> - places low-order bytes first.<BR>
For example, the hex number "1234" will generate the bytes "34" , "12".</P>
<P><B>Big Endian</B> - places high-order bytes first.<BR>
For example, the hex number "1234" will generate the bytes "12", "34".</P>
</BLOCKQUOTE>
<H3>Alignment</H3>
<UL type="disc">
<LI>Generally the alignment defaults to 1, but can be set to any number greater than 0. The
search results will be limited to those that begin on the specified byte alignment. In other
words, an alignment of 1 will get all matching results regardless of the address where each
begins. An alignment of 2 will only return matching results that begin on a word aligned
address.</LI>
</UL>
<P>&nbsp;</P>
<H3>Searching</H3>
<UL>
<LI>Next / Previous - Finds the next/previous occurrence of the byte pattern from the current
cursor location; if you mouse click in the Code Browser to move focus there, you can choose
<B><A name="Repeat_Memory_Search"></A>Search</B><IMG alt="" border="0" src=
"help/shared/arrow.gif"> <B>Repeat Memory Search</B> to go to the next/previous match
found.</LI>
<LI>Search All - Finds all occurrences of the byte pattern in a <A href=
"Query_Results_Dialog.htm">Query Results display</A>.</LI>
</UL>
<BLOCKQUOTE>
<P><IMG alt="" border="0" src="help/shared/tip.png"> For very large Programs that may take a
while to search, you can cancel the search at any time. For these situations, a progress bar
is displayed, along with a <B><FONT size="4">Cancel</FONT></B> button. Click on the <B><FONT
size="4">Cancel</FONT></B> button to stop the search.&nbsp;</P>
<P><IMG alt="" border="0" src="help/shared/note.png"> Dismissing the search dialog
automatically cancels the search operation.</P>
</BLOCKQUOTE>
<H3>&nbsp;</H3>
<H3>Highlight Search Option</H3>
<BLOCKQUOTE>
<P>You can specify that the bytes found in the search be highlighted in the Code Browser by
selecting the <I>Highlight Search Results</I> checkbox on the Search Options panel. To view
the Search Options, select <B>Edit</B><IMG alt="" border="0" src="help/shared/arrow.gif">
<B>Tool Options...</B> from the tool menu, then select the <I>Search</I> node in the Options
tree in the Options dialog. You can also change the highlight color. Click on the color bar
next to <I>Highlight Color</I> to bring up a color chooser. Choose the new color, click on
the <B>OK</B> button. Apply your changes by clicking on the <B>OK</B> or <B>Apply</B> button
on the Options dialog.&nbsp;</P>
<BLOCKQUOTE>
<P><IMG alt="" border="0" src="help/shared/note.png"> Highlights are displayed for the last
search that you did. For example, if you bring up the Search Program Text dialog and search
for text, that string now becomes the new highlight string. Similarly, if you invoke <A href=
"help/topics/CodeBrowserPlugin/CodeBrowser.htm#cursorTextHighlight">cursor text
highlighting</A>, that becomes the new highlight string.</P>
</BLOCKQUOTE>
<P>Highlights are dropped when you close the search dialog, or close the query results window
for your most recent search.</P>
<BR><BR>
</BLOCKQUOTE>
<H3><A name="Mnemonic_Search"></A>Search for Matching Instructions</H3>
<BLOCKQUOTE>
<P>This action works only on a selection of code. It uses the selected instructions to build
a combined mask/value bit pattern that is then used to populate the search field in the
Memory Search Dialog. This enables searching through memory for a particular ordering of
instructions. There are three options available:&nbsp;</P>
<UL>
<LI><B>Include Operands</B> - All bits that make up the instruction and all bits that make
up the operands will be included in the search pattern.</LI>
<LI><B>Exclude Operands</B> - All bits that make up the instruction are included in the
search pattern but the bits that make up the operands will be masked off to enable wild
carding for those bits.</LI>
<LI><B>Include Operands (except constants)</B> - All bits that make up the instruction are
included in the search pattern and all bits that make up the operands, except constant
operands, which will be masked off to enable wild carding for those bits.</LI>
</UL>
<BLOCKQUOTE>
<P>Example:</P>
<P>A user first selects the following lines of code. Then, from the Search menu they choose
<B>Search for Matching Instructions</B> and one of the following options:</P>
<P align="center"><IMG border="1" src="images/SearchInstructions.png" alt=""></P>
<B>Option 1:</B>
<H3>Search Controls</H3>
<BLOCKQUOTE>
<P>If the <B>Include Operands</B> action is chosen then the search will find all
instances of the following instructions and operands.</P>
<P>At the top of the window as shown above, there are several GUI elements for initializing and
executing a memory byte search. These controls can be closed from the view after a search
to give more space to view results using the <IMG alt="" border="0"
src="icon.base.mem.search.panel.search"> toolbar button.</P>
<P align="center"><IMG border="1" src="images/SearchInstructionsIncludeOperands.png" alt=
""></P>
<H4>Search Format Field:</H4>
<P>All of the bytes that make up the selected code will be searched for exactly, with no
wild carding. The bit pattern <B>10000101 11000000 01010110 01101010 00010100
01011110</B> which equates to the byte pattern <B>85 c0 56 6a 14 5e</B> is searched
for.<BR>
<BR>
</P>
</BLOCKQUOTE><B>Option 2:</B>
<BLOCKQUOTE>
<P>This is a drop-down list of formats whose selected format determines how to
interpret the text in the <I>Search Input Field</I>. The format will convert the user's
input into a sequence of bytes (and possibly masks). Details on each format are
described in the <A href="Search_Formats.htm"><I>Search Formats</I></A> section.</P>
</BLOCKQUOTE>
<H4>Search Input Field:</H4>
<BLOCKQUOTE>
<P>Next to the <I>Search Format</I> drop-down, there is a text field where users can
enter one or more values to be searched. This field performs validation depending on
the active format. For example, when the format is <I>Hex</I>, users can only enter
hexadecimal values.</P>
</BLOCKQUOTE>
<H4>Previous Search Drop Down:</H4>
<BLOCKQUOTE>
<P>At the end of the input field, there is a drop-down list of previous searches.
Selecting one of these will populate the input field with that previous search input,
as well as the relevant settings that were used in that search such as the search
format.</P>
</BLOCKQUOTE>
<H4>Search Button:</H4>
<BLOCKQUOTE>
<P>Pressing the search button will initiate a search. When the results table is empty,
the only choice is to do an initial search. If there are current results showing in the
table, a drop-down will appear at the back of the button, allowing the user to combine
new search results with the existing results using set operations. See the
<A href="#Combining_Searches"><I>Combining Searches</I></A> section
below for more details.</P>
</BLOCKQUOTE>
<H4>Byte Sequence Field:</H4>
<BLOCKQUOTE>
<P>This field is used to show the user the bytes sequence that will be search for based
on the format and the user input entered. Hovering on this field will also display the
masks that will be used (if applicable).</P>
</BLOCKQUOTE>
<H4>Selection Only Checkbox:</H4>
<BLOCKQUOTE>
<P>If there is a current selection, then this checkbox will be enabled and provide the
user with the option to restrict the search to only the selected addresses. Note that
there is an action that controls whether this option will be selected automatically if
a selection exists.</P>
</BLOCKQUOTE>
</BLOCKQUOTE><A name="Scan_Controls"></A>
<H3>Scan Controls</H3>
<BLOCKQUOTE>
<P>If the <B>Exclude Operands</B> option is chosen then the search will find all
instances of the following instructions only.</P>
<P>The scan controls are used to re-examine search results, looking for values that have
changed since the search was initiated. This is primary useful when debugging. The
scan controls are not showing by default. Pressing the <IMG alt="" border="0"
src="icon.base.mem.search.panel.scan"> toolbar button will show them along the right side of the
window</P>
<P align="center"><IMG border="1" src="images/SearchInstructionsExcludeOperands.png" alt=
""></P>
<P align="center"><IMG src="images/MemorySearchProviderWithScanPanelOn.png" border="0"
alt=""> &nbsp;</P>
<P>Only the parts of the byte pattern that make up the instructions will be searched for
with the remaining bits used as wildcards. The bit pattern <B>10000101 11...... 01010...
01101010 ........ 01011...</B> is searched for where the .'s indicate the wild carded
values.<BR>
<BR>
</P>
</BLOCKQUOTE><B>Option 3:</B>
<P align="center"><I>Memory Search Window With Scan Controls Showing</I></P>
<H4>Scan Values Button:</H4>
<BLOCKQUOTE>
<P>This button initiates a scan of the byte values in all the matches in the results
table. Depending on the selected scan option, the set of matches in the table may be
reduced based on the values that changed.</P>
</BLOCKQUOTE>
<H4>Scan Option Radio Buttons</H4>
<BLOCKQUOTE>
<P>One of the following buttons can be selected and they determine how the set of
current matches should be reduced based on changed values.</P>
<UL>
<LI><B>Equals</B> This option will keep all matches whose values haven't changed and
remove any matches whose bytes have changed.</LI>
<LI><B>Not Equals</B> This option will keep all matches whose values have changed and
will remove any matches whose bytes have not changed.</LI>
<LI><B>Increased</B> This option will keep all matches whose values have increased
and will remove any matches whose values decreased or stayed the same.</LI>
<LI><B>Decreased</B> This option will keep all matches whose values have decreased
and will remove any matches whose values increased or stayed the same.</LI>
</UL>
<P><IMG alt="" border="0" src="help/shared/tip.png">The <I>Increased</I> or
<I>Decreased</I> options really only make sense for matches that represent numerical
values such as integers or floats. In other cases it makes the determination based on
the first byte in the sequence that changed, as if they were a sequence of 1 byte
unsigned values.</P>
<P><IMG alt="" border="0" src="help/shared/tip.png">Another way to see changed bytes is
to use the <A href= "#Refresh_Values"><I>Refresh</I></A> toolbar action. This will
update the bytes for each search result and show them in red without reducing the set
of results.</P>
</BLOCKQUOTE>
</BLOCKQUOTE><A name="Results_Table"></A>
<H3>Results Table</H3>
<BLOCKQUOTE>
<P>If the <B>Include Operands (except constants)</B> option is chosen then the search
will find all instances of the instruction and all operands except the 0x14 which is a
constant.</P>
<P align="center"><IMG border="1" src=
"images/SearchInstructionsIncludeOperandsNoConsts.png" alt=""></P>
<P>The bit pattern <B>10000101 11000000 01010110 01101010 ........ 01011110</B> which
equates to the byte pattern <B>85 c0 56 6a xx 5e</B> is searched for where xx can be any
number N between 0x0 and 0xff.<BR>
<BR>
</P>
<P>The bottom part of the window is the search results table. Each row in the table
represents one search match. The table can contain combined results from multiple
searches. At the bottom of the results table, all the standard table filters are
available. The table has the following default columns, but additional columns can be
added by right-clicking on any column header.</P>
</BLOCKQUOTE>
<UL>
<LI><B>Location:</B> Displays the address of the first byte in the matching
sequence.</LI>
<LI><B>Match Bytes:</B> Displays the bytes of the matching sequence. (Note: if you
refresh or scan, the bytes can change. Changed bytes will be displayed in red.)</LI>
<LI><B>Match Value:</B> Displays the matching bytes as a value where the value is
determined by the <I>Search Format</I>. Note that not all formats have a meaningful
value, in which case the column value will be empty.</LI>
<LI><B>Label:</B> Displays any labels that are present at the match address.</LI>
<LI><B>Code Unit:</B> Displays the instruction or data that the match address.</LI>
</UL><A name="Options"></A>
<H3>Options</H3>
<BLOCKQUOTE>
<P>The options panel is not displayed by default. Pressing the <IMG alt="" border="0"
src="icon.base.mem.search.panel.options"> toolbar button will show them along the right side of the
window.</P>
</BLOCKQUOTE>
<P align="center"><IMG src="images/MemorySearchProviderWithOptionsOn.png" border="0" alt=
""> &nbsp;</P>
<P align="center"><I>Memory Search Window With Options Open</I></P>
<H4>Byte Options</H4>
<BLOCKQUOTE>
<P>These are general options that affect most searches.</P>
<UL>
<LI><B>Endianess:</B> This chooses the byte ordering for values that are larger than
one byte. Big Endian orders the bytes most significant first. Little Endian orders the
bytes least significant first.
<LI><B>Alignment:</B> The alignment requires that matches must start on an address that
has an offset that is a multiple of the specified integer field. For example, an
alignment of two would require that the address have an even value.</LI>
</BLOCKQUOTE>
<H4>Decimal Options</H4>
<BLOCKQUOTE>
<P>These options apply when parsing input as decimal values.</P>
<UL>
<LI><B>Size:</B> The size (in bytes) of the decimal values.</LI>
<LI><B>Unsigned:</B> If checked, the values will be interpreted as unsigned values
and the input field won't accept negative values.</LI>
</UL>
</BLOCKQUOTE>
<H4>String Options</H4>
<BLOCKQUOTE>
<P>These options apply when parsing input as string data.</P>
<UL>
<LI><B>Encoding:</B> Specified the char set used to convert between strings and
bytes. (ASCII, UTF8, or UTF 16) </LI>
<LI><B>Case Sensitive:</B> If unselected, causes mask bytes to be generated such that
the search will not be case sensitive. Otherwise, the bytes must match exactly the
input the user entered.</LI>
<LI><B>Escape Sequences:</B> Determines if standard escape sequences are interpreted
literally or not. For example, if checked, and the user enters "\n", the search will
look for an end of line character. If unchecked, the search will look for a "\"
followed by an "n". Supported escape sequences include "\n", "\r", "\b", "\f", "\0",
"\x##", "\u####", "\U#########".</LI>
</UL>
</BLOCKQUOTE>
<H4>Code Type Filters</H4>
<BLOCKQUOTE>
<P>These are filters that can be applied to choose what type(s) of code units to
include in the results. By default, they are all selected. The types are:</P>
<UL>
<LI><B>Instructions:</B> Include matches that start at or in an instruction.</LI>
<LI><B>Defined Data:</B> Include matches that start at or in a define data.</LI>
<LI><B>Undefined Data:</B> Include matches that start at or in undefined data.</LI>
</UL>
</BLOCKQUOTE>
<H4>Memory Regions</H4>
<BLOCKQUOTE>
<P>Choose one or more memory regions to search. The available regions can vary depending
on the context, but the default regions are:</P>
<UL>
<LI><B>Loaded Blocks:</B> These include all the memory blocks defined that are actually
part of a loaded executable binary. On by default.</LI>
<LI><B>Other Blocks:</B> These are other than loaded blocks, typically representing
file header data. Off by default.</LI>
</UL>
</BLOCKQUOTE>
</BLOCKQUOTE><A name="Search_Formats"></A>
<H2>Search Formats</H2>
<BLOCKQUOTE>
<P>The selected format determines how the user input is used to generate a search byte
sequence (and possibly mask byte sequence). They are also used to format bytes back into
"values" to be displayed in the table, if applicable.</P>
<P>See the page on <A href="Search_Formats.htm">Search Formats</A> for full details on each
format.</P>
</BLOCKQUOTE><A name="Actions"></A>
<H2>Actions</H2>
<BLOCKQUOTE>
<A name="Search_Next"></A>
<H3><IMG alt="" border="0" src="icon.down">&nbsp;&nbsp;Incremental Search Forward</H3>
<BLOCKQUOTE>
<P>This action searches forward in memory, starting at the address just after the current
cursor location. It will continue until a match is found or the highest address in the
search space is reached. It does not "wrap". If a match is found, it it is added to the
current table of results.</P>
</BLOCKQUOTE><A name="Search_Previous"></A>
<H3><IMG alt="" border="0" src="icon.up">&nbsp;&nbsp;Incremental Search Backwards</H3>
<BLOCKQUOTE>
<P>This action searches backwards in memory, starting at the address just before the
current cursor location. It will continue until a match is found or the lowest address in
the search space is reached. It does not "wrap". If a match is found, it it is added to
the current table of results.</P>
</BLOCKQUOTE><A name="Refresh_Values"></A>
<H3><IMG alt="" border="0" src="icon.refresh">&nbsp;&nbsp;Refresh</H3>
<BLOCKQUOTE>
<P>This action will read the bytes again from memory for every match in the results
table, looking to see if any of the bytes have changed. If so, the <I>Match Bytes</I> and
<I>Match Value</I> columns will display the changed values in red.</P>
</BLOCKQUOTE><A name="Toggle_Search"></A>
<H3><IMG alt="" border="0" src="icon.base.mem.search.panel.search">&nbsp;&nbsp;Toggle
Search Controls</H3>
<BLOCKQUOTE>
<P>This action toggles the <A href="#Search_Controls">search controls</A> on or off.</P>
</BLOCKQUOTE><A name="Toggle_Scan"></A>
<H3><IMG alt="" border="0" src="icon.base.mem.search.panel.scan">&nbsp;&nbsp;Toggle Scan
Controls</H3>
<BLOCKQUOTE>
<P>This action toggles the <A href="#Scan_Controls">scan controls</A> on or off.</P>
</BLOCKQUOTE><A name="Toggle_Options"></A>
<H3><IMG alt="" border="0" src="icon.base.mem.search.panel.options">&nbsp;&nbsp;Toggle
Options Panel</H3>
<BLOCKQUOTE>
<P>This action toggles the <A href="#Options">options display</A> on or off.</P>
</BLOCKQUOTE>
<H3><IMG alt="" border="0" src="icon.make.selection">&nbsp;&nbsp;Make Selection</H3>
<BLOCKQUOTE>
<P>This action will create a selection in the associated view from all the currently
selected match table rows.</P>
</BLOCKQUOTE>
<H3><IMG alt="" border="0" src="icon.navigate.in">&nbsp;&nbsp;Toggle Single Click
Navigation</H3>
<BLOCKQUOTE>
<P>This action toggles on or off whether a single row selection change triggers
navigation in the associated view. If this option is off, the user must double-click on a
row to navigate in the associated view.</P>
</BLOCKQUOTE>
<H3><IMG alt="" border="0" src="icon.plugin.table.delete.row">&nbsp;&nbsp;Delete Selected
Table Rows</H3>
<BLOCKQUOTE>
<P>This action deletes all selected rows in the results match table.</P>
</BLOCKQUOTE>
</BLOCKQUOTE><A name="Combining_Searches"></A>
<H2>Combining Searches</H2>
<BLOCKQUOTE>
<P>Results from multiple searches can be combined in various ways. These options are only
available once you have results in the table. Once results are present, the <I>Search
Button</I> changes to a button that has a drop down menu that allows you do decide how you
want additional searches to interact with the current results showing in the results table.
The options are as follows:</P>
<H4>New Search</H4>
<BLOCKQUOTE>
This option causes all existing result matches to be replaced with the results of the new
search. When this option is selected, the button text will show "New Search".
<P><IMG alt="" border="0" src="help/shared/tip.png">This does not create a new
search memory window, but re-uses this window. To create a new
search window, you must go back to the search memory action from the main menu.</P>
</BLOCKQUOTE>
<H4>A union B</H4>
<BLOCKQUOTE>
This option adds the results from the new search to all the existing result matches. When
this option is selected, the button text will show "Add To Search".
</BLOCKQUOTE>
<H4>A intersect B</H4>
<BLOCKQUOTE>
This option will combine the results of the new search with the existing search, but only
keep results that are in both the existing result set and the new search result set. When
this option is selected, the button text will show "Intersect Search".
</BLOCKQUOTE>
<H4>A xor B</H4>
<BLOCKQUOTE>
This option will combine the results of the new search with the existing search, but only
keep results that are in either the new or existing results, but not both.
When this option is selected, the button text will show "Xor Search".
</BLOCKQUOTE>
<H4>A - B</H4>
<BLOCKQUOTE>
Subtracts the new results from the existing results. When
this option is selected, the button text will show "A-B Search".
</BLOCKQUOTE>
<H4>B - A</H4>
<BLOCKQUOTE>
Subtracts the existing results from the new results. When this option is
selected, the button text will show "B-A Search".
</BLOCKQUOTE>
<P><IMG alt="" border="0" src="help/shared/tip.png">Many of these set operations only make
sense if you do advanced searches using wildcards. For example, if you do a search for
integer values of 5, it would make no sense to intersect that with a search for integer
values of 3. The sets are mutually exclusive, so the intersection would be empty.
Explaining how to take advantage of these options is beyond the scope of this document.</P>
</BLOCKQUOTE><A name="Repeating_Searches"></A>
<H2>Search Forward/Backwards Using Global Actions</H2>
<BLOCKQUOTE>
<P>Once at least one search has been executed using the <A href=
"#Memory_Search_Window"><I>Memory Search Window</I></A>, the search can be repeated in an
incremental fashion outside a search window using global actions in the main tool menu or
their assigned default keybindings.</P>
<A name="Repeat_Search_Forwards"></A>
<H3>Search Memory Forwards:</H3>
<P>This action will use the input data and settings from the last memory search and begin
searching forwards in memory starting at the cursor location in the associated Listing
display. If a match is found, the cursor in the Listing will be placed on the found match
location. To execute this action, select <B>Search</B> <IMG alt="" border="0" src=
"help/shared/arrow.gif"><B>Search Memory Forwards</B> from the main tool menu or press
<B>F3</B> (the default keybinding.)</P>
<A name="Repeat_Search_Backwards"></A>
<H3>Search Memory Backwards:</H3>
<P>This action will use the input data and settings from the last memory search and begin
searching backwards in memory starting at the cursor location in the associated Listing
display. If a match is found, the cursor in the Listing will be placed on the found match
location. To execute this action, select <B>Search</B> <IMG alt="" border="0" src=
"help/shared/arrow.gif"><B>Search Memory Backwards</B> from the main tool menu or press
<B>&lt;Shift&gt;F3</B> (the default keybinding.)</P>
</BLOCKQUOTE><A name="Highlight_Options"></A>
<H2>Highlight Search Options</H2>
<BLOCKQUOTE>
<P>You can control how the bytes found in the search be highlighted in the Code Browser by
selecting the <I>Highlight Search Results</I> checkbox on the Search Options panel. To view
the Search Options, select <B>Edit</B><IMG alt="" border="0" src="help/shared/arrow.gif">
<B>Tool Options...</B> from the tool menu, then select the <I>Search</I> node in the
Options tree in the Options dialog. You can also change the highlight color. Click on the
color bar next to <I>Highlight Color</I> to bring up a color chooser. Choose the new color,
click on the <B>OK</B> button. Apply your changes by clicking on the <B>OK</B> or
<B>Apply</B> button on the Options dialog.&nbsp;</P>
<BLOCKQUOTE>
<P><IMG alt="" border="0" src="help/shared/note.png"> Highlights are displayed for the
last search that you did. For example, if you bring up the Search Program Text dialog and
search for text, that string now becomes the new highlight string. Similarly, if you
invoke <A href="help/topics/CodeBrowserPlugin/CodeBrowser.htm#cursorTextHighlight">cursor
text highlighting</A>, that becomes the new highlight string.</P>
</BLOCKQUOTE>
<P>Highlights are removed when you close the search window.</P>
<BR>
<BR>
</BLOCKQUOTE>
<P><IMG alt="Note" src="help/shared/note.png">The previous operations can only work on a
<B>single</B> selected region. If multiple regions are selected, the following error dialog
will be shown and the operation will be cancelled.</P>
<P class="providedbyplugin">Provided by: <I>Memory Search Plugin</I></P>
<P align="center"><IMG border="1" src="images/MultipleSelectionError.png" alt=""></P>
<BR>
<P class="relatedtopic">Related Topics:</P>
<UL>
<LI><A href="Searching.htm">Searching Program Text</A></LI>
<LI><A href="Regular_Expressions.htm">Regular Expressions</A></LI>
</UL><BR>
<BR>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: the <B><I>MemSearchPlugin</I></B> &nbsp;</P>
&nbsp;
<P class="relatedtopic">Related Topics:</P>
<UL>
<LI><A href="Searching.htm">Searching Program Text</A></LI>
<LI><A href="Query_Results_Dialog.htm">Query Results</A></LI>
<LI><A href="Regular_Expressions.htm">Regular Expressions</A></LI>
</UL>
<BR>
<BR>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@ -4,9 +4,9 @@
* 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.
@ -18,6 +18,8 @@ package ghidra.app.nav;
import javax.swing.Icon;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.ProgramByteSource;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
@ -193,5 +195,16 @@ public interface Navigatable {
* @param highlightProvider the provider
* @param program the program
*/
public void removeHighlightProvider(ListingHighlightProvider highlightProvider, Program program);
public void removeHighlightProvider(ListingHighlightProvider highlightProvider,
Program program);
/**
* Returns a source for providing byte values of the program associated with this
* navigatable. For a static program, this is just a wrapper for a program's memory. But
* dynamic programs require special handling for reading bytes.
* @return a source of bytes for the navigatable's program
*/
public default AddressableByteSource getByteSource() {
return new ProgramByteSource(getProgram());
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -305,6 +305,7 @@ class MemSearchDialog extends ReusableDialogComponentProvider {
JPanel inputPanel = new JPanel();
inputPanel.setLayout(new GridLayout(0, 1));
valueComboBox = new GhidraComboBox<>();
valueComboBox.setAutoCompleteEnabled(false); // we do our own completion with validation
valueComboBox.setEditable(true);
valueComboBox.setToolTipText(currentFormat.getToolTip());
valueComboBox.setDocument(new RestrictedInputDocument());

View File

@ -4,9 +4,9 @@
* 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.
@ -16,14 +16,14 @@
package ghidra.app.plugin.core.searchmem;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.util.*;
import javax.swing.Icon;
import javax.swing.JComponent;
import docking.*;
import docking.action.*;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.tool.ToolConstants;
import docking.widgets.fieldpanel.support.Highlight;
import docking.widgets.table.threaded.*;
@ -64,7 +64,7 @@ import ghidra.util.task.*;
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
status = PluginStatus.DEPRECATED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.SEARCH,
shortDescription = "Search bytes in memory",
@ -73,12 +73,12 @@ import ghidra.util.task.*;
" The value may contain \"wildcards\" or regular expressions" +
" that will match any byte or nibble.",
servicesRequired = { ProgramManager.class, GoToService.class, TableService.class, CodeViewerService.class },
servicesProvided = { MemorySearchService.class },
// servicesProvided = { MemorySearchService.class },
eventsConsumed = { ProgramSelectionPluginEvent.class }
)
//@formatter:on
public class MemSearchPlugin extends Plugin implements OptionsChangeListener,
DockingContextListener, NavigatableRemovalListener, MemorySearchService {
DockingContextListener, NavigatableRemovalListener {
/** Constant for read/writeConfig() for dialog options */
private static final String SHOW_ADVANCED_OPTIONS = "Show Advanced Options";
@ -243,12 +243,12 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener,
}
@Override
public void setIsMnemonic(boolean isMnemonic) {
// provides the dialog with the knowledge of whether or not
// the action being performed is a MnemonicSearchPlugin
this.isMnemonic = isMnemonic;
}
// @Override
// public void setIsMnemonic(boolean isMnemonic) {
// // provides the dialog with the knowledge of whether or not
// // the action being performed is a MnemonicSearchPlugin
// this.isMnemonic = isMnemonic;
// }
private void setNavigatable(Navigatable newNavigatable) {
if (newNavigatable == navigatable) {
@ -329,16 +329,16 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener,
return new BytesFieldLocation(program, address);
}
@Override
public void search(byte[] bytes, NavigatableActionContext context) {
setNavigatable(context.getNavigatable());
invokeSearchDialog(context);
}
@Override
public void setSearchText(String maskedString) {
searchDialog.setSearchText(maskedString);
}
// @Override
// public void search(byte[] bytes, NavigatableActionContext context) {
// setNavigatable(context.getNavigatable());
// invokeSearchDialog(context);
// }
//
// @Override
// public void setSearchText(String maskedString) {
// searchDialog.setSearchText(maskedString);
// }
private void createActions() {
searchAction = new NavigatableContextAction("Search Memory", getName(), false) {
@ -349,9 +349,8 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener,
}
};
searchAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, searchAction.getName()));
String[] menuPath = new String[] { "&Search", "&Memory..." };
String[] menuPath = new String[] { "&Search", "Memory (Deprecated)..." };
searchAction.setMenuBarData(new MenuData(menuPath, "search"));
searchAction.setKeyBindingData(new KeyBindingData('S', 0));
searchAction.setDescription("Search Memory for byte sequence");
searchAction.addToWindowWhen(NavigatableActionContext.class);
tool.addAction(searchAction);
@ -372,10 +371,10 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener,
.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, searchAgainAction.getName()));
menuPath = new String[] { "&Search", "Repeat Memory Search" };
searchAgainAction.setMenuBarData(new MenuData(menuPath, "search"));
searchAgainAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_F3, 0));
searchAgainAction.setDescription("Search Memory for byte sequence");
searchAgainAction.addToWindowWhen(NavigatableActionContext.class);
tool.addAction(searchAgainAction);
}
private void initializeOptionListeners() {

View File

@ -4,9 +4,9 @@
* 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.
@ -15,23 +15,36 @@
*/
package ghidra.app.services;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.nav.Navigatable;
import ghidra.features.base.memsearch.gui.MemorySearchProvider;
import ghidra.features.base.memsearch.gui.SearchSettings;
/**
* Service for invoking the {@link MemorySearchProvider}
* @deprecated This is not a generally useful service, may go away at some point
*/
@Deprecated(since = "11.2")
public interface MemorySearchService {
/*
* sets up MemSearchDialog based on given bytes
/**
* Creates a new memory search provider window
* @param navigatable the navigatable used to get bytes to search
* @param input the input string to search for
* @param settings the settings that determine how to interpret the input string
* @param useSelection true if the provider should automatically restrict to a selection if
* a selection exists in the navigatable
*/
public void search(byte[] bytes, NavigatableActionContext context);
public void createMemorySearchProvider(Navigatable navigatable, String input,
SearchSettings settings, boolean useSelection);
/*
* sets the search value field to the masked bit string
*/
public void setSearchText(String maskedString);
// These method were removed because they didn't work correctly and were specific to the needs of
// one outlier plugin. The functionality has been replaced by the above method, which is also
// unlikely to be useful.
/*
* determines whether the dialog was called by a mnemonic or not
*/
public void setIsMnemonic(boolean isMnemonic);
// public void search(byte[] bytes, NavigatableActionContext context);
//
// public void setSearchText(String maskedString);
//
// public void setIsMnemonic(boolean isMnemonic);
}

View File

@ -4,9 +4,9 @@
* 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.
@ -25,8 +25,10 @@ import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.framework.options.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.util.AddressFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.exception.AssertException;
@ -128,16 +130,34 @@ public class ArrayValuesFieldFactory extends FieldFactory {
@Override
public FieldLocation getFieldLocation(ListingField lf, BigInteger index, int fieldNum,
ProgramLocation location) {
if (!(location instanceof ArrayElementFieldLocation)) {
// Unless the location is specifically targeting the address field, then we should
// process the location because the arrays display is different from all other format
// models in that one line actually represents more than one array data element. So
// this field has the best chance of representing the location for array data elements.
if (location instanceof AddressFieldLocation) {
return null;
}
ArrayElementFieldLocation loc = (ArrayElementFieldLocation) location;
ListingTextField btf = (ListingTextField) lf;
Data firstDataOnLine = (Data) btf.getProxy().getObject();
int elementIndex = loc.getElementIndexOnLine(firstDataOnLine);
RowColLocation rcl = btf.dataToScreenLocation(elementIndex, loc.getCharOffset());
return new FieldLocation(index, fieldNum, rcl.row(), rcl.col());
RowColLocation rcl = getDataRowColumnLocation(location, lf);
return new FieldLocation(index, fieldNum, rcl.row(), rcl.col());
}
private RowColLocation getDataRowColumnLocation(ProgramLocation location, ListingField field) {
ListingTextField ltf = (ListingTextField) field;
Data firstDataOnLine = (Data) field.getProxy().getObject();
if (location instanceof ArrayElementFieldLocation loc) {
int elementIndex = loc.getElementIndexOnLine(firstDataOnLine);
return ltf.dataToScreenLocation(elementIndex, loc.getCharOffset());
}
Address byteAddress = location.getByteAddress();
int byteOffset = (int) byteAddress.subtract(firstDataOnLine.getAddress());
int componentSize = firstDataOnLine.getLength();
int elementOffset = byteOffset / componentSize;
return ltf.dataToScreenLocation(elementOffset, 0);
}
@Override

View File

@ -0,0 +1,125 @@
/* ###
* 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.features.base.memsearch.bytesequence;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
/**
* This class provides a {@link ByteSequence} view into an {@link AddressableByteSource}. By
* specifying an address and length, this class provides a view into the byte source
* as a indexable sequence of bytes. It is mutable and can be reused by setting a new
* address range for this sequence. This was to avoid constantly allocating large byte arrays.
*/
public class AddressableByteSequence implements ByteSequence {
private final AddressableByteSource byteSource;
private final byte[] bytes;
private final int capacity;
private Address startAddress;
private int length;
/**
* Constructor
* @param byteSource the source of the underlying bytes that is a buffer into
* @param capacity the maximum size range that this object will buffer
*/
public AddressableByteSequence(AddressableByteSource byteSource, int capacity) {
this.byteSource = byteSource;
this.capacity = capacity;
this.length = 0;
this.bytes = new byte[capacity];
}
/**
* Sets this view to an empty byte sequence
*/
public void clear() {
startAddress = null;
length = 0;
}
/**
* Sets the range of bytes that this object will buffer. This immediately will read the bytes
* from the byte source into it's internal byte array buffer.
* @param range the range of bytes to buffer
*/
public void setRange(AddressRange range) {
// Note that this will throw an exception if the range length is larger then Integer.MAX
// which is unsupported by the ByteSequence interface
try {
setRange(range.getMinAddress(), range.getBigLength().intValueExact());
}
catch (ArithmeticException e) {
throw new IllegalArgumentException("Length exceeds capacity");
}
}
/**
* Returns the address of the byte represented by the given index into this buffer.
* @param index the index into the buffer to get its associated address
* @return the Address for the given index
*/
public Address getAddress(int index) {
if (index < 0 || index >= length) {
throw new IndexOutOfBoundsException();
}
if (index == 0) {
return startAddress;
}
return startAddress.add(index);
}
@Override
public int getLength() {
return length;
}
@Override
public byte getByte(int index) {
if (index < 0 || index >= length) {
throw new IndexOutOfBoundsException();
}
return bytes[index];
}
@Override
public byte[] getBytes(int index, int size) {
if (index < 0 || index + size > length) {
throw new IndexOutOfBoundsException();
}
byte[] results = new byte[size];
System.arraycopy(bytes, index, results, 0, size);
return results;
}
@Override
public boolean hasAvailableBytes(int index, int length) {
return index >= 0 && index + length <= getLength();
}
private void setRange(Address start, int length) {
if (length > capacity) {
throw new IllegalArgumentException("Length exceeds capacity");
}
this.startAddress = start;
this.length = length;
byteSource.getBytes(start, bytes, length);
}
}

View File

@ -0,0 +1,53 @@
/* ###
* 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.features.base.memsearch.bytesequence;
/**
* An interface for accessing bytes from a byte source.
*/
public interface ByteSequence {
/**
* Returns the length of available bytes.
* @return the length of the sequence of bytes
*/
public int getLength();
/**
* Returns the byte at the given index. The index must between 0 and the extended length.
* @param index the index in the byte sequence to retrieve a byte value
* @return the byte at the given index
*/
public byte getByte(int index);
/**
* A convenience method for checking if this sequence can provide a range of bytes from some
* offset.
* @param index the index of the start of the range to check for available bytes
* @param length the length of the range to check for available bytes
* @return true if bytes are available for the given range
*/
public boolean hasAvailableBytes(int index, int length);
/**
* Returns a byte array containing the bytes from the given range.
* @param start the start index of the range to get bytes
* @param length the number of bytes to get
* @return a byte array containing the bytes from the given range
*/
public byte[] getBytes(int start, int length);
}

View File

@ -0,0 +1,98 @@
/* ###
* 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.features.base.memsearch.bytesequence;
/**
* A class for accessing a contiguous sequence of bytes from some underlying byte source to
* be used for searching for a byte pattern within the byte source. This sequence of bytes
* consists of two parts; the primary sequence and an extended sequence. Search matches
* must begin in the primary sequence, but may extend into the extended sequence.
* <P>
* Searching large ranges of memory can be partitioned into searching smaller chunks. But
* to handle search sequences that span chunks, two chunks are presented at a time, with the second
* chunk being the extended bytes. On the next iteration of the search loop, the extended chunk
* will become the primary chunk, with the next chunk after that becoming the extended sequence
* and so on.
*/
public class ExtendedByteSequence implements ByteSequence {
private ByteSequence main;
private ByteSequence extended;
private int extendedLength;
/**
* Constructs an extended byte sequence from two {@link ByteSequence}s.
* @param main the byte sequence where search matches may start
* @param extended the byte sequence where search matches may extend into
* @param extendedLimit specifies how much of the extended byte sequence to allow search
* matches to extend into. (The extended buffer will be the primary buffer next time, so
* it is a full size buffer, but we only need to use a portion of it to support overlap.
*/
public ExtendedByteSequence(ByteSequence main, ByteSequence extended, int extendedLimit) {
this.main = main;
this.extended = extended;
this.extendedLength = main.getLength() + Math.min(extendedLimit, extended.getLength());
}
@Override
public int getLength() {
return main.getLength();
}
/**
* Returns the overall length of sequence of available bytes. This will be the length of
* the primary sequence as returned by {@link #getLength()} plus the length of the available
* extended bytes, if any.
* @return the
*/
public int getExtendedLength() {
return extendedLength;
}
@Override
public byte getByte(int i) {
int mainLength = main.getLength();
if (i >= mainLength) {
return extended.getByte(i - mainLength);
}
return main.getByte(i);
}
@Override
public byte[] getBytes(int index, int size) {
if (index < 0 || index + size > extendedLength) {
throw new IndexOutOfBoundsException();
}
int length = main.getLength();
if (index + size < length) {
return main.getBytes(index, size);
}
if (index >= length) {
return extended.getBytes(index - length, size);
}
// otherwise it spans
byte[] results = new byte[size];
for (int i = 0; i < size; i++) {
results[i] = getByte(index + i);
}
return results;
}
@Override
public boolean hasAvailableBytes(int index, int length) {
return index >= 0 && index + length <= getExtendedLength();
}
}

View File

@ -0,0 +1,59 @@
/* ###
* 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.features.base.memsearch.bytesource;
import java.util.List;
import ghidra.program.model.address.Address;
/**
* Interface for reading bytes from a program. This provides a level of indirection for reading the
* bytes of a program so that the provider of the bytes can possibly do more than just reading the
* bytes from the static program. For example, a debugger would have the opportunity to refresh the
* bytes first.
* <P>
* This interface also provides methods for determining what regions of memory can be queried and
* what addresses sets are associated with those regions. This would allow client to present choices
* about what areas of memory they are interested in AND are valid to be examined.
*/
public interface AddressableByteSource {
/**
* Retrieves the byte values for an address range.
*
* @param address The address of the first byte in the range
* @param bytes the byte array to store the retrieved byte values
* @param length the number of bytes to retrieve
* @return the number of bytes actually retrieved
*/
public int getBytes(Address address, byte[] bytes, int length);
/**
* Returns a list of memory regions where each region has an associated address set of valid
* addresses that can be read.
*
* @return a list of readable regions
*/
public List<SearchRegion> getSearchableRegions();
/**
* Invalidates any caching of byte values. This intended to provide a hint in debugging scenario
* that we are about to issue a sequence of byte value requests where we are re-acquiring
* previous requested byte values to look for changes.
*/
public void invalidate();
}

View File

@ -0,0 +1,42 @@
/* ###
* 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.features.base.memsearch.bytesource;
import java.util.List;
import ghidra.program.model.address.Address;
/**
* Implementation for an empty {@link AddressableByteSource}
*/
public enum EmptyByteSource implements AddressableByteSource {
INSTANCE;
@Override
public int getBytes(Address address, byte[] bytes, int length) {
return 0;
}
@Override
public List<SearchRegion> getSearchableRegions() {
return List.of();
}
@Override
public void invalidate() {
// nothing to do
}
}

View File

@ -0,0 +1,56 @@
/* ###
* 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.features.base.memsearch.bytesource;
import java.util.List;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
/**
* {@link AddressableByteSource} implementation for a Ghidra {@link Program}
*/
public class ProgramByteSource implements AddressableByteSource {
private Memory memory;
public ProgramByteSource(Program program) {
memory = program.getMemory();
}
@Override
public int getBytes(Address address, byte[] bytes, int length) {
try {
return memory.getBytes(address, bytes, 0, length);
}
catch (MemoryAccessException e) {
return 0;
}
}
@Override
public List<SearchRegion> getSearchableRegions() {
return ProgramSearchRegion.ALL;
}
@Override
public void invalidate() {
// nothing to do in the static case
}
}

View File

@ -0,0 +1,78 @@
/* ###
* 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.features.base.memsearch.bytesource;
import java.util.List;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
/**
* An enum specifying the selectable regions within a {@link Program} that users can select for
* searching memory.
*/
public enum ProgramSearchRegion implements SearchRegion {
LOADED("Loaded Blocks",
"Searches all memory blocks that represent loaded program instructions and data") {
@Override
public boolean isDefault() {
return true;
}
@Override
public AddressSetView getAddresses(Program program) {
Memory memory = program.getMemory();
return memory.getLoadedAndInitializedAddressSet();
}
},
OTHER("All Other Blocks", "Searches non-loaded initialized blocks") {
@Override
public boolean isDefault() {
return false;
}
@Override
public AddressSetView getAddresses(Program program) {
Memory memory = program.getMemory();
AddressSetView all = memory.getAllInitializedAddressSet();
AddressSetView loaded = memory.getLoadedAndInitializedAddressSet();
return all.subtract(loaded);
}
};
public static final List<SearchRegion> ALL = List.of(values());
private String name;
private String description;
ProgramSearchRegion(String name, String description) {
this.name = name;
this.description = description;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,52 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.base.memsearch.bytesource;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
/**
* Interface to specify a named region within a byte source (Program) that users can select to
* specify {@link AddressSetView}s that can be searched.
*/
public interface SearchRegion {
/**
* The name of the region.
* @return the name of the region
*/
public String getName();
/**
* Returns a description of the region.
* @return a description of the region
*/
public String getDescription();
/**
* Returns the set of addresses from a specific program that is associated with this region.
* @param program the program that determines the specific addresses for a named region
* @return the set of addresses for this region as applied to the given program
*/
public AddressSetView getAddresses(Program program);
/**
* Returns true if this region should be included in the default selection of which regions to
* search.
* @return true if this region should be selected by default
*/
public boolean isDefault();
}

View File

@ -0,0 +1,123 @@
/* ###
* 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.features.base.memsearch.combiner;
import java.util.*;
import java.util.function.BiFunction;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.program.model.address.Address;
/**
* An enum of search results "combiners". Each combiner determines how to combine two sets of
* memory search results. The current or existing results is represented as the "A" set and the
* new search is represented as the "B" set.
*/
public enum Combiner {
REPLACE("New", Combiner::replace),
UNION("Add To", Combiner::union),
INTERSECT("Intersect", Combiner::intersect),
XOR("Xor", Combiner::xor),
A_MINUS_B("A-B", Combiner::subtract),
B_MINUS_A("B-A", Combiner::reverseSubtract);
private String name;
private BiFunction<List<MemoryMatch>, List<MemoryMatch>, Collection<MemoryMatch>> function;
private Combiner(String name,
BiFunction<List<MemoryMatch>, List<MemoryMatch>, Collection<MemoryMatch>> function) {
this.name = name;
this.function = function;
}
public String getName() {
return name;
}
public Collection<MemoryMatch> combine(List<MemoryMatch> matches1, List<MemoryMatch> matches2) {
return function.apply(matches1, matches2);
}
private static Collection<MemoryMatch> replace(List<MemoryMatch> matches1,
List<MemoryMatch> matches2) {
return matches2;
}
private static Collection<MemoryMatch> union(List<MemoryMatch> matches1,
List<MemoryMatch> matches2) {
Map<Address, MemoryMatch> matches1Map = createMap(matches1);
for (MemoryMatch match2 : matches2) {
Address address = match2.getAddress();
MemoryMatch match1 = matches1Map.get(address);
if (match1 == null || match2.getLength() > match1.getLength()) {
matches1Map.put(address, match2);
}
}
return matches1Map.values();
}
private static Collection<MemoryMatch> intersect(List<MemoryMatch> matches1,
List<MemoryMatch> matches2) {
List<MemoryMatch> intersection = new ArrayList<>();
Map<Address, MemoryMatch> matches1Map = createMap(matches1);
for (MemoryMatch match2 : matches2) {
Address address = match2.getAddress();
MemoryMatch match1 = matches1Map.get(address);
if (match1 != null) {
MemoryMatch best = match2.getLength() > match1.getLength() ? match2 : match1;
intersection.add(best);
}
}
return intersection;
}
private static List<MemoryMatch> xor(List<MemoryMatch> matches1, List<MemoryMatch> matches2) {
List<MemoryMatch> results = new ArrayList<>();
results.addAll(subtract(matches1, matches2));
results.addAll(subtract(matches2, matches1));
return results;
}
private static Collection<MemoryMatch> subtract(List<MemoryMatch> matches1,
List<MemoryMatch> matches2) {
Map<Address, MemoryMatch> matches1Map = createMap(matches1);
for (MemoryMatch match2 : matches2) {
Address address = match2.getAddress();
matches1Map.remove(address);
}
return matches1Map.values();
}
private static Collection<MemoryMatch> reverseSubtract(List<MemoryMatch> matches1,
List<MemoryMatch> matches2) {
return subtract(matches2, matches1);
}
private static Map<Address, MemoryMatch> createMap(List<MemoryMatch> matches) {
Map<Address, MemoryMatch> map = new HashMap<>();
for (MemoryMatch result : matches) {
map.put(result.getAddress(), result);
}
return map;
}
}

View File

@ -0,0 +1,188 @@
/* ###
* 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.features.base.memsearch.format;
import java.util.*;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.matcher.*;
import ghidra.util.HTMLUtilities;
/**
* {@link SearchFormat} for parsing and display bytes in a binary format. This format only
* accepts 0s or 1s or wild card characters.
*/
class BinarySearchFormat extends SearchFormat {
private static final String VALID_CHARS = "01x?.";
private static final int MAX_GROUP_SIZE = 8;
BinarySearchFormat() {
super("Binary");
}
@Override
public ByteMatcher parse(String input, SearchSettings settings) {
input = input.trim();
if (input.isBlank()) {
return new InvalidByteMatcher("");
}
List<String> byteGroups = getByteGroups(input);
if (hasInvalidChars(byteGroups)) {
return new InvalidByteMatcher("Invalid character");
}
if (checkGroupSize(byteGroups)) {
return new InvalidByteMatcher("Max group size exceeded. Enter <space> to add more.");
}
byte[] bytes = getBytes(byteGroups);
byte[] masks = getMask(byteGroups);
return new MaskedByteSequenceByteMatcher(input, bytes, masks, settings);
}
@Override
public String getToolTip() {
return HTMLUtilities.toHTML(
"Interpret value as a sequence of binary digits.\n" +
"Spaces will start the next byte. Bit sequences less\n" +
"than 8 bits are padded with 0's to the left. \n" +
"Enter 'x', '.' or '?' for a wildcard bit");
}
private boolean checkGroupSize(List<String> byteGroups) {
for (String byteGroup : byteGroups) {
if (byteGroup.length() > MAX_GROUP_SIZE) {
return true;
}
}
return false;
}
private List<String> getByteGroups(String input) {
List<String> list = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(input);
while (st.hasMoreTokens()) {
list.add(st.nextToken());
}
return list;
}
private boolean hasInvalidChars(List<String> byteGroups) {
for (String byteGroup : byteGroups) {
if (hasInvalidChars(byteGroup)) {
return true;
}
}
return false;
}
private boolean hasInvalidChars(String string) {
for (int i = 0; i < string.length(); i++) {
if (VALID_CHARS.indexOf(string.charAt(i)) < 0) {
return true;
}
}
return false;
}
private byte getByte(String token) {
byte b = 0;
for (int i = 0; i < token.length(); i++) {
b <<= 1;
char c = token.charAt(i);
if (c == '1') {
b |= 1;
}
}
return b;
}
/**
* Return a mask byte that has a bit set to 1 for each bit that is not a wildcard. Any bits
* that aren't specified (i.e. token.lenght &lt; 8) are treated as valid test bits.
* @param token the string of bits to determine a mask for.
*/
private byte getMask(String token) {
byte b = 0;
for (int i = 0; i < 8; i++) {
b <<= 1;
if (i < token.length()) {
char c = token.charAt(i);
if (c == '1' || c == '0') {
b |= 1;
}
}
else {
b |= 1;
}
}
return b;
}
private byte[] getBytes(List<String> byteGroups) {
byte[] bytes = new byte[byteGroups.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = getByte(byteGroups.get(i));
}
return bytes;
}
private byte[] getMask(List<String> byteGroups) {
byte[] masks = new byte[byteGroups.size()];
for (int i = 0; i < masks.length; i++) {
masks[i] = getMask(byteGroups.get(i));
}
return masks;
}
@Override
public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) {
SearchFormat oldFormat = oldSettings.getSearchFormat();
if (oldFormat.getFormatType() != SearchFormatType.STRING_TYPE) {
ByteMatcher byteMatcher = oldFormat.parse(text, oldSettings);
if ((byteMatcher instanceof MaskedByteSequenceByteMatcher matcher)) {
byte[] bytes = matcher.getBytes();
byte[] mask = matcher.getMask();
return getMaskedInputString(bytes, mask);
}
}
return isValidText(text, newSettings) ? text : "";
}
private String getMaskedInputString(byte[] bytes, byte[] masks) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
for (int shift = 7; shift >= 0; shift--) {
int bit = bytes[i] >> shift & 0x1;
int maskBit = masks[i] >> shift & 0x1;
builder.append(maskBit == 0 ? '.' : Integer.toString(bit));
}
builder.append(" ");
}
return builder.toString().trim();
}
@Override
public SearchFormatType getFormatType() {
return SearchFormatType.BYTE;
}
}

View File

@ -0,0 +1,280 @@
/* ###
* 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.features.base.memsearch.format;
import java.math.BigInteger;
import java.util.StringTokenizer;
import org.bouncycastle.util.Arrays;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.matcher.*;
import ghidra.util.HTMLUtilities;
/**
* {@link SearchFormat} for parsing and display bytes in a decimal format. It supports sizes of
* 2,4,8,16 and can be either signed or unsigned.
*/
class DecimalSearchFormat extends SearchFormat {
DecimalSearchFormat() {
super("Decimal");
}
@Override
public ByteMatcher parse(String input, SearchSettings settings) {
input = input.trim();
if (input.isBlank()) {
return new InvalidByteMatcher("");
}
int byteSize = settings.getDecimalByteSize();
StringTokenizer tokenizer = new StringTokenizer(input);
int tokenCount = tokenizer.countTokens();
byte[] bytes = new byte[tokenCount * byteSize];
int bytesPosition = 0;
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
NumberParseResult result = parseNumber(token, settings);
if (result.errorMessage() != null) {
return new InvalidByteMatcher(result.errorMessage(), result.validInput());
}
System.arraycopy(result.bytes(), 0, bytes, bytesPosition, byteSize);
bytesPosition += byteSize;
}
return new MaskedByteSequenceByteMatcher(input, bytes, settings);
}
private NumberParseResult parseNumber(String tok, SearchSettings settings) {
BigInteger min = getMin(settings);
BigInteger max = getMax(settings);
try {
if (tok.equals("-")) {
if (settings.isDecimalUnsigned()) {
return new NumberParseResult(null,
"Negative numbers not allowed for unsigned values", false);
}
return new NumberParseResult(null, "Incomplete negative number", true);
}
BigInteger value = new BigInteger(tok);
if (value.compareTo(min) < 0 || value.compareTo(max) > 0) {
return new NumberParseResult(null,
"Number must be in the range [" + min + ", " + max + "]", false);
}
long longValue = value.longValue();
return createBytesResult(longValue, settings);
}
catch (NumberFormatException e) {
return new NumberParseResult(null, "Number parse error: " + e.getMessage(), false);
}
}
private BigInteger getMax(SearchSettings settings) {
boolean unsigned = settings.isDecimalUnsigned();
int size = settings.getDecimalByteSize();
int shift = unsigned ? 8 * size : 8 * size - 1;
return BigInteger.ONE.shiftLeft(shift).subtract(BigInteger.ONE);
}
private BigInteger getMin(SearchSettings settings) {
boolean unsigned = settings.isDecimalUnsigned();
int size = settings.getDecimalByteSize();
if (unsigned) {
return BigInteger.ZERO;
}
return BigInteger.ONE.shiftLeft(8 * size - 1).negate();
}
private NumberParseResult createBytesResult(long value, SearchSettings settings) {
int byteSize = settings.getDecimalByteSize();
byte[] bytes = new byte[byteSize];
for (int i = 0; i < byteSize; i++) {
byte b = (byte) value;
bytes[i] = b;
value >>= 8;
}
if (settings.isBigEndian()) {
reverse(bytes);
}
return new NumberParseResult(bytes, null, true);
}
@Override
public String getToolTip() {
return HTMLUtilities.toHTML(
"Interpret values as a sequence of decimal numbers, separated by spaces");
}
@Override
public int compareValues(byte[] bytes1, byte[] bytes2, SearchSettings settings) {
int byteSize = settings.getDecimalByteSize();
// check each value one at a time, and return the first one different
for (int i = 0; i < bytes1.length / byteSize; i++) {
long value1 = getValue(bytes1, i * byteSize, settings);
long value2 = getValue(bytes2, i * byteSize, settings);
if (value1 != value2) {
if (byteSize == 8 && settings.isDecimalUnsigned()) {
return Long.compareUnsigned(value1, value2);
}
return Long.compare(value1, value2);
}
}
return 0;
}
public long getValue(byte[] bytes, int index, SearchSettings settings) {
boolean isBigEndian = settings.isBigEndian();
int byteSize = settings.getDecimalByteSize();
boolean isUnsigned = settings.isDecimalUnsigned();
byte[] bigEndianBytes = getBigEndianBytes(bytes, index, isBigEndian, byteSize);
long value = isUnsigned ? bigEndianBytes[0] & 0xff : bigEndianBytes[0];
for (int i = 1; i < byteSize; i++) {
value = (value << 8) | (bigEndianBytes[i] & 0xff);
}
return value;
}
private byte[] getBigEndianBytes(byte[] bytes, int index, boolean isBigEndian, int byteSize) {
byte[] bigEndianBytes = new byte[byteSize];
System.arraycopy(bytes, index * byteSize, bigEndianBytes, 0, byteSize);
if (!isBigEndian) {
reverse(bigEndianBytes);
}
return bigEndianBytes;
}
@Override
public String getValueString(byte[] bytes, SearchSettings settings) {
return getValueString(bytes, settings, false);
}
protected String getValueString(byte[] bytes, SearchSettings settings, boolean padNegative) {
int byteSize = settings.getDecimalByteSize();
boolean isBigEndian = settings.isBigEndian();
boolean isUnsigned = settings.isDecimalUnsigned();
StringBuilder buffer = new StringBuilder();
int numValues = bytes.length / byteSize;
for (int i = 0; i < numValues; i++) {
long value = getValue(bytes, i, settings);
String text = isUnsigned ? Long.toUnsignedString(value) : Long.toString(value);
buffer.append(text);
if (i != numValues - 1) {
buffer.append(", ");
}
}
int remainder = bytes.length - numValues * byteSize;
if (remainder > 0) {
byte[] remainderBytes = new byte[remainder];
System.arraycopy(bytes, numValues * byteSize, remainderBytes, 0, remainder);
byte[] padded = padToByteSize(remainderBytes, byteSize, isBigEndian, padNegative);
long value = getValue(padded, 0, settings);
String text = isUnsigned ? Long.toUnsignedString(value) : Long.toString(value);
if (!buffer.isEmpty()) {
buffer.append(", ");
}
buffer.append(text);
}
return buffer.toString();
}
@Override
public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) {
SearchFormat oldFormat = oldSettings.getSearchFormat();
switch (oldFormat.getFormatType()) {
case BYTE:
return getTextFromBytes(text, oldSettings, newSettings);
case INTEGER:
return convertFromDifferentNumberFormat(text, oldSettings, newSettings);
case STRING_TYPE:
case FLOATING_POINT:
default:
return isValidText(text, newSettings) ? text : "";
}
}
private String convertFromDifferentNumberFormat(String text, SearchSettings oldSettings,
SearchSettings newSettings) {
int oldSize = oldSettings.getDecimalByteSize();
int newSize = newSettings.getDecimalByteSize();
boolean oldUnsigned = oldSettings.isDecimalUnsigned();
boolean newUnsigned = newSettings.isDecimalUnsigned();
if (oldSize == newSize && oldUnsigned == newUnsigned) {
return text;
}
// if the new format is smaller, first try re-parsing to avoid unnecessary 0's
if (oldSize > newSize) {
if (isValidText(text, newSettings)) {
return text;
}
}
return getTextFromBytes(text, oldSettings, newSettings);
}
private String getTextFromBytes(String text, SearchSettings oldSettings,
SearchSettings newSettings) {
byte[] bytes = getBytes(oldSettings.getSearchFormat(), text, oldSettings);
if (bytes == null) {
return "";
}
boolean padNegative = shouldPadNegative(text);
String valueString = getValueString(bytes, newSettings, padNegative);
return valueString.replaceAll(",", "");
}
private boolean shouldPadNegative(String text) {
if (text.isBlank()) {
return false;
}
int lastIndexOf = text.trim().lastIndexOf(" ");
if (lastIndexOf < 0) {
// only pad negative if there is only one word in the text and it begins with '-'
return text.charAt(0) == '-';
}
return false;
}
private byte[] getBytes(SearchFormat oldFormat, String text, SearchSettings settings) {
ByteMatcher byteMatcher = oldFormat.parse(text, settings);
if (byteMatcher instanceof MaskedByteSequenceByteMatcher matcher) {
return matcher.getBytes();
}
return null;
}
private byte[] padToByteSize(byte[] bytes, int byteSize, boolean isBigEndian,
boolean padNegative) {
if (bytes.length >= byteSize) {
return bytes;
}
byte[] newBytes = new byte[byteSize];
if (padNegative) {
Arrays.fill(newBytes, (byte) -1);
}
int startIndex = isBigEndian ? byteSize - bytes.length : 0;
System.arraycopy(bytes, 0, newBytes, startIndex, bytes.length);
return newBytes;
}
@Override
public SearchFormatType getFormatType() {
return SearchFormatType.INTEGER;
}
}

View File

@ -0,0 +1,200 @@
/* ###
* 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.features.base.memsearch.format;
import java.util.StringTokenizer;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.matcher.*;
import ghidra.util.HTMLUtilities;
/**
* {@link SearchFormat} for parsing and display bytes in a float or double format.
*/
class FloatSearchFormat extends SearchFormat {
private String longName;
private int byteSize;
FloatSearchFormat(String name, String longName, int size) {
super(name);
if (size != 8 && size != 4) {
throw new IllegalArgumentException("Only supports 4 or 8 byte floating point numbers");
}
this.longName = longName;
this.byteSize = size;
}
@Override
public ByteMatcher parse(String input, SearchSettings settings) {
input = input.trim();
if (input.isBlank()) {
return new InvalidByteMatcher("");
}
StringTokenizer tokenizer = new StringTokenizer(input);
int tokenCount = tokenizer.countTokens();
byte[] bytes = new byte[tokenCount * byteSize];
int bytesPosition = 0;
while (tokenizer.hasMoreTokens()) {
String tok = tokenizer.nextToken();
NumberParseResult result = parseNumber(tok, settings);
if (result.errorMessage() != null) {
return new InvalidByteMatcher(result.errorMessage(), result.validInput());
}
System.arraycopy(result.bytes(), 0, bytes, bytesPosition, byteSize);
bytesPosition += byteSize;
}
return new MaskedByteSequenceByteMatcher(input, bytes, settings);
}
private NumberParseResult parseNumber(String tok, SearchSettings settings) {
if (tok.equals("-") || tok.equals("-.")) {
return new NumberParseResult(null, "Incomplete negative floating point number", true);
}
if (tok.equals(".")) {
return new NumberParseResult(null, "Incomplete floating point number", true);
}
if (tok.endsWith("E") || tok.endsWith("e") || tok.endsWith("E-") || tok.endsWith("e-")) {
return new NumberParseResult(null, "Incomplete floating point number", true);
}
try {
long value = getValue(tok);
return new NumberParseResult(getBytes(value, settings), null, true);
}
catch (NumberFormatException e) {
return new NumberParseResult(null, "Floating point parse error: " + e.getMessage(),
false);
}
}
private long getValue(String tok) {
switch (byteSize) {
case 4:
float floatValue = Float.parseFloat(tok);
return Float.floatToIntBits(floatValue);
case 8:
default:
double dvalue = Double.parseDouble(tok);
return Double.doubleToLongBits(dvalue);
}
}
private byte[] getBytes(long value, SearchSettings settings) {
byte[] bytes = new byte[byteSize];
for (int i = 0; i < byteSize; i++) {
byte b = (byte) value;
bytes[i] = b;
value >>= 8;
}
if (settings.isBigEndian()) {
reverse(bytes);
}
return bytes;
}
@Override
public String getToolTip() {
return HTMLUtilities.toHTML(
"Interpret values as a sequence of\n" + longName + " numbers, separated by spaces");
}
@Override
public int compareValues(byte[] bytes1, byte[] bytes2, SearchSettings settings) {
boolean isBigEndian = settings.isBigEndian();
// check each value one at a time, and return the first one different
for (int i = 0; i < bytes1.length / byteSize; i++) {
double value1 = getValue(bytes1, i, isBigEndian);
double value2 = getValue(bytes2, i, isBigEndian);
if (value1 != value2) {
return Double.compare(value1, value2);
}
}
return 0;
}
public Double getValue(byte[] bytes, int index, boolean isBigEndian) {
long bits = fromBytes(bytes, index, isBigEndian);
switch (byteSize) {
case 4:
float f = Float.intBitsToFloat((int) bits);
return (double) f;
case 8:
default:
return Double.longBitsToDouble(bits);
}
}
private long fromBytes(byte[] bytes, int index, boolean isBigEndian) {
byte[] bigEndianBytes = new byte[byteSize];
System.arraycopy(bytes, index * byteSize, bigEndianBytes, 0, byteSize);
if (!isBigEndian) {
reverse(bigEndianBytes);
}
long value = 0;
for (int i = 0; i < bigEndianBytes.length; i++) {
value = (value << 8) | (bigEndianBytes[i] & 0xff);
}
return value;
}
@Override
public String getValueString(byte[] bytes, SearchSettings settings) {
StringBuilder buffer = new StringBuilder();
int numValues = bytes.length / byteSize;
for (int i = 0; i < numValues; i++) {
double value = getValue(bytes, i, settings.isBigEndian());
buffer.append(Double.toString(value));
if (i != numValues - 1) {
buffer.append(", ");
}
}
return buffer.toString();
}
@Override
public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) {
SearchFormat oldFormat = oldSettings.getSearchFormat();
switch (oldFormat.getFormatType()) {
case BYTE:
return getTextFromBytes(text, oldFormat, oldSettings);
case FLOATING_POINT:
case STRING_TYPE:
case INTEGER:
default:
return isValidText(text, newSettings) ? text : "";
}
}
private String getTextFromBytes(String text, SearchFormat oldFormat, SearchSettings settings) {
ByteMatcher byteMatcher = oldFormat.parse(text, settings);
if ((byteMatcher instanceof MaskedByteSequenceByteMatcher matcher)) {
byte[] bytes = matcher.getBytes();
if (bytes.length >= byteSize) {
String valueString = getValueString(bytes, settings);
return valueString.replaceAll(",", "");
}
}
return isValidText(text, settings) ? text : "";
}
@Override
public SearchFormatType getFormatType() {
return SearchFormatType.FLOATING_POINT;
}
}

View File

@ -0,0 +1,244 @@
/* ###
* 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.features.base.memsearch.format;
import java.util.*;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.matcher.*;
import ghidra.util.HTMLUtilities;
/**
* {@link SearchFormat} for parsing and display bytes in a hex format. This format only
* accepts hex digits or wild card characters.
*/
class HexSearchFormat extends SearchFormat {
private static final String WILD_CARDS = ".?";
private static final String VALID_CHARS = "0123456789abcdefABCDEF" + WILD_CARDS;
private static final int MAX_GROUP_SIZE = 16;
HexSearchFormat() {
super("Hex");
}
@Override
public ByteMatcher parse(String input, SearchSettings settings) {
input = input.trim();
if (input.isBlank()) {
return new InvalidByteMatcher("");
}
List<String> byteGroups = getByteGroups(input);
if (hasInvalidChars(byteGroups)) {
return new InvalidByteMatcher("Invalid character");
}
if (checkGroupSize(byteGroups)) {
return new InvalidByteMatcher("Max group size exceeded. Enter <space> to add more.");
}
List<String> byteList = getByteList(byteGroups, settings);
byte[] bytes = getBytes(byteList);
byte[] masks = getMask(byteList);
return new MaskedByteSequenceByteMatcher(input, bytes, masks, settings);
}
@Override
public String getToolTip() {
return HTMLUtilities.toHTML("Interpret value as a sequence of\n" +
"hex numbers, separated by spaces.\n" + "Enter '.' or '?' for a wildcard match");
}
private byte[] getBytes(List<String> byteList) {
byte[] bytes = new byte[byteList.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = getByte(byteList.get(i));
}
return bytes;
}
private byte[] getMask(List<String> byteList) {
byte[] masks = new byte[byteList.size()];
for (int i = 0; i < masks.length; i++) {
masks[i] = getMask(byteList.get(i));
}
return masks;
}
/**
* Returns the search mask for the given hex byte string. Normal hex digits result
* in a "1111" mask and wildcard digits result in a "0000" mask.
*/
private byte getMask(String tok) {
char c1 = tok.charAt(0);
char c2 = tok.charAt(1);
int index1 = WILD_CARDS.indexOf(c1);
int index2 = WILD_CARDS.indexOf(c2);
if (index1 >= 0 && index2 >= 0) {
return (byte) 0x00;
}
if (index1 >= 0 && index2 < 0) {
return (byte) 0x0F;
}
if (index1 < 0 && index2 >= 0) {
return (byte) 0xF0;
}
return (byte) 0xFF;
}
/**
* Returns the byte value to be used for the given hex bytes. Handles wildcard characters by
* return treating them as 0s.
*/
private byte getByte(String tok) {
char c1 = tok.charAt(0);
char c2 = tok.charAt(1);
// note: the hexValueOf() method will turn wildcard chars into 0s
return (byte) (hexValueOf(c1) * 16 + hexValueOf(c2));
}
private List<String> getByteList(List<String> byteGroups, SearchSettings settings) {
List<String> byteList = new ArrayList<>();
for (String byteGroup : byteGroups) {
List<String> byteStrings = getByteStrings(byteGroup);
if (!settings.isBigEndian()) {
Collections.reverse(byteStrings);
}
byteList.addAll(byteStrings);
}
return byteList;
}
private List<String> getByteStrings(String token) {
if (isSingleWildCardChar(token)) {
// normally, a wildcard character represents a nibble. For convenience, if the there
// is a single wild card character surrounded by whitespace, treat it
// as if the entire byte is wild
token += token;
}
else if (token.length() % 2 != 0) {
// pad an odd number of nibbles with 0; assuming users leave off leading 0
token = "0" + token;
}
int n = token.length() / 2;
List<String> list = new ArrayList<String>(n);
for (int i = 0; i < n; i++) {
list.add(token.substring(i * 2, i * 2 + 2));
}
return list;
}
private boolean isSingleWildCardChar(String token) {
if (token.length() == 1) {
char c = token.charAt(0);
return WILD_CARDS.indexOf(c) >= 0;
}
return false;
}
private boolean hasInvalidChars(List<String> byteGroups) {
for (String byteGroup : byteGroups) {
if (hasInvalidChars(byteGroup)) {
return true;
}
}
return false;
}
private boolean checkGroupSize(List<String> byteGroups) {
for (String byteGroup : byteGroups) {
if (byteGroup.length() > MAX_GROUP_SIZE) {
return true;
}
}
return false;
}
private List<String> getByteGroups(String input) {
List<String> list = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(input);
while (st.hasMoreTokens()) {
list.add(st.nextToken());
}
return list;
}
private boolean hasInvalidChars(String string) {
for (int i = 0; i < string.length(); i++) {
if (VALID_CHARS.indexOf(string.charAt(i)) < 0) {
return true;
}
}
return false;
}
/**
* Returns the value of the given hex digit character.
*/
private int hexValueOf(char c) {
if ((c >= '0') && (c <= '9')) {
return c - '0';
}
else if ((c >= 'a') && (c <= 'f')) {
return c - 'a' + 10;
}
else if ((c >= 'A') && (c <= 'F')) {
return c - 'A' + 10;
}
else {
return 0;
}
}
@Override
public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) {
SearchFormat oldFormat = oldSettings.getSearchFormat();
if (oldFormat.getClass() == getClass()) {
return text;
}
if (oldFormat.getFormatType() != SearchFormatType.STRING_TYPE) {
ByteMatcher byteMatcher = oldFormat.parse(text, oldSettings);
if ((byteMatcher instanceof MaskedByteSequenceByteMatcher matcher)) {
byte[] bytes = matcher.getBytes();
byte[] mask = matcher.getMask();
return getMaskedInputString(bytes, mask);
}
}
return isValidText(text, newSettings) ? text : "";
}
private String getMaskedInputString(byte[] bytes, byte[] mask) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String s = String.format("%02x", bytes[i]);
builder.append((mask[i] & 0xf0) == 0 ? "." : s.charAt(0));
builder.append((mask[i] & 0x0f) == 0 ? "." : s.charAt(1));
builder.append(" ");
}
return builder.toString().trim();
}
@Override
public SearchFormatType getFormatType() {
return SearchFormatType.BYTE;
}
}

View File

@ -0,0 +1,24 @@
/* ###
* 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.features.base.memsearch.format;
/**
* Used by the NumberSearchFormat and the FloatSearchFormat for intermediate parsing results.
* @param bytes The bytes that match the parsed number sequence
* @param errorMessage an optional parsing error message
* @param validInput boolean if the input was valid
*/
record NumberParseResult(byte[] bytes, String errorMessage, boolean validInput) {}

View File

@ -0,0 +1,66 @@
/* ###
* 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.features.base.memsearch.format;
import java.util.regex.PatternSyntaxException;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.matcher.*;
/**
* {@link SearchFormat} for parsing input as a regular expression. This format can't generate
* bytes or parse results.
*/
class RegExSearchFormat extends SearchFormat {
RegExSearchFormat() {
super("Reg Ex");
}
@Override
public ByteMatcher parse(String input, SearchSettings settings) {
input = input.trim();
if (input.isBlank()) {
return new InvalidByteMatcher("");
}
try {
return new RegExByteMatcher(input, settings);
}
catch (PatternSyntaxException e) {
return new InvalidByteMatcher("RegEx Pattern Error: " + e.getDescription(), true);
}
}
@Override
public String getToolTip() {
return "Interpret value as a regular expression.";
}
@Override
public String getValueString(byte[] bytes, SearchSettings settings) {
return new String(bytes);
}
@Override
public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) {
return isValidText(text, newSettings) ? text : "";
}
@Override
public SearchFormatType getFormatType() {
return SearchFormatType.STRING_TYPE;
}
}

View File

@ -0,0 +1,159 @@
/* ###
* 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.features.base.memsearch.format;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
/**
* SearchFormats are responsible for parsing user input data into a {@link ByteMatcher} that
* can be used for searching memory. It also can convert search matches back into string data and
* can convert string data from other formats into string data for this format.
*/
public abstract class SearchFormat {
//@formatter:off
public static SearchFormat HEX = new HexSearchFormat();
public static SearchFormat BINARY = new BinarySearchFormat();
public static SearchFormat DECIMAL = new DecimalSearchFormat();
public static SearchFormat STRING = new StringSearchFormat();
public static SearchFormat REG_EX = new RegExSearchFormat();
public static SearchFormat FLOAT = new FloatSearchFormat("Float", "Floating Point", 4);
public static SearchFormat DOUBLE = new FloatSearchFormat("Double", "Floating Point (8)", 8);
//@formatter:on
public static SearchFormat[] ALL =
{ HEX, BINARY, DECIMAL, STRING, REG_EX, FLOAT, DOUBLE };
// SearchFormats fall into one of 4 types
public enum SearchFormatType {
BYTE, INTEGER, FLOATING_POINT, STRING_TYPE
}
private final String name;
protected SearchFormat(String name) {
this.name = name;
}
/**
* Parse the given input and settings into a {@link ByteMatcher}
* @param input the user input string
* @param settings the current search/parse settings
* @return a ByteMatcher that can be used for searching bytes (or an error version of a matcher)
*/
public abstract ByteMatcher parse(String input, SearchSettings settings);
/**
* Returns a tool tip describing this search format
* @return a tool tip describing this search format
*/
public abstract String getToolTip();
/**
* Returns the name of the search format.
* @return the name of the search format
*/
public String getName() {
return name;
}
@Override
public String toString() {
return getName();
}
/**
* Reverse parses the bytes back into input value strings. Note that this is only used by
* numerical and string type formats. Byte oriented formats just return an empty string.
* @param bytes the to convert back into input value strings
* @param settings The search settings used to parse the input into bytes
* @return the string of the reversed parsed byte values
*/
public String getValueString(byte[] bytes, SearchSettings settings) {
return "";
}
/**
* Returns a new search input string, doing its best to convert an input string that
* was parsed by a previous {@link SearchFormat}. When it makes sense to do so, it will
* re-interpret the parsed bytes from the old format and reconstruct the input from those
* bytes. This allows the user to do conversions, for example, from numbers to hex or binary and
* vise-versa. If the byte conversion doesn't make sense based on the old and new formats, it
* will use the original input if that input can be parsed by the new input. Finally, if all
* else fails, the new input will be the empty string.
*
* @param text the old input that is parsable by the old format
* @param oldSettings the search settings used to parse the old text
* @param newSettings the search settings to used for the new text
* @return the "best" text to change the user search input to
*/
public abstract String convertText(String text, SearchSettings oldSettings,
SearchSettings newSettings);
/**
* Returns the {@link SearchFormatType} for this format. This is used to help with the
* {@link #convertText(String, SearchSettings, SearchSettings)} method.
* @return the type for this format
*/
public abstract SearchFormatType getFormatType();
/**
* Compares bytes from search results based on how this format interprets the bytes.
* By default, formats just compare the bytes one by one as if they were unsigned values.
* SearchFormats whose bytes represent numerical values will override this method and
* compare the bytes after interpreting them as numerical values.
*
* @param bytes1 the first array of bytes to compare
* @param bytes2 the second array of bytes to compare
* @param settings the search settings used to generate the bytes.
*
* @return a negative integer, zero, or a positive integer as the first byte array
* is less than, equal to, or greater than the second byte array
*
*/
public int compareValues(byte[] bytes1, byte[] bytes2, SearchSettings settings) {
return compareBytesUnsigned(bytes1, bytes2);
}
protected void reverse(byte[] bytes) {
for (int i = 0; i < bytes.length / 2; i++) {
int swapIndex = bytes.length - 1 - i;
byte tmp = bytes[i];
bytes[i] = bytes[swapIndex];
bytes[swapIndex] = tmp;
}
}
private int compareBytesUnsigned(byte[] oldBytes, byte[] newBytes) {
for (int i = 0; i < oldBytes.length; i++) {
int value1 = oldBytes[i] & 0xff;
int value2 = newBytes[i] & 0xff;
if (value1 != value2) {
return value1 - value2;
}
}
return 0;
}
protected boolean isValidText(String text, SearchSettings settings) {
ByteMatcher byteMatcher = parse(text, settings);
return byteMatcher.isValidSearch();
}
}

View File

@ -0,0 +1,145 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.base.memsearch.format;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.matcher.*;
import ghidra.util.StringUtilities;
/**
* {@link SearchFormat} for parsing and display bytes in a string format. This format uses
* several values from SearchSettings included character encoding, case sensitive, and escape
* sequences.
*/
class StringSearchFormat extends SearchFormat {
private final byte CASE_INSENSITIVE_MASK = (byte) 0xdf;
StringSearchFormat() {
super("String");
}
@Override
public ByteMatcher parse(String input, SearchSettings settings) {
input = input.trim();
if (input.isBlank()) {
return new InvalidByteMatcher("");
}
boolean isBigEndian = settings.isBigEndian();
int inputLength = input.length();
Charset charset = settings.getStringCharset();
if (charset == StandardCharsets.UTF_16) {
charset = isBigEndian ? StandardCharsets.UTF_16BE : StandardCharsets.UTF_16LE;
}
// Escape sequences in the "input" are 2 Characters long.
if (settings.useEscapeSequences() && inputLength >= 2) {
input = StringUtilities.convertEscapeSequences(input);
}
byte[] bytes = input.getBytes(charset);
byte[] maskArray = new byte[bytes.length];
Arrays.fill(maskArray, (byte) 0xff);
if (!settings.isCaseSensitive()) {
createCaseInsensitiveBytesAndMasks(charset, bytes, maskArray);
}
return new MaskedByteSequenceByteMatcher(input, bytes, maskArray, settings);
}
private void createCaseInsensitiveBytesAndMasks(Charset encodingCharSet, byte[] bytes,
byte[] masks) {
int i = 0;
while (i < bytes.length) {
if (encodingCharSet == StandardCharsets.US_ASCII &&
Character.isLetter(bytes[i])) {
masks[i] = CASE_INSENSITIVE_MASK;
bytes[i] = (byte) (bytes[i] & CASE_INSENSITIVE_MASK);
i++;
}
else if (encodingCharSet == StandardCharsets.UTF_8) {
int numBytes = bytesPerCharUTF8(bytes[i]);
if (numBytes == 1 && Character.isLetter(bytes[i])) {
masks[i] = CASE_INSENSITIVE_MASK;
bytes[i] = (byte) (bytes[i] & CASE_INSENSITIVE_MASK);
}
i += numBytes;
}
// Assumes UTF-16 will return 2 Bytes for each character.
// 4-byte UTF-16 will never satisfy the below checks because
// none of their bytes can ever be 0.
else if (encodingCharSet == StandardCharsets.UTF_16BE) {
if (bytes[i] == (byte) 0x0 && Character.isLetter(bytes[i + 1])) { // Checks if ascii character.
masks[i + 1] = CASE_INSENSITIVE_MASK;
bytes[i + 1] = (byte) (bytes[i + 1] & CASE_INSENSITIVE_MASK);
}
i += 2;
}
else if (encodingCharSet == StandardCharsets.UTF_16LE) {
if (bytes[i + 1] == (byte) 0x0 && Character.isLetter(bytes[i])) { // Checks if ascii character.
masks[i] = CASE_INSENSITIVE_MASK;
bytes[i] = (byte) (bytes[i] & CASE_INSENSITIVE_MASK);
}
i += 2;
}
else {
i++;
}
}
}
private int bytesPerCharUTF8(byte zByte) {
// This method is intended for UTF-8 encoding.
// The first byte in a sequence of UTF-8 bytes can tell
// us how many bytes make up a char.
int offset = 1;
// If the char is ascii, this loop will be skipped.
while ((zByte & 0x80) != 0x00) {
zByte <<= 1;
offset++;
}
return offset;
}
@Override
public String getToolTip() {
return "Interpret value as a sequence of characters.";
}
@Override
public String getValueString(byte[] bytes, SearchSettings settings) {
boolean isBigEndian = settings.isBigEndian();
Charset charset = settings.getStringCharset();
if (charset == StandardCharsets.UTF_16) {
charset = isBigEndian ? StandardCharsets.UTF_16BE : StandardCharsets.UTF_16LE;
}
return new String(bytes, charset);
}
@Override
public String convertText(String text, SearchSettings oldSettings, SearchSettings newSettings) {
return isValidText(text, newSettings) ? text : "";
}
@Override
public SearchFormatType getFormatType() {
return SearchFormatType.STRING_TYPE;
}
}

View File

@ -0,0 +1,75 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.base.memsearch.gui;
import java.util.Collection;
import java.util.List;
import ghidra.features.base.memsearch.combiner.Combiner;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.features.base.memsearch.searcher.MemorySearcher;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.task.TaskMonitor;
/**
* Table loader that performs a search and then combines the new results with existing results.
*/
public class CombinedMatchTableLoader implements MemoryMatchTableLoader {
private MemorySearcher memSearcher;
private List<MemoryMatch> previousResults;
private Combiner combiner;
private boolean completedSearch;
private MemoryMatch firstMatch;
public CombinedMatchTableLoader(MemorySearcher memSearcher,
List<MemoryMatch> previousResults, Combiner combiner) {
this.memSearcher = memSearcher;
this.previousResults = previousResults;
this.combiner = combiner;
}
@Override
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
ListAccumulator<MemoryMatch> listAccumulator = new ListAccumulator<>();
completedSearch = memSearcher.findAll(listAccumulator, monitor);
List<MemoryMatch> followOnResults = listAccumulator.asList();
firstMatch = followOnResults.isEmpty() ? null : followOnResults.get(0);
Collection<MemoryMatch> results = combiner.combine(previousResults, followOnResults);
accumulator.addAll(results);
}
@Override
public boolean didTerminateEarly() {
return !completedSearch;
}
@Override
public void dispose() {
previousResults = null;
}
@Override
public MemoryMatch getFirstMatch() {
return firstMatch;
}
@Override
public boolean hasResults() {
return firstMatch != null;
}
}

View File

@ -0,0 +1,51 @@
/* ###
* 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.features.base.memsearch.gui;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.task.TaskMonitor;
/**
* Table loader for clearing the existing results
*/
public class EmptyMemoryMatchTableLoader implements MemoryMatchTableLoader {
@Override
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
return;
}
@Override
public void dispose() {
// nothing to do
}
@Override
public boolean didTerminateEarly() {
return false;
}
@Override
public MemoryMatch getFirstMatch() {
return null;
}
@Override
public boolean hasResults() {
return false;
}
}

View File

@ -0,0 +1,94 @@
/* ###
* 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.features.base.memsearch.gui;
import java.util.List;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.features.base.memsearch.searcher.MemorySearcher;
import ghidra.program.model.address.Address;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.task.TaskMonitor;
/**
* Table loader for executing an incremental search forwards or backwards and adding that result
* to the table.
*/
public class FindOnceTableLoader implements MemoryMatchTableLoader {
private MemorySearcher searcher;
private Address address;
private List<MemoryMatch> previousResults;
private MemorySearchResultsPanel panel;
private MemoryMatch match;
private boolean forward;
public FindOnceTableLoader(MemorySearcher searcher, Address address,
List<MemoryMatch> previousResults, MemorySearchResultsPanel panel, boolean forward) {
this.searcher = searcher;
this.address = address;
this.previousResults = previousResults;
this.panel = panel;
this.forward = forward;
}
@Override
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
accumulator.addAll(previousResults);
match = searcher.findOnce(address, forward, monitor);
if (match != null) {
MemoryMatch existing = findExisingMatch(match.getAddress());
if (existing != null) {
existing.updateBytes(match.getBytes());
}
else {
accumulator.add(match);
}
}
}
private MemoryMatch findExisingMatch(Address newMatchAddress) {
for (MemoryMatch memoryMatch : previousResults) {
if (newMatchAddress.equals(memoryMatch.getAddress())) {
return memoryMatch;
}
}
return null;
}
@Override
public boolean didTerminateEarly() {
return false;
}
@Override
public MemoryMatch getFirstMatch() {
return match;
}
@Override
public void dispose() {
previousResults = null;
}
@Override
public boolean hasResults() {
return match != null;
}
}

View File

@ -0,0 +1,242 @@
/* ###
* 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.features.base.memsearch.gui;
import java.awt.Color;
import java.util.*;
import org.apache.commons.lang3.ArrayUtils;
import docking.widgets.fieldpanel.support.Highlight;
import docking.widgets.table.threaded.ThreadedTableModelListener;
import ghidra.app.nav.Navigatable;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.SearchConstants;
import ghidra.app.util.viewer.field.*;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
/**
* Listing highlight provider to highlight memory search results.
*/
public class MemoryMatchHighlighter implements ListingHighlightProvider {
private Navigatable navigatable;
private Program program;
private List<MemoryMatch> sortedResults;
private MemoryMatchTableModel model;
private MemorySearchOptions options;
private MemoryMatch selectedMatch;
public MemoryMatchHighlighter(Navigatable navigatable, MemoryMatchTableModel model,
MemorySearchOptions options) {
this.model = model;
this.options = options;
this.navigatable = navigatable;
this.program = navigatable.getProgram();
model.addThreadedTableModelListener(new ThreadedTableModelListener() {
@Override
public void loadingStarted() {
clearCache();
}
@Override
public void loadingFinished(boolean wasCancelled) {
// stub
}
@Override
public void loadPending() {
clearCache();
}
});
}
@Override
public Highlight[] createHighlights(String text, ListingField field, int cursorTextOffset) {
if (!options.isShowHighlights()) {
return NO_HIGHLIGHTS;
}
if (program != navigatable.getProgram()) {
return NO_HIGHLIGHTS;
}
Class<? extends FieldFactory> fieldFactoryClass = field.getFieldFactory().getClass();
if (fieldFactoryClass != BytesFieldFactory.class) {
return NO_HIGHLIGHTS;
}
ProxyObj<?> proxy = field.getProxy();
Object obj = proxy.getObject();
if (!(obj instanceof CodeUnit cu)) {
return NO_HIGHLIGHTS;
}
Address minAddr = cu.getMinAddress();
Address maxAddr = cu.getMaxAddress();
List<MemoryMatch> results = getMatchesInRange(minAddr, maxAddr);
if (results.isEmpty()) {
return NO_HIGHLIGHTS;
}
return getHighlights(text, minAddr, results);
}
private Highlight[] getHighlights(String text, Address minAddr, List<MemoryMatch> results) {
Highlight[] highlights = new Highlight[results.size()];
int selectedMatchIndex = -1;
for (int i = 0; i < highlights.length; i++) {
MemoryMatch match = results.get(i);
Color highlightColor = SearchConstants.SEARCH_HIGHLIGHT_COLOR;
if (match == selectedMatch) {
selectedMatchIndex = i;
highlightColor = SearchConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR;
}
highlights[i] = createHighlight(match, minAddr, text, highlightColor);
}
// move the selected match to the end so that it gets painted last and doesn't get
// painted over by the non-active highlights
if (selectedMatchIndex >= 0) {
ArrayUtils.swap(highlights, selectedMatchIndex, highlights.length - 1);
}
return highlights;
}
private Highlight createHighlight(MemoryMatch match, Address start, String text, Color color) {
int highlightLength = match.getLength();
Address address = match.getAddress();
int startByteOffset = (int) address.subtract(start);
int endByteOffset = startByteOffset + highlightLength - 1;
startByteOffset = Math.max(startByteOffset, 0);
return getHighlight(text, startByteOffset, endByteOffset, color);
}
private Highlight getHighlight(String text, int start, int end, Color color) {
int charStart = getCharPosition(text, start);
int charEnd = getCharPosition(text, end) + 1;
return new Highlight(charStart, charEnd, color);
}
private int getCharPosition(String text, int byteOffset) {
int byteGroupSize = options.getByteGroupSize();
int byteDelimiterLength = options.getByteDelimiter().length();
int groupSize = byteGroupSize * 2 + byteDelimiterLength;
int groupIndex = byteOffset / byteGroupSize;
int groupOffset = byteOffset % byteGroupSize;
int pos = groupIndex * groupSize + 2 * groupOffset;
return Math.min(text.length() - 1, pos);
}
List<MemoryMatch> getMatches() {
if (sortedResults != null) {
return sortedResults;
}
if (model.isBusy()) {
return Collections.emptyList();
}
List<MemoryMatch> modelData = model.getModelData();
if (model.isSortedOnAddress()) {
return modelData;
}
sortedResults = new ArrayList<>(modelData);
Collections.sort(sortedResults);
return sortedResults;
}
private List<MemoryMatch> getMatchesInRange(Address start, Address end) {
List<MemoryMatch> matches = getMatches();
int startIndex = findFirstIndex(matches, start, end);
if (startIndex < 0) {
return Collections.emptyList();
}
int endIndex = findIndexAtOrGreater(matches, end);
if (endIndex < matches.size() && (matches.get(endIndex).getAddress().equals(end))) {
endIndex++; // end index is non-inclusive and we want to include direct hit
}
List<MemoryMatch> resultList = matches.subList(startIndex, endIndex);
return resultList;
}
private int findFirstIndex(List<MemoryMatch> matches, Address start, Address end) {
int startIndex = findIndexAtOrGreater(matches, start);
if (startIndex > 0) { // see if address before extends into this range.
MemoryMatch resultBefore = matches.get(startIndex - 1);
Address beforeAddr = resultBefore.getAddress();
int length = resultBefore.getLength();
if (start.hasSameAddressSpace(beforeAddr) && start.subtract(beforeAddr) < length) {
return startIndex - 1;
}
}
if (startIndex == matches.size()) {
return -1;
}
MemoryMatch result = matches.get(startIndex);
Address addr = result.getAddress();
if (end.compareTo(addr) >= 0) {
return startIndex;
}
return -1;
}
private int findIndexAtOrGreater(List<MemoryMatch> matches, Address address) {
MemoryMatch key = new MemoryMatch(address);
int index = Collections.binarySearch(matches, key);
if (index < 0) {
index = -index - 1;
}
return index;
}
private void clearCache() {
if (sortedResults != null) {
sortedResults.clear();
sortedResults = null;
}
}
void dispose() {
navigatable.removeHighlightProvider(this, program);
clearCache();
}
void setSelectedMatch(MemoryMatch selectedMatch) {
this.selectedMatch = selectedMatch;
}
}

View File

@ -0,0 +1,60 @@
/* ###
* 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.features.base.memsearch.gui;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.task.TaskMonitor;
/**
* Interface for loading the memory search results table. Various implementations handle the
* different cases such as a search all, or a search next, or combining results with a previous
* search, etc.
*/
public interface MemoryMatchTableLoader {
/**
* Called by the table model to initiate searching and loading using the threaded table models
* threading infrastructure.
* @param accumulator the accumulator to store results that will appear in the results table
* @param monitor the task monitor
*/
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor);
/**
* Returns true if the search/loading did not fully complete. (Search limit reached, cancelled
* by user, etc.)
* @return true if the search/loading did not fully complete
*/
public boolean didTerminateEarly();
/**
* Cleans up resources
*/
public void dispose();
/**
* Returns the first match found. Typically used to navigate the associated navigatable.
* @return the first match found
*/
public MemoryMatch getFirstMatch();
/**
* Returns true if at least one match was found.
* @return true if at least one match was found
*/
public boolean hasResults();
}

View File

@ -0,0 +1,287 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.base.memsearch.gui;
import java.awt.*;
import docking.widgets.table.*;
import generic.theme.GThemeDefaults.Colors.Tables;
import ghidra.docking.settings.Settings;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.*;
import ghidra.util.HTMLUtilities;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.AddressBasedTableModel;
import ghidra.util.table.column.AbstractGColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
import ghidra.util.table.field.*;
import ghidra.util.task.TaskMonitor;
/**
* Table model for memory search results.
*/
public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
private Color CHANGED_COLOR = Tables.ERROR_UNSELECTED;
private Color CHANGED_SELECTED_COLOR = Tables.ERROR_SELECTED;
private MemoryMatchTableLoader loader;
MemoryMatchTableModel(ServiceProvider serviceProvider, Program program) {
super("Memory Search", serviceProvider, program, null, true);
}
@Override
protected TableColumnDescriptor<MemoryMatch> createTableColumnDescriptor() {
TableColumnDescriptor<MemoryMatch> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn(
DiscoverableTableUtils.adaptColumForModel(this, new AddressTableColumn()), 1, true);
descriptor.addVisibleColumn(new MatchBytesColumn());
descriptor.addVisibleColumn(new MatchValueColumn());
descriptor.addVisibleColumn(
DiscoverableTableUtils.adaptColumForModel(this, new LabelTableColumn()));
descriptor.addVisibleColumn(
DiscoverableTableUtils.adaptColumForModel(this, new CodeUnitTableColumn()));
return descriptor;
}
@Override
protected void doLoad(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor)
throws CancelledException {
if (loader == null) {
return;
}
loader.loadResults(accumulator, monitor);
loader = null;
}
void setLoader(MemoryMatchTableLoader loader) {
this.loader = loader;
reload();
}
public boolean isSortedOnAddress() {
TableSortState sortState = getTableSortState();
if (sortState.isUnsorted()) {
return false;
}
ColumnSortState primaryState = sortState.getAllSortStates().get(0);
DynamicTableColumn<MemoryMatch, ?, ?> column =
getColumn(primaryState.getColumnModelIndex());
String name = column.getColumnName();
if (AddressTableColumn.NAME.equals(name)) {
return true;
}
return false;
}
@Override
public ProgramLocation getProgramLocation(int modelRow, int modelColumn) {
Program p = getProgram();
if (p == null) {
return null; // we've been disposed
}
DynamicTableColumn<MemoryMatch, ?, ?> column = getColumn(modelColumn);
Class<?> columnClass = column.getClass();
if (column instanceof MappedTableColumn mappedColumn) {
columnClass = mappedColumn.getMappedColumnClass();
}
if (columnClass == AddressTableColumn.class || columnClass == MatchBytesColumn.class ||
columnClass == MatchValueColumn.class) {
return new BytesFieldLocation(p, getAddress(modelRow));
}
return super.getProgramLocation(modelRow, modelColumn);
}
@Override
public Address getAddress(int row) {
MemoryMatch result = getRowObject(row);
return result.getAddress();
}
@Override
public ProgramSelection getProgramSelection(int[] rows) {
AddressSet addressSet = new AddressSet();
for (int row : rows) {
MemoryMatch result = getRowObject(row);
int addOn = result.getLength() - 1;
Address minAddr = getAddress(row);
Address maxAddr = minAddr;
try {
maxAddr = minAddr.addNoWrap(addOn);
addressSet.addRange(minAddr, maxAddr);
}
catch (AddressOverflowException e) {
// I guess we don't care--not sure why this is undocumented :(
}
}
return new ProgramSelection(addressSet);
}
public class MatchBytesColumn
extends DynamicTableColumnExtensionPoint<MemoryMatch, String, Program> {
private ByteArrayRenderer renderer = new ByteArrayRenderer();
@Override
public String getColumnName() {
return "Match Bytes";
}
@Override
public String getValue(MemoryMatch match, Settings settings, Program pgm,
ServiceProvider service) throws IllegalArgumentException {
return getByteString(match.getBytes());
}
private String getByteString(byte[] bytes) {
StringBuilder b = new StringBuilder();
int max = bytes.length - 1;
for (int i = 0;; i++) {
b.append(String.format("%02x", bytes[i]));
if (i == max) {
break;
}
b.append(" ");
}
return b.toString();
}
@Override
public int getColumnPreferredWidth() {
return 200;
}
@Override
public GColumnRenderer<String> getColumnRenderer() {
return renderer;
}
}
public class MatchValueColumn
extends DynamicTableColumnExtensionPoint<MemoryMatch, String, Program> {
private ValueRenderer renderer = new ValueRenderer();
@Override
public String getColumnName() {
return "Match Value";
}
@Override
public String getValue(MemoryMatch match, Settings settings, Program pgm,
ServiceProvider service) throws IllegalArgumentException {
ByteMatcher byteMatcher = match.getByteMatcher();
SearchSettings searchSettings = byteMatcher.getSettings();
SearchFormat format = searchSettings.getSearchFormat();
return format.getValueString(match.getBytes(), searchSettings);
}
@Override
public int getColumnPreferredWidth() {
return 200;
}
@Override
public GColumnRenderer<String> getColumnRenderer() {
return renderer;
}
}
private class ByteArrayRenderer extends AbstractGColumnRenderer<String> {
public ByteArrayRenderer() {
setHTMLRenderingEnabled(true);
}
@Override
protected Font getDefaultFont() {
return fixedWidthFont;
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
MemoryMatch match = (MemoryMatch) data.getRowObject();
String text = data.getValue().toString();
if (match.isChanged()) {
text = getHtmlColoredString(match, data.isSelected());
}
setText(text);
return this;
}
private String getHtmlColoredString(MemoryMatch match, boolean isSelected) {
Color color = isSelected ? Tables.ERROR_SELECTED : Tables.ERROR_UNSELECTED;
StringBuilder b = new StringBuilder();
b.append("<HTML>");
byte[] bytes = match.getBytes();
byte[] previousBytes = match.getPreviousBytes();
int max = bytes.length - 1;
for (int i = 0;; i++) {
String byteString = String.format("%02x", bytes[i]);
if (bytes[i] != previousBytes[i]) {
byteString = HTMLUtilities.colorString(color, byteString);
}
b.append(byteString);
if (i == max)
break;
b.append(" ");
}
return b.toString();
}
@Override
public String getFilterString(String t, Settings settings) {
// This returns the formatted string without the formatted markup
return t;
}
}
private class ValueRenderer extends AbstractGColumnRenderer<String> {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
setText((String) data.getValue());
MemoryMatch match = (MemoryMatch) data.getRowObject();
if (match.isChanged()) {
setForeground(data.isSelected() ? CHANGED_SELECTED_COLOR : CHANGED_COLOR);
}
return this;
}
@Override
public String getFilterString(String t, Settings settings) {
return t;
}
}
}

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.features.base.memsearch.gui;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.util.table.ProgramLocationTableRowMapper;
/**
* Maps {@link MemoryMatch} objects (search result) to an address to pick up address based
* table columns.
*/
public class MemoryMatchToAddressTableRowMapper
extends ProgramLocationTableRowMapper<MemoryMatch, Address> {
@Override
public Address map(MemoryMatch rowObject, Program data, ServiceProvider serviceProvider) {
return rowObject.getAddress();
}
}

View File

@ -0,0 +1,37 @@
/* ###
* 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.features.base.memsearch.gui;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.table.ProgramLocationTableRowMapper;
/**
* Maps {@link MemoryMatch} objects (search result) to program locations to pick up
* program location based table columns.
*/
public class MemoryMatchToProgramLocationTableRowMapper
extends ProgramLocationTableRowMapper<MemoryMatch, ProgramLocation> {
@Override
public ProgramLocation map(MemoryMatch rowObject, Program program,
ServiceProvider serviceProvider) {
return new ProgramLocation(program, rowObject.getAddress());
}
}

View File

@ -0,0 +1,37 @@
/* ###
* 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.features.base.memsearch.gui;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.*;
import ghidra.util.table.ProgramLocationTableRowMapper;
/**
* Maps {@link MemoryMatch} objects (search result) to functions to pick up function based
* table columns.
*/
public class MemoryMatchtToFunctionTableRowMapper
extends ProgramLocationTableRowMapper<MemoryMatch, Function> {
@Override
public Function map(MemoryMatch rowObject, Program program,
ServiceProvider serviceProvider) {
FunctionManager functionManager = program.getFunctionManager();
return functionManager.getFunctionContaining(rowObject.getAddress());
}
}

View File

@ -0,0 +1,81 @@
/* ###
* 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.features.base.memsearch.gui;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.*;
import docking.widgets.button.GRadioButton;
import ghidra.app.util.HelpTopics;
import ghidra.features.base.memsearch.scan.Scanner;
import ghidra.util.HelpLocation;
import help.Help;
import help.HelpService;
/**
* Internal panel of the memory search window that manages the controls for the scan feature. This
* panel can be added or removed via a toolbar action. Not showing by default.
*/
public class MemoryScanControlPanel extends JPanel {
private Scanner selectedScanner = Scanner.NOT_EQUALS;
private boolean hasResults;
private boolean isBusy;
private JButton scanButton;
MemoryScanControlPanel(MemorySearchProvider provider) {
super(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
add(buildButtonPanel(), BorderLayout.CENTER);
scanButton = new JButton("Scan Values");
scanButton.setToolTipText("Refreshes byte values of current results and eliminates " +
"those that don't meet the selected change criteria");
HelpService helpService = Help.getHelpService();
helpService.registerHelp(this, new HelpLocation(HelpTopics.SEARCH, "Scan_Controls"));
add(scanButton, BorderLayout.WEST);
scanButton.addActionListener(e -> provider.scan(selectedScanner));
}
private JComponent buildButtonPanel() {
JPanel panel = new JPanel(new FlowLayout());
ButtonGroup buttonGroup = new ButtonGroup();
for (Scanner scanner : Scanner.values()) {
GRadioButton button = new GRadioButton(scanner.getName());
buttonGroup.add(button);
panel.add(button);
button.setSelected(scanner == selectedScanner);
button.addActionListener(e -> selectedScanner = scanner);
button.setToolTipText(scanner.getDescription());
}
return panel;
}
public void setSearchStatus(boolean hasResults, boolean isBusy) {
this.hasResults = hasResults;
this.isBusy = isBusy;
updateScanButton();
}
private void updateScanButton() {
scanButton.setEnabled(canScan());
}
private boolean canScan() {
return hasResults && !isBusy;
}
}

View File

@ -0,0 +1,452 @@
/* ###
* 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.features.base.memsearch.gui;
import static ghidra.features.base.memsearch.combiner.Combiner.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.text.*;
import docking.DockingUtils;
import docking.menu.ButtonState;
import docking.menu.MultiStateButton;
import docking.widgets.PopupWindow;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GDLabel;
import docking.widgets.list.GComboBoxCellRenderer;
import generic.theme.GThemeDefaults.Colors.Messages;
import ghidra.features.base.memsearch.combiner.Combiner;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.features.base.memsearch.matcher.InvalidByteMatcher;
import ghidra.util.HTMLUtilities;
import ghidra.util.Swing;
import ghidra.util.layout.PairLayout;
import ghidra.util.layout.VerticalLayout;
import ghidra.util.timer.GTimer;
/**
* Internal panel of the memory search window that manages the controls for the search feature. This
* panel can be added or removed via a toolbar action. This panel is showing by default.
*/
class MemorySearchControlPanel extends JPanel {
private MultiStateButton<Combiner> searchButton;
private GhidraComboBox<ByteMatcher> searchInputField;
private GDLabel hexSearchSequenceField;
private boolean hasResults;
private ByteMatcher currentMatcher = new InvalidByteMatcher("");
private SearchHistory searchHistory;
private SearchGuiModel model;
private JCheckBox selectionCheckbox;
private boolean isBusy;
private MemorySearchProvider provider;
private List<ButtonState<Combiner>> initialSearchButtonStates;
private List<ButtonState<Combiner>> combinerSearchButtonStates;
private JComboBox<SearchFormat> formatComboBox;
private PopupWindow popup;
private String errorMessage;
MemorySearchControlPanel(MemorySearchProvider provider, SearchGuiModel model,
SearchHistory history) {
super(new BorderLayout());
this.provider = provider;
this.searchHistory = history;
this.model = model;
model.addChangeCallback(this::guiModelChanged);
initialSearchButtonStates = createButtonStatesForInitialSearch();
combinerSearchButtonStates = createButtonStatesForAdditionSearches();
setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
add(buildLeftSearchInputPanel(), BorderLayout.CENTER);
add(buildRightSearchInputPanel(), BorderLayout.EAST);
}
private JComponent buildRightSearchInputPanel() {
JPanel panel = new JPanel(new VerticalLayout(5));
panel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
searchButton = new MultiStateButton<Combiner>(initialSearchButtonStates);
searchButton
.setStateChangedListener(state -> model.setMatchCombiner(state.getClientData()));
searchButton.addActionListener(e -> search());
panel.add(searchButton, BorderLayout.WEST);
selectionCheckbox = new JCheckBox("Selection Only");
selectionCheckbox.setSelected(model.isSearchSelectionOnly());
selectionCheckbox.setEnabled(model.hasSelection());
selectionCheckbox
.setToolTipText("If selected, search will be restricted to selected addresses");
selectionCheckbox.addActionListener(
e -> model.setSearchSelectionOnly(selectionCheckbox.isSelected()));
panel.add(selectionCheckbox);
searchButton.setEnabled(false);
return panel;
}
private List<ButtonState<Combiner>> createButtonStatesForAdditionSearches() {
List<ButtonState<Combiner>> states = new ArrayList<>();
states.add(new ButtonState<Combiner>("New Search", "New Search",
"Replaces the current results with the new search results", REPLACE));
states.add(new ButtonState<Combiner>("Add To Search", "A union B",
"Adds the results of the new search to the existing results", UNION));
states.add(new ButtonState<Combiner>("Intersect Search", "A intersect B",
"Keep results that in both the existing and new results", INTERSECT));
states.add(new ButtonState<Combiner>("Xor Search", "A xor B",
"Keep results that are in either existig or results, but not both", XOR));
states.add(new ButtonState<Combiner>("A-B Search", "A - B",
"Subtracts the new results from the existing results", A_MINUS_B));
states.add(new ButtonState<Combiner>("B-A Search", "B - A",
"Subtracts the existing results from the new results.", B_MINUS_A));
return states;
}
private List<ButtonState<Combiner>> createButtonStatesForInitialSearch() {
List<ButtonState<Combiner>> states = new ArrayList<>();
states.add(new ButtonState<Combiner>("Search", "",
"Perform a search for the entered values.", null));
return states;
}
private void guiModelChanged(SearchSettings oldSettings) {
SearchFormat searchFormat = model.getSearchFormat();
if (!formatComboBox.getSelectedItem().equals(searchFormat)) {
formatComboBox.setSelectedItem(searchFormat);
}
selectionCheckbox.setSelected(model.isSearchSelectionOnly());
selectionCheckbox.setEnabled(model.hasSelection());
searchInputField.setToolTipText(searchFormat.getToolTip());
String text = searchInputField.getText();
String convertedText = searchFormat.convertText(text, oldSettings, model.getSettings());
searchInputField.setText(convertedText);
ByteMatcher byteMatcher = searchFormat.parse(convertedText, model.getSettings());
setByteMatcher(byteMatcher);
}
private JComponent buildLeftSearchInputPanel() {
createSearchInputField();
hexSearchSequenceField = new GDLabel();
hexSearchSequenceField.setName("HexSequenceField");
Border outerBorder = BorderFactory.createLoweredBevelBorder();
Border innerBorder = BorderFactory.createEmptyBorder(0, 4, 0, 4);
Border border = BorderFactory.createCompoundBorder(outerBorder, innerBorder);
hexSearchSequenceField.setBorder(border);
JPanel panel = new JPanel(new PairLayout(2, 10));
panel.add(buildSearchFormatCombo());
panel.add(searchInputField);
JLabel byteSequenceLabel = new JLabel("Byte Sequence:", SwingConstants.RIGHT);
byteSequenceLabel.setToolTipText(
"This field shows the byte sequence that will be search (if applicable)");
panel.add(byteSequenceLabel);
panel.add(hexSearchSequenceField);
return panel;
}
private void createSearchInputField() {
searchInputField = new GhidraComboBox<>() {
@Override
public void setSelectedItem(Object obj) {
if (obj instanceof String) {
// this can happen when a user types a string and presses enter
// our data model is ByteMatcher, not strings
return;
}
ByteMatcher matcher = (ByteMatcher) obj;
model.setSettings(matcher.getSettings());
super.setSelectedItem(obj);
}
};
updateCombo();
searchInputField.setAutoCompleteEnabled(false); // this interferes with validation
searchInputField.setEditable(true);
searchInputField.setToolTipText(model.getSearchFormat().getToolTip());
searchInputField.setDocument(new RestrictedInputDocument());
searchInputField.addActionListener(ev -> search());
JTextField searchTextField = searchInputField.getTextField();
// add escape key listener to dismiss any error popup windows
searchTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(java.awt.event.KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
clearInputError();
e.consume();
}
}
});
// add focus lost listener to dismiss any error popup windows
searchTextField.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
clearInputError();
}
});
searchInputField.setRenderer(new SearchHistoryRenderer());
}
private boolean canSearch() {
return !isBusy && currentMatcher.isValidSearch();
}
private void search() {
if (canSearch()) {
provider.search();
searchHistory.addSearch(currentMatcher);
updateCombo();
}
}
private JComponent buildSearchFormatCombo() {
formatComboBox = new JComboBox<>(SearchFormat.ALL);
formatComboBox.setSelectedItem(model.getSearchFormat());
formatComboBox.addItemListener(this::formatComboChanged);
formatComboBox.setToolTipText("The selected format will determine how to " +
"interpret text typed into the input field");
return formatComboBox;
}
private void formatComboChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) {
return;
}
SearchFormat newFormat = (SearchFormat) e.getItem();
SearchSettings oldSettings = model.getSettings();
SearchSettings newSettings = oldSettings.withSearchFormat(newFormat);
String newText = convertInput(oldSettings, newSettings);
model.setSearchFormat(newFormat);
searchInputField.setText(newText);
}
String convertInput(SearchSettings oldSettings, SearchSettings newSettings) {
String text = searchInputField.getText();
SearchFormat newFormat = newSettings.getSearchFormat();
return newFormat.convertText(text, oldSettings, newSettings);
}
private void setByteMatcher(ByteMatcher byteMatcher) {
clearInputError();
currentMatcher = byteMatcher;
String text = currentMatcher.getDescription();
hexSearchSequenceField.setText(text);
hexSearchSequenceField.setToolTipText(currentMatcher.getToolTip());
updateSearchButton();
provider.setByteMatcher(byteMatcher);
}
void setSearchStatus(boolean hasResults, boolean isBusy) {
this.hasResults = hasResults;
this.isBusy = isBusy;
updateSearchButton();
}
private void updateSearchButton() {
searchButton.setEnabled(canSearch());
if (!hasResults) {
searchButton.setButtonStates(initialSearchButtonStates);
return;
}
Combiner combiner = model.getMatchCombiner();
searchButton.setButtonStates(combinerSearchButtonStates);
searchButton.setSelectedStateByClientData(combiner);
}
private void adjustLocationForCaretPosition(Point location) {
JTextField textField = searchInputField.getTextField();
Caret caret = textField.getCaret();
Point p = caret.getMagicCaretPosition();
if (p != null) {
location.x += p.x;
}
}
private void reportInputError(String message) {
this.errorMessage = message;
// Sometimes when user input is being processed we will get multiple events, with initial
// events putting our model in a bad state, but with follow-up events correcting the state.
// By showing the error message later, we give the follow-up events a change to fix the
// state and clear the error message which prevents the temporary bad state from actually
// displaying an error message to the user.
Swing.runLater(this::popupErrorMessage);
}
private void popupErrorMessage() {
if (errorMessage == null) {
return;
}
errorMessage = null;
DockingUtils.setTipWindowEnabled(false);
Point location = searchInputField.getLocation();
adjustLocationForCaretPosition(location);
location.y += searchInputField.getHeight() + 5;
JToolTip tip = new JToolTip();
tip.setTipText(errorMessage);
if (popup != null) {
popup.dispose();
}
popup = new PopupWindow(tip);
popup.showPopup(searchInputField, location, true);
GTimer.scheduleRunnable(1500, this::clearInputError);
Toolkit.getDefaultToolkit().beep();
}
private void clearInputError() {
errorMessage = null;
DockingUtils.setTipWindowEnabled(true);
PopupWindow.hideAllWindows();
if (popup != null) {
popup.dispose();
}
}
private void updateCombo() {
ByteMatcher[] historyArray = searchHistory.getHistoryAsArray();
searchInputField.setModel(new DefaultComboBoxModel<>(historyArray));
}
/**
* Custom Document that validates user input on the fly.
*/
public class RestrictedInputDocument extends DefaultStyledDocument {
/**
* Called before new user input is inserted into the entry text field. The super
* method is called if the input is accepted.
*/
@Override
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
// allow pasting numbers in forms like 0xABC or ABCh
str = removeNumberBasePrefixAndSuffix(str);
String currentText = getText(0, getLength());
String beforeOffset = currentText.substring(0, offs);
String afterOffset = currentText.substring(offs, currentText.length());
String proposedText = beforeOffset + str + afterOffset;
ByteMatcher byteMatcher = model.parse(proposedText);
if (!byteMatcher.isValidInput()) {
reportInputError(byteMatcher.getDescription());
return;
}
super.insertString(offs, str, a);
setByteMatcher(byteMatcher);
}
/**
* Called before the user deletes some text. If the result is valid, the super
* method is called.
*/
@Override
public void remove(int offs, int len) throws BadLocationException {
clearInputError();
String currentText = getText(0, getLength());
String beforeOffset = currentText.substring(0, offs);
String afterOffset = currentText.substring(len + offs, currentText.length());
String proposedResult = beforeOffset + afterOffset;
if (proposedResult.length() == 0) {
super.remove(offs, len);
setByteMatcher(new InvalidByteMatcher(""));
return;
}
ByteMatcher byteMatcher = model.parse(proposedResult);
if (!byteMatcher.isValidInput()) {
reportInputError(byteMatcher.getDescription());
return;
}
super.remove(offs, len);
setByteMatcher(byteMatcher);
}
private String removeNumberBasePrefixAndSuffix(String str) {
SearchFormat format = model.getSearchFormat();
if (!(format == SearchFormat.HEX || format == SearchFormat.BINARY)) {
return str;
}
String numMaybe = str.strip();
String lowercase = numMaybe.toLowerCase();
if (format == SearchFormat.HEX) {
if (lowercase.startsWith("0x")) {
numMaybe = numMaybe.substring(2);
}
else if (lowercase.startsWith("$")) {
numMaybe = numMaybe.substring(1);
}
else if (lowercase.endsWith("h")) {
numMaybe = numMaybe.substring(0, numMaybe.length() - 1);
}
}
else {
if (lowercase.startsWith("0b")) {
numMaybe = numMaybe.substring(2);
}
}
// check if the resultant number looks valid for insertion (i.e. not empty)
if (!numMaybe.isEmpty()) {
return numMaybe;
}
return str;
}
}
void setSearchInput(String initialInput) {
searchInputField.setText(initialInput);
}
private class SearchHistoryRenderer extends GComboBoxCellRenderer<ByteMatcher> {
{
setHTMLRenderingEnabled(true);
}
@Override
public Component getListCellRendererComponent(JList<? extends ByteMatcher> list,
ByteMatcher matcher, int index,
boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, matcher, index, isSelected, cellHasFocus);
Font font = getFont();
int formatSize = Math.max(font.getSize() - 3, 6);
SearchFormat format = matcher.getSettings().getSearchFormat();
String formatHint = HTMLUtilities.setFontSize(format.getName(), formatSize);
if (!isSelected) {
formatHint = HTMLUtilities.colorString(Messages.HINT, formatHint);
}
setText("<html>" + matcher.getInput() + " <I>" + formatHint + "</I>");
return this;
}
}
}

View File

@ -0,0 +1,139 @@
/* ###
* 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.features.base.memsearch.gui;
import static ghidra.GhidraOptions.*;
import static ghidra.app.util.SearchConstants.*;
import ghidra.GhidraOptions;
import ghidra.app.util.SearchConstants;
import ghidra.app.util.viewer.field.BytesFieldFactory;
import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.bean.opteditor.OptionsVetoException;
/**
* Class for managing search tool options.
*/
public class MemorySearchOptions {
private static final String PRE_POPULATE_MEM_SEARCH = "Pre-populate Memory Search";
private static final String AUTO_RESTRICT_SELECTION = "Auto Restrict Search on Selection";
private OptionsChangeListener searchOptionsListener;
private OptionsChangeListener browserOptionsListener;
private boolean prepopulateSearch = true;
private int searchLimit = DEFAULT_SEARCH_LIMIT;
private boolean highlightMatches = true;
private boolean autoRestrictSelection = true;
private int byteGroupSize;
private String byteDelimiter;
public MemorySearchOptions(PluginTool tool) {
registerSearchOptions(tool);
registerBrowserOptionsListener(tool);
}
public MemorySearchOptions() {
}
public int getByteGroupSize() {
return byteGroupSize;
}
public String getByteDelimiter() {
return byteDelimiter;
}
public boolean isShowHighlights() {
return highlightMatches;
}
public int getSearchLimit() {
return searchLimit;
}
public boolean isAutoRestrictSelection() {
return autoRestrictSelection;
}
private void registerSearchOptions(PluginTool tool) {
ToolOptions options = tool.getOptions(SearchConstants.SEARCH_OPTION_NAME);
options.registerOption(PRE_POPULATE_MEM_SEARCH, prepopulateSearch, null,
"Initializes memory search byte sequence from " +
"the current selection provided the selection is less than 10 bytes.");
options.registerOption(AUTO_RESTRICT_SELECTION, autoRestrictSelection, null,
"Automactically restricts searches to the to the current selection," +
" if a selection exists");
options.registerOption(SearchConstants.SEARCH_HIGHLIGHT_NAME, highlightMatches, null,
"Toggles highlight search results");
options.registerThemeColorBinding(SearchConstants.SEARCH_HIGHLIGHT_COLOR_OPTION_NAME,
SearchConstants.SEARCH_HIGHLIGHT_COLOR.getId(), null,
"The search result highlight color");
options.registerThemeColorBinding(
SearchConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_OPTION_NAME,
SearchConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR.getId(), null,
"The search result highlight color for the currently selected match");
loadSearchOptions(options);
searchOptionsListener = this::searchOptionsChanged;
options.addOptionsChangeListener(searchOptionsListener);
}
private void registerBrowserOptionsListener(PluginTool tool) {
ToolOptions options = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
loadBrowserOptions(options);
browserOptionsListener = this::browserOptionsChanged;
options.addOptionsChangeListener(browserOptionsListener);
}
private void loadBrowserOptions(ToolOptions options) {
byteGroupSize = options.getInt(BytesFieldFactory.BYTE_GROUP_SIZE_MSG, 1);
byteDelimiter = options.getString(BytesFieldFactory.DELIMITER_MSG, " ");
}
private void searchOptionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
if (optionName.equals(OPTION_SEARCH_LIMIT)) {
int limit = (int) newValue;
if (limit <= 0) {
throw new OptionsVetoException("Search limit must be greater than 0");
}
}
loadSearchOptions(options);
}
private void loadSearchOptions(ToolOptions options) {
searchLimit = options.getInt(OPTION_SEARCH_LIMIT, DEFAULT_SEARCH_LIMIT);
highlightMatches = options.getBoolean(SEARCH_HIGHLIGHT_NAME, true);
autoRestrictSelection = options.getBoolean(AUTO_RESTRICT_SELECTION, true);
prepopulateSearch = options.getBoolean(PRE_POPULATE_MEM_SEARCH, true);
}
private void browserOptionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
loadBrowserOptions(options);
}
}

View File

@ -0,0 +1,317 @@
/* ###
* 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.features.base.memsearch.gui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ItemEvent;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import javax.swing.text.*;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox;
import ghidra.app.util.HelpTopics;
import ghidra.docking.util.LookAndFeelUtils;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.program.model.lang.Endian;
import ghidra.util.HelpLocation;
import ghidra.util.layout.PairLayout;
import ghidra.util.layout.VerticalLayout;
import help.Help;
import help.HelpService;
/**
* Internal panel of the memory search window that manages the controls for the search settings.
* This panel can be added or removed via a toolbar action. Not showing by default.
*/
class MemorySearchOptionsPanel extends JPanel {
private SearchGuiModel model;
private GCheckBox caseSensitiveCheckbox;
private GCheckBox escapeSequencesCheckbox;
private GCheckBox decimalUnsignedCheckbox;
private GComboBox<Integer> decimalByteSizeCombo;
private GComboBox<Charset> charsetCombo;
private GComboBox<String> endianessCombo;
private boolean isNimbus;
MemorySearchOptionsPanel(SearchGuiModel model) {
super(new BorderLayout());
this.model = model;
// if the look and feel is Nimbus, the spaceing it too big, so we use less spacing
// between elements.
isNimbus = LookAndFeelUtils.isUsingNimbusUI();
JPanel scrolledPanel = new JPanel(new VerticalLayout(isNimbus ? 8 : 16));
scrolledPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5));
scrolledPanel.add(buildByteOptionsPanel());
scrolledPanel.add(buildDecimalOptions());
scrolledPanel.add(buildStringOptions());
scrolledPanel.add(buildCodeUnitScopePanel());
scrolledPanel.add(buildMemorySearchRegionsPanel());
JScrollPane scroll = new JScrollPane(scrolledPanel);
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
add(scroll, BorderLayout.CENTER);
model.addChangeCallback(this::guiModelChanged);
HelpService helpService = Help.getHelpService();
helpService.registerHelp(this, new HelpLocation(HelpTopics.SEARCH, "Options"));
}
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
size.width += 20; // reserve space for the optional vertical scroll bar
return size;
}
private JComponent buildMemorySearchRegionsPanel() {
JPanel panel = new JPanel(new VerticalLayout(3));
panel.setBorder(createBorder("Search Region Filter"));
List<SearchRegion> choices = model.getMemoryRegionChoices();
for (SearchRegion region : choices) {
GCheckBox checkbox = new GCheckBox(region.getName());
checkbox.setToolTipText(region.getDescription());
checkbox.setSelected(model.isSelectedRegion(region));
checkbox.addItemListener(e -> model.selectRegion(region, checkbox.isSelected()));
panel.add(checkbox);
}
return panel;
}
private JComponent buildDecimalOptions() {
JPanel panel = new JPanel(new VerticalLayout(3));
panel.setBorder(createBorder("Decimal Options"));
JPanel innerPanel = new JPanel(new PairLayout(5, 5));
JLabel label = new JLabel("Size:");
label.setToolTipText("Size of decimal values in bytes");
innerPanel.add(label);
Integer[] decimalSizes = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 16 };
decimalByteSizeCombo = new GComboBox<>(decimalSizes);
decimalByteSizeCombo.setSelectedItem(4);
decimalByteSizeCombo.addItemListener(this::byteSizeComboChanged);
decimalByteSizeCombo.setToolTipText("Size of decimal values in bytes");
innerPanel.add(decimalByteSizeCombo);
panel.add(innerPanel);
decimalUnsignedCheckbox = new GCheckBox("Unsigned");
decimalUnsignedCheckbox.setToolTipText(
"Sets whether decimal values should be interpreted as unsigned values");
decimalUnsignedCheckbox.addActionListener(
e -> model.setDecimalUnsigned(decimalUnsignedCheckbox.isSelected()));
panel.add(decimalUnsignedCheckbox);
return panel;
}
private void byteSizeComboChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) {
return;
}
int byteSize = (Integer) e.getItem();
model.setDecimalByteSize(byteSize);
}
private JComponent buildCodeUnitScopePanel() {
JPanel panel = new JPanel(new VerticalLayout(5));
panel.setBorder(createBorder("Code Type Filter"));
GCheckBox instructionsCheckBox = new GCheckBox("Instructions");
GCheckBox definedDataCheckBox = new GCheckBox("Defined Data");
GCheckBox undefinedDataCheckBox = new GCheckBox("Undefined Data");
instructionsCheckBox.setToolTipText(
"If selected, include matches found in instructions");
definedDataCheckBox.setToolTipText(
"If selected, include matches found in defined data");
undefinedDataCheckBox.setToolTipText(
"If selected, include matches found in undefined data");
instructionsCheckBox.setSelected(model.includeInstructions());
definedDataCheckBox.setSelected(model.includeDefinedData());
undefinedDataCheckBox.setSelected(model.includeUndefinedData());
instructionsCheckBox.addActionListener(
e -> model.setIncludeInstructions(instructionsCheckBox.isSelected()));
definedDataCheckBox.addActionListener(
e -> model.setIncludeDefinedData(definedDataCheckBox.isSelected()));
undefinedDataCheckBox.addActionListener(
e -> model.setIncludeUndefinedData(undefinedDataCheckBox.isSelected()));
panel.add(instructionsCheckBox);
panel.add(definedDataCheckBox);
panel.add(undefinedDataCheckBox);
return panel;
}
private JComponent buildByteOptionsPanel() {
JPanel panel = new JPanel(new PairLayout(3, 2));
panel.setBorder(createBorder("Byte Options"));
String[] endianess = new String[] { "Big", "Little" };
endianessCombo = new GComboBox<>(endianess);
endianessCombo.setSelectedIndex(model.isBigEndian() ? 0 : 1);
endianessCombo.addItemListener(this::endianessComboChanged);
endianessCombo.setToolTipText("Selects the endianess");
JTextField alignField = new JTextField(5);
alignField.setDocument(new RestrictedInputDocument());
alignField.setName("Alignment");
alignField.setText(Integer.toString(model.getAlignment()));
alignField.setToolTipText(
"Filters out matches whose address is not divisible by the alignment value");
panel.add(new JLabel("Endianess:"));
panel.add(endianessCombo);
panel.add(new JLabel("Alignment:"));
panel.add(alignField);
return panel;
}
private void endianessComboChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) {
return;
}
String endianString = (String) e.getItem();
Endian endian = Endian.toEndian(endianString);
model.setBigEndian(endian.isBigEndian());
}
private JComponent buildStringOptions() {
JPanel panel = new JPanel(new VerticalLayout(3));
Charset[] supportedCharsets =
{ StandardCharsets.US_ASCII, StandardCharsets.UTF_8, StandardCharsets.UTF_16 };
charsetCombo = new GComboBox<>(supportedCharsets);
charsetCombo.setName("Encoding Options");
charsetCombo.setSelectedIndex(0);
charsetCombo.addItemListener(this::encodingComboChanged);
charsetCombo.setToolTipText("Character encoding for translating strings to bytes");
JPanel innerPanel = new JPanel(new PairLayout(5, 5));
JLabel label = new JLabel("Encoding:");
label.setToolTipText("Character encoding for translating strings to bytes");
innerPanel.add(label);
innerPanel.add(charsetCombo);
panel.add(innerPanel);
caseSensitiveCheckbox = new GCheckBox("Case Sensitive");
caseSensitiveCheckbox.setSelected(model.isCaseSensitive());
caseSensitiveCheckbox.setToolTipText("Allows for case sensitive searching.");
caseSensitiveCheckbox.addActionListener(
e -> model.setCaseSensitive(caseSensitiveCheckbox.isSelected()));
escapeSequencesCheckbox = new GCheckBox("Escape Sequences");
escapeSequencesCheckbox.setSelected(model.useEscapeSequences());
escapeSequencesCheckbox.setToolTipText(
"Allows specifying control characters using escape sequences " +
"(i.e., allows \\n to be searched for as a single line feed character).");
escapeSequencesCheckbox.addActionListener(
e -> model.setUseEscapeSequences(escapeSequencesCheckbox.isSelected()));
panel.setBorder(createBorder("String Options"));
panel.add(caseSensitiveCheckbox);
panel.add(escapeSequencesCheckbox);
return panel;
}
private void encodingComboChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) {
return;
}
Charset charSet = (Charset) e.getItem();
model.setStringCharset(charSet);
}
private void guiModelChanged(SearchSettings oldSettings) {
endianessCombo.setSelectedItem(model.isBigEndian() ? "Big" : "Little");
caseSensitiveCheckbox.setSelected(model.isCaseSensitive());
escapeSequencesCheckbox.setSelected(model.useEscapeSequences());
decimalByteSizeCombo.setSelectedItem(model.getDecimalByteSize());
decimalUnsignedCheckbox.setSelected(model.isDecimalUnsigned());
charsetCombo.setSelectedItem(model.getStringCharset());
}
private Border createBorder(String name) {
TitledBorder outerBorder = BorderFactory.createTitledBorder(name);
if (isNimbus) {
return outerBorder;
}
Border innerBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
return BorderFactory.createCompoundBorder(outerBorder, innerBorder);
}
/**
* Custom Document that validates user input on the fly.
*/
private class RestrictedInputDocument extends DefaultStyledDocument {
/**
* Called before new user input is inserted into the entry text field. The super
* method is called if the input is accepted.
*/
@Override
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
String currentText = getText(0, getLength());
String beforeOffset = currentText.substring(0, offs);
String afterOffset = currentText.substring(offs, currentText.length());
String proposedText = beforeOffset + str + afterOffset;
int alignment = getValue(proposedText);
if (alignment > 0) {
super.insertString(offs, str, a);
model.setAlignment(alignment);
}
}
@Override
public void remove(int offs, int len) throws BadLocationException {
String currentText = getText(0, getLength());
String beforeOffset = currentText.substring(0, offs);
String afterOffset = currentText.substring(len + offs, currentText.length());
String proposedResult = beforeOffset + afterOffset;
int alignment = getValue(proposedResult);
if (alignment > 0) {
super.remove(offs, len);
model.setAlignment(alignment);
}
}
private int getValue(String proposedText) {
if (proposedText.isBlank()) {
return 1;
}
try {
return Integer.parseInt(proposedText);
}
catch (NumberFormatException e) {
return -1;
}
}
}
}

View File

@ -0,0 +1,252 @@
/* ###
* 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.features.base.memsearch.gui;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.KeyStroke;
import docking.action.builder.ActionBuilder;
import ghidra.app.CorePluginPackage;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.*;
import ghidra.app.util.HelpTopics;
import ghidra.app.util.query.TableService;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.features.base.memsearch.searcher.MemorySearcher;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.util.*;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
/**
* Plugin for searching program memory.
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.SEARCH,
shortDescription = "Search bytes in memory",
description = "This plugin searches bytes in memory. The search " +
"is based on a value entered as hex or decimal numbers, or strings." +
" The value may contain \"wildcards\" or regular expressions" +
" that will match any byte or nibble.",
servicesRequired = { ProgramManager.class, GoToService.class, TableService.class, CodeViewerService.class },
servicesProvided = { MemorySearchService.class },
eventsConsumed = { ProgramSelectionPluginEvent.class }
)
//@formatter:on
public class MemorySearchPlugin extends Plugin implements MemorySearchService {
private static final int MAX_HISTORY = 10;
private static final String SHOW_OPTIONS_PANEL = "Show Options Panel";
private static final String SHOW_SCAN_PANEL = "Show Scan Panel";
private ByteMatcher lastByteMatcher;
private MemorySearchOptions options;
private SearchHistory searchHistory = new SearchHistory(MAX_HISTORY);
private Address lastSearchAddress;
private boolean showScanPanel;
private boolean showOptionsPanel;
public MemorySearchPlugin(PluginTool tool) {
super(tool);
createActions();
options = new MemorySearchOptions(tool);
}
private void createActions() {
new ActionBuilder("Memory Search", getName())
.menuPath("&Search", "&Memory...")
.menuGroup("search", "a")
.keyBinding("s")
.description("Search Memory for byte sequence")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Memory Search"))
.withContext(NavigatableActionContext.class, true)
.onAction(this::showSearchMemoryProvider)
.buildAndInstall(tool);
new ActionBuilder("Repeat Memory Search Forwards", getName())
.menuPath("&Search", "Repeat Search &Forwards")
.menuGroup("search", "b")
.keyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0))
.description("Repeat last memory search fowards once")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Repeat Search Forwards"))
.withContext(NavigatableActionContext.class, true)
.enabledWhen(c -> lastByteMatcher != null && c.getAddress() != null)
.onAction(c -> searchOnce(c, true))
.buildAndInstall(tool);
new ActionBuilder("Repeat Memory Search Backwards", getName())
.menuPath("&Search", "Repeat Search &Backwards")
.menuGroup("search", "c")
.keyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.SHIFT_DOWN_MASK))
.description("Repeat last memory search backwards once")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Repeat Search Backwards"))
.withContext(NavigatableActionContext.class, true)
.enabledWhen(c -> lastByteMatcher != null && c.getAddress() != null)
.onAction(c -> searchOnce(c, false))
.buildAndInstall(tool);
}
private void showSearchMemoryProvider(NavigatableActionContext c) {
SearchSettings settings = lastByteMatcher != null ? lastByteMatcher.getSettings() : null;
SearchHistory copy = new SearchHistory(searchHistory);
MemorySearchProvider provider =
new MemorySearchProvider(this, c.getNavigatable(), settings, options, copy);
provider.showOptions(showOptionsPanel);
provider.showScanPanel(showScanPanel);
}
private void searchOnce(NavigatableActionContext c, boolean forward) {
SearchOnceTask task = new SearchOnceTask(c.getNavigatable(), forward);
TaskLauncher.launch(task);
}
void updateByteMatcher(ByteMatcher matcher) {
lastByteMatcher = matcher;
searchHistory.addSearch(matcher);
}
@Override
public void readConfigState(SaveState saveState) {
showOptionsPanel = saveState.getBoolean(SHOW_OPTIONS_PANEL, false);
showScanPanel = saveState.getBoolean(SHOW_SCAN_PANEL, false);
}
@Override
public void writeConfigState(SaveState saveState) {
saveState.putBoolean(SHOW_OPTIONS_PANEL, showOptionsPanel);
saveState.putBoolean(SHOW_SCAN_PANEL, showOptionsPanel);
}
//==================================================================================================
// MemorySearchService methods
//==================================================================================================
@Override
public void createMemorySearchProvider(Navigatable navigatable, String input,
SearchSettings settings, boolean useSelection) {
SearchHistory copy = new SearchHistory(searchHistory);
MemorySearchProvider provider =
new MemorySearchProvider(this, navigatable, settings, options, copy);
provider.setSearchInput(input);
provider.setSearchSelectionOnly(false);
// Custom providers may use input and settings that are fairly unique and not chosen
// by the user directly. We therefore don't want those settings to be reported back for
// adding to the default settings state and history and thereby affecting future normal
// memory searches.
provider.setPrivate();
}
private class SearchOnceTask extends Task {
private Navigatable navigatable;
private boolean forward;
public SearchOnceTask(Navigatable navigatable, boolean forward) {
super("Search Next", true, true, true);
this.navigatable = navigatable;
this.forward = forward;
}
private AddressSet getSearchAddresses() {
SearchSettings settings = lastByteMatcher.getSettings();
AddressSet searchAddresses = settings.getSearchAddresses(navigatable.getProgram());
ProgramSelection selection = navigatable.getSelection();
if (selection != null && !selection.isEmpty()) {
searchAddresses = searchAddresses.intersect(navigatable.getSelection());
}
return searchAddresses;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
AddressableByteSource source = navigatable.getByteSource();
AddressSet addresses = getSearchAddresses();
if (addresses.isEmpty()) {
Msg.showWarn(this, null, "Search Failed!", "Addresses to search is empty!");
return;
}
Address start = getSearchStartAddress();
if (start == null) {
Msg.showWarn(this, null, "Search Failed!", "No valid start address!");
return;
}
MemorySearcher searcher = new MemorySearcher(source, lastByteMatcher, addresses, 1);
MemoryMatch match = searcher.findOnce(start, forward, monitor);
Swing.runLater(() -> navigateToMatch(match));
}
private Address getSearchStartAddress() {
ProgramLocation location = navigatable.getLocation();
if (location == null) {
return null;
}
Address start = navigatable.getLocation().getByteAddress();
if (lastSearchAddress != null) {
CodeUnit cu = navigatable.getProgram().getListing().getCodeUnitContaining(start);
if (cu != null && cu.contains(lastSearchAddress)) {
start = lastSearchAddress;
}
}
return forward ? start.next() : start.previous();
}
private void navigateToMatch(MemoryMatch match) {
if (match != null) {
lastSearchAddress = match.getAddress();
Program program = navigatable.getProgram();
navigatable.goTo(program, new BytesFieldLocation(program, match.getAddress()));
}
else {
Msg.showWarn(this, null, "Match Not Found",
"No match found going forward for " + lastByteMatcher.getInput());
}
}
}
public void setShowOptionsPanel(boolean show) {
showOptionsPanel = show;
}
public void setShowScanPanel(boolean show) {
showScanPanel = show;
}
}

View File

@ -0,0 +1,655 @@
/* ###
* 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.features.base.memsearch.gui;
import java.awt.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import javax.swing.*;
import docking.ActionContext;
import docking.DockingContextListener;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import generic.theme.GIcon;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.nav.Navigatable;
import ghidra.app.nav.NavigatableRemovalListener;
import ghidra.app.util.HelpTopics;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.features.base.memsearch.scan.Scanner;
import ghidra.features.base.memsearch.searcher.*;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.layout.VerticalLayout;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.SelectionNavigationAction;
import ghidra.util.table.actions.DeleteTableRowAction;
import ghidra.util.table.actions.MakeProgramSelectionAction;
import resources.Icons;
/**
* ComponentProvider used to search memory and display search results.
*/
public class MemorySearchProvider extends ComponentProviderAdapter
implements DockingContextListener, NavigatableRemovalListener, DomainObjectClosedListener {
// @formatter:off
private static final Icon SHOW_SEARCH_PANEL_ICON = new GIcon("icon.base.mem.search.panel.search");
private static final Icon SHOW_SCAN_PANEL_ICON = new GIcon("icon.base.mem.search.panel.scan");
private static final Icon SHOW_OPTIONS_ICON = new GIcon("icon.base.mem.search.panel.options");
// @formatter:on
private static Set<Integer> USED_IDS = new HashSet<>();
private final int id = getId();
private Navigatable navigatable;
private Program program;
private AddressableByteSource byteSource;
private JComponent mainComponent;
private JPanel controlPanel;
private MemorySearchControlPanel searchPanel;
private MemoryScanControlPanel scanPanel;
private MemorySearchOptionsPanel optionsPanel;
private MemorySearchResultsPanel resultsPanel;
private ToggleDockingAction toggleOptionsPanelAction;
private ToggleDockingAction toggleScanPanelAction;
private ToggleDockingAction toggleSearchPanelAction;
private DockingAction previousAction;
private DockingAction nextAction;
private DockingAction refreshAction;
private ByteMatcher byteMatcher;
private Address lastMatchingAddress;
private boolean isBusy;
private MemoryMatchHighlighter matchHighlighter;
private MemorySearchPlugin plugin;
private MemorySearchOptions options;
private SearchGuiModel model;
private boolean isPrivate = false;
public MemorySearchProvider(MemorySearchPlugin plugin, Navigatable navigatable,
SearchSettings settings, MemorySearchOptions options, SearchHistory history) {
super(plugin.getTool(), "Memory Search", plugin.getName());
this.plugin = plugin;
this.navigatable = navigatable;
this.options = options;
this.program = navigatable.getProgram();
this.byteSource = navigatable.getByteSource();
// always initially use the byte ordering of the program, regardless of previous searches
if (settings == null) {
settings = new SearchSettings();
}
settings = settings.withBigEndian(program.getMemory().isBigEndian());
this.model = new SearchGuiModel(settings, byteSource.getSearchableRegions());
model.setHasSelection(hasSelection(navigatable.getSelection()));
model.setAutoRestrictSelection(options.isAutoRestrictSelection());
setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Memory_Search"));
SearchMarkers markers = new SearchMarkers(tool, getTitle(), program);
searchPanel = new MemorySearchControlPanel(this, model, history);
scanPanel = new MemoryScanControlPanel(this);
optionsPanel = new MemorySearchOptionsPanel(model);
resultsPanel = new MemorySearchResultsPanel(this, markers);
mainComponent = buildMainComponent();
matchHighlighter =
new MemoryMatchHighlighter(navigatable, resultsPanel.getTableModel(), options);
setTransient();
addToTool();
setVisible(true);
createActions(plugin.getName());
tool.addContextListener(this);
navigatable.addNavigatableListener(this);
program.addCloseListener(this);
updateTitle();
}
public void setSearchInput(String input) {
searchPanel.setSearchInput(input);
}
public String getSearchInput() {
return byteMatcher == null ? "" : byteMatcher.getInput();
}
public void setSearchSelectionOnly(boolean b) {
model.setSearchSelectionOnly(b);
}
void setPrivate() {
this.isPrivate = true;
}
private void updateTitle() {
StringBuilder builder = new StringBuilder();
String searchInput = getSearchInput();
builder.append("Search Memory: ");
if (!searchInput.isBlank()) {
builder.append("\"");
builder.append(searchInput);
builder.append("\"");
}
builder.append(" (");
builder.append(getProgramName());
builder.append(")");
setTitle(builder.toString());
}
@Override
public JComponent getComponent() {
return mainComponent;
}
void setByteMatcher(ByteMatcher byteMatcher) {
this.byteMatcher = byteMatcher;
tool.contextChanged(this);
}
/*
* This method will disable "search" actions immediately upon initiating any search or
* scan action. Normally, these actions would enable and disable via context as usual, but
* context changes are issued in a delayed fashion.
*/
void disableActionsFast() {
nextAction.setEnabled(false);
previousAction.setEnabled(false);
refreshAction.setEnabled(false);
}
boolean canProcessResults() {
return !isBusy && resultsPanel.hasResults();
}
private void searchOnce(boolean forward) {
if (hasInvalidSearchSettings()) {
return;
}
updateTitle();
Address start = getSearchStartAddress(forward);
AddressSet addresses = getSearchAddresses();
MemorySearcher searcher = new MemorySearcher(byteSource, byteMatcher, addresses, 1);
searcher.setMatchFilter(createFilter());
setBusy(true);
resultsPanel.searchOnce(searcher, start, forward);
// Only update future memory search settings if this is a standard memory search provider
// because we don't want potentially highly specialized inputs and settings to be in
// the history for standard memory search operations.
if (!isPrivate) {
plugin.updateByteMatcher(byteMatcher);
}
}
// public so can be called by tests
public void search() {
if (hasInvalidSearchSettings()) {
return;
}
updateTitle();
int limit = options.getSearchLimit();
AddressSet addresses = getSearchAddresses();
MemorySearcher searcher = new MemorySearcher(byteSource, byteMatcher, addresses, limit);
searcher.setMatchFilter(createFilter());
setBusy(true);
searchPanel.setSearchStatus(resultsPanel.hasResults(), true);
resultsPanel.search(searcher, model.getMatchCombiner());
// Only update future memory search settings if this is a standard memory search provider
// because we don't want potentially highly specialized inputs and settings to be in
// the history for standard memory search operations.
if (!isPrivate) {
plugin.updateByteMatcher(byteMatcher);
}
}
private boolean hasInvalidSearchSettings() {
Set<SearchRegion> selectedMemoryRegions = model.getSelectedMemoryRegions();
if (selectedMemoryRegions.isEmpty()) {
Msg.showInfo(getClass(), resultsPanel, "No Memory Regions Selected!",
"You must select one or more memory regions to perform a search!");
return true;
}
if (!(model.includeInstructions() ||
model.includeDefinedData() ||
model.includeUndefinedData())) {
Msg.showInfo(getClass(), resultsPanel, "No Code Types Selected!",
"You must select at least one of \"Instructions\"," +
" \"Defined Data\" or \"Undefined Data\" to perform a search!");
return true;
}
return false;
}
/**
* Performs a scan on the current results, keeping only the results that match the type of scan.
* Note: this method is public to facilitate testing.
*
* @param scanner the scanner to use to reduce the results.
*/
public void scan(Scanner scanner) {
setBusy(true);
resultsPanel.refreshAndMaybeScanForChanges(byteSource, scanner);
}
private AddressSet getSearchAddresses() {
AddressSet set = model.getSettings().getSearchAddresses(program);
if (model.isSearchSelectionOnly()) {
set = set.intersect(navigatable.getSelection());
}
return set;
}
private void refreshResults() {
setBusy(true);
resultsPanel.refreshAndMaybeScanForChanges(byteSource, null);
}
private void setBusy(boolean isBusy) {
this.isBusy = isBusy;
boolean hasResults = resultsPanel.hasResults();
searchPanel.setSearchStatus(hasResults, isBusy);
scanPanel.setSearchStatus(hasResults, isBusy);
if (isBusy) {
disableActionsFast();
}
tool.contextChanged(this);
}
private Predicate<MemoryMatch> createFilter() {
AlignmentFilter alignmentFilter = new AlignmentFilter(model.getAlignment());
CodeUnitFilter codeUnitFilter =
new CodeUnitFilter(program, model.includeInstructions(),
model.includeDefinedData(), model.includeUndefinedData());
return alignmentFilter.and(codeUnitFilter);
}
private Address getSearchStartAddress(boolean forward) {
ProgramLocation location = navigatable.getLocation();
Address startAddress = location == null ? null : location.getByteAddress();
if (startAddress == null) {
startAddress = forward ? program.getMinAddress() : program.getMaxAddress();
}
/*
Finding the correct starting address is tricky. Ideally, we would just use the
current cursor location's address and begin searching. However, this doesn't work
for subsequent searches for two reasons.
The first reason is simply that subsequent searches need to start one address past the
current address or else you will just find the same location again.
The second reason is caused by the way the listing handles arrays. Since arrays don't
have a bytes field, a previous search may have found a hit inside an array, but because
there is no place in the listing to represent that, the cursor is actually placed at
address that is before (possibly several addresses before) the actual hit. So going
forward in the next search, even after incrementing the address, will result in finding
that same hit.
To solve this, the provider keeps track of a last match address. Subsequent searches
will use this address as long as that address and the cursor address are in the same
code unit. If they are not in the same code unit, we assume the user manually moved the
cursor and want to start searching from that new location.
*/
if (lastMatchingAddress == null) {
return startAddress;
}
CodeUnit cu = program.getListing().getCodeUnitContaining(startAddress);
if (cu.contains(lastMatchingAddress)) {
startAddress = forward ? lastMatchingAddress.next() : lastMatchingAddress.previous();
}
if (startAddress == null) {
startAddress = program.getMinAddress();
}
return startAddress;
}
void searchAllCompleted(boolean foundResults, boolean cancelled, boolean terminatedEarly) {
setBusy(false);
updateSubTitle();
if (!cancelled && terminatedEarly) {
Msg.showInfo(getClass(), resultsPanel, "Search Limit Exceeded!",
"Stopped search after finding " + options.getSearchLimit() + " matches.\n" +
"The search limit can be changed at Edit->Tool Options, under Search.");
}
else if (!foundResults) {
showAlert("No matches found!");
}
}
void searchOnceCompleted(MemoryMatch match, boolean cancelled) {
setBusy(false);
updateSubTitle();
if (match != null) {
lastMatchingAddress = match.getAddress();
navigatable.goTo(program, new BytesFieldLocation(program, match.getAddress()));
}
else {
showAlert("No Match Found!");
}
}
void refreshAndScanCompleted(MemoryMatch match) {
setBusy(false);
updateSubTitle();
if (match != null) {
lastMatchingAddress = match.getAddress();
navigatable.goTo(program, new BytesFieldLocation(program, match.getAddress()));
}
}
@Override
public void componentActivated() {
resultsPanel.providerActivated();
navigatable.setHighlightProvider(matchHighlighter, program);
}
private void updateSubTitle() {
StringBuilder builder = new StringBuilder();
builder.append(" ");
int matchCount = resultsPanel.getMatchCount();
if (matchCount > 0) {
builder.append("(");
builder.append(matchCount);
builder.append(matchCount == 1 ? " entry)" : " entries)");
}
setSubTitle(builder.toString());
}
private String getProgramName() {
return program.getDomainFile().getName();
}
private void updateControlPanel() {
controlPanel.removeAll();
boolean showSearchPanel = toggleSearchPanelAction.isSelected();
boolean showScanPanel = toggleScanPanelAction.isSelected();
if (showSearchPanel) {
controlPanel.add(searchPanel);
}
if (showSearchPanel && showScanPanel) {
controlPanel.add(new JSeparator());
}
if (showScanPanel) {
controlPanel.add(scanPanel);
}
controlPanel.revalidate();
}
private void toggleShowScanPanel() {
plugin.setShowScanPanel(toggleScanPanelAction.isSelected());
updateControlPanel();
}
private void toggleShowSearchPanel() {
updateControlPanel();
}
private void toggleShowOptions() {
plugin.setShowOptionsPanel(toggleOptionsPanelAction.isSelected());
if (toggleOptionsPanelAction.isSelected()) {
mainComponent.add(optionsPanel, BorderLayout.EAST);
}
else {
mainComponent.remove(optionsPanel);
}
mainComponent.validate();
}
private boolean canSearch() {
return !isBusy && byteMatcher != null && byteMatcher.isValidSearch();
}
private JComponent buildMainComponent() {
JPanel panel = new JPanel(new BorderLayout());
panel.setPreferredSize(new Dimension(900, 650));
panel.add(buildCenterPanel(), BorderLayout.CENTER);
return panel;
}
private JComponent buildCenterPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.add(buildControlPanel(), BorderLayout.NORTH);
panel.add(resultsPanel, BorderLayout.CENTER);
return panel;
}
private JComponent buildControlPanel() {
controlPanel = new JPanel(new VerticalLayout(0));
controlPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
controlPanel.add(searchPanel);
return controlPanel;
}
private void createActions(String owner) {
nextAction = new ActionBuilder("Search Next", owner)
.toolBarIcon(Icons.DOWN_ICON)
.toolBarGroup("A")
.description("Search forward for 1 result")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search_Next"))
.enabledWhen(c -> canSearch())
.onAction(c -> searchOnce(true))
.buildAndInstallLocal(this);
previousAction = new ActionBuilder("Search Previous", owner)
.toolBarIcon(Icons.UP_ICON)
.toolBarGroup("A")
.description("Search backward for 1 result")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search_Previous"))
.enabledWhen(c -> canSearch())
.onAction(c -> searchOnce(false))
.buildAndInstallLocal(this);
refreshAction = new ActionBuilder("Refresh Results", owner)
.toolBarIcon(Icons.REFRESH_ICON)
.toolBarGroup("A")
.description(
"Reload bytes from memory for each search result and show changes in red")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Refresh_Values"))
.enabledWhen(c -> canProcessResults())
.onAction(c -> refreshResults())
.buildAndInstallLocal(this);
toggleSearchPanelAction = new ToggleActionBuilder("Show Memory Search Controls", owner)
.toolBarIcon(SHOW_SEARCH_PANEL_ICON)
.toolBarGroup("Z")
.description("Toggles showing the search controls")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Toggle_Search"))
.selected(true)
.onAction(c -> toggleShowSearchPanel())
.buildAndInstallLocal(this);
toggleScanPanelAction = new ToggleActionBuilder("Show Memory Scan Controls", owner)
.toolBarIcon(SHOW_SCAN_PANEL_ICON)
.toolBarGroup("Z")
.description("Toggles showing the scan controls")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Toggle_Scan"))
.onAction(c -> toggleShowScanPanel())
.buildAndInstallLocal(this);
toggleOptionsPanelAction = new ToggleActionBuilder("Show Options", owner)
.toolBarIcon(SHOW_OPTIONS_ICON)
.toolBarGroup("Z")
.description("Toggles showing the search options panel")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Toggle_Options"))
.onAction(c -> toggleShowOptions())
.buildAndInstallLocal(this);
// add standard table actions
GhidraTable table = resultsPanel.getTable();
addLocalAction(new MakeProgramSelectionAction(navigatable, owner, table));
addLocalAction(new SelectionNavigationAction(owner, table));
addLocalAction(new DeleteTableRowAction(table, owner) {
@Override
public void actionPerformed(ActionContext context) {
super.actionPerformed(context);
updateSubTitle();
}
});
}
@Override
public void removeFromTool() {
dispose();
super.removeFromTool();
}
private void dispose() {
matchHighlighter.dispose();
USED_IDS.remove(id);
if (navigatable != null) {
navigatable.removeNavigatableListener(this);
}
resultsPanel.dispose();
tool.removeContextListener(this);
program.removeCloseListener(this);
}
@Override
public void contextChanged(ActionContext context) {
model.setHasSelection(hasSelection(navigatable.getSelection()));
}
private boolean hasSelection(ProgramSelection selection) {
if (selection == null) {
return false;
}
return !selection.isEmpty();
}
@Override
public void navigatableRemoved(Navigatable nav) {
closeComponent();
}
@Override
public void domainObjectClosed(DomainObject dobj) {
closeComponent();
}
Navigatable getNavigatable() {
return navigatable;
}
private static int getId() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
if (!USED_IDS.contains(i)) {
USED_IDS.add(i);
return i;
}
}
return 0;
}
void tableSelectionChanged() {
MemoryMatch selectedMatch = resultsPanel.getSelectedMatch();
matchHighlighter.setSelectedMatch(selectedMatch);
if (selectedMatch != null) {
lastMatchingAddress = selectedMatch.getAddress();
}
tool.contextChanged(this);
}
public void showOptions(boolean b) {
toggleOptionsPanelAction.setSelected(b);
toggleShowOptions();
}
public void showScanPanel(boolean b) {
toggleScanPanelAction.setSelected(b);
updateControlPanel();
}
public void showSearchPanel(boolean b) {
toggleSearchPanelAction.setSelected(b);
updateControlPanel();
}
// testing
public boolean isBusy() {
return isBusy;
}
public List<MemoryMatch> getSearchResults() {
return resultsPanel.getTableModel().getModelData();
}
public void setSettings(SearchSettings settings) {
String converted = searchPanel.convertInput(model.getSettings(), settings);
model.setSettings(settings);
searchPanel.setSearchInput(converted);
}
public boolean isSearchSelection() {
return model.isSearchSelectionOnly();
}
public String getByteString() {
return byteMatcher.getDescription();
}
void showAlert(String alertMessage) {
// replace with water mark concept
Toolkit.getDefaultToolkit().beep();
Msg.showInfo(this, null, "Search Results", alertMessage);
}
@Override
protected ActionContext createContext(Component sourceComponent, Object contextObject) {
ActionContext context = new NavigatableActionContext(this, navigatable);
context.setContextObject(contextObject);
context.setSourceComponent(sourceComponent);
return context;
}
}

View File

@ -0,0 +1,294 @@
/* ###
* 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.features.base.memsearch.gui;
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableModelEvent;
import ghidra.app.nav.Navigatable;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.combiner.Combiner;
import ghidra.features.base.memsearch.scan.Scanner;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.features.base.memsearch.searcher.MemorySearcher;
import ghidra.program.model.address.Address;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.*;
import ghidra.util.task.*;
/**
* Internal panel of the memory search window that manages the display of the search results
* in a table. This panel also includes most of the search logic as it has direct access to the
* table for showing the results.
*/
class MemorySearchResultsPanel extends JPanel {
private GhidraThreadedTablePanel<MemoryMatch> threadedTablePanel;
private GhidraTableFilterPanel<MemoryMatch> tableFilterPanel;
private GhidraTable table;
private MemoryMatchTableModel tableModel;
private MemorySearchProvider provider;
private SearchMarkers markers;
MemorySearchResultsPanel(MemorySearchProvider provider, SearchMarkers markers) {
super(new BorderLayout());
this.provider = provider;
this.markers = markers;
Navigatable navigatable = provider.getNavigatable();
tableModel = new MemoryMatchTableModel(provider.getTool(), navigatable.getProgram());
threadedTablePanel = new GhidraThreadedTablePanel<>(tableModel);
table = threadedTablePanel.getTable();
table.setActionsEnabled(true);
table.installNavigation(provider.getTool(), navigatable);
tableModel.addTableModelListener(this::tableChanged);
add(threadedTablePanel, BorderLayout.CENTER);
add(createFilterFieldPanel(), BorderLayout.SOUTH);
ListSelectionModel selectionModel = threadedTablePanel.getTable().getSelectionModel();
selectionModel.addListSelectionListener(this::selectionChanged);
}
private void tableChanged(TableModelEvent event) {
markers.loadMarkers(provider.getTitle(), tableModel.getModelData());
}
void providerActivated() {
markers.makeActiveMarkerSet();
}
private void selectionChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
provider.tableSelectionChanged();
}
private JComponent createFilterFieldPanel() {
tableFilterPanel = new GhidraTableFilterPanel<>(table, tableModel);
tableFilterPanel.setToolTipText("Filter search results");
return tableFilterPanel;
}
public void search(MemorySearcher searcher, Combiner combiner) {
MemoryMatchTableLoader loader = createLoader(searcher, combiner);
tableModel.addInitialLoadListener(
cancelled -> provider.searchAllCompleted(loader.hasResults(), cancelled,
loader.didTerminateEarly()));
tableModel.setLoader(loader);
}
public void searchOnce(MemorySearcher searcher, Address address, boolean forward) {
SearchOnceTask task = new SearchOnceTask(forward, searcher, address);
TaskLauncher.launch(task);
}
public void refreshAndMaybeScanForChanges(AddressableByteSource byteSource, Scanner scanner) {
RefreshAndScanTask task = new RefreshAndScanTask(byteSource, scanner);
TaskLauncher.launch(task);
}
private MemoryMatchTableLoader createLoader(MemorySearcher searcher, Combiner combiner) {
if (hasResults()) {
// If we have existing results, the combiner determines how the new search results get
// combined with the existing results.
//
// However, if the combiner is the "Replace" combiner, the results are not combined
// and only the new results are kept. In this case, it is preferred to use the same
// loader as if doing an initial search because you get incremental loading and also
// don't need to copy the existing results to feed to a combiner.
if (combiner != Combiner.REPLACE) {
List<MemoryMatch> previousResults = tableModel.getModelData();
return new CombinedMatchTableLoader(searcher, previousResults, combiner);
}
}
return new NewSearchTableLoader(searcher);
}
public boolean hasResults() {
return tableModel.getRowCount() > 0;
}
public void clearResults() {
tableModel.addInitialLoadListener(b -> provider.searchAllCompleted(true, false, false));
tableModel.setLoader(new EmptyMemoryMatchTableLoader());
}
public int getMatchCount() {
return tableModel.getRowCount();
}
void select(MemoryMatch match) {
int rowIndex = tableModel.getRowIndex(match);
if (rowIndex >= 0) {
threadedTablePanel.getTable().selectRow(rowIndex);
}
}
GhidraTable getTable() {
return table;
}
public MemoryMatch getSelectedMatch() {
int row = table.getSelectedRow();
return row < 0 ? null : tableModel.getRowObject(row);
}
public void dispose() {
markers.dispose();
tableFilterPanel.dispose();
}
MemoryMatchTableModel getTableModel() {
return tableModel;
}
private class SearchOnceTask extends Task {
private boolean forward;
private MemorySearcher searcher;
private Address start;
public SearchOnceTask(boolean forward, MemorySearcher searcher, Address start) {
super(forward ? "Search Next" : "Search Previous", true, true, true);
this.forward = forward;
this.searcher = searcher;
this.start = start;
}
private void tableLoadComplete(MemoryMatch match, boolean wasCancelled) {
int rowIndex = tableModel.getRowIndex(match);
if (rowIndex >= 0) {
table.selectRow(rowIndex);
table.scrollToSelectedRow();
provider.searchOnceCompleted(match, wasCancelled);
}
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
MemoryMatch match = searcher.findOnce(start, forward, monitor);
if (match != null) {
tableModel.addInitialLoadListener(b -> tableLoadComplete(match, b));
tableModel.addObject(match);
return;
}
}
catch (Throwable t) {
// Catch any runtime errors so that we exit task gracefully and don't leave
// the provider in a stuck "busy" state.
Msg.showError(this, null, "Unexpected error refreshing bytes", t);
}
Swing.runLater(() -> provider.searchOnceCompleted(null, monitor.isCancelled()));
}
}
private class RefreshAndScanTask extends Task {
private AddressableByteSource byteSource;
private Scanner scanner;
public RefreshAndScanTask(AddressableByteSource byteSource, Scanner scanner) {
super("Refreshing", true, true, true);
this.byteSource = byteSource;
this.scanner = scanner;
}
private void tableLoadComplete(MemoryMatch match) {
if (match == null) {
provider.refreshAndScanCompleted(null);
}
int rowIndex = tableModel.getRowIndex(match);
if (rowIndex >= 0) {
table.selectRow(rowIndex);
table.scrollToSelectedRow();
}
provider.refreshAndScanCompleted(match);
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
List<MemoryMatch> matches = tableModel.getModelData();
if (refreshByteValues(monitor, matches) && scanner != null) {
performScanFiltering(monitor, matches);
}
else {
tableModel.fireTableDataChanged(); // some data bytes may have changed, repaint
provider.refreshAndScanCompleted(null);
}
}
private boolean refreshByteValues(TaskMonitor monitor, List<MemoryMatch> matches) {
try {
byteSource.invalidate(); // clear any caches before refreshing byte values
monitor.initialize(matches.size(), "Refreshing...");
for (MemoryMatch match : matches) {
byte[] bytes = new byte[match.getLength()];
byteSource.getBytes(match.getAddress(), bytes, bytes.length);
match.updateBytes(bytes);
monitor.incrementProgress();
if (monitor.isCancelled()) {
return false;
}
}
return true;
}
catch (Throwable t) {
// Catch any runtime errors so that we exit task gracefully and don't leave
// the provider in a stuck "busy" state.
Msg.showError(this, null, "Unexpected error refreshing bytes", t);
}
return false;
}
private void performScanFiltering(TaskMonitor monitor, List<MemoryMatch> matches) {
monitor.initialize(matches.size(), "Scanning for changes...");
List<MemoryMatch> scanResults = new ArrayList<>();
for (MemoryMatch match : matches) {
if (scanner.accept(match)) {
scanResults.add(match);
}
if (monitor.isCancelled()) {
break;
}
}
MemoryMatch firstIfReduced = getFirstMatchIfReduced(matches, scanResults);
tableModel.addInitialLoadListener(b -> tableLoadComplete(firstIfReduced));
tableModel.setLoader(new RefreshResultsTableLoader(scanResults));
}
private MemoryMatch getFirstMatchIfReduced(List<MemoryMatch> matches,
List<MemoryMatch> scanResults) {
MemoryMatch firstIfReduced = null;
if (!scanResults.isEmpty() && scanResults.size() != matches.size()) {
firstIfReduced = scanResults.isEmpty() ? null : scanResults.getFirst();
}
return firstIfReduced;
}
}
}

View File

@ -0,0 +1,67 @@
/* ###
* 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.features.base.memsearch.gui;
import java.util.Iterator;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.features.base.memsearch.searcher.MemorySearcher;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.task.TaskMonitor;
/**
* Table loader that performs a search and displays the results in the table.
*/
public class NewSearchTableLoader implements MemoryMatchTableLoader {
private MemorySearcher memSearcher;
private boolean completedSearch;
private MemoryMatch firstMatch;
NewSearchTableLoader(MemorySearcher memSearcher) {
this.memSearcher = memSearcher;
}
@Override
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
completedSearch = memSearcher.findAll(accumulator, monitor);
Iterator<MemoryMatch> iterator = accumulator.iterator();
if (iterator.hasNext()) {
firstMatch = iterator.next();
}
}
@Override
public boolean didTerminateEarly() {
return !completedSearch;
}
@Override
public void dispose() {
// nothing to do
}
@Override
public MemoryMatch getFirstMatch() {
return firstMatch;
}
@Override
public boolean hasResults() {
return firstMatch != null;
}
}

View File

@ -0,0 +1,63 @@
/* ###
* 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.features.base.memsearch.gui;
import java.util.List;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.task.TaskMonitor;
/**
* Table loader that reloads the table with existing results after refreshing the byte values in
* those results.
*/
public class RefreshResultsTableLoader implements MemoryMatchTableLoader {
private List<MemoryMatch> matches;
private boolean hasResults;
public RefreshResultsTableLoader(List<MemoryMatch> matches) {
this.matches = matches;
}
@Override
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
accumulator.addAll(matches);
hasResults = !accumulator.isEmpty();
}
@Override
public void dispose() {
matches.clear();
matches = null;
}
@Override
public boolean didTerminateEarly() {
return false;
}
@Override
public MemoryMatch getFirstMatch() {
return null;
}
@Override
public boolean hasResults() {
return hasResults;
}
}

View File

@ -0,0 +1,287 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.features.base.memsearch.gui;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Consumer;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.features.base.memsearch.combiner.Combiner;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
/**
* Maintains the state of all the settings and controls for the memory search window.
*/
public class SearchGuiModel {
private SearchSettings settings;
private List<Consumer<SearchSettings>> changeCallbacks = new ArrayList<>();
private boolean hasSelection;
private List<SearchRegion> regionChoices;
private boolean autoResrictSelection = false;
private boolean isSearchSelectionOnly;
private Combiner combiner;
public SearchGuiModel(SearchSettings settings, List<SearchRegion> regionChoices) {
this.regionChoices = regionChoices;
// make a copy of settings so they don't change out from under us
this.settings = settings != null ? settings : new SearchSettings();
if (!isValidRegionSettings() || this.settings.getSelectedMemoryRegions().isEmpty()) {
installDefaultRegions();
}
}
private void installDefaultRegions() {
Set<SearchRegion> defaultRegions = new HashSet<>();
for (SearchRegion region : regionChoices) {
if (region.isDefault()) {
defaultRegions.add(region);
}
}
settings = settings.withSelectedRegions(defaultRegions);
}
private boolean isValidRegionSettings() {
for (SearchRegion region : settings.getSelectedMemoryRegions()) {
if (!regionChoices.contains(region)) {
return false;
}
}
return true;
}
public void setAutoRestrictSelection() {
autoResrictSelection = true;
}
public void addChangeCallback(Consumer<SearchSettings> changeCallback) {
changeCallbacks.add(changeCallback);
}
private void notifySettingsChanged(SearchSettings oldSettings) {
for (Consumer<SearchSettings> callback : changeCallbacks) {
callback.accept(oldSettings);
}
}
public boolean isSearchSelectionOnly() {
return isSearchSelectionOnly;
}
public boolean hasSelection() {
return hasSelection;
}
public void setHasSelection(boolean b) {
if (hasSelection == b) {
return;
}
hasSelection = b;
if (b) {
if (autoResrictSelection) {
// autoRestrictSelection means to auto turn on restrict search to selection when
// a selection happens
isSearchSelectionOnly = true;
}
}
else {
// if no selection, then we can't search in a selection!
isSearchSelectionOnly = false;
}
notifySettingsChanged(settings);
}
public void setSearchSelectionOnly(boolean b) {
if (isSearchSelectionOnly == b) {
return;
}
// can only set it if there is a current selection
isSearchSelectionOnly = b && hasSelection;
notifySettingsChanged(settings);
}
public SearchFormat getSearchFormat() {
return settings.getSearchFormat();
}
public SearchSettings getSettings() {
return settings;
}
public void setSearchFormat(SearchFormat searchFormat) {
if (searchFormat == settings.getSearchFormat()) {
return;
}
SearchSettings oldSettings = settings;
settings = settings.withSearchFormat(searchFormat);
notifySettingsChanged(oldSettings);
}
public ByteMatcher parse(String proposedText) {
return settings.getSearchFormat().parse(proposedText, settings);
}
public int getAlignment() {
return settings.getAlignment();
}
public void setAlignment(int alignment) {
settings = settings.withAlignment(alignment);
// this setting doesn't affect any other gui state, so no need to notify change
}
public Set<SearchRegion> getSelectedMemoryRegions() {
return settings.getSelectedMemoryRegions();
}
public boolean includeInstructions() {
return settings.includeInstructions();
}
public boolean includeDefinedData() {
return settings.includeDefinedData();
}
public boolean includeUndefinedData() {
return settings.includeUndefinedData();
}
public void setIncludeInstructions(boolean selected) {
settings = settings.withIncludeInstructions(selected);
}
public void setIncludeDefinedData(boolean selected) {
settings = settings.withIncludeDefinedData(selected);
}
public void setIncludeUndefinedData(boolean selected) {
settings = settings.withIncludeUndefinedData(selected);
}
public boolean isBigEndian() {
return settings.isBigEndian();
}
public void setBigEndian(boolean b) {
if (settings.isBigEndian() == b) {
return;
}
SearchSettings oldSettings = settings;
settings = settings.withBigEndian(b);
notifySettingsChanged(oldSettings);
}
public boolean isCaseSensitive() {
return settings.isCaseSensitive();
}
public void setCaseSensitive(boolean selected) {
if (settings.isCaseSensitive() == selected) {
return;
}
SearchSettings oldSettings = settings;
settings = settings.withCaseSensitive(selected);
notifySettingsChanged(oldSettings);
}
public boolean useEscapeSequences() {
return settings.useEscapeSequences();
}
public void setUseEscapeSequences(boolean selected) {
if (settings.useEscapeSequences() == selected) {
return;
}
SearchSettings oldSettings = settings;
settings = settings.withUseEscapeSequence(selected);
notifySettingsChanged(oldSettings);
}
public void setDecimalUnsigned(boolean selected) {
if (settings.isDecimalUnsigned() == selected) {
return;
}
SearchSettings oldSettings = settings;
settings = settings.withDecimalUnsigned(selected);
notifySettingsChanged(oldSettings);
}
public boolean isDecimalUnsigned() {
return settings.isDecimalUnsigned();
}
public void setDecimalByteSize(int byteSize) {
if (settings.getDecimalByteSize() == byteSize) {
return;
}
SearchSettings oldSettings = settings;
settings = settings.withDecimalByteSize(byteSize);
notifySettingsChanged(oldSettings);
}
public int getDecimalByteSize() {
return settings.getDecimalByteSize();
}
public void setStringCharset(Charset charset) {
if (settings.getStringCharset() == charset) {
return;
}
SearchSettings oldSettings = settings;
settings = settings.withStringCharset(charset);
notifySettingsChanged(oldSettings);
}
public Charset getStringCharset() {
return settings.getStringCharset();
}
public List<SearchRegion> getMemoryRegionChoices() {
return regionChoices;
}
public void setMatchCombiner(Combiner combiner) {
this.combiner = combiner;
}
public Combiner getMatchCombiner() {
return combiner;
}
public void setAutoRestrictSelection(boolean autoRestrictSelection) {
this.autoResrictSelection = autoRestrictSelection;
}
public void selectRegion(SearchRegion region, boolean selected) {
settings.withSelectedRegion(region, selected);
}
public boolean isSelectedRegion(SearchRegion region) {
return settings.isSelectedRegion(region);
}
public void setSettings(SearchSettings newSettings) {
SearchSettings oldSettings = settings;
settings = newSettings;
if (!isValidRegionSettings() || this.settings.getSelectedMemoryRegions().isEmpty()) {
installDefaultRegions();
}
notifySettingsChanged(oldSettings);
}
}

View File

@ -0,0 +1,79 @@
/* ###
* 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.features.base.memsearch.gui;
import java.util.*;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
/**
* Class for managing memory search history. It maintains a list of previously used ByteMatchers to
* do memory searching. Each ByteMatcher records the input search text and the search settings used
* to create it.
*/
public class SearchHistory {
private List<ByteMatcher> history = new LinkedList<>();
private int maxHistory;
public SearchHistory(int maxHistory) {
this.maxHistory = maxHistory;
}
public SearchHistory(SearchHistory other) {
this.history = new LinkedList<>(other.history);
this.maxHistory = other.maxHistory;
}
public void addSearch(ByteMatcher matcher) {
removeSimilarMatchers(matcher);
history.addFirst(matcher);
truncateHistoryAsNeeded();
}
private void removeSimilarMatchers(ByteMatcher matcher) {
Iterator<ByteMatcher> it = history.iterator();
String newInput = matcher.getInput();
SearchFormat newFormat = matcher.getSettings().getSearchFormat();
while (it.hasNext()) {
ByteMatcher historyMatch = it.next();
SearchFormat historyFormat = historyMatch.getSettings().getSearchFormat();
String historyInput = historyMatch.getInput();
if (historyFormat.equals(newFormat) && historyInput.equals(newInput)) {
it.remove();
}
}
}
private void truncateHistoryAsNeeded() {
int historySize = history.size();
if (historySize > maxHistory) {
int numToRemove = historySize - maxHistory;
for (int i = 0; i < numToRemove; i++) {
history.remove(history.size() - 1);
}
}
}
public ByteMatcher[] getHistoryAsArray() {
ByteMatcher[] historyArray = new ByteMatcher[history.size()];
history.toArray(historyArray);
return historyArray;
}
}

View File

@ -0,0 +1,113 @@
/* ###
* 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.features.base.memsearch.gui;
import java.util.List;
import javax.swing.Icon;
import generic.theme.GIcon;
import ghidra.app.services.*;
import ghidra.app.util.SearchConstants;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.program.util.*;
/**
* Manages the {@link MarkerSet} for a given {@link MemorySearchProvider} window.
*/
public class SearchMarkers {
private static final Icon SEARCH_MARKER_ICON = new GIcon("icon.base.search.marker");
private MarkerService service;
private MarkerSet markerSet;
private Program program;
public SearchMarkers(PluginTool tool, String title, Program program) {
this.program = program;
service = tool.getService(MarkerService.class);
if (service == null) {
return;
}
}
private MarkerSet createMarkerSet(String title) {
MarkerSet markers = service.createPointMarker(title, "Search", program,
MarkerService.SEARCH_PRIORITY, true, true, false,
SearchConstants.SEARCH_HIGHLIGHT_COLOR, SEARCH_MARKER_ICON);
markers.setMarkerDescriptor(new MarkerDescriptor() {
@Override
public ProgramLocation getProgramLocation(MarkerLocation loc) {
return new BytesFieldLocation(program, loc.getAddr());
}
});
// remove it; we will add it later to a group
service.removeMarker(markers, program);
return markers;
}
void makeActiveMarkerSet() {
if (service == null || markerSet == null) {
return;
}
service.setMarkerForGroup(MarkerService.HIGHLIGHT_GROUP, markerSet, program);
}
void loadMarkers(String title, List<MemoryMatch> matches) {
if (service == null) {
return;
}
if (matches.isEmpty()) {
deleteMarkerSet();
return;
}
// If the title of the provider changes, we need to re-create the marker set as the
// provider's title is what is used as the marker set's name. The name is what shows up in
// the marker set gui for turning markers on and off - if they don't match the provider's
// title, it isn't obvious what provider the markers represent. (And currently, there is
// no way to change a marker set's name once it is created.)
if (markerSet != null && !markerSet.getName().equals(title)) {
deleteMarkerSet();
}
if (markerSet == null) {
markerSet = createMarkerSet(title);
}
markerSet.clearAll();
for (MemoryMatch match : matches) {
markerSet.add(match.getAddress());
}
service.setMarkerForGroup(MarkerService.HIGHLIGHT_GROUP, markerSet, program);
}
private void deleteMarkerSet() {
if (markerSet != null) {
markerSet.clearAll();
service.removeMarker(markerSet, program);
markerSet = null;
}
}
public void dispose() {
deleteMarkerSet();
program = null;
}
}

View File

@ -0,0 +1,277 @@
/* ###
* 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.features.base.memsearch.gui;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Program;
/**
* Immutable container for all the relevant search settings.
*/
public class SearchSettings {
private final SearchFormat searchFormat;
private final Set<SearchRegion> selectedRegions;
private final int alignment;
private final boolean bigEndian;
private final boolean caseSensitive;
private final boolean useEscapeSequences;
private final boolean includeInstructions;
private final boolean includeDefinedData;
private final boolean includeUndefinedData;
private final boolean isDecimalUnsigned;
private final int decimalByteSize;
private final Charset charset;
public SearchSettings() {
this(SearchFormat.HEX, false, false, false, true, true, true, false, 4, 1, new HashSet<>(),
StandardCharsets.US_ASCII);
}
//@formatter:off
private SearchSettings(
SearchFormat format,
boolean bigEndian,
boolean caseSensitive,
boolean useEscapeSequences,
boolean includeInstructions,
boolean includeDefinedData,
boolean includeUndefinedData,
boolean isDecimalUnsigned,
int decimalByteSize,
int alignment,
Set<SearchRegion> selectedRegions,
Charset charset) {
this.searchFormat = format;
this.bigEndian = bigEndian;
this.caseSensitive = caseSensitive;
this.useEscapeSequences = useEscapeSequences;
this.includeInstructions = includeInstructions;
this.includeDefinedData = includeDefinedData;
this.includeUndefinedData = includeUndefinedData;
this.alignment = alignment;
this.decimalByteSize = decimalByteSize;
this.isDecimalUnsigned = isDecimalUnsigned;
this.selectedRegions = Collections.unmodifiableSet(new HashSet<>(selectedRegions));
this.charset = charset;
}
//@formatter:on
/**
* Returns the {@link SearchFormat} to be used to parse the input text.
* @return the search format to be used to parse the input text
*/
public SearchFormat getSearchFormat() {
return searchFormat;
}
/**
* Creates a copy of this settings object, but using the given search format.
* @param format the new search format
* @return a new search settings that is the same as this settings except for the format
*/
public SearchSettings withSearchFormat(SearchFormat format) {
if (this.searchFormat == format) {
return this;
}
return new SearchSettings(format, bigEndian, caseSensitive,
useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset);
}
public boolean isBigEndian() {
return bigEndian;
}
public SearchSettings withBigEndian(boolean isBigEndian) {
if (this.bigEndian == isBigEndian) {
return this;
}
return new SearchSettings(searchFormat, isBigEndian, caseSensitive,
useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset);
}
public SearchSettings withStringCharset(Charset stringCharset) {
if (this.charset == stringCharset) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, stringCharset);
}
public Charset getStringCharset() {
return charset;
}
public boolean useEscapeSequences() {
return useEscapeSequences;
}
public SearchSettings withUseEscapeSequence(boolean b) {
if (this.useEscapeSequences == b) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
b, includeInstructions, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset);
}
public boolean isCaseSensitive() {
return caseSensitive;
}
public SearchSettings withCaseSensitive(boolean b) {
if (this.caseSensitive == b) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, b,
useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset);
}
public boolean isDecimalUnsigned() {
return isDecimalUnsigned;
}
public SearchSettings withDecimalUnsigned(boolean b) {
if (this.isDecimalUnsigned == b) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData,
b, decimalByteSize, alignment, selectedRegions, charset);
}
public int getDecimalByteSize() {
return decimalByteSize;
}
public SearchSettings withDecimalByteSize(int byteSize) {
if (this.decimalByteSize == byteSize) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, byteSize, alignment, selectedRegions, charset);
}
public boolean includeInstructions() {
return includeInstructions;
}
public SearchSettings withIncludeInstructions(boolean b) {
if (this.includeInstructions == b) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
useEscapeSequences, b, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset);
}
public boolean includeDefinedData() {
return includeDefinedData;
}
public SearchSettings withIncludeDefinedData(boolean b) {
if (this.includeDefinedData == b) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
useEscapeSequences, includeInstructions, b, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset);
}
public boolean includeUndefinedData() {
return includeUndefinedData;
}
public SearchSettings withIncludeUndefinedData(boolean b) {
if (this.includeUndefinedData == b) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
useEscapeSequences, includeInstructions, includeDefinedData, b,
isDecimalUnsigned, decimalByteSize, alignment, selectedRegions, charset);
}
public int getAlignment() {
return alignment;
}
public SearchSettings withAlignment(int newAlignment) {
if (this.alignment == newAlignment) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, newAlignment, selectedRegions, charset);
}
public Set<SearchRegion> getSelectedMemoryRegions() {
return selectedRegions;
}
public SearchSettings withSelectedRegions(Set<SearchRegion> regions) {
if (this.selectedRegions.equals(regions)) {
return this;
}
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, alignment, regions, charset);
}
public boolean isSelectedRegion(SearchRegion region) {
return selectedRegions.contains(region);
}
public SearchSettings withSelectedRegion(SearchRegion region, boolean select) {
return new SearchSettings(searchFormat, bigEndian, caseSensitive,
useEscapeSequences, includeInstructions, includeDefinedData, includeUndefinedData,
isDecimalUnsigned, decimalByteSize, alignment,
createRegionSet(selectedRegions, region, select), charset);
}
public AddressSet getSearchAddresses(Program program) {
AddressSet set = new AddressSet();
for (SearchRegion memoryRegion : selectedRegions) {
set.add(memoryRegion.getAddresses(program));
}
return set;
}
private static Set<SearchRegion> createRegionSet(Set<SearchRegion> regions,
SearchRegion region, boolean select) {
Set<SearchRegion> set = new HashSet<>(regions);
if (select) {
set.add(region);
}
else {
set.remove(region);
}
return set;
}
}

View File

@ -0,0 +1,126 @@
/* ###
* 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.features.base.memsearch.matcher;
import java.util.Objects;
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
import ghidra.features.base.memsearch.gui.SearchSettings;
/**
* ByteMatcher is the base class for an object that be used to scan bytes looking for sequences
* that match some criteria. As a convenience, it also stores the input string and settings that
* were used to generated this ByteMatcher.
*/
public abstract class ByteMatcher {
private final String input;
private final SearchSettings settings;
protected ByteMatcher(String input, SearchSettings settings) {
this.input = input;
this.settings = settings;
}
/**
* Returns the original input text that generated this ByteMatacher.
* @return the original input text that generated this BytesMatcher
*/
public final String getInput() {
return input == null ? "" : input;
}
/**
* Returns the settings used to generate this ByteMatcher.
* @return the settings used to generate this ByteMatcher
*/
public SearchSettings getSettings() {
return settings;
}
/**
* Returns an {@link Iterable} for returning matches within the given byte sequence.
* @param bytes the byte sequence to search
* @return an iterable for return matches in the given sequence
*/
public abstract Iterable<ByteMatch> match(ExtendedByteSequence bytes);
/**
* Returns a description of what this byte matcher matches. (Typically a sequence of bytes)
* @return a description of what this byte matcher matches
*/
public abstract String getDescription();
/**
* Returns additional information about this byte matcher. (Typically the mask bytes)
* @return additional information about this byte matcher
*/
public abstract String getToolTip();
/**
* Returns true if this byte matcher is valid and can be used to perform a search. If false,
* the the description will return a an error message explaining why this byte matcher is
* invalid.
* @return true if this byte matcher is valid and can be used to perform a search.
*/
public boolean isValidSearch() {
return true;
}
/**
* Returns true if this byte matcher has valid (but possibly incomplete) input text. For
* example, when entering decimal values, the input could be just "-" as the user starts
* to enter a negative number. In this case the input is valid, but the {@link #isValidSearch()}
* would return false.
* @return true if this byte matcher has valid text
*/
public boolean isValidInput() {
return true;
}
@Override
public String toString() {
return input;
}
@Override
public int hashCode() {
return input.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ByteMatcher other = (ByteMatcher) obj;
return Objects.equals(input, other.input) &&
settings.getSearchFormat() == other.settings.getSearchFormat();
}
/**
* Record class to contain a match specification.
*/
public record ByteMatch(int start, int length) {}
}

View File

@ -0,0 +1,83 @@
/* ###
* 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.features.base.memsearch.matcher;
import org.apache.commons.collections4.iterators.EmptyIterator;
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
import ghidra.features.base.memsearch.format.SearchFormat;
import util.CollectionUtils;
/**
* Objects of this class are the result of {@link SearchFormat}s not being able to fully parse
* input text. There are two cases. The first is the user type an illegal character for the
* selected search format. In that case this matcher is both an invalid search and an invalid
* input and the description will explain the error. The second case is the input is valid text,
* but not complete so that a fully valid byte matcher could not be created. In this case, the
* search is still invalid, but the input is valid. The description will reflect this situation.
*/
public class InvalidByteMatcher extends ByteMatcher {
private final String errorMessage;
private final boolean isValidInput;
/**
* Construct an invalid matcher from invalid input text.
* @param errorMessage the message describing the invalid input
*/
public InvalidByteMatcher(String errorMessage) {
this(errorMessage, false);
}
/**
* Construct an invalid matcher from invalid input text or partial input text.
* @param errorMessage the message describing why this matcher is invalid
* @param isValidInput return true if the reason this is invalid is simply that the input
* text is not complete. For example, the user types "-" as they are starting to input
* a negative number.
*/
public InvalidByteMatcher(String errorMessage, boolean isValidInput) {
super(null, null);
this.errorMessage = errorMessage;
this.isValidInput = isValidInput;
}
@Override
public Iterable<ByteMatch> match(ExtendedByteSequence bytes) {
return CollectionUtils.asIterable(EmptyIterator.emptyIterator());
}
@Override
public String getDescription() {
return errorMessage;
}
@Override
public String getToolTip() {
return null;
}
@Override
public boolean isValidInput() {
return isValidInput;
}
@Override
public boolean isValidSearch() {
return false;
}
}

View File

@ -0,0 +1,179 @@
/* ###
* 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.features.base.memsearch.matcher;
import java.util.Iterator;
import org.bouncycastle.util.Arrays;
import ghidra.features.base.memsearch.bytesequence.ByteSequence;
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
import ghidra.features.base.memsearch.gui.SearchSettings;
/**
* {@link ByteMatcher} where the user search input has been parsed into a sequence of bytes and
* masks to be used for searching a byte sequence.
*/
public class MaskedByteSequenceByteMatcher extends ByteMatcher {
private final byte[] searchBytes;
private final byte[] masks;
/**
* Constructor where no masking will be required. The bytes must match exactly.
* @param input the input text used to create this matcher
* @param bytes the sequence of bytes to use for searching
* @param settings the {@link SearchSettings} used to parse the input text
*/
public MaskedByteSequenceByteMatcher(String input, byte[] bytes, SearchSettings settings) {
this(input, bytes, null, settings);
}
/**
* Constructor that includes a mask byte for each search byte.
* @param input the input text used to create this matcher
* @param bytes the sequence of bytes to use for searching
* @param masks the sequence of mask bytes to use for search. Each mask byte will be applied
* to the bytes being search before comparing them to the target bytes.
* @param settings the {@link SearchSettings} used to parse the input text
*/
public MaskedByteSequenceByteMatcher(String input, byte[] bytes, byte[] masks,
SearchSettings settings) {
super(input, settings);
if (masks == null) {
masks = new byte[bytes.length];
Arrays.fill(masks, (byte) 0xff);
}
if (bytes.length != masks.length) {
throw new IllegalArgumentException("Search bytes and mask bytes must be same length!");
}
this.searchBytes = bytes;
this.masks = masks;
}
@Override
public Iterable<ByteMatch> match(ExtendedByteSequence byteSequence) {
return new MatchIterator(byteSequence);
}
@Override
public String getDescription() {
return getByteString(searchBytes);
}
@Override
public String getToolTip() {
return "Mask = " + getByteString(masks);
}
private String getByteString(byte[] bytes) {
StringBuilder buf = new StringBuilder();
for (byte b : bytes) {
String hexString = Integer.toHexString(b & 0xff);
if (hexString.length() == 1) {
buf.append("0");
}
buf.append(hexString);
buf.append(" ");
}
return buf.toString().trim();
}
//==================================================================================================
// Methods to facilitate testing
//==================================================================================================
public byte[] getBytes() {
return searchBytes;
}
public byte[] getMask() {
return masks;
}
//==================================================================================================
// Inner classes
//==================================================================================================
private class MatchIterator implements Iterator<ByteMatch>, Iterable<ByteMatch> {
private ByteSequence byteSequence;
private int startIndex = 0;
private ByteMatch nextMatch;
public MatchIterator(ByteSequence byteSequence) {
this.byteSequence = byteSequence;
nextMatch = findNextMatch();
}
@Override
public Iterator<ByteMatch> iterator() {
return this;
}
@Override
public boolean hasNext() {
return nextMatch != null;
}
@Override
public ByteMatch next() {
if (nextMatch == null) {
return null;
}
ByteMatch returnValue = nextMatch;
nextMatch = findNextMatch();
return returnValue;
}
private ByteMatch findNextMatch() {
int nextPossibleStart = findNextPossibleStart(startIndex);
while (nextPossibleStart >= 0) {
startIndex = nextPossibleStart + 1;
if (isValidMatch(nextPossibleStart)) {
return new ByteMatch(nextPossibleStart, searchBytes.length);
}
nextPossibleStart = findNextPossibleStart(startIndex);
}
return null;
}
private boolean isValidMatch(int possibleStart) {
if (!byteSequence.hasAvailableBytes(possibleStart, searchBytes.length)) {
return false;
}
// we know 1st byte matches, check others
for (int i = 1; i < searchBytes.length; i++) {
if (searchBytes[i] != (byteSequence.getByte(possibleStart + i) & masks[i])) {
return false;
}
}
return true;
}
private int findNextPossibleStart(int start) {
for (int i = start; i < byteSequence.getLength(); i++) {
if (searchBytes[0] == (byteSequence.getByte(i) & masks[0])) {
return i;
}
}
return -1;
}
}
}

View File

@ -0,0 +1,140 @@
/* ###
* 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.features.base.memsearch.matcher;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.help.UnsupportedOperationException;
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
import ghidra.features.base.memsearch.gui.SearchSettings;
/**
* {@link ByteMatcher} where the user search input has been parsed as a regular expression.
*/
public class RegExByteMatcher extends ByteMatcher {
private final Pattern pattern;
public RegExByteMatcher(String input, SearchSettings settings) {
super(input, settings);
// without DOTALL mode, bytes that match line terminator characters will cause
// the regular expression pattern to not match.
this.pattern = Pattern.compile(input, Pattern.DOTALL);
}
@Override
public Iterable<ByteMatch> match(ExtendedByteSequence byteSequence) {
return new PatternMatchIterator(byteSequence);
}
@Override
public String getDescription() {
return "Reg Ex";
}
@Override
public String getToolTip() {
return null;
}
//==================================================================================================
// Inner classes
//==================================================================================================
/**
* Class for converting byte sequences into a {@link CharSequence} that can be used by
* the java regular expression engine
*/
private class ByteCharSequence implements CharSequence {
private ExtendedByteSequence byteSequence;
ByteCharSequence(ExtendedByteSequence byteSequence) {
this.byteSequence = byteSequence;
}
@Override
public int length() {
return byteSequence.getExtendedLength();
}
@Override
public char charAt(int index) {
byte b = byteSequence.getByte(index);
return (char) (b & 0xff);
}
@Override
public CharSequence subSequence(int start, int end) {
throw new UnsupportedOperationException();
}
}
/**
* Adapter class for converting java {@link Pattern} matching into an iterator of
* {@link ByteMatch}s.
*/
private class PatternMatchIterator implements Iterable<ByteMatch>, Iterator<ByteMatch> {
private Matcher matcher;
private ByteMatch nextMatch;
private ExtendedByteSequence byteSequence;
public PatternMatchIterator(ExtendedByteSequence byteSequence) {
this.byteSequence = byteSequence;
matcher = pattern.matcher(new ByteCharSequence(byteSequence));
nextMatch = findNextMatch();
}
@Override
public boolean hasNext() {
return nextMatch != null;
}
@Override
public ByteMatch next() {
if (nextMatch == null) {
return null;
}
ByteMatch returnValue = nextMatch;
nextMatch = findNextMatch();
return returnValue;
}
@Override
public Iterator<ByteMatch> iterator() {
return this;
}
private ByteMatch findNextMatch() {
if (!matcher.find()) {
return null;
}
int start = matcher.start();
int end = matcher.end();
if (start >= byteSequence.getLength()) {
return null;
}
return new ByteMatch(start, end - start);
}
}
}

View File

@ -4,16 +4,16 @@
* 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.searchmem.mask;
package ghidra.features.base.memsearch.mnemonic;
import java.util.*;

View File

@ -4,16 +4,16 @@
* 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.searchmem.mask;
package ghidra.features.base.memsearch.mnemonic;
import java.util.Arrays;

View File

@ -4,16 +4,16 @@
* 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.searchmem.mask;
package ghidra.features.base.memsearch.mnemonic;
import docking.action.MenuData;
import ghidra.app.CorePluginPackage;
@ -23,6 +23,8 @@ import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.MemorySearchService;
import ghidra.app.util.HelpTopics;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.Program;
@ -110,13 +112,13 @@ public class MnemonicSearchPlugin extends Plugin {
// dialog with the proper search string.
if (mask != null) {
maskedBitString = createMaskedBitString(mask.getValue(), mask.getMask());
byte[] maskedBytes = maskedBitString.getBytes();
MemorySearchService memorySearchService =
tool.getService(MemorySearchService.class);
memorySearchService.setIsMnemonic(true);
memorySearchService.search(maskedBytes, newContext);
memorySearchService.setSearchText(maskedBitString);
SearchSettings settings = new SearchSettings().withSearchFormat(SearchFormat.BINARY);
memorySearchService.createMemorySearchProvider(context.getNavigatable(),
maskedBitString, settings, false);
}
}

View File

@ -1,20 +1,19 @@
/* ###
* 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.
* 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.searchmem.mask;
package ghidra.features.base.memsearch.mnemonic;
/**
* Represents a filter for a single instruction. This defines what portions of the instruction will

View File

@ -0,0 +1,71 @@
/* ###
* 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.features.base.memsearch.scan;
import java.util.function.Predicate;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
/**
* Scan algorithms that examine the byte values of existing search results and look for changes.
* The specific scanner algorithm determines which results to keep and which to discard.
*/
public enum Scanner {
// keep unchanged results
EQUALS("Equals", mm -> compareBytes(mm) == 0, "Keep results whose values didn't change"),
// keep changed results
NOT_EQUALS("Not Equals", mm -> compareBytes(mm) != 0, "Keep results whose values changed"),
// keep results whose values increased
INCREASED("Increased", mm -> compareBytes(mm) > 0, "Keep results whose values increased"),
// keep results whose values decreased
DECREASED("Decreased", mm -> compareBytes(mm) < 0, "Keep results whose values decreased");
private final String name;
private final Predicate<MemoryMatch> acceptCondition;
private final String description;
private Scanner(String name, Predicate<MemoryMatch> condition, String description) {
this.name = name;
this.acceptCondition = condition;
this.description = description;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public boolean accept(MemoryMatch match) {
return acceptCondition.test(match);
}
private static int compareBytes(MemoryMatch match) {
byte[] bytes = match.getBytes();
byte[] originalBytes = match.getPreviousBytes();
ByteMatcher matcher = match.getByteMatcher();
SearchSettings settings = matcher.getSettings();
SearchFormat searchFormat = settings.getSearchFormat();
return searchFormat.compareValues(bytes, originalBytes, settings);
}
}

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.features.base.memsearch.searcher;
import java.util.function.Predicate;
/**
* Search filter that can test a search result and determine if that result is at an address
* whose offset matches the given alignment (i.e. its offset is a multiple of the alignment value)
*/
public class AlignmentFilter implements Predicate<MemoryMatch> {
private int alignment;
public AlignmentFilter(int alignment) {
this.alignment = alignment;
}
@Override
public boolean test(MemoryMatch match) {
return match.getAddress().getOffset() % alignment == 0;
}
}

View File

@ -0,0 +1,69 @@
/* ###
* 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.features.base.memsearch.searcher;
import java.util.function.Predicate;
import ghidra.program.model.listing.*;
/**
* Search filter that can test a search result and determine if that result starts at or inside
* a code unit that matches one of the selected types.
*/
public class CodeUnitFilter implements Predicate<MemoryMatch> {
private boolean includeInstructions;
private boolean includeUndefinedData;
private boolean includeDefinedData;
private boolean includeAll;
private Listing listing;
/**
* Constructor
* @param program the program to get code units from for testing its type
* @param includeInstructions if true, accept matches that are in an instruction
* @param includeDefinedData if true, accept matches that are in defined data
* @param includeUndefinedData if true, accept matches that are in undefined data
*/
public CodeUnitFilter(Program program, boolean includeInstructions, boolean includeDefinedData,
boolean includeUndefinedData) {
this.listing = program.getListing();
this.includeInstructions = includeInstructions;
this.includeDefinedData = includeDefinedData;
this.includeUndefinedData = includeUndefinedData;
this.includeAll = includeInstructions && includeDefinedData && includeUndefinedData;
}
@Override
public boolean test(MemoryMatch match) {
if (includeAll) {
return true;
}
CodeUnit codeUnit = listing.getCodeUnitContaining(match.getAddress());
if (codeUnit instanceof Instruction) {
return includeInstructions;
}
else if (codeUnit instanceof Data) {
Data data = (Data) codeUnit;
if (data.isDefined()) {
return includeDefinedData;
}
return includeUndefinedData;
}
return false;
}
}

View File

@ -0,0 +1,114 @@
/* ###
* 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.features.base.memsearch.searcher;
import java.util.Arrays;
import java.util.Objects;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.program.model.address.Address;
/**
* A class that represents a memory search hit at an address. Matches can also be updated with
* new byte values (from a scan or refresh action). The original bytes that matched the original
* search are maintained in addition to the "refreshed" bytes.
*/
public class MemoryMatch implements Comparable<MemoryMatch> {
private final Address address;
private byte[] bytes;
private byte[] previousBytes;
private final ByteMatcher matcher;
public MemoryMatch(Address address, byte[] bytes, ByteMatcher matcher) {
if (bytes == null || bytes.length < 1) {
throw new IllegalArgumentException("Must provide at least 1 byte");
}
this.address = Objects.requireNonNull(address);
this.bytes = bytes;
this.previousBytes = bytes;
this.matcher = matcher;
}
public MemoryMatch(Address address) {
this.address = address;
this.matcher = null;
}
public void updateBytes(byte[] newBytes) {
previousBytes = bytes;
if (!Arrays.equals(bytes, newBytes)) {
bytes = newBytes;
}
}
public Address getAddress() {
return address;
}
public int getLength() {
return bytes.length;
}
public byte[] getBytes() {
return bytes;
}
public byte[] getPreviousBytes() {
return previousBytes;
}
public ByteMatcher getByteMatcher() {
return matcher;
}
@Override
public int compareTo(MemoryMatch o) {
return address.compareTo(o.address);
}
@Override
public int hashCode() {
return address.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MemoryMatch other = (MemoryMatch) obj;
// just compare addresses. The bytes are mutable and we want matches to be equal even
// if the bytes are different
return Objects.equals(address, other.address);
}
@Override
public String toString() {
return address.toString();
}
public boolean isChanged() {
return !bytes.equals(previousBytes);
}
}

View File

@ -0,0 +1,337 @@
/* ###
* 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.features.base.memsearch.searcher;
import java.util.function.Predicate;
import ghidra.features.base.memsearch.bytesequence.*;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch;
import ghidra.program.model.address.*;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.task.TaskMonitor;
/**
* Class for searching bytes from a byteSource (memory) using a {@link ByteMatcher}. It handles
* breaking the search down into a series of searches, handling gaps in the address set and
* breaking large address ranges down into manageable sizes.
* <P>
* It is created with a specific byte source, matcher, address set, and search limit. Clients can
* then either call the {@link #findAll(Accumulator, TaskMonitor)} method or use it to incrementally
* search using {@link #findNext(Address, TaskMonitor)},
* {@link #findPrevious(Address, TaskMonitor)}, or {@link #findOnce(Address, boolean, TaskMonitor)}.
*/
public class MemorySearcher {
private static final int DEFAULT_CHUNK_SIZE = 16 * 1024;
private static final int OVERLAP_SIZE = 100;
private final AddressableByteSequence bytes1;
private final AddressableByteSequence bytes2;
private final ByteMatcher matcher;
private final int chunkSize;
private Predicate<MemoryMatch> filter = r -> true;
private final int searchLimit;
private final AddressSetView searchSet;
/**
* Constructor
* @param byteSource the source of the bytes to be searched
* @param matcher the matcher that can find matches in a byte sequence
* @param addresses the address in the byte source to search
* @param searchLimit the max number of hits before stopping
*/
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher,
AddressSet addresses, int searchLimit) {
this(byteSource, matcher, addresses, searchLimit, DEFAULT_CHUNK_SIZE);
}
/**
* Constructor
* @param byteSource the source of the bytes to be searched
* @param matcher the matcher that can find matches in a byte sequence
* @param addresses the address in the byte source to search
* @param searchLimit the max number of hits before stopping
* @param chunkSize the maximum number of bytes to feed to the matcher at any one time.
*/
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher,
AddressSet addresses, int searchLimit, int chunkSize) {
this.matcher = matcher;
this.searchSet = addresses;
this.searchLimit = searchLimit;
this.chunkSize = chunkSize;
bytes1 = new AddressableByteSequence(byteSource, chunkSize);
bytes2 = new AddressableByteSequence(byteSource, chunkSize);
}
/**
* Sets any match filters. The filter can be used to exclude matches that don't meet some
* criteria that is not captured in the byte matcher such as alignment and code unit type.
* @param filter the predicate to use to filter search results
*/
public void setMatchFilter(Predicate<MemoryMatch> filter) {
this.filter = filter;
}
/**
* Searches all the addresses in this search's {@link AddressSetView} using the byte matcher to
* find matches. As each match is found (and passes any filters), the match is given to the
* accumulator. The search continues until either the entire address set has been search or
* the search limit has been reached.
* @param accumulator the accumulator for found matches
* @param monitor the task monitor
* @return true if the search completed searching through the entire address set.
*/
public boolean findAll(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
monitor.initialize(searchSet.getNumAddresses(), "Searching...");
for (AddressRange range : searchSet.getAddressRanges()) {
if (!findAll(accumulator, range, monitor)) {
return false;
}
}
return true;
}
/**
* Searches forwards or backwards starting at the given address until a match is found or
* the start or end of the address set is reached. It does not currently wrap the search.
* @param start the address to start searching
* @param forward if true, search forward, otherwise, search backwards.
* @param monitor the task monitor
* @return the first match found or null if no match found.
*/
public MemoryMatch findOnce(Address start, boolean forward, TaskMonitor monitor) {
if (forward) {
return findNext(start, monitor);
}
return findPrevious(start, monitor);
}
/**
* Searches forwards starting at the given address until a match is found or
* the end of the address set is reached. It does not currently wrap the search.
* @param start the address to start searching
* @param monitor the task monitor
* @return the first match found or null if no match found.
*/
public MemoryMatch findNext(Address start, TaskMonitor monitor) {
long numAddresses = searchSet.getNumAddresses() - searchSet.getAddressCountBefore(start);
monitor.initialize(numAddresses, "Searching....");
for (AddressRange range : searchSet.getAddressRanges(start, true)) {
range = range.intersectRange(start, range.getMaxAddress());
MemoryMatch match = findFirst(range, monitor);
if (match != null) {
return match;
}
if (monitor.isCancelled()) {
break;
}
}
return null;
}
/**
* Searches backwards starting at the given address until a match is found or
* the beginning of the address set is reached. It does not currently wrap the search.
* @param start the address to start searching
* @param monitor the task monitor
* @return the first match found or null if no match found.
*/
public MemoryMatch findPrevious(Address start, TaskMonitor monitor) {
monitor.initialize(searchSet.getAddressCountBefore(start) + 1, "Searching....");
for (AddressRange range : searchSet.getAddressRanges(start, false)) {
MemoryMatch match = findLast(range, start, monitor);
if (match != null) {
return match;
}
if (monitor.isCancelled()) {
break;
}
}
return null;
}
private MemoryMatch findFirst(AddressRange range, TaskMonitor monitor) {
AddressableByteSequence searchBytes = bytes1;
AddressableByteSequence extra = bytes2;
AddressRangeIterator it = new AddressRangeSplitter(range, chunkSize, true);
AddressRange first = it.next();
searchBytes.setRange(first);
while (it.hasNext()) {
AddressRange next = it.next();
extra.setRange(next);
MemoryMatch match = findFirst(searchBytes, extra, monitor);
if (match != null) {
return match;
}
if (monitor.isCancelled()) {
break;
}
// Flip flop the byte buffers, making the extended buffer become primary and preparing
// the primary buffer to be used to read the next chunk. See the
// ExtendedByteSequence class for an explanation of this approach.
searchBytes = extra;
extra = searchBytes == bytes1 ? bytes2 : bytes1;
}
// last segment, no extra bytes to overlap, so just search the primary buffer
extra.clear();
return findFirst(searchBytes, extra, monitor);
}
private MemoryMatch findLast(AddressRange range, Address start, TaskMonitor monitor) {
AddressableByteSequence searchBytes = bytes1;
AddressableByteSequence extra = bytes2;
extra.clear();
if (range.contains(start)) {
Address min = range.getMinAddress();
Address max = range.getMaxAddress();
range = new AddressRangeImpl(min, start);
AddressRange remaining = new AddressRangeImpl(start.next(), max);
AddressRange extraRange = new AddressRangeSplitter(remaining, chunkSize, true).next();
extra.setRange(extraRange);
}
AddressRangeIterator it = new AddressRangeSplitter(range, chunkSize, false);
while (it.hasNext()) {
AddressRange next = it.next();
searchBytes.setRange(next);
MemoryMatch match = findLast(searchBytes, extra, monitor);
if (match != null) {
return match;
}
if (monitor.isCancelled()) {
break;
}
// Flip flop the byte buffers, making the primary buffer the new extended buffer
// and refilling the primary buffer with new data going backwards.
extra = searchBytes;
searchBytes = extra == bytes1 ? bytes2 : bytes1;
}
return null;
}
private MemoryMatch findFirst(AddressableByteSequence searchBytes, ByteSequence extra,
TaskMonitor monitor) {
ExtendedByteSequence searchSequence =
new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE);
for (ByteMatch byteMatch : matcher.match(searchSequence)) {
Address address = searchBytes.getAddress(byteMatch.start());
byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length());
MemoryMatch match = new MemoryMatch(address, bytes, matcher);
if (filter.test(match)) {
return match;
}
if (monitor.isCancelled()) {
break;
}
}
monitor.incrementProgress(searchBytes.getLength());
return null;
}
private MemoryMatch findLast(AddressableByteSequence searchBytes, ByteSequence extra,
TaskMonitor monitor) {
MemoryMatch last = null;
ExtendedByteSequence searchSequence =
new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE);
for (ByteMatch byteMatch : matcher.match(searchSequence)) {
Address address = searchBytes.getAddress(byteMatch.start());
byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length());
MemoryMatch match = new MemoryMatch(address, bytes, matcher);
if (filter.test(match)) {
last = match;
}
if (monitor.isCancelled()) {
return null;
}
}
monitor.incrementProgress(searchBytes.getLength());
return last;
}
private boolean findAll(Accumulator<MemoryMatch> accumulator, AddressRange range,
TaskMonitor monitor) {
AddressableByteSequence searchBytes = bytes1;
AddressableByteSequence extra = bytes2;
AddressRangeIterator it = new AddressRangeSplitter(range, chunkSize, true);
AddressRange first = it.next();
searchBytes.setRange(first);
while (it.hasNext()) {
AddressRange next = it.next();
extra.setRange(next);
if (!findAll(accumulator, searchBytes, extra, monitor)) {
return false;
}
searchBytes = extra;
extra = searchBytes == bytes1 ? bytes2 : bytes1;
}
extra.clear();
return findAll(accumulator, searchBytes, extra, monitor);
}
private boolean findAll(Accumulator<MemoryMatch> accumulator,
AddressableByteSequence searchBytes, ByteSequence extra, TaskMonitor monitor) {
if (monitor.isCancelled()) {
return false;
}
ExtendedByteSequence searchSequence =
new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE);
for (ByteMatch byteMatch : matcher.match(searchSequence)) {
Address address = searchBytes.getAddress(byteMatch.start());
byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length());
MemoryMatch match = new MemoryMatch(address, bytes, matcher);
if (filter.test(match)) {
if (accumulator.size() >= searchLimit) {
return false;
}
accumulator.add(match);
}
if (monitor.isCancelled()) {
return false;
}
}
// Reset the monitor message, since clients may change the message (such as the
// incremental table loader)
monitor.setMessage("Searching...");
monitor.incrementProgress(searchBytes.getLength());
return true;
}
}

View File

@ -27,6 +27,7 @@ public class MemSearchResult implements Comparable<MemSearchResult> {
private Address address;
private int length;
private byte[] bytes;
public MemSearchResult(Address address, int length) {
this.address = Objects.requireNonNull(address);
@ -37,6 +38,15 @@ public class MemSearchResult implements Comparable<MemSearchResult> {
this.length = length;
}
public MemSearchResult(Address address, byte[] bytes) {
if (bytes == null || bytes.length < 1) {
throw new IllegalArgumentException("Must provide at least 1 byte");
}
this.address = Objects.requireNonNull(address);
this.bytes = bytes;
this.length = bytes.length;
}
public Address getAddress() {
return address;
}
@ -45,6 +55,10 @@ public class MemSearchResult implements Comparable<MemSearchResult> {
return length;
}
public byte[] getBytes() {
return bytes;
}
@Override
public int compareTo(MemSearchResult o) {
return address.compareTo(o.address);

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

View File

@ -1,307 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.searchmem;
import static org.junit.Assert.*;
import java.awt.Container;
import java.awt.Window;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.swing.*;
import org.apache.commons.collections4.IteratorUtils;
import docking.action.DockingActionIf;
import docking.test.AbstractDockingTest;
import docking.widgets.fieldpanel.support.Highlight;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.plugin.core.table.TableServicePlugin;
import ghidra.app.services.*;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.field.BytesFieldFactory;
import ghidra.app.util.viewer.field.ListingField;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.test.AbstractProgramBasedTest;
import ghidra.util.Msg;
import ghidra.util.search.memory.MemSearchResult;
import ghidra.util.table.GhidraTable;
/**
* Base class for memory search tests.
*/
public abstract class AbstractMemSearchTest extends AbstractProgramBasedTest {
protected MemSearchPlugin memSearchPlugin;
protected DockingActionIf searchAction;
protected CodeBrowserPlugin cb;
protected CodeViewerProvider provider;
protected JLabel statusLabel;
protected JTextField valueField;
protected JComboBox<?> valueComboBox;
protected Container pane;
protected JLabel hexLabel;
protected Memory memory;
protected Listing listing;
protected TableServicePlugin tableServicePlugin;
protected MarkerService markerService;
protected MemSearchDialog dialog;
/*
* Note that this setup function does not have the @Before annotation - this is because
* sub-classes often need to override this and if we have the annotation here, the test
* runner will only invoke this base class implementation.
*/
public void setUp() throws Exception {
// this builds the program and launches the tool
initialize();
memSearchPlugin = env.getPlugin(MemSearchPlugin.class);
listing = program.getListing();
memory = program.getMemory();
searchAction = getAction(memSearchPlugin, "Search Memory");
cb = codeBrowser; // TODO delete after 7.3 release; just use the parent's CodeBrowser
provider = cb.getProvider();
markerService = tool.getService(MarkerService.class);
tableServicePlugin = env.getPlugin(TableServicePlugin.class);
showMemSearchDialog();
setToggleButtonSelected(pane, MemSearchDialog.ADVANCED_BUTTON_NAME, true);
selectRadioButton("Binary");
}
@Override
protected Program getProgram() throws Exception {
return buildProgram();
}
protected abstract Program buildProgram() throws Exception;
protected void waitForSearch(String panelName, int expectedResults) {
waitForCondition(() -> {
return !memSearchPlugin.isSearching();
}, "Timed-out waiting for search results");
Window window = AbstractDockingTest.waitForWindowByTitleContaining(panelName);
GhidraTable gTable = findComponent(window, GhidraTable.class, true);
waitForSwing();
assertEquals(expectedResults, gTable.getRowCount());
}
protected void waitForSearchTask() {
waitForSwing();
Thread t = dialog.getTaskScheduler().getCurrentThread();
if (t == null) {
return;
}
try {
t.join();
}
catch (InterruptedException e) {
Msg.debug(this, "Interrupted waiting for the search task thread to finish");
}
waitForSwing();
}
protected void showMemSearchDialog() {
performAction(searchAction, provider, true);
// dig up the components of the dialog
dialog = waitForDialogComponent(MemSearchDialog.class);
pane = dialog.getComponent();
statusLabel = (JLabel) findComponentByName(pane, "statusLabel");
valueComboBox = findComponent(pane, JComboBox.class);
valueField = (JTextField) valueComboBox.getEditor().getEditorComponent();
hexLabel = (JLabel) findComponentByName(pane, "HexSequenceField");
}
protected void selectRadioButton(String text) {
setToggleButtonSelected(pane, text, true);
}
protected void selectCheckBox(String text, boolean state) {
setToggleButtonSelected(pane, text, state);
}
@SuppressWarnings("unchecked")
private List<Address> getHighlightAddresses() {
CodeViewerService service = tool.getService(CodeViewerService.class);
Object codeViewerProvider = getInstanceField("connectedProvider", service);
Map<Program, ListingHighlightProvider> highlighterMap =
(Map<Program, ListingHighlightProvider>) getInstanceField("programHighlighterMap",
codeViewerProvider);
ListingHighlightProvider highlightProvider = highlighterMap.get(program);
assertEquals("The inner-class has been renamed", "SearchTableHighlightHandler",
highlightProvider.getClass().getSimpleName());
MemSearchTableModel model =
(MemSearchTableModel) getInstanceField("model", highlightProvider);
List<MemSearchResult> data = model.getModelData();
return data.stream().map(result -> result.getAddress()).collect(Collectors.toList());
}
protected void checkMarkerSet(List<Address> expected) {
TableComponentProvider<?>[] providers = tableServicePlugin.getManagedComponents();
TableComponentProvider<?> tableProvider = providers[0];
assertTrue(tool.isVisible(tableProvider));
List<Address> highlights = getHighlightAddresses();
assertListEqualUnordered("Search highlights not correctly generated", expected, highlights);
MarkerSet markers =
runSwing(() -> markerService.getMarkerSet(tableProvider.getName(), program));
assertNotNull(markers);
AddressSet addressSet = runSwing(() -> markers.getAddressSet());
AddressIterator it = addressSet.getAddresses(true);
List<Address> list = IteratorUtils.toList(it);
assertListEqualUnordered("Search markers not correctly generated", expected, list);
}
protected void pressSearchAllButton() {
runSwing(() -> invokeInstanceMethod("allCallback", dialog));
}
protected void pressSearchButton(String text) throws Exception {
pressButtonByText(pane, text);
waitForSearchTask();
}
protected void performSearchTest(List<Address> expected, String buttonText) throws Exception {
for (Address addr : expected) {
pressSearchButton(buttonText);
assertEquals("Found", getStatusText());
cb.updateNow();
assertEquals(addr, cb.getCurrentLocation().getAddress());
}
pressSearchButton(buttonText);
assertEquals("Not Found", getStatusText());
}
protected String getStatusText() {
AtomicReference<String> ref = new AtomicReference<>();
runSwing(() -> ref.set(statusLabel.getText()));
return ref.get();
}
protected void setValueText(String s) {
setText(valueField, s);
}
protected void myTypeText(String text) {
// Note: we do not use setFocusedComponent(valueField), as that method will fail if the
// focus change doesn't work. Here, we will keep on going if the focus change
// doesn't work.
runSwing(() -> valueField.requestFocus());
triggerText(valueField, text);
}
protected ListingHighlightProvider getHighlightProvider() {
CodeViewerService service = tool.getService(CodeViewerService.class);
FormatManager fm = (FormatManager) getInstanceField("formatMgr", service);
return (ListingHighlightProvider) getInstanceField("highlightProvider", fm);
}
protected void repeatSearch() {
DockingActionIf action = getAction(memSearchPlugin, "Repeat Memory Search");
assertTrue(action.isEnabled());
performAction(action, provider, true);
waitForSearchTask();
}
protected Address currentAddress() {
cb.updateNow();
Address addr = cb.getCurrentLocation().getAddress();
return addr;
}
protected CodeUnit currentCodeUnit() {
CodeUnit cu = program.getListing().getCodeUnitContaining(currentAddress());
return cu;
}
protected CodeUnit codeUnitContaining(Address addr) {
CodeUnit cu = program.getListing().getCodeUnitContaining(addr);
return cu;
}
protected void assertSearchSelectionSelected() {
AbstractButton b = findAbstractButtonByText(pane, "Search Selection");
assertTrue(isEnabled(b));
assertTrue(isSelected(b));
}
protected void assertButtonState(String text, boolean isEnabled, boolean isSelected) {
AbstractButton b = findAbstractButtonByText(pane, text);
assertEquals(isEnabled, isEnabled(b));
assertEquals(isSelected, isSelected(b));
}
protected void assertEnabled(String text, boolean isEnabled) {
// Note: we do not use the findAbstractButtonByText() here as there are two buttons with
// the same text. Only one of the buttons is actually a JButton, so this call works.
// Ideally, all buttons would have a name set so that wouldn't have to rely on the
// button text.
JButton b = findButtonByText(pane, text);
assertEquals(isEnabled, isEnabled(b));
}
protected void setAlignment(String alignment) {
JTextField alignmentField =
(JTextField) findComponentByName(dialog.getComponent(), "Alignment");
setText(alignmentField, alignment);
}
protected Highlight[] getByteHighlights(Address address, String bytes) {
goTo(address);
CodeUnit cu = codeUnitContaining(address);
ListingHighlightProvider hlProvider = getHighlightProvider();
ListingField field = getField(address, BytesFieldFactory.FIELD_NAME);
return hlProvider.createHighlights(bytes, field, -1);
}
protected void setEndianness(String text) {
// we use this method because the given button may be disabled, which means we cannot
// click it, but we can select it
AbstractButton button = findAbstractButtonByText(pane, text);
runSwing(() -> button.setSelected(true));
}
}

View File

@ -1,433 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.searchmem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import javax.swing.JComboBox;
import org.junit.Before;
import org.junit.Test;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.test.ToyProgramBuilder;
/**
* Tests for searching memory for ascii.
*/
public class MemSearchAsciiTest extends AbstractMemSearchTest {
@Override
@Before
public void setUp() throws Exception {
super.setUp();
selectRadioButton("String");
}
@Override
protected Program buildProgram() throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder("Test", false, ProgramBuilder._TOY);
builder.createMemory(".text", "0x1001000", 0x6600);
builder.createMemory(".data", "0x1008000", 0x600);
builder.createMemory(".rsrc", "0x100a000", 0x5400);
//create some strings
builder.createEncodedString("0x010016ec", "something", StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16LE,
true);
builder.createEncodedString("0x01001840", "\u039d\u03bf\u03c4\u03b5\u03c0\u03b1\u03bd",
StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x0100186a",
"\u03c1\u03b8\u03c4\u03b5\u03c0\u03b1\u03bd\u03c2\u03b2", StandardCharsets.UTF_16LE,
true);
builder.createEncodedString("0x0100196a",
"\u03c1\u03b8\u03c4\u03b5\u03c0\u03b1\u03bd\u03c2\u03b2", StandardCharsets.UTF_8, true);
builder.createEncodedString("0x0100189d", "\"Hello world!\"\n\t-new programmer",
StandardCharsets.US_ASCII, true);
builder.createEncodedString("0x0100198e", "\"Hello world!\"\n\t-new programmer",
StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false);
builder.createEncodedString("0x010013e0", "notepad.exe", StandardCharsets.US_ASCII, false);
builder.createEncodedString("0x1006c6a", "GetLocaleInfoW", StandardCharsets.US_ASCII,
false);
builder.createEncodedString("0x1006f26", "GetCPInfo", StandardCharsets.US_ASCII, false);
builder.createEncodedString("0x0100dde0", "NOTEPAD.EXE", StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x0100eb90",
"This string contains notepad twice. Here is the second NotePad.",
StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x0100ed00", "Another string", StandardCharsets.UTF_16LE,
true);
return builder.getProgram();
}
@Test
public void testStringFormatSelected() throws Exception {
// verify that String options are showing: case sensitive, unicode,
// regular expression check boxes.
assertButtonState("Case Sensitive", true, false);
@SuppressWarnings("unchecked")
JComboBox<Charset> comboBox =
(JComboBox<Charset>) findComponentByName(pane, "Encoding Options");
assertNotNull(comboBox);
}
@Test
public void testCaseSensitiveOff() throws Exception {
selectCheckBox("Case Sensitive", false);
setValueText("notepad");
List<Address> addrs = addrs(0x010013cc, 0x010013e0);
performSearchTest(addrs, "Next");
}
@Test
public void testCaseSensitiveOn() throws Exception {
selectCheckBox("Case Sensitive", true);
setValueText("NOTEpad");
pressSearchButton("Next");
assertEquals("Not Found", statusLabel.getText());
}
@Test
public void testUnicodeNotCaseSensitive() throws Exception {
selectCheckBox("Case Sensitive", false);
setEncoding(StandardCharsets.UTF_16);
setValueText("NOTEpad");
List<Address> addrs = addrs(0x01001708, 0x0100dde0, 0x0100eb90, 0x0100eb90); // this code unit contains two notepads in one string
performSearchTest(addrs, "Next");
}
@Test
public void testGreekUnicodeSearch() throws Exception {
selectCheckBox("Case Sensitive", false);
setEncoding(StandardCharsets.UTF_16);
setValueText("\u03c4\u03b5\u03c0\u03b1\u03bd");
List<Address> addrs = addrs(0x01001840, 0x0100186a);
performSearchTest(addrs, "Next");
addrs.add(addr(0x0100196a));
setEncoding(StandardCharsets.UTF_8);
pressSearchButton("Next");
assertEquals("Found", statusLabel.getText());
assertEquals(addrs.get(2), cb.getCurrentLocation().getAddress());
pressSearchButton("Next");
assertEquals("Not Found", statusLabel.getText());
}
@Test
public void testRepeatUnicodeNotCaseSensitive() throws Exception {
selectCheckBox("Case Sensitive", false);
setEncoding(StandardCharsets.UTF_16);
setValueText("NOTEpad");
List<Address> startList = addrs(0x01001708, 0x0100dde0, 0x0100eb90, 0x0100eb90); // this code unit contains two notepads in one string
for (int i = 0; i < startList.size(); i++) {
Address start = startList.get(i);
if (i == 0) {
pressSearchButton("Next");
}
else {
repeatSearch();
}
assertEquals(start, cb.getCurrentLocation().getAddress());
assertEquals("Found", statusLabel.getText());
}
pressSearchButton("Next");
assertEquals("Not Found", statusLabel.getText());
}
@Test
public void testUnicodeCaseSensitive() throws Exception {
selectCheckBox("Case Sensitive", true);
setEncoding(StandardCharsets.UTF_16);
setValueText("Notepad");
performSearchTest(addrs(0x01001708), "Next");
}
@Test
public void testUnicodeBigEndian() throws Exception {
// with Big Endian selected, unicode bytes should be reversed
setEndianness("Big Endian");
setEncoding(StandardCharsets.UTF_16);
setValueText("start");
assertEquals("00 73 00 74 00 61 00 72 00 74 ", hexLabel.getText());
selectRadioButton("Little Endian");
assertEquals("73 00 74 00 61 00 72 00 74 00 ", hexLabel.getText());
}
@Test
public void testSearchAllUnicodeNotCaseSensitive() throws Exception {
// test for markers
// QueryResults should get displayed
// test the marker stuff
selectCheckBox("Case Sensitive", false);
setEncoding(StandardCharsets.UTF_16);
setValueText("NOTEpad");
pressSearchAllButton();
waitForSearch("Search Memory - ", 4);
List<Address> addrs = addrs(0x01001708, 0x0100dde0, 0x0100ebba, 0x0100ebfe);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllUnicodeCaseSensitive() throws Exception {
// test for markers
// QueryResults should get displayed
// test the marker stuff
selectCheckBox("Case Sensitive", true);
setEncoding(StandardCharsets.UTF_16);
setValueText("Notepad");
pressSearchAllButton();
waitForSearch("Search Memory - ", 1);
checkMarkerSet(addrs(0x01001708));
}
@Test
public void testSearchAllNotCaseSensitive() throws Exception {
// QueryResults should get displayed
// test the marker stuff
selectCheckBox("Case Sensitive", false);
setValueText("NOTEpad");
pressSearchAllButton();
waitForSearch("Search Memory - ", 2);
List<Address> addrs = addrs(0x010013cc, 0x010013e0);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllCaseSensitive() throws Exception {
// QueryResults should get displayed
// test the marker stuff
// create an set of ascii bytes to do this test
byte[] b = new byte[] { 'N', 'O', 'T', 'E', 'p', 'a', 'd' };
int transactionID = program.startTransaction("test");
memory.setBytes(addr(0x0100b451), b);
program.endTransaction(transactionID, true);
selectCheckBox("Case Sensitive", true);
setValueText("NOTEpad");
pressSearchAllButton();
waitForSearch("Search Memory - ", 1);
checkMarkerSet(addrs(0x0100b451));
}
@Test
public void testSearchAllCaseSensitiveAlign8() throws Exception {
// QueryResults should get displayed
// test the marker stuff
// create an set of ascii bytes to do this test
setAlignment("8");
selectCheckBox("Case Sensitive", true);
setValueText("notepad");
pressSearchAllButton();
waitForSearch("Search Memory - ", 1);
checkMarkerSet(addrs(0x010013e0));
}
@Test
public void testSearchSelection() throws Exception {
makeSelection(tool, program, addr(0x01006c73), addr(0x01006f02));
assertSearchSelectionSelected();
selectCheckBox("Case Sensitive", false);
setValueText("Info");
performSearchTest(addrs(0x01006c6a), "Next");
}
@Test
public void testSearchNonContiguousSelection() throws Exception {
makeSelection(tool, program, range(0x01006c70, 0x01006c80), range(0x01006f2b, 0x01006f37));
assertSearchSelectionSelected();
selectCheckBox("Case Sensitive", false);
setValueText("Info");
List<Address> addrs = addrs(0x01006c6a, 0x01006f26);
performSearchTest(addrs, "Next");
}
@Test
public void testSearchBackward() throws Exception {
goTo(tool, program, addr(0x1006f56));
selectCheckBox("Case Sensitive", true);
setValueText("Info");
List<Address> addrs = addrs(0x01006f26, 0x01006c6a);
performSearchTest(addrs, "Previous");
}
@Test
public void testSearchBackwardInSelection() throws Exception {
goTo(tool, program, addr(0x01006f02));
makeSelection(tool, program, addr(0x01006c73), addr(0x01006f02));
assertSearchSelectionSelected();
selectCheckBox("Case Sensitive", false);
setValueText("Info");
List<Address> addrs = addrs(0x01006c6a);
performSearchTest(addrs, "Previous");
}
@Test
public void testSearchBackwardAlign4() throws Exception {
goTo(tool, program, addr(0x1006f56));
selectCheckBox("Case Sensitive", true);
setAlignment("8");
setValueText("notepad");
List<Address> addrs = addrs(0x010013e0);
performSearchTest(addrs, "Previous");
}
@Test
public void testSearchBackwardAlign4NoneFound() throws Exception {
goTo(tool, program, addr(0x1006f56));
selectCheckBox("Case Sensitive", true);
setAlignment("8");
setValueText("Info");
pressSearchButton("Previous");
assertEquals("Not Found", statusLabel.getText());
}
@Test
public void testSearchEscapeSequences() throws Exception {
selectCheckBox("Case Sensitive", true);
selectCheckBox("Escape Sequences", true);
setEncoding(StandardCharsets.US_ASCII);
setValueText("\"Hello world!\"\\n\\t-new programmer");
List<Address> addrs = addrs(0x0100189d, 0x0100198e);
pressSearchButton("Next");
assertEquals(addrs.get(0), cb.getCurrentLocation().getAddress());
assertEquals("Found", statusLabel.getText());
pressSearchButton("Next");
assertEquals("Not Found", statusLabel.getText());
setEncoding(StandardCharsets.UTF_16LE);
pressSearchButton("Next");
assertEquals("Found", statusLabel.getText());
pressSearchButton("Next");
assertEquals("Not Found", statusLabel.getText());
}
//==================================================================================================
// Private Methods
//==================================================================================================
@SuppressWarnings("unchecked")
private void setEncoding(Charset encoding) throws Exception {
JComboBox<Charset> encodingOptions =
(JComboBox<Charset>) findComponentByName(pane, "Encoding Options", false);
// Makes encoding UTF_16 in case encoding is UTF_16BE or UTF_16LE
// BE and LE are not choices in the combo box.
if (encoding == StandardCharsets.UTF_16BE || encoding == StandardCharsets.UTF_16LE) {
encoding = StandardCharsets.UTF_16;
}
for (int i = 0; i < encodingOptions.getItemCount(); i++) {
if (encodingOptions.getItemAt(i) == encoding) {
int index = i;
runSwing(() -> encodingOptions.setSelectedIndex(index));
break;
}
}
}
}

View File

@ -1,559 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.searchmem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.nio.charset.StandardCharsets;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import org.junit.Before;
import org.junit.Test;
import docking.widgets.fieldpanel.support.Highlight;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Pointer32DataType;
import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
/**
* Tests for the Binary format in searching memory.
*/
public class MemSearchBinaryTest extends AbstractMemSearchTest {
public MemSearchBinaryTest() {
super();
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
selectRadioButton("Binary");
}
@Override
protected Program buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86);
builder.createMemory(".text", Long.toHexString(0x1001000), 0x6600);
builder.createMemory(".data", Long.toHexString(0x1008000), 0x600);
builder.createMemory(".rsrc", Long.toHexString(0x100A000), 0x5400);
builder.createMemory(".bound_import_table", Long.toHexString(0xF0000248), 0xA8);
builder.createMemory(".debug_data", Long.toHexString(0xF0001300), 0x1C);
//create and disassemble a function
builder.setBytes(
"0x01002cf5",
"55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 33 " +
"ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 74 27 " +
"56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 04 12 00 " +
"01 56 8b f8 ff 15 c0 10 00 01 eb 14 ff 75 18 ff 75 0c ff 75 10 ff 75 08 ff 15 04 " +
"12 00 01 8b f8 8b c7 5f 5e 5d c2 14");
builder.disassemble("0x01002cf5", 0x121, true);
builder.createFunction("0x01002cf5");
//create some data
builder.setBytes("0x1001004", "85 4f dc 77");
builder.applyDataType("0x1001004", new Pointer32DataType(), 1);
builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true);
builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, true);
builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, true);
//create some undefined data
builder.setBytes("0x1001500", "4e 00 65 00 77 00");
builder.setBytes("0x1003000", "55 00");
return builder.getProgram();
}
@Test
public void testBinaryInvalidEntry() {
// enter a non-binary digit; the search field should not accept it
setValueText("2");
assertEquals("", valueField.getText());
}
@Test
public void testBinaryMoreThan8Chars() throws Exception {
// try entering more than 8 binary digits (no spaces); the dialog
// should not accept the 9th digit.
myTypeText("010101010");
assertEquals("01010101", valueField.getText());
}
@Test
public void testBinaryEnterSpaces() {
// verify that more than 8 digits are allowed if spaces are entered
myTypeText("01110000 01110000");
assertEquals("01110000 01110000", valueField.getText());
}
@Test
public void testBinaryPasteNumberWithPrefix() {
// paste a number with a binary prefix;
// the prefix should be removed before the insertion
setValueText("0b00101010");
assertEquals("00101010", valueField.getText());
setValueText("0B1010 10");
assertEquals("1010 10", valueField.getText());
}
@Test
public void testBinarySearch() throws Exception {
goTo(0x01001000);
setValueText("00010100 11111111");
pressButtonByText(pane, "Next");
waitForSearchTask();
Address currentAddress = currentAddress();
CodeUnit cu = codeUnitContaining(addr(0x01002d08));
assertEquals(cu.getMinAddress(), currentAddress);
assertEquals("Found", statusLabel.getText());
}
@Test
public void testBinarySearchNext() throws Exception {
goTo(0x01001000);
setValueText("01110101");
//@formatter:off
List<Address> addrs = addrs(0x01002d06,
0x01002d11,
0x01002d2c,
0x01002d2f,
0x01002d37,
0x01002d3a,
0x01002d3e,
0x01002d52,
0x01002d55,
0x01002d58,
0x01002d5b);
//@formatter:on
for (int i = 0; i < addrs.size(); i++) {
Address start = addrs.get(i);
pressSearchButton("Next");
CodeUnit cu = listing.getCodeUnitContaining(start);
assertEquals(cu.getMinAddress(), cb.getCurrentLocation().getAddress());
assertEquals("Found", statusLabel.getText());
}
pressSearchButton("Next");
assertEquals("Not Found", statusLabel.getText());
}
@Test
public void testBinarySearchNextAlign4() throws Exception {
// hit the enter key in the values field;
// should go to next match found
Address addr = addr(0x01001000);
tool.firePluginEvent(new ProgramLocationPluginEvent("test", new ProgramLocation(program,
addr), program));
waitForSwing();
// enter a Binary value and hit the search button
setValueText("01110101");
setAlignment("4");
//the bytes are at the right alignment value but the code units are not
List<Address> addrs = addrs(0x01002d2f, 0x01002d37, 0x01002d5b);
for (int i = 0; i < addrs.size(); i++) {
Address start = addrs.get(i);
pressSearchButton("Next");
CodeUnit cu = listing.getCodeUnitContaining(start);
assertEquals(cu.getMinAddress(), cb.getCurrentLocation().getAddress());
assertEquals("Found", statusLabel.getText());
}
pressSearchButton("Next");
assertEquals("Not Found", statusLabel.getText());
}
@Test
public void testBinaryContiguousSelection() throws Exception {
goTo(0x01001070);
makeSelection(tool, program, range(0x01002cf5, 0x01002d6d));
assertSearchSelectionSelected();
setValueText("11110110");
// the bytes are at the right alignment value but the code units are not
performSearchTest(addrs(0x01002d27), "Next");
}
@Test
public void testBinaryNonContiguousSelection() throws Exception {
makeSelection(tool, program, range(0x01002cf5, 0x01002d0e), range(0x01002d47, 0x01002d51));
assertSearchSelectionSelected();
setValueText("01010110");
// the bytes are at the right alignment value but the code units are not
List<Address> addrs = addrs(0x01002cfc, 0x01002d47);
performSearchTest(addrs, "Next");
}
@Test
public void testBinarySelectionNotOn() throws Exception {
goTo(0x01002cf5);
// make a selection but turn off the Selection checkbox;
// the search should go outside the selection
makeSelection(tool, program, range(0x01002cf5, 0x01002d0d), range(0x01002d37, 0x01002d47));
// select Search All option to turn off searching only in selection
assertButtonState("Search All", true, false);
// Note: this is 'Search All' for the search type, not the JButton on the button panel
pressButtonByText(pane, "Search All");
setValueText("11110110");
pressButtonByText(pane, "Next");
waitForSearchTask();
Address resultAddr = addr(0x1002d27);
// verify the code browser goes to resulting address
CodeUnit cu = codeUnitContaining(resultAddr);
assertEquals(cu.getMinAddress(), currentAddress());
assertEquals("Found", statusLabel.getText());
}
@Test
public void testBinarySearchAll() throws Exception {
// QueryResults should get displayed
// test the marker stuff
setValueText("11110110");
pressSearchAllButton();
waitForSearch("Search Memory - ", 1);
checkMarkerSet(addrs(0x1002d28));
}
@Test
public void testBinarySearchAll2() throws Exception {
// enter search string for multiple byte match
// ff d6
setValueText("11111111 11010110");
pressSearchAllButton();
waitForSearch("Search Memory - ", 2);
List<Address> addrs = addrs(0x1002d09, 0x1002d14);
checkMarkerSet(addrs);
}
@Test
public void testBinarySearchAllAlign4() throws Exception {
// QueryResults should get displayed
// test the marker stuff
setValueText("11111111 01110101");
setAlignment("4");
pressSearchAllButton();
waitForSearch("Search Memory - ", 2);
List<Address> startList = addrs(0x1002d2c, 0x1002d58);
checkMarkerSet(startList);
}
@Test
public void testBinaryHighlight() throws Exception {
setValueText("00010000 00000000 00000001");
pressSearchAllButton();
waitForSearch("Search Memory - ", 3);
Highlight[] h = getByteHighlights(addr(0x1002cfd), "8b 35 e0 10 00 01");
assertEquals(1, h.length);
assertEquals(9, h[0].getStart());
assertEquals(16, h[0].getEnd());
}
@Test
public void testBinarySearchSelection() throws Exception {
goTo(0x01001074);
makeSelection(tool, program, range(0x01002cf5, 0x01002d6d));
assertSearchSelectionSelected();
setValueText("11110110");
performSearchTest(addrs(0x01002d27), "Next");
}
@Test
public void testBinarySearchPreviousNotFound() throws Exception {
goTo(0x01001000);
setValueText("00000111");
pressButtonByText(pane, "Previous");
waitForSearchTask();
assertEquals("Not Found", statusLabel.getText());
}
@Test
public void testCodeUnitScope_Instructions() throws Exception {
//
// Turn on Instructions scope and make sure only that scope yields matches
//
goTo(0x1002cf5);
selectCheckBox("Instructions", true);
selectCheckBox("Defined Data", false);
selectCheckBox("Undefined Data", false);
setValueText("01010101");
pressSearchButton("Next");
Address expectedSearchAddressHit = addr(0x1002cf5);
assertEquals(
"Did not find a hit at the next matching Instruction when we are searching Instructions",
expectedSearchAddressHit, currentAddress());
// Turn off Instructions scope and make sure we have no match at the expected address
goTo(0x1002cf5);
selectCheckBox("Instructions", false);
selectCheckBox("Defined Data", true);
selectCheckBox("Undefined Data", true);
pressSearchButton("Next");
assertTrue(
"Found a search match at an Instruction, even though no Instruction should be searched",
!expectedSearchAddressHit.equals(currentAddress()));
CodeUnit codeUnit = currentCodeUnit();
assertTrue("Did not find a data match when searching instructions is disabled",
codeUnit instanceof Data);
}
@Test
public void testCodeUnitScope_DefinedData() throws Exception {
//
// Turn on Defined Data scope and make sure only that scope yields matches
//
goTo(0x1001000);// start of program; pointer data
selectCheckBox("Instructions", false);
selectCheckBox("Defined Data", true);
selectCheckBox("Undefined Data", false);
setValueText("10000101");
pressSearchButton("Next");
Address expectedSearchAddressHit = addr(0x1001004);
assertEquals(
"Did not find a hit at the next matching Defined Data when we are searching Defined Data",
expectedSearchAddressHit, currentAddress());
// Turn off Defined Data scope and make sure we have no match at the expected address
goTo(0x1001000);// start of program; pointer data
selectCheckBox("Instructions", true);
selectCheckBox("Defined Data", false);
selectCheckBox("Undefined Data", true);
pressSearchButton("Next");
assertTrue(
"Found a search match at a Defined Data, even though no Defined Data should be searched",
!expectedSearchAddressHit.equals(currentAddress()));
CodeUnit codeUnit = currentCodeUnit();
assertTrue("Did not find a instruction match when searching defined data is disabled",
codeUnit instanceof Instruction);
// try backwards
goTo(0x1002000);
assertEquals(
"Did not find a hit at the next matching Defined Data when we are searching Defined Data",
addr(0x1002000), currentAddress());
selectCheckBox("Instructions", false);
selectCheckBox("Defined Data", true);
selectCheckBox("Undefined Data", false);
pressSearchButton("Previous");
expectedSearchAddressHit = addr(0x01001004);
assertEquals(
"Did not find a hit at the previous matching Defined Data when we are searching Defined Data",
expectedSearchAddressHit, currentAddress());
}
@Test
public void testCodeUnitScope_UndefinedData() throws Exception {
//
// Turn on Undefined Data scope and make sure only that scope yields matches
//
goTo(0x1001000);
selectCheckBox("Instructions", false);
selectCheckBox("Defined Data", false);
selectCheckBox("Undefined Data", true);
setValueText("01100101");
pressSearchButton("Next");
Address expectedSearchAddressHit = addr(0x1001502);
assertEquals(
"Did not find a hit at the next matching Undefined Data when we are searching Undefined Data",
expectedSearchAddressHit, currentAddress());
// Turn off Undefined Data scope and make sure we have no match at the expected address
goTo(0x1001000);
selectCheckBox("Instructions", true);
selectCheckBox("Defined Data", true);
selectCheckBox("Undefined Data", false);
pressSearchButton("Next");
assertTrue(
"Found a search match at an Undefined Data, even though no Undefined Data should be searched",
!expectedSearchAddressHit.equals(currentAddress()));
CodeUnit codeUnit = listing.getCodeUnitAt(cb.getCurrentLocation().getAddress());
assertTrue("Did not find a instruction match when searching defined data is disabled",
codeUnit instanceof Data);
// try backwards
goTo(0x1003000);
selectCheckBox("Instructions", false);
selectCheckBox("Defined Data", false);
selectCheckBox("Undefined Data", true);
pressSearchButton("Previous");
expectedSearchAddressHit = addr(0x1001502);
assertEquals(
"Did not find a hit at the previous matching Undefined Data when we are searching Undefined Data",
expectedSearchAddressHit, currentAddress());
}
@Test
public void testBinarySearchPrevious() throws Exception {
// enter search string for multiple byte match
// ff 15
// start at 01002d6b
goTo(0x01002d6b);
setValueText("11111111 00010101");
List<Address> addrs = addrs(0x01002d5e, 0x01002d4a, 0x01002d41, 0x01002d1f);
performSearchTest(addrs, "Previous");
}
@Test
public void testBinarySearchPreviousAlign4() throws Exception {
// enter search string for multiple byte match
// ff 15
goTo(0x1002d6d);
setValueText("11111111 01110101");
setAlignment("4");
List<Address> addrs = addrs(0x1002d58, 0x1002d2c);
performSearchTest(addrs, "Previous");
}
@Test
public void testBinaryWildcardSearch() throws Exception {
goTo(0x01001000);
setValueText("010101xx 10001011");
List<Address> addrs = addrs(0x01002cf5, 0x01002cfc, 0x01002d47);
performSearchTest(addrs, "Next");
}
@Test
public void testBinaryWildcardSearchAll() throws Exception {
setValueText("10001011 1111xxxx");
pressSearchAllButton();
waitForSearch("Search Memory - ", 4);
List<Address> addrs = addrs(0x1002d0b, 0x1002d25, 0x1002d48, 0x1002d64);
checkMarkerSet(addrs);
}
@SuppressWarnings("rawtypes")
@Test
public void testValueComboBox() throws Exception {
setValueText("1x1xx1x1");
pressSearchButton("Next");
setValueText("");
setValueText("00000");
pressSearchButton("Next");
setValueText("");
setValueText("111");
pressSearchButton("Next");
setValueText("");
// the combo box should list most recently entered values
DefaultComboBoxModel cbModel = (DefaultComboBoxModel) valueComboBox.getModel();
assertEquals(3, cbModel.getSize());
assertEquals("111", cbModel.getElementAt(0));
assertEquals("00000", cbModel.getElementAt(1));
assertEquals("1x1xx1x1", cbModel.getElementAt(2));
}
}

View File

@ -1,668 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.searchmem;
import static org.junit.Assert.*;
import java.awt.Container;
import java.nio.charset.StandardCharsets;
import java.util.List;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import org.junit.Before;
import org.junit.Test;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Pointer32DataType;
import ghidra.program.model.listing.Program;
/**
* Tests for searching for decimal values in memory.
*/
public class MemSearchDecimal2Test extends AbstractMemSearchTest {
public MemSearchDecimal2Test() {
super();
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
selectRadioButton("Decimal");
}
@Override
protected Program buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86);
builder.createMemory(".text", Long.toHexString(0x1001000), 0x6600);
builder.createMemory(".data", Long.toHexString(0x1008000), 0x600);
builder.createMemory(".rsrc", Long.toHexString(0x100A000), 0x5400);
builder.createMemory(".bound_import_table", Long.toHexString(0xF0000248), 0xA8);
builder.createMemory(".debug_data", Long.toHexString(0xF0001300), 0x1C);
//create and disassemble a function
builder.setBytes(
"0x01002cf5",
"55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 " +
"33 ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 " +
"74 27 56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 " +
"04 12 00 01 56 8b f8 ff 15 c0 10 00 01 eb 14 ff 75 18 ff 75 0c ff 75 10 ff 75 " +
"08 ff 15 04 12 00 01 8b f8 8b c7 5f 5e 5d c2 14");
builder.disassemble("0x01002cf5", 0x121, true);
builder.createFunction("0x01002cf5");
//create some data
builder.setBytes("0x1001004", "85 4f dc 77");
builder.applyDataType("0x1001004", new Pointer32DataType(), 1);
builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true);
builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, true);
builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false);
//create some undefined data
builder.setBytes("0x1001500", "4e 00 65 00 77 00");
builder.setBytes("0x1003000", "55 00");
builder.setBytes("0x1004100", "64 00 00 00");//100 dec
builder.setBytes("0x1004120", "50 ff 75 08");//7.4027124e-34 float
builder.setBytes("0x1004135", "64 00 00 00");//100 dec
builder.setBytes("0x1004200", "50 ff 75 08 e8 8d 3c 00");//1.588386874245921e-307
builder.setBytes("0x1004247", "50 ff 75 08");//7.4027124e-34 float
builder.setBytes("0x1004270", "65 00 6e 00 64 00 69 00");//29555302058557541 qword
return builder.getProgram();
}
@Test
public void testDecimalOptionsShowing() throws Exception {
// select the Decimal option; verify radio buttons for decimal types
// are showing in the Decimal Options panel.
JRadioButton rb = (JRadioButton) findAbstractButtonByText(pane, "Byte");
assertNotNull(rb);
JPanel p = findTitledJPanel(rb, "Format Options");
assertNotNull(p);
assertTrue(p.isVisible());
assertTrue(!rb.isSelected());
assertTrue(rb.isVisible());
rb = (JRadioButton) findAbstractButtonByText(pane, "Word");
assertNotNull(rb);
assertTrue(rb.isSelected());
assertTrue(rb.isVisible());
rb = (JRadioButton) findAbstractButtonByText(pane, "DWord");
assertNotNull(rb);
assertTrue(!rb.isSelected());
assertTrue(rb.isVisible());
rb = (JRadioButton) findAbstractButtonByText(pane, "QWord");
assertNotNull(rb);
assertTrue(!rb.isSelected());
assertTrue(rb.isVisible());
rb = (JRadioButton) findAbstractButtonByText(pane, "Float");
assertNotNull(rb);
assertTrue(!rb.isSelected());
assertTrue(rb.isVisible());
rb = (JRadioButton) findAbstractButtonByText(pane, "Double");
assertNotNull(rb);
assertTrue(!rb.isSelected());
assertTrue(rb.isVisible());
}
@Test
public void testInvalidEntry() throws Exception {
// enter non-numeric value
setValueText("z");
assertEquals("", valueField.getText());
assertEquals("", hexLabel.getText());
}
@Test
public void testValueTooLarge() throws Exception {
// select "Byte" and enter 260; should not accept "0"
selectRadioButton("Byte");
myTypeText("260");
assertEquals("26", valueField.getText());
assertEquals(statusLabel.getText(), "Number must be in the range [-128,255]");
}
@Test
public void testValueTooLarge2() throws Exception {
// select "Word" and enter 2698990; should not accept "26989"
selectRadioButton("Word");
myTypeText("2698990");
assertEquals(statusLabel.getText(), "Number must be in the range [-32768,65535]");
assertEquals("26989", valueField.getText());
}
@Test
public void testNegativeValueEntered() throws Exception {
// enter a negative value; the hexLabel should show the correct
// byte sequence
setValueText("-1234");
assertEquals("2e fb ", hexLabel.getText());
selectRadioButton("Byte");
assertEquals("", valueField.getText());
assertEquals("", hexLabel.getText());
setValueText("-55");
assertEquals("c9 ", hexLabel.getText());
selectRadioButton("DWord");
assertEquals("c9 ff ff ff ", hexLabel.getText());
selectRadioButton("QWord");
assertEquals("c9 ff ff ff ff ff ff ff ", hexLabel.getText());
selectRadioButton("Float");
assertEquals("00 00 5c c2 ", hexLabel.getText());
selectRadioButton("Double");
assertEquals("00 00 00 00 00 80 4b c0 ", hexLabel.getText());
}
@Test
public void testMulipleValuesEntered() throws Exception {
// enter values separated by a space; values should be accepted
selectRadioButton("Byte");
setValueText("12 34 56 78");
assertEquals("0c 22 38 4e ", hexLabel.getText());
selectRadioButton("Word");
assertEquals("0c 00 22 00 38 00 4e 00 ", hexLabel.getText());
selectRadioButton("DWord");
assertEquals("0c 00 00 00 22 00 00 00 38 00 00 00 4e 00 00 00 ", hexLabel.getText());
selectRadioButton("QWord");
assertEquals("0c 00 00 00 00 00 00 00 22 00 00 00 00 00 00 00 "
+ "38 00 00 00 00 00 00 00 4e 00 00 00 00 00 00 00 ", hexLabel.getText());
selectRadioButton("Float");
assertEquals("00 00 40 41 00 00 08 42 00 00 60 42 00 00 9c 42 ", hexLabel.getText());
selectRadioButton("Double");
assertEquals("00 00 00 00 00 00 28 40 00 00 00 00 00 00 41 40 "
+ "00 00 00 00 00 00 4c 40 00 00 00 00 00 80 53 40 ", hexLabel.getText());
}
@Test
public void testByteOrder() throws Exception {
setValueText("12 34 56 78");
selectRadioButton("Byte");
selectRadioButton("Big Endian");
// should be unaffected
assertEquals("0c 22 38 4e ", hexLabel.getText());
selectRadioButton("Word");
assertEquals("00 0c 00 22 00 38 00 4e ", hexLabel.getText());
selectRadioButton("DWord");
assertEquals("00 00 00 0c 00 00 00 22 00 00 00 38 00 00 00 4e ", hexLabel.getText());
selectRadioButton("QWord");
assertEquals("00 00 00 00 00 00 00 0c 00 00 00 00 00 00 00 22 "
+ "00 00 00 00 00 00 00 38 00 00 00 00 00 00 00 4e ", hexLabel.getText());
selectRadioButton("Float");
assertEquals("41 40 00 00 42 08 00 00 42 60 00 00 42 9c 00 00 ", hexLabel.getText());
selectRadioButton("Double");
assertEquals("40 28 00 00 00 00 00 00 40 41 00 00 00 00 00 00 "
+ "40 4c 00 00 00 00 00 00 40 53 80 00 00 00 00 00 ", hexLabel.getText());
}
@Test
public void testFloatDoubleFormat() throws Exception {
selectRadioButton("Float");
setValueText("12.345");
assertEquals("12.345", valueField.getText());
assertEquals("1f 85 45 41 ", hexLabel.getText());
selectRadioButton("Double");
assertEquals("71 3d 0a d7 a3 b0 28 40 ", hexLabel.getText());
}
@Test
public void testSearchByte() throws Exception {
goTo(program.getMinAddress());
List<Address> addrs = addrs(0x1002d3e, 0x1002d5b, 0x1004123, 0x1004203, 0x100424a);
selectRadioButton("Byte");
setValueText("8");
performSearchTest(addrs, "Next");
}
@Test
public void testSearchWord() throws Exception {
goTo(program.getMinAddress());
selectRadioButton("Word");
setValueText("20");
List<Address> addrs = addrs(0x1002cf8, 0x1002d6b);
performSearchTest(addrs, "Next");
}
@Test
public void testSearchWordBackward() throws Exception {
goTo(0x01002d6e);
selectRadioButton("Word");
setValueText("20");
List<Address> addrs = addrs(0x1002d6b, 0x1002cf8);
performSearchTest(addrs, "Previous");
}
@Test
public void testSearchDWord() throws Exception {
goTo(program.getMinAddress());
selectRadioButton("DWord");
setValueText("100");
List<Address> addrs = addrs(0x1001708, 0x1004100, 0x1004135);
performSearchTest(addrs, "Next");
}
@Test
public void testSearchDWordBackward() throws Exception {
goTo(0x01005000);
selectRadioButton("DWord");
setValueText("100");
List<Address> addrs = addrs(0x1004135, 0x1004100, 0x1001708);
performSearchTest(addrs, "Previous");
}
@Test
public void testSearchQWord() throws Exception {
goTo(program.getMinAddress());
selectRadioButton("QWord");
setValueText("29555302058557541");
performSearchTest(addrs(0x1004270), "Next");
}
@Test
public void testSearchQWordBackward() throws Exception {
goTo(program.getMaxAddress());
selectRadioButton("QWord");
setValueText("29555302058557541");
performSearchTest(addrs(0x1004270), "Previous");
}
@Test
public void testSearchFloat() throws Exception {
goTo(program.getMinAddress());
selectRadioButton("Float");
setValueText("7.4027124e-34");
List<Address> addrs = addrs(0x1004120, 0x1004200, 0x1004247);
performSearchTest(addrs, "Next");
}
@Test
public void testSearchFloatBackward() throws Exception {
goTo(0x01005000);
selectRadioButton("Float");
setValueText("7.4027124e-34");
List<Address> addrs = addrs(0x1004247, 0x1004200, 0x1004120);
performSearchTest(addrs, "Previous");
}
@Test
public void testSearchFloatBackwardAlign8() throws Exception {
goTo(program.getMaxAddress());
JTextField alignment = (JTextField) findComponentByName(dialog.getComponent(), "Alignment");
setText(alignment, "8");
selectRadioButton("Float");
setValueText("7.4027124e-34");
List<Address> addrs = addrs(0x1004200, 0x1004120);
performSearchTest(addrs, "Previous");
}
@Test
public void testSearchDouble() throws Exception {
goTo(program.getMinAddress());
selectRadioButton("Double");
setValueText("1.588386874245921e-307");
List<Address> addrs = addrs(0x1004200);
performSearchTest(addrs, "Next");
}
@Test
public void testSearchDoubleBackward() throws Exception {
goTo(program.getMaxAddress());
selectRadioButton("Double");
setValueText("1.588386874245921e-307");
List<Address> addrs = addrs(0x1004200);
performSearchTest(addrs, "Previous");
}
@Test
public void testSearchAllByte() throws Exception {
selectRadioButton("Byte");
setValueText("8");
pressSearchAllButton();
waitForSearch("Search Memory - ", 5);
List<Address> addrs = addrs(0x1002d40, 0x1002d5d, 0x1004123, 0x1004203, 0x100424a);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllWord() throws Exception {
selectRadioButton("Word");
setValueText("20");
pressSearchAllButton();
waitForSearch("Search Memory - ", 2);
List<Address> addrs = addrs(0x1002cfa, 0x1002d6c);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllWordAlign4() throws Exception {
JTextField alignment = (JTextField) findComponentByName(dialog.getComponent(), "Alignment");
setText(alignment, "4");
selectRadioButton("Word");
setValueText("20");
pressSearchAllButton();
waitForSearch("Search Memory - ", 1);
checkMarkerSet(addrs(0x1002d6c));
}
@Test
public void testSearchAllDWord() throws Exception {
selectRadioButton("DWord");
setValueText("100");
pressSearchAllButton();
waitForSearch("Search Memory - ", 3);
List<Address> addrs = addrs(0x1001715, 0x1004100, 0x1004135);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllQWord() throws Exception {
selectRadioButton("QWord");
setValueText("29555302058557541");
pressSearchAllButton();
waitForSearch("Search Memory - ", 1);
checkMarkerSet(addrs(0x1004270));
}
@Test
public void testSearchAllFloat() throws Exception {
selectRadioButton("Float");
setValueText("7.4027124e-34");
pressSearchAllButton();
waitForSearch("Search Memory - ", 3);
List<Address> addrs = addrs(0x1004120, 0x1004200, 0x1004247);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllDouble() throws Exception {
selectRadioButton("Double");
setValueText("1.588386874245921e-307");
pressSearchAllButton();
waitForSearch("Search Memory - ", 1);
checkMarkerSet(addrs(0x1004200));
}
@Test
public void testSearchSelectionByte() throws Exception {
makeSelection(tool, program, range(0x01004000, 0x01005000));
assertSearchSelectionSelected();
selectRadioButton("Byte");
setValueText("8");
List<Address> addrs = addrs(0x1004123, 0x1004203, 0x100424a);
performSearchTest(addrs, "Next");
}
@Test
public void testSearchSelectionWord() throws Exception {
makeSelection(tool, program, range(0x01002c00, 0x01002d00));
assertSearchSelectionSelected();
selectRadioButton("Word");
setValueText("20");
performSearchTest(addrs(0x1002cf8), "Next");
}
@Test
public void testSearchSelectionDWord() throws Exception {
makeSelection(tool, program, range(0x01004000, 0x01005000));
assertSearchSelectionSelected();
selectRadioButton("DWord");
setValueText("100");
List<Address> addrs = addrs(0x1004100, 0x1004135);
performSearchTest(addrs, "Next");
}
@Test
public void testSearchSelectionQWord() throws Exception {
makeSelection(tool, program, range(0x01004000, 0x01005000));
assertSearchSelectionSelected();
selectRadioButton("QWord");
setValueText("29555302058557541");
performSearchTest(addrs(0x1004270), "Next");
}
@Test
public void testSearchSelectionFloat() throws Exception {
makeSelection(tool, program, range(0x01004200, 0x01004300));
assertSearchSelectionSelected();
selectRadioButton("Float");
setValueText("7.4027124e-34");
List<Address> addrs = addrs(0x1004200, 0x1004247);
performSearchTest(addrs, "Next");
}
@Test
public void testSearchSelectionDouble() throws Exception {
makeSelection(tool, program, range(0x01004000, 0x01005000));
assertSearchSelectionSelected();
selectRadioButton("Double");
setValueText("1.588386874245921e-307");
performSearchTest(addrs(0x1004200), "Next");
}
@Test
public void testSearchAllInSelection() throws Exception {
makeSelection(tool, program, range(0x01002cf5, 0x01002d6d));
assertSearchSelectionSelected();
selectRadioButton("Byte");
setValueText("8");
pressSearchAllButton();
waitForSearch("Search Memory - ", 2);
List<Address> addrs = addrs(0x1002d40, 0x1002d5d);
checkMarkerSet(addrs);
}
@Test
public void testSearchBackwardsInSelection() throws Exception {
goTo(program.getMaxAddress());
makeSelection(tool, program, range(0x01004000, 0x01005000));
assertSearchSelectionSelected();
selectRadioButton("Double");
setValueText("1.588386874245921e-307");
performSearchTest(addrs(0x1004200), "Previous");
}
//==================================================================================================
// Private Methods
//==================================================================================================
@Override
protected void showMemSearchDialog() {
super.showMemSearchDialog();
selectRadioButton("Decimal");
}
private JPanel findTitledJPanel(Container container, String title) {
if (container instanceof JPanel) {
JPanel p = (JPanel) container;
Border b = p.getBorder();
if ((b instanceof TitledBorder) && ((TitledBorder) b).getTitle().equals(title)) {
return p;
}
}
Container parent = container.getParent();
while (parent != null) {
if (parent instanceof JPanel) {
JPanel p = findTitledJPanel(parent, title);
if (p != null) {
return p;
}
}
parent = parent.getParent();
}
return null;
}
}

View File

@ -0,0 +1,289 @@
/* ###
* 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.features.base.memsearch;
import static org.junit.Assert.*;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.collections4.IteratorUtils;
import docking.action.DockingActionIf;
import docking.widgets.fieldpanel.support.Highlight;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.services.*;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.field.BytesFieldFactory;
import ghidra.app.util.viewer.field.ListingField;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.features.base.memsearch.gui.*;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.test.AbstractProgramBasedTest;
import ghidra.util.Swing;
/**
* Base class for memory search tests.
*/
public abstract class AbstractMemSearchTest extends AbstractProgramBasedTest {
protected MemorySearchPlugin memorySearchPlugin;
protected DockingActionIf searchAction;
protected CodeViewerProvider provider;
protected Memory memory;
protected Listing listing;
protected MarkerService markerService;
protected MemorySearchProvider searchProvider;
private SearchSettings settings = new SearchSettings();
/*
* Note that this setup function does not have the @Before annotation - this is because
* sub-classes often need to override this and if we have the annotation here, the test
* runner will only invoke this base class implementation.
*/
public void setUp() throws Exception {
// this builds the program and launches the tool
initialize();
memorySearchPlugin = env.getPlugin(MemorySearchPlugin.class);
listing = program.getListing();
memory = program.getMemory();
searchAction = getAction(memorySearchPlugin, "Memory Search");
provider = codeBrowser.getProvider();
markerService = tool.getService(MarkerService.class);
showMemorySearchProvider();
}
protected void setInput(String input) {
Swing.runNow(() -> searchProvider.setSearchInput(input));
}
@Override
protected Program getProgram() throws Exception {
return buildProgram();
}
protected abstract Program buildProgram() throws Exception;
protected void waitForSearch(int expectedResults) {
waitForCondition(() -> {
return runSwing(() -> !searchProvider.isBusy());
}, "Timed-out waiting for search results");
assertEquals(expectedResults, searchProvider.getSearchResults().size());
}
protected void waitForSearchTask() {
waitForSwing();
waitForTasks();
waitForSwing();
}
protected void showMemorySearchProvider() {
performAction(searchAction, provider, true);
searchProvider = waitForComponentProvider(MemorySearchProvider.class);
}
@SuppressWarnings("unchecked")
private List<Address> getHighlightAddresses() {
CodeViewerService service = tool.getService(CodeViewerService.class);
Object codeViewerProvider = getInstanceField("connectedProvider", service);
Map<Program, ListingHighlightProvider> highlighterMap =
(Map<Program, ListingHighlightProvider>) getInstanceField("programHighlighterMap",
codeViewerProvider);
ListingHighlightProvider highlightProvider = highlighterMap.get(program);
assertEquals("The inner-class has been renamed", "MemoryMatchHighlighter",
highlightProvider.getClass().getSimpleName());
List<MemoryMatch> data = searchProvider.getSearchResults();
return data.stream().map(result -> result.getAddress()).collect(Collectors.toList());
}
protected void checkMarkerSet(List<Address> expected) {
List<Address> highlights = getHighlightAddresses();
assertListEqualUnordered("Search highlights not correctly generated", expected, highlights);
MarkerSet markers =
runSwing(() -> markerService.getMarkerSet(searchProvider.getTitle(), program));
assertNotNull(markers);
AddressSet addressSet = runSwing(() -> markers.getAddressSet());
AddressIterator it = addressSet.getAddresses(true);
List<Address> list = IteratorUtils.toList(it);
assertListEqualUnordered("Search markers not correctly generated", expected, list);
}
protected void performSearchNext(Address expected) throws Exception {
DockingActionIf action = getAction(tool, "MemorySearchPlugin", "Search Next");
performAction(action);
waitForSearchTask();
codeBrowser.updateNow();
assertEquals(expected, codeBrowser.getCurrentAddress());
}
protected void performSearchNext(List<Address> expected) throws Exception {
DockingActionIf action = getAction(tool, "MemorySearchPlugin", "Search Next");
performSearchNextPrevious(expected, action);
}
protected void performSearchPrevious(List<Address> expected) throws Exception {
DockingActionIf action = getAction(tool, "MemorySearchPlugin", "Search Previous");
performSearchNextPrevious(expected, action);
}
protected void performSearchNextPrevious(List<Address> expected, DockingActionIf action)
throws Exception {
for (Address addr : expected) {
performAction(action);
waitForSearchTask();
codeBrowser.updateNow();
assertEquals(addr, codeBrowser.getCurrentAddress());
}
Address addr = codeBrowser.getCurrentAddress();
performAction(action);
waitForSearchTask();
codeBrowser.updateNow();
assertEquals(addr, codeBrowser.getCurrentAddress());
}
protected void performSearchAll() {
runSwing(() -> searchProvider.search());
}
protected ListingHighlightProvider getHighlightProvider() {
CodeViewerService service = tool.getService(CodeViewerService.class);
FormatManager fm = (FormatManager) getInstanceField("formatMgr", service);
return (ListingHighlightProvider) getInstanceField("highlightProvider", fm);
}
protected void repeatSearchForward() {
DockingActionIf action = getAction(memorySearchPlugin, "Repeat Memory Search Forwards");
assertTrue(action.isEnabled());
performAction(action, provider, true);
waitForSearchTask();
}
protected void repeatSearchBackward() {
DockingActionIf action = getAction(memorySearchPlugin, "Repeat Memory Search Backwards");
assertTrue(action.isEnabled());
performAction(action, provider, true);
waitForSearchTask();
}
protected Address currentAddress() {
codeBrowser.updateNow();
Address addr = codeBrowser.getCurrentLocation().getAddress();
return addr;
}
protected CodeUnit currentCodeUnit() {
CodeUnit cu = program.getListing().getCodeUnitContaining(currentAddress());
return cu;
}
protected CodeUnit codeUnitContaining(Address addr) {
CodeUnit cu = program.getListing().getCodeUnitContaining(addr);
return cu;
}
protected void assertSearchSelectionSelected() {
waitForSwing();
assertTrue(Swing.runNow(() -> searchProvider.isSearchSelection()));
}
protected Highlight[] getByteHighlights(Address address, String bytes) {
goTo(address);
ListingHighlightProvider hlProvider = getHighlightProvider();
ListingField field = getField(address, BytesFieldFactory.FIELD_NAME);
return hlProvider.createHighlights(bytes, field, -1);
}
protected String getInput() {
return Swing.runNow(() -> searchProvider.getSearchInput());
}
protected String getByteString() {
return Swing.runNow(() -> searchProvider.getByteString());
}
protected void setSearchFormat(SearchFormat format) {
settings = settings.withSearchFormat(format);
runSwing(() -> searchProvider.setSettings(settings));
}
protected void setDecimalSize(int size) {
settings = settings.withDecimalByteSize(size);
runSwing(() -> searchProvider.setSettings(settings));
}
protected void setAlignment(int alignment) {
settings = settings.withAlignment(alignment);
runSwing(() -> searchProvider.setSettings(settings));
}
protected void setSearchSelectionOnly(boolean b) {
runSwing(() -> searchProvider.setSearchSelectionOnly(b));
}
protected void setBigEndian(boolean b) {
settings = settings.withBigEndian(b);
runSwing(() -> searchProvider.setSettings(settings));
}
protected void setCaseSensitive(boolean b) {
settings = settings.withCaseSensitive(b);
runSwing(() -> searchProvider.setSettings(settings));
}
protected void setCharset(Charset charset) {
settings = settings.withStringCharset(charset);
runSwing(() -> searchProvider.setSettings(settings));
}
protected void setEscapeSequences(boolean b) {
settings = settings.withUseEscapeSequence(b);
runSwing(() -> searchProvider.setSettings(settings));
}
protected void addSearchRegion(SearchRegion region, boolean b) {
settings = settings.withSelectedRegion(region, b);
runSwing(() -> searchProvider.setSettings(settings));
}
protected void setCodeTypeFilters(boolean instructions, boolean data, boolean undefinedData) {
settings = settings.withIncludeInstructions(instructions);
settings = settings.withIncludeDefinedData(data);
settings = settings.withIncludeUndefinedData(undefinedData);
runSwing(() -> searchProvider.setSettings(settings));
}
}

View File

@ -0,0 +1,342 @@
/* ###
* 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.features.base.memsearch;
import static org.junit.Assert.*;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.test.ToyProgramBuilder;
/**
* Tests for searching memory for ascii.
*/
public class MemSearchAsciiTest extends AbstractMemSearchTest {
@Before
@Override
public void setUp() throws Exception {
super.setUp();
setSearchFormat(SearchFormat.STRING);
}
@Override
protected Program buildProgram() throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder("Test", false, ProgramBuilder._TOY);
builder.createMemory(".text", "0x1001000", 0x6600);
builder.createMemory(".data", "0x1008000", 0x600);
builder.createMemory(".rsrc", "0x100a000", 0x5400);
//create some strings
builder.createEncodedString("0x010016ec", "something", StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16LE,
true);
builder.createEncodedString("0x01001840", "\u039d\u03bf\u03c4\u03b5\u03c0\u03b1\u03bd",
StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x0100186a",
"\u03c1\u03b8\u03c4\u03b5\u03c0\u03b1\u03bd\u03c2\u03b2", StandardCharsets.UTF_16LE,
true);
builder.createEncodedString("0x0100196a",
"\u03c1\u03b8\u03c4\u03b5\u03c0\u03b1\u03bd\u03c2\u03b2", StandardCharsets.UTF_8, true);
builder.createEncodedString("0x0100189d", "\"Hello world!\"\n\t-new programmer",
StandardCharsets.US_ASCII, true);
builder.createEncodedString("0x0100198e", "\"Hello world!\"\n\t-new programmer",
StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false);
builder.createEncodedString("0x010013e0", "notepad.exe", StandardCharsets.US_ASCII, false);
builder.createEncodedString("0x1006c6a", "GetLocaleInfoW", StandardCharsets.US_ASCII,
false);
builder.createEncodedString("0x1006f26", "GetCPInfo", StandardCharsets.US_ASCII, false);
builder.createEncodedString("0x0100dde0", "NOTEPAD.EXE", StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x0100eb90",
"This string contains notepad twice. Here is the second NotePad.",
StandardCharsets.UTF_16LE, true);
builder.createEncodedString("0x0100ed00", "Another string", StandardCharsets.UTF_16LE,
true);
return builder.getProgram();
}
@Test
public void testCaseSensitiveOff() throws Exception {
setCaseSensitive(false);
setInput("notepad");
List<Address> addrs = addrs(0x010013cc, 0x010013e0);
performSearchNext(addrs);
}
@Test
public void testCaseSensitiveOn() throws Exception {
setCaseSensitive(true);
setInput("NOTEpad");
performSearchNext(Collections.emptyList());
}
@Test
public void testUnicodeNotCaseSensitive() throws Exception {
setCaseSensitive(false);
setCharset(StandardCharsets.UTF_16);
setInput("NOTEpad");
List<Address> addrs = addrs(0x01001708, 0x0100dde0, 0x0100eb90, 0x0100eb90); // this code unit contains two notepads in one string
performSearchNext(addrs);
}
@Test
public void testGreekUnicodeSearch() throws Exception {
setCaseSensitive(false);
setCharset(StandardCharsets.UTF_16);
setInput("\u03c4\u03b5\u03c0\u03b1\u03bd");
List<Address> addrs = addrs(0x01001840, 0x0100186a);
performSearchNext(addrs);
setCharset(StandardCharsets.UTF_8);
addrs = addrs(0x0100196a);
performSearchNext(addrs);
}
@Test
public void testRepeatUnicodeNotCaseSensitive() throws Exception {
setCaseSensitive(false);
setCharset(StandardCharsets.UTF_16);
setInput("NOTEpad");
performSearchNext(addr(0x01001708));
// this code unit contains two notepads in one string
List<Address> addrs = addrs(0x0100dde0, 0x0100eb90, 0x0100eb90);
for (Address address : addrs) {
repeatSearchForward();
assertEquals(address, codeBrowser.getCurrentLocation().getAddress());
}
repeatSearchForward();
assertEquals(addrs.get(2), codeBrowser.getCurrentLocation().getAddress());
}
@Test
public void testUnicodeCaseSensitive() throws Exception {
setCaseSensitive(true);
setCharset(StandardCharsets.UTF_16);
setInput("Notepad");
performSearchNext(addrs(0x01001708));
}
@Test
public void testSearchAllUnicodeNotCaseSensitive() throws Exception {
setCaseSensitive(false);
setCharset(StandardCharsets.UTF_16);
setInput("NOTEpad");
performSearchAll();
waitForSearch(4);
List<Address> addrs = addrs(0x01001708, 0x0100dde0, 0x0100ebba, 0x0100ebfe);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllUnicodeCaseSensitive() throws Exception {
setCaseSensitive(true);
setCharset(StandardCharsets.UTF_16);
setInput("Notepad");
performSearchAll();
waitForSearch(1);
checkMarkerSet(addrs(0x01001708));
}
@Test
public void testSearchAllNotCaseSensitive() throws Exception {
setCaseSensitive(false);
setInput("NOTEpad");
performSearchAll();
waitForSearch(2);
List<Address> addrs = addrs(0x010013cc, 0x010013e0);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllCaseSensitive() throws Exception {
byte[] b = new byte[] { 'N', 'O', 'T', 'E', 'p', 'a', 'd' };
int transactionID = program.startTransaction("test");
memory.setBytes(addr(0x0100b451), b);
program.endTransaction(transactionID, true);
setCaseSensitive(true);
setInput("NOTEpad");
performSearchAll();
waitForSearch(1);
checkMarkerSet(addrs(0x0100b451));
}
@Test
public void testSearchAllCaseSensitiveAlign8() throws Exception {
setAlignment(8);
setCaseSensitive(true);
setInput("notepad");
performSearchAll();
waitForSearch(1);
checkMarkerSet(addrs(0x010013e0));
}
@Test
public void testSearchSelection() throws Exception {
makeSelection(tool, program, addr(0x01006c73), addr(0x01006f02));
assertSearchSelectionSelected();
setCaseSensitive(false);
setInput("Info");
performSearchNext(addrs(0x01006c6a));
}
@Test
public void testSearchNonContiguousSelection() throws Exception {
makeSelection(tool, program, range(0x01006c70, 0x01006c80), range(0x01006f2b, 0x01006f37));
assertSearchSelectionSelected();
setCaseSensitive(false);
setInput("Info");
List<Address> addrs = addrs(0x01006c6a, 0x01006f26);
performSearchNext(addrs);
}
@Test
public void testSearchBackward() throws Exception {
goTo(tool, program, addr(0x1006f56));
setCaseSensitive(true);
setInput("Info");
List<Address> addrs = addrs(0x01006f26, 0x01006c6a);
performSearchPrevious(addrs);
}
@Test
public void testSearchBackwardInSelection() throws Exception {
goTo(tool, program, addr(0x01006f02));
makeSelection(tool, program, addr(0x01006c73), addr(0x01006f02));
assertSearchSelectionSelected();
setCaseSensitive(false);
setInput("Info");
List<Address> addrs = addrs(0x01006c6a);
performSearchPrevious(addrs);
}
@Test
public void testSearchBackwardAlign4() throws Exception {
goTo(tool, program, addr(0x1006f56));
setCaseSensitive(true);
setAlignment(8);
setInput("notepad");
List<Address> addrs = addrs(0x010013e0);
performSearchPrevious(addrs);
}
@Test
public void testSearchBackwardAlign4NoneFound() throws Exception {
goTo(tool, program, addr(0x1006f56));
setCaseSensitive(true);
setAlignment(8);
setInput("Info");
performSearchPrevious(Collections.emptyList());
}
@Test
public void testSearchEscapeSequences() throws Exception {
setCaseSensitive(true);
setEscapeSequences(true);
setCharset(StandardCharsets.US_ASCII);
setInput("\"Hello world!\"\\n\\t-new programmer");
List<Address> addrs = addrs(0x0100189d);
performSearchNext(addrs);
setBigEndian(false);
setCharset(StandardCharsets.UTF_16);
addrs = addrs(0x0100198e);
performSearchNext(addrs);
}
}

View File

@ -0,0 +1,258 @@
/* ###
* 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.features.base.memsearch;
import static org.junit.Assert.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Pointer32DataType;
import ghidra.program.model.listing.Program;
/**
* Tests for the Binary format in searching memory.
*/
public class MemSearchBinaryTest extends AbstractMemSearchTest {
@Override
@Before
public void setUp() throws Exception {
super.setUp();
setSearchFormat(SearchFormat.BINARY);
}
@Override
protected Program buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86);
builder.createMemory(".text", Long.toHexString(0x1001000), 0x6600);
builder.createMemory(".data", Long.toHexString(0x1008000), 0x600);
builder.createMemory(".rsrc", Long.toHexString(0x100A000), 0x5400);
builder.createMemory(".bound_import_table", Long.toHexString(0xF0000248), 0xA8);
builder.createMemory(".debug_data", Long.toHexString(0xF0001300), 0x1C);
//create and disassemble a function
builder.setBytes(
"0x01002cf5",
"55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 33 " +
"ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 74 27 " +
"56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 04 12 00 " +
"01 56 8b f8 ff 15 c0 10 00 01 eb 14 ff 75 18 ff 75 0c ff 75 10 ff 75 08 ff 15 04 " +
"12 00 01 8b f8 8b c7 5f 5e 5d c2 14");
builder.disassemble("0x01002cf5", 0x121, true);
builder.createFunction("0x01002cf5");
//create some data
builder.setBytes("0x1001004", "85 4f dc 77");
builder.applyDataType("0x1001004", new Pointer32DataType(), 1);
builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true);
builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE,
true);
builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, true);
//create some undefined data
builder.setBytes("0x1001500", "4e 00 65 00 77 00");
builder.setBytes("0x1003000", "55 00");
return builder.getProgram();
}
@Test
public void testBinaryInvalidEntry() {
// enter a non-binary digit; the search field should not accept it
setInput("2");
assertEquals("", getInput());
}
@Test
public void testBinaryEnterSpaces() {
// verify that more than 8 digits are allowed if spaces are entered
setInput("01110000 01110000");
assertEquals("01110000 01110000", getInput());
}
@Test
public void testBinaryPasteNumberWithPrefix() {
// paste a number with a binary prefix;
// the prefix should be removed before the insertion
setInput("0b00101010");
assertEquals("00101010", getInput());
setInput("0B1010 10");
assertEquals("1010 10", getInput());
}
@Test
public void testBinarySearch() throws Exception {
goTo(0x01001000);
setInput("00010100 11111111");
performSearchNext(addr(0x01002d06));
}
@Test
public void testBinarySearchNext() throws Exception {
goTo(0x01001000);
setInput("01110101");
//@formatter:off
List<Address> addrs = addrs(0x01002d06,
0x01002d11,
0x01002d2c,
0x01002d2f,
0x01002d37,
0x01002d3a,
0x01002d3e,
0x01002d52,
0x01002d55,
0x01002d58,
0x01002d5b);
//@formatter:on
performSearchNext(addrs);
}
@Test
public void testBinarySearchNextAlign4() throws Exception {
goTo(0x01001000);
setInput("01110101");
setAlignment(4);
//the bytes are at the right alignment value but the code units are not
List<Address> addrs = addrs(0x01002d2f, 0x01002d37, 0x01002d5b);
performSearchNext(addrs);
}
@Test
public void testBinarySearchAll() throws Exception {
setInput("11110110");
performSearchAll();
waitForSearch(1);
checkMarkerSet(addrs(0x1002d28));
}
@Test
public void testBinarySearchAllAlign4() throws Exception {
setInput("11111111 01110101");
setAlignment(4);
performSearchAll();
waitForSearch(2);
List<Address> startList = addrs(0x1002d2c, 0x1002d58);
checkMarkerSet(startList);
}
@Test
public void testCodeUnitScope_DefinedData() throws Exception {
//
// Turn on Defined Data scope and make sure only that scope yields matches
//
goTo(0x1001000);// start of program; pointer data
setCodeTypeFilters(false, true, false);
setInput("10000101");
performSearchNext(addr(0x1001004));
// Turn off Defined Data scope and make sure we have no match at the expected address
goTo(0x1001000);// start of program; pointer data
setCodeTypeFilters(true, false, true);
performSearchNext(addr(0x1002d27)); // this is in an instruction past the data match
// try backwards
goTo(0x1002000);
setCodeTypeFilters(false, true, false);
performSearchPrevious(addrs(0x1001004));
}
@Test
public void testCodeUnitScope_UndefinedData() throws Exception {
//
// Turn on Undefined Data scope and make sure only that scope yields matches
//
goTo(0x1001000);
setCodeTypeFilters(false, false, true);
setInput("01100101");
performSearchNext(addr(0x1001502));
// Turn off Undefined Data scope and make sure we have no match at the expected address
goTo(0x1001500);
setCodeTypeFilters(true, true, false);
performSearchNext(addr(0x1001708));
// try backwards
goTo(0x1003000);
setCodeTypeFilters(false, false, true);
performSearchPrevious(addrs(0x1001502));
}
@Test
public void testBinarySearchPrevious() throws Exception {
goTo(0x01002d6b);
setInput("11111111 00010101");
List<Address> addrs = addrs(0x01002d5e, 0x01002d4a, 0x01002d41, 0x01002d1f);
performSearchPrevious(addrs);
}
@Test
public void testBinaryWildcardSearch() throws Exception {
goTo(0x01001000);
setInput("010101xx 10001011");
List<Address> addrs = addrs(0x01002cf5, 0x01002cfc, 0x01002d47);
performSearchNext(addrs);
}
@Test
public void testBinaryWildcardSearchAll() throws Exception {
setInput("10001011 1111xxxx");
performSearchAll();
waitForSearch(4);
List<Address> addrs = addrs(0x1002d0b, 0x1002d25, 0x1002d48, 0x1002d64);
checkMarkerSet(addrs);
}
}

View File

@ -4,36 +4,32 @@
* 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.searchmem;
package ghidra.features.base.memsearch;
import static org.junit.Assert.*;
import java.awt.Component;
import java.awt.Container;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import org.junit.Before;
import org.junit.Test;
import docking.widgets.fieldpanel.support.Highlight;
import ghidra.GhidraOptions;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.services.MarkerSet;
import ghidra.app.util.viewer.field.BytesFieldFactory;
import ghidra.features.base.memsearch.bytesource.ProgramSearchRegion;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.framework.options.Options;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
@ -50,11 +46,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
super();
}
@Override
@Before
@Override
public void setUp() throws Exception {
super.setUp();
selectRadioButton("Hex");
setSearchFormat(SearchFormat.HEX);
}
@Override
@ -115,98 +111,43 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
@Test
public void testDisplayDialog() throws Exception {
assertTrue(searchAction.isEnabled());
// dig up the components of the dialog
dialog = waitForDialogComponent(MemSearchDialog.class);
assertNotNull(dialog);
assertNotNull(valueComboBox);
assertNotNull(hexLabel);
assertButtonState("Hex", true, true);
assertButtonState("String", true, false);
assertButtonState("Decimal", true, false);
assertButtonState("Binary", true, false);
assertButtonState("Regular Expression", true, false);
assertButtonState("Little Endian", true, true);
assertButtonState("Big Endian", true, false);
assertButtonState("Search Selection", false, false);
assertEnabled("Next", false);
assertEnabled("Previous", false);
assertEnabled("Search All", false);
assertEnabled("Dismiss", true);
JPanel p = findTitledJPanel(pane, "Format Options");
assertNotNull(p);
assertTrue(p.isVisible());
assertTrue(searchProvider.isVisible());
}
@Test
public void testHexInvalidEntry() {
// enter a non-hex digit; the search field should not accept it
setValueText("z");
setInput("z");
assertEquals("", valueField.getText());
assertEquals("", getInput());
}
@Test
public void testHexEnterSpaces() {
// verify that more than 16 digits are allowed if spaces are entered
setValueText("01 23 45 67 89 a b c d e f 1 2 3");
assertEquals("01 23 45 67 89 a b c d e f 1 2 3", valueField.getText());
setInput("01 23 45 67 89 a b c d e f 1 2 3");
assertEquals("01 23 45 67 89 a b c d e f 1 2 3", getInput());
}
@Test
public void testHexNoSpaces() {
// enter a hex sequence (no spaces) more than 2 digits;
// the hex label should display the bytes reversed
setValueText("012345678");
assertEquals("78 56 34 12 00 ", hexLabel.getText());
}
@Test
public void testHexBigLittleEndian() throws Exception {
// switch between little and big endian;
// verify the hex label
setValueText("012345678");
pressButtonByText(pane, "Big Endian", true);
waitForSwing();
assertEquals("00 12 34 56 78 ", hexLabel.getText());
}
@Test
public void testHexSpaceBetweenBytes() throws Exception {
// enter a hex sequence where each byte is separated by a space;
// ensure that the byte order setting has no effect on the sequence
setValueText("01 23 45 67 89");
assertEquals("01 23 45 67 89", valueField.getText());
pressButtonByText(pane, "Big Endian", true);
assertEquals("01 23 45 67 89", valueField.getText());
}
@Test
public void testHexPasteNumberWithPrefixAndSuffix() {
// paste a number with a hex prefix;
// the prefix should be removed before the insertion
setValueText("0xabcdef");
assertEquals("abcdef", valueField.getText());
setValueText("$68000");
assertEquals("68000", valueField.getText());
setInput("0xabcdef");
assertEquals("abcdef", getInput());
setInput("$68000");
assertEquals("68000", getInput());
// same for 'h' the suffix
setValueText("ABCDEFh");
assertEquals("ABCDEF", valueField.getText());
setInput("ABCDEFh");
assertEquals("ABCDEF", getInput());
// should also somehow work with leading and trailing white spaces
setValueText(" 0X321 ");
assertEquals("321", valueField.getText().strip());
setValueText(" 123H ");
assertEquals("123", valueField.getText().strip());
setInput(" 0X321 ");
assertEquals("321", getInput().strip());
setInput(" 123H ");
assertEquals("123", getInput().strip());
}
@Test
@ -216,9 +157,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
List<Address> addrs = addrs(0x1002d06, 0x1002d2c, 0x1002d50);
setValueText("14 ff");
setInput("14 ff");
performSearchTest(addrs, "Next");
performSearchNext(addrs);
}
@Test
@ -248,22 +189,21 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
);
//@formatter:on
setValueText("75");
setInput("75");
performSearchTest(addrs, "Next");
performSearchNext(addrs);
}
@Test
public void testHexContiguousSelection() throws Exception {
makeSelection(tool, program, range(0x01002cf5, 0x01002d6d));
assertSearchSelectionSelected();
setValueText("50");
setInput("50");
performSearchTest(addrs(0x01002d1c), "Next");
performSearchNext(addrs(0x01002d1c));
}
@Test
@ -273,11 +213,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
assertSearchSelectionSelected();
setValueText("50");
setInput("50");
List<Address> addrs = addrs(0x01002d1c, 0x01004120, 0x01004200, 0x01004247);
performSearchTest(addrs, "Next");
performSearchNext(addrs);
}
@Test
@ -290,17 +230,14 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
makeSelection(tool, program, range(0x01002cf5, 0x01002d6d), range(0x01004100, 0x010041ff));
// select Search All option to turn off searching only in selection
assertButtonState("Search All", true, false);
// Note: this is 'Search All' for the search type, not the JButton on the button panel
pressButtonByText(pane, "Search All");
assertSearchSelectionSelected();
setSearchSelectionOnly(false);
List<Address> addrs = addrs(0x01002d1c, 0x01004120, 0x01004200, 0x01004247);
setValueText("50");
setInput("50");
performSearchTest(addrs, "Next");
performSearchNext(addrs);
}
@Test
@ -309,10 +246,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
// test the marker stuff
goTo(0x1004180);
setValueText("50");
pressSearchAllButton();
setInput("50");
waitForSearch("Search Memory - ", 4);
performSearchAll();
waitForSearch(4);
List<Address> addrs = addrs(0x01002d1c, 0x01004120, 0x01004200, 0x01004247);
@ -322,11 +260,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
@Test
public void testHexSearchAll2() throws Exception {
// enter search string for multiple byte match
// ff 15
setValueText("ff 15");
pressSearchAllButton();
waitForSearch("Search Memory - ", 5);
setInput("ff 15");
performSearchAll();
waitForSearch(5);
List<Address> addrs = addrs(0x01002d1f, 0x01002d41, 0x01002d4a, 0x01002d5e, 0x010029bd);
@ -335,16 +273,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
@Test
public void testHexSearchAllAlign8() throws Exception {
// QueryResults should get displayed
// test the marker stuff
setAlignment(8);
setInput("8b");
performSearchAll();
JTextField alignment = (JTextField) findComponentByName(dialog.getComponent(), "Alignment");
setText(alignment, "8");
setValueText("8b");
pressSearchAllButton();
waitForSearch("Search Memory - ", 1);
waitForSearch(1);
checkMarkerSet(addrs(0x01002d48));
}
@ -352,11 +285,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
@Test
public void testHexHighlight() throws Exception {
setValueText("80 00 01");
setInput("80 00 01");
pressSearchAllButton();
performSearchAll();
waitForSearch("Search Memory - ", 1);
waitForSearch(1);
Highlight[] h = getByteHighlights(addr(0x10040d9), "8b 0d 58 80 00 01");
assertEquals(1, h.length);
@ -366,10 +299,10 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
@Test
public void testHexHighlight2() throws Exception {
setValueText("01 8b");
pressSearchAllButton();
setInput("01 8b");
performSearchAll();
waitForSearch("Search Memory - ", 3);
waitForSearch(3);
Highlight[] h = getByteHighlights(addr(0x10029bd), "ff 15 d4 10 00 01");
assertEquals(1, h.length);
@ -379,10 +312,10 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
@Test
public void testHexHighlight3() throws Exception {
setValueText("d8 33 f6 3b");
pressSearchAllButton();
setInput("d8 33 f6 3b");
performSearchAll();
waitForSearch("Search Memory - ", 1);
waitForSearch(1);
Highlight[] h = getByteHighlights(addr(0x10029c3), "8b d8");
assertEquals(1, h.length);
@ -393,10 +326,10 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
@Test
public void testHexHighlight4() throws Exception {
setValueText("fd ff ff");
pressSearchAllButton();
setInput("fd ff ff");
performSearchAll();
waitForSearch("Search Memory - ", 1);
waitForSearch(1);
Highlight[] h = getByteHighlights(addr(0x10035f8), "b9 30 fd ff ff");
assertEquals(1, h.length);
@ -410,10 +343,10 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
opt.setInt(BytesFieldFactory.BYTE_GROUP_SIZE_MSG, 3);
opt.setString(BytesFieldFactory.DELIMITER_MSG, "#@#");
setValueText("fd ff ff");
pressSearchAllButton();
setInput("fd ff ff");
performSearchAll();
waitForSearch("Search Memory - ", 1);
waitForSearch(1);
Highlight[] h = getByteHighlights(addr(0x10035f8), "b930fd#@#ffff");
assertEquals(1, h.length);
@ -424,22 +357,19 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
@Test
public void testMarkersRemoved() throws Exception {
setValueText("ff 15");
pressSearchAllButton();
waitForSearch("Search Memory - ", 5);
setInput("ff 15");
performSearchAll();
waitForSearch(5);
List<Address> startList = addrs(0x01002d1f, 0x01002d41, 0x01002d4a, 0x01002d5e, 0x010029bd);
String title = searchProvider.getTitle();
checkMarkerSet(startList);
TableComponentProvider<?>[] providers = tableServicePlugin.getManagedComponents();
assertEquals(1, providers.length);
assertTrue(tool.isVisible(providers[0]));
MarkerSet markerSet = markerService.getMarkerSet(title, program);
assertNotNull(markerSet);
//close it
runSwing(() -> providers[0].closeComponent());
runSwing(() -> searchProvider.closeComponent());
MarkerSet markerSet = markerService.getMarkerSet("Memory Search Results", program);
markerSet = markerService.getMarkerSet("Memory Search Results", program);
assertNull(markerSet);
}
@ -448,11 +378,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
goTo(0x01001000);
setValueText("75");
pressButtonByText(pane, "Previous");
waitForSearchTask();
setInput("75");
assertEquals("Not Found", getStatusText());
performSearchPrevious(Collections.emptyList());
}
@Test
@ -463,11 +391,11 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
//start at 1002d6d and search backwards
goTo(0x1002d6d);
setValueText("ff 15");
setInput("ff 15");
List<Address> addrs = addrs(0x01002d5e, 0x01002d4a, 0x01002d41, 0x01002d1f, 0x010029bd);
performSearchTest(addrs, "Previous");
performSearchPrevious(addrs);
}
@Test
@ -477,13 +405,13 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
goTo(0x1002d6d);
setAlignment("2");
setAlignment(2);
setValueText("ff 15");
setInput("ff 15");
List<Address> addrs = addrs(0x01002d5e, 0x01002d4a);
performSearchTest(addrs, "Previous");
performSearchPrevious(addrs);
}
@Test
@ -495,9 +423,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
assertSearchSelectionSelected();
setValueText("50");
setInput("50");
performSearchTest(addrs(0x01002d1c), "Previous");
performSearchPrevious(addrs(0x01002d1c));
}
@Test
@ -511,9 +439,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
List<Address> addrs = addrs(0x01004247, 0x01004200, 0x01004120, 0x01002d1c);
setValueText("50");
setInput("50");
performSearchTest(addrs, "Previous");
performSearchPrevious(addrs);
}
@Test
@ -522,9 +450,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
List<Address> addrs = addrs(0x01002d0b, 0x01002d25, 0x01002d48, 0x01002d64);
setValueText("8b f?");
setInput("8b f?");
performSearchTest(addrs, "Next");
performSearchNext(addrs);
}
@Test
@ -539,9 +467,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
List<Address> addrs = addrs(0x01001004, 0x01002d27);
setValueText("85 ?");
setInput("85 ?");
performSearchTest(addrs, "Next");
performSearchNext(addrs);
}
@Test
@ -556,9 +484,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
List<Address> addrs = addrs(0x01001004, 0x01002d27);
setValueText("85 .");
setInput("85 .");
performSearchTest(addrs, "Next");
performSearchNext(addrs);
}
@Test
@ -568,9 +496,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
List<Address> addrs = addrs(0x01002d64, 0x01002d48, 0x01002d25, 0x01002d0b);
setValueText("8b f?");
setInput("8b f?");
performSearchTest(addrs, "Previous");
performSearchPrevious(addrs);
}
@Test
@ -578,9 +506,9 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
// QueryResults should get displayed
// test the marker stuff
setValueText("8b f?");
pressSearchAllButton();
waitForSearch("Search Memory - ", 4);
setInput("8b f?");
performSearchAll();
waitForSearch(4);
List<Address> addrs = addrs(0x01002d64, 0x01002d48, 0x01002d25, 0x01002d0b);
@ -589,96 +517,39 @@ public class MemSearchHexTest extends AbstractMemSearchTest {
@Test
public void testHexByteOrder() throws Exception {
selectRadioButton("Big Endian");
setBigEndian(true);
goTo(0x01001000);
setValueText("8bec");
setInput("8bec");
performSearchTest(addrs(0x01002cf6), "Next");
performSearchNext(addrs(0x01002cf6));
}
@Test
public void testSearchInOtherSpace() throws Exception {
goTo(0x01001000);
setValueText("01 02 03 04 05 06 07 08 09");
setInput("01 02 03 04 05 06 07 08 09");
selectRadioButton("All Blocks");
addSearchRegion(ProgramSearchRegion.OTHER, true);
List<Address> addrs = addrs(program.getAddressFactory().getAddress("otherOverlay:1"));
performSearchTest(addrs, "Next");
performSearchNext(addrs);
}
@Test
public void testValueComboBox() throws Exception {
setValueText("00 65");
public void testRepeatSearchForwardThenBackwards() throws Exception {
pressSearchButton("Next");
setValueText("");
setInput("8b f8");
performSearchNext(addr(0x01002d0b));
setValueText("d1 e1");
pressSearchButton("Next");
setValueText("");
setValueText("0123456");
pressSearchButton("Next");
setValueText("");
// the combo box should list most recently entered values
DefaultComboBoxModel<?> cbModel = (DefaultComboBoxModel<?>) valueComboBox.getModel();
assertEquals(3, cbModel.getSize());
assertEquals("0123456", cbModel.getElementAt(0));
assertEquals("d1 e1", cbModel.getElementAt(1));
assertEquals("00 65", cbModel.getElementAt(2));
}
@Test
public void testRepeatSearchAction() throws Exception {
setValueText("8b f8");
pressSearchButton("Next");
assertEquals(addr(0x01002d0b), currentAddress());
repeatSearch();
repeatSearchForward();
assertEquals(addr(0x01002d48), currentAddress());
repeatSearchBackward();
assertEquals(addr(0x01002d0b), currentAddress());
}
@Test
public void testSearchBackwardsWhenAtFirstAddressWithCurrentMatch() throws Exception {
setValueText("00");
pressSearchButton("Next");
pressSearchButton("Previous");
pressSearchButton("Previous");
assertEquals("Not Found", getStatusText());
}
//==================================================================================================
// Private Methods
//==================================================================================================
private JPanel findTitledJPanel(Container container, String title) {
if (container instanceof JPanel) {
JPanel p = (JPanel) container;
Border b = p.getBorder();
if ((b instanceof TitledBorder) && ((TitledBorder) b).getTitle().equals(title)) {
return p;
}
}
Component[] comps = container.getComponents();
for (Component element : comps) {
if (element instanceof Container) {
JPanel p = findTitledJPanel((Container) element, title);
if (p != null) {
return p;
}
}
}
return null;
}
}

View File

@ -0,0 +1,473 @@
/* ###
* 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.features.base.memsearch;
import static org.junit.Assert.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Pointer32DataType;
import ghidra.program.model.listing.Program;
/**
* Tests for searching for decimal values in memory.
*/
public class MemSearchNumbersTest extends AbstractMemSearchTest {
@Override
@Before
public void setUp() throws Exception {
super.setUp();
setSearchFormat(SearchFormat.DECIMAL);
setDecimalSize(4);
}
@Override
protected Program buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("TestX86", ProgramBuilder._X86);
builder.createMemory(".text", Long.toHexString(0x1001000), 0x6600);
builder.createMemory(".data", Long.toHexString(0x1008000), 0x600);
builder.createMemory(".rsrc", Long.toHexString(0x100A000), 0x5400);
builder.createMemory(".bound_import_table", Long.toHexString(0xF0000248), 0xA8);
builder.createMemory(".debug_data", Long.toHexString(0xF0001300), 0x1C);
//create and disassemble a function
builder.setBytes(
"0x01002cf5",
"55 8b ec 83 7d 14 00 56 8b 35 e0 10 00 01 57 74 09 ff 75 14 ff d6 8b f8 eb 02 " +
"33 ff ff 75 10 ff d6 03 c7 8d 44 00 02 50 6a 40 ff 15 dc 10 00 01 8b f0 85 f6 " +
"74 27 56 ff 75 14 ff 75 10 e8 5c ff ff ff ff 75 18 ff 75 0c 56 ff 75 08 ff 15 " +
"04 12 00 01 56 8b f8 ff 15 c0 10 00 01 eb 14 ff 75 18 ff 75 0c ff 75 10 ff 75 " +
"08 ff 15 04 12 00 01 8b f8 8b c7 5f 5e 5d c2 14");
builder.disassemble("0x01002cf5", 0x121, true);
builder.createFunction("0x01002cf5");
//create some data
builder.setBytes("0x1001004", "85 4f dc 77");
builder.applyDataType("0x1001004", new Pointer32DataType(), 1);
builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true);
builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE,
true);
builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false);
//create some undefined data
builder.setBytes("0x1001500", "4e 00 65 00 77 00");
builder.setBytes("0x1003000", "55 00");
builder.setBytes("0x1004100", "64 00 00 00");//100 dec
builder.setBytes("0x1004120", "50 ff 75 08");//7.4027124e-34 float
builder.setBytes("0x1004135", "64 00 00 00");//100 dec
builder.setBytes("0x1004200", "50 ff 75 08 e8 8d 3c 00");//1.588386874245921e-307
builder.setBytes("0x1004247", "50 ff 75 08");//7.4027124e-34 float
builder.setBytes("0x1004270", "65 00 6e 00 64 00 69 00");//29555302058557541 qword
return builder.getProgram();
}
@Test
public void testInvalidEntry() throws Exception {
// enter non-numeric value
setInput("z");
assertEquals("", getInput());
}
@Test
public void testValueTooLarge() throws Exception {
setDecimalSize(1);
setInput("262");
assertEquals("", getInput());
}
@Test
public void testNegativeValueEntered() throws Exception {
// enter a negative value; the hexLabel should show the correct
// byte sequence
setSearchFormat(SearchFormat.DECIMAL);
setDecimalSize(2);
setInput("-1234");
assertEquals("2e fb", getByteString());
setDecimalSize(1);
assertEquals("46 -5", getInput());
setInput("-55");
assertEquals("c9", getByteString());
setDecimalSize(4);
assertEquals("-55", getInput());
assertEquals("c9 ff ff ff", getByteString());
setDecimalSize(8);
assertEquals("-55", getInput());
assertEquals("c9 ff ff ff ff ff ff ff", getByteString());
setSearchFormat(SearchFormat.FLOAT);
assertEquals("00 00 5c c2", getByteString());
setSearchFormat(SearchFormat.DOUBLE);
assertEquals("00 00 00 00 00 80 4b c0", getByteString());
}
@Test
public void testMulipleValuesEntered() throws Exception {
// enter values separated by a space; values should be accepted
setDecimalSize(1);
setInput("12 34 56 78");
assertEquals("0c 22 38 4e", getByteString());
setDecimalSize(2);
assertEquals("8716 20024", getInput());
assertEquals("0c 22 38 4e", getByteString());
setDecimalSize(4);
assertEquals("1312301580", getInput());
assertEquals("0c 22 38 4e", getByteString());
setDecimalSize(8);
assertEquals("1312301580", getInput());
assertEquals("0c 22 38 4e 00 00 00 00", getByteString());
setSearchFormat(SearchFormat.FLOAT);
assertEquals("1312301580", getInput());
assertEquals("44 70 9c 4e", getByteString());
setSearchFormat(SearchFormat.DOUBLE);
assertEquals("1312301580", getInput());
assertEquals("00 00 00 83 08 8e d3 41", getByteString());
}
@Test
public void testByteOrder() throws Exception {
setBigEndian(true);
setInput("12 34 56 78");
setDecimalSize(1);
setBigEndian(true);
// should be unaffected
assertEquals("0c 22 38 4e", getByteString());
setDecimalSize(2);
assertEquals("3106 14414", getInput());
assertEquals("0c 22 38 4e", getByteString());
setDecimalSize(4);
assertEquals("203569230", getInput());
assertEquals("0c 22 38 4e", getByteString());
setDecimalSize(8);
assertEquals("203569230", getInput());
assertEquals("00 00 00 00 0c 22 38 4e", getByteString());
setSearchFormat(SearchFormat.FLOAT);
assertEquals("203569230", getInput());
assertEquals("4d 42 23 85", getByteString());
setSearchFormat(SearchFormat.DOUBLE);
assertEquals("203569230", getInput());
assertEquals("41 a8 44 70 9c 00 00 00", getByteString());
}
@Test
public void testFloatDoubleFormat() throws Exception {
setSearchFormat(SearchFormat.FLOAT);
setInput("12.345");
assertEquals("12.345", getInput());
assertEquals("1f 85 45 41", getByteString());
setSearchFormat(SearchFormat.DOUBLE);
assertEquals("71 3d 0a d7 a3 b0 28 40", getByteString());
}
@Test
public void testSearchByte() throws Exception {
goTo(program.getMinAddress());
List<Address> addrs = addrs(0x1002d3e, 0x1002d5b, 0x1004123, 0x1004203, 0x100424a);
setDecimalSize(1);
setInput("8");
performSearchNext(addrs);
}
@Test
public void testSearchByteBackward() throws Exception {
goTo(0x01002d6d);
setDecimalSize(1);
setInput("8");
List<Address> addrs = addrs(0x1002d5b, 0x1002d3e);
performSearchPrevious(addrs);
}
@Test
public void testSearchWord() throws Exception {
goTo(program.getMinAddress());
setDecimalSize(2);
setInput("20");
List<Address> addrs = addrs(0x1002cf8, 0x1002d6b);
performSearchNext(addrs);
}
@Test
public void testSearchWordBackward() throws Exception {
goTo(0x01002d6e);
setDecimalSize(2);
setInput("20");
List<Address> addrs = addrs(0x1002d6b, 0x1002cf8);
performSearchPrevious(addrs);
}
@Test
public void testSearchDWord() throws Exception {
goTo(program.getMinAddress());
setDecimalSize(4);
setInput("100");
List<Address> addrs = addrs(0x1001708, 0x1004100, 0x1004135);
performSearchNext(addrs);
}
@Test
public void testSearchDWordBackward() throws Exception {
goTo(0x01005000);
setDecimalSize(4);
setInput("100");
List<Address> addrs = addrs(0x1004135, 0x1004100, 0x1001708);
performSearchPrevious(addrs);
}
@Test
public void testSearchQWord() throws Exception {
goTo(program.getMinAddress());
setDecimalSize(8);
setInput("29555302058557541");
performSearchNext(addrs(0x1004270));
}
@Test
public void testSearchQWordBackward() throws Exception {
goTo(program.getMaxAddress());
setDecimalSize(8);
setInput("29555302058557541");
performSearchPrevious(addrs(0x1004270));
}
@Test
public void testSearchFloat() throws Exception {
goTo(program.getMinAddress());
setSearchFormat(SearchFormat.FLOAT);
setInput("7.4027124e-34");
List<Address> addrs = addrs(0x1004120, 0x1004200, 0x1004247);
performSearchNext(addrs);
}
@Test
public void testSearchFloatBackward() throws Exception {
goTo(0x01005000);
setSearchFormat(SearchFormat.FLOAT);
setInput("7.4027124e-34");
List<Address> addrs = addrs(0x1004247, 0x1004200, 0x1004120);
performSearchPrevious(addrs);
}
@Test
public void testSearchFloatBackwardAlign8() throws Exception {
goTo(program.getMaxAddress());
setAlignment(8);
setSearchFormat(SearchFormat.FLOAT);
setInput("7.4027124e-34");
List<Address> addrs = addrs(0x1004200, 0x1004120);
performSearchPrevious(addrs);
}
@Test
public void testSearchDouble() throws Exception {
goTo(program.getMinAddress());
setSearchFormat(SearchFormat.DOUBLE);
setInput("1.588386874245921e-307");
List<Address> addrs = addrs(0x1004200);
performSearchNext(addrs);
}
@Test
public void testSearchDoubleBackward() throws Exception {
goTo(program.getMaxAddress());
setSearchFormat(SearchFormat.DOUBLE);
setInput("1.588386874245921e-307");
List<Address> addrs = addrs(0x1004200);
performSearchPrevious(addrs);
}
@Test
public void testSearchAllByte() throws Exception {
setDecimalSize(1);
setInput("8");
performSearchAll();
waitForSearch(5);
List<Address> addrs = addrs(0x1002d40, 0x1002d5d, 0x1004123, 0x1004203, 0x100424a);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllWord() throws Exception {
setDecimalSize(2);
setInput("20");
performSearchAll();
waitForSearch(2);
List<Address> addrs = addrs(0x1002cfa, 0x1002d6c);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllWordAlign4() throws Exception {
setAlignment(4);
setDecimalSize(2);
setInput("20");
performSearchAll();
waitForSearch(1);
checkMarkerSet(addrs(0x1002d6c));
}
@Test
public void testSearchAllDWord() throws Exception {
setDecimalSize(4);
setInput("100");
performSearchAll();
waitForSearch(3);
List<Address> addrs = addrs(0x1001715, 0x1004100, 0x1004135);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllQWord() throws Exception {
setDecimalSize(8);
setInput("29555302058557541");
performSearchAll();
waitForSearch(1);
checkMarkerSet(addrs(0x1004270));
}
@Test
public void testSearchAllFloat() throws Exception {
setSearchFormat(SearchFormat.FLOAT);
setInput("7.4027124e-34");
performSearchAll();
waitForSearch(3);
List<Address> addrs = addrs(0x1004120, 0x1004200, 0x1004247);
checkMarkerSet(addrs);
}
@Test
public void testSearchAllDouble() throws Exception {
setSearchFormat(SearchFormat.DOUBLE);
setInput("1.588386874245921e-307");
performSearchAll();
waitForSearch(1);
checkMarkerSet(addrs(0x1004200));
}
}

View File

@ -4,16 +4,16 @@
* 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.searchmem;
package ghidra.features.base.memsearch;
import static org.junit.Assert.*;
@ -24,10 +24,11 @@ import org.junit.Before;
import org.junit.Test;
import docking.widgets.fieldpanel.support.Highlight;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Pointer32DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Program;
/**
* Tests for searching memory for hex reg expression.
@ -38,7 +39,7 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
@Before
public void setUp() throws Exception {
super.setUp();
selectRadioButton("Regular Expression");
setSearchFormat(SearchFormat.REG_EX);
}
@Override
@ -112,13 +113,11 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
// NOTE: the following regular expression searches for 0x8b followed
// by 0-10 occurrences of any character, followed by 0x56.
setValueText("\\x8b.{0,10}\\x56");
assertEquals("", hexLabel.getText());
setInput("\\x8b.{0,10}\\x56");
List<Address> addrs = addrs(0x01002cf6, 0x01002d25);
performSearchTest(addrs, "Next");
performSearchNext(addrs);
}
@Test
@ -126,33 +125,18 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
//
// Turn on Instructions scope and make sure only that scope yields matches
//
goTo(0x1002cf5); // 'ghidra' function address
goTo(0x1002cf4); // just before instruction hit
setCodeTypeFilters(true, false, false); // only accept instruction matches
selectCheckBox("Instructions", true);
selectCheckBox("Defined Data", false);
selectCheckBox("Undefined Data", false);
setValueText("\\x55");
pressSearchButton("Next");
Address expectedSearchAddressHit = addr(0x1002cf5);
assertEquals(
"Did not find a hit at the next matching Instruction when we are searching Instructions",
expectedSearchAddressHit, cb.getCurrentLocation().getAddress());
setInput("\\x55");
performSearchNext(addr(0x1002cf5));
// Turn off Instructions scope and make sure we have no match at the expected address
goTo(0x1002cf5); // 'ghidra' function address
goTo(0x1002cf4);
selectCheckBox("Instructions", false);
selectCheckBox("Defined Data", true);
selectCheckBox("Undefined Data", true);
pressSearchButton("Next");
assertTrue(
"Found a search match at an Instruction, even though no Instruction should be searched",
!expectedSearchAddressHit.equals(currentAddress()));
setCodeTypeFilters(false, true, true); // all but instruction matches
performSearchNext(addr(0x1003000)); // this is in undefined data
CodeUnit codeUnit = currentCodeUnit();
assertTrue("Did not find a data match when searching instructions is disabled",
codeUnit instanceof Data);
}
@Test
@ -160,33 +144,18 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
//
// Turn on Defined Data scope and make sure only that scope yields matches
//
goTo(0x1001004);// start of program; pointer data
goTo(0x1001000);// start of program
setCodeTypeFilters(false, true, false); // only accept defined data matches
setInput("\\x85");
selectCheckBox("Instructions", false);
selectCheckBox("Defined Data", true);
selectCheckBox("Undefined Data", false);
setValueText("\\x85");
pressSearchButton("Next");
Address expectedSearchAddressHit = addr(0x1001004);
assertEquals(
"Did not find a hit at the next matching Defined Data when we are searching Defined Data",
expectedSearchAddressHit, cb.getCurrentLocation().getAddress());
performSearchNext(addr(0x1001004));
// Turn off Defined Data scope and make sure we have no match at the expected address
goTo(0x1001004);// start of program; pointer data
goTo(0x1001000);// start of program
selectCheckBox("Instructions", true);
selectCheckBox("Defined Data", false);
selectCheckBox("Undefined Data", true);
pressSearchButton("Next");
assertTrue(
"Found a search match at a Defined Data, even though no Defined Data should be searched",
!expectedSearchAddressHit.equals(currentAddress()));
setCodeTypeFilters(true, false, true); // don't accept defined data
performSearchNext(addr(0x1002d27));
CodeUnit codeUnit = currentCodeUnit();
assertTrue("Did not find a instruction match when searching defined data is disabled",
codeUnit instanceof Instruction);
}
@Test
@ -194,33 +163,19 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
//
// Turn on Undefined Data scope and make sure only that scope yields matches
//
goTo(0x1004270);
goTo(0x1004269);
selectCheckBox("Instructions", false);
selectCheckBox("Defined Data", false);
selectCheckBox("Undefined Data", true);
setCodeTypeFilters(false, false, true); // only accept undefined data matches
setInput("\\x65");
setValueText("\\x65");
pressSearchButton("Next");
Address expectedSearchAddressHit = addr(0x1004270);
assertEquals(
"Did not find a hit at the next matching Undefined Data when we are searching Undefined Data",
expectedSearchAddressHit, cb.getCurrentLocation().getAddress());
performSearchNext(addr(0x1004270));
// Turn off Undefined Data scope and make sure we have no match at the expected address
goTo(0x1004270);
setCodeTypeFilters(true, true, false); // don't accept undefined data
goTo(0x1004260);
selectCheckBox("Instructions", true);
selectCheckBox("Defined Data", true);
selectCheckBox("Undefined Data", false);
pressSearchButton("Next");
assertTrue(
"Found a search match at an Undefined Data, even though no Undefined Data should be searched",
!expectedSearchAddressHit.equals(currentAddress()));
performSearchNext(addr(0x1004300)); // this is defined data past where we undefined match
CodeUnit codeUnit = currentCodeUnit();
assertTrue("Did not find a data match when searching undefined data is disabled",
codeUnit instanceof Data);
}
@Test
@ -228,13 +183,11 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
// NOTE: the following regular expression searches for 0x56 followed
// by 0-10 occurrences of any character, followed by 0x10.
setValueText("\\x56.{0,10}\\x10");
setInput("\\x56.{0,10}\\x10");
assertEquals("", hexLabel.getText());
setAlignment(4);
setAlignment("4");
performSearchTest(addrs(0x01002cfc), "Next");
performSearchNext(addrs(0x01002cfc));
}
@Test
@ -242,14 +195,12 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
// Note: the following regular expression searches for 0x56 followed
// by 0-10 occurrences of any character, followed by 0x10.
setValueText("\\x56.{0,10}\\x10");
assertEquals("", hexLabel.getText());
setInput("\\x56.{0,10}\\x10");
List<Address> addrs = addrs(0x01002cfc, 0x01002d2b, 0x01002d47);
pressSearchAllButton();
waitForSearch("Search Memory - ", 3);
performSearchAll();
waitForSearch(3);
checkMarkerSet(addrs);
}
@ -259,13 +210,12 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
// NOTE: the following regular expression searches for 0xf4 followed
// by 0x77.
setValueText("\\xf4\\x77");
assertEquals("", hexLabel.getText());
setInput("\\xf4\\x77");
List<Address> addrs = addrs(0x01001042, 0x01001046);
pressSearchAllButton();
waitForSearch("Search Memory - ", 2);
performSearchAll();
waitForSearch(2);
checkMarkerSet(addrs);
}
@ -275,24 +225,21 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
// NOTE: the following regular expression searches for 0x56 followed
// by 0-10 occurrences of any character, followed by 0x10.
setValueText("\\x56.{0,10}\\x10");
setInput("\\x56.{0,10}\\x10");
assertEquals("", hexLabel.getText());
setAlignment(4);
setAlignment("4");
pressSearchAllButton();
waitForSearch("Search Memory - ", 1);
performSearchAll();
waitForSearch(1);
checkMarkerSet(addrs(0x01002cfc));
}
@Test
public void testRegExpHighlight() throws Exception {
setValueText("\\x6a\\x01");
pressSearchAllButton();
waitForSearch("Search Memory - ", 3);
setInput("\\x6a\\x01");
performSearchAll();
waitForSearch(3);
Highlight[] h = getByteHighlights(addr(0x10029cb), "6a 01");
assertEquals(1, h.length);
@ -302,10 +249,9 @@ public class MemSearchRegExTest extends AbstractMemSearchTest {
@Test
public void testRegExpHighlight2() throws Exception {
setValueText("\\x6a\\x01");
pressSearchAllButton();
waitForSearch("Search Memory - ", 3);
setInput("\\x6a\\x01");
performSearchAll();
waitForSearch(3);
Highlight[] h = getByteHighlights(addr(0x1002826), "6a 01");
assertEquals(1, h.length);

View File

@ -4,16 +4,16 @@
* 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.searchmem;
package ghidra.features.base.memsearch;
import java.nio.charset.StandardCharsets;
import java.util.List;
@ -21,25 +21,28 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import ghidra.features.base.memsearch.format.SearchFormat;
import ghidra.features.base.memsearch.scan.Scanner;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Pointer32DataType;
import ghidra.program.model.listing.Program;
/**
* Tests for searching for decimal values in memory.
* Tests for the search results "scan" feature
*/
public class MemSearchDecimal1Test extends AbstractMemSearchTest {
public class MemSearchScanTest extends AbstractMemSearchTest {
public MemSearchDecimal1Test() {
public MemSearchScanTest() {
super();
}
@Override
@Before
@Override
public void setUp() throws Exception {
super.setUp();
selectRadioButton("Decimal");
setSearchFormat(SearchFormat.DECIMAL);
setDecimalSize(4);
}
@Override
@ -67,7 +70,8 @@ public class MemSearchDecimal1Test extends AbstractMemSearchTest {
builder.setBytes("0x1001004", "85 4f dc 77");
builder.applyDataType("0x1001004", new Pointer32DataType(), 1);
builder.createEncodedString("0x01001708", "Notepad", StandardCharsets.UTF_16BE, true);
builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE, true);
builder.createEncodedString("0x01001740", "something else", StandardCharsets.UTF_16BE,
true);
builder.createEncodedString("0x010013cc", "notepad.exe", StandardCharsets.US_ASCII, false);
//create some undefined data
@ -84,26 +88,109 @@ public class MemSearchDecimal1Test extends AbstractMemSearchTest {
}
@Test
public void testSearchByteBackward() throws Exception {
public void testScanEquals() throws Exception {
goTo(0x01002d6d);
setInput("100");
performSearchAll();
waitForSearch(3);
selectRadioButton("Byte");
List<Address> addrs = addrs(0x1001715, 0x1004100, 0x1004135);
checkMarkerSet(addrs);
setValueText("8");
setValue(addr(0x1004100), 101);
List<Address> addrs = addrs(0x1002d5b, 0x1002d3e);
// only keep values that don't change
scan(Scanner.EQUALS);
waitForSearch(2);
performSearchTest(addrs, "Previous");
// the address we changed should now be removed from the results
addrs = addrs(0x1001715, 0x1004135);
checkMarkerSet(addrs);
}
//==================================================================================================
// Private Methods
//==================================================================================================
@Test
public void testScanNotEquals() throws Exception {
@Override
protected void showMemSearchDialog() {
super.showMemSearchDialog();
selectRadioButton("Decimal");
setInput("100");
performSearchAll();
waitForSearch(3);
List<Address> addrs = addrs(0x1001715, 0x1004100, 0x1004135);
checkMarkerSet(addrs);
setValue(addr(0x1004100), 101);
// only keep values that don't change
scan(Scanner.NOT_EQUALS);
waitForSearch(1);
// the address we changed should now be removed from the results
addrs = addrs(0x1004100);
checkMarkerSet(addrs);
}
@Test
public void testScanIncrement() throws Exception {
setInput("100");
performSearchAll();
waitForSearch(3);
List<Address> addrs = addrs(0x1001715, 0x1004100, 0x1004135);
checkMarkerSet(addrs);
setValue(addr(0x1004100), 101);
setValue(addr(0x1004135), 99);
// only keep values that don't change
scan(Scanner.INCREASED);
waitForSearch(1);
// the address we changed should now be removed from the results
addrs = addrs(0x1004100);
checkMarkerSet(addrs);
}
@Test
public void testScanDecrement() throws Exception {
setInput("100");
performSearchAll();
waitForSearch(3);
List<Address> addrs = addrs(0x1001715, 0x1004100, 0x1004135);
checkMarkerSet(addrs);
setValue(addr(0x1004100), 101);
setValue(addr(0x1004135), 99);
// only keep values that don't change
scan(Scanner.DECREASED);
waitForSearch(1);
// the address we changed should now be removed from the results
addrs = addrs(0x1004135);
checkMarkerSet(addrs);
}
private void scan(Scanner scanner) {
runSwing(() -> searchProvider.scan(scanner));
}
private void setValue(Address address, int value) throws Exception {
byte[] bytes = getBytes(value);
int transactionID = program.startTransaction("test");
memory.setBytes(address, bytes);
program.endTransaction(transactionID, true);
}
private byte[] getBytes(int value) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (value & 0xff);
bytes[1] = (byte) ((value >> 8) & 0xff);
bytes[2] = (byte) ((value >> 16) & 0xff);
bytes[3] = (byte) ((value >> 24) & 0xff);
return bytes;
}
}

View File

@ -4,25 +4,21 @@
* 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.searchmem;
package ghidra.features.base.memsearch;
import static org.junit.Assert.*;
import java.awt.Container;
import java.awt.Window;
import javax.swing.JComboBox;
import javax.swing.JTextField;
import org.junit.*;
import docking.action.DockingActionIf;
@ -30,8 +26,10 @@ import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.marker.MarkerManagerPlugin;
import ghidra.app.plugin.core.programtree.ProgramTreePlugin;
import ghidra.app.plugin.core.searchmem.mask.MnemonicSearchPlugin;
import ghidra.app.plugin.core.searchmem.MemSearchPlugin;
import ghidra.app.services.ProgramManager;
import ghidra.features.base.memsearch.gui.MemorySearchProvider;
import ghidra.features.base.memsearch.mnemonic.MnemonicSearchPlugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
@ -40,6 +38,7 @@ import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.Swing;
public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
@ -50,6 +49,7 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes
private DockingActionIf searchMnemonicNoOperandsNoConstAction;
private DockingActionIf searchMnemonicOperandsConstAction;
private CodeBrowserPlugin cb;
private MemorySearchProvider searchProvider;
@Before
public void setUp() throws Exception {
@ -97,18 +97,12 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes
tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", sel, program));
performAction(searchMnemonicOperandsNoConstAction, cb.getProvider(), true);
searchProvider = waitForComponentProvider(MemorySearchProvider.class);
MemSearchDialog dialog = waitForDialogComponent(MemSearchDialog.class);
assertNotNull(dialog);
Container component = dialog.getComponent();
@SuppressWarnings("unchecked")
JComboBox<String> comboBox = findComponent(component, JComboBox.class);
JTextField valueField = (JTextField) comboBox.getEditor().getEditorComponent();
assertNotNull(searchProvider);
assertEquals(
"01010101 10001011 11101100 10000001 11101100 ........ ........ ........ ........",
valueField.getText().strip());
getInput());
}
@ -119,17 +113,12 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes
performAction(searchMnemonicNoOperandsNoConstAction, cb.getProvider(), true);
MemSearchDialog dialog = waitForDialogComponent(MemSearchDialog.class);
assertNotNull(dialog);
Container component = dialog.getComponent();
@SuppressWarnings("unchecked")
JComboBox<String> comboBox = findComponent(component, JComboBox.class);
JTextField valueField = (JTextField) comboBox.getEditor().getEditorComponent();
searchProvider = waitForComponentProvider(MemorySearchProvider.class);
assertNotNull(searchProvider);
assertEquals(
"01010... 10001011 11...... 10000001 11101... ........ ........ ........ ........",
valueField.getText().strip());
getInput());
}
@ -140,17 +129,14 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes
performAction(searchMnemonicOperandsConstAction, cb.getProvider(), true);
MemSearchDialog dialog = waitForDialogComponent(MemSearchDialog.class);
assertNotNull(dialog);
Container component = dialog.getComponent();
performAction(searchMnemonicOperandsConstAction, cb.getProvider(), true);
@SuppressWarnings("unchecked")
JComboBox<String> comboBox = findComponent(component, JComboBox.class);
JTextField valueField = (JTextField) comboBox.getEditor().getEditorComponent();
searchProvider = waitForComponentProvider(MemorySearchProvider.class);
assertNotNull(searchProvider);
assertEquals(
"01010101 10001011 11101100 10000001 11101100 00000100 00000001 00000000 00000000",
valueField.getText().strip());
getInput());
}
/**
@ -186,4 +172,7 @@ public class MnemonicSearchPluginTest extends AbstractGhidraHeadedIntegrationTes
return program.getMinAddress().getNewAddress(offset);
}
protected String getInput() {
return Swing.runNow(() -> searchProvider.getSearchInput());
}
}

View File

@ -0,0 +1,51 @@
/* ###
* 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.features.base.memsearch.bytesequence;
public class ByteArrayByteSequence implements ByteSequence {
private final byte[] bytes;
public ByteArrayByteSequence(byte... bytes) {
this.bytes = bytes;
}
@Override
public int getLength() {
return bytes.length;
}
@Override
public byte getByte(int i) {
return bytes[i];
}
@Override
public byte[] getBytes(int index, int size) {
if (index < 0 || index + size > bytes.length) {
throw new IndexOutOfBoundsException();
}
byte[] results = new byte[size];
System.arraycopy(bytes, index, results, 0, size);
return results;
}
@Override
public boolean hasAvailableBytes(int index, int length) {
return index >= 0 && index + length <= getLength();
}
}

View File

@ -0,0 +1,134 @@
/* ###
* 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.features.base.memsearch.bytesequence;
import static org.junit.Assert.*;
import org.junit.Test;
public class ByteArrayByteSequenceTest {
private ByteSequence main = new ByteArrayByteSequence((byte) 0, (byte) 1, (byte) 2, (byte) 3);
private ByteSequence extra = new ByteArrayByteSequence((byte) 4, (byte) 5);
private ByteSequence extended = new ExtendedByteSequence(main, extra, 100);
@Test
public void testSimpleByteSeqeunce() {
assertEquals(4, main.getLength());
assertEquals(0, main.getByte(0));
assertEquals(1, main.getByte(1));
assertEquals(2, main.getByte(2));
assertEquals(3, main.getByte(3));
try {
main.getByte(4);
fail("Expected index out of bounds exception");
}
catch (IndexOutOfBoundsException e) {
// expected
}
}
@Test
public void testSimpleGetAvailableBytes() {
assertTrue(main.hasAvailableBytes(0, 1));
assertTrue(main.hasAvailableBytes(0, 2));
assertTrue(main.hasAvailableBytes(0, 3));
assertTrue(main.hasAvailableBytes(0, 4));
assertFalse(main.hasAvailableBytes(0, 5));
assertTrue(main.hasAvailableBytes(1, 1));
assertTrue(main.hasAvailableBytes(1, 2));
assertTrue(main.hasAvailableBytes(1, 3));
assertFalse(main.hasAvailableBytes(1, 4));
assertFalse(main.hasAvailableBytes(1, 5));
assertTrue(main.hasAvailableBytes(2, 1));
assertTrue(main.hasAvailableBytes(2, 2));
assertFalse(main.hasAvailableBytes(2, 3));
assertFalse(main.hasAvailableBytes(2, 4));
assertTrue(main.hasAvailableBytes(3, 1));
assertFalse(main.hasAvailableBytes(3, 2));
assertFalse(main.hasAvailableBytes(3, 3));
assertFalse(main.hasAvailableBytes(4, 1));
assertFalse(main.hasAvailableBytes(4, 2));
}
@Test
public void testExtendedByteSeqeunce() {
assertEquals(4, extended.getLength());
assertEquals(0, extended.getByte(0));
assertEquals(1, extended.getByte(1));
assertEquals(2, extended.getByte(2));
assertEquals(3, extended.getByte(3));
assertEquals(4, extended.getByte(4));
assertEquals(5, extended.getByte(5));
try {
extended.getByte(6);
fail("Expected index out of bounds exception");
}
catch (IndexOutOfBoundsException e) {
// expected
}
}
@Test
public void testExtendedGetAvailableBytes() {
assertTrue(extended.hasAvailableBytes(0, 1));
assertTrue(extended.hasAvailableBytes(0, 2));
assertTrue(extended.hasAvailableBytes(0, 3));
assertTrue(extended.hasAvailableBytes(0, 4));
assertTrue(extended.hasAvailableBytes(0, 5));
assertTrue(extended.hasAvailableBytes(0, 6));
assertFalse(extended.hasAvailableBytes(0, 7));
assertTrue(extended.hasAvailableBytes(1, 1));
assertTrue(extended.hasAvailableBytes(1, 2));
assertTrue(extended.hasAvailableBytes(1, 3));
assertTrue(extended.hasAvailableBytes(1, 4));
assertTrue(extended.hasAvailableBytes(1, 5));
assertFalse(extended.hasAvailableBytes(1, 6));
assertFalse(extended.hasAvailableBytes(1, 7));
assertTrue(extended.hasAvailableBytes(2, 1));
assertTrue(extended.hasAvailableBytes(2, 2));
assertTrue(extended.hasAvailableBytes(2, 3));
assertTrue(extended.hasAvailableBytes(2, 4));
assertFalse(extended.hasAvailableBytes(2, 5));
assertFalse(extended.hasAvailableBytes(2, 6));
assertTrue(extended.hasAvailableBytes(3, 1));
assertTrue(extended.hasAvailableBytes(3, 2));
assertTrue(extended.hasAvailableBytes(3, 3));
assertFalse(extended.hasAvailableBytes(3, 4));
assertFalse(extended.hasAvailableBytes(3, 5));
assertTrue(extended.hasAvailableBytes(4, 1));
assertTrue(extended.hasAvailableBytes(4, 2));
assertFalse(extended.hasAvailableBytes(4, 3));
assertFalse(extended.hasAvailableBytes(4, 4));
assertTrue(extended.hasAvailableBytes(5, 1));
assertFalse(extended.hasAvailableBytes(5, 2));
assertFalse(extended.hasAvailableBytes(5, 3));
assertFalse(extended.hasAvailableBytes(6, 1));
}
}

View File

@ -0,0 +1,116 @@
/* ###
* 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.features.base.memsearch.bytesequence;
import static org.junit.Assert.*;
import java.util.Iterator;
import org.junit.Before;
import org.junit.Test;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch;
import ghidra.features.base.memsearch.matcher.MaskedByteSequenceByteMatcher;
public class MaskedBytesSequenceByteMatcherTest {
private ExtendedByteSequence byteSequence;
@Before
public void setUp() {
ByteSequence main = new ByteArrayByteSequence(makeBytes(1, 2, 3, 2, 4, 5, 2, 6, 2, 3, 2));
ByteSequence extra = new ByteArrayByteSequence(makeBytes(4, 1, 1, 3, 2, 4));
byteSequence = new ExtendedByteSequence(main, extra, 100);
}
@Test
public void testSimplePatterWithOneMatchCrossingBoundary() {
byte[] searchBytes = makeBytes(3, 2, 4);
ByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null);
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
assertTrue(it.hasNext());
assertEquals(new ByteMatch(2, 3), it.next());
assertTrue(it.hasNext());
assertEquals(new ByteMatch(9, 3), it.next());
assertFalse(it.hasNext());
}
@Test
public void testSimplePatterWithOneMatchCrossingBoundaryNoHasNextCalls() {
byte[] searchBytes = makeBytes(3, 2, 4);
ByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null);
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
assertEquals(new ByteMatch(2, 3), it.next());
assertEquals(new ByteMatch(9, 3), it.next());
assertNull(it.next());
}
@Test
public void testMaskPattern() {
byte[] searchBytes = makeBytes(2, 0, 2);
byte[] masks = makeBytes(0xff, 0x00, 0xff);
ByteMatcher byteMatcher =
new MaskedByteSequenceByteMatcher("", searchBytes, masks, null);
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
assertEquals(new ByteMatch(1, 3), it.next());
assertEquals(new ByteMatch(6, 3), it.next());
assertEquals(new ByteMatch(8, 3), it.next());
assertNull(it.next());
}
@Test
public void testPatternStartButNotEnoughExtraBytes() {
byte[] searchBytes = makeBytes(6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
byte[] masks = makeBytes(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
ByteMatcher byteMatcher =
new MaskedByteSequenceByteMatcher("", searchBytes, masks, null);
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
assertFalse(it.hasNext());
}
@Test
public void testGetDescription() {
byte[] searchBytes = makeBytes(1, 2, 3, 0xaa);
ByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null);
assertEquals("01 02 03 aa", byteMatcher.getDescription());
}
private static byte[] makeBytes(int... byteValues) {
byte[] bytes = new byte[byteValues.length];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) byteValues[i];
}
return bytes;
}
}

Some files were not shown because too many files have changed in this diff Show More