mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
Merge remote-tracking branch 'origin/Ghidra_11.4'
This commit is contained in:
commit
db8e21463b
14 changed files with 321 additions and 217 deletions
|
@ -43,6 +43,9 @@ python3 -m pip install psutil protobuf==3.20.3
|
||||||
</LI>
|
</LI>
|
||||||
</UL>
|
</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
|
<P>If you are offline, or would like to use our provided packages, we still use Pip, but with a
|
||||||
more complicated invocation:</P>
|
more complicated invocation:</P>
|
||||||
|
|
||||||
|
@ -205,5 +208,63 @@ perl -i -pe 's/(?<=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
|
<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
|
necessary for connecting to the Android debugger, but executes via the normal lldb
|
||||||
mechanism.</P>
|
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> C:\path\to\android-ndk\...\lldb
|
||||||
|
(lldb) script
|
||||||
|
>>> import sys
|
||||||
|
>>> sys.version
|
||||||
|
[copy down the version indicated]
|
||||||
|
>>> 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> C:\path\to\android-ndk\...\python -m ensurepip
|
||||||
|
PS> 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>
|
</BODY>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
|
|
@ -178,7 +178,7 @@ public class DtFilterDialog extends DialogComponentProvider {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.typeCb = new GCheckBox(type);
|
this.typeCb = new GCheckBox(type);
|
||||||
this.typeDefCb = new GCheckBox();
|
this.typeDefCb = new GCheckBox();
|
||||||
this.typeDefCb.setName(type + "Typedefs");
|
this.typeDefCb.setName(type + "TypeDefs");
|
||||||
}
|
}
|
||||||
|
|
||||||
JComponent getLeft() {
|
JComponent getLeft() {
|
||||||
|
|
|
@ -137,40 +137,38 @@ public class DtFilterState {
|
||||||
DataType baseDt = DataTypeUtils.getBaseDataType(dt);
|
DataType baseDt = DataTypeUtils.getBaseDataType(dt);
|
||||||
|
|
||||||
if (dt instanceof Array) {
|
if (dt instanceof Array) {
|
||||||
return passes(arraysFilter, dt, baseDt);
|
return passes(arraysFilter, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dt instanceof Pointer) {
|
if (dt instanceof Pointer) {
|
||||||
return passes(pointersFilter, dt, baseDt);
|
return passes(pointersFilter, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseDt instanceof Enum) {
|
if (baseDt instanceof Enum) {
|
||||||
return passes(enumsFilter, dt, baseDt);
|
return passes(enumsFilter, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseDt instanceof Function) {
|
if (baseDt instanceof Function) {
|
||||||
return passes(functionsFilter, dt, baseDt);
|
return passes(functionsFilter, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseDt instanceof Structure) {
|
if (baseDt instanceof Structure) {
|
||||||
return passes(structuresFilter, dt, baseDt);
|
return passes(structuresFilter, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseDt instanceof Union) {
|
if (baseDt instanceof Union) {
|
||||||
return passes(unionsFilter, dt, baseDt);
|
return passes(unionsFilter, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean passes(DtTypeFilter filter, DataType dt, DataType baseDt) {
|
private boolean passes(DtTypeFilter filter, DataType dt) {
|
||||||
if (filter.isTypeActive()) {
|
if (dt instanceof TypeDef) {
|
||||||
return true;
|
return filter.isTypeDefActive();
|
||||||
}
|
}
|
||||||
if (filter.isTypeDefActive() && dt instanceof TypeDef) {
|
|
||||||
return true;
|
return filter.isTypeActive();
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(SaveState parentSaveState) {
|
public void save(SaveState parentSaveState) {
|
||||||
|
|
|
@ -65,50 +65,57 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
|
||||||
return loadSpecs;
|
return loadSpecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Efficient check to fail fast
|
// This loader can handle both Mach-O files as well as Universal Binary files. If it's a
|
||||||
byte[] magicBytes = provider.readBytes(0, 4);
|
// Universal Binary, each Mach-O it contains will be presented as a single "preferred"
|
||||||
if (!MachConstants.isMagic(LittleEndianDataConverter.INSTANCE.getInt(magicBytes))) {
|
// load spec, forcing the user to have to select the desired processor from the import
|
||||||
return loadSpecs;
|
// dialog.
|
||||||
|
List<ByteProvider> allProviders = new ArrayList<>();
|
||||||
|
boolean onlyPreferred;
|
||||||
|
if (isUniveralBinary(provider)) {
|
||||||
|
allProviders.addAll(getUniveralBinaryProviders(provider));
|
||||||
|
onlyPreferred = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
allProviders.add(provider);
|
||||||
|
onlyPreferred = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
for (ByteProvider machoProvider : allProviders) {
|
||||||
MachHeader machHeader = new MachHeader(provider);
|
byte[] magicBytes = machoProvider.readBytes(0, 4);
|
||||||
String magic =
|
if (!MachConstants.isMagic(LittleEndianDataConverter.INSTANCE.getInt(magicBytes))) {
|
||||||
CpuTypes.getMagicString(machHeader.getCpuType(), machHeader.getCpuSubType());
|
continue;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
if (loadSpecs.isEmpty()) {
|
try {
|
||||||
loadSpecs.add(new LoadSpec(this, machHeader.getImageBase(), true));
|
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;
|
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
|
@Override
|
||||||
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
|
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
|
||||||
Program program, TaskMonitor monitor, MessageLog log) throws IOException {
|
Program program, TaskMonitor monitor, MessageLog log) throws IOException {
|
||||||
|
|
||||||
|
if (isUniveralBinary(provider)) {
|
||||||
|
provider = matchUniversalBinaryProvider(provider, loadSpec, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
|
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
|
||||||
|
|
||||||
|
@ -292,16 +299,6 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
|
||||||
return null;
|
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}
|
* {@inheritDoc}
|
||||||
* <p>
|
* <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}
|
* {@inheritDoc}
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -429,6 +399,141 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
|
||||||
monitor);
|
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}
|
* "Reexports" symbols from to a {@link Program}
|
||||||
*
|
*
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.app.plugin.core.datamgr;
|
package ghidra.app.plugin.core.datamgr;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.Container;
|
import java.awt.Container;
|
||||||
|
@ -959,7 +960,7 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
// Now also turn off typedefs
|
// Now also turn off typedefs
|
||||||
performAction(action, provider, false);
|
performAction(action, provider, false);
|
||||||
dialog = waitForDialogComponent(DtFilterDialog.class);
|
dialog = waitForDialogComponent(DtFilterDialog.class);
|
||||||
setToggleButtonSelected(dialog.getComponent(), "StructuresTypedefs", false);
|
setToggleButtonSelected(dialog.getComponent(), "StructuresTypeDefs", false);
|
||||||
pressButtonByText(dialog, "OK");
|
pressButtonByText(dialog, "OK");
|
||||||
waitForTree();
|
waitForTree();
|
||||||
|
|
||||||
|
@ -967,6 +968,26 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
assertType("TypeDefToMyStruct", false);
|
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
|
@Test
|
||||||
public void testFilter_ClonedProvider() {
|
public void testFilter_ClonedProvider() {
|
||||||
|
|
||||||
|
@ -1058,14 +1079,14 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, DataTypeNode> getNodes(DataTypeManager dtm) {
|
private Map<String, DataTypeNode> getNodes(DataTypeManager dtm) {
|
||||||
|
|
||||||
DataTypeArchiveGTree gTree = provider.getGTree();
|
DataTypeArchiveGTree gTree = provider.getGTree();
|
||||||
GTreeNode rootNode = gTree.getViewRoot();
|
GTreeNode rootNode = gTree.getViewRoot();
|
||||||
GTreeNode dtmNode = rootNode.getChild(dtm.getName());
|
GTreeNode dtmNode = rootNode.getChild(dtm.getName());
|
||||||
assertNotNull(dtmNode);
|
assertNotNull(dtmNode);
|
||||||
|
|
||||||
expandNode(dtmNode);
|
expandNode(dtmNode);
|
||||||
|
|
||||||
Map<String, DataTypeNode> nodesByName = new HashMap<>();
|
Map<String, DataTypeNode> nodesByName = new HashMap<>();
|
||||||
Iterator<GTreeNode> it = dtmNode.iterator(true);
|
Iterator<GTreeNode> it = dtmNode.iterator(true);
|
||||||
for (GTreeNode node : CollectionUtils.asIterable(it)) {
|
for (GTreeNode node : CollectionUtils.asIterable(it)) {
|
||||||
|
@ -1076,7 +1097,7 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
DataType dt = dtNode.getDataType();
|
DataType dt = dtNode.getDataType();
|
||||||
nodesByName.put(dt.getName(), dtNode);
|
nodesByName.put(dt.getName(), dtNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodesByName;
|
return nodesByName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
|
||||||
private int s1f3Offset = 50;
|
private int s1f3Offset = 50;
|
||||||
private ArrayDataType structArray;
|
private ArrayDataType structArray;
|
||||||
private StructureDataType struct2DT;
|
private StructureDataType struct2DT;
|
||||||
|
private StructureDataType struct3DT;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
|
@ -68,6 +69,9 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
|
||||||
struct2DT.replaceAtOffset(0, intDT, -1, "f1", null);
|
struct2DT.replaceAtOffset(0, intDT, -1, "f1", null);
|
||||||
struct2DT.replaceAtOffset(10, struct1DT, -1, "f2", 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);
|
builder.createMemory("test", "0x0", 0x20000);
|
||||||
program = builder.getProgram();
|
program = builder.getProgram();
|
||||||
}
|
}
|
||||||
|
@ -144,6 +148,19 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
|
||||||
assertEquals(s1ArrayElemIndex + 2, list.size());
|
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) {
|
private long arrayElementAddr(long arrayAddr, int elemSize, int elemIndex) {
|
||||||
return arrayAddr + (elemSize * elemIndex);
|
return arrayAddr + (elemSize * elemIndex);
|
||||||
}
|
}
|
||||||
|
@ -155,7 +172,7 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
|
||||||
|
|
||||||
builder.applyFixedLengthDataType("0x0", intDT, -1); // +1 candidate count
|
builder.applyFixedLengthDataType("0x0", intDT, -1); // +1 candidate count
|
||||||
builder.createString(addrStr(str1Addr), "test1", StandardCharsets.UTF_8, true, stringDT); // +1
|
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);
|
DataType byteArray = new ArrayDataType(ByteDataType.dataType, 2000, -1);
|
||||||
builder.applyFixedLengthDataType("0x1000", byteArray, -1); // +1
|
builder.applyFixedLengthDataType("0x1000", byteArray, -1); // +1
|
||||||
|
@ -163,7 +180,7 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration
|
||||||
DefinedStringIterator it = DefinedStringIterator.forProgram(program);
|
DefinedStringIterator it = DefinedStringIterator.forProgram(program);
|
||||||
List<Data> list = IteratorUtils.toList(it);
|
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
|
assertEquals(21, list.size()); // 1 ds@0x10 + 2 per structArray element
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,9 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
||||||
|
|
||||||
File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
|
File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
|
||||||
if (pdbFile == null) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +158,7 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
||||||
* Normally the analyzer would locate the PDB file on its own, but if a
|
* Normally the analyzer would locate the PDB file on its own, but if a
|
||||||
* headless script wishes to override the analyzer's behaivor, it can
|
* headless script wishes to override the analyzer's behaivor, it can
|
||||||
* use this method to specify a file.
|
* use this method to specify a file.
|
||||||
*
|
*
|
||||||
* @param program {@link Program}
|
* @param program {@link Program}
|
||||||
* @param pdbFile the pdb file
|
* @param pdbFile the pdb file
|
||||||
*/
|
*/
|
||||||
|
@ -171,7 +173,7 @@ public class PdbAnalyzer extends AbstractAnalyzer {
|
||||||
* Normally when the analyzer attempts to locate a matching PDB file it
|
* Normally when the analyzer attempts to locate a matching PDB file it
|
||||||
* will default to NOT searching untrusted symbol servers. A headless script could
|
* will default to NOT searching untrusted symbol servers. A headless script could
|
||||||
* use this method to allow the analyzer to search untrusted symbol servers.
|
* use this method to allow the analyzer to search untrusted symbol servers.
|
||||||
*
|
*
|
||||||
* @param program {@link Program}
|
* @param program {@link Program}
|
||||||
* @param allowUntrusted boolean flag, true means analyzer can search untrusted symbol
|
* @param allowUntrusted boolean flag, true means analyzer can search untrusted symbol
|
||||||
* servers
|
* servers
|
||||||
|
|
|
@ -167,7 +167,9 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer {
|
||||||
pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
|
pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor);
|
||||||
}
|
}
|
||||||
if (pdbFile == null) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,7 @@ public class DefinedStringIterator implements DataIterator {
|
||||||
|
|
||||||
private static class StructDtcIterator implements DataIterator {
|
private static class StructDtcIterator implements DataIterator {
|
||||||
private Data data;
|
private Data data;
|
||||||
private int currentIndex;
|
private int currentIndex = -1;
|
||||||
private DataTypeComponent[] dtcs;
|
private DataTypeComponent[] dtcs;
|
||||||
|
|
||||||
public StructDtcIterator(Data data, Composite compDT) {
|
public StructDtcIterator(Data data, Composite compDT) {
|
||||||
|
|
|
@ -388,6 +388,12 @@ public abstract class AbstractGdbTraceRmiTest extends AbstractGhidraHeadedDebugg
|
||||||
return new MemDump(address, buf.toByteArray());
|
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 {
|
protected ManagedDomainObject openDomainObject(String path) throws Exception {
|
||||||
DomainFile df = env.getProject().getProjectData().getFile(path);
|
DomainFile df = env.getProject().getProjectData().getFile(path);
|
||||||
assertNotNull(df);
|
assertNotNull(df);
|
||||||
|
|
|
@ -160,7 +160,6 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStopTrace() throws Exception {
|
public void testStopTrace() throws Exception {
|
||||||
// TODO: This test assumes gdb and the target file bash are x86-64
|
|
||||||
runThrowError(addr -> """
|
runThrowError(addr -> """
|
||||||
%s
|
%s
|
||||||
ghidra trace connect %s
|
ghidra trace connect %s
|
||||||
|
@ -169,10 +168,8 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
|
||||||
ghidra trace stop
|
ghidra trace stop
|
||||||
quit
|
quit
|
||||||
""".formatted(PREAMBLE, addr));
|
""".formatted(PREAMBLE, addr));
|
||||||
DomainFile dfBash = env.getProject().getProjectData().getFile("/New Traces/gdb/bash");
|
// NOTE: Given the 'quit' command, I'm not sure this assertion is checking anything.
|
||||||
assertNotNull(dfBash);
|
waitDomainObjectClosed("/New Traces/gdb/bash");
|
||||||
// TODO: Given the 'quit' command, I'm not sure this assertion is checking anything.
|
|
||||||
assertFalse(dfBash.isOpen());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -278,6 +275,7 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
|
||||||
ghidra trace tx-commit
|
ghidra trace tx-commit
|
||||||
quit
|
quit
|
||||||
""".formatted(PREAMBLE, addr));
|
""".formatted(PREAMBLE, addr));
|
||||||
|
waitDomainObjectClosed("/New Traces/no-save");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/no-save")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/no-save")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
assertEquals(0, tb.trace.getTimeManager().getAllSnapshots().size());
|
assertEquals(0, tb.trace.getTimeManager().getAllSnapshots().size());
|
||||||
|
@ -294,6 +292,7 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
|
||||||
ghidra trace save
|
ghidra trace save
|
||||||
quit
|
quit
|
||||||
""".formatted(PREAMBLE, addr));
|
""".formatted(PREAMBLE, addr));
|
||||||
|
waitDomainObjectClosed("/New Traces/save");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/save")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/save")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
assertEquals(1, tb.trace.getTimeManager().getAllSnapshots().size());
|
assertEquals(1, tb.trace.getTimeManager().getAllSnapshots().size());
|
||||||
|
|
|
@ -418,6 +418,12 @@ public abstract class AbstractLldbTraceRmiTest extends AbstractGhidraHeadedDebug
|
||||||
return new MemDump(address, buf.toByteArray());
|
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 {
|
protected ManagedDomainObject openDomainObject(String path) throws Exception {
|
||||||
DomainFile df = env.getProject().getProjectData().getFile(path);
|
DomainFile df = env.getProject().getProjectData().getFile(path);
|
||||||
assertNotNull(df);
|
assertNotNull(df);
|
||||||
|
|
|
@ -167,10 +167,8 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
|
||||||
ghidra trace stop
|
ghidra trace stop
|
||||||
quit
|
quit
|
||||||
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
|
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
|
||||||
DomainFile df = env.getProject().getProjectData().getFile("/New Traces/lldb/expPrint");
|
// NOTE: Given the 'quit' command, I'm not sure this assertion is checking anything.
|
||||||
assertNotNull(df);
|
waitDomainObjectClosed("/New Traces/lldb/expPrint");
|
||||||
// TODO: Given the 'quit' command, I'm not sure this assertion is checking anything.
|
|
||||||
assertFalse(df.isOpen());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -270,6 +268,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
|
||||||
ghidra trace tx-commit
|
ghidra trace tx-commit
|
||||||
quit
|
quit
|
||||||
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
|
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
|
||||||
|
waitDomainObjectClosed("/New Traces/no-save");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/no-save")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/no-save")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
assertEquals(0, tb.trace.getTimeManager().getAllSnapshots().size());
|
assertEquals(0, tb.trace.getTimeManager().getAllSnapshots().size());
|
||||||
|
@ -286,6 +285,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
|
||||||
ghidra trace save
|
ghidra trace save
|
||||||
quit
|
quit
|
||||||
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
|
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
|
||||||
|
waitDomainObjectClosed("/New Traces/save");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/save")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/save")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
assertEquals(1, tb.trace.getTimeManager().getAllSnapshots().size());
|
assertEquals(1, tb.trace.getTimeManager().getAllSnapshots().size());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue