diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile b/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile index e806893116..9c0cfaedbe 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/Makefile @@ -94,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 testfunction +SPECIAL=consolemain sleighexample test # Any additional modules for the command line decompiler EXTRA= $(filter-out $(CORE) $(DECCORE) $(SLEIGH) $(GHIDRA) $(SLACOMP) $(SPECIAL),$(ALL_NAMES)) @@ -117,7 +117,7 @@ COMMANDLINE_NAMES=$(CORE) $(DECCORE) $(EXTRA) $(SLEIGH) consolemain COMMANDLINE_DEBUG=-DCPUI_DEBUG -D__TERMINAL__ COMMANDLINE_OPT=-D__TERMINAL__ -TEST_NAMES=$(CORE) $(DECCORE) $(SLEIGH) $(EXTRA) testfunction test +TEST_NAMES=$(CORE) $(DECCORE) $(SLEIGH) $(EXTRA) test TEST_DEBUG=-D__TERMINAL__ GHIDRA_NAMES=$(CORE) $(DECCORE) $(GHIDRA) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc index 2363a94b02..6eab4572ed 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/database.cc @@ -1688,6 +1688,7 @@ Symbol *Scope::addConvertSymbol(uint4 format,uintb value,Address &addr,uint8 has Symbol *sym; sym = new EquateSymbol(owner,"",format,value); + addSymbolInternal(sym); RangeList rnglist; if (!addr.isInvalid()) rnglist.insertRange(addr.getSpace(),addr.getOffset(),addr.getOffset()); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc index 215b15a4bf..a24ebaf589 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.cc @@ -130,6 +130,9 @@ void IfaceDecompCapability::registerCommands(IfaceStatus *status) status->registerCom(new IfcPreferSplit(),"prefersplit"); status->registerCom(new IfcStructureBlocks(),"structure","blocks"); status->registerCom(new IfcAnalyzeRange(), "analyze","range"); + status->registerCom(new IfcLoadTestFile(), "load","test","file"); + status->registerCom(new IfcListTestCommands(), "list","test","commands"); + status->registerCom(new IfcExecuteTestCommand(), "execute","test","command"); #ifdef CPUI_RULECOMPILE status->registerCom(new IfcParseRule(),"parse","rule"); status->registerCom(new IfcExperimentalRules(),"experimental","rules"); @@ -218,6 +221,7 @@ IfaceDecompData::IfaceDecompData(void) conf = (Architecture *)0; fd = (Funcdata *)0; cgraph = (CallGraph *)0; + testCollection = (FunctionTestCollection *)0; #ifdef OPACTION_DEBUG jumptabledebug = false; #endif @@ -230,6 +234,8 @@ IfaceDecompData::~IfaceDecompData(void) delete cgraph; if (conf != (Architecture *)0) delete conf; + if (testCollection != (FunctionTestCollection *)0) + delete testCollection; // fd will get deleted with Database } @@ -3143,6 +3149,71 @@ void IfcAnalyzeRange::execute(istream &s) } } +/// \class IfcLoadTestFile +/// \brief Load a datatest environment file: `load test ` +/// +/// The program and associated script from a decompiler test file is loaded +void IfcLoadTestFile::execute(istream &s) + +{ + string filename; + + if (dcp->conf != (Architecture *)0) + throw IfaceExecutionError("Load image already present"); + s >> filename; + dcp->testCollection = new FunctionTestCollection(status); + dcp->testCollection->loadTest(filename); + *status->optr << filename << " test successfully loaded: " << dcp->conf->getDescription() << endl; +} + +/// \class IfaceListTestCommands +/// \brief List all the script commands in the current test: `list test commands` +void IfcListTestCommands::execute(istream &s) + +{ + if (dcp->testCollection == (FunctionTestCollection *)0) + throw IfaceExecutionError("No test file is loaded"); + for(int4 i=0;itestCollection->numCommands();++i) { + *status->optr << ' ' << dec << i+1 << ": " << dcp->testCollection->getCommand(i) << endl; + } +} + +/// \class IfcExecuteTestCommands +/// \brief Execute a specified range of the test script: `execute test command <#>-<#> +void IfcExecuteTestCommand::execute(istream &s) + +{ + if (dcp->testCollection == (FunctionTestCollection *)0) + throw IfaceExecutionError("No test file is loaded"); + int4 first = -1; + int4 last = -1; + char hyphen; + + s >> ws >> dec >> first; + first -= 1; + if (first < 0 || first > dcp->testCollection->numCommands()) + throw IfaceExecutionError("Command index out of bounds"); + s >> ws; + if (!s.eof()) { + s >> ws >> hyphen; + if (hyphen != '-') + throw IfaceExecutionError("Missing hyphenated command range"); + s >> ws >> last; + last -= 1; + if (last < 0 || last < first || last > dcp->testCollection->numCommands()) + throw IfaceExecutionError("Command index out of bounds"); + } + else { + last = first; + } + ostringstream s1; + for(int4 i=first;i<=last;++i) { + s1 << dcp->testCollection->getCommand(i) << endl; + } + istringstream *s2 = new istringstream(s1.str()); + status->pushScript(s2, "test> "); +} + #ifdef OPACTION_DEBUG void IfcDebugAction::execute(istream &s) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh index d68da85044..9d71cfcc26 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifacedecomp.hh @@ -19,11 +19,11 @@ #ifndef __IFACE_DECOMP__ #define __IFACE_DECOMP__ -#include "ifaceterm.hh" #include "graph.hh" #include "grammar.hh" #include "callgraph.hh" #include "paramid.hh" +#include "testfunction.hh" #ifdef CPUI_RULECOMPILE #include "rulecompile.hh" #endif @@ -44,6 +44,7 @@ public: Funcdata *fd; ///< Current function active in the console Architecture *conf; ///< Current architecture/program active in the console CallGraph *cgraph; ///< Call-graph information for the program + FunctionTestCollection *testCollection; ///< Executable environment from a datatest map prototypePieces; void storePrototypePieces( Funcdata *fd_in, PrototypePieces pp_in ) { prototypePieces.insert(pair(fd_in,pp_in)); } @@ -568,6 +569,21 @@ public: virtual void execute(istream &s); }; +class IfcLoadTestFile : public IfaceDecompCommand { +public: + virtual void execute(istream &s); +}; + +class IfcListTestCommands : public IfaceDecompCommand { +public: + virtual void execute(istream &s); +}; + +class IfcExecuteTestCommand : public IfaceDecompCommand { +public: + virtual void execute(istream &s); +}; + #ifdef CPUI_RULECOMPILE class IfcParseRule : public IfaceDecompCommand { public: diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.cc index 41e8ab718d..7cba748208 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.cc @@ -237,15 +237,12 @@ void IfaceTerm::readLine(string &line) } while(val != '\n'); } -void IfaceTerm::pushScript(const string &filename,const string &newprompt) +void IfaceTerm::pushScript(istream *iptr,const string &newprompt) { - ifstream *s = new ifstream(filename.c_str()); - if (!*s) - throw IfaceParseError("Unable to open script file"); inputstack.push_back(sptr); - sptr = s; - IfaceStatus::pushScript(filename,newprompt); + sptr = iptr; + IfaceStatus::pushScript(iptr,newprompt); } void IfaceTerm::popScript(void) @@ -254,6 +251,7 @@ void IfaceTerm::popScript(void) delete sptr; sptr = inputstack.back(); inputstack.pop_back(); + IfaceStatus::popScript(); } bool IfaceTerm::isStreamFinished(void) const diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.hh index 3c1eb0092d..3ca912dd6b 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ifaceterm.hh @@ -44,7 +44,7 @@ class IfaceTerm : public IfaceStatus { public: IfaceTerm(const string &prmpt,istream &is,ostream &os); ///< Constructor virtual ~IfaceTerm(void); - virtual void pushScript(const string &filename,const string &newprompt); + virtual void pushScript(istream *iptr,const string &newprompt); virtual void popScript(void); virtual bool isStreamFinished(void) const; }; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/interface.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/interface.cc index 9c5674b8f0..f0a98b6179 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/interface.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/interface.cc @@ -134,14 +134,27 @@ IfaceStatus::IfaceStatus(const string &prmpt,ostream &os,int4 mxhist) curhistory = 0; } -/// \brief Provide a new script file to execute, with an associated command prompt +/// \brief Push a new file on the script stack /// -/// The script provides a subsidiary input stream to the current stream. -/// Once commands from the script are complete, processing will resume on this stream. -/// \param filename is the name of the file containing the script -/// \param newprompt is the command line prompt +/// Attempt to open the file, and if we succeed put the open stream onto the script stack. +/// \param filename is the name of the script file +/// \param newprompt is the command line prompt to associate with the file void IfaceStatus::pushScript(const string &filename,const string &newprompt) +{ + ifstream *s = new ifstream(filename.c_str()); + if (!*s) + throw IfaceParseError("Unable to open script file"); +} + +/// \brief Provide a new input stream to execute, with an associated command prompt +/// +/// The new stream is added to a stack and becomes the primary source for parsing new commands. +/// Once commands from the stream are exhausted, parsing will resume in the previous stream. +/// \param iptr is the new input stream +/// \param newprompt is the command line prompt to associate with the new stream +void IfaceStatus::pushScript(istream *iptr,const string &newprompt) + { promptstack.push_back(prompt); uint4 flags = 0; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/interface.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/interface.hh index 7635a824b3..0771e74289 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/interface.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/interface.hh @@ -224,9 +224,10 @@ public: IfaceStatus(const string &prmpt,ostream &os,int4 mxhist=10); ///< Constructor virtual ~IfaceStatus(void); ///< Destructor void setErrorIsDone(bool val) { errorisdone = val; } ///< Set if processing should terminate on an error - virtual void pushScript(const string &filename,const string &newprompt); + void pushScript(const string &filename,const string &newprompt); + virtual void pushScript(istream *iptr,const string &newprompt); virtual void popScript(void); - void reset(void); ///< Pop any existing script streams and return to processing from the base stream + virtual void reset(void); ///< Pop any existing script streams and return to processing from the base stream int4 getNumInputStreamSize(void) const { return promptstack.size(); } ///< Get depth of script nesting void writePrompt(void) { *optr << prompt; } ///< Write the current command prompt to the current output stream void registerCom(IfaceCommand *fptr, const char *nm1, diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh_arch.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh_arch.cc index 038bc379e6..a8efac8c7e 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh_arch.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh_arch.cc @@ -16,8 +16,7 @@ #include "sleigh_arch.hh" #include "inject_sleigh.hh" -Sleigh *SleighArchitecture::last_sleigh = (Sleigh *)0; -int4 SleighArchitecture::last_languageindex; +map SleighArchitecture::translators; vector SleighArchitecture::description; FileManage SleighArchitecture::specpaths; // Global specfile manager @@ -138,25 +137,23 @@ string SleighArchitecture::getDescription(void) const bool SleighArchitecture::isTranslateReused(void) { - if (last_sleigh == (Sleigh *)0) return false; - if (last_languageindex == languageindex) return true; - delete last_sleigh; // It doesn't match so free old Translate - last_sleigh = (Sleigh *)0; - return false; + return (translators.find(languageindex) != translators.end()); } Translate *SleighArchitecture::buildTranslator(DocumentStorage &store) { // Build a sleigh translator - if (isTranslateReused()) { - last_sleigh->reset(loader,context); - return last_sleigh; - } - else { - last_sleigh = new Sleigh(loader,context); - last_languageindex = languageindex; - return last_sleigh; + map::const_iterator iter; + Sleigh *sleigh; + iter = translators.find(languageindex); + if (iter != translators.end()) { + sleigh = (*iter).second; + sleigh->reset(loader,context); + return sleigh; } + sleigh = new Sleigh(loader,context); + translators[languageindex] = sleigh; + return sleigh; } PcodeInjectLibrary *SleighArchitecture::buildPcodeInjectLibrary(void) @@ -463,9 +460,9 @@ void SleighArchitecture::scanForSleighDirectories(const string &rootpath) void SleighArchitecture::shutdown(void) { - if (last_sleigh != (Sleigh *)0) { - delete last_sleigh; - last_sleigh = (Sleigh *)0; - } + if (translators.empty()) return; // Already cleared + for(map::const_iterator iter=translators.begin();iter!=translators.end();++iter) + delete (*iter).second; + translators.clear(); // description.clear(); // static vector is destroyed by the normal exit handler } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh_arch.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh_arch.hh index 01d41fe077..84b55fd54b 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh_arch.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/sleigh_arch.hh @@ -87,8 +87,7 @@ public: /// Generally a \e language \e id (i.e. x86:LE:64:default) is provided, then this /// object is able to automatically load in configuration and construct the Translate object. class SleighArchitecture : public Architecture { - static Sleigh *last_sleigh; ///< Last Translate object used by a SleighArchitecture - static int4 last_languageindex; ///< Index of the LanguageDescription associated with the last Translate object + static map translators; ///< Map from language index to instantiated translators static vector description; ///< List of languages we know about int4 languageindex; ///< Index (within LanguageDescription array) of the active language string filename; ///< Name of active load-image file diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/test.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/test.cc index aad6686bc6..e81f2564aa 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/test.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/test.cc @@ -14,7 +14,7 @@ * limitations under the License. */ #include "test.hh" -#include "testfunction.hh" +#include "libdecomp.hh" vector UnitTest::tests; @@ -44,6 +44,37 @@ void UnitTest::run(set &testNames) std::cerr << passed << "/" << total << " tests passed." << std::endl; } +/// Create list of the absolute path of all tests to be run +/// \param dirname is a directory containing the XML test files +/// \param testNames (if not empty) specifies particular tests to run +/// \param testFiles will hold the resulting list of paths +void gatherDataTests(const string &dirname,set &testNames,vector &testFiles) + +{ + FileManage fileManage; + + set fullNames; + for(set::iterator iter=testNames.begin();iter!=testNames.end();++iter) { + string val = dirname; + if (dirname.back() != '/') + val += '/'; + val += *iter; + fullNames.insert(val); + } + fileManage.addDir2Path(dirname); + if (fullNames.empty()) { + fileManage.matchList(testFiles,".xml",true); // Take all test files + return; + } + vector allTestFiles; + fileManage.matchList(allTestFiles,".xml",true); + for(int4 i=0;i unitTestNames; set dataTestNames; string dirname("../datatests"); + string sleighdirname("../../../../../../.."); if (argc > 0) { string command(argv[0]); if (command == "-path") { @@ -61,6 +93,22 @@ int main(int argc, char **argv) { argv += 2; argc -= 2; } + else if (command == "-sleighpath") { + sleighdirname = argv[1]; + argv += 2; + argc -= 2; + } + else if (command == "-usesleighenv") { + const char *sleighhomepath = getenv("SLEIGHHOME"); + if (sleighhomepath != (const char *)0) { + cout << "Using SLEIGHHOME=" << sleighhomepath << endl; + sleighdirname = sleighhomepath; + } + else + cout << "No SLEIGHHOME environment variable" << endl; + argv += 1; + argc -= 1; + } } if (argc > 0) { string command(argv[0]); @@ -78,16 +126,13 @@ int main(int argc, char **argv) { cout << "USAGE: ghidra_test [-path ] [[unittests|datatests] [testname1 testname2 ...]]" << endl; } } + startDecompilerLibrary(sleighdirname.c_str()); if (runUnitTests) UnitTest::run(unitTestNames); if (runDataTests) { + vector testFiles; + gatherDataTests(dirname,dataTestNames,testFiles); 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); + FunctionTestCollection::runTestFiles(testFiles,cout); } } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc index a18e21ea91..66f8d3fa2e 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/testfunction.cc @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "testfunction.hh" -#include "filemanage.hh" +#include "ifacedecomp.hh" void FunctionTestProperty::startTest(void) const @@ -57,8 +56,10 @@ void ConsoleCommands::readLine(string &line) pos += 1; } -ConsoleCommands::ConsoleCommands(void) : - IfaceStatus("> ", cout) +/// \param s is the stream where command output is printed +/// \param comms is the list of commands to be issued +ConsoleCommands::ConsoleCommands(ostream &s,vector &comms) : + IfaceStatus("> ", s), commands(comms) { pos = 0; IfaceCapability::registerAllCommands(this); @@ -67,14 +68,22 @@ ConsoleCommands::ConsoleCommands(void) : void ConsoleCommands::reset(void) { - commands.clear(); pos = 0; inerror = false; done = false; } +void FunctionTestCollection::clear(void) + +{ + dcp->clearArchitecture(); + commands.clear(); + testList.clear(); + console->reset(); +} + /// \param el is the root \