Merge remote-tracking branch 'origin/Ghidra_11.4'

This commit is contained in:
Ryan Kurtz 2025-05-16 14:25:58 -04:00
commit db8e21463b
14 changed files with 321 additions and 217 deletions

View file

@ -43,6 +43,9 @@ python3 -m pip install psutil protobuf==3.20.3
</LI>
</UL>
<P>If you're using <TT>lldb</TT> from the Android NDK and do not have Pip, see <A href=
"#setup_ndk">Setup for Android NDK</A></P>
<P>If you are offline, or would like to use our provided packages, we still use Pip, but with a
more complicated invocation:</P>
@ -205,5 +208,63 @@ perl -i -pe 's/(?&lt;=pendingNMI\x00{4})\x00/\x01/' macOS_15-1234567.vmss
<P>This has the same options as the <A href="#ssh">LLDB via SSH</A> launcher, which are
necessary for connecting to the Android debugger, but executes via the normal lldb
mechanism.</P>
<H3><A name="setup_ndk"></A>Setup for Android NDK</H3>
<P>If you're using the copy of <TT>lldb</TT> included with the Android NDK (Native Development
Kit), it may not include <TT>pip</TT>. Notably, this is the case on Windows at the time of
writing. Fortunately, you can retrieve the components to install Pip into the NDK from an
official Python distribution.</P>
<OL>
<LI>
First, figure out the version of Python that is embedded in the NDK's build of LLDB, and
get its path. (If you know the path to lldb, you probably already know the path to its
Python.) From a Windows Command Prompt or Powershell:
<PRE>
PS&gt; C:\path\to\android-ndk\...\lldb
(lldb) script
&gt;&gt;&gt; import sys
&gt;&gt;&gt; sys.version
[copy down the version indicated]
&gt;&gt;&gt; sys.path
[look for the paths ending with Lib and DLLs, and copy them down]
</PRE>
</LI>
<LI>Now, obtain the same version of Python from the official Python website, and install or
unpack it.</LI>
<LI>Locate your new installation of Python. If you don't already know where it landed, this
can be found by examining the Properties of the Python shortcut in your Start Menu.</LI>
<LI>There should be a <TT>Lib\ensurepip</TT> directory in the official Python installation.
Copy this into the same place in the Android NDK's build of Python.</LI>
<LI>
There are also three native modules that need to be copied from the official Python's
<TT>DLLs\</TT> directory to the same in the NDK's build. This is to support SSL for
downloading packages from PyPI: (Substitue the ??'s appropriately.)
<UL>
<LI><TT>_ssl.pyd</TT></LI>
<LI><TT>libssl-??.dll</TT></LI>
<LI><TT>libcrypto-??.dll</TT></LI>
</UL>
</LI>
<LI>
We should now have enough to bootstrap the NDK's Python with Pip. Again at the Windows
Command Prompt or Powershell:
<PRE>
PS&gt; C:\path\to\android-ndk\...\python -m ensurepip
PS&gt; C:\path\to\android-ndk\...\python -m pip install ...
</PRE>
See the <A href="#setup">Setup</A> section for the arguments to pass to <TT>pip install
...</TT>.
</LI>
</OL>
</BODY>
</HTML>

View file

@ -178,7 +178,7 @@ public class DtFilterDialog extends DialogComponentProvider {
this.type = type;
this.typeCb = new GCheckBox(type);
this.typeDefCb = new GCheckBox();
this.typeDefCb.setName(type + "Typedefs");
this.typeDefCb.setName(type + "TypeDefs");
}
JComponent getLeft() {

View file

@ -137,40 +137,38 @@ public class DtFilterState {
DataType baseDt = DataTypeUtils.getBaseDataType(dt);
if (dt instanceof Array) {
return passes(arraysFilter, dt, baseDt);
return passes(arraysFilter, dt);
}
if (dt instanceof Pointer) {
return passes(pointersFilter, dt, baseDt);
return passes(pointersFilter, dt);
}
if (baseDt instanceof Enum) {
return passes(enumsFilter, dt, baseDt);
return passes(enumsFilter, dt);
}
if (baseDt instanceof Function) {
return passes(functionsFilter, dt, baseDt);
return passes(functionsFilter, dt);
}
if (baseDt instanceof Structure) {
return passes(structuresFilter, dt, baseDt);
return passes(structuresFilter, dt);
}
if (baseDt instanceof Union) {
return passes(unionsFilter, dt, baseDt);
return passes(unionsFilter, dt);
}
return true;
}
private boolean passes(DtTypeFilter filter, DataType dt, DataType baseDt) {
if (filter.isTypeActive()) {
return true;
private boolean passes(DtTypeFilter filter, DataType dt) {
if (dt instanceof TypeDef) {
return filter.isTypeDefActive();
}
if (filter.isTypeDefActive() && dt instanceof TypeDef) {
return true;
}
return false;
return filter.isTypeActive();
}
public void save(SaveState parentSaveState) {

View file

@ -65,50 +65,57 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
return loadSpecs;
}
// Efficient check to fail fast
byte[] magicBytes = provider.readBytes(0, 4);
if (!MachConstants.isMagic(LittleEndianDataConverter.INSTANCE.getInt(magicBytes))) {
return loadSpecs;
// This loader can handle both Mach-O files as well as Universal Binary files. If it's a
// Universal Binary, each Mach-O it contains will be presented as a single "preferred"
// load spec, forcing the user to have to select the desired processor from the import
// dialog.
List<ByteProvider> allProviders = new ArrayList<>();
boolean onlyPreferred;
if (isUniveralBinary(provider)) {
allProviders.addAll(getUniveralBinaryProviders(provider));
onlyPreferred = true;
}
else {
allProviders.add(provider);
onlyPreferred = false;
}
try {
MachHeader machHeader = new MachHeader(provider);
String magic =
CpuTypes.getMagicString(machHeader.getCpuType(), machHeader.getCpuSubType());
String compiler = detectCompilerName(machHeader);
List<QueryResult> results = QueryOpinionService.query(MACH_O_NAME, magic, compiler);
for (QueryResult result : results) {
loadSpecs.add(new LoadSpec(this, machHeader.getImageBase(), result));
for (ByteProvider machoProvider : allProviders) {
byte[] magicBytes = machoProvider.readBytes(0, 4);
if (!MachConstants.isMagic(LittleEndianDataConverter.INSTANCE.getInt(magicBytes))) {
continue;
}
if (loadSpecs.isEmpty()) {
loadSpecs.add(new LoadSpec(this, machHeader.getImageBase(), true));
try {
MachHeader machHeader = new MachHeader(machoProvider);
String magic =
CpuTypes.getMagicString(machHeader.getCpuType(), machHeader.getCpuSubType());
String compiler = detectCompilerName(machHeader);
List<QueryResult> results = QueryOpinionService.query(MACH_O_NAME, magic, compiler);
for (QueryResult result : results) {
if (!onlyPreferred || result.preferred) {
loadSpecs.add(new LoadSpec(this, machHeader.getImageBase(), result));
}
}
if (loadSpecs.isEmpty() && !onlyPreferred) {
loadSpecs.add(new LoadSpec(this, machHeader.getImageBase(), true));
}
}
catch (MachException e) {
// not a problem, just don't add it
}
}
catch (MachException e) {
// not a problem, just don't add it
}
return loadSpecs;
}
private String detectCompilerName(MachHeader machHeader) throws IOException {
List<String> sectionNames = machHeader.parseSegments()
.stream()
.flatMap(seg -> seg.getSections().stream())
.map(section -> section.getSectionName())
.toList();
if (SwiftUtils.isSwift(sectionNames)) {
return SwiftUtils.SWIFT_COMPILER;
}
if (GoRttiMapper.hasGolangSections(sectionNames)) {
return GoConstants.GOLANG_CSPEC_NAME;
}
return null;
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, TaskMonitor monitor, MessageLog log) throws IOException {
if (isUniveralBinary(provider)) {
provider = matchUniversalBinaryProvider(provider, loadSpec, monitor);
}
try {
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
@ -292,16 +299,6 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
return null;
}
/**
* Checks to see if reexports should be performed
*
* @param options a {@link List} of {@link Option}s
* @return True if reexports should be performed; otherwise, false
*/
private boolean shouldPerformReexports(List<Option> options) {
return OptionUtils.getOption(REEXPORT_OPTION_NAME, options, REEXPORT_OPTION_DEFAULT);
}
/**
* {@inheritDoc}
* <p>
@ -361,33 +358,6 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
}
/**
* Gets a {@link List} of reexport library paths from the given {@link Program}
*
* @param program The {@link Program}
* @param log The log
* @return A {@link List} of reexport library paths from the given {@link Program}
* @throws MachException if there was a problem parsing the Mach-O {@link Program}
* @throws IOException if there was an IO-related error
*/
private List<String> getReexportPaths(Program program, MessageLog log)
throws MachException, IOException {
Symbol header =
program.getSymbolTable().getSymbols(MachoProgramBuilder.HEADER_SYMBOL).next();
if (header == null) {
log.appendMsg("Failed to lookup reexport paths...couldn't find '%s' symbol"
.formatted(MachoProgramBuilder.HEADER_SYMBOL));
return List.of();
}
ByteProvider p = new MemoryByteProvider(program.getMemory(), header.getAddress());
return new MachHeader(p).parseReexports()
.stream()
.map(DynamicLibraryCommand::getDynamicLibrary)
.map(DynamicLibrary::getName)
.map(LoadCommandString::getString)
.toList();
}
/**
* {@inheritDoc}
* <p>
@ -429,6 +399,141 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
monitor);
}
/**
* Checks to see if the given {@link ByteProvider} is a Universal Binary
*
* @param provider The {@link ByteProvider} to check
* @return True if the given {@link ByteProvider} is a Universal Binary; otherwise, false
* @throws IOException if there was an IO-related error
*/
private boolean isUniveralBinary(ByteProvider provider) throws IOException {
BinaryReader reader = new BinaryReader(provider, false);
int magic = reader.readInt(0);
return magic == FatHeader.FAT_MAGIC || magic == FatHeader.FAT_CIGAM;
}
/**
* Gets a {@link List} of {@link ByteProviderWrapper}s, one for each entry in the Universal
* Binary
*
* @param provider The Universal Binary's provider
* @return A {@link List} of {@link ByteProviderWrapper}s, one for each entry in the Universal
* Binary
* @throws IOException if an IO-related error occurred
*/
private List<ByteProviderWrapper> getUniveralBinaryProviders(ByteProvider provider)
throws IOException {
List<ByteProviderWrapper> wrappers = new ArrayList<>();
try {
FatHeader fatHeader = new FatHeader(provider);
List<Long> machStarts = fatHeader.getMachStarts();
List<Long> machSizes = fatHeader.getMachSizes();
for (int i = 0; i < machStarts.size(); i++) {
wrappers.add(new ByteProviderWrapper(provider, machStarts.get(i), machSizes.get(i),
provider.getFSRL()));
}
}
catch (MachException | UbiException e) {
// not a problem, just don't add it
}
return wrappers;
}
/**
* Attempts to match a Mach-O entry in the given Universal Binary {@link ByteProvider} to the
* given {@link LoadSpec}
*
* @param provider A Universal Binary {@link ByteProvider}
* @param loadSpec The {@link LoadSpec} to match
* @param monitor A {@link TaskMonitor monitor}
* @return The matched Mach-O {@link ByteProvider}, or {@code null} if a match was not found
* @throws IOException if an IO-related error occurred
*/
private ByteProvider matchUniversalBinaryProvider(ByteProvider provider, LoadSpec loadSpec,
TaskMonitor monitor) throws IOException {
ByteProvider ret = null;
boolean stop = false;
for (ByteProvider machoProvider : getUniveralBinaryProviders(provider)) {
for (LoadSpec ls : findSupportedLoadSpecs(machoProvider)) {
if (monitor.isCancelled()) {
stop = true;
break;
}
if (loadSpec.getLanguageCompilerSpec().equals(ls.getLanguageCompilerSpec())) {
ret = machoProvider;
stop = true;
break;
}
}
if (stop) {
break;
}
}
if (ret == null) {
throw new IOException("Failed to match the load spec to a Universal Binary Mach-O");
}
return ret;
}
/**
* Attempts to detect a more specific compiler from the Mach-O
*
* @param machHeader The {@link MachHeader}
* @return The detected compiler name, or {@code null} if one could be detected
* @throws IOException if an IO-related error occurred
*/
private String detectCompilerName(MachHeader machHeader) throws IOException {
List<String> sectionNames = machHeader.parseSegments()
.stream()
.flatMap(seg -> seg.getSections().stream())
.map(section -> section.getSectionName())
.toList();
if (SwiftUtils.isSwift(sectionNames)) {
return SwiftUtils.SWIFT_COMPILER;
}
if (GoRttiMapper.hasGolangSections(sectionNames)) {
return GoConstants.GOLANG_CSPEC_NAME;
}
return null;
}
/**
* Checks to see if reexports should be performed
*
* @param options a {@link List} of {@link Option}s
* @return True if reexports should be performed; otherwise, false
*/
private boolean shouldPerformReexports(List<Option> options) {
return OptionUtils.getOption(REEXPORT_OPTION_NAME, options, REEXPORT_OPTION_DEFAULT);
}
/**
* Gets a {@link List} of reexport library paths from the given {@link Program}
*
* @param program The {@link Program}
* @param log The log
* @return A {@link List} of reexport library paths from the given {@link Program}
* @throws MachException if there was a problem parsing the Mach-O {@link Program}
* @throws IOException if there was an IO-related error
*/
private List<String> getReexportPaths(Program program, MessageLog log)
throws MachException, IOException {
Symbol header =
program.getSymbolTable().getSymbols(MachoProgramBuilder.HEADER_SYMBOL).next();
if (header == null) {
log.appendMsg("Failed to lookup reexport paths...couldn't find '%s' symbol"
.formatted(MachoProgramBuilder.HEADER_SYMBOL));
return List.of();
}
ByteProvider p = new MemoryByteProvider(program.getMemory(), header.getAddress());
return new MachHeader(p).parseReexports()
.stream()
.map(DynamicLibraryCommand::getDynamicLibrary)
.map(DynamicLibrary::getName)
.map(LoadCommandString::getString)
.toList();
}
/**
* "Reexports" symbols from to a {@link Program}
*

View file

@ -1,113 +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.util.opinion;
import java.io.IOException;
import java.util.*;
import ghidra.app.util.Option;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.format.macho.MachException;
import ghidra.app.util.bin.format.ubi.FatHeader;
import ghidra.app.util.bin.format.ubi.UbiException;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
/**
* A {@link Loader} for Mach-O files contained in a Universal Binary
*/
public class UniversalMachoLoader extends MachoLoader {
public final static String UNIVERSAL_MACH_O_NAME = "Universal Mach-O";
private static final long MIN_BYTE_LENGTH = 4;
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
List<LoadSpec> loadSpecs = new ArrayList<>();
// Efficient check to fail fast
if (provider.length() < MIN_BYTE_LENGTH) {
return loadSpecs;
}
// Efficient check to fail fast
BinaryReader reader = new BinaryReader(provider, false);
int magic = reader.readInt(0);
if (magic != FatHeader.FAT_MAGIC && magic != FatHeader.FAT_CIGAM) {
return loadSpecs;
}
// Only add the preferred load specs for each Mach-O so only 1 entry for each architecture
// shows up. Keep them all preferred though so the user is forced to pick one (headless
// will just use the "first preferred" by default)
for (ByteProvider wrapper : getWrappers(provider)) {
super.findSupportedLoadSpecs(wrapper).stream()
.filter(LoadSpec::isPreferred)
.forEach(loadSpecs::add);
}
return loadSpecs;
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, TaskMonitor monitor, MessageLog log) throws IOException {
for (ByteProvider wrapper : getWrappers(provider)) {
for (LoadSpec ls : super.findSupportedLoadSpecs(wrapper)) {
if (monitor.isCancelled()) {
break;
}
if (loadSpec.getLanguageCompilerSpec().equals(ls.getLanguageCompilerSpec())) {
super.load(wrapper, loadSpec, options, program, monitor, log);
return;
}
}
}
}
@Override
public String getName() {
return UNIVERSAL_MACH_O_NAME;
}
/**
* Gets a {@link List} of {@link ByteProviderWrapper}s, one for each entry in the Universal
* Binary
*
* @param provider The Universal Binary's provider
* @return A {@link List} of {@link ByteProviderWrapper}s, one for each entry in the Universal
* Binary
* @throws IOException if an IO-related error occurred
*/
private List<ByteProviderWrapper> getWrappers(ByteProvider provider) throws IOException {
List<ByteProviderWrapper> wrappers = new ArrayList<>();
try {
FatHeader fatHeader = new FatHeader(provider);
List<Long> machStarts = fatHeader.getMachStarts();
List<Long> machSizes = fatHeader.getMachSizes();
for (int i = 0; i < machStarts.size(); i++) {
wrappers.add(new ByteProviderWrapper(provider, machStarts.get(i), machSizes.get(i),
provider.getFSRL()));
}
}
catch (MachException | UbiException e) {
// not a problem, just don't add it
}
return wrappers;
}
}

View file

@ -16,6 +16,7 @@
package ghidra.app.plugin.core.datamgr;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
import java.awt.Container;
@ -959,7 +960,7 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
// Now also turn off typedefs
performAction(action, provider, false);
dialog = waitForDialogComponent(DtFilterDialog.class);
setToggleButtonSelected(dialog.getComponent(), "StructuresTypedefs", false);
setToggleButtonSelected(dialog.getComponent(), "StructuresTypeDefs", false);
pressButtonByText(dialog, "OK");
waitForTree();
@ -967,6 +968,26 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
assertType("TypeDefToMyStruct", false);
}
@Test
public void testFilter_Structures_HideTypeDefs() {
assertStructures(true);
assertType("TypeDefToMyStruct", true);
// press the filter button
DockingActionIf action = getAction(plugin, "Show Filter");
performAction(action, provider, false);
DtFilterDialog dialog = waitForDialogComponent(DtFilterDialog.class);
// turn off Structure TypeDefs
setToggleButtonSelected(dialog.getComponent(), "StructuresTypeDefs", false);
pressButtonByText(dialog, "OK");
waitForTree();
assertStructures(true); // still have structures
assertType("TypeDefToMyStruct", false); // no longer have structure typedefs
}
@Test
public void testFilter_ClonedProvider() {

View file

@ -44,6 +44,7 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
private int s1f3Offset = 50;
private ArrayDataType structArray;
private StructureDataType struct2DT;
private StructureDataType struct3DT;
@Before
public void setUp() throws Exception {
@ -68,6 +69,9 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
struct2DT.replaceAtOffset(0, intDT, -1, "f1", null);
struct2DT.replaceAtOffset(10, struct1DT, -1, "f2", null);
struct3DT = new StructureDataType("struct3", 200);
struct3DT.replaceAtOffset(0, charArray, -1, "f1", null);
builder.createMemory("test", "0x0", 0x20000);
program = builder.getProgram();
}
@ -144,6 +148,19 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
assertEquals(s1ArrayElemIndex + 2, list.size());
}
@Test
public void test_StructFirstField() throws Exception {
// ensure we get the first field of a struct
int structAddr = 0x100;
builder.applyFixedLengthDataType(addrStr(structAddr), struct3DT, -1);
DefinedStringIterator it = DefinedStringIterator.forProgram(program);
List<Data> list = IteratorUtils.toList(it);
assertEquals(1, list.size());
}
private long arrayElementAddr(long arrayAddr, int elemSize, int elemIndex) {
return arrayAddr + (elemSize * elemIndex);
}
@ -155,7 +172,7 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
builder.applyFixedLengthDataType("0x0", intDT, -1); // +1 candidate count
builder.createString(addrStr(str1Addr), "test1", StandardCharsets.UTF_8, true, stringDT); // +1
builder.applyFixedLengthDataType(addrStr(s1ArrayAddr), structArray, -1); // +(1 + 10*3)
builder.applyFixedLengthDataType(addrStr(s1ArrayAddr), structArray, -1); // +(1 + 10 + 10*3)
DataType byteArray = new ArrayDataType(ByteDataType.dataType, 2000, -1);
builder.applyFixedLengthDataType("0x1000", byteArray, -1); // +1
@ -163,7 +180,7 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
DefinedStringIterator it = DefinedStringIterator.forProgram(program);
List<Data> list = IteratorUtils.toList(it);
assertEquals(34, it.getDataCandidateCount()); // 1 + 1 + 1 + 10*3
assertEquals(44, it.getDataCandidateCount()); // 1 + 1 + 1 + 10 + 10*3
assertEquals(21, list.size()); // 1 ds@0x10 + 2 per structArray element
}
}

View file

@ -87,7 +87,9 @@ public class PdbAnalyzer extends AbstractAnalyzer {
File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
if (pdbFile == null) {
// warnings have already been logged
// Warnings have already been logged, but nice to have a post-analysis pop-up that
// PDB analysis was not done
log.appendMsg(NAME, "Aborted: Could not find an appropriate PDB file; see log");
return false;
}

View file

@ -167,7 +167,9 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
}
if (pdbFile == null) {
// warnings have already been logged
// Warnings have already been logged, but nice to have a post-analysis pop-up that
// PDB analysis was not done
log.appendMsg(NAME, "Aborted: Could not find an appropriate PDB file; see log");
return false;
}

View file

@ -177,7 +177,7 @@ public class DefinedStringIterator implements DataIterator {
private static class StructDtcIterator implements DataIterator {
private Data data;
private int currentIndex;
private int currentIndex = -1;
private DataTypeComponent[] dtcs;
public StructDtcIterator(Data data, Composite compDT) {

View file

@ -388,6 +388,12 @@ public abstract class AbstractGdbTraceRmiTest extends AbstractGhidraHeadedDebugg
return new MemDump(address, buf.toByteArray());
}
protected void waitDomainObjectClosed(String path) {
DomainFile df = env.getProject().getProjectData().getFile(path);
assertNotNull(df);
waitForPass(() -> assertFalse(df.isOpen()));
}
protected ManagedDomainObject openDomainObject(String path) throws Exception {
DomainFile df = env.getProject().getProjectData().getFile(path);
assertNotNull(df);

View file

@ -160,7 +160,6 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
@Test
public void testStopTrace() throws Exception {
// TODO: This test assumes gdb and the target file bash are x86-64
runThrowError(addr -> """
%s
ghidra trace connect %s
@ -169,10 +168,8 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
ghidra trace stop
quit
""".formatted(PREAMBLE, addr));
DomainFile dfBash = env.getProject().getProjectData().getFile("/New Traces/gdb/bash");
assertNotNull(dfBash);
// TODO: Given the 'quit' command, I'm not sure this assertion is checking anything.
assertFalse(dfBash.isOpen());
// NOTE: Given the 'quit' command, I'm not sure this assertion is checking anything.
waitDomainObjectClosed("/New Traces/gdb/bash");
}
@Test
@ -278,6 +275,7 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
ghidra trace tx-commit
quit
""".formatted(PREAMBLE, addr));
waitDomainObjectClosed("/New Traces/no-save");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/no-save")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
assertEquals(0, tb.trace.getTimeManager().getAllSnapshots().size());
@ -294,6 +292,7 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
ghidra trace save
quit
""".formatted(PREAMBLE, addr));
waitDomainObjectClosed("/New Traces/save");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/save")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
assertEquals(1, tb.trace.getTimeManager().getAllSnapshots().size());

View file

@ -418,6 +418,12 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
return new MemDump(address, buf.toByteArray());
}
protected void waitDomainObjectClosed(String path) {
DomainFile df = env.getProject().getProjectData().getFile(path);
assertNotNull(df);
waitForPass(() -> assertFalse(df.isOpen()));
}
protected ManagedDomainObject openDomainObject(String path) throws Exception {
DomainFile df = env.getProject().getProjectData().getFile(path);
assertNotNull(df);

View file

@ -167,10 +167,8 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
ghidra trace stop
quit
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
DomainFile df = env.getProject().getProjectData().getFile("/New Traces/lldb/expPrint");
assertNotNull(df);
// TODO: Given the 'quit' command, I'm not sure this assertion is checking anything.
assertFalse(df.isOpen());
// NOTE: Given the 'quit' command, I'm not sure this assertion is checking anything.
waitDomainObjectClosed("/New Traces/lldb/expPrint");
}
@Test
@ -270,6 +268,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
ghidra trace tx-commit
quit
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
waitDomainObjectClosed("/New Traces/no-save");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/no-save")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
assertEquals(0, tb.trace.getTimeManager().getAllSnapshots().size());
@ -286,6 +285,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
ghidra trace save
quit
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
waitDomainObjectClosed("/New Traces/save");
try (ManagedDomainObject mdo = openDomainObject("/New Traces/save")) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
assertEquals(1, tb.trace.getTimeManager().getAllSnapshots().size());