Merge remote-tracking branch 'origin/GP-4045_dev747368_lock_sla_files'

(Closes #8866)
This commit is contained in:
Ryan Kurtz
2026-03-11 05:14:29 -04:00
28 changed files with 2188 additions and 679 deletions
@@ -22,10 +22,10 @@ import docking.ActionContext;
import docking.Tool;
import docking.action.DockingActionIf;
import docking.actions.PopupActionProvider;
import generic.jar.ResourceFile;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
import ghidra.app.plugin.processors.sleigh.SleighLanguageDescription;
import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.plugintool.*;
@@ -202,27 +202,17 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro
protected Collection<LanguageID> getAlternativeLanguageIDs(Language language) {
// One of the alternatives is the language's actual default
LanguageDescription desc = language.getLanguageDescription();
if (!(desc instanceof SleighLanguageDescription)) {
if (!(desc instanceof SleighLanguageDescription sld)) {
return List.of();
}
SleighLanguageDescription sld = (SleighLanguageDescription) desc;
ResourceFile slaFile = sld.getSlaFile();
List<LanguageID> result = new ArrayList<>();
LanguageService langServ = DefaultLanguageService.getLanguageService();
for (LanguageDescription altDesc : langServ.getLanguageDescriptions(false)) {
if (!(altDesc instanceof SleighLanguageDescription)) {
continue;
if (altDesc instanceof SleighLanguageDescription altSld &&
sld.isSameSleighLanguageFile(altSld) && sld.getEndian() == altSld.getEndian()) {
result.add(altSld.getLanguageID());
}
SleighLanguageDescription altSld = (SleighLanguageDescription) altDesc;
if (!altSld.getSlaFile().equals(slaFile)) {
continue;
}
if (altSld.getEndian() != sld.getEndian()) {
// Memory endian, not necessarily instruction endian
continue;
}
result.add(altSld.getLanguageID());
}
return result;
}
@@ -23,6 +23,7 @@ import org.apache.commons.lang3.tuple.Pair;
import db.DBRecord;
import generic.jar.ResourceFile;
import ghidra.app.plugin.processors.sleigh.SleighLanguageDescription;
import ghidra.app.util.PseudoInstruction;
import ghidra.framework.data.OpenMode;
import ghidra.lifecycle.Internal;
@@ -304,7 +305,7 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
private static ResourceFile getSlaFile(Language language) {
SleighLanguageDescription desc =
(SleighLanguageDescription) language.getLanguageDescription();
return desc.getSlaFile();
return desc.getLanguageFile().getSlaFile();
}
@Override
@@ -23,6 +23,7 @@ import org.jdom2.*;
import org.jdom2.input.SAXBuilder;
import generic.jar.ResourceFile;
import ghidra.app.plugin.processors.sleigh.SleighLanguageDescription;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.framework.*;
@@ -27,6 +27,7 @@ import ghidra.app.CorePluginPackage;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.context.ProgramContextAction;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.processors.sleigh.SleighLanguageDescription;
import ghidra.app.util.GenericHelpTopics;
import ghidra.app.util.HelpTopics;
import ghidra.framework.main.ApplicationLevelPlugin;
@@ -169,7 +170,7 @@ public class AboutProgramPlugin extends Plugin implements ApplicationLevelPlugin
metadata.put("Language Spec",
lDesc.getDefsFile() + (lav.isMismatch() ? lav.getVersionDisplay() : ""));
metadata.put("Processor Spec", lDesc.getSpecFile().getAbsolutePath());
metadata.put("Sleigh Spec", lDesc.getSlaFile().getAbsolutePath() + "spec");
metadata.put("Sleigh Spec", lDesc.getLanguageFile().getSlaSpecFile().getAbsolutePath());
}
if (lav.compilerSpec != null) {
metadata.put("Compiler Spec",
@@ -27,6 +27,7 @@ import java.util.regex.Pattern;
import generic.jar.ResourceFile;
import ghidra.app.cmd.comments.AppendCommentCmd;
import ghidra.app.plugin.processors.sleigh.SleighLanguageDescription;
import ghidra.app.util.bin.format.dwarf.attribs.DWARFAttributeValue;
import ghidra.app.util.bin.format.dwarf.attribs.DWARFNumericAttribute;
import ghidra.app.util.bin.format.dwarf.expression.DWARFExpressionException;
@@ -170,8 +170,12 @@ public class ProgramOpener {
// we don't care, the task has been cancelled
}
catch (LanguageNotFoundException e) {
Msg.showError(this, null, "Error Opening " + filename,
e.getMessage() + "\nPlease contact the Ghidra team for assistance.");
String msg = e.getMessage() + "\n";
if (e.getCause() != null) {
msg += e.getCause().getMessage() + "\n";
}
msg += "Please contact the Ghidra team for assistance.";
Msg.showError(this, null, "Error Opening " + filename, msg);
}
catch (Exception e) {
if (domainFile.isInWritableProject() && (e instanceof IOException)) {
@@ -25,8 +25,7 @@ import java.util.ArrayList;
import generic.jar.ResourceFile;
import ghidra.app.decompiler.signature.DebugSignature;
import ghidra.app.decompiler.signature.SignatureResult;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.UniqueLayout;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Function;
@@ -278,8 +277,7 @@ public class DecompInterface {
xmlEncode.clear();
dtmanage.encodeCoreTypes(xmlEncode);
String coretypes = xmlEncode.toString();
SleighLanguageDescription sleighdescription =
(SleighLanguageDescription) pcodelanguage.getLanguageDescription();
SleighLanguageDescription sleighdescription = pcodelanguage.getLanguageDescription();
ResourceFile pspecfile = sleighdescription.getSpecFile();
String pspecxml = fileToString(pspecfile);
xmlEncode.clear();
@@ -88,7 +88,8 @@ public class SleighLanguageHelper {
);
langDesc.setDefsFile(lDefsFile);
langDesc.setSpecFile(pSpecFile);
langDesc.setSlaFile(slaFile);
langDesc.setLanguageFile(
SleighLanguageFile.fromSlaFilename(slaFile.getParentFile(), slaFile.getName()));
MOCK_BE_64_LANGUAGE = new SleighLanguage(langDesc);
return MOCK_BE_64_LANGUAGE;
@@ -25,29 +25,78 @@ import utilities.util.reflection.ReflectionUtilities;
import utility.function.Dummy;
/**
* Class to pass to a thread pool that will consume all output from an external process. This is
* a {@link Runnable} that get submitted to a thread pool. This class records the data it reads
* {@link Runnable} that will consume all text output from an {@link InputStream} tied to an
* external processes (stdout / stderr).
* <p>
* The output can be inspected line-by-line by providing a string {@link Consumer}, or the entire
* output of the process can be inspected by calling {@link #getOutput()} or
* {@link #getOutputAsString()}.
*/
public class IOResult implements Runnable {
public static final String THREAD_POOL_NAME = "I/O Thread Pool";
private List<String> outputLines = new ArrayList<>();
private final List<String> outputLines;
private BufferedReader commandOutput;
private final Throwable inception;
private Consumer<String> consumer = Dummy.consumer();
private final Consumer<String> consumer;
/**
* Creates a {@link IOResult} that consumes the specified {@link InputStream}, saving it
* as text lines.
*
* @param input {@link InputStream}
*/
public IOResult(InputStream input) {
this(ReflectionUtilities.createThrowableWithStackOlderThan(IOResult.class), input);
this(input, null, true,
ReflectionUtilities.createThrowableWithStackOlderThan(IOResult.class));
}
public IOResult(Throwable inception, InputStream input) {
/**
* Creates a {@link IOResult} that consumes the specified {@link InputStream}, saving it
* as text lines.
*
* @param input {@link InputStream}
* @param inception information about where this object was created
*/
public IOResult(InputStream input, Throwable inception) {
this(input, null, true, inception);
}
/**
* Creates a {@link IOResult} that consumes the specified {@link InputStream}, handing each
* line to the {@link Consumer}.
* <p>
* Example: {@code new IOResult(process.getInputStream(), s -> System.out.println(s), null);}
*
* @param input {@link InputStream}
* @param lineConsumer {@link Consumer string consumer}
* @param inception information about where this object was created
*/
public IOResult(InputStream input, Consumer<String> lineConsumer, Throwable inception) {
this(input, lineConsumer, false, inception);
}
/**
* Creates a {@link IOResult} that consumes the specified {@link InputStream}, handing each
* line to the {@link Consumer} and optionally storing each line for later retrieval.
*
* @param input {@link InputStream}
* @param lineConsumer {@link Consumer string consumer}, optional
* @param retainLines boolean flag, if true, the contents read from the InputStream will be
* available via {@link #getOutput()} and {@link #getOutputAsString()}
* @param inception information about where this object was created
*/
public IOResult(InputStream input, Consumer<String> lineConsumer, boolean retainLines,
Throwable inception) {
this.outputLines = retainLines ? new ArrayList<>() : List.of();
if (retainLines) {
lineConsumer = Dummy.ifNull(lineConsumer).andThen(outputLines::add);
}
this.consumer = lineConsumer;
this.inception = inception;
commandOutput = new BufferedReader(new InputStreamReader(input));
}
public void setConsumer(Consumer<String> consumer) {
this.consumer = consumer;
commandOutput = new BufferedReader(new InputStreamReader(input));
}
public String getOutputAsString() {
@@ -68,8 +117,7 @@ public class IOResult implements Runnable {
try {
while ((line = commandOutput.readLine()) != null) {
consumer.accept(line);
outputLines.add(line);
consumer.accept(line); // this both adds to outputLines and calls the upstream consumer
}
}
catch (Exception e) {
@@ -53,8 +53,7 @@ public class ProcessConsumer {
* @param lineConsumer the line consumer; may be null
* @return the future that will be complete when all lines are read
*/
public static Future<IOResult> consume(InputStream is,
Consumer<String> lineConsumer) {
public static Future<IOResult> consume(InputStream is, Consumer<String> lineConsumer) {
lineConsumer = Dummy.ifNull(lineConsumer);
@@ -62,9 +61,38 @@ public class ProcessConsumer {
ReflectionUtilities.createThrowableWithStackOlderThan(ProcessConsumer.class));
GThreadPool pool = GThreadPool.getSharedThreadPool(IOResult.THREAD_POOL_NAME);
IOResult runnable = new IOResult(inception, is);
runnable.setConsumer(lineConsumer);
IOResult runnable = new IOResult(is, lineConsumer, true, inception);
Future<IOResult> future = pool.submit(runnable, runnable);
return future;
}
/**
* Reads the given input stream line-by-line, calling the given consumer. When the
* InputStream reaches EOF, a final {@code null} will be sent to the line consumer.
* <p>
* The Inputstream is consumed via a Thread created just for this setup.
*
* @param is the input stream
* @param lineConsumer the line consumer; may be null
* @param processName descriptive name of process being monitored
*/
public static void monitorAndSignalEof(InputStream is, Consumer<String> lineConsumer,
String processName) {
Throwable inception = ReflectionUtilities.filterJavaThrowable(
ReflectionUtilities.createThrowableWithStackOlderThan(ProcessConsumer.class));
IOResult stdoutReader = new IOResult(is, lineConsumer, false, inception);
Runnable threadRunnable = lineConsumer != null // change mode if null
? () -> {
stdoutReader.run();
lineConsumer.accept(null); // signal that eof was reached
}
: stdoutReader;
Thread t = new Thread(threadRunnable, "IO Thread for " + processName);
t.setDaemon(true);
t.start();
}
}
@@ -1,27 +1,31 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//package ghidra.app.plugin.processors.sleigh;
//
//import ghidra.app.plugin.processors.sleigh.symbol.SymbolTable;
//import ghidra.program.model.address.AddressSpace;
//import ghidra.program.model.lang.Language;
//
//public interface SleighLanguage extends Language {
// public AddressSpace getDefaultSpace();
// public SymbolTable getSymbolTable();
// public DecisionNode getRootDecisionNode();
//}
package ghidra.app.plugin.processors.sleigh;
/**
* Thrown when error concerning a sleigh file is encountered
*/
public class SleighFileException extends SleighException {
public SleighFileException(String message) {
super(message);
}
public SleighFileException(String message, Throwable e) {
super(message, e);
}
}
@@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.processors.sleigh;
public class SleighFileLockException extends SleighFileException {
public SleighFileLockException(String message) {
super(message);
}
public SleighFileLockException(String message, Throwable e) {
super(message, e);
}
}
@@ -22,10 +22,10 @@ import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.antlr.runtime.RecognitionException;
import org.xml.sax.*;
import generic.jar.ResourceFile;
@@ -36,7 +36,6 @@ import ghidra.app.plugin.processors.sleigh.expression.PatternValue;
import ghidra.app.plugin.processors.sleigh.symbol.*;
import ghidra.framework.Application;
import ghidra.pcode.utils.SlaFormat;
import ghidra.pcodeCPort.slgh_compile.SleighCompileLauncher;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.DefaultProgramContext;
@@ -44,10 +43,11 @@ import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.*;
import ghidra.program.model.util.ProcessorSymbolType;
import ghidra.sleigh.grammar.SleighPreprocessor;
import ghidra.sleigh.grammar.SourceFileIndexer;
import ghidra.util.*;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.exception.TimeoutException;
import ghidra.util.task.PreserveStateWrappingTaskMonitor;
import ghidra.util.task.TaskMonitor;
import ghidra.util.xml.SpecXmlUtils;
import ghidra.xml.*;
@@ -70,7 +70,7 @@ public class SleighLanguage implements Language {
private int numSections = 0; // Number of named sections for this language
private int alignment = 1;
private int defaultPointerWordSize = 1; // Default wordsize to send down with pointer data-types
private SleighLanguageDescription description;
private final SleighLanguageDescription description;
private ParallelInstructionLanguageHelper parallelHelper;
private SourceFileIndexer indexer; //used to provide source file info for constructors
@@ -99,11 +99,16 @@ public class SleighLanguage implements Language {
private AddressSpace default_space;
private List<ContextSetting> ctxsetting = new ArrayList<>();
private LinkedHashMap<String, String> properties = new LinkedHashMap<>();
SortedMap<String, ManualEntry> manual = null;
private SortedMap<String, ManualEntry> manual = null;
SleighLanguage(SleighLanguageDescription description)
throws DecoderException, SAXException, IOException {
initialize(description);
SleighLanguage(SleighLanguageDescription description) throws SleighException {
this(description, TaskMonitor.DUMMY);
}
SleighLanguage(SleighLanguageDescription description, TaskMonitor monitor)
throws SleighException {
this.description = description;
initialize(false, monitor);
}
private void addAdditionInject(InjectPayloadSleigh payload) {
@@ -113,37 +118,68 @@ public class SleighLanguage implements Language {
additionalInject.add(payload);
}
private void initialize(SleighLanguageDescription langDescription)
throws DecoderException, SAXException, IOException {
private void initialize(boolean forceCompile, TaskMonitor monitor) throws SleighException {
long startTS = System.currentTimeMillis();
this.defaultSymbols = new ArrayList<>();
this.defaultMemoryBlocks = new MemoryBlockDefinition[0];
this.compilerSpecDescriptions = new LinkedHashMap<>();
for (CompilerSpecDescription compilerSpecDescription : langDescription
for (CompilerSpecDescription compilerSpecDescription : description
.getCompatibleCompilerSpecDescriptions()) {
this.compilerSpecDescriptions.put(compilerSpecDescription.getCompilerSpecID(),
(SleighCompilerSpecDescription) compilerSpecDescription);
}
compilerSpecs = new HashMap<>();
this.description = langDescription;
additionalInject = null;
SleighLanguageValidator.validatePspecFile(langDescription.getSpecFile());
SleighLanguageValidator.validatePspecFile(description.getSpecFile());
readInitialDescription();
// should addressFactory and registers initialization be done at
// construction time?
// should addressFactory and registers initialization be done at construction time?
// for now we'll assume yes.
contextcache = new ContextCache();
ResourceFile slaFile = langDescription.getSlaFile();
if (!slaFile.exists() ||
(slaFile.canWrite() && (isSLAWrongVersion(slaFile) || isSLAStale(slaFile)))) {
reloadLanguage(TaskMonitor.DUMMY, true);
SleighLanguageFile langFile = description.getLanguageFile();
if (!langFile.getSlaSpecFile().exists()) {
throw new SleighFileException("Missing slaspec: " + langFile.getSlaSpecFile());
}
// check .sla file freshness inside lock, and recompile if necessary before releasing lock.
// if can't lock, it's single jar mode and we can't recompile anyways
AtomicLong lockElapsed = new AtomicLong();
if (langFile.canLock()) {
try (PreserveStateWrappingTaskMonitor tm =
new PreserveStateWrappingTaskMonitor(monitor)) {
tm.setCancelEnabled(true);
tm.setShowProgressValue(true);
langFile.withLock(SleighLanguageProvider.LANGUAGE_LOCK_TIMEOUT, tm, () -> {
tm.setCancelEnabled(false);
long lockStartTS = System.currentTimeMillis();
if (forceCompile || langFile.needsCompilation(SlaFormat.FORMAT_VERSION)) {
langFile.compileSlaFile(monitor);
}
lockElapsed.set(System.currentTimeMillis() - lockStartTS);
});
}
catch (TimeoutException e) {
throw new SleighFileLockException(
"Timeout waiting for Sleigh language file lock: %s"
.formatted(langFile.getSlaFile()),
e);
}
catch (IOException e) {
throw new SleighFileException(
"Error locking Sleigh language file %s".formatted(langFile.getSlaFile()), e);
}
}
// Read in the sleigh specification
PackedDecode decoder = SlaFormat.buildDecoder(slaFile);
decode(decoder);
try (PackedDecode decoder = SlaFormat.buildDecoder(langFile.getSlaFile())) {
decode(decoder);
}
catch (IOException | DecoderException e) {
throw new SleighException("Error decoding", e);
}
registerBuilder = new RegisterBuilder();
loadRegisters(registerBuilder);
@@ -154,6 +190,10 @@ public class SleighLanguage implements Language {
instructProtoMap = new ConcurrentHashMap<>();
initParallelHelper();
long initElapsed = System.currentTimeMillis() - startTS;
Msg.debug(this, "Took %dms (%dms inside lock) to initialize language %s"
.formatted(initElapsed, lockElapsed.get(), langFile));
}
private void buildVolatileSymbolAddresses() {
@@ -165,37 +205,6 @@ public class SleighLanguage implements Language {
}
}
private boolean isSLAWrongVersion(ResourceFile slaFile) {
try (InputStream stream = slaFile.getInputStream()) {
return !SlaFormat.isSlaFormat(stream);
}
catch (Exception e) {
return true;
}
}
private boolean isSLAStale(ResourceFile slaFile) {
String slafilename = slaFile.getName();
int index = slafilename.lastIndexOf('.');
String slabase = slafilename.substring(0, index);
String slaspecfilename = slabase + ".slaspec";
ResourceFile slaspecFile = new ResourceFile(slaFile.getParentFile(), slaspecfilename);
File resourceAsFile = slaspecFile.getFile(true);
SleighPreprocessor preprocessor =
new SleighPreprocessor(new ModuleDefinitionsAdapter(), resourceAsFile);
long sourceTimestamp = Long.MAX_VALUE;
try {
sourceTimestamp = preprocessor.scanForTimestamp();
}
catch (Exception e) {
// squash the error because we will force recompilation and errors
// will propagate elsewhere
}
long compiledTimestamp = slaFile.lastModified();
return (sourceTimestamp > compiledTimestamp);
}
/**
* Returns the unique base offset from which additional temporary variables
* may be created.
@@ -417,79 +426,11 @@ public class SleighLanguage implements Language {
@Override
public void reloadLanguage(TaskMonitor monitor) throws IOException {
reloadLanguage(monitor, false);
}
private void reloadLanguage(TaskMonitor monitor, boolean calledFromInitialize)
throws IOException {
if (monitor == null) {
monitor = TaskMonitor.DUMMY;
}
monitor.setMessage("Compiling Language File...");
ResourceFile slaFile = description.getSlaFile();
String slaName = slaFile.getName();
int index = slaName.lastIndexOf('.');
String specName = slaName.substring(0, index);
String languageName = specName + ".slaspec";
ResourceFile languageFile = new ResourceFile(slaFile.getParentFile(), languageName);
// see gradle/processorUtils.gradle for sleighArgs.txt generation
ResourceFile sleighArgsFile = null;
ResourceFile languageModule = Application.getModuleContainingResourceFile(languageFile);
if (languageModule != null) {
if (SystemUtilities.isInReleaseMode()) {
sleighArgsFile = new ResourceFile(languageModule, "data/sleighArgs.txt");
}
else {
sleighArgsFile = new ResourceFile(languageModule, "build/tmp/sleighArgs.txt");
}
}
String[] args;
if (sleighArgsFile != null && sleighArgsFile.isFile()) {
String baseDir = Application.getInstallationDirectory()
.getAbsolutePath()
.replace(File.separatorChar, '/');
if (!baseDir.endsWith("/")) {
baseDir += "/";
}
args = new String[] { "-DBaseDir=" + baseDir, "-i", sleighArgsFile.getAbsolutePath(),
languageFile.getAbsolutePath(), description.getSlaFile().getAbsolutePath() };
}
else {
args = new String[] { languageFile.getAbsolutePath(),
description.getSlaFile().getAbsolutePath() };
}
try {
StringBuilder buf = new StringBuilder();
for (String str : args) {
buf.append(str);
buf.append(" ");
}
Msg.debug(this, "Sleigh compile: " + buf);
int returnCode = SleighCompileLauncher.runMain(args);
if (returnCode != 0) {
throw new SleighException("Errors compiling " + languageFile.getAbsolutePath() +
" -- please check log messages for details");
}
initialize(true, TaskMonitor.dummyIfNull(monitor));
}
catch (RecognitionException e) {
throw new IOException("RecognitionException error recompiling: " + e.getMessage());
}
if (!calledFromInitialize) {
monitor.setMessage("Reloading Language...");
try {
initialize(description);
}
catch (DecoderException e) {
throw new IOException(e.getMessage());
}
catch (SAXException e) {
throw new IOException(e.getMessage());
}
catch (SleighException e) {
throw new IOException("Failed to reload Sleigh language", e);
}
}
@@ -517,26 +458,33 @@ public class SleighLanguage implements Language {
}
};
private void readInitialDescription() throws SAXException, IOException {
ResourceFile specFile = description.getSpecFile();
XmlPullParser parser = XmlPullParserFactory.create(specFile, SPEC_ERR_HANDLER, false);
private void readInitialDescription() throws SleighException {
try {
XmlElement nextElement = parser.peek();
while (nextElement != null && !nextElement.getName().equals("segmented_address")) {
parser.next(); // skip element
nextElement = parser.peek();
}
if (nextElement != null) {
XmlElement element = parser.start(); // segmented_address element
segmentedspace = element.getAttribute("space");
segmentType = element.getAttribute("type");
if (segmentType == null) {
segmentType = "";
ResourceFile specFile = description.getSpecFile();
XmlPullParser parser = XmlPullParserFactory.create(specFile, SPEC_ERR_HANDLER, false);
try {
XmlElement nextElement = parser.peek();
while (nextElement != null && !nextElement.getName().equals("segmented_address")) {
parser.next(); // skip element
nextElement = parser.peek();
}
if (nextElement != null) {
XmlElement element = parser.start(); // segmented_address element
segmentedspace = element.getAttribute("space");
segmentType = element.getAttribute("type");
if (segmentType == null) {
segmentType = "";
}
}
}
finally {
parser.dispose();
}
}
finally {
parser.dispose();
catch (SAXException | IOException e) {
throw new SleighException(
"Error reading initial description - language probably did not compile properly",
e);
}
}
@@ -864,19 +812,26 @@ public class SleighLanguage implements Language {
parser.end(el);
}
private void readRemainingSpecification() throws SAXException, IOException {
ResourceFile specFile = description.getSpecFile();
XmlPullParser parser = XmlPullParserFactory.create(specFile, SPEC_ERR_HANDLER, false);
private void readRemainingSpecification() throws SleighException {
try {
read(parser);
ResourceFile specFile = description.getSpecFile();
XmlPullParser parser = XmlPullParserFactory.create(specFile, SPEC_ERR_HANDLER, false);
try {
read(parser);
}
catch (XmlParseException e) {
Msg.error(this, "Failed to parse Sleigh Specification (" + specFile.getName() +
"): " + e.getMessage());
}
finally {
parser.dispose();
}
}
catch (XmlParseException e) {
Msg.error(this, "Failed to parse Sleigh Specification (" + specFile.getName() + "): " +
e.getMessage());
}
finally {
parser.dispose();
catch (SAXException | IOException e) {
throw new SleighException(
"Error reading remaining spec - language probably did not compile properly", e);
}
}
private void decode(Decoder decoder) throws DecoderException {
@@ -1163,7 +1118,7 @@ public class SleighLanguage implements Language {
}
@Override
public LanguageDescription getLanguageDescription() {
public SleighLanguageDescription getLanguageDescription() {
return description;
}
@@ -1503,8 +1458,7 @@ public class SleighLanguage implements Language {
}
encoder.closeElement(ElementId.ELEM_SPACES);
SleighLanguageDescription sleighDescription =
(SleighLanguageDescription) getLanguageDescription();
SleighLanguageDescription sleighDescription = getLanguageDescription();
Set<String> truncatedSpaceNames = sleighDescription.getTruncatedSpaceNames();
if (!truncatedSpaceNames.isEmpty()) {
for (String spaceName : truncatedSpaceNames) {
@@ -4,30 +4,31 @@
* 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.program.model.lang;
import generic.jar.ResourceFile;
package ghidra.app.plugin.processors.sleigh;
import java.util.*;
import generic.jar.ResourceFile;
import ghidra.program.model.lang.*;
/**
* Class for holding Language identifiers
*/
public class SleighLanguageDescription extends BasicLanguageDescription {
private ResourceFile defsFile; // defs file
private ResourceFile specFile; // specification file
private ResourceFile slaFile; // just cramming this in here until major cleanup
private ResourceFile specFile; // pspec specification file
private ResourceFile manualIndexFile; // the manual index file
private SleighLanguageFile languageFile; // sla, slaspec file
private Map<String, Integer> truncatedSpaceMap;
@@ -36,8 +37,8 @@ public class SleighLanguageDescription extends BasicLanguageDescription {
* @param id the name of the language
* @param description language description text
* @param processor processor name/family
* @param endian data endianness
* @param instructionEndian instruction endianness
* @param endian data endianess
* @param instructionEndian instruction endianess
* @param size processor size
* @param variant processor variant name
* @param version the major version of the language.
@@ -54,19 +55,15 @@ public class SleighLanguageDescription extends BasicLanguageDescription {
Map<String, List<String>> externalNames) {
super(id, processor, endian, instructionEndian, size, variant, description, version,
minorVersion, deprecated, compilerSpecDescriptions, externalNames);
this.specFile = null;
this.slaFile = null;
this.manualIndexFile = null;
this.truncatedSpaceMap = spaceTruncations;
}
/**
* @return set of address space names which have been identified for truncation
*/
@SuppressWarnings("unchecked")
public Set<String> getTruncatedSpaceNames() {
if (truncatedSpaceMap == null) {
return Collections.EMPTY_SET;
return Set.of();
}
return truncatedSpaceMap.keySet();
}
@@ -107,33 +104,37 @@ public class SleighLanguageDescription extends BasicLanguageDescription {
* Set the (optional) specification file associated with this language
*
* @param specFile
* the specFile to associate with this description.
* the specFile (.pspec) to associate with this description.
*/
public void setSpecFile(ResourceFile specFile) {
this.specFile = specFile;
}
/**
* Get the specification file (if it exists)
* Get the specification (.pspec) file (if it exists)
*
* @return specification file
* @return specification file (.pspec)
*/
public ResourceFile getSpecFile() {
return specFile;
}
/**
* @param slaFile
* Sets the {@link SleighLanguageFile} which represents the .sla and .slaspec files.
*
* @param langFile {@link SleighLanguageFile} which represents the .sla and .slaspec files
*/
public void setSlaFile(ResourceFile slaFile) {
this.slaFile = slaFile;
void setLanguageFile(SleighLanguageFile langFile) {
this.languageFile = langFile;
}
/**
* @return
* Returns the {@link SleighLanguageFile} which represents the .sla and .slaspec files.
*
* @return {@link SleighLanguageFile} which represents the .sla and .slaspec files
*/
public ResourceFile getSlaFile() {
return slaFile;
public SleighLanguageFile getLanguageFile() {
return languageFile;
}
public ResourceFile getManualIndexFile() {
@@ -143,4 +144,16 @@ public class SleighLanguageDescription extends BasicLanguageDescription {
public void setManualIndexFile(ResourceFile manualIndexFile) {
this.manualIndexFile = manualIndexFile;
}
/**
* Tests if two Sleigh languages are based on the same .sla file.
*
* @param other {@link SleighLanguageDescription}
* @return true if the other {@link SleighLanguageDescription} uses the same .sla file
*/
public boolean isSameSleighLanguageFile(SleighLanguageDescription other) {
return other != null &&
languageFile.getSlaFile().equals(other.getLanguageFile().getSlaFile());
}
}
@@ -200,18 +200,26 @@ public class SlaFormat {
* @throws IOException for any errors reading from the stream
*/
public static boolean isSlaFormat(InputStream stream) throws IOException {
return getSlaFormat(stream) == FORMAT_VERSION;
}
/**
* Returns the format version number of the specified binary .sla file.
*
* @param stream {@link InputStream}
* @return sla version number, -1 if invalid header
* @throws IOException if error reading
*/
public static int getSlaFormat(InputStream stream) throws IOException {
byte[] header = new byte[4];
int readLen = stream.read(header);
if (readLen < 4) {
return false;
return -1;
}
if (header[0] != 's' || header[1] != 'l' || header[2] != 'a') {
return false;
return -1;
}
if (header[3] != FORMAT_VERSION) {
return false;
}
return true;
return Byte.toUnsignedInt(header[3]);
}
/**
@@ -1784,6 +1784,22 @@ public class SleighCompile extends SleighBase {
return 0;
}
public void setOptions(SleighCompileOptions options) {
Set<Entry<String, String>> entrySet = options.preprocs.entrySet();
for (Entry<String, String> entry : entrySet) {
setPreprocValue(entry.getKey(), entry.getValue());
}
setUnnecessaryPcodeWarning(options.unnecessaryPcodeWarning);
setLenientConflict(options.lenientConflict);
setLocalCollisionWarning(options.allCollisionWarning);
setAllNopWarning(options.allNopWarning);
setDeadTempWarning(options.deadTempWarning);
setUnusedFieldWarning(options.unusedFieldWarning);
setEnforceLocalKeyWord(options.enforceLocalKeyWord);
setInsensitiveDuplicateError(!options.caseSensitiveRegisterNames);
setDebugOutput(options.debugOutput);
}
public void setAllOptions(Map<String, String> preprocs, boolean unnecessaryPcodeWarning,
boolean lenientConflict, boolean allCollisionWarning, boolean allNopWarning,
boolean deadTempWarning, boolean unusedFieldWarning, boolean enforceLocalKeyWord,
@@ -16,18 +16,16 @@
package ghidra.pcodeCPort.slgh_compile;
import java.io.*;
import java.util.*;
import java.util.TreeSet;
import org.antlr.runtime.RecognitionException;
import org.jdom2.JDOMException;
import generic.jar.ResourceFile;
import ghidra.GhidraApplicationLayout;
import ghidra.GhidraLaunchable;
import ghidra.app.plugin.processors.sleigh.SleighException;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
/**
* <code>SleighCompileLauncher</code> Sleigh compiler launch provider
@@ -59,250 +57,68 @@ public class SleighCompileLauncher implements GhidraLaunchable {
* @throws RecognitionException for parse errors
*/
public static int runMain(String[] args) throws IOException, RecognitionException {
int retval;
String filein = null;
String fileout = null;
Map<String, String> preprocs = new HashMap<>();
SleighCompile.yydebug = false;
boolean allMode = false;
if (args.length < 1) {
// @formatter:off
Msg.info(SleighCompile.class, "Usage: sleigh [options...] [<infile.slaspec> [<outfile.sla>] | -a <directory-path>]");
Msg.info(SleighCompile.class, " sleigh [options...] <infile.slaspec> [<outfile.sla>]");
Msg.info(SleighCompile.class, " <infile.slaspec> source slaspec file to be compiled");
Msg.info(SleighCompile.class, " <outfile.sla> optional output sla file (infile.sla assumed)");
Msg.info(SleighCompile.class, " or");
Msg.info(SleighCompile.class, " sleigh [options...] -a <directory-path>");
Msg.info(SleighCompile.class, " <directory-path> directory to have all slaspec files compiled");
Msg.info(SleighCompile.class, " options:");
Msg.info(SleighCompile.class, " -x turns on parser debugging");
Msg.info(SleighCompile.class, " -y write .sla using XML debug format");
Msg.info(SleighCompile.class, " -u print warnings for unnecessary pcode instructions");
Msg.info(SleighCompile.class, " -l report pattern conflicts");
Msg.info(SleighCompile.class, " -n print warnings for all NOP constructors");
Msg.info(SleighCompile.class, " -t print warnings for dead temporaries");
Msg.info(SleighCompile.class, " -e enforce use of 'local' keyword for temporaries");
Msg.info(SleighCompile.class, " -c print warnings for all constructors with colliding operands");
Msg.info(SleighCompile.class, " -f print warnings for unused token fields");
Msg.info(SleighCompile.class, " -s treat register names as case sensitive");
Msg.info(SleighCompile.class, " -DNAME=VALUE defines a preprocessor macro NAME with value VALUE (option may be repeated)");
Msg.info(SleighCompile.class, " -dMODULE defines a preprocessor macro MODULE with a value of its module path (option may be repeated)");
Msg.info(SleighCompile.class, " -i <options-file> inject options from specified file");
// @formatter:on
if (args.length == 0) {
SleighCompileOptions.usage();
return 2;
}
SleighCompileOptions options;
try {
options = SleighCompileOptions.parse(args);
}
catch (SleighException e) {
return 1;
}
return launchCompile(options);
}
boolean unnecessaryPcodeWarning = false;
boolean lenientConflict = true;
boolean allCollisionWarning = false;
boolean allNopWarning = false;
boolean deadTempWarning = false;
boolean enforceLocalKeyWord = false;
boolean unusedFieldWarning = false;
boolean caseSensitiveRegisterNames = false;
boolean debugOutput = false;
public static int launchCompile(SleighCompileOptions options)
throws IOException, RecognitionException {
if (options.allMode) {
return compileAll(options);
}
else {
return compileOne(options);
}
}
int i;
for (i = 0; i < args.length; ++i) {
if (args[i].charAt(0) != '-') {
break;
}
else if (args[i].charAt(1) == 'i') {
// inject options from file specified by next argument
args = injectOptionsFromFile(args, ++i);
if (args == null) {
return 1;
}
}
else if (args[i].charAt(1) == 'D') {
String preproc = args[i].substring(2);
int pos = preproc.indexOf('=');
if (pos == -1) {
Msg.error(SleighCompile.class, "Bad sleigh option: " + args[i]);
return 1;
}
String name = preproc.substring(0, pos);
String value = preproc.substring(pos + 1);
preprocs.put(name, value); // Preprocessor macro definitions
}
else if (args[i].charAt(1) == 'd') {
String moduleName = args[i].substring(2);
ResourceFile module = Application.getModuleRootDir(moduleName);
if (module == null || !module.isDirectory()) {
Msg.error(SleighCompile.class,
"Failed to resolve module reference: " + args[i]);
return 1;
}
Msg.debug(SleighCompile.class,
"Sleigh resolved module: " + moduleName + "=" + module.getAbsolutePath());
preprocs.put(moduleName, module.getAbsolutePath()); // Preprocessor macro definitions
}
else if (args[i].charAt(1) == 'u') {
unnecessaryPcodeWarning = true;
}
else if (args[i].charAt(1) == 't') {
deadTempWarning = true;
}
else if (args[i].charAt(1) == 'e') {
enforceLocalKeyWord = true;
}
else if (args[i].charAt(1) == 'f') {
unusedFieldWarning = true;
}
else if (args[i].charAt(1) == 'l') {
lenientConflict = false;
}
else if (args[i].charAt(1) == 'c') {
allCollisionWarning = true;
}
else if (args[i].charAt(1) == 'n') {
allNopWarning = true;
}
else if (args[i].charAt(1) == 'a') {
allMode = true;
}
else if (args[i].charAt(1) == 's') {
caseSensitiveRegisterNames = true;
}
else if (args[i].charAt(1) == 'y') {
debugOutput = true;
}
else if (args[i].charAt(1) == 'x') {
SleighCompile.yydebug = true; // Debug option
public static int compileOne(SleighCompileOptions options)
throws IOException, RecognitionException {
SleighCompile compiler = new SleighCompile();
compiler.setOptions(options);
return compiler.run_compilation(options.inputFile.getPath(), options.outputFile.getPath());
}
public static int compileAll(SleighCompileOptions options)
throws IOException, RecognitionException {
TreeSet<String> failures = new TreeSet<>();
int totalFailures = 0;
int totalSuccesses = 0;
DirectoryVisitor visitor = new DirectoryVisitor(options.allDir, SLASPEC_FILTER);
for (File input : visitor) {
System.out.println("Compiling " + input + ":");
SleighCompile compiler = new SleighCompile();
compiler.setOptions(options);
String outname = input.getName().replace(".slaspec", ".sla");
File output = new File(input.getParentFile(), outname);
int retval = compiler.run_compilation(input.getPath(), output.getPath());
System.out.println();
if (retval != 0) {
++totalFailures;
failures.add(input.getPath());
}
else {
Msg.error(SleighCompile.class, "Unknown option: " + args[i]);
return 1;
++totalSuccesses;
}
}
if (i < args.length - 2) {
Msg.error(SleighCompile.class, "Too many parameters");
return 1;
}
if (allMode) {
if (i == args.length) {
Msg.error(SleighCompile.class, "Missing input directory path");
return 1;
System.out.println(totalSuccesses + " languages successfully compiled");
if (totalFailures != 0) {
for (String path : failures) {
System.out.println(path + " failed to compile");
}
String directory = args[i];
File dir = new File(directory);
if (!dir.exists() || !dir.isDirectory()) {
Msg.error(SleighCompile.class, directory + " is not a directory");
return 1;
}
TreeSet<String> failures = new TreeSet<>();
int totalFailures = 0;
int totalSuccesses = 0;
DirectoryVisitor visitor = new DirectoryVisitor(dir, SLASPEC_FILTER);
for (File input : visitor) {
System.out.println("Compiling " + input + ":");
SleighCompile compiler = new SleighCompile();
compiler.setAllOptions(preprocs, unnecessaryPcodeWarning, lenientConflict,
allCollisionWarning, allNopWarning, deadTempWarning, unusedFieldWarning,
enforceLocalKeyWord, caseSensitiveRegisterNames,
debugOutput);
String outname = input.getName().replace(".slaspec", ".sla");
File output = new File(input.getParent(), outname);
retval =
compiler.run_compilation(input.getAbsolutePath(), output.getAbsolutePath());
System.out.println();
if (retval != 0) {
++totalFailures;
failures.add(input.getAbsolutePath());
}
else {
++totalSuccesses;
}
}
System.out.println(totalSuccesses + " languages successfully compiled");
if (totalFailures != 0) {
for (String path : failures) {
System.out.println(path + " failed to compile");
}
System.out.println(totalFailures + " languages total failed to compile");
}
return -totalFailures;
System.out.println(totalFailures + " languages total failed to compile");
}
// single file compile
SleighCompile compiler = new SleighCompile();
compiler.setAllOptions(preprocs, unnecessaryPcodeWarning, lenientConflict,
allCollisionWarning, allNopWarning, deadTempWarning, unusedFieldWarning,
enforceLocalKeyWord, caseSensitiveRegisterNames, debugOutput);
if (i == args.length) {
Msg.error(SleighCompile.class, "Missing input file name");
return 1;
}
filein = args[i];
if (i < args.length - 1) {
fileout = args[i + 1];
}
String baseName = filein;
if (filein.toLowerCase().endsWith(FILE_IN_DEFAULT_EXT)) {
baseName = filein.substring(0, filein.length() - FILE_IN_DEFAULT_EXT.length());
}
filein = baseName + FILE_IN_DEFAULT_EXT;
String baseOutName = fileout;
if (fileout == null) {
baseOutName = baseName;
}
else if (fileout.toLowerCase().endsWith(FILE_OUT_DEFAULT_EXT)) {
baseOutName = fileout.substring(0, fileout.length() - FILE_OUT_DEFAULT_EXT.length());
}
fileout = baseOutName + FILE_OUT_DEFAULT_EXT;
return compiler.run_compilation(filein, fileout);
return -totalFailures;
}
private static String[] injectOptionsFromFile(String[] args, int index) {
if (index >= args.length) {
Msg.error(SleighCompile.class, "Missing options input file name");
return null;
}
File optionsFile = new File(args[index]);
if (!optionsFile.isFile()) {
Msg.error(SleighCompile.class,
"Options file not found: " + optionsFile.getAbsolutePath());
if (SystemUtilities.isInDevelopmentMode()) {
Msg.error(SleighCompile.class,
"Eclipse language module must be selected and 'gradle prepdev' prevously run");
}
return null;
}
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i <= index; i++) {
list.add(args[i]);
}
try (BufferedReader r = new BufferedReader(new FileReader(optionsFile))) {
String option = r.readLine();
while (option != null) {
option = option.trim();
if (option.length() != 0 && !option.startsWith("#")) {
list.add(option);
}
option = r.readLine();
}
}
catch (IOException e) {
Msg.error(SleighCompile.class,
"Reading options file failed (" + optionsFile.getName() + "): " + e.getMessage());
return null;
}
for (int i = index + 1; i < args.length; i++) {
list.add(args[i]);
}
return list.toArray(new String[list.size()]);
}
}
@@ -0,0 +1,283 @@
/* ###
* 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.pcodeCPort.slgh_compile;
import java.io.File;
import java.io.IOException;
import java.util.*;
import org.apache.commons.io.FilenameUtils;
import generic.jar.ResourceFile;
import ghidra.app.plugin.processors.sleigh.SleighException;
import ghidra.app.plugin.processors.sleigh.SleighLanguageFile;
import ghidra.framework.Application;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import utilities.util.FileUtilities;
/**
* Represents the options the sleigh compiler uses
*/
public class SleighCompileOptions {
public File inputFile;
public File outputFile;
public boolean allMode = false;
public File allDir;
public Map<String, String> preprocs = new HashMap<>();
public boolean unnecessaryPcodeWarning = false;
public boolean lenientConflict = true;
public boolean allCollisionWarning = false;
public boolean allNopWarning = false;
public boolean deadTempWarning = false;
public boolean enforceLocalKeyWord = false;
public boolean unusedFieldWarning = false;
public boolean caseSensitiveRegisterNames = false;
public boolean debugOutput = false;
public static void usage() {
// @formatter:off
Msg.info(SleighCompileOptions.class, "Usage: sleigh [options...] [<infile.slaspec> [<outfile.sla>] | -a <directory-path>]");
Msg.info(SleighCompileOptions.class, " sleigh [options...] <infile.slaspec> [<outfile.sla>]");
Msg.info(SleighCompileOptions.class, " <infile.slaspec> source slaspec file to be compiled");
Msg.info(SleighCompileOptions.class, " <outfile.sla> optional output sla file (infile.sla assumed)");
Msg.info(SleighCompileOptions.class, " or");
Msg.info(SleighCompileOptions.class, " sleigh [options...] -a <directory-path>");
Msg.info(SleighCompileOptions.class, " <directory-path> directory to have all slaspec files compiled");
Msg.info(SleighCompileOptions.class, " options:");
Msg.info(SleighCompileOptions.class, " -x turns on parser debugging");
Msg.info(SleighCompileOptions.class, " -y write .sla using XML debug format");
Msg.info(SleighCompileOptions.class, " -u print warnings for unnecessary pcode instructions");
Msg.info(SleighCompileOptions.class, " -l report pattern conflicts");
Msg.info(SleighCompileOptions.class, " -n print warnings for all NOP constructors");
Msg.info(SleighCompileOptions.class, " -t print warnings for dead temporaries");
Msg.info(SleighCompileOptions.class, " -e enforce use of 'local' keyword for temporaries");
Msg.info(SleighCompileOptions.class, " -c print warnings for all constructors with colliding operands");
Msg.info(SleighCompileOptions.class, " -f print warnings for unused token fields");
Msg.info(SleighCompileOptions.class, " -s treat register names as case sensitive");
Msg.info(SleighCompileOptions.class, " -DNAME=VALUE defines a preprocessor macro NAME with value VALUE (option may be repeated)");
Msg.info(SleighCompileOptions.class, " -dMODULE defines a preprocessor macro MODULE with a value of its module path (option may be repeated)");
Msg.info(SleighCompileOptions.class, " -i <options-file> inject options from specified file");
// @formatter:on
}
/**
* Evaluates an array of string arguments and saves the values into a {@link SleighCompileOptions}
* instance.
*
* @param args array of arg strings
* @return new {@link SleighCompileOptions} instance
* @throws SleighException if error in an argument
*/
public static SleighCompileOptions parse(String[] args) throws SleighException {
SleighCompileOptions results = new SleighCompileOptions();
Deque<String> argList = new ArrayDeque<>(List.of(args));
while (!argList.isEmpty()) {
if (!argList.peekFirst().startsWith("-")) {
break;
}
String arg = argList.removeFirst();
if (arg.isBlank()) {
continue;
}
results.processArg(arg, argList);
}
if (!results.allMode) {
// Process trailing source and destination filename parameters
if (argList.isEmpty()) {
Msg.error(SleighCompileOptions.class, "Missing input file name");
throw new SleighException("Missing input file name");
}
results.inputFile = new File(argList.removeFirst()).getAbsoluteFile();
results.outputFile =
!argList.isEmpty() ? new File(argList.removeFirst()).getAbsoluteFile() : null;
String baseName = results.inputFile.getName();
if (baseName.toLowerCase().endsWith(SleighLanguageFile.SLASPEC_EXT)) {
baseName = FilenameUtils.getBaseName(baseName);
}
else {
results.inputFile = new File(results.inputFile.getParentFile(),
results.inputFile.getName() + SleighLanguageFile.SLASPEC_EXT);
}
if (results.outputFile == null) {
results.outputFile = new File(results.inputFile.getParentFile(),
baseName + SleighLanguageFile.SLA_EXT);
}
else if (!results.outputFile.getName()
.toLowerCase()
.endsWith(SleighLanguageFile.SLA_EXT)) {
results.outputFile = new File(results.outputFile.getParentFile(),
results.outputFile.getName() + SleighLanguageFile.SLA_EXT);
}
}
if (!argList.isEmpty()) {
Msg.error(SleighCompileOptions.class, "Too many parameters: " + argList.toString());
throw new SleighException("Too many parameters: " + argList.toString());
}
return results;
}
/**
* Evaluates the arguments in a sleighArgs.txt file and returns a {@link SleighCompileOptions}
*
* @param argsFile file containing an argument per line
* @return {@link SleighCompileOptions}
*/
public static SleighCompileOptions fromFile(File argsFile) {
SleighCompileOptions results = new SleighCompileOptions();
Deque<String> argList = new ArrayDeque<>(getOptionsFromFile(argsFile));
while (!argList.isEmpty()) {
String arg = argList.removeFirst();
if (arg.isBlank()) {
continue;
}
if (!arg.startsWith("-")) {
break;
}
results.processArg(arg, argList);
}
return results;
}
public void addPreprocessorMacroDefinition(String name, String value) {
preprocs.put(name, value); // Preprocessor macro definitions
}
private void processArg(String arg, Deque<String> argList) {
if (arg.length() < 2 || arg.charAt(0) != '-') {
throw new SleighException("Invalid argument: " + arg);
}
switch (arg.charAt(1)) {
case 'i':
// inject options from file specified by next argument
if (argList.isEmpty()) {
Msg.error(SleighCompileOptions.class, "Missing options input file name");
throw new SleighException("Missing options input file name");
}
String optFilename = argList.removeFirst();
File optionsFile = new File(optFilename).getAbsoluteFile();
if (!optionsFile.isFile()) {
Msg.error(SleighCompileOptions.class, "Options file not found: " + optionsFile);
if (SystemUtilities.isInDevelopmentMode()) {
Msg.error(SleighCompileOptions.class,
"Eclipse language module must be selected and 'gradle prepdev' prevously run");
}
throw new SleighException("Options file not found: " + optionsFile);
}
Deque<String> newArgList = new ArrayDeque<>(getOptionsFromFile(optionsFile));
newArgList.addAll(argList);
argList.clear();
argList.addAll(newArgList);
break;
case 'D':
String preproc = arg.substring(2);
int pos = preproc.indexOf('=');
if (pos == -1) {
Msg.error(SleighCompileOptions.class, "Bad sleigh option: " + arg);
throw new SleighException("Bad sleigh option: " + arg);
}
String name = preproc.substring(0, pos);
String value = preproc.substring(pos + 1);
addPreprocessorMacroDefinition(name, value);
break;
case 'd':
String moduleName = arg.substring(2);
ResourceFile module = Application.getModuleRootDir(moduleName);
if (module == null || !module.isDirectory()) {
Msg.error(SleighCompileOptions.class,
"Failed to resolve module reference: " + arg);
throw new SleighException("Failed to resolve module reference: " + arg);
}
Msg.debug(SleighCompileOptions.class,
"Sleigh resolved module: " + moduleName + "=" + module.getAbsolutePath());
addPreprocessorMacroDefinition(moduleName, module.getAbsolutePath());
break;
case 'u':
unnecessaryPcodeWarning = true;
break;
case 't':
deadTempWarning = true;
break;
case 'e':
enforceLocalKeyWord = true;
break;
case 'f':
unusedFieldWarning = true;
break;
case 'l':
lenientConflict = false;
break;
case 'c':
allCollisionWarning = true;
break;
case 'n':
allNopWarning = true;
break;
case 'a':
if (argList.isEmpty()) {
Msg.error(SleighCompileOptions.class, "Missing input directory path");
throw new SleighException("Missing input directory path");
}
File dir = new File(argList.removeFirst()).getAbsoluteFile();
if (!dir.exists() || !dir.isDirectory()) {
Msg.error(SleighCompileOptions.class, dir + " is not a directory");
throw new SleighException(dir + " is not a directory");
}
allMode = true;
allDir = dir;
break;
case 's':
caseSensitiveRegisterNames = true;
break;
case 'y':
debugOutput = true;
break;
case 'x':
SleighCompile.yydebug = true; // Debug option
break;
default:
Msg.error(SleighCompileOptions.class, "Unknown option: " + arg);
throw new SleighException("Unknown option: " + arg);
}
}
private static List<String> getOptionsFromFile(File optionsFile) throws SleighException {
try {
return FileUtilities.getLines(optionsFile)
.stream()
.map(String::trim)
.filter(option -> !option.isBlank() && !option.startsWith("#"))
.toList();
}
catch (IOException e) {
Msg.error(SleighCompileOptions.class,
"Reading options file failed (" + optionsFile.getName() + "): " + e.getMessage());
throw new SleighException("Failed reading options file [%s]".formatted(optionsFile), e);
}
}
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,10 +35,21 @@ public class LanguageNotFoundException extends IOException {
/**
* Language not found
* @param languageID
*
* @param languageID {@link LanguageID}
*/
public LanguageNotFoundException(LanguageID languageID) {
super("Language not found for '" + languageID + "'");
this(languageID, (Throwable) null);
}
/**
* Language not found because of an exception
*
* @param languageID {@link LanguageID}
* @param cause {@link Throwable} that caused the language to not be found
*/
public LanguageNotFoundException(LanguageID languageID, Throwable cause) {
super("Language not found for '" + languageID + "'", cause);
}
public LanguageNotFoundException(String message) {
@@ -16,6 +16,7 @@
package ghidra.program.model.lang;
import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.task.TaskMonitor;
/**
* NOTE: ALL LanguageProvider CLASSES MUST END IN "LanguageProvider". If not,
@@ -31,9 +32,22 @@ public interface LanguageProvider extends ExtensionPoint {
*
* @param languageId the name of the language to be retrieved
* @return the {@link Language} with the given name or null if not found
* @throws RuntimeException if language instantiation error occurs
* @throws LanguageNotFoundException if language instantiation error
*/
Language getLanguage(LanguageID languageId);
default Language getLanguage(LanguageID languageId) throws LanguageNotFoundException {
return getLanguage(languageId, TaskMonitor.DUMMY);
}
/**
* Returns the language with the given name or null if no language has that name.
*
* @param languageId the name of the language to be retrieved
* @param monitor {@link TaskMonitor}
* @return the {@link Language} with the given name or null if not found
* @throws LanguageNotFoundException if language instantiation error
*/
Language getLanguage(LanguageID languageId, TaskMonitor monitor)
throws LanguageNotFoundException;
/**
* Returns a list of language descriptions provided by this provider
@@ -15,8 +15,7 @@
*/
package ghidra.program.model.pcode;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.util.ArrayList;
import ghidra.program.model.address.AddressFactory;
@@ -50,7 +49,7 @@ import ghidra.program.model.address.AddressSpace;
* For strings, the integer encoded after the \e type byte, is the actual length of the string. The
* string data itself is stored immediately after the length integer using UTF8 format.
* */
public class PackedDecode implements Decoder {
public class PackedDecode implements Decoder, Closeable {
public static final int HEADER_MASK = 0xc0;
public static final int ELEMENT_START = 0x40;
@@ -231,6 +230,7 @@ public class PackedDecode implements Decoder {
* Close stream cached by the ingestStreamAsNeeded method.
* @throws IOException for low-level problems with the stream
*/
@Override
public void close() throws IOException {
inStream.close();
inStream = null;
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,6 +16,7 @@
package ghidra.program.util;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -23,6 +24,7 @@ import org.apache.logging.log4j.Logger;
import ghidra.app.plugin.processors.sleigh.SleighLanguageProvider;
import ghidra.program.model.lang.*;
import ghidra.util.task.TaskBuilder;
import ghidra.util.task.TaskMonitor;
/**
* Default Language service used gather up all the languages that were found
@@ -318,27 +320,54 @@ public class DefaultLanguageService implements LanguageService {
this.provider = lp;
}
// synchronized to prevent multiple clients from trying to load the language at once
synchronized Language getLanguage() {
/**
* Loads a {@link Language} from a {@link LanguageProvider}, using a Task with a modal
* {@link TaskMonitor}.
* <p>
* This is the call path that users will see when using Ghidra and a language is
* initially loaded from disk.
*
* @return {@link Language}
* @throws LanguageNotFoundException if error initializing Language
*/
synchronized Language getLanguage() throws LanguageNotFoundException {
// synchronized to prevent multiple clients from trying to load the language at once
LanguageID id = description.getLanguageID();
if (provider.isLanguageLoaded(id)) {
// already loaded; no need to create a task
return provider.getLanguage(id);
return provider.getLanguage(id, TaskMonitor.DUMMY);
}
//@formatter:off
TaskBuilder.withRunnable(monitor -> {
provider.getLanguage(id); // load and cache
})
.setTitle("Loading language '" + id + "'")
.setCanCancel(false)
.setHasProgress(false)
.launchModal()
;
//@formatter:on
AtomicReference<Throwable> langError = new AtomicReference<>();
AtomicReference<Language> langResult = new AtomicReference<>();
return provider.getLanguage(id);
//@formatter:off
// Need to start task with canCancel true to get the cancel button added to the task dialog
TaskBuilder.withRunnable(monitor -> {
monitor.setCancelEnabled(false);
try {
langResult.set(provider.getLanguage(id, monitor));
}
catch (Throwable th) {
langError.set(th);
}
})
.setTitle("Loading language '%s'".formatted(id))
.setCanCancel(true)
.setHasProgress(false)
.launchModal();
//@formatter:on
Throwable th = langError.get();
if (th instanceof LanguageNotFoundException lnfe) {
throw lnfe;
}
else if (th != null) {
throw new LanguageNotFoundException(id, th);
}
return langResult.get();
}
@Override
@@ -0,0 +1,254 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.processors.sleigh;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Consumer;
import generic.concurrent.io.ProcessConsumer;
/**
* Helps creating and launching a java process.
* <p>
* By default, the launched process will have the same classpath as the current jvm and use the
* same java binary to start the session.
* <p>
* Example usage:
* <pre>
* Process newproc = new JavaProcessBuilder(AClassWithMainEntryPoint.class)
* .addProperty("sun.java2d.opengl", "false")
* .addLaunchArg("-Xmx2G")
* .withStdoutMonitor( (s) -> System.out.println(s) )
* .start();
* </pre>
*/
class JavaProcessBuilder {
private static final String CP_SEP = System.getProperty("path.separator");
private File javaBinary;
private String mainClassname;
private List<String> arguments;
private String classPaths;
private Map<String, String> javaProperties = new HashMap<>();
private List<String> launchArgs = new ArrayList<>();
private Consumer<String> stdoutMonitor;
/**
* Creates a java process builder, setting the main entry point for the launched process.
*
* @param mainClass class that contains a {@code public static void main(String[])} entry point.
* Also implies that the launched process should have the same classpath as the current
* jvm, or that the referenced class is somehow included in the launched process's classpath
*/
public JavaProcessBuilder(Class<?> mainClass) {
this.mainClassname = mainClass.getName();
}
/**
* Creates a java process builder, setting the main entry point for the launched process.
*
* @param mainClassname name of a class that contains a
* {@code public static void main(String[])} entry point.
*/
public JavaProcessBuilder(String mainClassname) {
this.mainClassname = mainClassname;
}
/**
* Sets the arguments for the launched processes {@code main(String[] args)} method.
*
* @param newArguments arguments for the launched main() entry point
* @return chainable ref to same
*/
public JavaProcessBuilder withArguments(List<String> newArguments) {
this.arguments = new ArrayList<>(newArguments);
return this;
}
/**
* Sets the location of the java jdk install, which controls how the bin/java[.exe] is found.
*
* @param javaHomeDir java home directory
* @return chainable ref to same
*/
public JavaProcessBuilder withJavaHome(File javaHomeDir) {
this.javaBinary = javaHomeDir != null ? javaBinaryFromJavaHome(javaHomeDir) : null;
return this;
}
private static File javaBinaryFromJavaHome(File javaHomeDir) {
File binDir = new File(javaHomeDir, "bin");
return new File(binDir, "java");
}
/**
* Returns the location of the java binary that will be used to launch the new process.
*
* @return File pointing to the java jvm binary (java or java.exe)
*/
public File getJavaBinary() {
File f = javaBinary;
if (f == null) {
f = javaBinaryFromJavaHome(new File(System.getProperty("java.home")));
}
return f;
}
/**
* Sets the classpaths for the launched process. The string is expected to be delimited with
* the correct separators.
*
* @param newClassPaths classpath string for the launched process
* @return chainable ref to same
*/
public JavaProcessBuilder withClasspaths(String newClassPaths) {
this.classPaths = newClassPaths;
return this;
}
/**
* Returns the classpath string that will be used to launch the new process.
*
* @return string
*/
public String getClasspaths() {
String s = classPaths;
if (s == null) {
s = System.getProperty("java.class.path");
}
return s;
}
/**
* Adds an element to the classpath.
*
* @param classPath single classpath element
* @return chainable ref to same
*/
public JavaProcessBuilder addClasspath(String classPath) {
this.classPaths = Objects.requireNonNullElse(this.classPaths, "") + CP_SEP + classPath;
return this;
}
/**
* Sets a callback that will handle each line written to stdout by the launched process.
* <p>
* The monitor will be called an additional time with a {@code null} value after the
* spawned process has exited and its stdout has emptied.
*
* @param newStdoutMonitor Consumer<String> that will receive each text line written by
* the launched process to it's stdout
* @return chainable ref to same
*/
public JavaProcessBuilder withStdoutMonitor(Consumer<String> newStdoutMonitor) {
this.stdoutMonitor = newStdoutMonitor;
return this;
}
/**
* Adds a java system property to the launched process (e.g. "-DpropertyName=value").
*
* @param propertyName name of the property
* @param propertyValue value of the property
* @return chainable ref to same
*/
public JavaProcessBuilder addProperty(String propertyName, String propertyValue) {
javaProperties.put(propertyName, propertyValue);
return this;
}
/**
* Returns a list of properties that will be added to the launched process.
*
* @return list of property definition arguments
*/
public List<String> getProperties() {
return javaProperties.entrySet()
.stream()
.map(entry -> "-D%s=%s".formatted(entry.getKey(), entry.getValue()))
.toList();
}
/**
* Adds a java launch argument (e.g. "-Xmx512M", etc)
*
* @param launchArg raw argument to pass to the java binary when launching
* @return chainable ref to same
*/
public JavaProcessBuilder addLaunchArg(String launchArg) {
launchArgs.add(launchArg);
return this;
}
/**
* Creates a 'real' {@link ProcessBuilder} using the information specified in this builder.
* <p>
* The stdout monitor will still need to be installed into any launched process.
*
* @return {@link ProcessBuilder}
*/
public ProcessBuilder getProcessBuilder() {
Objects.requireNonNull(mainClassname);
List<String> commandParts = new ArrayList<>();
commandParts.add(getJavaBinary().getPath());
commandParts.addAll(launchArgs);
String cp = getClasspaths();
if (!cp.isBlank()) {
commandParts.add("-cp");
commandParts.add(cp);
}
commandParts.addAll(getProperties());
commandParts.add(mainClassname);
commandParts.addAll(arguments != null ? arguments : List.of());
ProcessBuilder pb = new ProcessBuilder(commandParts);
return pb;
}
/**
* Installs a monitor that reads the processes stdout
*
* @param process {@link Process}
*/
public void installMonitor(Process process) {
ProcessConsumer.monitorAndSignalEof(process.getInputStream(), stdoutMonitor,
"stdout[%d]".formatted(process.pid()));
}
/**
* Launches a new java process using the information specified in this builder.
*
* @return {@link Process}
* @throws IOException if error starting process
*/
public Process start() throws IOException {
ProcessBuilder pb = getProcessBuilder();
Process process = pb.start();
if (stdoutMonitor != null) {
installMonitor(process);
}
return process;
}
}
@@ -0,0 +1,230 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.processors.sleigh;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import generic.jar.ResourceFile;
import generic.test.AbstractGenericTest;
import ghidra.framework.Application;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.LanguageID;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.task.TaskMonitor;
public class SleighLanguageProviderTest extends AbstractGenericTest {
private LanguageID x86LangId = new LanguageID("x86:LE:32:default");
private ResourceFile x86LdefsFile = Application.findDataFileInAnyModule("languages/x86.ldefs");
private SleighLanguageProvider langProvider;
@Test(timeout = 60000 + 5000 /* 1 minute (default lock timeout) + 5 seconds */)
public void testSlaThunderingHerds() throws Exception {
// Tests when a thundering herd of processes all try to read a sleigh file at the same time
langProvider = new SleighLanguageProvider(x86LdefsFile);
// Ensure the lang file exist, and then tweak the timestamp to force one of the
// spawned procs to recompile the sla
SleighLanguage lang = langProvider.getLanguage(x86LangId, TaskMonitor.DUMMY);
SleighLanguageDescription langDesc = lang.getLanguageDescription();
SleighLanguageFile langFile = langDesc.getLanguageFile();
File slaFile = langFile.getSlaFile().getFile(false);
File slaSpecFile = langFile.getSlaSpecFile().getFile(false);
slaFile.setLastModified(slaSpecFile.lastModified() - 60 * 1000);
int procsToStart = 20;
long langProviderTimeout = SleighLanguageProvider.LANGUAGE_LOCK_TIMEOUT.toMillis();
long test_start = System.currentTimeMillis();
// 1) start a lot of processes
// 2) wait until they initialize themselves and get to step1 (they wait also for next step)
// 3) allow all launched processes to proceed at same time
// 4) wait for all to exit with success exit code
List<TestProc> procs = new ArrayList<>();
for (int procNum = 0; procNum < procsToStart; procNum++) {
procs.add(TestProc.start(procNum, langProviderTimeout));
}
for (TestProc testProc : procs) {
if (!testProc.waitUntilStep(1)) {
fail(testProc.msg("failed to reach step1"));
}
}
// If the spawned processes are writing to the .sla file incorrectly and simultaneously,
// giving a small delay between each process's critical action historically help expose
// the error.
int DELAY_BETWEEN_PROCESSES = 600;
for (TestProc testProc : procs) {
testProc.sendGoahead();
sleep(DELAY_BETWEEN_PROCESSES);
}
int badExitCount = 0;
for (TestProc testProc : procs) {
if (!testProc.waitUntilStep(2)) {
testProc.log("exited before step 2 reached");
badExitCount++;
}
int exitCode = testProc.proc.waitFor();
if (exitCode != 0) {
testProc.log("exited with error: %d".formatted(exitCode));
badExitCount++;
}
}
Msg.info(this, "Total test time: " + (System.currentTimeMillis() - test_start));
assertTrue(badExitCount == 0);
}
@Test(timeout = 10000 + 5000 /* shorterTimeout + 5 seconds */)
public void testSlaLockTimeout() throws Exception {
// Test that lock timeout successfully causes a failure
langProvider = new SleighLanguageProvider(x86LdefsFile);
SleighLanguageDescription langDesc = langProvider.getLanguageDescription(x86LangId);
SleighLanguageFile langFile = langDesc.getLanguageFile();
long shorterTimeoutMS = Duration.ofSeconds(10).toMillis();
TestProc testProc = TestProc.start(0, shorterTimeoutMS);
assertTrue(testProc.waitUntilStep(1));
langFile.withLock(Duration.ofMillis(10), TaskMonitor.DUMMY, () -> {
testProc.sendGoahead();
Thread.sleep(shorterTimeoutMS + 1000); // hold lock while testProc is trying to get it, force it to fail
});
testProc.assertExitNum(1);
}
public void testSlaLanguage_FromOtherProcess() throws Throwable {
// this is not a junit test entry point, but instead is what is run
// in each sub-process launched by each TestProc instance.
langProvider = new SleighLanguageProvider(x86LdefsFile);
TestProc.waitForGoahead();
Language lang = langProvider.getLanguage(x86LangId, TaskMonitor.DUMMY);
// write via stdout to the parent process, which will handle logging
System.out.println("STEP 2 Got language %s".formatted(lang));
System.out.flush();
System.exit(0);
}
public static void main(String[] args) {
// this is the entry point for the launched TestProcs that will be fighting over
// a sleigh language file
try {
SleighLanguageProviderTest test = new SleighLanguageProviderTest();
test.testSlaLanguage_FromOtherProcess();
}
catch (Throwable th) {
// write via stdout to the parent process, which will handle logging
System.out.println("Exception " + th);
th.printStackTrace(System.out);
System.exit(1);
}
}
/**
* Handles coordinating an external java process that will be attempting to access a common
* sleigh language file.
*/
static class TestProc {
static TestProc start(int procNum, long timeoutMS) throws IOException {
TestProc testProc = new TestProc();
testProc.procNum = procNum;
testProc.proc = new JavaProcessBuilder(SleighLanguageProviderTest.class) // self class's main()
.addProperty(SystemUtilities.TESTING_PROPERTY, "true")
.addProperty(SleighLanguageProvider.LANGUAGE_LOCK_TIMEOUT_PROPNAME,
"" + timeoutMS)
.withStdoutMonitor(testProc::update)
.start();
testProc.log("Started");
return testProc;
}
Process proc;
int procNum;
AtomicInteger stepNum = new AtomicInteger();
AtomicBoolean stdoutMonitorDone = new AtomicBoolean();
Thread monitorThread;
void sendGoahead() throws IOException {
proc.getOutputStream().write('\n');
proc.getOutputStream().flush();
}
static void waitForGoahead() throws IOException {
// write via stdout to the parent process, which will handle logging
System.out.println("STEP 1 Waiting for synchronization go-ahead...");
System.out.flush();
int b = System.in.read();
System.out.println("Read byte: " + b);
System.out.flush();
}
boolean waitUntilStep(int waitForStepNum) throws InterruptedException {
while (stepNum.get() < waitForStepNum) {
if (stdoutMonitorDone.get()) {
return false;
}
Thread.sleep(200);
}
return true;
}
void update(String s) {
if (s == null) {
stdoutMonitorDone.set(true);
return;
}
log(s);
if (s.startsWith("STEP ")) {
stepNum.set(Integer.parseInt(s.split(" ")[1]));
}
}
void assertExitNum(int exitNum) throws InterruptedException {
assertEquals(exitNum, proc.waitFor()); // wait until helper process has finished
log("exit code: " + proc.waitFor());
}
String msg(String s) {
return "TestProc[%d-%d] %s".formatted(procNum, proc.pid(), s);
}
void log(String s) {
Msg.info(this, msg(s));
}
}
}
@@ -0,0 +1,58 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.processors.sleigh;
import java.time.Duration;
import org.junit.Test;
import generic.jar.ResourceFile;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import ghidra.program.model.lang.LanguageID;
import ghidra.util.task.TaskMonitor;
public class X86SleighLanguageLockerTest {
private LanguageID x86LangId = new LanguageID("x86:LE:32:default");
//@Test
public void testLockLanguageForever() throws Exception {
// Locks the language's file forever, for manually testing the GUI frontend when it tries
// to load a language.
// This test should not be enabled by default.
// Don't use a test application config because we need to use the same user specific
// .ghidra/.ghidra-ver config directories
Application.initializeApplication(new GhidraApplicationLayout(),
new ApplicationConfiguration());
ResourceFile x86LdefsFile = Application.findDataFileInAnyModule("languages/x86.ldefs");
SleighLanguageProvider langProvider = new SleighLanguageProvider(x86LdefsFile);
SleighLanguageDescription langDesc = langProvider.getLanguageDescription(x86LangId);
SleighLanguageFile langFile = langDesc.getLanguageFile();
// we are reading from stdin, so should use stdout to prompt the user
System.out.println("Locking lang file: " + langFile.getSlaFile());
langFile.withLock(Duration.ofMillis(10), TaskMonitor.DUMMY, () -> {
System.out.println("Press enter to end lock");
System.in.read(); // if user hits enter in the console of the test, will exit
});
}
}
@@ -0,0 +1,150 @@
/* ###
* 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.pcodeCPort.slgh_compile;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import utilities.util.FileUtilities;
public class SleighCompileOptionsTest extends AbstractGenericTest {
@Test
public void testArgFile() throws IOException {
File argTmp = createTempFile("sleigh_args");
FileUtilities.writeStringToFile(argTmp, """
-x
-u
-l
-n
-t
-e
-c
-f
-s
-DTESTNAME=TESTVALUE
-dx86
""");
// test default -x before doing any option parsing because its a global flag
assertFalse(SleighCompile.yydebug);
SleighCompileOptions defaultOpts = new SleighCompileOptions();
SleighCompileOptions opts = SleighCompileOptions.fromFile(argTmp);
// -x
assertTrue(SleighCompile.yydebug);
// -u
assertTrue(opts.unnecessaryPcodeWarning);
assertNotEquals(defaultOpts.unnecessaryPcodeWarning, opts.unnecessaryPcodeWarning);
// -l
assertFalse(opts.lenientConflict);
assertNotEquals(defaultOpts.lenientConflict, opts.lenientConflict);
// -n
assertTrue(opts.allNopWarning);
assertNotEquals(defaultOpts.allNopWarning, opts.allNopWarning);
// -t
assertTrue(opts.deadTempWarning);
assertNotEquals(defaultOpts.deadTempWarning, opts.deadTempWarning);
// -e
assertTrue(opts.enforceLocalKeyWord);
assertNotEquals(defaultOpts.enforceLocalKeyWord, opts.enforceLocalKeyWord);
// -c
assertTrue(opts.allCollisionWarning);
assertNotEquals(defaultOpts.allCollisionWarning, opts.allCollisionWarning);
// -f
assertTrue(opts.unusedFieldWarning);
assertNotEquals(defaultOpts.unusedFieldWarning, opts.unusedFieldWarning);
// -s
assertTrue(opts.caseSensitiveRegisterNames);
assertNotEquals(defaultOpts.caseSensitiveRegisterNames, opts.caseSensitiveRegisterNames);
// -D
assertNull(defaultOpts.preprocs.get("TESTNAME"));
assertEquals("TESTVALUE", opts.preprocs.get("TESTNAME"));
// -d
assertNull(defaultOpts.preprocs.get("x86"));
assertNotNull(opts.preprocs.get("x86"));
assertTrue(new File(opts.preprocs.get("x86")).isDirectory());
}
@Test
public void testCmdLineArgs_1input() throws IOException {
File argTmp = createTempFile("sleigh_args");
FileUtilities.writeStringToFile(argTmp, "-DTESTNAME=TESTVALUE");
String[] args = { "-i", argTmp.getPath(), "inputfile.slaspec" };
SleighCompileOptions opts = SleighCompileOptions.parse(args);
assertEquals("TESTVALUE", opts.preprocs.get("TESTNAME"));
assertEquals(opts.inputFile.getName(), "inputfile.slaspec");
assertEquals(opts.outputFile.getName(), "inputfile.sla");
}
@Test
public void testCmdLineArgs_1input_default_ext() throws IOException {
File argTmp = createTempFile("sleigh_args");
FileUtilities.writeStringToFile(argTmp, "-DTESTNAME=TESTVALUE");
String[] args = { "-i", argTmp.getPath(), "inputfile" };
SleighCompileOptions opts = SleighCompileOptions.parse(args);
assertEquals("TESTVALUE", opts.preprocs.get("TESTNAME"));
assertEquals(opts.inputFile.getName(), "inputfile.slaspec");
assertEquals(opts.outputFile.getName(), "inputfile.sla");
}
@Test
public void testCmdLineArgs_input_and_output() throws IOException {
File argTmp = createTempFile("sleigh_args");
FileUtilities.writeStringToFile(argTmp, "-DTESTNAME=TESTVALUE");
String[] args = { "-i", argTmp.getPath(), "inputfile.slaspec", "outputfile.sla" };
SleighCompileOptions opts = SleighCompileOptions.parse(args);
assertEquals("TESTVALUE", opts.preprocs.get("TESTNAME"));
assertEquals(opts.inputFile.getName(), "inputfile.slaspec");
assertEquals(opts.outputFile.getName(), "outputfile.sla");
}
@Test
public void testCmdLineArgs_all() throws IOException {
File argTmp = createTempFile("sleigh_args");
FileUtilities.writeStringToFile(argTmp, "-DTESTNAME=TESTVALUE");
File allDir = createTempDirectory("alldir");
String[] args = { "-i", argTmp.getPath(), "-a", allDir.getPath() };
SleighCompileOptions opts = SleighCompileOptions.parse(args);
assertEquals("TESTVALUE", opts.preprocs.get("TESTNAME"));
assertTrue(opts.allMode);
assertEquals(opts.allDir.getPath(), allDir.getPath());
}
}