New combined decompiler testing framework

This commit is contained in:
caheckman 2021-01-27 14:55:00 -05:00
parent 5d7a7c5291
commit ab76cc6095
32 changed files with 1788 additions and 791 deletions

View file

@ -11,6 +11,25 @@ src/decompile/build.gradle||GHIDRA||||END|
src/decompile/cpp/.gitignore||GHIDRA||||END|
src/decompile/cpp/Doxyfile||GHIDRA|||Most of this file is autogenerated by doxygen which falls under the GPL - output from GPL products are NOT GPL! - mjbell4|END|
src/decompile/cpp/Makefile||GHIDRA||||END|
src/decompile/datatests/deadvolatile.xml||GHIDRA||||END|
src/decompile/datatests/floatprint.xml||GHIDRA||||END|
src/decompile/datatests/forloop1.xml||GHIDRA||||END|
src/decompile/datatests/forloop_loaditer.xml||GHIDRA||||END|
src/decompile/datatests/forloop_thruspecial.xml||GHIDRA||||END|
src/decompile/datatests/forloop_varused.xml||GHIDRA||||END|
src/decompile/datatests/forloop_withskip.xml||GHIDRA||||END|
src/decompile/datatests/loopcomment.xml||GHIDRA||||END|
src/decompile/datatests/namespace.xml||GHIDRA||||END|
src/decompile/datatests/nestedoffset.xml||GHIDRA||||END|
src/decompile/datatests/noforloop_alias.xml||GHIDRA||||END|
src/decompile/datatests/noforloop_globcall.xml||GHIDRA||||END|
src/decompile/datatests/noforloop_iterused.xml||GHIDRA||||END|
src/decompile/datatests/offsetarray.xml||GHIDRA||||END|
src/decompile/datatests/promotecompare.xml||GHIDRA||||END|
src/decompile/datatests/readvolatile.xml||GHIDRA||||END|
src/decompile/datatests/threedim.xml||GHIDRA||||END|
src/decompile/datatests/twodim.xml||GHIDRA||||END|
src/decompile/datatests/wayoffarray.xml||GHIDRA||||END|
src/main/doc/cspec.xml||GHIDRA||||END|
src/main/doc/cspec_html.xsl||GHIDRA||||END|
src/main/doc/decompileplugin.xml||GHIDRA||||END|

View file

@ -1,288 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="cdt.managedbuild.toolchain.gnu.base.1693333286">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.base.1693333286" moduleId="org.eclipse.cdt.core.settings" name="Default">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.base.1693333286" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="cdt.managedbuild.toolchain.gnu.base.1693333286.895166479" name="/" resourcePath="">
<toolChain id="cdt.managedbuild.toolchain.gnu.base.1966692717" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.base">
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.target.gnu.platform.base.2081261468" name="Debug Platform" osList="linux,hpux,aix,qnx" superClass="cdt.managedbuild.target.gnu.platform.base"/>
<builder buildPath="${workspace_loc:/_Decompiler}/cpp" id="cdt.managedbuild.target.gnu.builder.base.2048731974" incrementalBuildTarget="ghidra_opt" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="cdt.managedbuild.target.gnu.builder.base"/>
<tool id="cdt.managedbuild.tool.gnu.archiver.base.1325849601" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.233123430" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base">
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.1087598226" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.compiler.base.1211851151" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.base">
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.12359898" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.c.linker.base.1810518227" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base"/>
<tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.1320265924" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base">
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.361769658" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
<tool id="cdt.managedbuild.tool.gnu.assembler.base.74027553" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.base">
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.1774124767" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
</tool>
</toolChain>
</folderInfo>
<sourceEntries>
<entry excluding="extcpp" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
<entry excluding="unittests|extcpp" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="extcpp"/>
<entry flags="VALUE_WORKSPACE_PATH" kind="sourcePath" name="unittests"/>
</sourceEntries>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="_Decompiler.null.1084391757" name="_Decompiler"/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="refreshScope" versionNumber="2">
<configuration configurationName="Default">
<resource resourceType="PROJECT" workspacePath="/_Decompiler"/>
</configuration>
</storageModule>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1693333286;cdt.managedbuild.toolchain.gnu.base.1693333286.895166479;cdt.managedbuild.tool.gnu.c.compiler.base.1211851151;cdt.managedbuild.tool.gnu.c.compiler.input.12359898">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.base.1693333286;cdt.managedbuild.toolchain.gnu.base.1693333286.895166479;cdt.managedbuild.tool.gnu.cpp.compiler.base.233123430;cdt.managedbuild.tool.gnu.cpp.compiler.input.1087598226">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
<buildTargets>
<target name="reallyclean" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>reallyclean</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="sleigh_dbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>sleigh_dbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="ghidra_dbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>ghidra_dbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="decomp_dbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>decomp_dbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="ghidra_opt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>ghidra_opt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="decomp_opt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>decomp_opt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="sleigh_opt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>sleigh_opt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="install_ghidradbg" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>install_ghidradbg</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
<target name="install_ghidraopt" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
<buildCommand>make</buildCommand>
<buildArguments>-j8</buildArguments>
<buildTarget>install_ghidraopt</buildTarget>
<stopOnError>true</stopOnError>
<useDefaultCommand>true</useDefaultCommand>
<runAllBuilders>true</runAllBuilders>
</target>
</buildTargets>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>
</cproject>

View file

@ -56,6 +56,9 @@ LNK=
# Source files
ALL_SOURCE= $(wildcard *.cc)
ALL_NAMES=$(subst .cc,,$(ALL_SOURCE))
UNITTEST_SOURCE= $(wildcard ../unittests/*.cc)
UNITTEST_NAMES=$(subst .cc,,$(UNITTEST_SOURCE))
UNITTEST_STRIP=$(subst ../unittests/,,$(UNITTEST_NAMES))
COREEXT_SOURCE= $(wildcard coreext_*.cc)
COREEXT_NAMES=$(subst .cc,,$(COREEXT_SOURCE))
@ -91,7 +94,7 @@ GHIDRA= ghidra_arch inject_ghidra ghidra_translate loadimage_ghidra \
# Additional files specific to the sleigh compiler
SLACOMP=slgh_compile slghparse slghscan
# Additional special files that should not be considered part of the library
SPECIAL=consolemain sleighexample test
SPECIAL=consolemain sleighexample test testfunction
# Any additional modules for the command line decompiler
EXTRA= $(filter-out $(CORE) $(DECCORE) $(SLEIGH) $(GHIDRA) $(SLACOMP) $(SPECIAL),$(ALL_NAMES))
@ -114,8 +117,8 @@ COMMANDLINE_NAMES=$(CORE) $(DECCORE) $(EXTRA) $(SLEIGH) consolemain
COMMANDLINE_DEBUG=-DCPUI_DEBUG -D__TERMINAL__
COMMANDLINE_OPT=-D__TERMINAL__
TEST_NAMES=$(CORE) $(DECCORE) $(SLEIGH) test
TEST_DEBUG=-D__TERMINAL__ -g -O0
TEST_NAMES=$(CORE) $(DECCORE) $(SLEIGH) $(EXTRA) testfunction test
TEST_DEBUG=-D__TERMINAL__
GHIDRA_NAMES=$(CORE) $(DECCORE) $(GHIDRA)
GHIDRA_NAMES_DBG=$(GHIDRA_NAMES) callgraph ifacedecomp ifaceterm interface
@ -136,7 +139,7 @@ LIBDECOMP_NAMES=$(CORE) $(DECCORE) $(EXTRA) $(SLEIGH)
# object file macros
COMMANDLINE_DBG_OBJS=$(COMMANDLINE_NAMES:%=com_dbg/%.o)
COMMANDLINE_OPT_OBJS=$(COMMANDLINE_NAMES:%=com_opt/%.o)
TEST_DEBUG_OBJS=$(TEST_NAMES:%=test_dbg/%.o)
TEST_DEBUG_OBJS=$(TEST_NAMES:%=test_dbg/%.o) $(UNITTEST_STRIP:%=test_dbg/%.o)
GHIDRA_DBG_OBJS=$(GHIDRA_NAMES_DBG:%=ghi_dbg/%.o)
GHIDRA_OPT_OBJS=$(GHIDRA_NAMES:%=ghi_opt/%.o)
SLEIGH_DBG_OBJS=$(SLEIGH_NAMES:%=sla_dbg/%.o)
@ -214,7 +217,9 @@ com_dbg/%.o: %.cc
com_opt/%.o: %.cc
$(CXX) $(ARCH_TYPE) -c $(OPT_CXXFLAGS) $(ADDITIONAL_FLAGS) $(COMMANDLINE_OPT) $< -o $@
test_dbg/%.o: %.cc
$(CXX) $(ARCH_TYPE) -c $(OPT_CXXFLAGS) $(ADDITIONAL_FLAGS) $(TEST_DEBUG) $< -o $@
$(CXX) $(ARCH_TYPE) -c $(DBG_CXXFLAGS) $(ADDITIONAL_FLAGS) $(TEST_DEBUG) $< -o $@
test_dbg/%.o: ../unittests/%.cc
$(CXX) -I. $(ARCH_TYPE) -c $(DBG_CXXFLAGS) $(ADDITIONAL_FLAGS) $(TEST_DEBUG) $< -o $@
ghi_dbg/%.o: %.cc
$(CXX) $(ARCH_TYPE) -c $(DBG_CXXFLAGS) $(ADDITIONAL_FLAGS) $(GHIDRA_DEBUG) $< -o $@
ghi_opt/%.o: %.cc
@ -248,7 +253,7 @@ decomp_opt: $(COMMANDLINE_OPT_OBJS)
$(CXX) $(OPT_CXXFLAGS) $(ARCH_TYPE) -o decomp_opt $(COMMANDLINE_OPT_OBJS) $(BFDLIB) $(LNK)
ghidra_test_dbg: $(TEST_DEBUG_OBJS)
$(CXX) $(OPT_CXXFLAGS) $(ARCH_TYPE) -o ghidra_test_dbg $(TEST_DEBUG_OBJS) $(BFDLIB) $(LNK)
$(CXX) $(DBG_CXXFLAGS) $(ARCH_TYPE) -o ghidra_test_dbg $(TEST_DEBUG_OBJS) $(BFDLIB) $(LNK)
test: ghidra_test_dbg
./ghidra_test_dbg
@ -339,10 +344,10 @@ com_opt/depend: $(COMMANDLINE_NAMES:%=%.cc)
sed 's,\(.*\)\.o[ :]*,com_opt/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
test_dbg/depend: $(TEST_NAMES:%=%.cc)
test_dbg/depend: $(TEST_NAMES:%=%.cc) $(UNITTEST_NAMES:%=%.cc)
mkdir -p test_dbg
@set -e; rm -f $@; \
$(CXX) -MM $(TEST_DEBUG) $^ > $@.$$$$; \
$(CXX) -I. -MM $(TEST_DEBUG) $^ > $@.$$$$; \
sed 's,\(.*\)\.o[ :]*,test_dbg/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$

View file

@ -64,6 +64,20 @@ ArchitectureCapability *ArchitectureCapability::findCapability(Document *doc)
return (ArchitectureCapability *)0;
}
/// Return the ArchitectureCapability object with the matching name
/// \param name is the name to match
/// \return the ArchitectureCapability or null if no match is found
ArchitectureCapability *ArchitectureCapability::getCapability(const string &name)
{
for(int4 i=0;i<thelist.size();++i) {
ArchitectureCapability *res = thelist[i];
if (res->getName() == name)
return res;
}
return (ArchitectureCapability *)0;
}
/// Modify order that extensions are searched, to effect which gets a chance
/// to run first.
/// Right now all we need to do is make sure the raw architecture comes last

View file

@ -104,6 +104,7 @@ public:
static ArchitectureCapability *findCapability(const string &filename); ///< Find an extension to process a file
static ArchitectureCapability *findCapability(Document *doc); ///< Find an extension to process an XML document
static ArchitectureCapability *getCapability(const string &name); ///< Get a capability by name
static void sortCapabilities(void); ///< Sort extensions
static uint4 getMajorVersion(void) { return majorversion; } ///< Get \e major decompiler version
static uint4 getMinorVersion(void) { return minorversion; } ///< Get \e minor decompiler version

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// \file ifaceterm.hh
/// \brief Add some terminal capabilities to the command-line interface (IfaceStatus)

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// \file interface.hh
/// \brief Classes and utilities for a \e generic command-line interface

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// \file sleigh.hh
/// \brief Classes and utilities for the main SLEIGH engine

View file

@ -13,470 +13,81 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// \file test.cc
/// \brief Unit tests for Ghidra C++ components.
#include "float.hh"
#include "opbehavior.hh"
#include "test.hh"
#include "testfunction.hh"
#include <cmath>
#include <cstdint>
#include <cstring>
vector<UnitTest *> UnitTest::tests;
#include <limits>
#include <vector>
/// Run all the tests unless a non-empty set of names is passed in.
/// In which case, only the named tests in the set are run.
/// \param testnames is the set of names
void UnitTest::run(set<string> &testNames)
// utility functions
float floatFromRawBits(uintb e) {
float f;
memcpy(&f, &e, 4);
return f;
}
{
int total = 0;
int passed = 0;
uintb floatToRawBits(float f) {
uintb result = 0;
memcpy(&result, &f, 4);
return result;
}
double doubleFromRawBits(uintb e) {
double f;
memcpy(&f, &e, 8);
return f;
}
uintb doubleToRawBits(double f) {
uintb result = 0;
memcpy(&result, &f, 8);
return result;
}
// macros to preserve call site
#define ASSERT_FLOAT_ENCODING(f) \
do { \
FloatFormat format(4); \
\
uintb true_encoding = floatToRawBits(f); \
uintb encoding = format.getEncoding(f); \
\
ASSERT_EQUALS(true_encoding, encoding); \
} while (0);
#define ASSERT_DOUBLE_ENCODING(f) \
do { \
FloatFormat format(8); \
\
uintb true_encoding = doubleToRawBits(f); \
uintb encoding = format.getEncoding(f); \
\
ASSERT_EQUALS(true_encoding, encoding); \
} while (0);
//// FloatFormat tests
static std::vector<float> float_test_values{
-0.0f,
+0.0f,
-1.0f,
+1.0f,
-1.234f,
+1.234f,
-std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min(),
std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min(),
-std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::quiet_NaN(),
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::infinity()
};
static std::vector<int> int_test_values = {
0, -1, 1, 1234, -1234, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()
};
TEST(float_encoding_normal) {
ASSERT_FLOAT_ENCODING(1.234);
ASSERT_FLOAT_ENCODING(-1.234);
}
TEST(double_encoding_normal) {
ASSERT_DOUBLE_ENCODING(1.234);
ASSERT_DOUBLE_ENCODING(-1.234);
}
TEST(float_encoding_nan) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::quiet_NaN());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::quiet_NaN());
}
TEST(double_encoding_nan) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::quiet_NaN());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::quiet_NaN());
}
TEST(float_encoding_subnormal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::denorm_min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::denorm_min());
}
TEST(double_encoding_subnormal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::denorm_min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::denorm_min());
}
TEST(float_encoding_min_normal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::min());
}
TEST(double_encoding_min_normal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::min());
}
TEST(float_encoding_infinity) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::infinity());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::infinity());
}
TEST(double_encoding_infinity) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::infinity());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::infinity());
}
TEST(float_midpoint_rounding) {
FloatFormat ff(4);
// IEEE754 recommends "round to nearest even" for binary formats, like single and double
// precision floating point. It rounds to the nearest integer (significand) when unambiguous,
// and to the nearest even on the midpoint.
// There are 52 bits of significand in a double and 23 in a float.
// Below we construct a sequence of double precision values to demonstrate each case
// in rounding,
// d0 - zeros in low 29 bits, round down
// d1 - on the rounding midpoint with integer even integer part, round down
// d2 - just above the midpoint, round up
double d0 = doubleFromRawBits(0x4010000000000000L);
double d1 = doubleFromRawBits(0x4010000010000000L);
double d2 = doubleFromRawBits(0x4010000010000001L);
// d3 - zeros in low 29 bits, round down
// d4 - on the rounding midpoint with integer part odd, round up
// d5 - just above the midpoint, round up
double d3 = doubleFromRawBits(0x4010000020000000L);
double d4 = doubleFromRawBits(0x4010000030000000L);
double d5 = doubleFromRawBits(0x4010000030000001L);
float f0 = (float)d0;
float f1 = (float)d1;
float f2 = (float)d2;
float f3 = (float)d3;
float f4 = (float)d4;
float f5 = (float)d5;
uintb e0 = ff.getEncoding(d0);
uintb e1 = ff.getEncoding(d1);
uintb e2 = ff.getEncoding(d2);
uintb e3 = ff.getEncoding(d3);
uintb e4 = ff.getEncoding(d4);
uintb e5 = ff.getEncoding(d5);
ASSERT_EQUALS(floatToRawBits(f0), e0);
ASSERT_EQUALS(floatToRawBits(f1), e1);
ASSERT_EQUALS(floatToRawBits(f2), e2);
ASSERT_EQUALS(floatToRawBits(f3), e3);
ASSERT_EQUALS(floatToRawBits(f4), e4);
ASSERT_EQUALS(floatToRawBits(f5), e5);
ASSERT_EQUALS(e0, e1);
ASSERT_NOT_EQUALS(e1, e2);
ASSERT_NOT_EQUALS(e3, e4);
ASSERT_EQUALS(e4, e5);
}
// op tests
// generated
TEST(float_opNan) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = isnan(f);
uintb encoding = format.getEncoding(f);
uintb result = format.opNan(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opNeg) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(-f);
uintb encoding = format.getEncoding(f);
uintb result = format.opNeg(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opAbs) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(abs(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opAbs(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opSqrt) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(sqrtf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opSqrt(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opCeil) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(ceilf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opCeil(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opFloor) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(floorf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opFloor(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opRound) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(roundf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opRound(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opInt2Float_size4) {
FloatFormat format(4);
for(int i:int_test_values) {
uintb true_result = floatToRawBits((float)i);
uintb result = format.opInt2Float(i, 4);
ASSERT_EQUALS(true_result, result);
}
}
// TODO other sized ints
TEST(float_to_double_opFloat2Float) {
FloatFormat format(4);
FloatFormat format8(8);
for(float f:float_test_values) {
uintb true_result = doubleToRawBits((double)f);
uintb encoding = format.getEncoding(f);
uintb result = format.opFloat2Float(encoding, format8);
ASSERT_EQUALS(true_result, result);
}
}
// TODO float2float going the other direction, double_to_float_opFloat2Float
TEST(float_opTrunc_to_int) {
FloatFormat format(4);
FloatFormat format8(8);
for(float f:float_test_values) {
// avoid undefined behavior
if((int64_t)f > std::numeric_limits<int>::max() || (int64_t)f < std::numeric_limits<int>::min())
for(auto &t : UnitTest::tests) {
if (testNames.size() > 0 && testNames.find(t->name) == testNames.end()) {
continue;
uintb true_result = ((uintb)(int32_t)f) & 0xffffffff;
uintb encoding = format.getEncoding(f);
uintb result = format.opTrunc(encoding, 4);
ASSERT_EQUALS(true_result, result);
}
}
// TODO trunc to other sizes
TEST(float_opEqual) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1==f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
std::cerr << "testing : " << t->name << " ..." << std::endl;
++total;
try {
t->func();
++passed;
std::cerr << " passed." << std::endl;
} catch(...) {
}
}
std::cerr << "==============================" << std::endl;
std::cerr << passed << "/" << total << " tests passed." << std::endl;
}
int main(int argc, char **argv) {
bool runUnitTests = true;
bool runDataTests = true;
TEST(float_opNotEqual) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1!=f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opNotEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
argc -= 1;
argv += 1;
set<string> unitTestNames;
set<string> dataTestNames;
string dirname("../datatests");
if (argc > 0) {
string command(argv[0]);
if (command == "-path") {
dirname = argv[1];
runDataTests = true;
argv += 2;
argc -= 2;
}
}
if (argc > 0) {
string command(argv[0]);
if (command == "unittests") {
runUnitTests = true;
runDataTests = false; // Run only unit tests
unitTestNames.insert(argv + 1,argv + argc);
}
TEST(float_opLess) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1<f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opLess(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
else if (command == "datatests") {
runUnitTests = false; // Run only data-tests
runDataTests = true;
dataTestNames.insert(argv + 1,argv + argc);
}
else {
cout << "USAGE: ghidra_test [-path <datatestdir>] [[unittests|datatests] [testname1 testname2 ...]]" << endl;
}
}
}
TEST(float_opLessEqual) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1<=f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opLessEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
if (runUnitTests)
UnitTest::run(unitTestNames);
if (runDataTests) {
cout << endl << endl;
const char *sleighhomepath = getenv("SLEIGHHOME");
if (sleighhomepath != (const char *)0)
cout << "Using SLEIGHHOME=" << sleighhomepath << endl;
else
cout << "No SLEIGHHOME environment variable" << endl;
startDecompilerLibrary(sleighhomepath);
FunctionTestCollection::runTestCollections(dirname,dataTestNames);
}
}
}
TEST(float_opAdd) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1+f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opAdd(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opDiv) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1/f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opDiv(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opMult) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1*f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opMult(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opSub) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1-f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opSub(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
// end generated

View file

@ -27,32 +27,40 @@
/// }
///
#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
#include <string>
#include <iostream>
namespace {
struct Test;
typedef void (*testfunc_t)();
std::vector<Test *> tests;
/// \brief Simple unit test class
///
/// The macro TEST instantiates this object with a name and function pointer.
/// The static run() method calls all the function pointers of all instantiated
/// objects.
struct UnitTest {
static std::vector<UnitTest *> tests; ///< The collection of test objects
std::string name; ///< Name of the test
testfunc_t func; ///< Call-back function executing the test
struct Test {
std::string name;
testfunc_t func;
Test(const std::string &name, testfunc_t func) : name(name), func(func) {
/// \brief Constructor
///
/// \param name is the identifier for the test
/// \param func is a call-back function that executes the test
UnitTest(const std::string &name,testfunc_t func) :
name(name), func(func)
{
tests.push_back(this);
}
static void run(std::set<std::string> &testNames); ///< Run all the instantiated tests
};
} // namespace
#define TEST(testname) \
void testname(); \
Test testname##_obj{ #testname, testname }; \
UnitTest testname##_obj{ #testname, testname }; \
void testname()
#define ASSERT(test) \
@ -80,26 +88,3 @@ namespace {
<< " != " << ssb.str() << "\"." << std::endl; \
throw 0; \
}
int main(int argc, char **argv) {
int total = 0;
int passed = 0;
std::set<std::string> testnames(argv + 1, argv + argc);
for (auto &t : tests) {
if(testnames.size()>0 && testnames.find(t->name)==testnames.end()) {
continue;
}
std::cerr << "testing : " << t->name << " ..." << std::endl;
++total;
try {
t->func();
++passed;
std::cerr << " passed." << std::endl;
} catch (...) {
}
}
std::cerr << "==============================" << std::endl;
std::cerr << passed << "/" << total << " tests passed." << std::endl;
}

View file

@ -0,0 +1,345 @@
/* ###
* 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.
*/
#include "testfunction.hh"
#include "filemanage.hh"
void FunctionTestProperty::startTest(void) const
{
count = 0;
}
void FunctionTestProperty::processLine(const string &line) const
{
if (regex_search(line,pattern))
count += 1;
}
bool FunctionTestProperty::endTest(void) const
{
return (count >= minimumMatch && count <= maximumMatch);
}
void FunctionTestProperty::restoreXml(const Element *el)
{
name = el->getAttributeValue("name");
istringstream s1(el->getAttributeValue("min"));
s1 >> minimumMatch;
istringstream s2(el->getAttributeValue("max"));
s2 >> maximumMatch;
pattern = regex(el->getContent());
}
void ConsoleCommands::readLine(string &line)
{
if (pos >= commands.size()) {
line.clear();
return;
}
line = commands[pos];
pos += 1;
}
ConsoleCommands::ConsoleCommands(void) :
IfaceStatus("> ", cout)
{
pos = 0;
IfaceCapability::registerAllCommands(this);
}
void ConsoleCommands::reset(void)
{
commands.clear();
pos = 0;
inerror = false;
done = false;
}
/// \param el is the root \<script> tag
void ConsoleCommands::restoreXml(const Element *el)
{
const List &list(el->getChildren());
List::const_iterator iter;
for(iter=list.begin();iter!=list.end();++iter) {
const Element *subel = *iter;
commands.push_back(subel->getContent());
}
pos = 0;
}
void FunctionTestCollection::clear(void)
{
dcp->clearArchitecture();
testList.clear();
console.reset();
}
/// Instantiate an Architecture object
void FunctionTestCollection::buildProgram(DocumentStorage &docStorage)
{
ArchitectureCapability *capa = ArchitectureCapability::getCapability("xml");
if (capa == (ArchitectureCapability *)0)
throw IfaceExecutionError("Missing XML architecture capability");
dcp->conf = capa->buildArchitecture("test", "", console.optr);
string errmsg;
bool iserror = false;
try {
dcp->conf->init(docStorage);
dcp->conf->readLoaderSymbols("::"); // Read in loader symbols
} catch(XmlError &err) {
errmsg = err.explain;
iserror = true;
} catch(LowlevelError &err) {
errmsg = err.explain;
iserror = true;
}
if (iserror)
throw IfaceExecutionError("Error during architecture initialization: " + errmsg);
}
/// Let each test initialize itself thru its startTest() method
void FunctionTestCollection::startTests(void) const
{
list<FunctionTestProperty>::const_iterator iter;
for(iter=testList.begin();iter!=testList.end();++iter) {
(*iter).startTest();
}
}
/// Each test gets a chance to process a line of output
/// \param line is the given line of output
void FunctionTestCollection::passLineToTests(const string &line) const
{
list<FunctionTestProperty>::const_iterator iter;
for(iter=testList.begin();iter!=testList.end();++iter) {
(*iter).processLine(line);
}
}
/// \brief Do the final evaluation of each test
///
/// This is called after each test has been fed all lines of output.
/// The result of each test is printed to the \e midStream, and then
/// failures are written to the lateStream in order to see a summary.
/// \param midStream is the stream write results to as the test is performed
/// \param lateStream collects failures to display as a summary
void FunctionTestCollection::evaluateTests(ostream &midStream,list<string> &lateStream) const
{
list<FunctionTestProperty>::const_iterator iter;
for(iter=testList.begin();iter!=testList.end();++iter) {
numTestsApplied += 1;
if ((*iter).endTest()) {
midStream << "Success -- " << (*iter).getName() << endl;
numTestsSucceeded += 1;
}
else {
midStream << "FAIL -- " << (*iter).getName() << endl;
lateStream.push_back((*iter).getName());
}
}
}
FunctionTestCollection::FunctionTestCollection(void)
{
dcp = (IfaceDecompData *)console.getData("decompile");
console.setErrorIsDone(true);
numTestsApplied = 0;
numTestsSucceeded = 0;
}
/// Load the architecture based on the discovered \<binaryimage> tag.
/// Collect the script commands and the specific tests.
/// \param filename is the XML file holding the test data
void FunctionTestCollection::loadTest(const string &filename)
{
fileName = filename;
DocumentStorage docStorage;
Document *doc = docStorage.openDocument(filename);
Element *el = doc->getRoot();
if (el->getName() == "decompilertest")
restoreXml(docStorage,el);
else if (el->getName() == "binaryimage")
restoreXmlOldForm(docStorage,el);
else
throw IfaceParseError("Test file " + filename + " has unrecognized XML tag: "+el->getName());
}
void FunctionTestCollection::restoreXml(DocumentStorage &store,const Element *el)
{
clear();
const List &list(el->getChildren());
List::const_iterator iter = list.begin();
bool sawScript = false;
bool sawTests = false;
bool sawProgram = false;
while(iter != list.end()) {
const Element *subel = *iter;
++iter;
if (subel->getName() == "script") {
sawScript = true;
console.restoreXml(subel);
}
else if (subel->getName() == "stringmatch") {
sawTests = true;
testList.emplace_back();
testList.back().restoreXml(subel);
}
else if (subel->getName() == "binaryimage") {
sawProgram = true;
store.registerTag(subel);
buildProgram(store);
}
else
throw IfaceParseError("Unknown tag in <decompiletest>: "+subel->getName());
}
if (!sawScript)
throw IfaceParseError("Did not see <script> tag in <decompiletest>");
if (!sawTests)
throw IfaceParseError("Did not see any <stringmatch> tags in <decompiletest>");
if (!sawProgram)
throw IfaceParseError("No <binaryimage> tag in <decompiletest>");
}
/// Pull the script and tests from a comment in \<binaryimage>
void FunctionTestCollection::restoreXmlOldForm(DocumentStorage &store,const Element *el)
{
clear();
throw IfaceParseError("Old format test not supported");
}
/// Run the script commands on the current program.
/// Collect any bulk output, and run tests over the output.
/// Report test failures back to the caller
/// \param midStream is the output stream to write to during the test
/// \param lateStream collects messages for a final summary
void FunctionTestCollection::runTests(ostream &midStream,list<string> &lateStream)
{
numTestsApplied = 0;
numTestsSucceeded = 0;
ostringstream midBuffer; // Collect command console output
console.optr = &midBuffer;
ostringstream bulkout;
console.fileoptr = &bulkout;
mainloop(&console);
console.optr = &midStream;
console.fileoptr = &midStream;
if (console.isInError()) {
midStream << "Error: Did not apply tests in " << fileName << endl;
midStream << midBuffer.str() << endl;
ostringstream fs;
fs << "Execution failed for " << fileName;
lateStream.push_back(fs.str());
return;
}
string result = bulkout.str();
if (result.size() == 0) {
ostringstream fs;
fs << "No output for " << fileName;
lateStream.push_back(fs.str());
return;
}
startTests();
string::size_type prevpos = 0;
string::size_type pos = result.find_first_of('\n');
while(pos != string::npos) {
string line = result.substr(prevpos,pos - prevpos);
passLineToTests(line);
prevpos = pos + 1;
pos = result.find_first_of('\n',prevpos);
}
if (prevpos != result.size()) {
string line = result.substr(prevpos); // Process final line without a newline char
passLineToTests(line);
}
evaluateTests(midStream, lateStream);
}
/// Run through all XML files in the given directory, processing each in turn.
/// \param dirname is a directory containing the XML test files
/// \param testNames (if not empty) specifies particular tests to run
void FunctionTestCollection::runTestCollections(const string &dirname,set<string> &testNames)
{
FileManage fileManage;
set<string> fullNames;
for(set<string>::iterator iter=testNames.begin();iter!=testNames.end();++iter) {
string val = dirname;
if (dirname.back() != '/')
val += '/';
val += *iter;
fullNames.insert(val);
}
fileManage.addDir2Path(dirname);
vector<string> testFiles;
fileManage.matchList(testFiles,".xml",true);
int4 totalTestsApplied = 0;
int4 totalTestsSucceeded = 0;
list<string> failures;
FunctionTestCollection testCollection;
for(int4 i=0;i<testFiles.size();++i) {
if (!fullNames.empty() && fullNames.find(testFiles[i]) == fullNames.end())
continue;
try {
testCollection.loadTest(testFiles[i]);
testCollection.runTests(cout, failures);
totalTestsApplied += testCollection.getTestsApplied();
totalTestsSucceeded += testCollection.getTestsSucceeded();
} catch(IfaceParseError &err) {
ostringstream fs;
fs << "Error parsing " << testFiles[i] << ": " << err.explain;
cout << fs.str() << endl;
failures.push_back(fs.str());
} catch(IfaceExecutionError &err) {
ostringstream fs;
fs << "Error executing " << testFiles[i] << ": " << err.explain;
cout << fs.str() << endl;
failures.push_back(fs.str());
}
}
cout << endl;
cout << "Total tests applied = " << totalTestsApplied << endl;
cout << "Total passing tests = " << totalTestsSucceeded << endl;
cout << endl;
if (!failures.empty()) {
cout << "Failures: " << endl;
list<string>::const_iterator iter = failures.begin();
for(int4 i=0;i<10;++i) {
cout << " " << *iter << endl;
++iter;
if (iter == failures.end()) break;
}
}
}

View file

@ -0,0 +1,88 @@
/* ###
* 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.
*/
/// \file testfunction.hh
/// \brief Framework for decompiler data driven single function tests
#ifndef __TESTFUNCTION__
#define __TESTFUNCTION__
#include "libdecomp.hh"
#include <iostream>
#include <regex>
/// \brief A single property to be searched for in the output of a function decompilation
///
/// This is generally a regular expression run over the characters in the
/// decompiled "source" form of the function.
/// The property may "match" more than once or not at all.
class FunctionTestProperty {
int4 minimumMatch; ///< Minimum number of times property is expected to match
int4 maximumMatch; ///< Maximum number of times property is expected to match
string name; ///< Name of the test, to be printed in test summaries
regex pattern; ///< Regular expression to match against a line of output
mutable uint4 count; ///< Number of times regular expression has been seen
public:
string getName(void) const { return name; } ///< Get the name of the property
void startTest(void) const; ///< Reset "state", counting number of matching lines
void processLine(const string &line) const; ///< Search thru \e line, update state if match found
bool endTest(void) const; ///< Return results of property search
void restoreXml(const Element *el); ///< Reconstruct the property from an XML tag
};
/// \brief A console command run as part of a test sequence
class ConsoleCommands : public IfaceStatus {
vector<string> commands; ///< Sequence of commands
uint4 pos; ///< Position of next command to execute
virtual void readLine(string &line);
public:
ConsoleCommands(void); ///< Constructor
void reset(void); ///< Reset console for a new program
virtual bool isStreamFinished(void) const { return pos == commands.size(); }
void restoreXml(const Element *el); ///< Reconstruct the command from an XML tag
};
/// \brief A collection of tests around a single program/function
///
/// The collection of tests is loaded from a single XML file via loadTest(),
/// and the tests are run by calling runTests().
/// An entire program is loaded and possibly annotated by a series of
/// console command lines. Decompiler output is also triggered by a command,
/// and then the output is scanned for by the test objects (FunctionTestProperty).
/// Results of passed/failed tests are collected. If the command line script
/// does not complete properly, this is considered a special kind of failure.
class FunctionTestCollection {
IfaceDecompData *dcp; ///< Program data for the test collection
string fileName; ///< Name of the file containing test data
list<FunctionTestProperty> testList; ///< List of tests for this collection
ConsoleCommands console; ///< Decompiler console for executing scripts
mutable int4 numTestsApplied; ///< Count of tests that were executed
mutable int4 numTestsSucceeded; ///< Count of tests that passed
void clear(void); ///< Clear any previous architecture and function
void buildProgram(DocumentStorage &store); ///< Build program (Architecture) from \<binaryimage> tag
void startTests(void) const; ///< Initialize each FunctionTestProperty
void passLineToTests(const string &line) const; ///< Let all tests analyze a line of the results
void evaluateTests(ostream &midStream,list<string> &lateStream) const;
public:
FunctionTestCollection(void); ///< Constructor
int4 getTestsApplied(void) const { return numTestsApplied; } ///< Get the number of tests executed
int4 getTestsSucceeded(void) const { return numTestsSucceeded; } ///< Get the number of tests that passed
void loadTest(const string &filename); ///< Load a test program, tests, and script
void restoreXml(DocumentStorage &store,const Element *el); ///< Load tests from a \<decompilertest> tag.
void restoreXmlOldForm(DocumentStorage &store,const Element *el); ///< Load tests from \<binaryimage> tag.
void runTests(ostream &midStream,list<string> &lateStream); ///< Run the script and perform the tests
static void runTestCollections(const string &dirname,set<string> &testNames); ///< Run test files in a whole directory
};
#endif

View file

@ -0,0 +1,30 @@
<decompilertest>
<binaryimage arch="MIPS:LE:32:default:default">
<!--
Contrived example of a LOAD off of a pointer that eventually
collapses to an address in a volatile region. The value returned
by the LOAD is unused. This test makes sure the side-effects of
the volatile read are still present.
-->
<bytechunk space="ram" offset="0xbfc05558" readonly="true">
c1bf053c5cdfa58c
e8ffbd2701a0023c5491428c1400bfaf
120001240000a090
8618f00f00000000
0800e0038001bd27
</bytechunk>
<bytechunk space="ram" offset="0xbfc0df5c" readonly="true">
00100000
</bytechunk>
<symbol space="ram" offset="0xbfc05558" name="deadvolatile"/>
</binaryimage>
<script>
<com>option readonly on</com>
<com>volatile [ram,0x1000,16]</com>
<com>lo fu deadvolatile</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Dead Volatile #1" min="1" max="1">read_volatile.*0x1000</stringmatch>
</decompilertest>

View file

@ -0,0 +1,70 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A contrived function with different float and double encodings.
-->
<bytechunk space="ram" offset="0x4004c7" readonly="true">
554889e5f30f100539
030000f30f1105550b2000f30f10052d
030000f30f1105490b2000f30f100521
030000f30f11053d0b2000f30f100515
030000f30f1105310b2000f30f100509
030000660fefc9f30f5ec1f30f11051d
0b2000660fefc0660fefc9f30f5ec1f3
0f11050d0b2000f30f1005e1020000f3
0f1105010b2000f20f1005d9020000f2
0f1105f90a2000f20f1005d1020000f2
0f1105f10a2000f20f1005c9020000f2
0f1105e90a2000f20f1005c1020000f2
0f1105e10a2000f20f1005b902000066
0fefc9f20f5ec1f20f1105d10a200066
0fefc0660fefc9f20f5ec1f20f1105c5
0a2000f20f100595020000f20f1105bd
0a2000905dc3
</bytechunk>
<bytechunk space="ram" offset="0x40080c" readonly="true">
abaaaa3e
000000406f1283babd3786350000803f
24d4523600000000555555555555e53f
0000000000000040fca9f1d24d6250bf
bbbdd7d9df7cdb3d000000000000f03f
7e7480d3845aca3e
</bytechunk>
<symbol space="ram" offset="0x4004c7" name="establish"/>
</binaryimage>
<script>
<com>map addr r0x601030 float4 floatv1</com>
<com>map addr r0x601034 float4 floatv2</com>
<com>map addr r0x601038 float4 floatv3</com>
<com>map addr r0x60103c float4 floatv4</com>
<com>map addr r0x601040 float4 floatv5</com>
<com>map addr r0x601044 float4 floatv6</com>
<com>map addr r0x601048 float4 floatv7</com>
<com>map addr r0x601050 float8 double1</com>
<com>map addr r0x601058 float8 double2</com>
<com>map addr r0x601060 float8 double3</com>
<com>map addr r0x601068 float8 double4</com>
<com>map addr r0x601070 float8 double5</com>
<com>map addr r0x601078 float8 double6</com>
<com>map addr r0x601080 float8 double7</com>
<com>option readonly on</com>
<com>lo fu establish</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Float print #1" min="1" max="1">floatv1 = 0.3333333;</stringmatch>
<stringmatch name="Float print #2" min="1" max="1">floatv2 = 2.0;</stringmatch>
<stringmatch name="Float print #3" min="1" max="1">floatv3 = -0.001;</stringmatch>
<stringmatch name="Float print #4" min="1" max="1">floatv4 = 1e-06;</stringmatch>
<stringmatch name="Float print #5" min="1" max="1">floatv5 = INFINITY;</stringmatch>
<stringmatch name="Float print #6" min="1" max="1">floatv6 = -NAN;</stringmatch>
<stringmatch name="Float print #7" min="1" max="1">floatv7 = 3.141592e-06;</stringmatch>
<stringmatch name="Float print #8" min="1" max="1">double1 = 0.6666666666666666;</stringmatch>
<stringmatch name="Float print #9" min="1" max="1">double2 = 2.0;</stringmatch>
<stringmatch name="Float print #10" min="1" max="1">double3 = -0.001;</stringmatch>
<stringmatch name="Float print #11" min="1" max="1">double4 = 1e-10;</stringmatch>
<stringmatch name="Float print #12" min="1" max="1">double5 = INFINITY;</stringmatch>
<stringmatch name="Float print #13" min="1" max="1">double6 = -NAN;</stringmatch>
<stringmatch name="Float print #14" min="1" max="1">double7 = 3.141592653589793e-06;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,22 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Basic for-loop. Contrived example.
-->
<bytechunk space="ram" offset="0x400517" readonly="true">
554889e5534883ec18
897decbb00000000eb0dbf20084000e8
fcfeffff83c3013b5dec7cee904883c4
185b5dc3
</bytechunk>
<symbol space="ram" offset="0x400517" name="forloop1"/>
</binaryimage>
<script>
<com>lo fu forloop1</com>
<com>parse line extern void forloop1(int4 max);</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop #1" min="1" max="1">for.*iVar1 = 0; iVar1 &lt; max; iVar1 = iVar1 \+ 1</stringmatch>match>
</decompilertest>

View file

@ -0,0 +1,31 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
For-loop where the iterator statement LOADs the next value. Contrived example.
-->
<bytechunk space="ram" offset="0x4005fb" readonly="true">
554889e548
83ec2048897de8c745fc00000000eb10
488b45e8488b4018488945e88345fc01
48837de80075e98b45fc89c6bf440840
00b800000000e805feffff90c9c3
</bytechunk>
<bytechunk space="ram" offset="0x400844" readonly="true">
436f756e74203d2025640a00
</bytechunk>
<symbol space="ram" offset="0x4005fb" name="forloop_loaditer"/>
<symbol space="ram" offset="0x400440" name="printf"/>
</binaryimage>
<script>
<com>parse line extern void printf(char *,...);</com>
<com>parse line extern void forloop_loaditer(int4 **ptr);</com>
<com>lo fu forloop_loaditer</com>
<com>map addr s0xfffffffffffffff4 int4 count</com>
<com>map addr s0xffffffffffffffe0 int4 **loopvar</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop iterator load #1" min="1" max="1">for \(loopvar = ptr; loopvar != \(int4 \*\*\)0x0; loopvar = \(int4 \*\*\)loopvar\[3\]\)</stringmatch>
<stringmatch name="For-loop iterator load #2" min="1" max="1">count = count \+ 1;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,30 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
For loop where the increment statement must cross special operations. Contrived example.
-->
<bytechunk space="ram" offset="0x4005bb" readonly="true">
554889e553
4883ec18897dec488975e0bb03000000
eb1c83c301488b45e0c7000a00000048
8345e004bf38084000e842feffff3b5d
ec7cdf904883c4185b5dc3
</bytechunk>
<bytechunk space="ram" offset="0x400838" readonly="true">
4d616b6520612063616c6c00
</bytechunk>
<symbol space="ram" offset="0x4005bb" name="forloop_thruspecial"/>
<symbol space="ram" offset="0x400430" name="puts"/>
</binaryimage>
<script>
<com>parse line extern void puts(char *);</com>
<com>parse line extern void forloop_thruspecial(int4 max,int4 *ptr);</com>
<com>lo fu forloop_thruspecial</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop thru special #1" min="1" max="1">for.*iVar1 = 3; iVar1 &lt; max; iVar1 = iVar1 \+ 1</stringmatch>
<stringmatch name="For-loop thru special #2" min="1" max="1">\*piStack.* = 10;</stringmatch>
<stringmatch name="For-loop thru special #3" min="1" max="1">puts.*Make a call.*;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,31 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
For-loop where the loop variable is used elsewhere in the loop body. Contrived example.
-->
<bytechunk space="ram" offset="0x400544" readonly="true">
554889e5534883ec18897dec
bb00000000eb2e89d883e00385c07511
bf26084000b800000000e8d1feffffeb
1189debf2e084000b800000000e8befe
ffff83c3013b5dec7ccd904883c4185b
5dc3
</bytechunk>
<bytechunk space="ram" offset="0x400826" readonly="true">
30206d6f6420340076616c203d2025640a00
</bytechunk>
<symbol space="ram" offset="0x400544" name="forloop_loopvarused"/>
<symbol space="ram" offset="0x400440" name="printf"/>
</binaryimage>
<script>
<com>parse line extern void printf(char *,...);</com>
<com>parse line extern void forloop_loopvarused(int4 max);</com>
<com>lo fu forloop_loopvarused</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop var used #1" min="1" max="1">for.*uVar1 = 0;.*uVar1 &lt; max; uVar1 = uVar1 \+ 1</stringmatch>
<stringmatch name="For-loop var used #2" min="1" max="1">if \(\(uVar1 &amp; 3\) == 0\)</stringmatch>
<stringmatch name="For-loop var used #3" min="1" max="1">val = %d.*uVar1</stringmatch>
</decompilertest>

View file

@ -0,0 +1,24 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
For-loop where the loop var may be additionally incremented in the loop body. Contrived example.
-->
<bytechunk space="ram" offset="0x400592" readonly="true">
554889e553897df4bb00000000eb
0c837df40a7e0383c30183c3018b057d
0a200039c37cea905b5dc3
</bytechunk>
<symbol space="ram" offset="0x400592" name="forloop_withskip"/>
</binaryimage>
<script>
<com>map addr r0x601030 int4 globvar</com>
<com>parse line extern void forloop_withskip(int4 val);</com>
<com>lo fu forloop_withskip</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="For-loop with skip #1" min="1" max="1">for.*iVar1 = 0; iVar1 &lt; globvar; iVar1 = iVar1 \+ 1</stringmatch>
<stringmatch name="For-loop with skip #2" min="1" max="1">if \(10 &lt; val\)</stringmatch>
<stringmatch name="For-loop with skip #3" min="1" max="1">iVar1 = iVar1 \+ 1;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,47 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A contrived function with different loops, where we put comments on instructions
comprising the loop conditions. The comments should get moved to a separate line
and not get printed in the middle of the condition.
-->
<bytechunk space="ram" offset="0x1006e4" readonly="true">
554889e54883ec20897dec89
75e88955e4894de08b45ec83f8097f1c
837de8647e168b45e43de8030000750c
488d3d0d020000e864feffffc745fc00
000000eb28488d3d04020000e84ffeff
ff488d45ec4889c7e88dffffff488d45
e04889c7e881ffffff8345fc018b45e0
3dc70000007f378b45ec83f8137f2f81
7dfcc70000007ebdeb24488d3dc80100
00e80afeffff488d45ec4889c7e848ff
ffff488d45e04889c7e83cffffff8b45
ec83f8630f9ec28b45e083f80a0f9fc0
21d084c075c48b45ec83c00a8945ec48
8d3d8e010000e8c5fdffff8b45ec89c6
488d3d85010000b800000000e8bffdff
ff488d45ec4889c7e8edfeffff488d45
e44889c7e8e1feffff8b45ec3dcf0700
007f088b45e483f81d7eab8b45ec89c6
488d3d49010000b800000000e87ffdff
ff90c9c3
</bytechunk>
<symbol space="ram" offset="0x1006e4" name="loopcomment"/>
</binaryimage>
<script>
<com>lo fu loopcomment</com>
<com>comment instr r0x100704 ifcomment</com>
<com>comment instr r0x10075d forcomment</com>
<com>comment instr r0x100791 whilecomment</com>
<com>comment instr r0x1007f9 dowhilecomment</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Loop comment #1" min="1" max="1">^ */\* ifcomment \*/ *$</stringmatch>
<stringmatch name="Loop comment #2" min="1" max="1">^ */\* forcomment \*/ *$</stringmatch>
<stringmatch name="Loop comment #3" min="1" max="1">^ */\* whilecomment \*/ *$</stringmatch>
<stringmatch name="Loop comment #4" min="1" max="1">^ */\* dowhilecomment \*/ *$</stringmatch>
<stringmatch name="Loop comment #5" min="4" max="4">.*/\*</stringmatch>
</decompilertest>

View file

@ -0,0 +1,29 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A routine that uses multiple symbols with the same name. The decompiler should
detect the name collisions and append appropriate namespace tokens.
-->
<bytechunk space="ram" offset="0x400537" readonly="true">
554889e5897dfc8b45
fc83c0018905e60a20008b55fc89d0c1
e00201d001c08905d80a20008b15d60a
20008b45fc01d05dc3
</bytechunk>
<symbol space="ram" offset="0x400537" name="a::b::assign_vals"/>
</binaryimage>
<script>
<com>map addr r0x601030 int4 spam</com>
<com>map addr r0x601034 int4 a::spam</com>
<com>map addr r0x601038 int4 c::spam</com>
<com>parse line extern int4 a::b::assign_vals(int4 spam);</com>
<com>lo fu a::b::assign_vals</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Namespace #1" min="1" max="1">a::b::assign_vals\(int4 spam\)</stringmatch>
<stringmatch name="Namespace #2" min="1" max="1">^ ::spam = spam \+ 1;</stringmatch>
<stringmatch name="Namespace #3" min="1" max="1">^ a::spam = spam \* 10;</stringmatch>
<stringmatch name="Namespace #4" min="1" max="1">return spam \+ c::spam;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,25 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Hand coded routine where the offset into a structure to reach an array field
is contained inside the index expression for the array. The decompiler must
distribute the constant multiple scaling the array index to "see" the offset.
-->
<bytechunk space="ram" offset="0x400517" readonly="true">
554889e5488d441602
488d04878b005dc3
</bytechunk>
<symbol space="ram" offset="0x400517" name="readstruct"/>
</binaryimage>
<script>
<com>parse line struct twostruct { int4 field1; int4 array[5]; };</com>
<com>parse line extern int4 readstruct(twostruct *ptr,int8 a,int8 b);</com>
<com>lo fu readstruct</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Nested offset #1" min="1" max="1">return ptr-&gt;array\[b \+ a\];</stringmatch>
<stringmatch name="Nested offset #2" min="0" max="0">field</stringmatch>
<stringmatch name="Nested offset #3" min="0" max="0">\* 4</stringmatch>
</decompilertest>

View file

@ -0,0 +1,35 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A loop that should not recover as a for-loop as the loop variable is potentially aliased after the iterate statement.
Contrived example.
-->
<bytechunk space="ram" offset="0x400690" readonly="true">
554889e54883ec20897decc745f00100
0000c745f400000000c745f802000000
c745fc03000000eb298b45f483c00189
45f4488d45f04889c7e8adffffff8b45
f489c6bf5d084000b800000000e85efd
ffff8b45f43945ec7fcf90c9c3
</bytechunk>
<bytechunk space="ram" offset="0x40085d" readonly="true">
56616c203d2025640a00
</bytechunk>
<symbol space="ram" offset="0x400690" name="noforloop_alias"/>
<symbol space="ram" offset="0x400440" name="printf"/>
<symbol space="ram" offset="0x40067b" name="might_change"/>
</binaryimage>
<script>
<com>parse line extern void printf(char *,...);</com>
<com>parse line extern void noforloop_alias(int4 max);</com>
<com>lo fu noforloop_alias</com>
<com>map addr s0xffffffffffffffe8 int4 i[4]</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="No for-loop alias #1" min="1" max="1">while \(i\[1\] &lt; max\)</stringmatch>
<stringmatch name="No for-loop alias #2" min="1" max="1">i\[1\] = i\[1\] \+ 1;</stringmatch>
<stringmatch name="No for-loop alias #3" min="4" max="4">i\[[0-3]\] = [0-3];</stringmatch>
<stringmatch name="No for-loop alias #4" min="1" max="1">might_change\(i\);</stringmatch>
</decompilertest>

View file

@ -0,0 +1,27 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A loop that should not recover as a for-loop as the loop variable is global and may change
in a subfunction after the iteration statement.
-->
<bytechunk space="ram" offset="0x4006ed" readonly="true">
554889
e54883ec0848897df8c7052d09200000
000000eb1b8b052509200083c0018905
1c092000488b45f84889c7e85bffffff
8b050a09200083f8097eda90c9c3
</bytechunk>
<symbol space="ram" offset="0x4006ed" name="noforloop_globcall"/>
</binaryimage>
<script>
<com>parse line extern void noforloop_globcall(int4 *ptr);</com>
<com>map addr r0x601030 int4 globvar</com>
<com>lo fu noforloop_globcall</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="No for-loop global call #1" min="1" max="1">while \(globvar &lt; 10\)</stringmatch>
<stringmatch name="No for-loop global call #2" min="1" max="1">globvar = 0;</stringmatch>
<stringmatch name="No for-loop global call #3" min="1" max="1">globvar = globvar \+ 1;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,32 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
A loop that should not recover as a for-loop as the loop variable is used after the iterate statement. Contrived example.
-->
<bytechunk space="ram" offset="0x40063e" readonly="true">
5548
89e5534883ec18897decbb0a000000eb
1d89debf50084000b800000000e8defd
ffff83c3016bc3648905c20920003b5d
ec7cde904883c4185b5dc3
</bytechunk>
<bytechunk space="ram" offset="0x400850" readonly="true">
4265666f7265203d2025640a00
</bytechunk>
<symbol space="ram" offset="0x40063e" name="noforloop_iterused"/>
<symbol space="ram" offset="0x400440" name="printf"/>
</binaryimage>
<script>
<com>parse line extern void printf(char *,...);</com>
<com>parse line extern void noforloop_iterused(int4 max);</com>
<com>map addr r0x601030 int4 globvar</com>
<com>lo fu noforloop_iterused</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="No for-loop iterator used #1" min="1" max="1">while \(.*uVar1 &lt; max\)</stringmatch>
<stringmatch name="No for-loop iterator used #2" min="1" max="1">uVar1 = 10;</stringmatch>
<stringmatch name="No for-loop iterator used #3" min="1" max="1">uVar1 = uVar1 \+ 1;</stringmatch>
<stringmatch name="No for-loop iterator used #4" min="1" max="1">globvar = uVar1 \* 100;</stringmatch>
</decompilertest>

View file

@ -0,0 +1,27 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Contrived routine that indexes into an array as a subfield of a structure. The
original source code had a negative offset added to the index, which got folded into
the stack offset for the start of the array. The offset is easily confused with the
offset of the "firstfield" of the structure, but the decompiler should figure it out
because of the clear array indexing.
-->
<bytechunk space="ram" offset="0x100000" readonly="true">
534889fb4881ec900000004889e7e8ed
0f00008b049c4881c4900000005bc3
</bytechunk>
<symbol space="ram" offset="0x100000" name="access_array1"/>
<symbol space="ram" offset="0x101000" name="populate_mystruct"/>
</binaryimage>
<script>
<com>parse line struct mystruct { int4 firstfield; int4 array[32]; };</com>
<com>parse line extern void populate_mystruct(mystruct *ptr);</com>
<com>lo fu access_array1</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Offset array #1" min="1" max="1">return mStack.*array\[param_1 \+ -2\]</stringmatch>
<stringmatch name="Offset array #2" min="0" max="0">firstfield</stringmatch>
</decompilertest>

View file

@ -0,0 +1,20 @@
<decompilertest>
<binaryimage arch="x86:LE:32:default:gcc">
<bytechunk space="ram" offset="0x80662e0" readonly="true">
5589e58b55080fb60284c0750eeb17
</bytechunk>
<bytechunk space="ram" offset="0x80662f0" readonly="true">
0fb6420184c0740e83c20183e8303c09
76ee5d31c0c35db801000000c3
</bytechunk>
<symbol space="ram" offset="0x80662e0" name="promote_compare"/>
</binaryimage>
<script>
<com>lo fu promote_compare</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Promotion on compare #1" min="1" max="1">9.*uint1..cVar.*0x30</stringmatch>
<stringmatch name="Promotion on compare #2" min="1" max="1">uint1</stringmatch>
</decompilertest>

View file

@ -0,0 +1,24 @@
<decompilertest>
<binaryimage arch="68000:BE:32:MC68020:default">
<!--
Simple example of a read from a volatile region, where the "value" is not
used, but the read may have side-effects that the decompiler should see.
-->
<bytechunk space="ram" offset="0x484">
1028001e4200114000141140
000a114000141140000a1140000a4e73
</bytechunk>
<symbol space="ram" offset="0x484" name="iofunc"/>
</binaryimage>
<script>
<com>volatile [ram,0x210000,64]</com>
<com>map addr r0x210000 int1 NVRAM[32]</com>
<com>set track A0 0x210000 r0x484 r0x485</com>
<com>lo fu iofunc</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Read Volatile #1" min="1" max="1">read_volatile.*NVRAM.*30</stringmatch>
<stringmatch name="Read Volatile #2" min="5" max="5">write_volatile.*NVRAM</stringmatch>
</decompilertest>

View file

@ -0,0 +1,32 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Contrived example of reading and writing to a two dimensional array.
-->
<bytechunk space="ram" offset="0x400590" readonly="true">
554889e5897dec8975e8c745fc140000
00eb758b05b73f2c008b55ec4863ca48
988b55fc4863d24869f0d00700004889
d048c1e0024801d04801c04801f04801
c88b0485601060008945f88b057f3f2c
008b55e84863ca48988b55fc4863d248
69f0d00700004889d048c1e0024801d0
4801c04801f0488d14088b45f8890495
601060008345fc01837dfc1d7e858b45
f85dc3
</bytechunk>
<symbol space="ram" offset="0x400590" name="array_access"/>
</binaryimage>
<script>
<com>map addr r0x6c4560 int4 globindex</com>
<com>map addr r0x601060 int4 myarray[100][200][10]</com>
<com>parse line extern int4 array_access(int4 valin,int4 valout);</com>
<com>lo fu array_access</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Three dimension #1" min="1" max="1">iStack16 = myarray\[globindex\]\[iStack12\]\[valin\];</stringmatch>
<stringmatch name="Three dimension #2" min="1" max="1">myarray\[globindex\]\[iStack12\]\[valout\] = iStack16;</stringmatch>
<stringmatch name="Three dimension #3" min="0" max="0"> \* </stringmatch>
</decompilertest>

View file

@ -0,0 +1,29 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Contrived example of reading and writing to a two dimensional array.
-->
<bytechunk space="ram" offset="0x400568" readonly="true">
554889e5897dec89
75e88b05240b20008b55ec4863ca4863
d04889d048c1e0024801d04801c88b04
85601060008945fc8b05fe0a20008b55
fc8d4a0a8b55e84863f24863d04889d0
48c1e0024801d04801f0890c85601060
008b45fc5dc3
</bytechunk>
<symbol space="ram" offset="0x400568" name="array_access"/>
</binaryimage>
<script>
<com>map addr r0x60109c int4 globindex</com>
<com>map addr r0x601060 int4 myarray[3][5]</com>
<com>parse line extern int4 array_access(int4 valin,int4 valout);</com>
<com>lo fu array_access</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Two dimension #1" min="1" max="1">iVar. = myarray\[globindex\]\[valin\];</stringmatch>
<stringmatch name="Two dimension #2" min="1" max="1">myarray\[globindex\]\[valout\] = iVar. \+ 10;</stringmatch>
<stringmatch name="Two dimension #3" min="0" max="0"> \* </stringmatch>
</decompilertest>

View file

@ -0,0 +1,27 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Contrived routine that indexes into an array as a subfield of a structure. The
original source code had a negative offset added to the index, which got folded into
the stack offset for the start of the array. The offset pulls the base reference
entirely out of the mapped structure, so the decompiler must search forward in
the local map to find it.
-->
<bytechunk space="ram" offset="0x100000" readonly="true">
534889fb4881ec900000004889e7e8ed
0f00008b449cf84881c4900000005bc3
</bytechunk>
<symbol space="ram" offset="0x100000" name="access_array1"/>
<symbol space="ram" offset="0x101000" name="populate_mystruct"/>
</binaryimage>
<script>
<com>parse line struct mystruct { int8 firstfield; int4 array[32]; };</com>
<com>parse line extern void populate_mystruct(mystruct *ptr);</com>
<com>lo fu access_array1</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Wayoff array #1" min="1" max="1">return mStack.*array\[param_1 \+ -4\]</stringmatch>
<stringmatch name="Wayoff array #2" min="0" max="0">firstfield</stringmatch>
</decompilertest>

View file

@ -0,0 +1,482 @@
/* ###
* 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.
*/
/// \file test.cc
/// \brief Unit tests for Ghidra C++ components.
#include "float.hh"
#include "opbehavior.hh"
#include "test.hh"
#include <cmath>
#include <cstdint>
#include <cstring>
#include <limits>
#include <vector>
// utility functions
float floatFromRawBits(uintb e) {
float f;
memcpy(&f, &e, 4);
return f;
}
uintb floatToRawBits(float f) {
uintb result = 0;
memcpy(&result, &f, 4);
return result;
}
double doubleFromRawBits(uintb e) {
double f;
memcpy(&f, &e, 8);
return f;
}
uintb doubleToRawBits(double f) {
uintb result = 0;
memcpy(&result, &f, 8);
return result;
}
// macros to preserve call site
#define ASSERT_FLOAT_ENCODING(f) \
do { \
FloatFormat format(4); \
\
uintb true_encoding = floatToRawBits(f); \
uintb encoding = format.getEncoding(f); \
\
ASSERT_EQUALS(true_encoding, encoding); \
} while (0);
#define ASSERT_DOUBLE_ENCODING(f) \
do { \
FloatFormat format(8); \
\
uintb true_encoding = doubleToRawBits(f); \
uintb encoding = format.getEncoding(f); \
\
ASSERT_EQUALS(true_encoding, encoding); \
} while (0);
//// FloatFormat tests
static std::vector<float> float_test_values{
-0.0f,
+0.0f,
-1.0f,
+1.0f,
-1.234f,
+1.234f,
-std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min(),
std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min(),
-std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::quiet_NaN(),
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::infinity()
};
static std::vector<int> int_test_values = {
0, -1, 1, 1234, -1234, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()
};
TEST(float_encoding_normal) {
ASSERT_FLOAT_ENCODING(1.234);
ASSERT_FLOAT_ENCODING(-1.234);
}
TEST(double_encoding_normal) {
ASSERT_DOUBLE_ENCODING(1.234);
ASSERT_DOUBLE_ENCODING(-1.234);
}
TEST(float_encoding_nan) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::quiet_NaN());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::quiet_NaN());
}
TEST(double_encoding_nan) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::quiet_NaN());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::quiet_NaN());
}
TEST(float_encoding_subnormal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::denorm_min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::denorm_min());
}
TEST(double_encoding_subnormal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::denorm_min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::denorm_min());
}
TEST(float_encoding_min_normal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::min());
}
TEST(double_encoding_min_normal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::min());
}
TEST(float_encoding_infinity) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::infinity());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::infinity());
}
TEST(double_encoding_infinity) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::infinity());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::infinity());
}
TEST(float_midpoint_rounding) {
FloatFormat ff(4);
// IEEE754 recommends "round to nearest even" for binary formats, like single and double
// precision floating point. It rounds to the nearest integer (significand) when unambiguous,
// and to the nearest even on the midpoint.
// There are 52 bits of significand in a double and 23 in a float.
// Below we construct a sequence of double precision values to demonstrate each case
// in rounding,
// d0 - zeros in low 29 bits, round down
// d1 - on the rounding midpoint with integer even integer part, round down
// d2 - just above the midpoint, round up
double d0 = doubleFromRawBits(0x4010000000000000L);
double d1 = doubleFromRawBits(0x4010000010000000L);
double d2 = doubleFromRawBits(0x4010000010000001L);
// d3 - zeros in low 29 bits, round down
// d4 - on the rounding midpoint with integer part odd, round up
// d5 - just above the midpoint, round up
double d3 = doubleFromRawBits(0x4010000020000000L);
double d4 = doubleFromRawBits(0x4010000030000000L);
double d5 = doubleFromRawBits(0x4010000030000001L);
float f0 = (float)d0;
float f1 = (float)d1;
float f2 = (float)d2;
float f3 = (float)d3;
float f4 = (float)d4;
float f5 = (float)d5;
uintb e0 = ff.getEncoding(d0);
uintb e1 = ff.getEncoding(d1);
uintb e2 = ff.getEncoding(d2);
uintb e3 = ff.getEncoding(d3);
uintb e4 = ff.getEncoding(d4);
uintb e5 = ff.getEncoding(d5);
ASSERT_EQUALS(floatToRawBits(f0), e0);
ASSERT_EQUALS(floatToRawBits(f1), e1);
ASSERT_EQUALS(floatToRawBits(f2), e2);
ASSERT_EQUALS(floatToRawBits(f3), e3);
ASSERT_EQUALS(floatToRawBits(f4), e4);
ASSERT_EQUALS(floatToRawBits(f5), e5);
ASSERT_EQUALS(e0, e1);
ASSERT_NOT_EQUALS(e1, e2);
ASSERT_NOT_EQUALS(e3, e4);
ASSERT_EQUALS(e4, e5);
}
// op tests
// generated
TEST(float_opNan) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = isnan(f);
uintb encoding = format.getEncoding(f);
uintb result = format.opNan(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opNeg) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(-f);
uintb encoding = format.getEncoding(f);
uintb result = format.opNeg(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opAbs) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(abs(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opAbs(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opSqrt) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(sqrtf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opSqrt(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opCeil) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(ceilf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opCeil(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opFloor) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(floorf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opFloor(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opRound) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(roundf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opRound(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opInt2Float_size4) {
FloatFormat format(4);
for(int i:int_test_values) {
uintb true_result = floatToRawBits((float)i);
uintb result = format.opInt2Float(i, 4);
ASSERT_EQUALS(true_result, result);
}
}
// TODO other sized ints
TEST(float_to_double_opFloat2Float) {
FloatFormat format(4);
FloatFormat format8(8);
for(float f:float_test_values) {
uintb true_result = doubleToRawBits((double)f);
uintb encoding = format.getEncoding(f);
uintb result = format.opFloat2Float(encoding, format8);
ASSERT_EQUALS(true_result, result);
}
}
// TODO float2float going the other direction, double_to_float_opFloat2Float
TEST(float_opTrunc_to_int) {
FloatFormat format(4);
FloatFormat format8(8);
for(float f:float_test_values) {
// avoid undefined behavior
if((int64_t)f > std::numeric_limits<int>::max() || (int64_t)f < std::numeric_limits<int>::min())
continue;
uintb true_result = ((uintb)(int32_t)f) & 0xffffffff;
uintb encoding = format.getEncoding(f);
uintb result = format.opTrunc(encoding, 4);
ASSERT_EQUALS(true_result, result);
}
}
// TODO trunc to other sizes
TEST(float_opEqual) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1==f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opNotEqual) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1!=f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opNotEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opLess) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1<f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opLess(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opLessEqual) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1<=f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opLessEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opAdd) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1+f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opAdd(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opDiv) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1/f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opDiv(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opMult) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1*f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opMult(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opSub) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1-f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opSub(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
// end generated