mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
A bunch of doxygen fixes
This commit is contained in:
parent
8d2b737a72
commit
5d7a7c5291
27 changed files with 514 additions and 195 deletions
|
@ -457,7 +457,7 @@ RECURSIVE = NO
|
||||||
# excluded from the INPUT source files. This way you can easily exclude a
|
# excluded from the INPUT source files. This way you can easily exclude a
|
||||||
# subdirectory from a directory tree whose root is specified with the INPUT tag.
|
# subdirectory from a directory tree whose root is specified with the INPUT tag.
|
||||||
|
|
||||||
EXCLUDE = unify.hh unify.cc rulecompile.hh rulecompile.cc slgh_compile.hh slgh_compile.hh slghpattern.hh slghpattern.cc slghpatexpress.hh slghpatexpress.cc slghsymbol.hh slghsymbol.cc ifacedecomp.hh ifacedecomp.cc ifaceterm.hh ifaceterm.cc codedata.hh codedata.cc semantics.hh semantics.cc grammar.hh grammar.cc callgraph.hh callgraph.cc filemanage.hh filemanage.cc graph.hh graph.cc interface.hh interface.cc loadimage_bfd.hh loadimage_bfd.cc pcodecompile.cc pcodecompile.hh pcodeparse.hh pcodeparse.cc inject_sleigh.hh inject_sleigh.cc context.hh context.cc consolemain.cc sleighexample.cc xml.cc
|
EXCLUDE = unify.hh unify.cc rulecompile.hh rulecompile.cc slgh_compile.hh slgh_compile.cc slghparse.cc slghparse.hh slghscan.cc slghpattern.hh slghpattern.cc slghpatexpress.hh slghpatexpress.cc slghsymbol.hh slghsymbol.cc ifacedecomp.hh ifacedecomp.cc codedata.hh codedata.cc semantics.hh semantics.cc grammar.hh grammar.cc callgraph.hh callgraph.cc filemanage.hh filemanage.cc graph.hh graph.cc loadimage_bfd.hh loadimage_bfd.cc pcodecompile.cc pcodecompile.hh pcodeparse.hh pcodeparse.cc inject_sleigh.hh inject_sleigh.cc context.hh context.cc consolemain.cc sleighexample.cc xml.cc double.hh double.cc paramid.hh paramid.cc prefersplit.hh prefersplit.cc
|
||||||
|
|
||||||
# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
|
# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
|
||||||
# directories that are symbolic links (a Unix filesystem feature) are excluded
|
# directories that are symbolic links (a Unix filesystem feature) are excluded
|
||||||
|
|
|
@ -597,8 +597,8 @@ class BlockWhileDo : public BlockGraph {
|
||||||
bool testIterateForm(void) const; ///< Return \b false if the iterate statement is of an unacceptable form
|
bool testIterateForm(void) const; ///< Return \b false if the iterate statement is of an unacceptable form
|
||||||
public:
|
public:
|
||||||
BlockWhileDo(void) { initializeOp = (PcodeOp *)0; iterateOp = (PcodeOp *)0; loopDef = (PcodeOp *)0; } ///< Constructor
|
BlockWhileDo(void) { initializeOp = (PcodeOp *)0; iterateOp = (PcodeOp *)0; loopDef = (PcodeOp *)0; } ///< Constructor
|
||||||
PcodeOp *getInitializeOp(void) const { return initializeOp; }
|
PcodeOp *getInitializeOp(void) const { return initializeOp; } ///< Get root of initialize statement or null
|
||||||
PcodeOp *getIterateOp(void) const { return iterateOp; }
|
PcodeOp *getIterateOp(void) const { return iterateOp; } ///< Get root of iterate statement or null
|
||||||
bool hasOverflowSyntax(void) const { return ((getFlags() & f_whiledo_overflow)!=0); } ///< Does \b this require overflow syntax
|
bool hasOverflowSyntax(void) const { return ((getFlags() & f_whiledo_overflow)!=0); } ///< Does \b this require overflow syntax
|
||||||
void setOverflowSyntax(void) { setFlag(f_whiledo_overflow); } ///< Set that \b this requires overflow syntax
|
void setOverflowSyntax(void) { setFlag(f_whiledo_overflow); } ///< Set that \b this requires overflow syntax
|
||||||
virtual block_type getType(void) const { return t_whiledo; }
|
virtual block_type getType(void) const { return t_whiledo; }
|
||||||
|
|
|
@ -3477,9 +3477,10 @@ bool ActionDeadCode::isEventualConstant(Varnode *vn,int4 addCount,int4 loadCount
|
||||||
/// \brief Check if there are any unconsumed LOADs that may be from volatile addresses.
|
/// \brief Check if there are any unconsumed LOADs that may be from volatile addresses.
|
||||||
///
|
///
|
||||||
/// It may be too early to remove certain LOAD operations even though their result isn't
|
/// It may be too early to remove certain LOAD operations even though their result isn't
|
||||||
/// consumed because it be of a volatile address with side effects. If a LOAD meets this
|
/// consumed because it may be of a volatile address with side effects. If a LOAD meets this
|
||||||
/// criteria, it is added to the worklist and \b true is returned.
|
/// criteria, it is added to the worklist and \b true is returned.
|
||||||
/// \param data is the function being analyzed
|
/// \param data is the function being analyzed
|
||||||
|
/// \param worklist is the container of consumed Varnodes to further process
|
||||||
/// \return \b true if there was at least one LOAD added to the worklist
|
/// \return \b true if there was at least one LOAD added to the worklist
|
||||||
bool ActionDeadCode::lastChanceLoad(Funcdata &data,vector<Varnode *> &worklist)
|
bool ActionDeadCode::lastChanceLoad(Funcdata &data,vector<Varnode *> &worklist)
|
||||||
|
|
||||||
|
|
|
@ -2817,7 +2817,7 @@ void Database::fillResolve(Scope *scope)
|
||||||
|
|
||||||
/// Initialize a new symbol table, with no initial scopes or symbols.
|
/// Initialize a new symbol table, with no initial scopes or symbols.
|
||||||
/// \param g is the Architecture that owns the symbol table
|
/// \param g is the Architecture that owns the symbol table
|
||||||
/// \param isByName is \b true if scope ids are calculated as a hash of the scope name.
|
/// \param idByName is \b true if scope ids are calculated as a hash of the scope name.
|
||||||
Database::Database(Architecture *g,bool idByName)
|
Database::Database(Architecture *g,bool idByName)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
/// \param nm is the (base) name of the function
|
/// \param nm is the (base) name of the function
|
||||||
/// \param scope is Symbol scope associated with the function
|
/// \param scope is Symbol scope associated with the function
|
||||||
/// \param addr is the entry address for the function
|
/// \param addr is the entry address for the function
|
||||||
|
/// \param sym is the symbol representing the function
|
||||||
/// \param sz is the number of bytes (of code) in the function body
|
/// \param sz is the number of bytes (of code) in the function body
|
||||||
Funcdata::Funcdata(const string &nm,Scope *scope,const Address &addr,FunctionSymbol *sym,int4 sz)
|
Funcdata::Funcdata(const string &nm,Scope *scope,const Address &addr,FunctionSymbol *sym,int4 sz)
|
||||||
: baseaddr(addr),
|
: baseaddr(addr),
|
||||||
|
|
|
@ -249,7 +249,6 @@ public:
|
||||||
int4 numCalls(void) const { return qlst.size(); } ///< Get the number of calls made by \b this function
|
int4 numCalls(void) const { return qlst.size(); } ///< Get the number of calls made by \b this function
|
||||||
FuncCallSpecs *getCallSpecs(int4 i) const { return qlst[i]; } ///< Get the i-th call specification
|
FuncCallSpecs *getCallSpecs(int4 i) const { return qlst[i]; } ///< Get the i-th call specification
|
||||||
FuncCallSpecs *getCallSpecs(const PcodeOp *op) const; ///< Get the call specification associated with a CALL op
|
FuncCallSpecs *getCallSpecs(const PcodeOp *op) const; ///< Get the call specification associated with a CALL op
|
||||||
void updateOpFromSpec(FuncCallSpecs *fc);
|
|
||||||
int4 fillinExtrapop(void); ///< Recover and return the \e extrapop for this function
|
int4 fillinExtrapop(void); ///< Recover and return the \e extrapop for this function
|
||||||
|
|
||||||
// Varnode routines
|
// Varnode routines
|
||||||
|
|
|
@ -291,7 +291,7 @@ void Funcdata::destroyVarnode(Varnode *vn)
|
||||||
|
|
||||||
/// Check if the given storage range is a potential laned register.
|
/// Check if the given storage range is a potential laned register.
|
||||||
/// If so, record the storage with the matching laned register record.
|
/// If so, record the storage with the matching laned register record.
|
||||||
/// \param s is the size of the storage range in bytes
|
/// \param size is the size of the storage range in bytes
|
||||||
/// \param addr is the starting address of the storage range
|
/// \param addr is the starting address of the storage range
|
||||||
void Funcdata::checkForLanedRegister(int4 size,const Address &addr)
|
void Funcdata::checkForLanedRegister(int4 size,const Address &addr)
|
||||||
|
|
||||||
|
|
|
@ -690,6 +690,18 @@ void ArchitectureGhidra::getBytes(uint1 *buf,int4 size,const Address &inaddr)
|
||||||
readResponseEnd(sin);
|
readResponseEnd(sin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Get string data at a specific address
|
||||||
|
///
|
||||||
|
/// The data is always returned as a sequence of bytes in UTF-8 format. The in-memory form of
|
||||||
|
/// the string may be different than UTF-8 but is always translated into UTF-8 by this method.
|
||||||
|
/// The caller can inform the in-memory format of the string by specifying a specific string
|
||||||
|
/// data-type. A maximum number of bytes to return is specified. If this is exceeded, a boolean
|
||||||
|
/// reference is set to \b true.
|
||||||
|
/// \param buffer will hold the string bytes in UTF-8 format
|
||||||
|
/// \param addr is program Address that holds the string data in memory
|
||||||
|
/// \param ct is string data-type expected
|
||||||
|
/// \param maxBytes is the maximum number of bytes to return
|
||||||
|
/// \param isTrunc is the boolean reference indicating whether the data is truncated
|
||||||
void ArchitectureGhidra::getStringData(vector<uint1> &buffer,const Address &addr,Datatype *ct,int4 maxBytes,bool &isTrunc)
|
void ArchitectureGhidra::getStringData(vector<uint1> &buffer,const Address &addr,Datatype *ct,int4 maxBytes,bool &isTrunc)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -127,7 +127,7 @@ public:
|
||||||
|
|
||||||
bool getSendParamMeasures(void) const { return sendParamMeasures; } ///< Get the current setting for emitting parameter info
|
bool getSendParamMeasures(void) const { return sendParamMeasures; } ///< Get the current setting for emitting parameter info
|
||||||
|
|
||||||
virtual void getStringData(vector<uint1> &buffer,const Address &addr,Datatype *ct,int4 maxBytes,bool &isTrunc);
|
void getStringData(vector<uint1> &buffer,const Address &addr,Datatype *ct,int4 maxBytes,bool &isTrunc);
|
||||||
virtual void printMessage(const string &message) const;
|
virtual void printMessage(const string &message) const;
|
||||||
|
|
||||||
static void segvHandler(int4 sig); ///< Handler for a segment violation (SIGSEGV) signal
|
static void segvHandler(int4 sig); ///< Handler for a segment violation (SIGSEGV) signal
|
||||||
|
|
|
@ -58,9 +58,16 @@ IfaceTerm::~IfaceTerm(void)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Respond to a TAB key press and try to 'complete' any existing tokens.
|
||||||
|
/// The method is handed the current state of the command-line in a string, and
|
||||||
|
/// it updates the command-line in place.
|
||||||
|
///
|
||||||
|
/// \param line is current command-line and will hold the final completion
|
||||||
|
/// \param cursor is the current position of the cursor
|
||||||
|
/// \return the (possibly new) position of the cursor, after completion
|
||||||
int4 IfaceTerm::doCompletion(string &line,int4 cursor)
|
int4 IfaceTerm::doCompletion(string &line,int4 cursor)
|
||||||
|
|
||||||
{ // Try to complete the current command
|
{
|
||||||
vector<string> fullcommand;
|
vector<string> fullcommand;
|
||||||
istringstream s(line);
|
istringstream s(line);
|
||||||
string tok;
|
string tok;
|
||||||
|
|
|
@ -13,7 +13,10 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
// Add some terminal capabilities to the command-line interface
|
|
||||||
|
/// \file ifaceterm.hh
|
||||||
|
/// \brief Add some terminal capabilities to the command-line interface (IfaceStatus)
|
||||||
|
|
||||||
#include "interface.hh"
|
#include "interface.hh"
|
||||||
|
|
||||||
#ifdef __TERMINAL__
|
#ifdef __TERMINAL__
|
||||||
|
@ -23,18 +26,24 @@ extern "C" {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// \brief Implement the command-line interface on top of a specific input stream
|
||||||
|
///
|
||||||
|
/// An initial input stream is provided as the base stream to parse for commands.
|
||||||
|
/// Additional input streams can be stacked by invoking scripts.
|
||||||
|
/// If the stream supports it, the stream parser recognizes special command-line editing
|
||||||
|
/// and completion keys.
|
||||||
class IfaceTerm : public IfaceStatus {
|
class IfaceTerm : public IfaceStatus {
|
||||||
#ifdef __TERMINAL__
|
#ifdef __TERMINAL__
|
||||||
bool is_terminal; // True if the input stream is a terminal
|
bool is_terminal; ///< True if the input stream is a terminal
|
||||||
int4 ifd; // Underlying file descriptor
|
int4 ifd; ///< Underlying file descriptor
|
||||||
struct termios itty; // Original terminal settings
|
struct termios itty; ///< Original terminal settings
|
||||||
#endif
|
#endif
|
||||||
istream *sptr; // Where to get input
|
istream *sptr; ///< The base input stream for the interface
|
||||||
vector<istream *> inputstack;
|
vector<istream *> inputstack; ///< Stack of nested input streams
|
||||||
int4 doCompletion(string &line,int4 cursor);
|
int4 doCompletion(string &line,int4 cursor); ///< 'Complete' the current command line
|
||||||
virtual void readLine(string &line);
|
virtual void readLine(string &line);
|
||||||
public:
|
public:
|
||||||
IfaceTerm(const string &prmpt,istream &is,ostream &os);
|
IfaceTerm(const string &prmpt,istream &is,ostream &os); ///< Constructor
|
||||||
virtual ~IfaceTerm(void);
|
virtual ~IfaceTerm(void);
|
||||||
virtual void pushScript(const string &filename,const string &newprompt);
|
virtual void pushScript(const string &filename,const string &newprompt);
|
||||||
virtual void popScript(void);
|
virtual void popScript(void);
|
||||||
|
|
|
@ -29,9 +29,12 @@ void IfaceCapability::initialize(void)
|
||||||
thelist.push_back(this);
|
thelist.push_back(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allow each capability to register its own commands
|
||||||
|
///
|
||||||
|
/// \param status is the command line interface to register commands with
|
||||||
void IfaceCapability::registerAllCommands(IfaceStatus *status)
|
void IfaceCapability::registerAllCommands(IfaceStatus *status)
|
||||||
|
|
||||||
{ // Allow each capability to register its own commands
|
{
|
||||||
for(uint4 i=0;i<thelist.size();++i)
|
for(uint4 i=0;i<thelist.size();++i)
|
||||||
thelist[i]->registerCommands(status);
|
thelist[i]->registerCommands(status);
|
||||||
}
|
}
|
||||||
|
@ -114,6 +117,9 @@ bool RemoteSocket::isSocketOpen(void)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// \param prmpt is the base command line prompt
|
||||||
|
/// \param os is the base stream to write output to
|
||||||
|
/// \param mxhist is the maximum number of lines to store in history
|
||||||
IfaceStatus::IfaceStatus(const string &prmpt,ostream &os,int4 mxhist)
|
IfaceStatus::IfaceStatus(const string &prmpt,ostream &os,int4 mxhist)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -128,9 +134,15 @@ IfaceStatus::IfaceStatus(const string &prmpt,ostream &os,int4 mxhist)
|
||||||
curhistory = 0;
|
curhistory = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Provide a new script file to execute, with an associated command prompt
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
void IfaceStatus::pushScript(const string &filename,const string &newprompt)
|
void IfaceStatus::pushScript(const string &filename,const string &newprompt)
|
||||||
|
|
||||||
{ // Push new input stream on stack (with new prompt)
|
{
|
||||||
promptstack.push_back(prompt);
|
promptstack.push_back(prompt);
|
||||||
uint4 flags = 0;
|
uint4 flags = 0;
|
||||||
if (errorisdone)
|
if (errorisdone)
|
||||||
|
@ -139,9 +151,13 @@ void IfaceStatus::pushScript(const string &filename,const string &newprompt)
|
||||||
prompt = newprompt;
|
prompt = newprompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Return to processing the parent stream
|
||||||
|
///
|
||||||
|
/// The current input stream, as established by a script, is popped from the stack,
|
||||||
|
/// along with its command prompt, and processing continues with the previous stream.
|
||||||
void IfaceStatus::popScript(void)
|
void IfaceStatus::popScript(void)
|
||||||
|
|
||||||
{ // Pop the current input stream (and current prompt)
|
{
|
||||||
prompt = promptstack.back();
|
prompt = promptstack.back();
|
||||||
promptstack.pop_back();
|
promptstack.pop_back();
|
||||||
uint4 flags = flagstack.back();
|
uint4 flags = flagstack.back();
|
||||||
|
@ -159,9 +175,11 @@ void IfaceStatus::reset(void)
|
||||||
done = false;
|
done = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The line is saved in a circular history buffer
|
||||||
|
/// \param line is the command line to save
|
||||||
void IfaceStatus::saveHistory(const string &line)
|
void IfaceStatus::saveHistory(const string &line)
|
||||||
|
|
||||||
{ // Save line in circular history buffer
|
{
|
||||||
if (history.size() < maxhistory)
|
if (history.size() < maxhistory)
|
||||||
history.push_back(line);
|
history.push_back(line);
|
||||||
else
|
else
|
||||||
|
@ -171,6 +189,10 @@ void IfaceStatus::saveHistory(const string &line)
|
||||||
curhistory = 0;
|
curhistory = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A command line is selected by specifying how many steps in time
|
||||||
|
/// to go back through the list of successful command lines.
|
||||||
|
/// \param line will hold the selected command line from history
|
||||||
|
/// \param i is the number of steps back to go
|
||||||
void IfaceStatus::getHistory(string &line,int4 i) const
|
void IfaceStatus::getHistory(string &line,int4 i) const
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -182,9 +204,10 @@ void IfaceStatus::getHistory(string &line,int4 i) const
|
||||||
line = history[i];
|
line = history[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The last command has failed, decide if we are completely abandoning this stream
|
||||||
void IfaceStatus::evaluateError(void)
|
void IfaceStatus::evaluateError(void)
|
||||||
|
|
||||||
{ // The last command has failed, decide if we are completely abandoning this stream
|
{
|
||||||
if (errorisdone) {
|
if (errorisdone) {
|
||||||
*optr << "Aborting process" << endl;
|
*optr << "Aborting process" << endl;
|
||||||
inerror = true;
|
inerror = true;
|
||||||
|
@ -199,6 +222,7 @@ void IfaceStatus::evaluateError(void)
|
||||||
inerror = false;
|
inerror = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Concatenate a list of tokens into a single string, separated by a space character
|
||||||
void IfaceStatus::wordsToString(string &res,const vector<string> &list)
|
void IfaceStatus::wordsToString(string &res,const vector<string> &list)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -229,13 +253,24 @@ IfaceStatus::~IfaceStatus(void)
|
||||||
delete (*iter).second;
|
delete (*iter).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Register a command with this interface
|
||||||
|
///
|
||||||
|
/// A command object is associated with one or more tokens on the command line.
|
||||||
|
/// A string containing up to 5 tokens can be associated with the command.
|
||||||
|
///
|
||||||
|
/// \param fptr is the IfaceCommand object
|
||||||
|
/// \param nm1 is the first token representing the command
|
||||||
|
/// \param nm2 is the second token (or null)
|
||||||
|
/// \param nm3 is the third token (or null)
|
||||||
|
/// \param nm4 is the fourth token (or null)
|
||||||
|
/// \param nm5 is the fifth token (or null)
|
||||||
void IfaceStatus::registerCom(IfaceCommand *fptr,const char *nm1,
|
void IfaceStatus::registerCom(IfaceCommand *fptr,const char *nm1,
|
||||||
const char *nm2,
|
const char *nm2,
|
||||||
const char *nm3,
|
const char *nm3,
|
||||||
const char *nm4,
|
const char *nm4,
|
||||||
const char *nm5)
|
const char *nm5)
|
||||||
|
|
||||||
{ // Register an interface command
|
{
|
||||||
fptr->addWord(nm1);
|
fptr->addWord(nm1);
|
||||||
if (nm2 != (const char *)0)
|
if (nm2 != (const char *)0)
|
||||||
fptr->addWord(nm2);
|
fptr->addWord(nm2);
|
||||||
|
@ -261,15 +296,24 @@ void IfaceStatus::registerCom(IfaceCommand *fptr,const char *nm1,
|
||||||
fptr->setData(this,data); // Inform command of its data
|
fptr->setData(this,data); // Inform command of its data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Commands (IfaceCommand) are associated with a particular module that has
|
||||||
|
/// a formal name and a data object associated with it. This method
|
||||||
|
/// retrieves the module specific data object by name.
|
||||||
|
/// \param nm is the name of the module
|
||||||
|
/// \return the IfaceData object or null
|
||||||
IfaceData *IfaceStatus::getData(const string &nm) const
|
IfaceData *IfaceStatus::getData(const string &nm) const
|
||||||
|
|
||||||
{ // Get data corresponding to the named module
|
{
|
||||||
map<string,IfaceData *>::const_iterator iter = datamap.find(nm);
|
map<string,IfaceData *>::const_iterator iter = datamap.find(nm);
|
||||||
if (iter == datamap.end())
|
if (iter == datamap.end())
|
||||||
return (IfaceData *)0;
|
return (IfaceData *)0;
|
||||||
return (*iter).second;
|
return (*iter).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single command line is read (via readLine) and executed.
|
||||||
|
/// If the command is successfully executed, the command line is
|
||||||
|
/// committed to history and \b true is returned.
|
||||||
|
/// \return \b true if a command successfully executes
|
||||||
bool IfaceStatus::runCommand(void)
|
bool IfaceStatus::runCommand(void)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -309,9 +353,16 @@ bool IfaceStatus::runCommand(void)
|
||||||
return true; // Indicate a command was executed
|
return true; // Indicate a command was executed
|
||||||
}
|
}
|
||||||
|
|
||||||
void IfaceStatus::restrict(vector<IfaceCommand *>::const_iterator &first,
|
/// \brief Restrict range of possible commands given a list of command line tokens
|
||||||
vector<IfaceCommand *>::const_iterator &last,
|
///
|
||||||
vector<string> &input)
|
/// Given a set of tokens partially describing a command, provide the most narrow
|
||||||
|
/// range of IfaceCommand objects that could be referred to.
|
||||||
|
/// \param first will hold an iterator to the first command in the range
|
||||||
|
/// \param last will hold an iterator (one after) the last command in the range
|
||||||
|
/// \param input is the list of command tokens to match on
|
||||||
|
void IfaceStatus::restrictCom(vector<IfaceCommand *>::const_iterator &first,
|
||||||
|
vector<IfaceCommand *>::const_iterator &last,
|
||||||
|
vector<string> &input)
|
||||||
|
|
||||||
{
|
{
|
||||||
vector<IfaceCommand *>::const_iterator newfirst,newlast;
|
vector<IfaceCommand *>::const_iterator newfirst,newlast;
|
||||||
|
@ -347,16 +398,22 @@ static bool maxmatch(string &res,const string &op1,const string &op2)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Expand tokens from the given input stream to a full command
|
||||||
|
///
|
||||||
|
/// A range of possible commands is returned. Processing of the stream
|
||||||
|
/// stops as soon as at least one complete command is recognized.
|
||||||
|
/// Tokens partially matching a command are expanded to the full command
|
||||||
|
/// and passed back.
|
||||||
|
/// \param expand will hold the list of expanded tokens
|
||||||
|
/// \param s is the input stream tokens are read from
|
||||||
|
/// \param first will hold the beginning of the matching range of commands
|
||||||
|
/// \param last will hold the end of the matching range of commands
|
||||||
|
/// \return the number of matching commands
|
||||||
int4 IfaceStatus::expandCom(vector<string> &expand,istream &s,
|
int4 IfaceStatus::expandCom(vector<string> &expand,istream &s,
|
||||||
vector<IfaceCommand *>::const_iterator &first,
|
vector<IfaceCommand *>::const_iterator &first,
|
||||||
vector<IfaceCommand *>::const_iterator &last)
|
vector<IfaceCommand *>::const_iterator &last)
|
||||||
|
|
||||||
{ // Expand tokens on stream to full command
|
{
|
||||||
// Return range of possible commands
|
|
||||||
// If command is complete with extra arguments
|
|
||||||
// return (dont process) remaining args
|
|
||||||
// Return number of matching commands
|
|
||||||
|
|
||||||
int4 pos; // Which word are we currently expanding
|
int4 pos; // Which word are we currently expanding
|
||||||
string tok;
|
string tok;
|
||||||
bool res;
|
bool res;
|
||||||
|
@ -386,7 +443,7 @@ int4 IfaceStatus::expandCom(vector<string> &expand,istream &s,
|
||||||
}
|
}
|
||||||
s >> tok; // Get next token
|
s >> tok; // Get next token
|
||||||
expand.push_back(tok);
|
expand.push_back(tok);
|
||||||
restrict(first,last,expand);
|
restrictCom(first,last,expand);
|
||||||
if (first == last) // If subrange is empty, return 0
|
if (first == last) // If subrange is empty, return 0
|
||||||
return 0;
|
return 0;
|
||||||
res = maxmatch(tok, (*first)->getCommandWord(pos), (*(last-1))->getCommandWord(pos));
|
res = maxmatch(tok, (*first)->getCommandWord(pos), (*(last-1))->getCommandWord(pos));
|
||||||
|
@ -403,9 +460,13 @@ void IfaceCommand::addWords(const vector<string> &wordlist)
|
||||||
com.push_back( *iter );
|
com.push_back( *iter );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The commands are ordered lexicographically and alphabetically by
|
||||||
|
/// the comparing tokens in their respective command line strings
|
||||||
|
/// \param op2 is the other command to compare with \b this
|
||||||
|
/// \return -1, 0, 1 if \b this is earlier, equal to, or after to the other command
|
||||||
int4 IfaceCommand::compare(const IfaceCommand &op2) const
|
int4 IfaceCommand::compare(const IfaceCommand &op2) const
|
||||||
|
|
||||||
{ // Sort command based on names
|
{
|
||||||
int4 res;
|
int4 res;
|
||||||
vector<string>::const_iterator iter1,iter2;
|
vector<string>::const_iterator iter1,iter2;
|
||||||
|
|
||||||
|
@ -424,12 +485,15 @@ int4 IfaceCommand::compare(const IfaceCommand &op2) const
|
||||||
return 0; // Never reaches here
|
return 0; // Never reaches here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \param res is overwritten with the full command line string
|
||||||
void IfaceCommand::commandString(string &res) const
|
void IfaceCommand::commandString(string &res) const
|
||||||
|
|
||||||
{
|
{
|
||||||
IfaceStatus::wordsToString(res,com);
|
IfaceStatus::wordsToString(res,com);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \class IfcQuit
|
||||||
|
/// \brief Quit command to terminate processing from the given interface
|
||||||
void IfcQuit::execute(istream &s)
|
void IfcQuit::execute(istream &s)
|
||||||
|
|
||||||
{ // Generic quit call back
|
{ // Generic quit call back
|
||||||
|
@ -439,6 +503,8 @@ void IfcQuit::execute(istream &s)
|
||||||
status->done = true; // Set flag to drop out of mainloop
|
status->done = true; // Set flag to drop out of mainloop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \class IfcHistory
|
||||||
|
/// \brief History command to list the most recent successful commands
|
||||||
void IfcHistory::execute(istream &s)
|
void IfcHistory::execute(istream &s)
|
||||||
|
|
||||||
{ // List most recent command lines
|
{ // List most recent command lines
|
||||||
|
@ -462,6 +528,8 @@ void IfcHistory::execute(istream &s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \class IfcOpenfile
|
||||||
|
/// \brief Open file command to redirect bulk output to a specific file stream
|
||||||
void IfcOpenfile::execute(istream &s)
|
void IfcOpenfile::execute(istream &s)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -482,6 +550,8 @@ void IfcOpenfile::execute(istream &s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \class IfcOpenfileAppend
|
||||||
|
/// \brief Open file command directing bulk output to be appended to a specific file
|
||||||
void IfcOpenfileAppend::execute(istream &s)
|
void IfcOpenfileAppend::execute(istream &s)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -502,6 +572,10 @@ void IfcOpenfileAppend::execute(istream &s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \class IfcClosefile
|
||||||
|
/// \brief Close command, closing the current bulk output file.
|
||||||
|
///
|
||||||
|
/// Subsequent bulk output is redirected to the basic interface output stream
|
||||||
void IfcClosefile::execute(istream &s)
|
void IfcClosefile::execute(istream &s)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -512,6 +586,8 @@ void IfcClosefile::execute(istream &s)
|
||||||
status->fileoptr = status->optr;
|
status->fileoptr = status->optr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \class IfcEcho
|
||||||
|
/// \brief Echo command to echo the current command line to the bulk output stream
|
||||||
void IfcEcho::execute(istream &s)
|
void IfcEcho::execute(istream &s)
|
||||||
|
|
||||||
{ // Echo command line to fileoptr
|
{ // Echo command line to fileoptr
|
||||||
|
|
|
@ -13,23 +13,9 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
// Very generic command line executor class: IfaceStatus
|
|
||||||
// A new class instance derived from IfaceCommand is attached to a command line via registerCom
|
|
||||||
// i.e.
|
|
||||||
// IfaceStatus stat(cin,cout);
|
|
||||||
// stat.registerCom(new IfcQuit(),"quit");
|
|
||||||
// stat.registerCom(new IfcOpenfileAppend(),"openfile","append");
|
|
||||||
// stat.mainloop();
|
|
||||||
|
|
||||||
// Command line processing is started with mainloop, which prints a
|
/// \file interface.hh
|
||||||
// prompt set with setprompt, allows bash style command line editing, including
|
/// \brief Classes and utilities for a \e generic command-line interface
|
||||||
// command completion and history, and executes the corresponding IfaceCommand.execute callback.
|
|
||||||
// Command words only have to match enough to disambiguate it from other commands.
|
|
||||||
|
|
||||||
// Custom history size can be passed in constructor to IfaceStatus.
|
|
||||||
// Applications should inherit from base class IfaceStatus in order
|
|
||||||
// to get custom data into IfaceCommand callbacks and to redefine
|
|
||||||
// the virtual function execute for custom error handling.
|
|
||||||
|
|
||||||
#ifndef __INTERFACE__
|
#ifndef __INTERFACE__
|
||||||
#define __INTERFACE__
|
#define __INTERFACE__
|
||||||
|
@ -70,43 +56,87 @@ public:
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// \brief An exception specific to the command line interface
|
||||||
struct IfaceError {
|
struct IfaceError {
|
||||||
string explain; // Explanatory string
|
string explain; ///< Explanatory string
|
||||||
IfaceError(const string &s) { explain = s; }
|
IfaceError(const string &s) { explain = s; } ///< Constructor
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief An exception describing a parsing error in a command line
|
||||||
|
///
|
||||||
|
/// Thrown when attempting to parse a command line. Options are missing or are in
|
||||||
|
/// the wrong form etc.
|
||||||
struct IfaceParseError : public IfaceError {
|
struct IfaceParseError : public IfaceError {
|
||||||
IfaceParseError(const string &s) : IfaceError(s) {}
|
IfaceParseError(const string &s) : IfaceError(s) {} ///< Constructor
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief An exception throw during the execution of a command
|
||||||
|
///
|
||||||
|
/// Processing of a specific command has started but has reached an error state
|
||||||
struct IfaceExecutionError : public IfaceError {
|
struct IfaceExecutionError : public IfaceError {
|
||||||
IfaceExecutionError(const string &s) : IfaceError(s) {}
|
IfaceExecutionError(const string &s) : IfaceError(s) {} ///< Constructor
|
||||||
};
|
};
|
||||||
|
|
||||||
class IfaceStatus; // Forward declaration
|
class IfaceStatus; // Forward declaration
|
||||||
|
|
||||||
class IfaceData { // Data specialized for a particular command
|
/// \brief Data specialized for a particular command module
|
||||||
|
///
|
||||||
|
/// IfaceCommands can have specialized data that is shared with other commands in
|
||||||
|
/// the same module. This is the root object for all such data.
|
||||||
|
class IfaceData {
|
||||||
public:
|
public:
|
||||||
virtual ~IfaceData(void) {}
|
virtual ~IfaceData(void) {} ///< Destructor
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief A command that can be executed from the command line
|
||||||
|
///
|
||||||
|
/// The command has data associated with it (via setData()) and is executed
|
||||||
|
/// via the execute() method. The command can get additional parameters from
|
||||||
|
/// the command line by reading the input stream passed to it.
|
||||||
|
/// The command is associated with a specific sequence of words (tokens)
|
||||||
|
/// that should appear at the start of the command line.
|
||||||
class IfaceCommand {
|
class IfaceCommand {
|
||||||
vector<string> com; // The command
|
vector<string> com; ///< The token sequence associated with the command
|
||||||
public:
|
public:
|
||||||
virtual ~IfaceCommand(void) {}
|
virtual ~IfaceCommand(void) {} ///< Destructor
|
||||||
|
|
||||||
|
/// \brief Associate a specific data object with this command.
|
||||||
|
///
|
||||||
|
/// \param root is the interface object this command is registered with
|
||||||
|
/// \param data is the data object the command should use
|
||||||
virtual void setData(IfaceStatus *root,IfaceData *data)=0;
|
virtual void setData(IfaceStatus *root,IfaceData *data)=0;
|
||||||
|
|
||||||
|
/// Execute this command. Additional state can be read from the given command line stream.
|
||||||
|
/// Otherwise, the command gets its data from its registered IfaceData object
|
||||||
|
/// \param s is the input stream from the command line
|
||||||
virtual void execute(istream &s)=0;
|
virtual void execute(istream &s)=0;
|
||||||
|
|
||||||
|
/// \brief Get the formal module name to which this command belongs
|
||||||
|
///
|
||||||
|
/// Commands in the same module share data through their registered IfaceData object
|
||||||
|
/// \return the formal module name
|
||||||
virtual string getModule(void) const=0;
|
virtual string getModule(void) const=0;
|
||||||
|
|
||||||
|
/// \brief Create a specialized data object for \b this command (and its module)
|
||||||
|
///
|
||||||
|
/// This method is only called once per module
|
||||||
|
/// \return the newly created data object for the module
|
||||||
virtual IfaceData *createData(void)=0;
|
virtual IfaceData *createData(void)=0;
|
||||||
|
|
||||||
|
/// \brief Add a token to the command line string associated with this command
|
||||||
|
///
|
||||||
|
/// \param temp is the new token to add
|
||||||
void addWord(const string &temp) { com.push_back(temp); }
|
void addWord(const string &temp) { com.push_back(temp); }
|
||||||
void removeWord(void) { com.pop_back(); }
|
|
||||||
const string &getCommandWord(int4 i) const { return com[i]; }
|
void removeWord(void) { com.pop_back(); } ///< Remove the last token from the associated command line string
|
||||||
void addWords(const vector<string> &wordlist);
|
const string &getCommandWord(int4 i) const { return com[i]; } ///< Get the i-th command token
|
||||||
int4 numWords(void) const { return com.size(); }
|
void addWords(const vector<string> &wordlist); ///< Add words to the associated command line string
|
||||||
void commandString(string &res) const;
|
int4 numWords(void) const { return com.size(); } ///< Return the number of tokens in the command line string
|
||||||
int4 compare(const IfaceCommand &op2) const;
|
void commandString(string &res) const; ///< Get the complete command line string
|
||||||
|
int4 compare(const IfaceCommand &op2) const; ///< Order two commands by their command line strings
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief A dummy command used during parsing
|
||||||
class IfaceCommandDummy : public IfaceCommand {
|
class IfaceCommandDummy : public IfaceCommand {
|
||||||
public:
|
public:
|
||||||
virtual void setData(IfaceStatus *root,IfaceData *data) {}
|
virtual void setData(IfaceStatus *root,IfaceData *data) {}
|
||||||
|
@ -115,73 +145,113 @@ public:
|
||||||
virtual IfaceData *createData(void) { return (IfaceData *)0; }
|
virtual IfaceData *createData(void) { return (IfaceData *)0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Compare to commands as pointers
|
||||||
|
///
|
||||||
|
/// \param a is a pointer to the first command
|
||||||
|
/// \param b is a pointer to the second command
|
||||||
|
/// \return \b true if the first pointer is ordered before the second
|
||||||
inline bool compare_ifacecommand(const IfaceCommand *a,const IfaceCommand *b) {
|
inline bool compare_ifacecommand(const IfaceCommand *a,const IfaceCommand *b) {
|
||||||
return (0>a->compare(*b));
|
return (0>a->compare(*b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Groups of console commands that are \e discovered by the loader
|
||||||
|
///
|
||||||
|
/// Any IfaceCommand that is registered with a grouping derived from this class
|
||||||
|
/// is automatically made available to any IfaceStatus object just by calling
|
||||||
|
/// the static registerAllCommands()
|
||||||
class IfaceCapability : public CapabilityPoint {
|
class IfaceCapability : public CapabilityPoint {
|
||||||
static vector<IfaceCapability *> thelist;
|
static vector<IfaceCapability *> thelist; ///< The global list of discovered command groupings
|
||||||
protected:
|
protected:
|
||||||
string name; // Identifying name for the capability
|
string name; ///< Identifying name for the capability
|
||||||
public:
|
public:
|
||||||
const string &getName(void) const { return name; }
|
const string &getName(void) const { return name; } ///< Get the name of the capability
|
||||||
virtual void initialize(void);
|
virtual void initialize(void);
|
||||||
virtual void registerCommands(IfaceStatus *status)=0;
|
virtual void registerCommands(IfaceStatus *status)=0; ///< Register commands for \b this grouping
|
||||||
|
|
||||||
static void registerAllCommands(IfaceStatus *status);
|
static void registerAllCommands(IfaceStatus *status); ///< Register all discovered commands with the interface
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief Current state of the console mode interface
|
/// \brief A generic console mode interface and command executor
|
||||||
|
///
|
||||||
|
/// Input is provided one command line at a time by providing calling readLine().
|
||||||
|
/// Output goes to a provided ostream, \e optr. Output to a separate bulk stream
|
||||||
|
/// can be enabled by setting \e fileoptr.
|
||||||
|
///
|
||||||
|
/// A derived IfaceCommand is attached to a command string via registerCom()
|
||||||
|
/// i.e.
|
||||||
|
/// stat.registerCom(new IfcQuit(),"quit");
|
||||||
|
/// stat.registerCom(new IfcOpenfileAppend(),"openfile","append");
|
||||||
|
/// stat.mainloop();
|
||||||
|
|
||||||
|
/// Command line processing is started with mainloop(), which prints a command prompt,
|
||||||
|
/// allows command line editing, including command completion and history, and executes
|
||||||
|
/// the corresponding IfaceComman::execute() callback.
|
||||||
|
/// Command words only have to match enough to disambiguate it from other commands.
|
||||||
|
|
||||||
|
/// A Custom history size and command prompt can be passed to the constructor.
|
||||||
|
/// Applications should inherit from base class IfaceStatus in order to
|
||||||
|
/// - Override the readLine() method
|
||||||
|
/// - Override pushScript() and popScript() to allow command scripts
|
||||||
|
/// - Get custom data into IfaceCommand callbacks
|
||||||
class IfaceStatus {
|
class IfaceStatus {
|
||||||
vector<string> promptstack;
|
vector<string> promptstack; ///< Stack of command prompts corresponding to script nesting level
|
||||||
vector<uint4> flagstack;
|
vector<uint4> flagstack; ///< Stack of flag state corresponding to script nesting level
|
||||||
string prompt;
|
string prompt; ///< The current command prompt
|
||||||
int4 maxhistory;
|
int4 maxhistory; ///< Maximum number of command lines to store in history
|
||||||
int4 curhistory; // most recent history
|
int4 curhistory; ///< Most recent history
|
||||||
vector<string> history;
|
vector<string> history; ///< History of commands executed through this interface
|
||||||
bool sorted; // Are commands sorted
|
bool sorted; ///< Set to \b true if commands are sorted
|
||||||
bool errorisdone; // -true- if any error terminates the process
|
bool errorisdone; ///< Set to \b true if any error terminates the process
|
||||||
void restrict(vector<IfaceCommand *>::const_iterator &first,vector<IfaceCommand *>::const_iterator &last,vector<string> &input);
|
void restrictCom(vector<IfaceCommand *>::const_iterator &first,
|
||||||
|
vector<IfaceCommand *>::const_iterator &last,vector<string> &input);
|
||||||
|
|
||||||
|
/// \brief Read the next command line
|
||||||
|
///
|
||||||
|
/// \param line is filled in with the next command to execute
|
||||||
virtual void readLine(string &line)=0;
|
virtual void readLine(string &line)=0;
|
||||||
void saveHistory(const string &line);
|
void saveHistory(const string &line); ///< Store the given command line into \e history
|
||||||
protected:
|
protected:
|
||||||
bool inerror; // -true- if last command did not succeed
|
bool inerror; ///< Set to \b true if last command did not succeed
|
||||||
vector<IfaceCommand *> comlist; // List of commands
|
vector<IfaceCommand *> comlist; ///< List of registered commands
|
||||||
map<string,IfaceData *> datamap; // Data associated with particular modules
|
map<string,IfaceData *> datamap; ///< Data associated with particular modules
|
||||||
int4 expandCom(vector<string> &expand,istream &s,
|
int4 expandCom(vector<string> &expand,istream &s,
|
||||||
vector<IfaceCommand *>::const_iterator &first,
|
vector<IfaceCommand *>::const_iterator &first,
|
||||||
vector<IfaceCommand *>::const_iterator &last);
|
vector<IfaceCommand *>::const_iterator &last);
|
||||||
public:
|
public:
|
||||||
bool done;
|
bool done; ///< Set to \b true (by a command) to indicate processing is finished
|
||||||
ostream *optr; // Where to put command line output
|
ostream *optr; ///< Where to put command line output
|
||||||
ostream *fileoptr; // Where to put bulk output
|
ostream *fileoptr; ///< Where to put bulk output
|
||||||
|
|
||||||
IfaceStatus(const string &prmpt,ostream &os,int4 mxhist=10);
|
IfaceStatus(const string &prmpt,ostream &os,int4 mxhist=10); ///< Constructor
|
||||||
virtual ~IfaceStatus(void);
|
virtual ~IfaceStatus(void); ///< Destructor
|
||||||
void setErrorIsDone(bool val) { errorisdone = val; }
|
void setErrorIsDone(bool val) { errorisdone = val; } ///< Set if processing should terminate on an error
|
||||||
virtual void pushScript(const string &filename,const string &newprompt);
|
virtual void pushScript(const string &filename,const string &newprompt);
|
||||||
virtual void popScript(void);
|
virtual void popScript(void);
|
||||||
void reset(void);
|
void reset(void); ///< Pop any existing script streams and return to processing from the base stream
|
||||||
int4 getNumInputStreamSize(void) const { return promptstack.size(); }
|
int4 getNumInputStreamSize(void) const { return promptstack.size(); } ///< Get depth of script nesting
|
||||||
void writePrompt(void) { *optr << prompt; }
|
void writePrompt(void) { *optr << prompt; } ///< Write the current command prompt to the current output stream
|
||||||
void registerCom(IfaceCommand *fptr, const char *nm1,
|
void registerCom(IfaceCommand *fptr, const char *nm1,
|
||||||
const char *nm2 = (const char *)0,
|
const char *nm2 = (const char *)0,
|
||||||
const char *nm3 = (const char *)0,
|
const char *nm3 = (const char *)0,
|
||||||
const char *nm4 = (const char *)0,
|
const char *nm4 = (const char *)0,
|
||||||
const char *nm5 = (const char *)0);
|
const char *nm5 = (const char *)0);
|
||||||
IfaceData *getData(const string &nm) const;
|
IfaceData *getData(const string &nm) const; ///< Get data associated with a IfaceCommand module
|
||||||
bool runCommand(void);
|
bool runCommand(void); ///< Run the next command
|
||||||
void getHistory(string &line,int4 i) const;
|
void getHistory(string &line,int4 i) const; ///< Get the i-th command line from history
|
||||||
int4 getHistorySize(void) const { return history.size(); }
|
int4 getHistorySize(void) const { return history.size(); } ///< Get the number of command lines in history
|
||||||
virtual bool isStreamFinished(void) const=0;
|
virtual bool isStreamFinished(void) const=0; ///< Return \b true if the current stream is finished
|
||||||
bool isInError(void) const { return inerror; }
|
bool isInError(void) const { return inerror; } ///< Return \b true if the last command failed
|
||||||
void evaluateError(void);
|
void evaluateError(void); ///< Adjust which stream to process based on last error
|
||||||
static void wordsToString(string &res,const vector<string> &list);
|
static void wordsToString(string &res,const vector<string> &list); ///< Concatenate tokens
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief A root class for a basic set of commands
|
||||||
|
///
|
||||||
|
/// Commands derived from this class are in the "base" module.
|
||||||
|
/// They are useful as part of any interface
|
||||||
class IfaceBaseCommand : public IfaceCommand {
|
class IfaceBaseCommand : public IfaceCommand {
|
||||||
protected:
|
protected:
|
||||||
IfaceStatus *status;
|
IfaceStatus *status; ///< The interface owning this command instance
|
||||||
public:
|
public:
|
||||||
virtual void setData(IfaceStatus *root,IfaceData *data) { status = root; }
|
virtual void setData(IfaceStatus *root,IfaceData *data) { status = root; }
|
||||||
virtual string getModule(void) const { return "base"; }
|
virtual string getModule(void) const { return "base"; }
|
||||||
|
|
|
@ -500,7 +500,7 @@ class JumpTable {
|
||||||
/// \brief An address table index and its corresponding out-edge
|
/// \brief An address table index and its corresponding out-edge
|
||||||
struct IndexPair {
|
struct IndexPair {
|
||||||
int4 blockPosition; ///< Out-edge index for the basic-block
|
int4 blockPosition; ///< Out-edge index for the basic-block
|
||||||
int4 addressIndex; /// Index of address targetting the basic-block
|
int4 addressIndex; ///< Index of address targeting the basic-block
|
||||||
IndexPair(int4 pos,int4 index) { blockPosition = pos; addressIndex = index; } ///< Constructor
|
IndexPair(int4 pos,int4 index) { blockPosition = pos; addressIndex = index; } ///< Constructor
|
||||||
bool operator<(const IndexPair &op2) const; ///< Compare by position then by index
|
bool operator<(const IndexPair &op2) const; ///< Compare by position then by index
|
||||||
static bool compareByPosition(const IndexPair &op1,const IndexPair &op2); ///< Compare just by position
|
static bool compareByPosition(const IndexPair &op1,const IndexPair &op2); ///< Compare just by position
|
||||||
|
|
|
@ -838,6 +838,10 @@ string OptionAliasBlock::apply(Architecture *glb,const string &p1,const string &
|
||||||
return "Alias block level set to " + p1;
|
return "Alias block level set to " + p1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \class OptionMaxInstruction
|
||||||
|
/// \brief Maximum number of instructions that can be processed in a single function
|
||||||
|
///
|
||||||
|
/// The first parameter is an integer specifying the maximum.
|
||||||
string OptionMaxInstruction::apply(Architecture *glb,const string &p1,const string &p2,const string &p3) const
|
string OptionMaxInstruction::apply(Architecture *glb,const string &p1,const string &p2,const string &p3) const
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -727,7 +727,7 @@ class EmitPrettyPrint : public EmitXml {
|
||||||
void print(const TokenSplit &tok); ///< Output the given token to the low-level emitter
|
void print(const TokenSplit &tok); ///< Output the given token to the low-level emitter
|
||||||
void advanceleft(void); ///< Emit tokens that have been fully committed
|
void advanceleft(void); ///< Emit tokens that have been fully committed
|
||||||
void scan(void); ///< Process a new token
|
void scan(void); ///< Process a new token
|
||||||
void resetDefaultsPrettyPrint(void) { setMaxLineSize(100); }
|
void resetDefaultsPrettyPrint(void) { setMaxLineSize(100); } ///< Reset the defaults
|
||||||
public:
|
public:
|
||||||
EmitPrettyPrint(void); ///< Construct with an initial maximum line size
|
EmitPrettyPrint(void); ///< Construct with an initial maximum line size
|
||||||
virtual ~EmitPrettyPrint(void);
|
virtual ~EmitPrettyPrint(void);
|
||||||
|
|
|
@ -199,9 +199,9 @@ void PrintC::pushSymbolScope(const Symbol *symbol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit the elements of the given function's namespace path that distinguish it within
|
/// Emit the elements of the given symbol's namespace path that distinguish it within
|
||||||
/// the current scope.
|
/// the current scope.
|
||||||
/// \param fd is the given function
|
/// \param symbol is the given Symbol
|
||||||
void PrintC::emitSymbolScope(const Symbol *symbol)
|
void PrintC::emitSymbolScope(const Symbol *symbol)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -9085,6 +9085,11 @@ int4 RulePiecePathology::applyOp(PcodeOp *op,Funcdata &data)
|
||||||
return tracePathologyForward(op, data);
|
return tracePathologyForward(op, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \class RuleXorSwap
|
||||||
|
/// \brief Simplify limited chains of XOR operations
|
||||||
|
///
|
||||||
|
/// `V = (a ^ b) ^ a => V = b`
|
||||||
|
/// `V = a ^ (b ^ a) => V = b`
|
||||||
void RuleXorSwap::getOpList(vector<uint4> &oplist) const
|
void RuleXorSwap::getOpList(vector<uint4> &oplist) const
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,10 +32,13 @@ PcodeCacher::~PcodeCacher(void)
|
||||||
delete [] poolstart;
|
delete [] poolstart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expand the VarnodeData pool so that \e size more elements fit, and return
|
||||||
|
/// a pointer to first available element.
|
||||||
|
/// \param size is the number of elements to expand the pool by
|
||||||
|
/// \return the first available VarnodeData
|
||||||
VarnodeData *PcodeCacher::expandPool(uint4 size)
|
VarnodeData *PcodeCacher::expandPool(uint4 size)
|
||||||
|
|
||||||
{ // Expand the pool so that -size- more elements fit
|
{
|
||||||
// Return pointer to first available element
|
|
||||||
uint4 curmax = endpool - poolstart;
|
uint4 curmax = endpool - poolstart;
|
||||||
uint4 cursize = curpool - poolstart;
|
uint4 cursize = curpool - poolstart;
|
||||||
if (cursize + size <= curmax)
|
if (cursize + size <= curmax)
|
||||||
|
@ -75,18 +78,26 @@ VarnodeData *PcodeCacher::expandPool(uint4 size)
|
||||||
return newpool + cursize;
|
return newpool + cursize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store off a reference to the Varnode and the absolute index of the next
|
||||||
|
/// instruction. The Varnode must be an operand of the current instruction.
|
||||||
|
/// \param ptr is the Varnode reference
|
||||||
void PcodeCacher::addLabelRef(VarnodeData *ptr)
|
void PcodeCacher::addLabelRef(VarnodeData *ptr)
|
||||||
|
|
||||||
{ // Store off a reference to a label and the next instruction
|
{
|
||||||
// address
|
|
||||||
label_refs.emplace_back();
|
label_refs.emplace_back();
|
||||||
label_refs.back().dataptr = ptr;
|
label_refs.back().dataptr = ptr;
|
||||||
label_refs.back().calling_index = issued.size();
|
label_refs.back().calling_index = issued.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The label has an id that is referred to by Varnodes holding
|
||||||
|
/// intra-instruction branch targets, prior to converting
|
||||||
|
/// them to a \e relative \e branch offset. The label is associated with
|
||||||
|
/// the absolute index of the next PcodeData object to be issued,
|
||||||
|
/// facilitating this conversion.
|
||||||
|
/// \param id is the given id of the label
|
||||||
void PcodeCacher::addLabel(uint4 id)
|
void PcodeCacher::addLabel(uint4 id)
|
||||||
|
|
||||||
{ // Attach a label to the address of the next instruction
|
{
|
||||||
while(labels.size() <= id)
|
while(labels.size() <= id)
|
||||||
labels.push_back(0xbadbeef);
|
labels.push_back(0xbadbeef);
|
||||||
labels[ id ] = issued.size();
|
labels[ id ] = issued.size();
|
||||||
|
@ -101,11 +112,12 @@ void PcodeCacher::clear(void)
|
||||||
labels.clear();
|
labels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assuming all the PcodeData has been generated for an
|
||||||
|
/// instruction, go resolve any relative offsets and back
|
||||||
|
/// patch their value(s) into the PcodeData
|
||||||
void PcodeCacher::resolveRelatives(void)
|
void PcodeCacher::resolveRelatives(void)
|
||||||
|
|
||||||
{ // Assuming all the PcodeData has been generated for an
|
{
|
||||||
// instruction, go resolve any relative offsets and back
|
|
||||||
// patch their value(s) into the PcodeData
|
|
||||||
list<RelativeRecord>::const_iterator iter;
|
list<RelativeRecord>::const_iterator iter;
|
||||||
for(iter=label_refs.begin();iter!=label_refs.end();++iter) {
|
for(iter=label_refs.begin();iter!=label_refs.end();++iter) {
|
||||||
VarnodeData *ptr = (*iter).dataptr;
|
VarnodeData *ptr = (*iter).dataptr;
|
||||||
|
@ -119,18 +131,25 @@ void PcodeCacher::resolveRelatives(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Each p-code operation is presented to the emitter via its dump() method.
|
||||||
|
/// \param addr is the Address associated with the p-code operation
|
||||||
|
/// \param emt is the emitter
|
||||||
void PcodeCacher::emit(const Address &addr,PcodeEmit *emt) const
|
void PcodeCacher::emit(const Address &addr,PcodeEmit *emt) const
|
||||||
|
|
||||||
{ // Emit any cached pcode
|
{
|
||||||
vector<PcodeData>::const_iterator iter;
|
vector<PcodeData>::const_iterator iter;
|
||||||
|
|
||||||
for(iter=issued.begin();iter!=issued.end();++iter)
|
for(iter=issued.begin();iter!=issued.end();++iter)
|
||||||
emt->dump(addr,(*iter).opc,(*iter).outvar,(*iter).invar,(*iter).isize);
|
emt->dump(addr,(*iter).opc,(*iter).outvar,(*iter).invar,(*iter).isize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Generate a concrete VarnodeData object from the given template (VarnodeTpl)
|
||||||
|
///
|
||||||
|
/// \param vntpl is the template to reference
|
||||||
|
/// \param vn is the object to fill in with concrete values
|
||||||
void SleighBuilder::generateLocation(const VarnodeTpl *vntpl,VarnodeData &vn)
|
void SleighBuilder::generateLocation(const VarnodeTpl *vntpl,VarnodeData &vn)
|
||||||
|
|
||||||
{ // Generate a concrete varnode -vn- from the template -vntpl-
|
{
|
||||||
vn.space = vntpl->getSpace().fixSpace(*walker);
|
vn.space = vntpl->getSpace().fixSpace(*walker);
|
||||||
vn.size = vntpl->getSize().fix(*walker);
|
vn.size = vntpl->getSize().fix(*walker);
|
||||||
if (vn.space == const_space)
|
if (vn.space == const_space)
|
||||||
|
@ -143,9 +162,18 @@ void SleighBuilder::generateLocation(const VarnodeTpl *vntpl,VarnodeData &vn)
|
||||||
vn.offset = vn.space->wrapOffset(vntpl->getOffset().fix(*walker));
|
vn.offset = vn.space->wrapOffset(vntpl->getOffset().fix(*walker));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Generate a pointer VarnodeData from a dynamic template (VarnodeTpl)
|
||||||
|
///
|
||||||
|
/// The symbol represents a value referenced through a dynamic pointer.
|
||||||
|
/// This method generates the varnode representing the pointer itself and also
|
||||||
|
/// returns the address space in anticipation of generating the LOAD or STORE
|
||||||
|
/// that actually manipulates the value.
|
||||||
|
/// \param vntpl is the dynamic template to reference
|
||||||
|
/// \param vn is the object to fill with concrete values
|
||||||
|
/// \return the address space being pointed to
|
||||||
AddrSpace *SleighBuilder::generatePointer(const VarnodeTpl *vntpl,VarnodeData &vn)
|
AddrSpace *SleighBuilder::generatePointer(const VarnodeTpl *vntpl,VarnodeData &vn)
|
||||||
|
|
||||||
{ // Generate the pointer varnode -vn- from a dynamic template -vntpl-
|
{
|
||||||
const FixedHandle &hand(walker->getFixedHandle(vntpl->getOffset().getHandleIndex()));
|
const FixedHandle &hand(walker->getFixedHandle(vntpl->getOffset().getHandleIndex()));
|
||||||
vn.space = hand.offset_space;
|
vn.space = hand.offset_space;
|
||||||
vn.size = hand.offset_size;
|
vn.size = hand.offset_size;
|
||||||
|
@ -218,9 +246,17 @@ void SleighBuilder::dump(OpTpl *op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Build a named p-code section of a constructor that contains only implied BUILD directives
|
||||||
|
///
|
||||||
|
/// If a named section of a constructor is empty, we still need to walk
|
||||||
|
/// through any subtables that might contain p-code in their named sections.
|
||||||
|
/// This method treats each subtable operand as an implied \e build directive,
|
||||||
|
/// in the otherwise empty section.
|
||||||
|
/// \param ct is the matching currently Constructor being built
|
||||||
|
/// \param secnum is the particular \e named section number to build
|
||||||
void SleighBuilder::buildEmpty(Constructor *ct,int4 secnum)
|
void SleighBuilder::buildEmpty(Constructor *ct,int4 secnum)
|
||||||
|
|
||||||
{ // Build a named p-code section of a constructor that contains only implied BUILD directives
|
{
|
||||||
int4 numops = ct->getNumOperands();
|
int4 numops = ct->getNumOperands();
|
||||||
|
|
||||||
for(int4 i=0;i<numops;++i) {
|
for(int4 i=0;i<numops;++i) {
|
||||||
|
@ -238,12 +274,23 @@ void SleighBuilder::buildEmpty(Constructor *ct,int4 secnum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bits used to make temporary registers unique across multiple instructions
|
||||||
|
/// are generated based on the given address.
|
||||||
|
/// \param addr is the given Address
|
||||||
void SleighBuilder::setUniqueOffset(const Address &addr)
|
void SleighBuilder::setUniqueOffset(const Address &addr)
|
||||||
|
|
||||||
{
|
{
|
||||||
uniqueoffset = (addr.getOffset() & uniquemask)<<4;
|
uniqueoffset = (addr.getOffset() & uniquemask)<<4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Constructor
|
||||||
|
///
|
||||||
|
/// \param w is the parsed instruction
|
||||||
|
/// \param dcache is a cache of nearby instruction parses
|
||||||
|
/// \param pc will hold the PcodeData and VarnodeData objects produced by \b this builder
|
||||||
|
/// \param cspc is the constant address space
|
||||||
|
/// \param uspc is the unique address space
|
||||||
|
/// \param umask is the mask to use to find unique bits within an Address
|
||||||
SleighBuilder::SleighBuilder(ParserWalker *w,DisassemblyCache *dcache,PcodeCacher *pc,AddrSpace *cspc,
|
SleighBuilder::SleighBuilder(ParserWalker *w,DisassemblyCache *dcache,PcodeCacher *pc,AddrSpace *cspc,
|
||||||
AddrSpace *uspc,uint4 umask)
|
AddrSpace *uspc,uint4 umask)
|
||||||
: PcodeBuilder(0)
|
: PcodeBuilder(0)
|
||||||
|
@ -259,7 +306,8 @@ SleighBuilder::SleighBuilder(ParserWalker *w,DisassemblyCache *dcache,PcodeCache
|
||||||
|
|
||||||
void SleighBuilder::appendBuild(OpTpl *bld,int4 secnum)
|
void SleighBuilder::appendBuild(OpTpl *bld,int4 secnum)
|
||||||
|
|
||||||
{ // Append pcode for a particular build statement
|
{
|
||||||
|
// Append p-code for a particular build statement
|
||||||
int4 index = bld->getIn(0)->getOffset().getReal(); // Recover operand index from build statement
|
int4 index = bld->getIn(0)->getOffset().getReal(); // Recover operand index from build statement
|
||||||
// Check if operand is a subtable
|
// Check if operand is a subtable
|
||||||
SubtableSymbol *sym = (SubtableSymbol *)walker->getConstructor()->getOperand(index)->getDefiningSymbol();
|
SubtableSymbol *sym = (SubtableSymbol *)walker->getConstructor()->getOperand(index)->getDefiningSymbol();
|
||||||
|
@ -283,8 +331,9 @@ void SleighBuilder::appendBuild(OpTpl *bld,int4 secnum)
|
||||||
|
|
||||||
void SleighBuilder::delaySlot(OpTpl *op)
|
void SleighBuilder::delaySlot(OpTpl *op)
|
||||||
|
|
||||||
{ // Append pcode for an entire instruction (delay slot)
|
{
|
||||||
// in the middle of the current instruction
|
// Append pcode for an entire instruction (delay slot)
|
||||||
|
// in the middle of the current instruction
|
||||||
ParserWalker *tmp = walker;
|
ParserWalker *tmp = walker;
|
||||||
uintb olduniqueoffset = uniqueoffset;
|
uintb olduniqueoffset = uniqueoffset;
|
||||||
|
|
||||||
|
@ -319,7 +368,8 @@ void SleighBuilder::setLabel(OpTpl *op)
|
||||||
|
|
||||||
void SleighBuilder::appendCrossBuild(OpTpl *bld,int4 secnum)
|
void SleighBuilder::appendCrossBuild(OpTpl *bld,int4 secnum)
|
||||||
|
|
||||||
{ // Weave in the p-code section from an instruction at another address
|
{
|
||||||
|
// Weave in the p-code section from an instruction at another address
|
||||||
// bld-param(0) contains the address of the instruction
|
// bld-param(0) contains the address of the instruction
|
||||||
// bld-param(1) contains the section number
|
// bld-param(1) contains the section number
|
||||||
if (secnum>=0)
|
if (secnum>=0)
|
||||||
|
@ -352,6 +402,8 @@ void SleighBuilder::appendCrossBuild(OpTpl *bld,int4 secnum)
|
||||||
uniqueoffset = olduniqueoffset;
|
uniqueoffset = olduniqueoffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \param min is the minimum number of allocations before a reuse is expected
|
||||||
|
/// \param hashsize is the number of elements in the hash-table
|
||||||
void DisassemblyCache::initialize(int4 min,int4 hashsize)
|
void DisassemblyCache::initialize(int4 min,int4 hashsize)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -382,6 +434,10 @@ void DisassemblyCache::free(void)
|
||||||
delete [] hashtable;
|
delete [] hashtable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \param ccache is the ContextCache front-end shared across all the parser contexts
|
||||||
|
/// \param cspace is the constant address space used for minting constant Varnodes
|
||||||
|
/// \param cachesize is the number of distinct ParserContext objects in this cache
|
||||||
|
/// \param windowsize is the size of the ParserContext hash-table
|
||||||
DisassemblyCache::DisassemblyCache(ContextCache *ccache,AddrSpace *cspace,int4 cachesize,int4 windowsize)
|
DisassemblyCache::DisassemblyCache(ContextCache *ccache,AddrSpace *cspace,int4 cachesize,int4 windowsize)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -390,13 +446,17 @@ DisassemblyCache::DisassemblyCache(ContextCache *ccache,AddrSpace *cspace,int4 c
|
||||||
initialize(cachesize,windowsize); // Set default settings for the cache
|
initialize(cachesize,windowsize); // Set default settings for the cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a (possibly cached) ParserContext that is associated with \e addr
|
||||||
|
/// If n different calls to this interface are made with n different Addresses, if
|
||||||
|
/// - n <= minimumreuse AND
|
||||||
|
/// - all the addresses are within the windowsize (=mask+1)
|
||||||
|
///
|
||||||
|
/// then the cacher guarantees that you get all different ParserContext objects
|
||||||
|
/// \param addr is the Address to disassemble at
|
||||||
|
/// \return the ParserContext associated with the address
|
||||||
ParserContext *DisassemblyCache::getParserContext(const Address &addr)
|
ParserContext *DisassemblyCache::getParserContext(const Address &addr)
|
||||||
|
|
||||||
{ // Return a (possibly cached) ParserContext that is associated with -addr-
|
{
|
||||||
// If n different calls to this interface are made with n different Addresses, if
|
|
||||||
// n <= minimumreuse AND
|
|
||||||
// all the addresses are within the windowsize (=mask+1)
|
|
||||||
// then the cacher guarantees that you get all different ParserContext objects
|
|
||||||
int4 hashindex = ((int4) addr.getOffset()) & mask;
|
int4 hashindex = ((int4) addr.getOffset()) & mask;
|
||||||
ParserContext *res = hashtable[ hashindex ];
|
ParserContext *res = hashtable[ hashindex ];
|
||||||
if (res->getAddr() == addr)
|
if (res->getAddr() == addr)
|
||||||
|
@ -411,6 +471,8 @@ ParserContext *DisassemblyCache::getParserContext(const Address &addr)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \param ld is the LoadImage to draw program bytes from
|
||||||
|
/// \param c_db is the context database
|
||||||
Sleigh::Sleigh(LoadImage *ld,ContextDatabase *c_db)
|
Sleigh::Sleigh(LoadImage *ld,ContextDatabase *c_db)
|
||||||
: SleighBase()
|
: SleighBase()
|
||||||
|
|
||||||
|
@ -435,10 +497,13 @@ Sleigh::~Sleigh(void)
|
||||||
clearForDelete();
|
clearForDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Completely clear everything except the base and reconstruct
|
||||||
|
/// with a new LoadImage and ContextDatabase
|
||||||
|
/// \param ld is the new LoadImage
|
||||||
|
/// \param c_db is the new ContextDatabase
|
||||||
void Sleigh::reset(LoadImage *ld,ContextDatabase *c_db)
|
void Sleigh::reset(LoadImage *ld,ContextDatabase *c_db)
|
||||||
|
|
||||||
{ // Completely clear everything except the base and reconstruct
|
{
|
||||||
// with a new loader and context
|
|
||||||
clearForDelete();
|
clearForDelete();
|
||||||
pcode_cache.clear();
|
pcode_cache.clear();
|
||||||
loader = ld;
|
loader = ld;
|
||||||
|
@ -447,6 +512,8 @@ void Sleigh::reset(LoadImage *ld,ContextDatabase *c_db)
|
||||||
discache = (DisassemblyCache *)0;
|
discache = (DisassemblyCache *)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The .sla file from the document store is loaded and cache objects are prepared
|
||||||
|
/// \param store is the document store containing the main \<sleigh> tag.
|
||||||
void Sleigh::initialize(DocumentStorage &store)
|
void Sleigh::initialize(DocumentStorage &store)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -467,10 +534,18 @@ void Sleigh::initialize(DocumentStorage &store)
|
||||||
discache = new DisassemblyCache(cache,getConstantSpace(),parser_cachesize,parser_windowsize);
|
discache = new DisassemblyCache(cache,getConstantSpace(),parser_cachesize,parser_windowsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Obtain a parse tree for the instruction at the given address
|
||||||
|
///
|
||||||
|
/// The tree may be cached from a previous access. If the address
|
||||||
|
/// has not been parsed, disassembly is performed, and a new parse tree
|
||||||
|
/// is prepared. Depending on the desired \e state, the parse tree
|
||||||
|
/// can be prepared either for disassembly or for p-code generation.
|
||||||
|
/// \param addr is the given address of the instruction
|
||||||
|
/// \param state is the desired parse state.
|
||||||
|
/// \return the parse tree object (ParseContext)
|
||||||
ParserContext *Sleigh::obtainContext(const Address &addr,int4 state) const
|
ParserContext *Sleigh::obtainContext(const Address &addr,int4 state) const
|
||||||
|
|
||||||
{ // Obtain a ParserContext for the instruction at the given -addr-. This may be cached.
|
{
|
||||||
// Make sure parsing has proceeded to at least the given -state.
|
|
||||||
ParserContext *pos = discache->getParserContext(addr);
|
ParserContext *pos = discache->getParserContext(addr);
|
||||||
int4 curstate = pos->getParserState();
|
int4 curstate = pos->getParserState();
|
||||||
if (curstate >= state)
|
if (curstate >= state)
|
||||||
|
@ -485,10 +560,11 @@ ParserContext *Sleigh::obtainContext(const Address &addr,int4 state) const
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve \e all the constructors involved in the instruction at the indicated address
|
||||||
|
/// \param pos is the parse object that will hold the resulting tree
|
||||||
void Sleigh::resolve(ParserContext &pos) const
|
void Sleigh::resolve(ParserContext &pos) const
|
||||||
|
|
||||||
{ // Resolve ALL the constructors involved in the
|
{
|
||||||
// instruction at this address
|
|
||||||
loader->loadFill(pos.getBuffer(),16,pos.getAddr());
|
loader->loadFill(pos.getBuffer(),16,pos.getAddr());
|
||||||
ParserWalkerChange walker(&pos);
|
ParserWalkerChange walker(&pos);
|
||||||
pos.deallocateState(walker); // Clear the previous resolve and initialize the walker
|
pos.deallocateState(walker); // Clear the previous resolve and initialize the walker
|
||||||
|
@ -538,9 +614,12 @@ void Sleigh::resolve(ParserContext &pos) const
|
||||||
pos.setParserState(ParserContext::disassembly);
|
pos.setParserState(ParserContext::disassembly);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve handle templates for the given parse tree, assuming Constructors
|
||||||
|
/// are already resolved.
|
||||||
|
/// \param pos is the given parse tree
|
||||||
void Sleigh::resolveHandles(ParserContext &pos) const
|
void Sleigh::resolveHandles(ParserContext &pos) const
|
||||||
|
|
||||||
{ // Resolve handles (assuming Constructors already resolved)
|
{
|
||||||
TripleSymbol *triple;
|
TripleSymbol *triple;
|
||||||
Constructor *ct;
|
Constructor *ct;
|
||||||
int4 oper,numoper;
|
int4 oper,numoper;
|
||||||
|
@ -671,7 +750,7 @@ int4 Sleigh::oneInstruction(PcodeEmit &emit,const Address &baseaddr) const
|
||||||
|
|
||||||
void Sleigh::registerContext(const string &name,int4 sbit,int4 ebit)
|
void Sleigh::registerContext(const string &name,int4 sbit,int4 ebit)
|
||||||
|
|
||||||
{ // Inform translator of existence of context variable
|
{
|
||||||
context_db->registerVariable(name,sbit,ebit);
|
context_db->registerVariable(name,sbit,ebit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/// \file sleigh.hh
|
||||||
|
/// \brief Classes and utilities for the main SLEIGH engine
|
||||||
|
|
||||||
#ifndef __SLEIGH__
|
#ifndef __SLEIGH__
|
||||||
#define __SLEIGH__
|
#define __SLEIGH__
|
||||||
|
|
||||||
|
@ -20,29 +24,52 @@
|
||||||
|
|
||||||
class LoadImage;
|
class LoadImage;
|
||||||
|
|
||||||
|
/// \brief Class for describing a relative p-code branch destination
|
||||||
|
///
|
||||||
|
/// An intra-instruction p-code branch takes a \e relative operand.
|
||||||
|
/// The actual value produced during p-code generation is calculated at
|
||||||
|
/// the last second using \b this. It stores the index of the BRANCH
|
||||||
|
/// instruction and a reference to its destination operand. This initially
|
||||||
|
/// holds a reference to a destination \e label symbol, but is later updated
|
||||||
|
/// with the final relative value.
|
||||||
struct RelativeRecord {
|
struct RelativeRecord {
|
||||||
VarnodeData *dataptr; // Record containing relative offset
|
VarnodeData *dataptr; ///< Varnode indicating relative offset
|
||||||
uintb calling_index; // Index of instruction containing relative offset
|
uintb calling_index; ///< Index of instruction containing relative offset
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PcodeData { // Data for building one pcode instruction
|
/// \brief Data for building one p-code instruction
|
||||||
OpCode opc;
|
///
|
||||||
VarnodeData *outvar; // Points to outvar is there is an output
|
/// Raw data used by the emitter to produce a single PcodeOp
|
||||||
VarnodeData *invar; // Inputs
|
struct PcodeData {
|
||||||
int4 isize; // Number of inputs
|
OpCode opc; ///< The op code
|
||||||
|
VarnodeData *outvar; ///< Output Varnode data (or null)
|
||||||
|
VarnodeData *invar; ///< Array of input Varnode data
|
||||||
|
int4 isize; ///< Number of input Varnodes
|
||||||
};
|
};
|
||||||
|
|
||||||
class PcodeCacher { // Cached chunk of pcode, prior to emitting
|
/// \brief Class for caching a chunk of p-code, prior to emitting
|
||||||
VarnodeData *poolstart;
|
///
|
||||||
VarnodeData *curpool;
|
/// The engine accumulates PcodeData and VarnodeData objects for
|
||||||
VarnodeData *endpool;
|
/// a single instruction. Once the full instruction is constructed,
|
||||||
vector<PcodeData> issued;
|
/// the objects are passed to the emitter (PcodeEmit) via the emit() method.
|
||||||
list<RelativeRecord> label_refs; // References to labels
|
/// The class acts as a pool of memory for PcodeData and VarnodeData objects
|
||||||
vector<uintb> labels; // Locations of labels
|
/// that can be reused repeatedly to emit multiple instructions.
|
||||||
VarnodeData *expandPool(uint4 size);
|
class PcodeCacher {
|
||||||
|
VarnodeData *poolstart; ///< Start of the pool of VarnodeData objects
|
||||||
|
VarnodeData *curpool; ///< First unused VarnodeData
|
||||||
|
VarnodeData *endpool; ///< End of the pool of VarnodeData objects
|
||||||
|
vector<PcodeData> issued; ///< P-code ops issued for the current instruction
|
||||||
|
list<RelativeRecord> label_refs; ///< References to labels
|
||||||
|
vector<uintb> labels; ///< Locations of labels
|
||||||
|
VarnodeData *expandPool(uint4 size); ///< Expand the memory pool
|
||||||
public:
|
public:
|
||||||
PcodeCacher(void);
|
PcodeCacher(void); ///< Constructor
|
||||||
~PcodeCacher(void);
|
~PcodeCacher(void); ///< Destructor
|
||||||
|
|
||||||
|
/// \brief Allocate data objects for a new set of Varnodes
|
||||||
|
///
|
||||||
|
/// \param size is the number of objects to allocate
|
||||||
|
/// \return a pointer to the array of available VarnodeData objects
|
||||||
VarnodeData *allocateVarnodes(uint4 size) {
|
VarnodeData *allocateVarnodes(uint4 size) {
|
||||||
VarnodeData *newptr = curpool + size;
|
VarnodeData *newptr = curpool + size;
|
||||||
if (newptr <= endpool) {
|
if (newptr <= endpool) {
|
||||||
|
@ -52,6 +79,10 @@ public:
|
||||||
}
|
}
|
||||||
return expandPool(size);
|
return expandPool(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Allocate a data object for a new p-code operation
|
||||||
|
///
|
||||||
|
/// \return the new PcodeData object
|
||||||
PcodeData *allocateInstruction(void) {
|
PcodeData *allocateInstruction(void) {
|
||||||
issued.emplace_back();
|
issued.emplace_back();
|
||||||
PcodeData *res = &issued.back();
|
PcodeData *res = &issued.back();
|
||||||
|
@ -59,41 +90,54 @@ public:
|
||||||
res->invar = (VarnodeData *)0;
|
res->invar = (VarnodeData *)0;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
void addLabelRef(VarnodeData *ptr);
|
void addLabelRef(VarnodeData *ptr); ///< Denote a Varnode holding a \e relative \e branch offset
|
||||||
void addLabel(uint4 id);
|
void addLabel(uint4 id); ///< Attach a label to the \e next p-code instruction
|
||||||
void clear(void);
|
void clear(void); ///< Reset the cache so that all objects are unallocated
|
||||||
void resolveRelatives(void);
|
void resolveRelatives(void); ///< Rewrite branch target Varnodes as \e relative offsets
|
||||||
void emit(const Address &addr,PcodeEmit *emt) const;
|
void emit(const Address &addr,PcodeEmit *emt) const; ///< Pass the cached p-code data to the emitter
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief A container for disassembly context used by the SLEIGH engine
|
||||||
|
///
|
||||||
|
/// This acts as a factor for the ParserContext objects which are used to disassemble
|
||||||
|
/// a single instruction. These all share a ContextCache which is a front end for
|
||||||
|
/// accessing the ContextDatabase and resolving context variables from the SLEIGH spec.
|
||||||
|
/// ParserContext objects are stored in a hash-table keyed by the address of the instruction.
|
||||||
class DisassemblyCache {
|
class DisassemblyCache {
|
||||||
ContextCache *contextcache;
|
ContextCache *contextcache; ///< Cached values from the ContextDatabase
|
||||||
AddrSpace *constspace;
|
AddrSpace *constspace; ///< The constant address space
|
||||||
int4 minimumreuse; // Can call getParserContext this many times, before a ParserContext is reused
|
int4 minimumreuse; ///< Can call getParserContext this many times, before a ParserContext is reused
|
||||||
uint4 mask; // Size of the hashtable in form 2^n-1
|
uint4 mask; ///< Size of the hashtable in form 2^n-1
|
||||||
ParserContext **list; // (circular) array of currently cached ParserContext objects
|
ParserContext **list; ///< (circular) array of currently cached ParserContext objects
|
||||||
int4 nextfree; // Current end/beginning of circular list
|
int4 nextfree; ///< Current end/beginning of circular list
|
||||||
ParserContext **hashtable; // Hashtable for looking up ParserContext via Address
|
ParserContext **hashtable; ///< Hashtable for looking up ParserContext via Address
|
||||||
void initialize(int4 min,int4 hashsize);
|
void initialize(int4 min,int4 hashsize); ///< Initialize the hash-table of ParserContexts
|
||||||
void free(void);
|
void free(void); ///< Free the hash-table of ParserContexts
|
||||||
public:
|
public:
|
||||||
DisassemblyCache(ContextCache *ccache,AddrSpace *cspace,int4 cachesize,int4 windowsize);
|
DisassemblyCache(ContextCache *ccache,AddrSpace *cspace,int4 cachesize,int4 windowsize); ///< Constructor
|
||||||
~DisassemblyCache(void) { free(); }
|
~DisassemblyCache(void) { free(); } ///< Destructor
|
||||||
ParserContext *getParserContext(const Address &addr);
|
ParserContext *getParserContext(const Address &addr); ///< Get the parser for a particular Address
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief Build p-code from a pre-parsed instruction
|
||||||
|
///
|
||||||
|
/// Through the build() method, \b this walks the parse tree and prepares data
|
||||||
|
/// for final emission as p-code. (The final emitting is done separately through the
|
||||||
|
/// PcodeCacher.emit() method). Generally, only p-code for one instruction is prepared.
|
||||||
|
/// But, through the \b delay-slot mechanism, build() may recursively visit
|
||||||
|
/// additional instructions.
|
||||||
class SleighBuilder : public PcodeBuilder {
|
class SleighBuilder : public PcodeBuilder {
|
||||||
virtual void dump( OpTpl *op );
|
virtual void dump( OpTpl *op );
|
||||||
AddrSpace *const_space;
|
AddrSpace *const_space; ///< The constant address space
|
||||||
AddrSpace *uniq_space;
|
AddrSpace *uniq_space; ///< The unique address space
|
||||||
uintb uniquemask;
|
uintb uniquemask; ///< Mask of address bits to use to uniquify temporary registers
|
||||||
uintb uniqueoffset;
|
uintb uniqueoffset; ///< Uniquifier bits for \b this instruction
|
||||||
DisassemblyCache *discache;
|
DisassemblyCache *discache; ///< Cache of disassembled instructions
|
||||||
PcodeCacher *cache;
|
PcodeCacher *cache; ///< Cache accumulating p-code data for the instruction
|
||||||
void buildEmpty(Constructor *ct,int4 secnum);
|
void buildEmpty(Constructor *ct,int4 secnum);
|
||||||
void generateLocation(const VarnodeTpl *vntpl,VarnodeData &vn);
|
void generateLocation(const VarnodeTpl *vntpl,VarnodeData &vn);
|
||||||
AddrSpace *generatePointer(const VarnodeTpl *vntpl,VarnodeData &vn);
|
AddrSpace *generatePointer(const VarnodeTpl *vntpl,VarnodeData &vn);
|
||||||
void setUniqueOffset(const Address &addr);
|
void setUniqueOffset(const Address &addr); ///< Set uniquifying bits for the current instruction
|
||||||
public:
|
public:
|
||||||
SleighBuilder(ParserWalker *w,DisassemblyCache *dcache,PcodeCacher *pc,AddrSpace *cspc,AddrSpace *uspc,uint4 umask);
|
SleighBuilder(ParserWalker *w,DisassemblyCache *dcache,PcodeCacher *pc,AddrSpace *cspc,AddrSpace *uspc,uint4 umask);
|
||||||
virtual void appendBuild(OpTpl *bld,int4 secnum);
|
virtual void appendBuild(OpTpl *bld,int4 secnum);
|
||||||
|
@ -102,21 +146,31 @@ public:
|
||||||
virtual void appendCrossBuild(OpTpl *bld,int4 secnum);
|
virtual void appendCrossBuild(OpTpl *bld,int4 secnum);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// \brief A full SLEIGH engine
|
||||||
|
///
|
||||||
|
/// Its provided with a LoadImage of the bytes to be disassembled and
|
||||||
|
/// a ContextDatabase.
|
||||||
|
///
|
||||||
|
/// Assembly is produced via the printAssembly() method, provided with an
|
||||||
|
/// AssemblyEmit object and an Address.
|
||||||
|
///
|
||||||
|
/// P-code is produced via the oneInstruction() method, provided with a PcodeEmit
|
||||||
|
/// object and an Address.
|
||||||
class Sleigh : public SleighBase {
|
class Sleigh : public SleighBase {
|
||||||
LoadImage *loader;
|
LoadImage *loader; ///< The mapped bytes in the program
|
||||||
ContextDatabase *context_db;
|
ContextDatabase *context_db; ///< Database of context values steering disassembly
|
||||||
ContextCache *cache;
|
ContextCache *cache; ///< Cache of recently used context values
|
||||||
mutable DisassemblyCache *discache;
|
mutable DisassemblyCache *discache; ///< Cache of recently parsed instructions
|
||||||
mutable PcodeCacher pcode_cache;
|
mutable PcodeCacher pcode_cache; ///< Cache of p-code data just prior to emitting
|
||||||
void clearForDelete(void);
|
void clearForDelete(void); ///< Delete the context and disassembly caches
|
||||||
protected:
|
protected:
|
||||||
ParserContext *obtainContext(const Address &addr,int4 state) const;
|
ParserContext *obtainContext(const Address &addr,int4 state) const;
|
||||||
void resolve(ParserContext &pos) const;
|
void resolve(ParserContext &pos) const; ///< Generate a parse tree suitable for disassembly
|
||||||
void resolveHandles(ParserContext &pos) const;
|
void resolveHandles(ParserContext &pos) const; ///< Prepare the parse tree for p-code generation
|
||||||
public:
|
public:
|
||||||
Sleigh(LoadImage *ld,ContextDatabase *c_db);
|
Sleigh(LoadImage *ld,ContextDatabase *c_db); ///< Constructor
|
||||||
virtual ~Sleigh(void);
|
virtual ~Sleigh(void); ///< Destructor
|
||||||
void reset(LoadImage *ld,ContextDatabase *c_db);
|
void reset(LoadImage *ld,ContextDatabase *c_db); ///< Reset the engine for a new program
|
||||||
virtual void initialize(DocumentStorage &store);
|
virtual void initialize(DocumentStorage &store);
|
||||||
virtual void registerContext(const string &name,int4 sbit,int4 ebit);
|
virtual void registerContext(const string &name,int4 sbit,int4 ebit);
|
||||||
virtual void setContextDefault(const string &nm,uintm val);
|
virtual void setContextDefault(const string &nm,uintm val);
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
/// \file ghidra_string.hh
|
/// \file string_ghidra.hh
|
||||||
/// \brief Implementation of the StringManager through the ghidra client
|
/// \brief Implementation of the StringManager through the ghidra client
|
||||||
|
|
||||||
#ifndef __STRING_GHIDRA__
|
#ifndef __STRING_GHIDRA__
|
||||||
|
|
|
@ -30,10 +30,11 @@ class Architecture;
|
||||||
/// Stores the decoded string until its needed for presentation.
|
/// Stores the decoded string until its needed for presentation.
|
||||||
class StringManager {
|
class StringManager {
|
||||||
protected:
|
protected:
|
||||||
|
/// \brief String data (a sequence of bytes) stored by StringManager
|
||||||
class StringData {
|
class StringData {
|
||||||
public:
|
public:
|
||||||
bool isTruncated; // \b true if the the string is truncated
|
bool isTruncated; ///< \b true if the the string is truncated
|
||||||
vector<uint1> byteData; // UTF8 encoded string data
|
vector<uint1> byteData; ///< UTF8 encoded string data
|
||||||
};
|
};
|
||||||
map<Address,StringData> stringMap; ///< Map from address to string data
|
map<Address,StringData> stringMap; ///< Map from address to string data
|
||||||
int4 maximumChars; ///< Maximum characters in a string before truncating
|
int4 maximumChars; ///< Maximum characters in a string before truncating
|
||||||
|
|
|
@ -201,7 +201,7 @@ public:
|
||||||
bool isFloatExtension(void) const { return (pieces.size() == 1); } ///< Does this record extend a float varnode
|
bool isFloatExtension(void) const { return (pieces.size() == 1); } ///< Does this record extend a float varnode
|
||||||
const VarnodeData &getPiece(int4 i) const { return pieces[i]; } ///< Get the i-th piece
|
const VarnodeData &getPiece(int4 i) const { return pieces[i]; } ///< Get the i-th piece
|
||||||
const VarnodeData &getUnified(void) const { return unified; } ///< Get the Varnode whole
|
const VarnodeData &getUnified(void) const { return unified; } ///< Get the Varnode whole
|
||||||
Address getEquivalentAddress(uintb offset,int4 &pos) const; ///< Given offset in \join space, get equivalent address of piece
|
Address getEquivalentAddress(uintb offset,int4 &pos) const; ///< Given offset in \e join space, get equivalent address of piece
|
||||||
bool operator<(const JoinRecord &op2) const; ///< Compare records lexigraphically by pieces
|
bool operator<(const JoinRecord &op2) const; ///< Compare records lexigraphically by pieces
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -318,7 +318,7 @@ void Datatype::saveXmlRef(ostream &s) const
|
||||||
|
|
||||||
/// A CPUI_PTRSUB must act on a pointer data-type where the given offset addresses a component.
|
/// A CPUI_PTRSUB must act on a pointer data-type where the given offset addresses a component.
|
||||||
/// Perform this check.
|
/// Perform this check.
|
||||||
/// \param is the given offset
|
/// \param offset is the given offset
|
||||||
/// \return \b true if \b this is a suitable PTRSUB data-type
|
/// \return \b true if \b this is a suitable PTRSUB data-type
|
||||||
bool Datatype::isPtrsubMatching(uintb offset) const
|
bool Datatype::isPtrsubMatching(uintb offset) const
|
||||||
|
|
||||||
|
@ -422,7 +422,7 @@ uint8 Datatype::hashName(const string &nm)
|
||||||
/// The hashing is reversible by feeding the output ID back into this function with the same size.
|
/// The hashing is reversible by feeding the output ID back into this function with the same size.
|
||||||
/// \param id is the given ID to (de)uniquify
|
/// \param id is the given ID to (de)uniquify
|
||||||
/// \param size is the instance size of the structure
|
/// \param size is the instance size of the structure
|
||||||
/// \param return the (de)uniquified id
|
/// \return the (de)uniquified id
|
||||||
uint8 Datatype::hashSize(uint8 id,int4 size)
|
uint8 Datatype::hashSize(uint8 id,int4 size)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -123,6 +123,7 @@ public:
|
||||||
/// Given a specific language and PcodeOp, emit the expression rooted at the operation.
|
/// Given a specific language and PcodeOp, emit the expression rooted at the operation.
|
||||||
/// \param lng is the PrintLanguage to emit
|
/// \param lng is the PrintLanguage to emit
|
||||||
/// \param op is the specific PcodeOp
|
/// \param op is the specific PcodeOp
|
||||||
|
/// \param readOp is the PcodeOp consuming the output (or null)
|
||||||
virtual void push(PrintLanguage *lng,const PcodeOp *op,const PcodeOp *readOp) const=0;
|
virtual void push(PrintLanguage *lng,const PcodeOp *op,const PcodeOp *readOp) const=0;
|
||||||
|
|
||||||
/// \brief Print (for debugging purposes) \b this specific PcodeOp to the stream
|
/// \brief Print (for debugging purposes) \b this specific PcodeOp to the stream
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
/// properly, in which case the union of the two ranges can exist without
|
/// properly, in which case the union of the two ranges can exist without
|
||||||
/// destroying data-type information.
|
/// destroying data-type information.
|
||||||
/// \param b is the range to reconcile with \b this
|
/// \param b is the range to reconcile with \b this
|
||||||
/// \param \b true if the data-type information can be reconciled
|
/// \return \b true if the data-type information can be reconciled
|
||||||
bool RangeHint::reconcile(const RangeHint *b) const
|
bool RangeHint::reconcile(const RangeHint *b) const
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -399,7 +399,7 @@ void Varnode::setSymbolEntry(SymbolEntry *entry)
|
||||||
/// This used when there is a constant address reference to the Symbol and the Varnode holds the
|
/// This used when there is a constant address reference to the Symbol and the Varnode holds the
|
||||||
/// reference, not the actual value of the Symbol.
|
/// reference, not the actual value of the Symbol.
|
||||||
/// \param entry is a mapping to the given Symbol
|
/// \param entry is a mapping to the given Symbol
|
||||||
/// \off is the byte offset into the Symbol of the reference
|
/// \param off is the byte offset into the Symbol of the reference
|
||||||
void Varnode::setSymbolReference(SymbolEntry *entry,int4 off)
|
void Varnode::setSymbolReference(SymbolEntry *entry,int4 off)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue