mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
New combined decompiler testing framework
This commit is contained in:
parent
5d7a7c5291
commit
ab76cc6095
32 changed files with 1788 additions and 791 deletions
|
@ -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|
|
||||
|
|
|
@ -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>
|
|
@ -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 $@.$$$$
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
345
Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc
Normal file
345
Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
88
Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.hh
Normal file
88
Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.hh
Normal 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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 < max; iVar1 = iVar1 \+ 1</stringmatch>match>
|
||||
</decompilertest>
|
|
@ -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>
|
|
@ -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 < 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>
|
|
@ -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 < max; uVar1 = uVar1 \+ 1</stringmatch>
|
||||
<stringmatch name="For-loop var used #2" min="1" max="1">if \(\(uVar1 & 3\) == 0\)</stringmatch>
|
||||
<stringmatch name="For-loop var used #3" min="1" max="1">val = %d.*uVar1</stringmatch>
|
||||
</decompilertest>
|
|
@ -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 < globvar; iVar1 = iVar1 \+ 1</stringmatch>
|
||||
<stringmatch name="For-loop with skip #2" min="1" max="1">if \(10 < val\)</stringmatch>
|
||||
<stringmatch name="For-loop with skip #3" min="1" max="1">iVar1 = iVar1 \+ 1;</stringmatch>
|
||||
</decompilertest>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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->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>
|
|
@ -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\] < 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>
|
|
@ -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 < 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>
|
|
@ -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 < 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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue