Foundation for replacing fold's dead/abstract binary encoder with a real
SSA P-Code frontend. Verified root-cause gap: serializeBinaryIR() emits only
data-discovery nodes (sections/strings/imports/exports); the control-flow
BinaryExprKind values (BasicBlock/CallSite/...) are never constructed, so the
Z3-BB encoder's encodable filter is always empty and encodeBinaryBBs never
runs — hence 0 binary Z3 findings. The P-Code pipeline supersedes that path.
Phase 0 — docs/internal/ghidra-pcode-integration-research.md: P-Code opcode
core, the decompiler C++ embedding API (LoadImage/SleighArchitecture/Funcdata),
the CORE+DECCORE+SLEIGH source sets, no-bison finding, and pypcode as build
reference.
Phase 1 — vendored Ghidra decompiler at pinned commit (Ghidra_12.1_build,
7e89d94e34...) under third_party/ghidra-decompiler/ (cpp + AArch64 specs +
Apache-2.0 LICENSE + VENDOR.md + .gitignore for build artifacts). Validated by
hand on this clang-21/macOS toolchain:
- full SSA engine (CORE+DECCORE+SLEIGH, 80 files incl. heritage/funcdata/
action) compiles clean, -std=c++11 -O2, NO BFD, NO bison (generated
parsers are committed upstream; ruleparse isn't in the lib set);
- sleigh compiler builds; AArch64 .slaspec -> .sla compiles;
- tools/pcode_dump.cpp (custom LoadImage, no BFD) lifts the real arm64
sym._div_by_zero_direct to correct P-Code: `mov w9,#0 -> COPY(const 0)`,
`sdiv -> INT_SDIV(w8,w9)` — divisor zero-ness explicit in the data flow,
so the Phase-3 encoder proves div-by-zero trivially, multi-arch.
Not yet meson-wired (next step) and no fold-side IR/encoder yet (Phase 2-3);
this commit is the validated vendor + smoke tool.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
618 lines
17 KiB
C++
618 lines
17 KiB
C++
/* ###
|
|
* IP: GHIDRA
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include "interface.hh"
|
|
#ifdef __REMOTE_SOCKET__
|
|
#include "sys/socket.h"
|
|
#include "sys/un.h"
|
|
#include "unistd.h"
|
|
#include "ext/stdio_filebuf.h"
|
|
#endif
|
|
|
|
namespace ghidra {
|
|
|
|
vector<IfaceCapability *> IfaceCapability::thelist;
|
|
|
|
void IfaceCapability::initialize(void)
|
|
|
|
{
|
|
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)
|
|
|
|
{
|
|
for(uint4 i=0;i<thelist.size();++i)
|
|
thelist[i]->registerCommands(status);
|
|
}
|
|
|
|
#ifdef __REMOTE_SOCKET__
|
|
|
|
RemoteSocket::RemoteSocket(void)
|
|
|
|
{
|
|
fileDescriptor = 0;
|
|
inbuf = (basic_filebuf<char> *)0;
|
|
outbuf = (basic_filebuf<char> *)0;
|
|
inStream = (istream *)0;
|
|
outStream = (ostream *)0;
|
|
isOpen = false;
|
|
}
|
|
|
|
void RemoteSocket::close(void)
|
|
|
|
{
|
|
if (inStream != (istream *)0) {
|
|
delete inStream;
|
|
inStream = (istream *)0;
|
|
}
|
|
if (outStream != (ostream *)0) {
|
|
delete outStream;
|
|
outStream = (ostream *)0;
|
|
}
|
|
if (inbuf != (basic_filebuf<char> *)0) {
|
|
// Destroying the buffer should automatically close the socket
|
|
delete inbuf;
|
|
inbuf = (basic_filebuf<char> *)0;
|
|
}
|
|
if (outbuf != (basic_filebuf<char> *)0) {
|
|
delete outbuf;
|
|
outbuf = (basic_filebuf<char> *)0;
|
|
}
|
|
isOpen = false;
|
|
}
|
|
|
|
bool RemoteSocket::open(const string &filename)
|
|
|
|
{
|
|
if (isOpen) return false;
|
|
if ((fileDescriptor = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
|
|
throw IfaceError("Could not create socket");
|
|
struct sockaddr_un addr;
|
|
addr.sun_family = AF_UNIX;
|
|
int4 len = filename.length();
|
|
if (len >= sizeof(addr.sun_path))
|
|
throw IfaceError("Socket name too long");
|
|
memcpy(addr.sun_path,filename.c_str(),len);
|
|
addr.sun_path[len] = '\0';
|
|
len += sizeof(addr.sun_family);
|
|
if (connect(fileDescriptor, (struct sockaddr *)&addr, len) < 0) {
|
|
::close(fileDescriptor);
|
|
return false;
|
|
}
|
|
|
|
fdopen(fileDescriptor, "r");
|
|
inbuf = new __gnu_cxx::stdio_filebuf<char>(fileDescriptor,ios::in);
|
|
fdopen(fileDescriptor, "w");
|
|
outbuf = new __gnu_cxx::stdio_filebuf<char>(fileDescriptor,ios::out);
|
|
inStream = new istream(inbuf);
|
|
outStream = new ostream(outbuf);
|
|
isOpen = true;
|
|
return true;
|
|
}
|
|
|
|
bool RemoteSocket::isSocketOpen(void)
|
|
|
|
{
|
|
if (!isOpen) return false;
|
|
if (inStream->eof()) {
|
|
close();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#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)
|
|
|
|
{
|
|
optr = &os;
|
|
fileoptr = optr; // Bulk out, defaults to command line output
|
|
sorted = false;
|
|
inerror = false;
|
|
errorisdone = false;
|
|
done = false;
|
|
prompt = prmpt;
|
|
maxhistory = mxhist;
|
|
curhistory = 0;
|
|
}
|
|
|
|
/// \brief Push a new file on the script stack
|
|
///
|
|
/// 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: "+filename);
|
|
pushScript(s,newprompt);
|
|
}
|
|
|
|
/// \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;
|
|
if (errorisdone)
|
|
flags |= 1;
|
|
flagstack.push_back(flags);
|
|
errorisdone = true; // Abort on first exception in a script
|
|
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)
|
|
|
|
{
|
|
prompt = promptstack.back();
|
|
promptstack.pop_back();
|
|
uint4 flags = flagstack.back();
|
|
flagstack.pop_back();
|
|
errorisdone = ((flags & 1)!=0);
|
|
inerror = false;
|
|
}
|
|
|
|
void IfaceStatus::reset(void)
|
|
|
|
{
|
|
while(!promptstack.empty())
|
|
popScript();
|
|
errorisdone = 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)
|
|
|
|
{
|
|
if (history.size() < maxhistory)
|
|
history.push_back(line);
|
|
else
|
|
history[curhistory] = line;
|
|
curhistory += 1;
|
|
if (curhistory == maxhistory)
|
|
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
|
|
|
|
{
|
|
if (i>=history.size())
|
|
return; // No change to line if history too far back
|
|
|
|
i = curhistory-1-i;
|
|
if (i<0) i+= maxhistory;
|
|
line = history[i];
|
|
}
|
|
|
|
// The last command has failed, decide if we are completely abandoning this stream
|
|
void IfaceStatus::evaluateError(void)
|
|
|
|
{
|
|
if (errorisdone) {
|
|
*optr << "Aborting process" << endl;
|
|
inerror = true;
|
|
done = true;
|
|
return;
|
|
}
|
|
if (getNumInputStreamSize()!=0) { // we have something to pop
|
|
*optr << "Aborting " << prompt << endl;
|
|
inerror = true;
|
|
return;
|
|
}
|
|
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)
|
|
|
|
{
|
|
vector<string>::const_iterator iter;
|
|
|
|
res.erase();
|
|
for(iter=list.begin();iter!=list.end();++iter) {
|
|
if (iter != list.begin())
|
|
res += ' ';
|
|
res += *iter;
|
|
}
|
|
}
|
|
|
|
IfaceStatus::~IfaceStatus(void)
|
|
|
|
{
|
|
if (optr != fileoptr) {
|
|
((ofstream *)fileoptr)->close();
|
|
delete fileoptr;
|
|
}
|
|
while(!promptstack.empty())
|
|
popScript();
|
|
for(int4 i=0;i<comlist.size();++i)
|
|
delete comlist[i];
|
|
map<string,IfaceData *>::const_iterator iter;
|
|
for(iter=datamap.begin();iter!=datamap.end();++iter)
|
|
if ((*iter).second != (IfaceData *)0)
|
|
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,
|
|
const char *nm2,
|
|
const char *nm3,
|
|
const char *nm4,
|
|
const char *nm5)
|
|
|
|
{
|
|
fptr->addWord(nm1);
|
|
if (nm2 != (const char *)0)
|
|
fptr->addWord(nm2);
|
|
if (nm3 != (const char *)0)
|
|
fptr->addWord(nm3);
|
|
if (nm4 != (const char *)0)
|
|
fptr->addWord(nm4);
|
|
if (nm5 != (const char *)0)
|
|
fptr->addWord(nm5);
|
|
|
|
comlist.push_back(fptr); // Enter new command
|
|
sorted = false;
|
|
|
|
const string &nm( fptr->getModule() ); // Name of module this command belongs to
|
|
map<string,IfaceData *>::const_iterator iter = datamap.find( nm );
|
|
IfaceData *data;
|
|
if (iter == datamap.end()) {
|
|
data = fptr->createData();
|
|
datamap[nm] = data;
|
|
}
|
|
else
|
|
data = (*iter).second;
|
|
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
|
|
|
|
{
|
|
map<string,IfaceData *>::const_iterator iter = datamap.find(nm);
|
|
if (iter == datamap.end())
|
|
return (IfaceData *)0;
|
|
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)
|
|
|
|
{
|
|
string line; // Next line from input stream
|
|
|
|
if (!sorted) {
|
|
sort(comlist.begin(),comlist.end(),compare_ifacecommand);
|
|
sorted = true;
|
|
}
|
|
readLine(line);
|
|
if (line.empty()) return false;
|
|
saveHistory(line);
|
|
|
|
vector<string> fullcommand;
|
|
vector<IfaceCommand *>::const_iterator first = comlist.begin();
|
|
vector<IfaceCommand *>::const_iterator last = comlist.end();
|
|
istringstream is(line);
|
|
int4 match;
|
|
|
|
match = expandCom(fullcommand, is,first,last); // Try to expand the command
|
|
if (match == 0) {
|
|
*optr << "ERROR: Invalid command" << endl;
|
|
return false;
|
|
}
|
|
else if ( fullcommand.size() == 0 ) // Nothing useful typed
|
|
return false;
|
|
else if (match>1) {
|
|
if ( (*first)->numWords() != fullcommand.size()) { // Check for complete but not unique
|
|
*optr << "ERROR: Incomplete command" << endl;
|
|
return false;
|
|
}
|
|
}
|
|
else if (match<0)
|
|
*optr << "ERROR: Incomplete command" << endl;
|
|
|
|
(*first)->execute(is); // Try to execute the (first) command
|
|
return true; // Indicate a command was executed
|
|
}
|
|
|
|
/// \brief Restrict range of possible commands given a list of command line tokens
|
|
///
|
|
/// 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;
|
|
IfaceCommandDummy dummy;
|
|
|
|
dummy.addWords(input);
|
|
newfirst = lower_bound(first,last,&dummy,compare_ifacecommand);
|
|
dummy.removeWord();
|
|
string temp( input.back() ); // Make copy of last word
|
|
temp[ temp.size()-1 ] += 1; // temp will now be greater than any word
|
|
// whose first letters match input.back()
|
|
dummy.addWord(temp);
|
|
newlast = upper_bound(first,last,&dummy,compare_ifacecommand);
|
|
first = newfirst;
|
|
last = newlast;
|
|
}
|
|
|
|
static bool maxmatch(string &res,const string &op1,const string &op2)
|
|
|
|
{ // Set res to maximum characters in common
|
|
// at the beginning of op1 and op2
|
|
int4 len;
|
|
|
|
len = ( op1.size() < op2.size() ) ? op1.size() : op2.size();
|
|
|
|
res.erase();
|
|
for(int4 i=0;i<len;++i) {
|
|
if (op1[i] == op2[i])
|
|
res += op1[i];
|
|
else
|
|
return false;
|
|
}
|
|
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,
|
|
vector<IfaceCommand *>::const_iterator &first,
|
|
vector<IfaceCommand *>::const_iterator &last)
|
|
|
|
{
|
|
int4 pos; // Which word are we currently expanding
|
|
string tok;
|
|
bool res;
|
|
|
|
expand.clear(); // Make sure command list is empty
|
|
res = true;
|
|
if (first == last) // If subrange is empty, return 0
|
|
return 0;
|
|
for(pos=0;;++pos) {
|
|
s >> ws; // Skip whitespace
|
|
if (first == (last-1)) { // If subrange is unique
|
|
if (s.eof()) // If no more input
|
|
for(;pos<(*first)->numWords();++pos) // Automatically provide missing words
|
|
expand.push_back( (*first)->getCommandWord(pos) );
|
|
if ((*first)->numWords() == pos) // If all words are matched
|
|
return 1; // Finished
|
|
}
|
|
if (!res) { // Last word was ambiguous
|
|
if (!s.eof())
|
|
return (last-first);
|
|
return (first-last); // Negative number to indicate last word incomplete
|
|
}
|
|
if (s.eof()) { // if no other words
|
|
if (expand.empty())
|
|
return (first-last);
|
|
return (last-first); // return number of matches
|
|
}
|
|
s >> tok; // Get next token
|
|
expand.push_back(tok);
|
|
restrictCom(first,last,expand);
|
|
if (first == last) // If subrange is empty, return 0
|
|
return 0;
|
|
res = maxmatch(tok, (*first)->getCommandWord(pos), (*(last-1))->getCommandWord(pos));
|
|
expand.back() = tok;
|
|
}
|
|
}
|
|
|
|
void IfaceCommand::addWords(const vector<string> &wordlist)
|
|
|
|
{
|
|
vector<string>::const_iterator iter;
|
|
|
|
for(iter=wordlist.begin();iter!=wordlist.end();++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 res;
|
|
vector<string>::const_iterator iter1,iter2;
|
|
|
|
for(iter1=com.begin(),iter2=op2.com.begin();;++iter1,++iter2) {
|
|
if (iter1 == com.end()) {
|
|
if (iter2 == op2.com.end())
|
|
return 0;
|
|
return -1; // This is less
|
|
}
|
|
if (iter2 == op2.com.end())
|
|
return 1;
|
|
res = (*iter1).compare( *iter2 );
|
|
if (res != 0)
|
|
return res;
|
|
}
|
|
return 0; // Never reaches here
|
|
}
|
|
|
|
/// \param res is overwritten with the full command line string
|
|
void IfaceCommand::commandString(string &res) const
|
|
|
|
{
|
|
IfaceStatus::wordsToString(res,com);
|
|
}
|
|
|
|
/// \class IfcQuit
|
|
/// \brief Quit command to terminate processing from the given interface
|
|
void IfcQuit::execute(istream &s)
|
|
|
|
{ // Generic quit call back
|
|
if (!s.eof())
|
|
throw IfaceParseError("Too many parameters to quit");
|
|
|
|
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)
|
|
|
|
{ // List most recent command lines
|
|
int4 num;
|
|
string historyline;
|
|
|
|
if (!s.eof()) {
|
|
s >> num >> ws;
|
|
if (!s.eof())
|
|
throw IfaceParseError("Too many parameters to history");
|
|
}
|
|
else
|
|
num = 10; // Default number of history lines
|
|
|
|
if (num > status->getHistorySize())
|
|
num = status->getHistorySize();
|
|
|
|
for(int4 i=num-1;i>=0;--i) { // List oldest to newest
|
|
status->getHistory(historyline,i);
|
|
*status->optr << historyline << endl;
|
|
}
|
|
}
|
|
|
|
/// \class IfcOpenfile
|
|
/// \brief Open file command to redirect bulk output to a specific file stream
|
|
void IfcOpenfile::execute(istream &s)
|
|
|
|
{
|
|
string filename;
|
|
|
|
if (status->optr != status->fileoptr)
|
|
throw IfaceExecutionError("Output file already opened");
|
|
s >> filename;
|
|
if (filename.empty())
|
|
throw IfaceParseError("No filename specified");
|
|
|
|
status->fileoptr = new ofstream;
|
|
((ofstream *)status->fileoptr)->open(filename.c_str());
|
|
if (!*status->fileoptr) {
|
|
delete status->fileoptr;
|
|
status->fileoptr = status->optr;
|
|
throw IfaceExecutionError("Unable to open file: "+filename);
|
|
}
|
|
}
|
|
|
|
/// \class IfcOpenfileAppend
|
|
/// \brief Open file command directing bulk output to be appended to a specific file
|
|
void IfcOpenfileAppend::execute(istream &s)
|
|
|
|
{
|
|
string filename;
|
|
|
|
if (status->optr != status->fileoptr)
|
|
throw IfaceExecutionError("Output file already opened");
|
|
s >> filename;
|
|
if (filename.empty())
|
|
throw IfaceParseError("No filename specified");
|
|
|
|
status->fileoptr = new ofstream;
|
|
((ofstream *)status->fileoptr)->open(filename.c_str(),ios_base::app); // Open for appending
|
|
if (!*status->fileoptr) {
|
|
delete status->fileoptr;
|
|
status->fileoptr = status->optr;
|
|
throw IfaceExecutionError("Unable to open file: "+filename);
|
|
}
|
|
}
|
|
|
|
/// \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)
|
|
|
|
{
|
|
if (status->optr == status->fileoptr)
|
|
throw IfaceExecutionError("No file open");
|
|
((ofstream *)status->fileoptr)->close();
|
|
delete status->fileoptr;
|
|
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)
|
|
|
|
{ // Echo command line to fileoptr
|
|
char c;
|
|
|
|
while(s.get(c))
|
|
status->fileoptr->put(c);
|
|
*status->fileoptr << endl;
|
|
}
|
|
|
|
} // End namespace ghidra
|