diff --git a/Ghidra/Features/Base/ghidra_scripts/GeneratePrototypeTestFileScript.java b/Ghidra/Features/Base/ghidra_scripts/GeneratePrototypeTestFileScript.java new file mode 100644 index 0000000000..50d2c39bf2 --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/GeneratePrototypeTestFileScript.java @@ -0,0 +1,1263 @@ +/* ### + * 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. + */ +// This script prompts the user to select a processor, compiler, and prototype and +// then generates a source file used to test that prototype (test using the script +// TestPrototypeScript.java). The script provides a dialog to the user to determine +// a number of properties of the source file, such as whether to include certain +// data types. A "Pre-specifier" is a string placed before a function definition +// to force the compiler to use a certain calling convention. A "Post-specifier" is +// defined similarly. +// +// Compile the resulting file with gcc as follows: +// gcc file.c -o file -O1 -c -fno-inline -fno-leading-underscore +// (note that the object file has the same name as the source file but no suffix). +// If compiling with a different compiler use equivalent options. + +import java.io.*; +import java.util.*; + +import org.apache.commons.lang3.StringUtils; + +import ghidra.app.script.GhidraScript; +import ghidra.features.base.values.GhidraValuesMap; +import ghidra.program.model.data.DataOrganization; +import ghidra.program.model.lang.*; +import ghidra.program.util.DefaultLanguageService; +import ghidra.test.compilers.support.CSpecPrototypeTestConstants; + +public class GeneratePrototypeTestFileScript extends GhidraScript { + + float floatIntPart = 2.0f; + float floatFracPart = 0.5f; + double doubleIntPart = 2.0; + double doubleFracPart = 0.5; + + private static final String INCLUDE_LONG_LONG = "Include long long"; + private static final String NUM_PARAMS = "Number of Parameters"; + private static final String INCLUDE_FLOAT = "Include float"; + private static final String INCLUDE_DOUBLE = "Include double"; + private static final String CALLING_CONVENTION = "Calling Convention"; + private static final String SPECIFIER = "Specifier"; + private static final String OUTPUT_DIRECTORY = "Output Directory"; + private static final String UNSIGNED_TYPES = "Unsigned Integral Types"; + private static final String DECORATE_VARARGS = "Decorate Variadic Functions"; + + // not final since user can choose to use signed or unsigned types + private static String CHAR_TYPE = "uchar"; + private static String SHORT_TYPE = "ushort"; + private static String INT_TYPE = "uint"; + private static String LONG_TYPE = "ulong"; + private static String LONG_LONG_TYPE = "ulonglong"; + + private long variableVal = 0x01; + private long multiplier = 0x0101010101010101l; + private int funcCounter = 0; + private String ccGhidraName; + private String preSpecifier = ""; + private boolean includeFloat; + private boolean includeDouble; + private boolean includeLongLong; + private boolean unsignedTypes = true; + private boolean decorateVarargs = true; + + private int charSize; + private int shortSize; + private int intSize; + private int longSize; + private int longLongSize; + + private int numParams; + private static final int DEFAULT_NUM_PARAMS = 12; + + private File outputDirectory; + + @Override + protected void run() throws Exception { + + Set processors = new HashSet<>(); + LanguageService langService = DefaultLanguageService.getLanguageService(); + langService.getLanguageDescriptions(false).forEach(l -> processors.add(l.getProcessor())); + List processorNames = new ArrayList<>(); + processors.forEach(p -> processorNames.add(p.toString())); + Collections.sort(processorNames, String.CASE_INSENSITIVE_ORDER); + String defaultProcessor = + currentProgram == null ? null : currentProgram.getLanguage().getProcessor().toString(); + String chosenProc = + askChoice("Select Processor", "Processor:", processorNames, defaultProcessor); + + LanguageCompilerSpecQuery query = new LanguageCompilerSpecQuery( + Processor.toProcessor(chosenProc), null, null, null, null); + List specPairs = langService.getLanguageCompilerSpecPairs(query); + Collections.sort(specPairs); + + LanguageCompilerSpecPair defaultPair = + currentProgram == null ? null : currentProgram.getLanguageCompilerSpecPair(); + + LanguageCompilerSpecPair langPair = + askChoice("Select Language and Compiler", "Lang/Comp:", specPairs, defaultPair); + DataOrganization dataOrg = langPair.getCompilerSpec().getDataOrganization(); + + charSize = dataOrg.getCharSize(); + shortSize = dataOrg.getShortSize(); + intSize = dataOrg.getIntegerSize(); + longSize = dataOrg.getLongSize(); + longLongSize = dataOrg.getLongLongSize(); + + PrototypeModel[] prototypes = langPair.getCompilerSpec().getCallingConventions(); + String[] ccNames = new String[prototypes.length]; + for (int i = 0; i < prototypes.length; ++i) { + ccNames[i] = prototypes[i].getName(); + } + PrototypeModel defaultModel = langPair.getCompilerSpec().getDefaultCallingConvention(); + + GhidraValuesMap values = new GhidraValuesMap(); + values.defineChoice(CALLING_CONVENTION, defaultModel.getName(), ccNames); + values.defineInt(NUM_PARAMS, DEFAULT_NUM_PARAMS); + values.defineBoolean(UNSIGNED_TYPES, true); + values.defineString(SPECIFIER, ""); + values.defineBoolean(DECORATE_VARARGS, true); + values.defineBoolean(INCLUDE_LONG_LONG, true); + values.defineBoolean(INCLUDE_FLOAT, true); + values.defineBoolean(INCLUDE_DOUBLE, true); + values.defineDirectory(OUTPUT_DIRECTORY, new File(System.getProperty("user.home"))); + + askValues("Configure Prototype Test", "Language/Compiler: " + langPair.toString(), values); + ccGhidraName = values.getChoice(CALLING_CONVENTION); + numParams = values.getInt(NUM_PARAMS); + int numVariables = numParams + 1; // create extra variable for testing return values + + preSpecifier = values.getString(SPECIFIER); + includeLongLong = values.getBoolean(INCLUDE_LONG_LONG); + includeFloat = values.getBoolean(INCLUDE_FLOAT); + includeDouble = values.getBoolean(INCLUDE_DOUBLE); + outputDirectory = values.getFile(OUTPUT_DIRECTORY); + unsignedTypes = values.getBoolean(UNSIGNED_TYPES); + decorateVarargs = values.getBoolean(DECORATE_VARARGS); + + StringBuilder src = new StringBuilder(); + + if (unsignedTypes) { + src.append("typedef unsigned char uchar;\n"); + src.append("typedef unsigned short ushort;\n"); + src.append("typedef unsigned int uint;\n"); + src.append("typedef unsigned long ulong;\n\n"); + } + else { + CHAR_TYPE = "char"; + SHORT_TYPE = "short"; + INT_TYPE = "int"; + LONG_TYPE = "long"; + LONG_LONG_TYPE = "longlong"; + } + + // create primitive type global variables + // create at least 11 since the variadic function tests need that many + int numPrimitives = Math.max(11, numVariables); + createIntegralTypeGlobals(src, CHAR_TYPE, charSize, numPrimitives); + createIntegralTypeGlobals(src, SHORT_TYPE, shortSize, numPrimitives); + createIntegralTypeGlobals(src, INT_TYPE, intSize, numPrimitives); + createIntegralTypeGlobals(src, LONG_TYPE, longSize, numPrimitives); + if (includeLongLong) { + if (unsignedTypes) { + src.append("typedef unsigned long long ulonglong;\n"); + } + else { + src.append("typedef long long longlong;\n"); + } + createIntegralTypeGlobals(src, LONG_LONG_TYPE, longLongSize, numPrimitives); + } + if (includeFloat) { + createFloatingPointGlobals(src, numPrimitives); + } + if (includeDouble) { + createDoubleGlobals(src, numPrimitives); + } + + // create singleton struct global variables + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_CHAR_SINGLETON_NAME, + List.of(CHAR_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_SHORT_SINGLETON_NAME, + List.of(SHORT_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_INT_SINGLETON_NAME, + List.of(INT_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_LONG_SINGLETON_NAME, + List.of(LONG_TYPE), + numVariables); + if (includeFloat) { + defineAndCreateStructGlobals(src, + CSpecPrototypeTestConstants.STRUCT_FLOAT_SINGLETON_NAME, List.of("float"), + numVariables); + } + if (includeDouble) { + defineAndCreateStructGlobals(src, + CSpecPrototypeTestConstants.STRUCT_DOUBLE_SINGLETON_NAME, List.of("double"), + numVariables); + } + if (includeLongLong) { + defineAndCreateStructGlobals(src, + CSpecPrototypeTestConstants.STRUCT_LONG_LONG_SINGLETON_NAME, + List.of(LONG_LONG_TYPE), numVariables); + } + + // create pair struct global variables + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_CHAR_PAIR_NAME, + List.of(CHAR_TYPE, CHAR_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_SHORT_PAIR_NAME, + List.of(SHORT_TYPE, SHORT_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_INT_PAIR_NAME, + List.of(INT_TYPE, INT_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_LONG_PAIR_NAME, + List.of(LONG_TYPE, LONG_TYPE), + numVariables); + if (includeFloat) { + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_FLOAT_PAIR_NAME, + List.of("float", "float"), + numVariables); + } + if (includeDouble) { + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_DOUBLE_PAIR_NAME, + List.of("double", "double"), + numVariables); + } + if (includeLongLong) { + defineAndCreateStructGlobals(src, + CSpecPrototypeTestConstants.STRUCT_LONG_LONG_PAIR_NAME, + List.of(LONG_LONG_TYPE, LONG_LONG_TYPE), numVariables); + } + + // create triple struct global variables + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_CHAR_TRIP_NAME, + List.of(CHAR_TYPE, CHAR_TYPE, CHAR_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_SHORT_TRIP_NAME, + List.of(SHORT_TYPE, SHORT_TYPE, SHORT_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_INT_TRIP_NAME, + List.of(INT_TYPE, INT_TYPE, INT_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_LONG_TRIP_NAME, + List.of(LONG_TYPE, LONG_TYPE, LONG_TYPE), + numVariables); + if (includeFloat) { + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_FLOAT_TRIP_NAME, + List.of("float", "float", "float"), + numVariables); + } + if (includeDouble) { + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_DOUBLE_TRIP_NAME, + List.of("double", "double", "double"), + numVariables); + } + if (includeLongLong) { + defineAndCreateStructGlobals(src, + CSpecPrototypeTestConstants.STRUCT_LONG_LONG_TRIP_NAME, + List.of(LONG_LONG_TYPE, LONG_LONG_TYPE, LONG_LONG_TYPE), numVariables); + } + + // create quad struct global variables + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_CHAR_QUAD_NAME, + List.of(CHAR_TYPE, CHAR_TYPE, CHAR_TYPE, CHAR_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_SHORT_QUAD_NAME, + List.of(SHORT_TYPE, SHORT_TYPE, SHORT_TYPE, SHORT_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_INT_QUAD_NAME, + List.of(INT_TYPE, INT_TYPE, INT_TYPE, INT_TYPE), + numVariables); + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_LONG_QUAD_NAME, + List.of(LONG_TYPE, LONG_TYPE, LONG_TYPE, LONG_TYPE), + numVariables); + if (includeFloat) { + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_FLOAT_QUAD_NAME, + List.of("float", "float", "float", "float"), + numVariables); + } + if (includeDouble) { + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_DOUBLE_QUAD_NAME, + List.of("double", "double", "double", "double"), + numVariables); + } + if (includeLongLong) { + defineAndCreateStructGlobals(src, + CSpecPrototypeTestConstants.STRUCT_LONG_LONG_QUAD_NAME, + List.of(LONG_LONG_TYPE, LONG_LONG_TYPE, LONG_LONG_TYPE, LONG_LONG_TYPE), + numVariables); + } + + // create mixed struct global variables + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_INT_LONG_INT, + List.of(INT_TYPE, LONG_TYPE, INT_TYPE), + numVariables); + if (includeFloat) { + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_FLOAT_INT_FLOAT, + List.of("float", INT_TYPE, "float"), numVariables); + } + if (includeDouble) { + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_LONG_DOUBLE_LONG, + List.of(LONG_TYPE, "double", LONG_TYPE), numVariables); + } + if (includeFloat && includeDouble) { + defineAndCreateStructGlobals(src, CSpecPrototypeTestConstants.STRUCT_FLOAT_DOUBLE_FLOAT, + List.of("float", "double", "float"), numVariables); + } + + // Create union variables + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_CHAR, List.of(CHAR_TYPE), + numVariables); + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_SHORT, + List.of(SHORT_TYPE), numVariables); + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_INT, List.of(INT_TYPE), + numVariables); + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_LONG, List.of(LONG_TYPE), + numVariables); + + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_INT_LONG, + List.of(INT_TYPE, LONG_TYPE), + numVariables); + + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_STRUCT_INT, + List.of(CSpecPrototypeTestConstants.STRUCT_INT_QUAD_NAME), numVariables); + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_INTEGRAL, + List.of(CSpecPrototypeTestConstants.STRUCT_INT_QUAD_NAME, + CSpecPrototypeTestConstants.STRUCT_LONG_QUAD_NAME, + CSpecPrototypeTestConstants.STRUCT_INT_LONG_INT), + numVariables); + + // Want some weird-sized unions + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_STRUCT_TRIP_CHAR, + List.of(CSpecPrototypeTestConstants.STRUCT_CHAR_TRIP_NAME), numVariables); + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_STRUCT_TRIP_SHORT, + List.of(CSpecPrototypeTestConstants.STRUCT_SHORT_TRIP_NAME), numVariables); + + if (includeFloat) { + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_FLOAT, + List.of("float"), numVariables); + defineAndCreateUnionGlobals( + src, CSpecPrototypeTestConstants.UNION_INT_FLOAT, List.of(INT_TYPE, "float"), + numVariables); + defineAndCreateUnionGlobals( + src, CSpecPrototypeTestConstants.UNION_LONG_FLOAT, List.of(LONG_TYPE, "float"), + numVariables); + defineAndCreateUnionGlobals( + src, CSpecPrototypeTestConstants.UNION_STRUCT_FLOAT, + List.of(CSpecPrototypeTestConstants.STRUCT_FLOAT_QUAD_NAME), numVariables); + defineAndCreateUnionGlobals( + src, CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_ALL_SMALL, + List.of(CSpecPrototypeTestConstants.STRUCT_FLOAT_INT_FLOAT), numVariables); + } + if (includeDouble) { + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_DOUBLE, + List.of("double"), numVariables); + defineAndCreateUnionGlobals( + src, CSpecPrototypeTestConstants.UNION_INT_DOUBLE, List.of(INT_TYPE, "double"), + numVariables); + defineAndCreateUnionGlobals( + src, CSpecPrototypeTestConstants.UNION_LONG_DOUBLE, List.of(LONG_TYPE, "double"), + numVariables); + defineAndCreateUnionGlobals( + src, CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_ALL_LARGE, + List.of(CSpecPrototypeTestConstants.STRUCT_LONG_DOUBLE_LONG), numVariables); + } + if (includeFloat && includeDouble) { + defineAndCreateUnionGlobals( + src, CSpecPrototypeTestConstants.UNION_FLOAT_DOUBLE, List.of("float", "double"), + numVariables); + defineAndCreateUnionGlobals(src, + CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_FLOATING, + List.of(CSpecPrototypeTestConstants.STRUCT_FLOAT_QUAD_NAME, + CSpecPrototypeTestConstants.STRUCT_DOUBLE_QUAD_NAME, + CSpecPrototypeTestConstants.STRUCT_FLOAT_DOUBLE_FLOAT), + numVariables); + } + if (includeLongLong) { + defineAndCreateUnionGlobals(src, CSpecPrototypeTestConstants.UNION_LONG_LONG, + List.of(LONG_LONG_TYPE), + numVariables); + } + + // create test functions for passing primitive types + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_IDENTICAL, + repeat(CHAR_TYPE, numParams), + false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_IDENTICAL, + repeat(SHORT_TYPE, numParams), + false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_IDENTICAL, + repeat(INT_TYPE, numParams), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_IDENTICAL, + repeat(LONG_TYPE, numParams), + false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_IDENTICAL, + repeat(INT_TYPE + " *", numParams), + false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, CHAR_TYPE, LONG_TYPE), false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, LONG_TYPE, CHAR_TYPE), false, Collections.emptyList()); + + if (includeLongLong) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_IDENTICAL, + repeat(LONG_LONG_TYPE, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, CHAR_TYPE, LONG_LONG_TYPE), false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, LONG_LONG_TYPE, CHAR_TYPE), false, Collections.emptyList()); + } + if (includeFloat) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_IDENTICAL, + repeat("float", numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, INT_TYPE, "float"), false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, "float", INT_TYPE), false, Collections.emptyList()); + } + if (includeDouble) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_IDENTICAL, + repeat("double", numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, LONG_TYPE, "double"), false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, "double", LONG_TYPE), false, Collections.emptyList()); + } + if (includeFloat && includeDouble) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, "float", "double"), false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PRIMITIVE_ALTERNATE, + repeatList(numParams, "double", "float"), false, Collections.emptyList()); + } + + // create test functions for passing singleton structs + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_SINGLETON_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_CHAR_SINGLETON_NAME, numParams), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_SINGLETON_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_SHORT_SINGLETON_NAME, numParams), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_SINGLETON_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_INT_SINGLETON_NAME, numParams), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_SINGLETON_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_LONG_SINGLETON_NAME, numParams), false, + Collections.emptyList()); + if (includeFloat) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_SINGLETON_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_FLOAT_SINGLETON_NAME, numParams), false, + Collections.emptyList()); + } + if (includeDouble) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_SINGLETON_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_DOUBLE_SINGLETON_NAME, numParams), false, + Collections.emptyList()); + } + if (includeLongLong) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_SINGLETON_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_LONG_LONG_SINGLETON_NAME, numParams), + false, Collections.emptyList()); + } + + // create test functions for passing pair structs + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PAIR_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_CHAR_PAIR_NAME, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PAIR_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_SHORT_PAIR_NAME, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PAIR_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_INT_PAIR_NAME, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PAIR_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_LONG_PAIR_NAME, numParams), + false, Collections.emptyList()); + if (includeFloat) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PAIR_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_FLOAT_PAIR_NAME, numParams), false, + Collections.emptyList()); + } + if (includeDouble) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PAIR_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_DOUBLE_PAIR_NAME, numParams), false, + Collections.emptyList()); + } + if (includeLongLong) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_PAIR_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_LONG_LONG_PAIR_NAME, numParams), false, + Collections.emptyList()); + } + + // create test functions for passing triple structs + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_TRIP_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_CHAR_TRIP_NAME, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_TRIP_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_SHORT_TRIP_NAME, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_TRIP_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_INT_TRIP_NAME, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_TRIP_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_LONG_TRIP_NAME, numParams), + false, Collections.emptyList()); + if (includeFloat) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_TRIP_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_FLOAT_TRIP_NAME, numParams), false, + Collections.emptyList()); + } + if (includeDouble) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_TRIP_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_DOUBLE_TRIP_NAME, numParams), false, + Collections.emptyList()); + } + if (includeLongLong) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_TRIP_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_LONG_LONG_TRIP_NAME, numParams), false, + Collections.emptyList()); + } + + // create test functions for passing quad structs + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_QUAD_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_CHAR_QUAD_NAME, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_QUAD_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_SHORT_QUAD_NAME, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_QUAD_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_INT_QUAD_NAME, numParams), + false, Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_QUAD_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_LONG_QUAD_NAME, numParams), + false, Collections.emptyList()); + if (includeFloat) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_QUAD_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_FLOAT_QUAD_NAME, numParams), false, + Collections.emptyList()); + } + if (includeDouble) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_QUAD_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_DOUBLE_QUAD_NAME, numParams), false, + Collections.emptyList()); + } + if (includeLongLong) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_QUAD_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_LONG_LONG_QUAD_NAME, numParams), false, + Collections.emptyList()); + } + + // create test functions for passing mixed structs + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_MIXED_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_INT_LONG_INT, numParams), + false, Collections.emptyList()); + if (includeFloat) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_MIXED_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_FLOAT_INT_FLOAT, numParams), false, + Collections.emptyList()); + } + if (includeDouble) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_MIXED_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_LONG_DOUBLE_LONG, numParams), false, + Collections.emptyList()); + } + if (includeFloat && includeDouble) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_MIXED_STRUCT, + repeat(CSpecPrototypeTestConstants.STRUCT_FLOAT_DOUBLE_FLOAT, numParams), false, + Collections.emptyList()); + } + + // create variadic test functions + // encode arg list in function name to facilitate applying signature overrides + // use capital letters for unsigned + String variadicTypeString = "_i_i_c_c_s_s_i_i_l_l"; + if (unsignedTypes) { + variadicTypeString = variadicTypeString.toUpperCase(); + } + + createParamTestFunction(src, + CSpecPrototypeTestConstants.PARAMS_VARIADIC + variadicTypeString, + List.of(INT_TYPE, INT_TYPE), true, + List.of(CHAR_TYPE, CHAR_TYPE, SHORT_TYPE, SHORT_TYPE, INT_TYPE, INT_TYPE, LONG_TYPE, + LONG_TYPE)); + + variadicTypeString = "_i_i_i_i_i_i_i_i_i_i"; + if (unsignedTypes) { + variadicTypeString = variadicTypeString.toUpperCase(); + } + createParamTestFunction(src, + CSpecPrototypeTestConstants.PARAMS_VARIADIC + variadicTypeString, + List.of(INT_TYPE), true, repeat(INT_TYPE, 9)); + + variadicTypeString = "_l_d_l_d_l_d_l_d_l_d"; + if (unsignedTypes) { + variadicTypeString = variadicTypeString.replace('l', 'L'); + } + if (includeDouble) { + createParamTestFunction(src, + CSpecPrototypeTestConstants.PARAMS_VARIADIC + variadicTypeString, + List.of(LONG_TYPE, "double"), true, + List.of(LONG_TYPE, "double", LONG_TYPE, "double", LONG_TYPE, "double", LONG_TYPE, + "double")); + } + + // create miscellaneous parameter tests + if (includeFloat) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_MISC, + repeatList(numParams, "float", + CSpecPrototypeTestConstants.STRUCT_FLOAT_SINGLETON_NAME), + false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_MISC, + repeatList(numParams, "float", CSpecPrototypeTestConstants.STRUCT_FLOAT_PAIR_NAME), + false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_MISC, + repeatList(numParams, CSpecPrototypeTestConstants.STRUCT_FLOAT_PAIR_NAME, INT_TYPE, + "float"), + false, + Collections.emptyList()); + } + + // Union tests + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_CHAR), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_SHORT), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_INT), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_LONG), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_INT_LONG), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_STRUCT_INT), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_INTEGRAL), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_STRUCT_TRIP_CHAR), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_STRUCT_TRIP_SHORT), false, + Collections.emptyList()); + + if (includeFloat) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_FLOAT), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_INT_FLOAT), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_LONG_FLOAT), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_STRUCT_FLOAT), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_ALL_SMALL), + false, Collections.emptyList()); + } + if (includeDouble) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_DOUBLE), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_INT_DOUBLE), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_LONG_DOUBLE), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_ALL_LARGE), + false, Collections.emptyList()); + } + if (includeDouble && includeFloat) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_FLOAT_DOUBLE), false, + Collections.emptyList()); + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_FLOATING), + false, Collections.emptyList()); + } + if (includeLongLong) { + createParamTestFunction(src, CSpecPrototypeTestConstants.PARAMS_UNION, + repeatList(numParams, CSpecPrototypeTestConstants.UNION_LONG_LONG), false, + Collections.emptyList()); + } + + // Return tests + + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PRIMITIVE, CHAR_TYPE); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_SINGLETON, + CSpecPrototypeTestConstants.STRUCT_CHAR_SINGLETON_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PAIR, + CSpecPrototypeTestConstants.STRUCT_CHAR_PAIR_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_TRIPLE, + CSpecPrototypeTestConstants.STRUCT_CHAR_TRIP_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_QUAD, + CSpecPrototypeTestConstants.STRUCT_CHAR_QUAD_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_CHAR); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_STRUCT_TRIP_CHAR); + + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PRIMITIVE, SHORT_TYPE); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_SINGLETON, + CSpecPrototypeTestConstants.STRUCT_SHORT_SINGLETON_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PAIR, + CSpecPrototypeTestConstants.STRUCT_SHORT_PAIR_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_TRIPLE, + CSpecPrototypeTestConstants.STRUCT_SHORT_TRIP_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_QUAD, + CSpecPrototypeTestConstants.STRUCT_SHORT_QUAD_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_SHORT); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_STRUCT_TRIP_SHORT); + + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PRIMITIVE, INT_TYPE); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_SINGLETON, + CSpecPrototypeTestConstants.STRUCT_INT_SINGLETON_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PAIR, + CSpecPrototypeTestConstants.STRUCT_INT_PAIR_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_TRIPLE, + CSpecPrototypeTestConstants.STRUCT_INT_TRIP_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_QUAD, + CSpecPrototypeTestConstants.STRUCT_INT_QUAD_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_INT); + + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PRIMITIVE, LONG_TYPE); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_SINGLETON, + CSpecPrototypeTestConstants.STRUCT_LONG_SINGLETON_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PAIR, + CSpecPrototypeTestConstants.STRUCT_LONG_PAIR_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_TRIPLE, + CSpecPrototypeTestConstants.STRUCT_LONG_TRIP_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_QUAD, + CSpecPrototypeTestConstants.STRUCT_LONG_QUAD_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_LONG); + + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_MIXED, + CSpecPrototypeTestConstants.STRUCT_INT_LONG_INT); + + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_STRUCT_INT); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_INTEGRAL); + + if (includeFloat) { + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PRIMITIVE, "float"); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_SINGLETON, + CSpecPrototypeTestConstants.STRUCT_FLOAT_SINGLETON_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PAIR, + CSpecPrototypeTestConstants.STRUCT_FLOAT_PAIR_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_TRIPLE, + CSpecPrototypeTestConstants.STRUCT_FLOAT_TRIP_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_QUAD, + CSpecPrototypeTestConstants.STRUCT_FLOAT_QUAD_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_MIXED, + CSpecPrototypeTestConstants.STRUCT_FLOAT_INT_FLOAT); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_FLOAT); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_STRUCT_FLOAT); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_ALL_SMALL); + } + if (includeDouble) { + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PRIMITIVE, "double"); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_SINGLETON, + CSpecPrototypeTestConstants.STRUCT_DOUBLE_SINGLETON_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PAIR, + CSpecPrototypeTestConstants.STRUCT_DOUBLE_PAIR_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_TRIPLE, + CSpecPrototypeTestConstants.STRUCT_DOUBLE_TRIP_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_QUAD, + CSpecPrototypeTestConstants.STRUCT_DOUBLE_QUAD_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_MIXED, + CSpecPrototypeTestConstants.STRUCT_LONG_DOUBLE_LONG); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_DOUBLE); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_ALL_LARGE); + } + if (includeFloat && includeDouble) { + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_MIXED, + CSpecPrototypeTestConstants.STRUCT_FLOAT_DOUBLE_FLOAT); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_FLOATING); + } + if (includeLongLong) { + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PRIMITIVE, + LONG_LONG_TYPE); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_SINGLETON, + CSpecPrototypeTestConstants.STRUCT_LONG_LONG_SINGLETON_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_PAIR, + CSpecPrototypeTestConstants.STRUCT_LONG_LONG_PAIR_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_TRIPLE, + CSpecPrototypeTestConstants.STRUCT_LONG_LONG_TRIP_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_QUAD, + CSpecPrototypeTestConstants.STRUCT_LONG_LONG_QUAD_NAME); + createReturnTestFunction(src, CSpecPrototypeTestConstants.RETURN_UNION, + CSpecPrototypeTestConstants.UNION_LONG_LONG); + } + + writeFile(langPair, src); + + } + + private void createReturnTestFunction(Appendable app, String baseFuncName, + String returnType) throws IOException { + int id = funcCounter++; + String count = Integer.toString(id); + app.append(returnType); + if (!StringUtils.isAllBlank(preSpecifier)) { + app.append(" "); + app.append(preSpecifier); + } + app.append(" "); + app.append(CSpecPrototypeTestConstants.PRODUCER); + app.append("_"); + app.append(count); + app.append("(void)"); + app.append(" { return "); + app.append(returnType); + app.append("_0;}\n"); + if (!StringUtils.isAllBlank(preSpecifier)) { + app.append(preSpecifier); + app.append(" "); + } + app.append("extern void consumer_"); + app.append(count); + app.append("("); + app.append(returnType); + app.append(");\n"); + app.append("void "); + app.append(baseFuncName); + app.append("_"); + app.append(count); + app.append("(void){ consumer_"); + app.append(count); + app.append("("); + app.append(CSpecPrototypeTestConstants.PRODUCER); + app.append("_"); + app.append(count); + app.append("());}\n\n"); + + } + + private void defineAndCreateStructGlobals(Appendable src, String structName, List types, + int nParams) throws IOException { + createStructDefinition(src, structName, types); + createStructGlobals(src, structName, types, nParams); + } + + private void defineAndCreateUnionGlobals(Appendable src, String unionName, List types, + int nParams) throws IOException { + createUnionDefinition(src, unionName, types); + createUnionGlobals(src, unionName, types, nParams); + } + + private void createStructDefinition(Appendable src, String structName, List types) + throws IOException { + int fieldCount = 0; + src.append("typedef struct "); + src.append(structName); + src.append(" {\n"); + for (int i = 0; i < types.size(); ++i) { + src.append(" "); + src.append(types.get(i)); + src.append(" "); + src.append(CSpecPrototypeTestConstants.FIELD_NAME_PREFIX); + src.append("_"); + src.append(types.get(i)); + src.append("_"); + src.append(Integer.toString(fieldCount++)); + src.append(";\n"); + } + src.append("} "); + src.append(structName); + src.append(";\n\n"); + } + + private void createUnionDefinition(Appendable src, String unionName, List types) + throws IOException { + int fieldCount = 0; + src.append("typedef union "); + src.append(unionName); + src.append(" {\n"); + for (int i = 0; i < types.size(); ++i) { + src.append(" "); + src.append(types.get(i)); + src.append(" "); + src.append(CSpecPrototypeTestConstants.FIELD_NAME_PREFIX); + src.append("_"); + src.append(types.get(i)); + src.append("_"); + src.append(Integer.toString(fieldCount++)); + src.append(";\n"); + } + src.append("} "); + src.append(unionName); + src.append(";\n\n"); + } + + private List repeat(String base, int length) { + List repeated = new ArrayList<>(length); + for (int i = 0; i < length; ++i) { + repeated.add(base); + } + return repeated; + } + + private List repeatList(int length, String... entries) { + List repeats = new ArrayList<>(length); + for (int i = 0; i < length; ++i) { + repeats.add(entries[i % entries.length]); + } + return repeats; + } + + private void createIntegralTypeGlobals(Appendable app, String typeName, int size, + int numVariables) throws IOException { + for (int i = 0; i < numVariables; ++i) { + app.append("const "); + app.append(typeName); + app.append(" "); + app.append(typeName); + app.append("_"); + app.append(Integer.toString(i)); + app.append(" = "); + app.append(getNextHexValue(size)); + app.append(";\n"); + } + app.append("\n"); + } + + private void structInitializer(Appendable app, List fieldTypes) throws IOException { + for (int j = 0; j < fieldTypes.size(); ++j) { + app.append("."); + app.append(CSpecPrototypeTestConstants.FIELD_NAME_PREFIX); + app.append("_"); + app.append(fieldTypes.get(j)); + app.append("_"); + app.append(Integer.toString(j)); + app.append(" = "); + switch (fieldTypes.get(j)) { + case "char": + case "uchar": + app.append(getNextHexValue(charSize)); + break; + case "short": + case "ushort": + app.append(getNextHexValue(shortSize)); + break; + case "int": + case "uint": + app.append(getNextHexValue(intSize)); + break; + case "long": + case "ulong": + app.append(getNextHexValue(longSize)); + break; + case "longlong": + case "ulonglong": + app.append(getNextHexValue(longLongSize)); + break; + case "float": + app.append(getNextFloatValue()); + break; + case "double": + app.append(getNextDoubleValue()); + break; + default: + throw new IllegalArgumentException( + "Unsupported field type: " + fieldTypes.get(j)); + } + if (j != fieldTypes.size() - 1) { + app.append(", "); + } + } + } + + private void createStructGlobals(Appendable app, String structName, List fieldTypes, + int num) throws IOException { + for (int i = 0; i < num; ++i) { + app.append("const "); + app.append(structName); + app.append(" "); + app.append(structName); + app.append("_"); + app.append(Integer.toString(i)); + app.append(" = { "); + structInitializer(app, fieldTypes); + app.append(" };\n"); + } + app.append("\n"); + } + + private void createUnionGlobals(Appendable app, String unionName, List fieldTypes, + int num) throws IOException { + for (int i = 0; i < num; ++i) { + app.append("const "); + app.append(unionName); + app.append(" "); + app.append(unionName); + app.append("_"); + app.append(Integer.toString(i)); + app.append(" = { "); + // Enable only one field + int whichField = i % fieldTypes.size(); + app.append("."); + app.append(CSpecPrototypeTestConstants.FIELD_NAME_PREFIX); + app.append("_"); + app.append(fieldTypes.get(whichField)); + app.append("_"); + app.append(Integer.toString(whichField)); + app.append(" = "); + switch (fieldTypes.get(whichField)) { + case "char": + case "uchar": + app.append(getNextHexValue(charSize)); + break; + case "short": + case "ushort": + app.append(getNextHexValue(shortSize)); + break; + case "int": + case "uint": + app.append(getNextHexValue(intSize)); + break; + case "long": + case "ulong": + app.append(getNextHexValue(longSize)); + break; + case "longlong": + case "ulonglong": + app.append(getNextHexValue(longLongSize)); + break; + case "float": + app.append(getNextFloatValue()); + break; + case "double": + app.append(getNextDoubleValue()); + break; + case CSpecPrototypeTestConstants.STRUCT_INT_QUAD_NAME: + app.append("{ "); + structInitializer(app, List.of(INT_TYPE, INT_TYPE, INT_TYPE, INT_TYPE)); + app.append(" }\n"); + break; + case CSpecPrototypeTestConstants.STRUCT_LONG_QUAD_NAME: + app.append("{ "); + structInitializer(app, List.of(LONG_TYPE, LONG_TYPE, LONG_TYPE, LONG_TYPE)); + app.append(" }\n"); + break; + case CSpecPrototypeTestConstants.STRUCT_INT_LONG_INT: + app.append("{ "); + structInitializer(app, List.of(INT_TYPE, LONG_TYPE, INT_TYPE)); + app.append(" }\n"); + break; + case CSpecPrototypeTestConstants.STRUCT_FLOAT_QUAD_NAME: + app.append("{ "); + structInitializer(app, List.of("float", "float", "float", "float")); + app.append(" }\n"); + break; + case CSpecPrototypeTestConstants.STRUCT_DOUBLE_QUAD_NAME: + app.append("{ "); + structInitializer(app, List.of("double", "double", "double", "double")); + app.append(" }\n"); + break; + case CSpecPrototypeTestConstants.STRUCT_FLOAT_DOUBLE_FLOAT: + app.append("{ "); + structInitializer(app, List.of("float", "double", "float")); + app.append(" }\n"); + break; + case CSpecPrototypeTestConstants.STRUCT_FLOAT_INT_FLOAT: + app.append("{ "); + structInitializer(app, List.of("float", INT_TYPE, "float")); + app.append(" }\n"); + break; + case CSpecPrototypeTestConstants.STRUCT_LONG_DOUBLE_LONG: + app.append("{ "); + structInitializer(app, List.of(LONG_TYPE, "double", LONG_TYPE)); + app.append(" }\n"); + break; + case CSpecPrototypeTestConstants.STRUCT_CHAR_TRIP_NAME: + app.append("{ "); + structInitializer(app, List.of(CHAR_TYPE, CHAR_TYPE, CHAR_TYPE)); + app.append(" }\n"); + break; + case CSpecPrototypeTestConstants.STRUCT_SHORT_TRIP_NAME: + app.append("{ "); + structInitializer(app, List.of(SHORT_TYPE, SHORT_TYPE, SHORT_TYPE)); + app.append(" }\n"); + break; + default: + throw new IllegalArgumentException( + "Unsupported field type: " + fieldTypes.get(whichField)); + } + app.append(" };\n"); + } + app.append("\n"); + + } + + private String getNextHexValue(int size) { + long localMultiplier = size == 8 ? multiplier : multiplier & ((1l << (8 * size)) - 1); + long retVal = localMultiplier * variableVal; + // if the value is larger than one byte, make the most significant byte 0xff + // this helps diagnose endianness issues + if (size > 1) { + long adjustment = 0xffL << (8 * (size - 1)); + retVal |= adjustment; + } + variableVal = variableVal == 0xfe ? 1 : variableVal + 1; + return "0x" + Long.toHexString(retVal); + } + + private void createFloatingPointGlobals(Appendable app, int numVariables) throws IOException { + for (int i = 0; i < numVariables; ++i) { + app.append("const float float"); + app.append("_"); + app.append(Integer.toString(i)); + app.append(" = "); + app.append(getNextFloatValue()); + app.append(";\n"); + } + app.append("\n"); + } + + private CharSequence getNextFloatValue() { + float retVal = floatIntPart + floatFracPart; + floatIntPart *= 2; + floatFracPart /= 2; + if (floatIntPart >= 8000) { + floatIntPart = 2.0f; + floatFracPart = .5f; + } + return Float.toString(retVal); + } + + private void createDoubleGlobals(Appendable app, int numVariables) throws IOException { + for (int i = 0; i < numVariables; ++i) { + app.append("const double double"); + app.append("_"); + app.append(Integer.toString(i)); + app.append(" = "); + app.append(getNextDoubleValue()); + app.append(";\n"); + } + app.append("\n"); + } + + private String getNextDoubleValue() { + double retVal = doubleIntPart + doubleFracPart; + doubleIntPart *= 2; + doubleFracPart /= 2; + if (doubleIntPart > 50000) { + doubleIntPart = 2.0; + doubleFracPart /= 2; + } + return Double.toString(retVal); + } + + private void createParamTestFunction(Appendable app, String baseFuncName, + List fixedInputTypes, boolean isVarArgs, List varArgsTypes) + throws IOException { + app.append("extern void"); + if (!StringUtils.isAllBlank(preSpecifier) && (!isVarArgs || decorateVarargs)) { + app.append(" "); + app.append(preSpecifier); + } + app.append(" "); + int testFuncNumber = funcCounter++; + String targetName = + CSpecPrototypeTestConstants.EXTERNAL + "_target_" + Integer.toString(testFuncNumber); + app.append(targetName); + app.append("("); + for (int i = 0; i < fixedInputTypes.size(); ++i) { + if (fixedInputTypes.get(i).contains("*")) { + app.append("const "); + } + app.append(fixedInputTypes.get(i)); + if (i != fixedInputTypes.size() - 1) { + app.append(","); + } + } + if (isVarArgs) { + app.append(",..."); + } + app.append(")"); + app.append(";\n"); + app.append("void "); + app.append(baseFuncName); + app.append("_"); + app.append(Integer.toString(testFuncNumber)); + app.append("(void) {\n"); + app.append(" "); + app.append(targetName); + app.append("("); + List inputs = new ArrayList<>(); + inputs.addAll(fixedInputTypes); + inputs.addAll(varArgsTypes); + for (int i = 0; i < inputs.size(); ++i) { + if (inputs.get(i).contains("*")) { + app.append("&"); // address-of operator + app.append(inputs.get(i).split(" ")[0]); + } + else { + app.append(inputs.get(i)); + } + app.append("_"); + app.append(Integer.toString(i + 1)); + if (i != inputs.size() - 1) { + app.append(","); + } + } + app.append(");\n}\n\n"); + } + + private void writeFile(LanguageCompilerSpecPair langPair, StringBuilder programText) + throws IOException { + File out = new File(outputDirectory, + langPair.toString().replace(":", "_") + "_" + ccGhidraName + ".c"); + try (FileWriter fWriter = new FileWriter(out)) { + fWriter.write(programText.toString()); + println("Wrote " + out.getAbsolutePath()); + } + return; + } + +} diff --git a/Ghidra/Features/Base/ghidra_scripts/TestPrototypeScript.java b/Ghidra/Features/Base/ghidra_scripts/TestPrototypeScript.java new file mode 100644 index 0000000000..a81f3d8a37 --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/TestPrototypeScript.java @@ -0,0 +1,178 @@ +/* ### + * 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. + */ +// This script uses the emulator to test a prototype defined in a cspec file. It is intended to be +// run on programs produced by compiling a source file produced by the script +// GeneratePrototypeTestFileScript.java. The program must have the same name as the source file +// except without the .c suffix (e.g., program = test_file, source = test_file.c) and the two files +// must reside in the same directory. The first time you run this file on a program, it will parse +// the c source file and apply the correct data types and function definitions. If you run the +// script without a selection it will test all test functions and print out which ones have +// errors. If the script is run with a selection, it will print out detailed information about each +// test function overlapping the selection (whether or not it has an error). +import java.util.*; +import java.util.function.Consumer; + +import ghidra.app.script.GhidraScript; +import ghidra.pcode.emu.EmulatorUtilities; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.InterruptPcodeExecutionException; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.ReferenceIterator; +import ghidra.test.compilers.support.CSpecPrototypeTestUtil; +import ghidra.test.compilers.support.CSpecPrototypeTestUtil.TestResult; +import ghidra.test.compilers.support.CSpecTestPCodeEmulator; +import ghidra.util.DataConverter; + +public class TestPrototypeScript extends GhidraScript { + // Whether to print extra diagnostic information, such as the emulator's disassembly + private static final boolean ENABLE_DEBUG_PRINTING = false; + private static final int DEBUG_PRINTING_LEVEL = 3; + + private DataConverter dataConverter; + private LanguageCompilerSpecPair langCompPair; + private boolean manualSelection = false; + private CSpecTestPCodeEmulator emulator; + private Consumer logger = (msg -> printf(" %s\n", msg)); + + @Override + protected void run() throws Exception { + langCompPair = getLangCompPair(currentProgram); + PrototypeModel model = + CSpecPrototypeTestUtil.getProtoModelToTest(currentProgram, langCompPair); + dataConverter = DataConverter.getInstance(langCompPair.getLanguage().isBigEndian()); + FunctionManager fManager = currentProgram.getFunctionManager(); + + CSpecPrototypeTestUtil.applyInfoFromSourceIfNeeded(currentProgram, model); + + // Load program into emulator + emulator = + new CSpecTestPCodeEmulator(currentProgram.getLanguage(), !ENABLE_DEBUG_PRINTING, + DEBUG_PRINTING_LEVEL, logger); + EmulatorUtilities.loadProgram(emulator, currentProgram); + + Iterator fIter = currentSelection == null ? fManager.getFunctionsNoStubs(true) + : fManager.getFunctionsOverlapping(currentSelection); + manualSelection = currentSelection != null; + + List errors = new ArrayList<>(); + while (fIter.hasNext()) { + Function caller = fIter.next(); + if (!(caller.getName().startsWith("params") || caller.getName().startsWith("return"))) { + continue; + } + + Function callee = CSpecPrototypeTestUtil.getFirstCall(caller); + ArrayList pieces = + CSpecPrototypeTestUtil.getParameterPieces(caller, callee, model); + Address breakpoint = null; + if (caller.getName().startsWith("params")) { + breakpoint = callee.getEntryPoint(); + } + else { + // find the address of the call to producer + ReferenceIterator refIter = + currentProgram.getReferenceManager().getReferencesTo(callee.getEntryPoint()); + if (!refIter.hasNext()) { + throw new AssertionError( + "no references to " + callee.getName() + " in " + caller.getName()); + } + Reference ref = null; + while (refIter.hasNext()) { + Reference r = refIter.next(); + if (!r.getReferenceType().isCall()) { + continue; + } + if (caller.getBody().contains(r.getFromAddress())) { + ref = r; + break; + } + } + if (ref == null) { + throw new AssertionError( + "call to " + callee.getName() + " not found in " + caller.getName()); + } + Instruction afterCall = + currentProgram.getListing().getInstructionAfter(ref.getFromAddress()); + // For architectures with a delay slot, break on the actual aftercall instruction, + // by stepping instructions until we are out of the delay slot. + while (afterCall.isInDelaySlot()) { + afterCall = afterCall.getNext(); + } + breakpoint = afterCall.getAddress(); + + } + + boolean error = testFunction(caller, callee, breakpoint, pieces); + if (error) { + errors.add(caller); + } + } + + if (errors.size() == 0) { + printf("No prototype errors found.\n"); + return; + } + printf("%d prototype error(s) found:\n", errors.size()); + for (Function errFunc : errors) { + printf(" %s\n", errFunc.getName()); + } + } + + private boolean testFunction(Function caller, Function callee, Address breakPoint, + ArrayList pieces) throws Exception { + + List groundTruth = + CSpecPrototypeTestUtil.getPassedValues(callee, pieces, dataConverter, logger); + + // breakpoint will be skipped if condition is false, so add condition that is always true + emulator.addBreakpoint(breakPoint, "1:1"); + + PcodeThread emuThread = emulator.prepareFunction(caller); + + Register stackReg = caller.getProgram().getCompilerSpec().getStackPointer(); + + try { + emuThread.run(); + printerr("Emulator should have hit breakpoint"); + } + catch (InterruptPcodeExecutionException e) { + // this is the breakpoint, which is what we want to happen + } + + List fromEmulator = new ArrayList<>(); + for (ParameterPieces piece : pieces) { + fromEmulator.add(CSpecPrototypeTestUtil.readParameterPieces(emuThread, piece, + emulator.getLanguage().getDefaultDataSpace(), stackReg, langCompPair, + dataConverter)); + } + + TestResult result = + CSpecPrototypeTestUtil.getTestResult(callee, caller, pieces, fromEmulator, groundTruth); + + if (manualSelection) { + printf("%s\n", result.message()); + } + return result.hasError(); + + } + + private LanguageCompilerSpecPair getLangCompPair(Program program) { + return program.getLanguageCompilerSpecPair(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecPrototypeTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecPrototypeTest.java new file mode 100644 index 0000000000..0942111979 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecPrototypeTest.java @@ -0,0 +1,429 @@ +/* ### + * 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.test.compilers.support; + +import static org.junit.Assert.*; + +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; + +import org.junit.*; + +import generic.jar.ResourceFile; +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.app.util.importer.ProgramLoader; +import ghidra.app.util.opinion.LoadResults; +import ghidra.base.project.GhidraProject; +import ghidra.framework.Application; +import ghidra.pcode.emu.EmulatorUtilities; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.InterruptPcodeExecutionException; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.ReferenceIterator; +import ghidra.program.util.DefaultLanguageService; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; +import ghidra.test.TestEnv; +import ghidra.test.compilers.support.CSpecPrototypeTestUtil.TestResult; +import ghidra.util.DataConverter; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; + +/** + * CSpecPrototypeTest provides an abstract JUnit test implementation + * for processor-specific and compiler-specific calling convention test cases. + * + * Tests which extend this class must implement abstract functions to specify LANGUAGE_ID, + * COMPILER_SPEC_ID, and CALLING_CONVENTION. + * + * An optional list of function names that contain errors can be passed to the constructor to + * designate those errors as expected. The test will pass as long as only expected errors are found. + * + * Source and binary files have a naming scheme. + * (LANGUAGE_ID)_(COMPILER_SPEC_ID)_(CALLING_CONVENTION) + * + * Trace logging is disabled by default. Specific traceLevel and traceLog disabled controlled via + * environment properties CSpecTestTraceLevel and EmuTestTraceDisable. + * + * To create a new CSpecPrototypeTest for a given Module (e.g. Processors x86) complete the + * following steps: + * + * 1. Generate source code using Ghidra and the Ghidra script "GeneratePrototypeTestFileScript". + * NOTE: Do not rename the generated file; the filename is required for the test suit. + * 2. Compile the source code using the following recommended GCC flags: + * gcc -O1 -c -fno-inline -fno-leading-underscore -o filename_without_extension filename.c + * 3. Place the source code and compiled binary in the module's "data/cspectests" directory or the + * ghidra.bin repository in the directory: "Ghidra/Test/TestResources/data/cspectests" + * 4. Add a new package named "ghidra.test.processors.cspec" to the module if it does not exist and + * place all new CSpecTest's in this package. + * 5. New CSpecTests should extend this class and have a class name which ends in 'CSpecTest' and + * starts with processor details that indicate what cspec prototype is being tested. + * - Implement abstract methods for Language ID, Compiler Spec ID, and Calling Convention. + * 6. Use Ghidra and the Ghidra script "TestPrototypeScript" to debug errors. + * - Click function links in the Script Console to jump to the Listing View. + * - To isolate a single function, highlight it in the Listing and re-run the script + * for detailed debug output. + * + * */ +public abstract class CSpecPrototypeTest extends AbstractGhidraHeadlessIntegrationTest { + + private static final String EMULATOR_TRACE_DISABLE_PROPERTY = "CSpecTestTraceDisable"; + private static final String EMULATOR_TRACE_LEVEL_PROPERTY = "CSpecTestTraceLevel"; + + // If cspectests data directory can not be found for the module containing the junit test, + // This default ProcessorTest module will be searched instead. + private static final String DEFAULT_PROCESSOR_TEST_MODULE = "Test/TestResources"; // module path relative to the Ghidra directory + + private static final String TEST_RESOURCE_PATH = "data/cspectests/"; + + private TestEnv env; + + private DataConverter dataConverter; + private LanguageCompilerSpecPair langCompPair; + private CSpecTestPCodeEmulator emulator; + private Program currentProgram; + + private final String languageId; + private final String compilerSpecId; + private final String testExecutableFileName; + + private Collection applicationRootDirectories; + private File resourcesTestDataDir; + + private final String[] EXPECTED_PROTOTYPE_ERRORS; + + private static boolean traceDisabled = true; + private static int traceLevel = 3; // 0:disabled 1:Instruction 2:RegisterState 3:Reads-n-Writes + + static { + if (System.getProperty(EMULATOR_TRACE_DISABLE_PROPERTY) != null) { + traceDisabled = Boolean.getBoolean(EMULATOR_TRACE_DISABLE_PROPERTY); + } + } + + protected CSpecPrototypeTest() throws Exception { + this(new String[] {}); + } + + protected CSpecPrototypeTest(String[] expectedPrototypeErrors) throws Exception { + languageId = getLanguageID(); + compilerSpecId = getCompilerSpecID(); + testExecutableFileName = this.languageId.toString().replace(":", "_") + "_" + + this.compilerSpecId + "_" + getCallingConvention(); + EXPECTED_PROTOTYPE_ERRORS = expectedPrototypeErrors; + + if (System.getProperty(EMULATOR_TRACE_DISABLE_PROPERTY) == null) { + traceDisabled = true; + } + + String levelStr = System.getProperty(EMULATOR_TRACE_LEVEL_PROPERTY); + if (levelStr != null) { + traceLevel = Integer.parseInt(levelStr); + } + } + + /** + * Ran before every test to prepare Ghidra for testing. + * @throws Exception when the test environment fails to be created or the emulator fails to + * load the program. + */ + @Before + public void setUp() throws Exception { + env = new TestEnv(10, "CSpec Prototype Tests"); + applicationRootDirectories = Application.getApplicationRootDirectories(); + + ResourceFile myModuleRootDirectory = + Application.getModuleContainingClass(getClass()); + if (myModuleRootDirectory != null) { + File myModuleRoot = myModuleRootDirectory.getFile(false); + if (myModuleRoot != null) { + resourcesTestDataDir = new File(myModuleRoot, TEST_RESOURCE_PATH); + if (!resourcesTestDataDir.isDirectory()) { + findTestResourceDirectory(getRelativeModulePath(myModuleRootDirectory)); + } + } + } + else { + Msg.warn(this, + "Unable to identify pcodetest module directory! Project must contain Module.manifest file"); + } + + if (resourcesTestDataDir == null || !resourcesTestDataDir.isDirectory()) { + findTestResourceDirectory(DEFAULT_PROCESSOR_TEST_MODULE); + } + + Msg.info(this, + "Locating " + testExecutableFileName + " C-Spec Prototype test binaries in: " + + resourcesTestDataDir.getPath()); + + GhidraProject project = env.getGhidraProject(); + + File binaryFile = new File(resourcesTestDataDir + File.separator + testExecutableFileName); + + LanguageService languageService = DefaultLanguageService.getLanguageService(); + Language language = languageService.getLanguage(new LanguageID(languageId)); + CompilerSpec compilerSpec = + language.getCompilerSpecByID(new CompilerSpecID(compilerSpecId)); + + LoadResults loadResults = ProgramLoader.builder() + .source(binaryFile) + .project(project.getProject()) + .language(language) + .compiler(compilerSpec) + .monitor(TaskMonitor.DUMMY) + .load(); + + currentProgram = loadResults.getPrimaryDomainObject(this); + + currentProgram.startTransaction("Analysis"); + AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(currentProgram); + aam.initializeOptions(); + aam.reAnalyzeAll(null); + aam.startAnalysis(TaskMonitor.DUMMY); + + langCompPair = currentProgram.getLanguageCompilerSpecPair(); + dataConverter = DataConverter.getInstance(langCompPair.getLanguage().isBigEndian()); + + // Load program into emulator + emulator = + new CSpecTestPCodeEmulator(currentProgram.getLanguage(), traceDisabled, traceLevel); + EmulatorUtilities.loadProgram(emulator, currentProgram); + } + + @After + public void tearDown() throws Exception { + Msg.info(this, "Disposing of testing environment."); + + if (env != null) { + env.dispose(); + } + } + + /** + * Tests that for a given binary and source code all functions in the binary are + * interpreted correctly by Ghidra using cspec files for the given calling convention. + * @throws Exception when the Prototype cannot be established, the source code could not be + * parsed correctly, or the test could not be completed. + */ + @Test + public void prototypeTest() throws Exception { + PrototypeModel model = + CSpecPrototypeTestUtil.getProtoModelToTest(currentProgram, langCompPair); + FunctionManager fManager = currentProgram.getFunctionManager(); + + Msg.info(this, "Locating C-Spec Prototype test source in: " + + currentProgram.getExecutablePath()); + CSpecPrototypeTestUtil.applyInfoFromSourceIfNeeded(currentProgram, model); + + Iterator fIter = fManager.getFunctionsNoStubs(true); + + List errors = new ArrayList<>(); + while (fIter.hasNext()) { + Function caller = fIter.next(); + if (!(caller.getName().startsWith("params") || caller.getName().startsWith("return"))) { + continue; + } + + Function callee = CSpecPrototypeTestUtil.getFirstCall(caller); + ArrayList pieces = + CSpecPrototypeTestUtil.getParameterPieces(caller, callee, model); + Address breakpoint = null; + if (caller.getName().startsWith("params")) { + breakpoint = callee.getEntryPoint(); + } + else { + // find the address of the call to producer + ReferenceIterator refIter = + currentProgram.getReferenceManager().getReferencesTo(callee.getEntryPoint()); + if (!refIter.hasNext()) { + throw new AssertionError( + "no references to " + callee.getName() + " in " + caller.getName()); + } + Reference ref = null; + while (refIter.hasNext()) { + Reference r = refIter.next(); + if (!r.getReferenceType().isCall()) { + continue; + } + if (caller.getBody().contains(r.getFromAddress())) { + ref = r; + break; + } + } + if (ref == null) { + throw new AssertionError( + "call to " + callee.getName() + " not found in " + caller.getName()); + } + Instruction afterCall = + currentProgram.getListing().getInstructionAfter(ref.getFromAddress()); + // For architectures with a delay slot, break on the actual aftercall instruction, + // by stepping instructions until we are out of the delay slot. + while (afterCall.isInDelaySlot()) { + afterCall = afterCall.getNext(); + } + breakpoint = afterCall.getAddress(); + + } + + boolean error = testFunction(caller, callee, breakpoint, pieces); + + if (error) { + errors.add(caller); + } + } + + if (errors.size() == 0) { + Msg.info(this, "No prototype errors found."); + } + else { + Msg.info(this, errors.size() + " prototype error(s) found:"); + for (Function errFunc : errors) { + Msg.info(this, "\t" + errFunc.getName()); + } + } + + Set actualErrors = errors.stream() + .map(Function::getName) + .collect(Collectors.toSet()); + + Set expectedErrors = Set.of(EXPECTED_PROTOTYPE_ERRORS); + + List missingErrors = expectedErrors.stream() + .filter(name -> !actualErrors.contains(name)) + .collect(Collectors.toList()); + + List unexpectedErrors = actualErrors.stream() + .filter(name -> !expectedErrors.contains(name)) + .collect(Collectors.toList()); + + assertTrue( + "The following prototype errors were expected, but no corresponding error was found: " + + missingErrors, + missingErrors.isEmpty()); + + assertTrue( + "The following prototype errors were found, but they were not in the expected list: " + + unexpectedErrors, + unexpectedErrors.isEmpty()); + } + + /** + * Compare the 'expected' parameters to the 'from emulator' parameters of a function call + * to determine if the binary was correctly interpreted by Ghidra using the cspec file for the + * specified calling convention. + * @param caller function calling the function to be tested + * @param callee function that is being tested, called by the caller. + * @param breakPoint Address to stop the emulator. + * @param pieces ArrayList parameter pieces gathered from parsing the binary's + * source code. + * @return boolean indicating the result of the test + * @throws Exception when there's a problem establishing expected parameter or getting parameter + * pieces from the emulator. + */ + private boolean testFunction(Function caller, Function callee, Address breakPoint, + ArrayList pieces) throws Exception { + + List groundTruth = + CSpecPrototypeTestUtil.getPassedValues(callee, pieces, dataConverter, + (msg -> Msg.warn(this, msg))); + + // breakpoint will be skipped if condition is false, so add condition that is always true + emulator.addBreakpoint(breakPoint, "1:1"); + + PcodeThread emuThread = emulator.prepareFunction(caller); + + Register stackReg = caller.getProgram().getCompilerSpec().getStackPointer(); + + try { + emuThread.run(); + Msg.error(this, "Emulator should have hit breakpoint"); + } + catch (InterruptPcodeExecutionException e) { + // this is the breakpoint, which is what we want to happen + } + + List fromEmulator = new ArrayList<>(); + for (ParameterPieces piece : pieces) { + fromEmulator.add(CSpecPrototypeTestUtil.readParameterPieces(emuThread, piece, + emulator.getLanguage().getDefaultDataSpace(), stackReg, langCompPair, + dataConverter)); + } + + TestResult result = + CSpecPrototypeTestUtil.getTestResult(callee, caller, pieces, fromEmulator, groundTruth); + + if (result.hasError()) { + Msg.info(this, result.message()); + } + + return result.hasError(); + + } + + /** + * Sets the resource directory for the test, this is where a binary and it's source code + * should be located. + * @param relativeModulePath directory of the module that contains this class + */ + private void findTestResourceDirectory(String relativeModulePath) { + if (relativeModulePath == null) { + return; + } + for (ResourceFile appRoot : applicationRootDirectories) { + File moduleRoot = new File(appRoot.getAbsolutePath(), relativeModulePath); + File dir = new File(moduleRoot, TEST_RESOURCE_PATH); + if (dir.isDirectory()) { + resourcesTestDataDir = dir; + break; + } + } + } + + /** + * Find the path of the test module for the purposes of finding a binary and source code to use with + * the test. + * @param myModuleRootDirectory directory of the root of the module that contains this class + * @return String + */ + private String getRelativeModulePath(ResourceFile myModuleRootDirectory) { + String absolutePath = myModuleRootDirectory.getAbsolutePath(); + for (ResourceFile appRoot : applicationRootDirectories) { + String rootPath = appRoot.getAbsolutePath(); + if (absolutePath.startsWith(rootPath)) { + return absolutePath.substring(rootPath.length() + 1); + } + } + return null; + } + + /** + * @return String Language ID + */ + public abstract String getLanguageID(); + + /** + * @return String Compiler Spec ID + */ + public abstract String getCompilerSpecID(); + + /** + * @return String Calling Convention + */ + public abstract String getCallingConvention(); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecPrototypeTestConstants.java b/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecPrototypeTestConstants.java new file mode 100644 index 0000000000..888d1a9262 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecPrototypeTestConstants.java @@ -0,0 +1,107 @@ +/* ### + * 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.test.compilers.support; + +/** + * Constants that are used by the CSpecPrototypeUtil to decode cspec test binary source code function + * names. + */ +public class CSpecPrototypeTestConstants { + public static final String FIELD_NAME_PREFIX = "fld"; + + public static final String STRUCT_CHAR_SINGLETON_NAME = "sc"; + public static final String STRUCT_SHORT_SINGLETON_NAME = "ss"; + public static final String STRUCT_INT_SINGLETON_NAME = "si"; + public static final String STRUCT_LONG_SINGLETON_NAME = "sl"; + public static final String STRUCT_LONG_LONG_SINGLETON_NAME = "sll"; + public static final String STRUCT_FLOAT_SINGLETON_NAME = "sf"; + public static final String STRUCT_DOUBLE_SINGLETON_NAME = "sd"; + + public static final String STRUCT_CHAR_PAIR_NAME = "prc"; + public static final String STRUCT_SHORT_PAIR_NAME = "prs"; + public static final String STRUCT_INT_PAIR_NAME = "pri"; + public static final String STRUCT_LONG_PAIR_NAME = "prl"; + public static final String STRUCT_LONG_LONG_PAIR_NAME = "prll"; + public static final String STRUCT_FLOAT_PAIR_NAME = "prf"; + public static final String STRUCT_DOUBLE_PAIR_NAME = "prd"; + + public static final String STRUCT_CHAR_TRIP_NAME = "trc"; + public static final String STRUCT_SHORT_TRIP_NAME = "trs"; + public static final String STRUCT_INT_TRIP_NAME = "tri"; + public static final String STRUCT_LONG_TRIP_NAME = "trl"; + public static final String STRUCT_LONG_LONG_TRIP_NAME = "trll"; + public static final String STRUCT_FLOAT_TRIP_NAME = "trf"; + public static final String STRUCT_DOUBLE_TRIP_NAME = "trd"; + + public static final String STRUCT_CHAR_QUAD_NAME = "qc"; + public static final String STRUCT_SHORT_QUAD_NAME = "qs"; + public static final String STRUCT_INT_QUAD_NAME = "qi"; + public static final String STRUCT_LONG_QUAD_NAME = "ql"; + public static final String STRUCT_LONG_LONG_QUAD_NAME = "qll"; + public static final String STRUCT_FLOAT_QUAD_NAME = "qf"; + public static final String STRUCT_DOUBLE_QUAD_NAME = "qd"; + + public static final String STRUCT_INT_LONG_INT = "stili"; + public static final String STRUCT_FLOAT_INT_FLOAT = "stfif"; + public static final String STRUCT_LONG_DOUBLE_LONG = "stldl"; + public static final String STRUCT_FLOAT_DOUBLE_FLOAT = "stfdf"; + + public static final String UNION_CHAR = "unsc"; + public static final String UNION_SHORT = "unss"; + public static final String UNION_INT = "unsi"; + public static final String UNION_LONG = "unsl"; + public static final String UNION_FLOAT = "unsf"; + public static final String UNION_DOUBLE = "unsd"; + public static final String UNION_LONG_LONG = "unsll"; + + public static final String UNION_INT_LONG = "unpil"; + public static final String UNION_FLOAT_DOUBLE = "unpfd"; + public static final String UNION_INT_FLOAT = "unpif"; + public static final String UNION_LONG_DOUBLE = "unpld"; + public static final String UNION_INT_DOUBLE = "unpid"; + public static final String UNION_LONG_FLOAT = "unplf"; + + public static final String UNION_STRUCT_INT = "unsti"; + public static final String UNION_STRUCT_FLOAT = "unstf"; + public static final String UNION_MIXED_STRUCT_INTEGRAL = "unmsti"; + public static final String UNION_MIXED_STRUCT_FLOATING = "unmstf"; + public static final String UNION_MIXED_STRUCT_ALL_SMALL = "unmstas"; + public static final String UNION_MIXED_STRUCT_ALL_LARGE = "unmstal"; + + public static final String UNION_STRUCT_TRIP_CHAR = "unsttc"; + public static final String UNION_STRUCT_TRIP_SHORT = "unstts"; + + public static final String PARAMS_PRIMITIVE_IDENTICAL = "paramsPrimitiveIdentical"; + public static final String PARAMS_PRIMITIVE_ALTERNATE = "paramsPrimitiveAlternate"; + public static final String PARAMS_MISC = "paramsMisc"; + public static final String PARAMS_VARIADIC = "paramsVariadic"; + public static final String PARAMS_SINGLETON_STRUCT = "paramsSingletonStruct"; + public static final String PARAMS_PAIR_STRUCT = "paramsPairStruct"; + public static final String PARAMS_TRIP_STRUCT = "paramsTripStruct"; + public static final String PARAMS_QUAD_STRUCT = "paramsQuadStruct"; + public static final String PARAMS_MIXED_STRUCT = "paramsMixedStruct"; + public static final String PARAMS_UNION = "paramsUnion"; + public static final String PRODUCER = "producer"; + public static final String EXTERNAL = "external"; + + public static final String RETURN_PRIMITIVE = "returnPrimitive"; + public static final String RETURN_SINGLETON = "returnSingleton"; + public static final String RETURN_PAIR = "returnPair"; + public static final String RETURN_TRIPLE = "returnTriple"; + public static final String RETURN_QUAD = "returnQuad"; + public static final String RETURN_MIXED = "returnMixed"; + public static final String RETURN_UNION = "returnUnion"; +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecPrototypeTestUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecPrototypeTestUtil.java new file mode 100644 index 0000000000..7066748e9d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecPrototypeTestUtil.java @@ -0,0 +1,817 @@ +/* ### + * 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.test.compilers.support; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.Consumer; + +import org.apache.commons.lang3.ArrayUtils; + +import com.google.common.primitives.*; + +import ghidra.app.cmd.function.ApplyFunctionDataTypesCmd; +import ghidra.app.util.cparser.C.CParserUtils; +import ghidra.app.util.cparser.C.ParseException; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.floatformat.FloatFormat; +import ghidra.pcode.floatformat.FloatFormatFactory; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.pcode.HighFunctionDBUtil; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.*; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.util.DataConverter; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.task.TaskMonitor; + +/** + * Utility for testing prototype models defined in cspec files. + */ +public class CSpecPrototypeTestUtil { + + private static HexFormat hexFormat = HexFormat.of(); + + public static record TestResult(String message, boolean hasError) {} + + /** + * Returns a byte array that represents parameters from the emulator. + * This data is used to compare against the representation of parameters constructed from + * the binary's source code. + * @param emulatorThread the active emulator thread to inspect + * @param piece basic elements of a parameter + * @param addrSpace the address space where the stack resides + * @param stackReg the register acting as the stack pointer + * @param langCompPair the language/compiler specification pair + * @param dataConverter used to determine endianness and convert data to byte representation. + * @return byte[] byte array representation of parameter pieces + * @throws Exception if there is a problem reading the emulator stack or getting the correct + * language. + */ + public static byte[] readParameterPieces(PcodeThread emulatorThread, + ParameterPieces piece, + AddressSpace addrSpace, Register stackReg, LanguageCompilerSpecPair langCompPair, + DataConverter dataConverter) + throws Exception { + if (piece.type instanceof VoidDataType) { + return new byte[0]; + } + if (piece.joinPieces != null) { + Varnode[] varnodes = piece.joinPieces.clone(); + if (!langCompPair.getLanguage().isBigEndian()) { + ArrayUtils.reverse(varnodes); // probably correct... + } + byte[] bytes = null; + for (Varnode vn : varnodes) { + byte[] varnodeBytes = null; + + if (vn.getAddress().isStackAddress()) { + varnodeBytes = + readEmulatorStack(emulatorThread, stackReg, addrSpace, (int) vn.getOffset(), + vn.getSize(), dataConverter); + } + else { + varnodeBytes = + readEmulatorMemory(emulatorThread, vn.getAddress(), vn.getSize()); + } + bytes = ArrayUtils.addAll(bytes, varnodeBytes); + } + return bytes; + } + int dataTypeSize = piece.type.getLength(); + byte[] bytes = null; + if (piece.address.isStackAddress()) { + bytes = + readEmulatorStack(emulatorThread, stackReg, addrSpace, + (int) piece.address.getOffset(), + dataTypeSize, dataConverter); + } + else { + bytes = readEmulatorMemory(emulatorThread, piece.address, dataTypeSize); + } + if ((piece.hiddenReturnPtr || piece.isIndirect) && (piece.address != null)) { + // value is an address, we need to verify the value stored there + if (!(piece.type instanceof PointerDataType pointerType)) { + return bytes; + } + if (!langCompPair.getLanguage().isBigEndian()) { + ArrayUtils.reverse(bytes); + } + long offset = -1; + switch (bytes.length) { + case 2: + offset = Shorts.fromByteArray(bytes); + break; + case 4: + offset = Ints.fromByteArray(bytes); + break; + case 8: + offset = Longs.fromByteArray(bytes); + break; + default: + throw new AssertionError("unsupported size: " + bytes.length); + } + Address addr = addrSpace.getAddress(offset); + DataType base = pointerType.getDataType(); + bytes = readEmulatorMemory(emulatorThread, addr, base.getLength()); + } + return bytes; + } + + /** + * Reads data from emulator's memory at the given Address and for the given size and returns as + * a byte array. + * @param emulatorThread the active emulator thread to inspect + * @param address Address of memory to read + * @param size Size of memory chunk to read + * @return byte[] containing the data read from the emulator's memory + */ + public static byte[] readEmulatorMemory(PcodeThread emulatorThread, Address address, + int size) { + return emulatorThread.getState().getVar(address, size, false, Reason.INSPECT); + } + + /** + * Reads data from the emulator's stack memory by resolving the current stack pointer + * and applying a specified offset. + * @param emulatorThread the active emulator thread to inspect + * @param stackReg the register acting as the stack pointer + * @param addrSpace the address space where the stack resides + * @param offset the byte offset from the stack pointer + * @param size the number of bytes to read from the stack + * @param dataConverter the converter used to interpret the stack pointer's endianness + * @return byte[] containing the data read from the emulator's memory + * @throws Exception if the register cannot be read or the address is invalid within the state + */ + public static byte[] readEmulatorStack(PcodeThread emulatorThread, Register stackReg, + AddressSpace addrSpace, int offset, int size, DataConverter dataConverter) + throws Exception { + byte[] stackPtr = emulatorThread.getState().getVar(stackReg, Reason.INSPECT); + ByteBuffer stackPtrBuf = ByteBuffer.wrap(stackPtr); + stackPtrBuf.order( + dataConverter.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); + long stackPtrVal; + if (stackPtr.length >= 8) { + stackPtrVal = stackPtrBuf.getLong(); + } + // Avoid sign-extending negative values + else if (stackPtr.length >= 4) { + stackPtrVal = Integer.toUnsignedLong(stackPtrBuf.getInt()); + } + else { + stackPtrVal = Short.toUnsignedLong(stackPtrBuf.getShort()); + } + Address addr = addrSpace.getAddress(stackPtrVal + offset); + return emulatorThread.getState().getVar(addr, size, false, Reason.INSPECT); + } + + /** + * For a given function, and list of parameter pieces, return a list of bytes representing the + * values of the parameters. This data is produced from the source code of the binary which is + * used in cspec tests to compare against the list of bytes representing parameter values from + * the emulator. + * @param func Function to get the parameter values of. + * @param pieces ParameterPieces representing basic elements of the parameters of the function. + * @param dataConverter used to convert data to bytes and vice versa. + * @param logger {@code Consumer} lambda function to print logging information + * @return {@code List} byte representation of function parameter values + * @throws MemoryAccessException if there is a problem accessing the function's program memory. + */ + public static List getPassedValues(Function func, List pieces, + DataConverter dataConverter, Consumer logger) + throws MemoryAccessException { + Program program = func.getProgram(); + List groundTruth = new ArrayList<>(); + for (int i = 0; i < pieces.size(); ++i) { + ParameterPieces piece = pieces.get(i); + DataType dt = piece.type; + if (dt == null) { + throw new AssertionError("null datatype for piece " + i + " in " + func.getName()); + } + if (dt instanceof VoidDataType) { + groundTruth.add(new byte[0]); // for testing return values + continue; + } + boolean isPointer = false; + DataType baseType = dt; + if (dt instanceof Pointer pointer) { + baseType = pointer.getDataType(); + isPointer = true; + } + int index = i; + if (i >= 1) { + Parameter param = func.getParameter(i - 1); + if (param != null) { + AutoParameterType autoType = param.getAutoParameterType(); + if (autoType != null && + param.getAutoParameterType().equals(AutoParameterType.RETURN_STORAGE_PTR)) { + index = 0; + } + } + } + + String symbolName = baseType.getName() + "_" + Integer.toString(index); + Symbol symbol = program.getSymbolTable().getSymbols(symbolName).next(); + if (symbol == null) { + // Sometimes compilers will prepend a leading underscore to symbol names + // try -fno-leading-underscore + symbolName = "_" + symbolName; + symbol = program.getSymbolTable().getSymbols(symbolName).next(); + if (symbol == null) { + throw new AssertionError("null Symbol for name " + symbolName + " in " + + func.getName() + " piece " + i); + } + + } + byte[] value = new byte[dt.getLength()]; + if (isPointer) { + if ((piece.hiddenReturnPtr || piece.isIndirect) && (piece.address != null)) { + value = new byte[baseType.getLength()]; + program.getMemory().getBytes(symbol.getAddress(), value); + } + else { + long offset = symbol.getAddress().getAddressableWordOffset(); + dataConverter.getBytes(offset, dt.getLength(), value, 0); + } + } + else { + program.getMemory().getBytes(symbol.getAddress(), value); + // handle calling-convention enforced conversions, such as + // converting floats to doubles + if (piece.joinPieces != null && piece.joinPieces.length == 1 && + piece.joinPieces[0].getSize() != dt.getLength()) { + value = getExtendedValue(value, dt, piece.joinPieces[0], dataConverter, logger); + } + } + groundTruth.add(value); + } + return groundTruth; + + } + + /** + * Extends the byte representation of values from their original DataType to the DataType that + * is indicated by the joinPiece Varnode's size. + * @param value the raw byte array of the original value + * @param dt the source data type (e.g., FloatDataType, DoubleDataType, or Structure) + * @param joinPiece representing the target storage, used to determine target size + * @param dataConverter the converter used to determine endianness + * @param logger {@code Consumer} lambda function to print logging information + * @return byte[] a byte array containing the extended value, + */ + public static byte[] getExtendedValue(byte[] value, DataType dt, Varnode joinPiece, + DataConverter dataConverter, Consumer logger) { + byte[] extended = new byte[joinPiece.getSize()]; + // float -> double + if (dt instanceof FloatDataType && dt.getLength() == 4 && joinPiece.getSize() == 8) { + ByteOrder byteOrder = + dataConverter.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; + float floatValue = ByteBuffer.wrap(value).order(byteOrder).getFloat(); + double doubleValue = floatValue; + extended = ByteBuffer.allocate(8).order(byteOrder).putDouble(doubleValue).array(); + } + // float -> double, but the float is a single-element HFA + else if (dt instanceof Structure struct && + struct.getComponent(0).getDataType() instanceof FloatDataType && dt.getLength() == 4 && + joinPiece.getSize() == 8) { + + ByteOrder byteOrder = + dataConverter.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; + float floatValue = ByteBuffer.wrap(value).order(byteOrder).getFloat(); + double doubleValue = floatValue; + extended = ByteBuffer.allocate(8).order(byteOrder).putDouble(doubleValue).array(); + } + // float -> 80 bit floating point format + else if (dt instanceof FloatDataType && dt.getLength() == 4 && joinPiece.getSize() == 10) { + ByteOrder byteOrder = + dataConverter.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; + float floatValue = ByteBuffer.wrap(value).order(byteOrder).getFloat(); + // See OpBehaviorFloatFloat2Float + FloatFormat inFF = FloatFormatFactory.getFloatFormat(4); + FloatFormat outFF = FloatFormatFactory.getFloatFormat(10); + long inEncoded = inFF.getEncoding(floatValue); + BigInteger inEncodedBig = BigInteger.valueOf(inEncoded); + BigInteger outEncoded = inFF.opFloat2Float(inEncodedBig, outFF); + + byte[] outBytes = outEncoded.toByteArray(); // Returned as Big-Endian + // Pad to expected size and/or flip byte order + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = 10 - outBytes.length; i < 10; i++) { + extended[i] = outBytes[i]; + } + } + else { + for (int i = 0; i < outBytes.length; i++) { + extended[9 - i] = outBytes[i]; + } + } + } + // double -> 80 bit floating point format + else if (dt instanceof DoubleDataType && dt.getLength() == 8 && joinPiece.getSize() == 10) { + ByteOrder byteOrder = + dataConverter.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; + double doubleValue = ByteBuffer.wrap(value).order(byteOrder).getDouble(); + // See OpBehaviorFloatFloat2Float + FloatFormat inFF = FloatFormatFactory.getFloatFormat(8); + FloatFormat outFF = FloatFormatFactory.getFloatFormat(10); + long inEncoded = inFF.getEncoding(doubleValue); + BigInteger inEncodedBig = BigInteger.valueOf(inEncoded); + BigInteger outEncoded = inFF.opFloat2Float(inEncodedBig, outFF); + + byte[] outBytes = outEncoded.toByteArray(); // Returned as Big-Endian + // Pad to expected size and/or flip byte order + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = 10 - outBytes.length; i < 10; i++) { + extended[i] = outBytes[i]; + } + } + else { + for (int i = 0; i < outBytes.length; i++) { + extended[9 - i] = outBytes[i]; + } + } + } + else { + logger.accept("Unhandled extension: dt=%s; size=%d, extended size=%d\n" + .formatted(dt.getDisplayName(), dt.getLength(), joinPiece.getSize())); + } + return extended; + } + + /** + * Gets the parameters between a caller and callee and organizes them in a PrototypePieces + * object by prototypeModel. ParameterPieces may be spread between them depending on the calling + * convention being used. + * @param caller The function that calls the callee. + * @param callee The function being called by the caller. + * @param model PrototypeModel corresponding to the calling convention. + * @return {@code ArrayList} + */ + public static ArrayList getParameterPieces(Function caller, Function callee, + PrototypeModel model) { + Program program = callee.getProgram(); + PrototypePieces pieces = new PrototypePieces(model, null); + FunctionSignature funcSig = callee.getSignature(true); + pieces.outtype = funcSig.getReturnType(); + if (callee.hasVarArgs()) { + // args for callsite encode in caller's name + List types = getVarArgsParamTypes(caller); + for (DataType type : types) { + pieces.intypes.add(type); + } + pieces.firstVarArgSlot = funcSig.getArguments().length; + } + else { + for (ParameterDefinition def : funcSig.getArguments()) { + pieces.intypes.add(def.getDataType()); + } + } + ArrayList paramPieces = new ArrayList<>(); + model.assignParameterStorage(pieces, program.getDataTypeManager(), paramPieces, true); + return paramPieces; + } + + /** + * All functions in the source code for these Cspec tests are named in such a way that the + * parameter types of the function are encoded in the name. This function decodes the + * function names to retrieve the function parameter types as a list. + * @param func The function to decode into it's parameter's datatypes + * @return {@code List} the datatypes of the parameters of the function. + */ + public static List getVarArgsParamTypes(Function func) { + List types = new ArrayList<>(); + String name = func.getName(); + String[] parts = name.split("_"); + DataTypeManager dtManager = func.getProgram().getDataTypeManager(); + // name is paramsVariadic_(type list)_counter + for (int i = 1; i < parts.length - 1; ++i) { + DataType type = null; + switch (parts[i]) { + case "c": + type = new CharDataType(dtManager); + break; + case "C": + type = new UnsignedCharDataType(dtManager); + break; + case "s": + type = new ShortDataType(dtManager); + break; + case "S": + type = new UnsignedShortDataType(dtManager); + break; + case "i": + type = new IntegerDataType(dtManager); + break; + case "I": + type = new UnsignedIntegerDataType(dtManager); + break; + case "l": + type = new LongDataType(dtManager); + break; + case "L": + type = new UnsignedLongDataType(dtManager); + break; + case "f": + type = new FloatDataType(dtManager); + break; + case "d": + type = new DoubleDataType(dtManager); + break; + default: + throw new AssertionError("Unsupported data type: " + parts[i]); + } + types.add(type); + } + return types; + } + + /** + * Returns the function that calls the given function first. + * @param function the callee which is searched for + * @return Function the caller which is first + */ + public static Function getFirstCall(Function function) { + String[] parts = function.getName().split("_"); + String count = parts[parts.length - 1]; + Set callees = function.getCalledFunctions(TaskMonitor.DUMMY); + if (callees.size() == 0) { + throw new AssertionError("no called functions found for " + function.getName()); + } + for (Function callee : callees) { + String calleeName = getAdjustedCalleeName(callee.getName()); + + if (calleeName.startsWith(CSpecPrototypeTestConstants.EXTERNAL) || + calleeName.startsWith(CSpecPrototypeTestConstants.PRODUCER)) { + if (calleeName.endsWith(count)) { + return callee; + } + } + } + throw new AssertionError("no appropriate functions called by " + function.getName()); + } + + /** + * Adjusts the name of a function to remove characters prepended by compilers. + * @param calleeName the name of the function + * @return String the name of the function after it has been adjusted + */ + public static String getAdjustedCalleeName(String calleeName) { + // Hack for PowerPC 64-bit object files that prepend plt_call. on the extern calls + if (!calleeName.contains("plt_call.")) { + return calleeName; + } + return calleeName.split("plt_call.")[1]; + } + + /** + * Returns the PrototypeModel from the compiler spec of the given LanguageCompilerSpecPair and + * calling convention specified in the program's name. + * @param program Program whose name contains the desired calling convention + * @param langComp the language/compiler specification pair to query + * @return PrototypeModel Model corresponding to the extracted calling convention + * @throws CompilerSpecNotFoundException if the compiler specification cannot be found/loaded + * @throws LanguageNotFoundException if the specified language is not available + */ + public static PrototypeModel getProtoModelToTest(Program program, + LanguageCompilerSpecPair langComp) + throws CompilerSpecNotFoundException, LanguageNotFoundException { + String name = program.getName(); + // Expected program name format: _____ + + //Handle cases where the architecture name contains underscores + String architecture = name.split("_LE_|_BE_")[0]; + if (architecture.contains("_")) { + String cleanArchitecture = name.replace("_", ""); + name = name.replaceFirst(architecture, cleanArchitecture); + } + + String[] splitname = name.split("_"); + + // Handle cases where calling convention contains underscores + String ccName = String.join("_", Arrays.copyOfRange(splitname, 5, splitname.length)); + + return langComp.getCompilerSpec().getCallingConvention(ccName); + } + + /** + * Parses function definitions and data types of global variables from the source code and + * applies them to the binary. Also applies the correct signature overrides to calls of + * variadic functions. + * @param program Program produced from the source code being parsed. + * @param model The PrototypeModel of the program. + * @throws ParseException when the c source code cannot be parsed. + * @throws IOException when the c source code cannot be parsed. + * @throws ghidra.app.util.cparser.CPP.ParseException when the c source code cannot be parsed. + * @throws CodeUnitInsertionException When code units cannot be created at the given address. + */ + public static void applyInfoFromSourceIfNeeded(Program program, PrototypeModel model) + throws ParseException, IOException, ghidra.app.util.cparser.CPP.ParseException, + CodeUnitInsertionException { + + Category funcsFromSource = getFuncDefs(program); + if (funcsFromSource != null) { + return; // assume that this method has already been run on the test binary. + } + + DataTypeManager dtManager = program.getDataTypeManager(); + + // Parse the c source file. This assumes that the name of the source file is the + // name of the test binary + ".c" and that the two files are in the same directory + CParserUtils.parseHeaderFiles(null, new String[] { program.getExecutablePath() + ".c" }, + new String[0], dtManager, TaskMonitor.DUMMY); + + funcsFromSource = getFuncDefs(program); + if (funcsFromSource == null) { + throw new AssertionError("Error parsing C file; datatypes not added"); + } + FunctionIterator fIter = program.getFunctionManager().getExternalFunctions(); + AddressSet entryPoints = new AddressSet(); + while (fIter.hasNext()) { + entryPoints.add(fIter.next().getEntryPoint()); + } + entryPoints = entryPoints.union(program.getMemory().getExecuteSet()); + + ApplyFunctionDataTypesCmd cmd = new ApplyFunctionDataTypesCmd(funcsFromSource, entryPoints, + SourceType.USER_DEFINED, true, true); + cmd.applyTo(program, TaskMonitor.DUMMY); + + // set the calling convention on the test functions + // first the external test functions + fIter = program.getFunctionManager().getExternalFunctions(); + while (fIter.hasNext()) { + Function func = fIter.next(); + try { + func.setCallingConvention(model.getName()); + } + catch (InvalidInputException e) { + // shouldn't happen + throw new AssertionError( + "Bad calling convention name for prototype: " + model.getName()); + } + } + // now the "producer" functions used for testing returned values + fIter = program.getFunctionManager().getFunctions(true); + while (fIter.hasNext()) { + Function func = fIter.next(); + if (!func.getName().startsWith(CSpecPrototypeTestConstants.PRODUCER)) { + continue; + } + try { + func.setCallingConvention(model.getName()); + } + catch (InvalidInputException e) { + // shouldn't happen + throw new AssertionError( + "Bad calling convention name for prototype: " + model.getName()); + } + } + + // apply the correct overrides to the varargs functions + ReferenceManager refManager = program.getReferenceManager(); + fIter = program.getFunctionManager().getFunctions(true); + while (fIter.hasNext()) { + Function func = fIter.next(); + if (!func.getName().startsWith(CSpecPrototypeTestConstants.PARAMS_VARIADIC)) { + continue; + } + Function varArgsFunc = CSpecPrototypeTestUtil.getFirstCall(func); + if (!varArgsFunc.hasVarArgs()) { + throw new AssertionError(varArgsFunc.getName() + " should be marked varargs"); + } + Reference call = null; + ReferenceIterator refIter = refManager.getReferencesTo(varArgsFunc.getEntryPoint()); + while (refIter.hasNext()) { + Reference ref = refIter.next(); + if (!ref.getReferenceType().isCall()) { + continue; + } + if (func.getBody().contains(ref.getFromAddress())) { + call = ref; // assume the first one found is what we want + break; + } + } + if (call == null) { + throw new AssertionError( + "no call references to " + varArgsFunc.getName() + " in " + func.getName()); + } + List types = CSpecPrototypeTestUtil.getVarArgsParamTypes(func); + FunctionDefinitionDataType override = + new FunctionDefinitionDataType(varArgsFunc.getName(), program.getDataTypeManager()); + try { + override.setCallingConvention(model.getName()); + } + catch (InvalidInputException e) { + // shouldn't happen + throw new AssertionError("bad calling convention name: " + model.getName()); + } + override.setReturnType(VoidDataType.dataType); + ParameterDefinition[] paramDefs = new ParameterDefinition[types.size()]; + for (int i = 0; i < types.size(); ++i) { + paramDefs[i] = new ParameterDefinitionImpl("param" + i, types.get(i), null); + } + override.setArguments(paramDefs); + try { + HighFunctionDBUtil.writeOverride(func, call.getFromAddress(), override); + } + catch (InvalidInputException e) { + throw new AssertionError("bad overriding signature for variadic function"); + } + } + + // finally, apply datatypes to global variables + SymbolIterator symbolIter = program.getSymbolTable().getDefinedSymbols(); + CategoryPath source = + new CategoryPath(CategoryPath.ROOT, List.of(program.getName() + ".c")); + while (symbolIter.hasNext()) { + Symbol symbol = symbolIter.next(); + String name = symbol.getName(); + if (name == null) { + continue; + } + int underScoreIndex = name.indexOf('_'); + if (underScoreIndex == -1) { + continue; + } + String typeName = name.substring(0, underScoreIndex); + DataType type = null; + switch (typeName) { + case "char": + case "short": + case "int": + case "long": + case "longlong": + case "float": + case "double": + type = dtManager.getDataType(CategoryPath.ROOT, typeName); + break; + case CSpecPrototypeTestConstants.STRUCT_CHAR_SINGLETON_NAME: + case CSpecPrototypeTestConstants.STRUCT_SHORT_SINGLETON_NAME: + case CSpecPrototypeTestConstants.STRUCT_INT_SINGLETON_NAME: + case CSpecPrototypeTestConstants.STRUCT_LONG_SINGLETON_NAME: + case CSpecPrototypeTestConstants.STRUCT_LONG_LONG_SINGLETON_NAME: + case CSpecPrototypeTestConstants.STRUCT_FLOAT_SINGLETON_NAME: + case CSpecPrototypeTestConstants.STRUCT_DOUBLE_SINGLETON_NAME: + case CSpecPrototypeTestConstants.STRUCT_CHAR_PAIR_NAME: + case CSpecPrototypeTestConstants.STRUCT_SHORT_PAIR_NAME: + case CSpecPrototypeTestConstants.STRUCT_INT_PAIR_NAME: + case CSpecPrototypeTestConstants.STRUCT_LONG_PAIR_NAME: + case CSpecPrototypeTestConstants.STRUCT_LONG_LONG_PAIR_NAME: + case CSpecPrototypeTestConstants.STRUCT_FLOAT_PAIR_NAME: + case CSpecPrototypeTestConstants.STRUCT_DOUBLE_PAIR_NAME: + case CSpecPrototypeTestConstants.STRUCT_CHAR_TRIP_NAME: + case CSpecPrototypeTestConstants.STRUCT_SHORT_TRIP_NAME: + case CSpecPrototypeTestConstants.STRUCT_INT_TRIP_NAME: + case CSpecPrototypeTestConstants.STRUCT_LONG_TRIP_NAME: + case CSpecPrototypeTestConstants.STRUCT_LONG_LONG_TRIP_NAME: + case CSpecPrototypeTestConstants.STRUCT_FLOAT_TRIP_NAME: + case CSpecPrototypeTestConstants.STRUCT_DOUBLE_TRIP_NAME: + case CSpecPrototypeTestConstants.STRUCT_CHAR_QUAD_NAME: + case CSpecPrototypeTestConstants.STRUCT_SHORT_QUAD_NAME: + case CSpecPrototypeTestConstants.STRUCT_INT_QUAD_NAME: + case CSpecPrototypeTestConstants.STRUCT_LONG_QUAD_NAME: + case CSpecPrototypeTestConstants.STRUCT_LONG_LONG_QUAD_NAME: + case CSpecPrototypeTestConstants.STRUCT_FLOAT_QUAD_NAME: + case CSpecPrototypeTestConstants.STRUCT_DOUBLE_QUAD_NAME: + case CSpecPrototypeTestConstants.STRUCT_INT_LONG_INT: + case CSpecPrototypeTestConstants.STRUCT_FLOAT_INT_FLOAT: + case CSpecPrototypeTestConstants.STRUCT_LONG_DOUBLE_LONG: + case CSpecPrototypeTestConstants.STRUCT_FLOAT_DOUBLE_FLOAT: + case CSpecPrototypeTestConstants.UNION_CHAR: + case CSpecPrototypeTestConstants.UNION_SHORT: + case CSpecPrototypeTestConstants.UNION_INT: + case CSpecPrototypeTestConstants.UNION_LONG: + case CSpecPrototypeTestConstants.UNION_LONG_LONG: + case CSpecPrototypeTestConstants.UNION_INT_LONG: + case CSpecPrototypeTestConstants.UNION_FLOAT_DOUBLE: + case CSpecPrototypeTestConstants.UNION_INT_FLOAT: + case CSpecPrototypeTestConstants.UNION_LONG_DOUBLE: + case CSpecPrototypeTestConstants.UNION_INT_DOUBLE: + case CSpecPrototypeTestConstants.UNION_LONG_FLOAT: + case CSpecPrototypeTestConstants.UNION_STRUCT_INT: + case CSpecPrototypeTestConstants.UNION_STRUCT_FLOAT: + case CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_INTEGRAL: + case CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_FLOATING: + case CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_ALL_SMALL: + case CSpecPrototypeTestConstants.UNION_MIXED_STRUCT_ALL_LARGE: + case CSpecPrototypeTestConstants.UNION_STRUCT_TRIP_CHAR: + case CSpecPrototypeTestConstants.UNION_STRUCT_TRIP_SHORT: + type = dtManager.getDataType(source, typeName); + break; + default: + break; + } + if (type == null) { + continue; + } + // first clear any types that an analyzer may have laid down + // for example, the ASCII string searcher + program.getListing() + .clearCodeUnits(symbol.getAddress(), symbol.getAddress().add(type.getLength()), + false); + program.getListing().createData(symbol.getAddress(), type); + } + } + + /** + * Returns a TestResult record that includes a message detailing the differences between + * parameter data in the c source code, and parameter data in the Ghidra emulator between a + * specific caller function and callee function. + * @param caller The function that calls the callee. + * @param callee The function being called by the caller. + * @param pieces {@code ArrayList} representing basic elements of the parameters + * between callers and callees. + * @param fromEmulator Byte list representing parameter values from the emulator. + * @param groundTruth Byte list representing parameter values from the c source code. + * @return TestResult result record object that contains a message and a boolean hasError. + */ + public static TestResult getTestResult(Function callee, Function caller, + ArrayList pieces, List fromEmulator, + List groundTruth) { + boolean error = false; + StringBuilder sb = new StringBuilder(); + sb.append("Caller: "); + sb.append(caller.getName()); + sb.append("\nCallee: "); + sb.append(callee.getName()); + sb.append("\n\n"); + + boolean inputTest = pieces.get(0).type instanceof VoidDataType; + int begin = inputTest ? 1 : 0; + int end = inputTest ? fromEmulator.size() : 1; + for (int i = begin; i < end; ++i) { + if (!Arrays.equals(groundTruth.get(i), fromEmulator.get(i))) { + error = true; + sb.append("X "); + } + else { + sb.append(" "); + } + sb.append(pieces.get(i).type.getDisplayName()); + if (i == 0) { + sb.append(" return"); + } + else { + sb.append(" param"); + sb.append(Integer.toString(i)); + } + sb.append("\n"); + sb.append(" location: "); + String location = pieces.get(i).getVariableStorage(callee.getProgram()).toString(); + sb.append(location); + sb.append("\n"); + ParameterPieces piece = pieces.get(i); + if ((piece.hiddenReturnPtr || piece.isIndirect) && (piece.address != null)) { + sb.append(" expected bytes points to: "); + } + else { + sb.append(" expected bytes: "); + } + sb.append(hexFormat.formatHex(groundTruth.get(i))); + sb.append("\n"); + + if ((piece.hiddenReturnPtr || piece.isIndirect) && (piece.address != null)) { + sb.append(" emulator points to: "); + } + else { + sb.append(" emulator: "); + } + sb.append(hexFormat.formatHex(fromEmulator.get(i))); + sb.append("\n"); + } + + return new TestResult(sb.toString(), error); + } + + /** + * Returns the function definitions for the program as a {@link Category}. + * @param program Program to get the function definitions from. + * @return Category + */ + private static Category getFuncDefs(Program program) { + String name = program.getName(); + CategoryPath path = new CategoryPath(CategoryPath.ROOT, List.of(name + ".c", "functions")); + return program.getDataTypeManager().getCategory(path); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecTestPCodeEmulator.java b/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecTestPCodeEmulator.java new file mode 100644 index 0000000000..863d53e5a2 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/compilers/support/CSpecTestPCodeEmulator.java @@ -0,0 +1,156 @@ +/* ### + * 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.test.compilers.support; + +import java.util.Arrays; +import java.util.HexFormat; +import java.util.function.Consumer; + +import ghidra.app.util.PseudoInstruction; +import ghidra.pcode.emu.*; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Instruction; +import ghidra.util.Msg; + +/** + * An extension of {@link PcodeEmulator} that can load program memory and set up the emulator + * to run at a specific function entry point. + */ +public class CSpecTestPCodeEmulator extends PcodeEmulator { + private boolean traceDisabled = true; + private int traceLevel = 3; + private Consumer logger = (msg -> Msg.debug(this, msg)); + + public CSpecTestPCodeEmulator(Language lang) { + super(lang); + } + + public CSpecTestPCodeEmulator(Language lang, boolean traceDisabled, int traceLevel) { + this(lang, traceDisabled, traceLevel, null); + } + + public CSpecTestPCodeEmulator(Language lang, boolean traceDisabled, int traceLevel, + Consumer logger) { + super(lang); + this.traceDisabled = traceDisabled; + this.traceLevel = traceLevel; + if (logger != null) + this.logger = logger; + } + + /** + * Create BytesPcodeThread object with an overwritten 'createInstructionDecoder' method. + * @param name The name of the thread. + */ + @Override + protected BytesPcodeThread createThread(String name) { + return new BytesPcodeThread(name, this) { + @Override + protected SleighInstructionDecoder createInstructionDecoder( + PcodeExecutorState sharedState) { + return new SleighInstructionDecoder(language, sharedState) { + @Override + public PseudoInstruction decodeInstruction(Address address, + RegisterValue context) { + //Msg.debug(this, "Dissassembly at " + address + ": "); + PseudoInstruction inst = super.decodeInstruction(address, context); + //Msg.debug(this, inst.toString()); + + if (!traceDisabled && traceLevel > 0) { + logger.accept( + "Disassembly at " + address + ": " + inst.toString()); + } + + return inst; + } + }; + } + }; + } + + /** + * Load the function entry point context registers into emulator, create stack space, + * set program counter. Return a emulator thread ready for a run() call + * @param func The function to prepare the emulator to run. + * @return {@code PcodeThread} + */ + public PcodeThread prepareFunction(Function func) { + PcodeThread emuThread = newThread(); + PcodeArithmetic emuArith = emuThread.getArithmetic(); + + long stackOffset = + (func.getEntryPoint().getAddressSpace().getMaxAddress().getOffset() >>> 1) - 0x7ff; + Register stackReg = func.getProgram().getCompilerSpec().getStackPointer(); + + emuThread.getState() + .setVar(stackReg, + emuArith.fromConst(stackOffset, stackReg.getMinimumByteSize())); + + Instruction entry = + func.getProgram().getListing().getInstructionAt(func.getEntryPoint()); + + for (Register reg : entry.getRegisters()) { + RegisterValue val = entry.getRegisterValue(reg); + if (reg.isBaseRegister() && val != null && val.hasAnyValue()) { + //Msg.debug(this, "Adding register: " + reg + ", is BE? " + reg.isBigEndian() + + // ", is context? " + reg.isProcessorContext()); + byte[] curVal = emuThread.getState().getVar(reg, Reason.INSPECT); + byte[] bytes = val.toBytes(); + // bytes field of a RegisterValue is (mask : val) concatenated + byte[] maskedVal = new byte[bytes.length / 2]; + for (int i = 0; i < maskedVal.length; i++) { + // don't adjust endianness for context registers + if (!reg.isBigEndian() && !reg.isProcessorContext()) { + maskedVal[maskedVal.length - 1 - i] = + (byte) (bytes[i] & bytes[i + maskedVal.length]); + } + else { + maskedVal[i] = (byte) (bytes[i] & bytes[i + maskedVal.length]); + } + } + emuThread.getState().setVar(reg, emuArith.fromConst(maskedVal)); + + if (!traceDisabled && traceLevel > 1) { + logger.accept("Adding register: " + reg + ", is BE? " + reg.isBigEndian() + + ", is context? " + reg.isProcessorContext()); + logger.accept("\tRegister " + reg + " set to value: [" + + HexFormat.ofDelimiter(", ").formatHex(maskedVal) + "]"); + logger.accept( + "\tFrom context (mask : value): [" + + HexFormat.ofDelimiter(", ") + .formatHex(Arrays.copyOfRange(bytes, 0, curVal.length)) + + " : " + HexFormat.ofDelimiter(", ") + .formatHex( + Arrays.copyOfRange(bytes, curVal.length, bytes.length)) + + "]"); + logger.accept( + "\tWas: [" + HexFormat.ofDelimiter(", ").formatHex(curVal) + "]"); + } + } + } + + emuThread.reInitialize(); + + emuThread.overrideCounter(func.getEntryPoint()); + + return emuThread; + } +} diff --git a/Ghidra/Processors/AARCH64/src/test.processors/java/ghidra/test/processors/cspec/AARCH64_CSpecTest.java b/Ghidra/Processors/AARCH64/src/test.processors/java/ghidra/test/processors/cspec/AARCH64_CSpecTest.java new file mode 100644 index 0000000000..82f44446be --- /dev/null +++ b/Ghidra/Processors/AARCH64/src/test.processors/java/ghidra/test/processors/cspec/AARCH64_CSpecTest.java @@ -0,0 +1,52 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class AARCH64_CSpecTest extends CSpecPrototypeTest { + private static final String LANGUAGE_ID = "AARCH64:LE:64:v8A"; + private static final String COMPILER_SPEC_ID = "default"; + + private static final String CALLING_CONVENTION = "__cdecl"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "paramsUnion_65", + "paramsUnion_68", + "paramsUnion_70", + "returnUnion_113" + }; + + public AARCH64_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } + +} diff --git a/Ghidra/Processors/AARCH64/src/test.processors/java/ghidra/test/processors/cspec/AARCH64_ilp32_CSpecTest.java b/Ghidra/Processors/AARCH64/src/test.processors/java/ghidra/test/processors/cspec/AARCH64_ilp32_CSpecTest.java new file mode 100644 index 0000000000..5ee334d500 --- /dev/null +++ b/Ghidra/Processors/AARCH64/src/test.processors/java/ghidra/test/processors/cspec/AARCH64_ilp32_CSpecTest.java @@ -0,0 +1,151 @@ +/* ### + * 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.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class AARCH64_ilp32_CSpecTest extends CSpecPrototypeTest { + private static final String LANGUAGE_ID = "AARCH64:BE:32:ilp32"; + private static final String COMPILER_SPEC_ID = "default"; + + private static final String CALLING_CONVENTION = "__cdecl"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "paramsPrimitiveIdentical_0", + "paramsPrimitiveIdentical_1", + "paramsPrimitiveIdentical_2", + "paramsPrimitiveIdentical_3", + "paramsPrimitiveIdentical_4", + "paramsPrimitiveAlternate_5", + "paramsPrimitiveAlternate_6", + "paramsPrimitiveAlternate_8", + "paramsPrimitiveAlternate_9", + "paramsPrimitiveIdentical_10", + "paramsPrimitiveIdentical_13", + "paramsPrimitiveAlternate_16", + "paramsPrimitiveAlternate_17", + "paramsSingletonStruct_18", + "paramsSingletonStruct_19", + "paramsSingletonStruct_20", + "paramsSingletonStruct_21", + "paramsSingletonStruct_22", + "paramsSingletonStruct_23", + "paramsPairStruct_25", + "paramsPairStruct_26", + "paramsPairStruct_29", + "paramsPairStruct_30", + "paramsPairStruct_31", + "paramsTripStruct_32", + "paramsTripStruct_33", + "paramsTripStruct_34", + "paramsTripStruct_35", + "paramsTripStruct_36", + "paramsTripStruct_37", + "paramsTripStruct_38", + "paramsQuadStruct_39", + "paramsQuadStruct_41", + "paramsQuadStruct_42", + "paramsQuadStruct_43", + "paramsQuadStruct_44", + "paramsQuadStruct_45", + "paramsMixedStruct_46", + "paramsMixedStruct_47", + "paramsMixedStruct_48", + "paramsMixedStruct_49", + "paramsVariadic_I_I_C_C_S_S_I_I_L_L_50", + "paramsVariadic_I_I_I_I_I_I_I_I_I_I_51", + "paramsMisc_53", + "paramsMisc_54", + "paramsMisc_55", + "paramsUnion_56", + "paramsUnion_57", + "paramsUnion_58", + "paramsUnion_59", + "paramsUnion_60", + "paramsUnion_61", + "paramsUnion_62", + "paramsUnion_63", + "paramsUnion_64", + "paramsUnion_65", + "paramsUnion_66", + "paramsUnion_67", + "paramsUnion_68", + "paramsUnion_69", + "paramsUnion_70", + "paramsUnion_73", + "paramsUnion_75", + "returnSingleton_78", + "returnPair_79", + "returnTriple_80", + "returnQuad_81", + "returnUnion_82", + "returnUnion_83", + "returnSingleton_85", + "returnPair_86", + "returnTriple_87", + "returnUnion_89", + "returnUnion_90", + "returnSingleton_92", + "returnTriple_94", + "returnQuad_95", + "returnUnion_96", + "returnSingleton_98", + "returnTriple_100", + "returnQuad_101", + "returnUnion_102", + "returnMixed_103", + "returnUnion_104", + "returnUnion_105", + "returnSingleton_107", + "returnPair_108", + "returnTriple_109", + "returnQuad_110", + "returnMixed_111", + "returnUnion_113", + "returnUnion_114", + "returnSingleton_116", + "returnPair_117", + "returnTriple_118", + "returnQuad_119", + "returnMixed_120", + "returnUnion_122", + "returnMixed_123", + "returnUnion_124", + "returnPair_127", + "returnTriple_128", + "returnQuad_129" + }; + + public AARCH64_ilp32_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } + +} diff --git a/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_CSpecTest.java b/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_CSpecTest.java new file mode 100644 index 0000000000..e686c25854 --- /dev/null +++ b/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_CSpecTest.java @@ -0,0 +1,55 @@ +/* ### + * 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.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class ARM_CSpecTest extends CSpecPrototypeTest { + //ARM_LE_32_v8_default___stdcall + private static final String LANGUAGE_ID = "ARM:LE:32:v8"; + private static final String COMPILER_SPEC_ID = "default"; + + private static final String CALLING_CONVENTION = "__stdcall"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "paramsUnion_65", + "paramsUnion_68", + "paramsUnion_70", + "returnUnion_112", + "returnUnion_113", + "returnUnion_121" + }; + + public ARM_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } + +} diff --git a/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_apcs_CSpecTest.java b/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_apcs_CSpecTest.java new file mode 100644 index 0000000000..c1355904ab --- /dev/null +++ b/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_apcs_CSpecTest.java @@ -0,0 +1,64 @@ +/* ### + * 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.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class ARM_apcs_CSpecTest extends CSpecPrototypeTest { + //ARM_BE_32_v8-m_apcs___stdcall + private static final String LANGUAGE_ID = "ARM:BE:32:v8-m"; + private static final String COMPILER_SPEC_ID = "apcs"; + + private static final String CALLING_CONVENTION = "__stdcall"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "paramsSingletonStruct_7", + "paramsSingletonStruct_8", + "paramsPairStruct_11", + "paramsTripStruct_15", + "paramsTripStruct_16", + "paramsUnion_26", + "paramsUnion_27", + "paramsUnion_33", + "paramsUnion_34", + "returnSingleton_36", + "returnPair_37", + "returnTriple_38", + "returnUnion_40", + "returnUnion_41", + "returnSingleton_43", + "returnUnion_47" + }; + + public ARM_apcs_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } +} diff --git a/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_softfp_CSpecTest.java b/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_softfp_CSpecTest.java new file mode 100644 index 0000000000..895e9f2ab0 --- /dev/null +++ b/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_softfp_CSpecTest.java @@ -0,0 +1,44 @@ +/* ### + * 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.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class ARM_softfp_CSpecTest extends CSpecPrototypeTest { + private static final String LANGUAGE_ID = "ARM:LE:32:v8"; + private static final String COMPILER_SPEC_ID = "default"; + + private static final String CALLING_CONVENTION = "__stdcall_softfp"; + + public ARM_softfp_CSpecTest() throws Exception { + super(); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } +} diff --git a/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_v45_CSpecTest.java b/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_v45_CSpecTest.java new file mode 100644 index 0000000000..4e87e88640 --- /dev/null +++ b/Ghidra/Processors/ARM/src/test.processors/java/ghidra/test/processors/cspec/ARM_v45_CSpecTest.java @@ -0,0 +1,105 @@ +/* ### + * 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.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class ARM_v45_CSpecTest extends CSpecPrototypeTest { + //ARM_LE_32_v8_default___stdcall + private static final String LANGUAGE_ID = "ARM:LE:32:v6"; + private static final String COMPILER_SPEC_ID = "default"; + + private static final String CALLING_CONVENTION = "__stdcall"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "paramsPrimitiveIdentical_7", + "paramsPrimitiveAlternate_8", + "paramsPrimitiveAlternate_9", + "paramsPrimitiveIdentical_13", + "paramsPrimitiveAlternate_14", + "paramsPrimitiveAlternate_15", + "paramsPrimitiveAlternate_16", + "paramsPrimitiveAlternate_17", + "paramsSingletonStruct_23", + "paramsSingletonStruct_24", + "paramsPairStruct_27", + "paramsPairStruct_28", + "paramsPairStruct_29", + "paramsPairStruct_30", + "paramsPairStruct_31", + "paramsTripStruct_33", + "paramsTripStruct_34", + "paramsTripStruct_35", + "paramsTripStruct_36", + "paramsTripStruct_37", + "paramsTripStruct_38", + "paramsQuadStruct_40", + "paramsQuadStruct_41", + "paramsQuadStruct_42", + "paramsQuadStruct_43", + "paramsQuadStruct_44", + "paramsQuadStruct_45", + "paramsMixedStruct_46", + "paramsMixedStruct_47", + "paramsMixedStruct_48", + "paramsMixedStruct_49", + "paramsVariadic_L_d_L_d_L_d_L_d_L_d_52", + "paramsMisc_54", + "paramsMisc_55", + "paramsUnion_61", + "paramsUnion_62", + "paramsUnion_64", + "paramsUnion_68", + "paramsUnion_69", + "paramsUnion_70", + "paramsUnion_71", + "paramsUnion_72", + "paramsUnion_73", + "paramsUnion_74", + "paramsUnion_75", + "paramsUnion_76", + "returnTriple_87", + "returnQuad_88", + "returnUnion_90", + "returnPair_93", + "returnPair_99", + "returnPair_108", + "returnSingleton_116", + "returnUnion_121", + "returnSingleton_126", + "returnUnion_130", + }; + + public ARM_v45_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } + +} diff --git a/Ghidra/Processors/TI_MSP430/src/test.processors/java/ghidra/test/processors/cspec/MSP430X_CSpecTest.java b/Ghidra/Processors/TI_MSP430/src/test.processors/java/ghidra/test/processors/cspec/MSP430X_CSpecTest.java new file mode 100644 index 0000000000..4f01f09f1a --- /dev/null +++ b/Ghidra/Processors/TI_MSP430/src/test.processors/java/ghidra/test/processors/cspec/MSP430X_CSpecTest.java @@ -0,0 +1,141 @@ +/* ### + * 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.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class MSP430X_CSpecTest extends CSpecPrototypeTest { + private static final String LANGUAGE_ID = "TI_MSP430X:LE:32:default"; + private static final String COMPILER_SPEC_ID = "default"; + + private static final String CALLING_CONVENTION = "__stdcall"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "paramsPrimitiveIdentical_0", + "paramsPrimitiveIdentical_3", + "paramsPrimitiveIdentical_4", + "paramsPrimitiveAlternate_5", + "paramsPrimitiveAlternate_6", + "paramsPrimitiveIdentical_7", + "paramsPrimitiveAlternate_9", + "paramsPrimitiveIdentical_10", + "paramsPrimitiveAlternate_11", + "paramsPrimitiveAlternate_12", + "paramsPrimitiveIdentical_13", + "paramsPrimitiveAlternate_14", + "paramsPrimitiveAlternate_15", + "paramsPrimitiveAlternate_16", + "paramsPrimitiveAlternate_17", + "paramsSingletonStruct_18", + "paramsSingletonStruct_19", + "paramsSingletonStruct_20", + "paramsSingletonStruct_21", + "paramsSingletonStruct_22", + "paramsSingletonStruct_23", + "paramsSingletonStruct_24", + "paramsPairStruct_25", + "paramsPairStruct_26", + "paramsPairStruct_27", + "paramsPairStruct_28", + "paramsPairStruct_29", + "paramsPairStruct_30", + "paramsPairStruct_31", + "paramsTripStruct_32", + "paramsTripStruct_33", + "paramsTripStruct_34", + "paramsTripStruct_35", + "paramsTripStruct_36", + "paramsTripStruct_37", + "paramsTripStruct_38", + "paramsQuadStruct_39", + "paramsQuadStruct_40", + "paramsQuadStruct_41", + "paramsQuadStruct_42", + "paramsQuadStruct_43", + "paramsQuadStruct_44", + "paramsQuadStruct_45", + "paramsMixedStruct_46", + "paramsMixedStruct_47", + "paramsMixedStruct_48", + "paramsMixedStruct_49", + "paramsVariadic_I_I_C_C_S_S_I_I_L_L_50", + "paramsVariadic_I_I_I_I_I_I_I_I_I_I_51", + "paramsVariadic_L_d_L_d_L_d_L_d_L_d_52", + "paramsMisc_53", + "paramsMisc_54", + "paramsMisc_55", + "paramsUnion_56", + "paramsUnion_57", + "paramsUnion_58", + "paramsUnion_59", + "paramsUnion_60", + "paramsUnion_61", + "paramsUnion_62", + "paramsUnion_63", + "paramsUnion_64", + "paramsUnion_65", + "paramsUnion_66", + "paramsUnion_67", + "paramsUnion_68", + "paramsUnion_69", + "paramsUnion_70", + "paramsUnion_71", + "paramsUnion_72", + "paramsUnion_73", + "paramsUnion_74", + "paramsUnion_75", + "paramsUnion_76", + "returnSingleton_78", + "returnPair_79", + "returnTriple_80", + "returnQuad_81", + "returnUnion_82", + "returnUnion_83", + "returnSingleton_85", + "returnPair_86", + "returnUnion_89", + "returnSingleton_92", + "returnPair_93", + "returnUnion_96", + "returnPrimitive_97", + "returnSingleton_98", + "returnUnion_102", + "returnPrimitive_106", + "returnSingleton_107", + "returnUnion_112", + "returnPrimitive_115", + "returnPrimitive_125", + }; + + public MSP430X_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } +} diff --git a/Ghidra/Processors/TI_MSP430/src/test.processors/java/ghidra/test/processors/cspec/MSP430_CSpecTest.java b/Ghidra/Processors/TI_MSP430/src/test.processors/java/ghidra/test/processors/cspec/MSP430_CSpecTest.java new file mode 100644 index 0000000000..f5d8cb2080 --- /dev/null +++ b/Ghidra/Processors/TI_MSP430/src/test.processors/java/ghidra/test/processors/cspec/MSP430_CSpecTest.java @@ -0,0 +1,79 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class MSP430_CSpecTest extends CSpecPrototypeTest { + private static final String LANGUAGE_ID = "TI_MSP430:LE:16:default"; + private static final String COMPILER_SPEC_ID = "default"; + + private static final String CALLING_CONVENTION = "__stdcall"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "paramsVariadic_I_I_C_C_S_S_I_I_L_L_50", + "paramsUnion_56", + "paramsUnion_57", + "paramsUnion_58", + "paramsUnion_59", + "paramsUnion_60", + "paramsUnion_61", + "paramsUnion_62", + "paramsUnion_63", + "paramsUnion_64", + "paramsUnion_65", + "paramsUnion_66", + "paramsUnion_67", + "paramsUnion_68", + "paramsUnion_69", + "paramsUnion_70", + "paramsUnion_71", + "paramsUnion_72", + "paramsUnion_73", + "paramsUnion_74", + "paramsUnion_75", + "paramsUnion_76", + "returnUnion_82", + "returnUnion_83", + "returnUnion_89", + "returnUnion_90", + "returnUnion_96", + "returnUnion_102", + "returnUnion_104", + "returnUnion_112", + "returnUnion_121", + "returnUnion_130", + }; + + public MSP430_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } +} diff --git a/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_64_gcc_CSpecTest.java b/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_64_gcc_CSpecTest.java new file mode 100644 index 0000000000..4625d8c7b5 --- /dev/null +++ b/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_64_gcc_CSpecTest.java @@ -0,0 +1,49 @@ +/* ### + * 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.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class X86_64_gcc_CSpecTest extends CSpecPrototypeTest { + private static final String LANGUAGE_ID = "x86:LE:64:default"; + private static final String COMPILER_SPEC_ID = "gcc"; + + private static final String CALLING_CONVENTION = "__stdcall"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "returnMixed_103", + "returnMixed_123" + }; + + public X86_64_gcc_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } +} diff --git a/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_64_gcc_MSABI_CSpecTest.java b/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_64_gcc_MSABI_CSpecTest.java new file mode 100644 index 0000000000..4885820f4f --- /dev/null +++ b/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_64_gcc_MSABI_CSpecTest.java @@ -0,0 +1,65 @@ +/* ### + * 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.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class X86_64_gcc_MSABI_CSpecTest extends CSpecPrototypeTest { + + private static final String LANGUAGE_ID = "x86:LE:64:default"; + private static final String COMPILER_SPEC_ID = "gcc"; + + private static final String CALLING_CONVENTION = "MSABI"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "paramsSingletonStruct_22", + "paramsSingletonStruct_23", + "paramsMisc_53", + "paramsUnion_63", + "paramsUnion_64", + "returnUnion_83", + "returnUnion_90", + "returnQuad_95", + "returnPair_99", + "returnMixed_103", + "returnUnion_104", + "returnQuad_110", + "returnUnion_113", + "returnPair_117", + "returnMixed_123", + "returnPair_127", + }; + + public X86_64_gcc_MSABI_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } + +} diff --git a/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_64_windows_CSpecTest.java b/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_64_windows_CSpecTest.java new file mode 100644 index 0000000000..6ef5ee7340 --- /dev/null +++ b/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_64_windows_CSpecTest.java @@ -0,0 +1,51 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class X86_64_windows_CSpecTest extends CSpecPrototypeTest { + private static final String LANGUAGE_ID = "x86:LE:64:default"; + private static final String COMPILER_SPEC_ID = "windows"; + + private static final String CALLING_CONVENTION = "__fastcall"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "paramsUnion_63", + "paramsUnion_64", + "returnUnion_83", + "returnUnion_90" + }; + + public X86_64_windows_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } +} diff --git a/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_gcc_CSpecTest.java b/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_gcc_CSpecTest.java new file mode 100644 index 0000000000..f608c92c24 --- /dev/null +++ b/Ghidra/Processors/x86/src/test.processors/java/ghidra/test/processors/cspec/X86_gcc_CSpecTest.java @@ -0,0 +1,56 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.test.processors.cspec; + +import ghidra.test.compilers.support.CSpecPrototypeTest; + +public class X86_gcc_CSpecTest extends CSpecPrototypeTest { + private static final String LANGUAGE_ID = "x86:LE:32:default"; + private static final String COMPILER_SPEC_ID = "gcc"; + + private static final String CALLING_CONVENTION = "__cdecl"; + + private static final String[] EXPECTED_PROTOTYPE_ERRORS = { + "returnUnion_82", + "returnUnion_83", + "returnUnion_89", + "returnUnion_90", + "returnUnion_96", + "returnUnion_102", + "returnUnion_112", + "returnUnion_121", + "returnUnion_130" + }; + + public X86_gcc_CSpecTest() throws Exception { + super(EXPECTED_PROTOTYPE_ERRORS); + } + + @Override + public String getLanguageID() { + return LANGUAGE_ID; + } + + @Override + public String getCompilerSpecID() { + return COMPILER_SPEC_ID; + } + + @Override + public String getCallingConvention() { + return CALLING_CONVENTION; + } +} diff --git a/gradle/javaProject.gradle b/gradle/javaProject.gradle index b9d9ea3bcf..f43ca85df7 100644 --- a/gradle/javaProject.gradle +++ b/gradle/javaProject.gradle @@ -132,6 +132,7 @@ configurations { integrationTestImplementation.extendsFrom testImplementation integrationTestRuntimeOnly.extendsFrom testRuntimeOnly, integrationTestImplementation pcodeTestImplementation.extendsFrom implementation + cspecTestImplementation.extendsFrom implementation scriptsImplementation.extendsFrom implementation testArtifacts.extendsFrom testRuntimeOnly integrationTestArtifacts.extendsFrom integrationTestRuntimeOnly @@ -163,6 +164,7 @@ dependencies { testImplementation "junit:junit:4.13.2" pcodeTestImplementation "junit:junit:4.13.2" + cspecTestImplementation "junit:junit:4.13.2" } // For Java 9, we must explicitly export references to the internal classes we are using. diff --git a/gradle/javaTestProject.gradle b/gradle/javaTestProject.gradle index f5cd9e391f..5e3614963e 100644 --- a/gradle/javaTestProject.gradle +++ b/gradle/javaTestProject.gradle @@ -94,6 +94,33 @@ task pcodeTest (type: Test) { t -> } } +task cspecTest (type: Test) { t -> + description = "Runs cspec tests." + group = "cspecTest" + testClassesDirs = files sourceSets.pcodeTest.output.classesDirs + classpath = sourceSets.pcodeTest.runtimeClasspath + + filter { + setIncludePatterns() // Clear default Ghidra test filters + // Tell JUnit to ONLY run tests located in the cspec package + includeTestsMatching "ghidra.test.processors.cspec.*" + + failOnNoMatchingTests = false + } + + // Enable if you want to force Gradle to launch a new JVM for each test. + forkEvery = 1 + + initTestJVM(t, rootProject.ext.cspecTestRootDirName) + + doFirst { + startTestTimer(t) + } + doLast { + endTestTimer(t) + } +} + rootProject.unitTestReport { testResults.from(this.project.test) } diff --git a/gradle/root/test.gradle b/gradle/root/test.gradle index 6eb7ce0b2d..3e29398e5f 100644 --- a/gradle/root/test.gradle +++ b/gradle/root/test.gradle @@ -62,6 +62,7 @@ if (!project.hasProperty('srcTreeName')) { project.ext.testRootDirName = "JunitTest_" + srcTreeName project.ext.pcodeTestRootDirName = "PCodeTest_" + srcTreeName +project.ext.cspecTestRootDirName = "CSpecTest_" + srcTreeName project.ext.shareDir = System.properties.get('share.dir') if (project.ext.shareDir != null) {