Merge remote-tracking branch 'origin/GP-6382_ghidra_red_TenetImporter--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-02-12 05:08:24 -05:00
6 changed files with 860 additions and 0 deletions

View File

@@ -0,0 +1 @@
# Debugger-importers

View File

@@ -0,0 +1,37 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply from: "${rootProject.projectDir}/gradle/javaProject.gradle"
apply from: "${rootProject.projectDir}/gradle/helpProject.gradle"
apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
apply from: "${rootProject.projectDir}/gradle/javadoc.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-importers'
dependencies {
api project(':ProposedUtils')
api project(':Pty')
api project(':Debugger')
api project(':Debugger-api')
testImplementation project(path: ':Generic', configuration: 'testArtifacts')
testImplementation project(path: ':Debugger', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-TraceModeling', configuration: 'testArtifacts')
}

View File

@@ -0,0 +1,3 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|

View File

@@ -0,0 +1,526 @@
/* ###
* 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.*;
import java.math.BigInteger;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jdom2.JDOMException;
import db.Transaction;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.DomainFileOption;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.program.database.ProgramContentHandler;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.AddressEvaluator;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.target.DBTraceObjectManager;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceOverlappedRegionException;
import ghidra.trace.model.modules.TraceModuleManager;
import ghidra.trace.model.modules.TraceStaticMappingManager;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.target.TraceObjectManager;
import ghidra.trace.model.target.path.KeyPath;
import ghidra.trace.model.target.schema.*;
import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
public class TenetLoader implements Loader {
private static final String TRACE_SUFFIX = ".trace";
private static final String TENET_SUFFIX = ".tenet";
private static final Set<String> SUFFIXES = Set.of(TRACE_SUFFIX, TENET_SUFFIX);
private static final Pattern SLIDE_PATTERN = Pattern.compile("slide=0x([0-9a-fA-F]+)");
private static final Pattern MEM_PATTERN =
Pattern.compile("(?:^|,)(mr|mw)=0x([^:]+):([0-9a-fA-F]+)");
private static final Pattern REG_PATTERN =
Pattern.compile("(?:^|,)(?!(?:mr|mw|slide)=)([a-zA-Z0-9]+)=0x([0-9a-fA-F]+)");
static final int ERROR_THRESHOLD = 10;
static final String DOMAIN_FILE_OPTION_NAME = "Program to associate trace file with";
private static final String TENET_CTX_XML = """
<context>
<schema name='TenetSession' canonical='yes'>
<interface name='Aggregate'/>
<interface name='Process'/>
<attribute name='Thread' schema='Thread'/>
<attribute name='Memory' schema='Memory'/>
<attribute name='Breakpoints' schema='BreakpointContainer'/>
<attribute name='Modules' schema='ModuleContainer'/>
</schema>
<schema name='Thread'>
<interface name='Aggregate'/>
<interface name='Thread'/>
<interface name='ExecutionStateful'/>
<attribute name='Stack' schema='Stack'/>
</schema>
<schema name='Stack' canonical='yes'>
<interface name='Stack'/>
<interface name='Aggregate'/>
<element schema='Frame'/>
</schema>
<schema name='Frame'>
<interface name='Aggregate'/>
<interface name='StackFrame'/>
<attribute name='Registers' schema='RegisterContainer'/>
</schema>
<schema name='RegisterContainer'>
<interface name='RegisterContainer'/>
<element schema='Register'/>
</schema>
<schema name='Register'>
<interface name='Register'/>
</schema>
<schema name='Memory' canonical='yes'>
<interface name='Memory'/>
<element schema='MemoryRegion'/>
</schema>
<schema name='MemoryRegion'>
<interface name='MemoryRegion'/>
</schema>
<schema name='ModuleContainer' canonical='yes'>
<element schema='Module'/>
</schema>
<schema name='Module'>
<interface name='Module'/>
<attribute name='Sections' schema='SectionContainer'/>
</schema>
<schema name='SectionContainer' canonical='yes'>
<element schema='Section'/>
</schema>
<schema name='Section'>
<interface name='Section'/>
</schema>
<schema name='BreakpointContainer' canonical='yes'>
<element schema='Breakpoint'/>
</schema>
<schema name='Breakpoint'>
<interface name='BreakpointSpec'/>
<interface name='BreakpointLocation'/>
</schema>
</context>
""";
private static final SchemaContext SAMPLE_CTX;
private static final TraceObjectSchema TENET_SESSION_SCHEMA;
static {
try {
SAMPLE_CTX = XmlSchemaContext.deserialize(TENET_CTX_XML);
}
catch (final JDOMException e) {
throw new AssertionError(e);
}
TENET_SESSION_SCHEMA = SAMPLE_CTX.getSchema(new SchemaName("TenetSession"));
}
private static AddressSpace defaultSpace;
private static Program program;
private static boolean STORE_REG_ATTRS = false;
/**
* Create an address in the processor's default space.
*
* @param offset the byte offset
* @return the address
*/
private static Address addr(final long offset) {
return defaultSpace.getAddress(offset);
}
private static boolean isTenetFile(final ByteProvider provider) {
final String nameLower = provider.getName().toLowerCase();
if (!SUFFIXES.stream().anyMatch(nameLower::endsWith)) {
return false;
}
return true;
}
/**
* Create an address range in the processor's default space.
*
* @param min the minimum byte offset
* @param max the maximum (inclusive) byte offset
* @return the range
*/
private static AddressRange rng(final long min, final long max) {
return new AddressRangeImpl(addr(min), addr(max));
}
private static Address toAddr(final String addressString) {
return AddressEvaluator.evaluate(program, addressString);
}
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(final ByteProvider provider)
throws IOException {
final List<LoadSpec> loadSpecs = new ArrayList<>();
if (isTenetFile(provider)) {
// TODO: This LanguageCompilerSpecPair is not used by the loader
// just putting a dummy value in so the user doesn't have to
loadSpecs.add(new LoadSpec(this, 0,
new LanguageCompilerSpecPair("DATA:LE:64:default", "pointer64"), true));
}
return loadSpecs;
}
@Override
public List<Option> getDefaultOptions(final ByteProvider provider, final LoadSpec loadSpec,
final DomainObject domainObject, final boolean loadIntoProgram,
final boolean mirrorFsLayout) {
final List<Option> list = new ArrayList<>();
list.add(new DomainFileOption(DOMAIN_FILE_OPTION_NAME, "", false));
return list;
}
@Override
public String getName() {
return "Tenet Trace Format";
}
@Override
public LoaderTier getTier() {
return LoaderTier.GENERIC_TARGET_LOADER;
}
@Override
public int getTierPriority() {
return 0;
}
@Override
public LoadResults<? extends DomainObject> load(final ImporterSettings settings)
throws IOException, CancelledException, VersionException, LoadException {
Trace trace;
String programPath = null;
for (final Option option : settings.options()) {
final String name = option.getName();
if (name.equals(DOMAIN_FILE_OPTION_NAME)) {
programPath = (String) option.getValue();
}
}
if ((programPath == null) || programPath.isBlank()) {
throw new LoadException("No progam to associate with trace was given");
}
Msg.info(this, "Loading trace for %s".formatted(programPath));
final DomainFile df = settings.project().getProjectData().getFile(programPath);
if (!df.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
throw new LoadException("Selected file is not a program");
}
Program program = null;
try {
program = (Program) df.getDomainObject(this, false, false, settings.monitor());
final long start = System.currentTimeMillis();
trace = this.loadTrace(settings.provider(), settings.importName(), program,
settings.consumer(), settings.log(), settings.monitor());
final long loadDone = System.currentTimeMillis();
// TODO: This is needed because
// ghidra.trace.database.DBTraceContentHandler.createFile can't do
// it, due to a lock in
// ghidra.framework.data.GhidraFolderData.createFile
final DBTraceObjectManager objManager = (DBTraceObjectManager) trace.getObjectManager();
objManager.flushWbCaches();
final long flushDone = System.currentTimeMillis();
settings.log()
.appendMsg("%d ms to load trace | %d ms to flush cache | %d ms total time"
.formatted(loadDone - start, flushDone - loadDone, flushDone - start));
return new LoadResults<>(new Loaded<>(trace, settings));
}
catch (final CancelledException e) {
throw e;
}
finally {
if (program != null) {
program.release(this);
}
}
}
@Override
public void loadInto(final Program program, final ImporterSettings settings)
throws IOException, LoadException, CancelledException {
throw new LoadException("Cannot add Trace to a program");
}
private Trace loadTrace(final ByteProvider provider, final String name, final Program program,
final Object consumer, final MessageLog log, final TaskMonitor monitor)
throws LanguageNotFoundException, IOException, CancelledException {
TenetLoader.program = program;
final Language lang = program.getLanguage();
TenetLoader.defaultSpace = lang.getAddressFactory().getDefaultAddressSpace();
final Trace trace = new DBTrace(name, program.getCompilerSpec(), consumer);
try (Transaction tx = trace.openTransaction("Import Tenet Trace: %s".formatted(name))) {
final TraceObjectManager om = trace.getObjectManager();
om.createRootObject(TENET_SESSION_SCHEMA);
final TraceThread traceThread =
trace.getThreadManager().createThread("Thread", "Thread", 0);
trace.getObjectManager()
.createObject(KeyPath.parse("Breakpoints"))
.insert(Lifespan.ALL, ConflictResolution.DENY);
final Pattern ipPattern = Pattern.compile(
"(?:^|,)%s=0x([0-9a-fA-F]+)".formatted(lang.getProgramCounter().getName()),
Pattern.CASE_INSENSITIVE);
final BufferedReader lineCounter = new BufferedReader(
new InputStreamReader(provider.getInputStream(0), StandardCharsets.UTF_8));
final long numLines = lineCounter.lines().count();
lineCounter.close();
monitor.setMaximum(numLines);
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(provider.getInputStream(0), StandardCharsets.UTF_8))) {
String line = reader.readLine();
long slideValue = 0;
long curIp = 0;
final Matcher slideMatcher = SLIDE_PATTERN.matcher(line);
int lineNumber = 1;
int errorCount = 0;
int snapNumber = 0;
if (slideMatcher.find()) {
slideValue = Long.parseLong(slideMatcher.group(1), 16);
lineNumber++;
line = reader.readLine();
}
final TraceSnapshot snapshot =
trace.getTimeManager().createSnapshot("Snapshot %d".formatted(snapNumber));
long snap = snapshot.getKey();
snapNumber++;
this.setupMemoryAndMapping(program, trace, slideValue, snap);
try {
while (line != null) {
monitor.checkCancelled();
if (errorCount >= ERROR_THRESHOLD) {
throw new LoadException("Encountered too many errors with this trace");
}
final Matcher ipMatcher = ipPattern.matcher(line);
if (!ipMatcher.find()) {
log.appendMsg(
"Line %d: Unable to find PC, skipping...".formatted(lineNumber));
errorCount++;
lineNumber++;
monitor.setProgress(lineNumber);
line = reader.readLine();
if (line != null) {
snap = trace.getTimeManager()
.createSnapshot("Snapshot %d".formatted(snapNumber))
.getKey();
snapNumber++;
}
continue;
}
curIp = Long.parseLong(ipMatcher.group(1), 16);
if (!this.parseRegisterOperations(snap, curIp, line, lineNumber,
traceThread, trace, log, monitor)) {
errorCount++;
lineNumber++;
monitor.setProgress(lineNumber);
line = reader.readLine();
if (line != null) {
snap = trace.getTimeManager()
.createSnapshot("Snapshot %d".formatted(snapNumber))
.getKey();
snapNumber++;
}
continue;
}
this.parseMemoryOperations(snap, curIp, line, trace, monitor);
lineNumber++;
monitor.setProgress(lineNumber);
line = reader.readLine();
if (line != null) {
snap = trace.getTimeManager()
.createSnapshot("Snapshot %d".formatted(snapNumber))
.getKey();
snapNumber++;
}
}
}
catch (final CancelledException e) {
throw e;
}
catch (final Exception e) {
throw new LoadException(e);
}
}
}
catch (TraceOverlappedRegionException | DuplicateNameException e) {
// This should not happen
throw new AssertionError(e);
}
return trace;
}
private void parseMemoryOperations(final long snap, final long curIp, final String line,
final Trace trace, final TaskMonitor monitor) throws Exception {
final Matcher memMatcher = MEM_PATTERN.matcher(line);
// TODO Speed this up
while (memMatcher.find()) {
monitor.checkCancelled();
final RefType refType = memMatcher.group(1).equals("mr") ? RefType.READ : RefType.WRITE;
final Address address = toAddr(memMatcher.group(2));
final byte[] bytes = NumericUtilities.convertStringToBytes(memMatcher.group(3));
trace.getReferenceManager()
.addMemoryReference(Lifespan.at(snap), addr(curIp),
new AddressRangeImpl(address, bytes.length), refType, SourceType.IMPORTED,
-1);
trace.getMemoryManager().putBytes(snap, address, ByteBuffer.wrap(bytes));
}
}
private boolean parseRegisterOperations(final long snap, final long curIp, final String line,
final int lineNumber, final TraceThread traceThread, final Trace trace,
final MessageLog log, final TaskMonitor monitor) throws Exception {
final TraceStackFrame frame =
trace.getStackManager().getStack(traceThread, snap, true).getFrame(snap, 0, true);
frame.setProgramCounter(Lifespan.nowOn(snap), addr(curIp));
final KeyPath traceRegistersPath = frame.getObject().getCanonicalPath().extend("Registers");
final TraceObject traceRegisters =
trace.getObjectManager().createObject(traceRegistersPath);
traceRegisters.insert(Lifespan.ALL, ConflictResolution.DENY);
final Matcher regMatcher = REG_PATTERN.matcher(line);
while (regMatcher.find()) {
monitor.checkCancelled();
final BigInteger val = new BigInteger(regMatcher.group(2), 16);
final Register regObj = trace.getProgramView().getRegister(regMatcher.group(1));
if (regObj == null) {
log.appendMsg("Line %d: Register %s not found in program language!"
.formatted(lineNumber, regMatcher.group(1)));
return false;
}
if (STORE_REG_ATTRS) {
traceRegisters.setElement(Lifespan.nowOn(snap), regObj.getName(), val.longValue());
}
trace.getMemoryManager()
.getMemoryRegisterSpace(frame, true)
.setValue(snap, new RegisterValue(regObj, val));
}
return true;
}
private void setupMemoryAndMapping(final Program program, final Trace trace, final long slideValue,
final long snap) throws DuplicateNameException, TraceOverlappedRegionException {
final TraceModuleManager modMan = trace.getModuleManager();
final TraceStaticMappingManager mapMan = trace.getStaticMappingManager();
final URL projectUrl = program.getDomainFile().getLocalProjectURL("");
if (slideValue != 0) {
Msg.info(this, "Adding slide %x".formatted(slideValue));
mapMan.add(rng(slideValue, slideValue + program.getMemory().getSize()),
Lifespan.nowOn(snap), projectUrl, "ram:%s".formatted(program.getImageBase()));
modMan.addLoadedModule("Modules[%s]".formatted(program.getName()),
program.getExecutablePath(),
rng(slideValue, slideValue + program.getMemory().getSize()), snap);
}
else {
mapMan.add(
rng(program.getImageBase().getUnsignedOffset(),
program.getImageBase().add(program.getMemory().getSize()).getUnsignedOffset()),
Lifespan.nowOn(snap), projectUrl, "ram:%s".formatted(program.getImageBase()));
modMan.addLoadedModule("Modules[%s]".formatted(program.getName()),
program.getExecutablePath(),
rng(program.getImageBase().getUnsignedOffset(), program.getMemory().getSize()),
snap);
}
// Add one giant memory region
// TODO: Maybe make this more accurate in the future through
// some analysis?
trace.getMemoryManager()
.addRegion("Memory[ALL]", Lifespan.nowOn(0), rng(0x0, 0xFFFF_FFFF_FFFF_FFFFL),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE, TraceMemoryFlag.WRITE);
}
@Override
public String validateOptions(final ByteProvider provider, final LoadSpec loadSpec,
final List<Option> options, final Program program) {
if (options != null) {
for (final Option option : options) {
final String name = option.getName();
if (name.equals(DOMAIN_FILE_OPTION_NAME) &&
!String.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass();
}
}
}
return null;
}
}

View File

@@ -0,0 +1,293 @@
/* ###
* 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 static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.model.DebuggerModelPlugin;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersPlugin;
import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPlugin;
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteArrayProvider;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.Loader.ImporterSettings;
import ghidra.framework.model.DomainObject;
import ghidra.program.model.address.AddressSpace;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.trace.model.thread.TraceThread;
public class TenetLoaderTest extends AbstractGhidraHeadedDebuggerTest {
/**
* Create a byte array
*
* <p>
* This is basically syntactic sugar, since expressing a byte array literal can get obtuse in
* Java. {@code new byte[] {0, 1, 2, (byte) 0x80, (byte) 0xff}} vs
* {@code arr(0, 1, 2, 0x80, 0xff)}.
*
* @param e the bytes' values
* @return the array
*/
private byte[] arr(final int... e) {
final byte[] result = new byte[e.length];
for (int i = 0; i < e.length; i++) {
result[i] = (byte) e[i];
}
return result;
}
private Trace createTraceWithLoader(final String testFile) throws Exception {
this.createProgram();
this.intoProject(this.program);
final TenetLoader loader = new TenetLoader();
final ByteProvider provider =
new ByteArrayProvider("test.tenet", testFile.getBytes("utf8"));
final LoadSpec loadSpec = Unique.assertOne(loader.findSupportedLoadSpecs(provider));
final List<Option> options =
loader.getDefaultOptions(provider, loadSpec, null, false, false);
final Option programOption = Unique.assertOne(
options.stream().filter(o -> o.getName().equals(TenetLoader.DOMAIN_FILE_OPTION_NAME)));
programOption.setValue(this.program.getDomainFile().getPathname());
final MessageLog log = new MessageLog();
final ImporterSettings settings = new ImporterSettings(provider, "test",
this.env.getProject(), "/", false, loadSpec, options, this, log, this.monitor);
final LoadResults<? extends DomainObject> results = loader.load(settings);
if (!(results.getPrimary().domainObject instanceof final Trace trace)) {
fail("Import result was not a trace");
return null;
}
return trace;
}
/*
* Test that the loader continues on a bad register name
*/
@Test
public void testBadRegisterNames() throws Exception {
final String testFile = """
pc=0x1,rax=0x1234
""";
final Trace trace = this.createTraceWithLoader(testFile);
assertNotNull(trace);
}
/*
* Test the filename check for the loader
*/
@Test
public void testFileName() throws Exception {
final String testFile = """
slide=0x0
pc=0x1,r0=0x1234
pc=0x2,r1=0x4321
""";
this.createProgram();
this.intoProject(this.program);
final TenetLoader loader = new TenetLoader();
ByteProvider provider = new ByteArrayProvider("test.tenet", testFile.getBytes("utf8"));
Unique.assertOne(loader.findSupportedLoadSpecs(provider));
provider = new ByteArrayProvider("test.trace", testFile.getBytes("utf8"));
Unique.assertOne(loader.findSupportedLoadSpecs(provider));
provider = new ByteArrayProvider("test.anythingelse", testFile.getBytes("utf8"));
assertEquals(0, loader.findSupportedLoadSpecs(provider).size());
}
/*
* Automate loading a trace for manual testing purposes
*/
@Ignore
public void testManual() throws Exception {
final String testFile = """
slide=0x0
pc=0x1,r0=0x1234
pc=0x2,r1=0x4321
""";
addPlugin(this.tool, DebuggerRegistersPlugin.class);
addPlugin(this.tool, DebuggerModelPlugin.class);
addPlugin(this.tool, DebuggerThreadsPlugin.class);
addPlugin(this.tool, DebuggerListingPlugin.class);
addPlugin(this.tool, DebuggerTimePlugin.class);
final Trace trace = this.createTraceWithLoader(testFile);
this.traceManager.openTrace(trace);
this.traceManager.activateTrace(trace);
Thread.sleep(1000);
}
/*
* Test that the loader after more than {@link TenetLoader.ERROR_THRESHOLD}
* lines with bad register names
*/
@Test
public void testManyLinesBadRegisterNames() throws Exception {
// Should fail after 10 lines with bad registers
final String badTestFile = "pc=0x1,rax=0x1234\n".repeat(TenetLoader.ERROR_THRESHOLD + 1);
assertThrows(LoadException.class, () -> this.createTraceWithLoader(badTestFile));
}
/*
* Test that the loader fails after {@TenetLoader.ERROR_THRESHOLD} lines
* without a PC
*/
@Test
public void testManyLinesNoPc() throws Exception {
// Should fail after {@TenetLoader.ERROR_THRESHOLD} lines with no PC
final String badTestFile = "r1=0x1234\n".repeat(TenetLoader.ERROR_THRESHOLD + 1);
assertThrows(LoadException.class, () -> this.createTraceWithLoader(badTestFile));
}
/*
* Test how the loader handles overlapping memory reads/writes
*/
@Test
public void testMemoryValueOverlap() throws Exception {
final String testFile = """
pc=0x1,r0=0x1234,mr=0x1000:0000000000001337
pc=0x2,r1=0x4321,mw=0x2000:deadbeefdeadbeef
pc=0x3,mw=0x1000:87654321,mr=0x2000:c0ffee
""";
final Trace trace = this.createTraceWithLoader(testFile);
final AddressSpace addrSpace =
trace.getBaseLanguage().getAddressFactory().getDefaultAddressSpace();
final ByteBuffer buffer =
ByteBuffer.allocate(trace.getBaseLanguage().getProgramCounter().getNumBytes());
trace.getMemoryManager().getBytes(0, addrSpace.getAddress(0x1000), buffer);
assertArrayEquals(this.arr(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, 0x37), buffer.array());
buffer.clear();
trace.getMemoryManager().getBytes(1, addrSpace.getAddress(0x2000), buffer);
assertArrayEquals(this.arr(0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef), buffer.array());
buffer.clear();
trace.getMemoryManager().getBytes(2, addrSpace.getAddress(0x1000), buffer);
assertArrayEquals(this.arr(0x87, 0x65, 0x43, 0x21, 0x00, 0x00, 0x13, 0x37), buffer.array());
buffer.clear();
trace.getMemoryManager().getBytes(2, addrSpace.getAddress(0x2000), buffer);
assertArrayEquals(this.arr(0xc0, 0xff, 0xee, 0xef, 0xde, 0xad, 0xbe, 0xef), buffer.array());
}
/*
* Test that the loader continues after a line with no PC
*/
@Test
public void testNoPc() throws Exception {
final String testFile = """
pc=0x1,r1=0x1234
r2=0x1234
""";
final Trace trace = this.createTraceWithLoader(testFile);
assertNotNull(trace);
}
/*
* Test that the loader handles no slide value properly
*/
@Test
public void testNoSlideValue() throws Exception {
final String testFile = """
pc=0x1,r0=0x1234
pc=0x2,r1=0x4321
""";
final Trace trace = this.createTraceWithLoader(testFile);
final TraceStaticMapping staticMapping =
Unique.assertOne(trace.getStaticMappingManager().getAllEntries());
assertEquals(this.program.getImageBase().getUnsignedOffset(),
staticMapping.getMinTraceAddress().getUnsignedOffset());
}
/*
* Test that the loader parses register values properly
*/
@Test
public void testRegisterValues() throws Exception {
final String testFile = """
slide=0x0
pc=0x1,r0=0x1234
pc=0x2,r1=0x4321
""";
final Trace trace = this.createTraceWithLoader(testFile);
final TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
final TraceMemorySpace regs =
trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
assertNotNull(regs);
assertEquals("1",
regs.getValue(0, this.program.getLanguage().getProgramCounter())
.getUnsignedValue()
.toString(16));
assertEquals("2",
regs.getValue(1, this.program.getLanguage().getProgramCounter())
.getUnsignedValue()
.toString(16));
}
/*
* Test that the loader handles a slide value properly
*/
@Test
public void testWithSlideValue() throws Exception {
final String testFile = """
slide=0x10000
pc=0x1,r0=0x1234
pc=0x2,r1=0x4321
""";
final Trace trace = this.createTraceWithLoader(testFile);
final TraceStaticMapping staticMapping =
Unique.assertOne(trace.getStaticMappingManager().getAllEntries());
assertEquals(this.program.getImageBase().getUnsignedOffset() + 0x10000,
staticMapping.getMinTraceAddress().getUnsignedOffset());
}
}