GP-4559 Creating new Memory Search Feature that include dynamic change
detection
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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|
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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: </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>
|
@ -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>
|
||||
|
@ -12,467 +12,525 @@
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1>Search Memory</H1>
|
||||
|
||||
<P>Search Memory locates sequences of bytes in program memory. The search is based on a
|
||||
value entered as hex numbers, decimal numbers or strings. 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>
|
||||
|
||||
- or -<BR>
|
||||
Choose "Previous" to find the previous occurrence<BR>
|
||||
|
||||
- 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=""> </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. 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. Wildcard
|
||||
characters can be used to match any single hex digit (i.e. any 4 bit value). 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: </B></P>
|
||||
</TD>
|
||||
|
||||
<TD width="252">
|
||||
<P> "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 </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 </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> </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. 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
|
||||
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 (-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.
|
||||
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=""> </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
|
||||
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> </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> </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. 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> </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. </P>
|
||||
|
||||
<P><IMG alt="" border="0" src="help/shared/note.png"> Dismissing the search dialog
|
||||
automatically cancels the search operation.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3> </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. </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: </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=""> </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=
|
||||
""> </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"> 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"> 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"> 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"> 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"> 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"> 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"> 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"> 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"> 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><Shift>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. </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> </P>
|
||||
|
||||
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 33 KiB |
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 < 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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) {}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.*;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
BIN
Ghidra/Features/Base/src/main/resources/images/view_bottom.png
Normal file
After Width: | Height: | Size: 946 B |
After Width: | Height: | Size: 822 B |
After Width: | Height: | Size: 767 B |
BIN
Ghidra/Features/Base/src/main/resources/images/viewmag+.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Ghidra/Features/Base/src/main/resources/images/viewmag.png
Normal file
After Width: | Height: | Size: 813 B |
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|