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>
269 lines
15 KiB
C++
269 lines
15 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.
|
|
*/
|
|
/// \file varmap.hh
|
|
/// \brief Classes for keeping track of local variables and reconstructing stack layout
|
|
|
|
#ifndef __VARMAP_HH__
|
|
#define __VARMAP_HH__
|
|
|
|
#include "database.hh"
|
|
|
|
namespace ghidra {
|
|
|
|
extern AttributeId ATTRIB_LOCK; ///< Marshaling attribute "lock"
|
|
extern AttributeId ATTRIB_MAIN; ///< Marshaling attribute "main"
|
|
|
|
extern ElementId ELEM_LOCALDB; ///< Marshaling element \<localdb>
|
|
|
|
/// \brief A symbol name recommendation with its associated storage location
|
|
///
|
|
/// The name is associated with a static Address and use point in the code. Symbols
|
|
/// present at the end of function decompilation without a name can acquire \b this name
|
|
/// if their storage matches.
|
|
class NameRecommend {
|
|
Address addr; ///< The starting address of the storage location
|
|
Address useaddr; ///< The code address at the point of use
|
|
int4 size; ///< An optional/recommended size for the variable being stored
|
|
string name; ///< The local symbol name recommendation
|
|
uint8 symbolId; ///< Id associated with the original Symbol
|
|
public:
|
|
NameRecommend(const Address &ad,const Address &use,int4 sz,const string &nm,uint8 id) :
|
|
addr(ad), useaddr(use), size(sz), name(nm), symbolId(id) {} ///< Constructor
|
|
const Address &getAddr(void) const { return addr; } ///< Get the storage address
|
|
const Address &getUseAddr(void) const { return useaddr; } ///< Get the use point address
|
|
int4 getSize(void) const { return size; } ///< Get the optional size
|
|
string getName(void) const { return name; } ///< Get the recommended name
|
|
uint8 getSymbolId(void) const { return symbolId; } ///< Get the original Symbol id
|
|
};
|
|
|
|
/// \brief A name recommendation for a particular dynamic storage location
|
|
///
|
|
/// A recommendation for a symbol name whose storage is dynamic. The storage
|
|
/// is identified using the DynamicHash mechanism and may or may not exist.
|
|
class DynamicRecommend {
|
|
Address usePoint; ///< Use point of the Symbol
|
|
uint8 hash; ///< Hash encoding the Symbols environment
|
|
string name; ///< The local symbol name recommendation
|
|
uint8 symbolId; ///< Id associated with the original Symbol
|
|
public:
|
|
DynamicRecommend(const Address &addr,uint8 h,const string &nm,uint8 id) :
|
|
usePoint(addr), hash(h), name(nm), symbolId(id) {} ///< Constructor
|
|
const Address &getAddress(void) const { return usePoint; } ///< Get the use point address
|
|
uint8 getHash(void) const { return hash; } ///< Get the dynamic hash
|
|
string getName(void) const { return name; } ///< Get the recommended name
|
|
uint8 getSymbolId(void) const { return symbolId; } ///< Get the original Symbol id
|
|
};
|
|
|
|
/// \brief Data-type for a storage location when there is no Symbol (yet)
|
|
///
|
|
/// Allow a data-type to be fed into a specific storage location. Currently
|
|
/// this only applies to input Varnodes.
|
|
class TypeRecommend {
|
|
Address addr; ///< Storage address of the Varnode
|
|
Datatype *dataType; ///< Data-type to assign to the Varnode
|
|
public:
|
|
TypeRecommend(const Address &ad,Datatype *dt) :
|
|
addr(ad), dataType(dt) {} ///< Constructor
|
|
const Address &getAddress(void) const { return addr; } ///< Get the storage address
|
|
Datatype *getType(void) const { return dataType; } ///< Get the data-type
|
|
};
|
|
|
|
/// \brief Partial data-type information mapped to a specific range of bytes
|
|
///
|
|
/// This object gives a hint about the data-type for a sequence of bytes
|
|
/// starting at a specific address offset (typically on the stack). It describes
|
|
/// where the data-type starts, what data-type it might be, and how far it extends
|
|
/// from the start point (possibly as an array).
|
|
class RangeHint {
|
|
friend class MapState;
|
|
friend class ScopeLocal;
|
|
public:
|
|
/// \brief The basic categorization of the range
|
|
enum RangeType {
|
|
fixed = 0, ///< A data-type with a fixed size
|
|
open = 1, ///< An array with a (possibly unknown) number of elements
|
|
endpoint = 2 ///< An (artificial) boundary to the range of bytes getting analyzed
|
|
};
|
|
/// \brief Boolean properties for the range
|
|
enum {
|
|
typelock = 1, ///< Data-type for the range is locked
|
|
copy_constant = 2 ///< Only a constant is COPYed into the range
|
|
};
|
|
private:
|
|
uintb start; ///< Starting offset of \b this range of bytes
|
|
int4 size; ///< Number of bytes in a single element of this range
|
|
intb sstart; ///< A signed version of the starting offset
|
|
Datatype *type; ///< Putative data-type for a single element of this range
|
|
uint4 flags; ///< Additional boolean properties of this range
|
|
RangeType rangeType; ///< The type of range
|
|
int4 highind; ///< Minimum upper bound on the array index (if \b this is \e open)
|
|
public:
|
|
RangeHint(void) {} ///< Uninitialized constructor
|
|
RangeHint(uintb st,int4 sz,intb sst,Datatype *ct,uint4 fl,RangeType rt,int4 hi) {
|
|
start=st; size=sz; sstart=sst; type=ct; flags=fl; rangeType = rt; highind=hi; } ///< Initialized constructor
|
|
bool isTypeLock(void) const { return ((flags & typelock)!=0); } ///< Is the data-type for \b this range locked
|
|
bool isConstAbsorbable(const RangeHint *b) const; ///< Can another range by absorbed into \b this as a constant
|
|
bool reconcile(const RangeHint *b) const;
|
|
bool contain(const RangeHint *b) const;
|
|
bool preferred(const RangeHint *b,bool reconcile) const;
|
|
bool attemptJoin(RangeHint *b); ///< Try to concatenate another RangeHint onto \b this
|
|
void absorb(RangeHint *b); ///< Absorb the other RangeHint into \b this
|
|
bool merge(RangeHint *b,AddrSpace *space,TypeFactory *typeFactory); ///< Try to form the union of \b this with another RangeHint
|
|
int4 compare(const RangeHint &op2) const; ///< Order \b this with another RangeHint
|
|
static bool compareRanges(const RangeHint *a,const RangeHint *b) { return (a->compare(*b) < 0); } ///< Compare two RangeHint pointers
|
|
};
|
|
|
|
class ProtoModel;
|
|
class LoadGuard;
|
|
|
|
/// \brief A light-weight class for analyzing pointers and aliasing on the stack
|
|
///
|
|
/// The gather() method looks for pointer references into a specific AddressSpace
|
|
/// (usually the stack). Then hasLocalAlias() checks if a specific Varnode within
|
|
/// the AddressSpace is (possibly) aliased by one of the gathered pointer references.
|
|
class AliasChecker {
|
|
public:
|
|
/// \brief A helper class holding a Varnode pointer reference and a possible index added to it
|
|
struct AddBase {
|
|
Varnode *base; ///< The Varnode holding the base pointer
|
|
Varnode *index; ///< The index value or NULL
|
|
AddBase(Varnode *b,Varnode *i) { base=b; index=i; } ///< Constructor
|
|
};
|
|
private:
|
|
const Funcdata *fd; ///< Function being searched for aliases
|
|
AddrSpace *space; ///< AddressSpace in which to search
|
|
mutable vector<AddBase> addBase; ///< Collection of pointers into the AddressSpace
|
|
mutable vector<uintb> alias; ///< List of aliased addresses (as offsets)
|
|
mutable bool calculated; ///< Have aliases been calculated
|
|
uintb localExtreme; ///< Largest possible offset for a local variable
|
|
uintb localBoundary; ///< Boundary offset separating locals and parameters
|
|
mutable uintb aliasBoundary; ///< Shallowest alias
|
|
int4 direction; ///< 1=stack grows negative, -1=positive
|
|
void deriveBoundaries(const FuncProto &proto); ///< Set up basic boundaries for the stack layout
|
|
void gatherInternal(void) const; ///< Run through Varnodes looking for pointers into the stack
|
|
public:
|
|
AliasChecker() { fd = (const Funcdata *)0; space = (AddrSpace *)0; calculated=false; } ///< Constructor
|
|
void gather(const Funcdata *f,AddrSpace *spc,bool defer); ///< Gather Varnodes that point on the stack
|
|
bool hasLocalAlias(Varnode *vn) const; ///< Return \b true if it looks like the given Varnode is aliased by a pointer
|
|
void sortAlias(void) const; ///< Sort the alias starting offsets
|
|
const vector<AddBase> &getAddBase(void) const { return addBase; } ///< Get the collection of pointer Varnodes
|
|
const vector<uintb> &getAlias(void) const { return alias; } ///< Get the list of alias starting offsets
|
|
static void gatherAdditiveBase(Varnode *startvn,vector<AddBase> &addbase);
|
|
static uintb gatherOffset(Varnode *vn);
|
|
};
|
|
|
|
/// \brief A container for hints about the data-type layout of an address space
|
|
///
|
|
/// A collection of data-type hints for the address space (as RangeHint objects) can
|
|
/// be collected from Varnodes, HighVariables or other sources, using the
|
|
/// gatherVarnodes(), gatherHighs(), and gatherOpen() methods. This class can then sort
|
|
/// and iterate through the RangeHint objects.
|
|
class MapState {
|
|
AddrSpace *spaceid; ///< The address space being analyzed
|
|
RangeList range; ///< The subset of ranges, within the whole address space to analyze
|
|
vector<RangeHint *> maplist; ///< The list of collected RangeHints
|
|
vector<RangeHint *>::iterator iter; ///< The current iterator into the RangeHints
|
|
Datatype *defaultType; ///< The default data-type to use for RangeHints
|
|
AliasChecker checker; ///< A collection of pointer Varnodes into our address space
|
|
void addGuard(const LoadGuard &guard,OpCode opc,TypeFactory *typeFactory); ///< Add LoadGuard record as a hint to the collection
|
|
void addRange(uintb st,Datatype *ct,uint4 fl,RangeHint::RangeType rt,int4 hi); ///< Add a hint to the collection
|
|
void addFixedType(uintb start,Datatype *ct,uint4 flags,TypeFactory *types); ///< Add a fixed reference to a specific data-type
|
|
void reconcileDatatypes(void); ///< Decide on data-type for RangeHints at the same address
|
|
static bool isReadActive(Varnode *vn); ///< Is the given Varnode read by an active operation
|
|
public:
|
|
#ifdef OPACTION_DEBUG
|
|
mutable bool debugon;
|
|
mutable Architecture *glb;
|
|
void turnOnDebug(Architecture *g) const { debugon = true; glb=g; }
|
|
void turnOffDebug(void) const { debugon = false; }
|
|
#endif
|
|
MapState(AddrSpace *spc,const RangeList &rn,const RangeList &pm,Datatype *dt); ///< Constructor
|
|
~MapState(void); ///< Destructor
|
|
bool initialize(void); ///< Initialize the hint collection for iteration
|
|
void sortAlias(void) { checker.sortAlias(); } ///< Sort the alias starting offsets
|
|
const vector<uintb> &getAlias(void) { return checker.getAlias(); } ///< Get the list of alias starting offsets
|
|
void gatherSymbols(const EntryMap *rangemap); ///< Add Symbol information as hints to the collection
|
|
void gatherVarnodes(const Funcdata &fd); ///< Add stack Varnodes as hints to the collection
|
|
void gatherOpen(const Funcdata &fd); ///< Add pointer references as hints to the collection
|
|
RangeHint *next(void) { return *iter; } ///< Get the current RangeHint in the collection
|
|
bool getNext(void) { ++iter; if (iter==maplist.end()) return false; return true; } ///< Advance the iterator, return \b true if another hint is available
|
|
};
|
|
|
|
/// \brief A Symbol scope for \e local variables of a particular function.
|
|
///
|
|
/// This acts like any other variable Scope, but is associated with a specific function
|
|
/// and the address space where the function maps its local variables and parameters, typically
|
|
/// the \e stack space. This object in addition to managing the local Symbols, builds up information
|
|
/// about the \e stack address space: what portions of it are used for mapped local variables, what
|
|
/// portions are used for temporary storage (not mapped), and what portion is for parameters.
|
|
class ScopeLocal : public ScopeInternal {
|
|
AddrSpace *space; ///< Address space containing the local stack
|
|
list<NameRecommend> nameRecommend; ///< Symbol name recommendations for specific addresses
|
|
list<DynamicRecommend> dynRecommend; ///< Symbol name recommendations for dynamic locations
|
|
list<TypeRecommend> typeRecommend; ///< Data-types for specific storage locations
|
|
uintb minParamOffset; ///< Minimum offset of parameter passed (to a called function) on the stack
|
|
uintb maxParamOffset; ///< Maximum offset of parameter passed (to a called function) on the stack
|
|
bool stackGrowsNegative; ///< Marked \b true if the stack is considered to \e grow towards smaller offsets
|
|
bool rangeLocked; ///< True if the subset of addresses \e mapped to \b this scope has been locked
|
|
bool overlapProblems; ///< True if the last \b restructure had overlapping variable problems
|
|
bool adjustFit(RangeHint &a) const; ///< Make the given RangeHint fit in the current Symbol map
|
|
void createEntry(const RangeHint &a); ///< Create a Symbol entry corresponding to the given (fitted) RangeHint
|
|
bool restructure(MapState &state); ///< Merge hints into a formal Symbol layout of the address space
|
|
void markUnaliased(const vector<uintb> &alias); ///< Mark all local symbols for which there are no aliases
|
|
void fakeInputSymbols(void); ///< Make sure all stack inputs have an associated Symbol
|
|
void addRecommendName(Symbol *sym); ///< Convert the given symbol to a name recommendation
|
|
void collectNameRecs(void); ///< Collect names of unlocked Symbols on the stack
|
|
void annotateRawStackPtr(void); ///< Generate placeholder PTRSUB off of stack pointer
|
|
void checkUnaliasedReturn(const vector<uintb> &alias); ///< Determine if return storage is mapped
|
|
public:
|
|
ScopeLocal(uint8 id,AddrSpace *spc,Funcdata *fd,Architecture *g); ///< Constructor
|
|
virtual ~ScopeLocal(void) {} ///< Destructor
|
|
|
|
AddrSpace *getSpaceId(void) const { return space; } ///< Get the associated (stack) address space
|
|
|
|
/// \brief Return \b true if \b restructure analysis discovered overlapping variables
|
|
bool hasOverlapProbems(void) const { return overlapProblems; }
|
|
|
|
/// \brief Is this a storage location for \e unaffected registers
|
|
///
|
|
/// \param vn is the Varnode storing an \e unaffected register
|
|
/// \return \b true is the Varnode can be used as unaffected storage
|
|
bool isUnaffectedStorage(Varnode *vn) const { return (vn->getSpace() == space); }
|
|
|
|
bool isUnmappedUnaliased(Varnode *vn) const; ///< Check if a given unmapped Varnode should be treated as unaliased.
|
|
|
|
void markNotMapped(AddrSpace *spc,uintb first,int4 sz,bool param); ///< Mark a specific address range is not mapped
|
|
|
|
// Routines that are specific to one address space
|
|
virtual void encode(Encoder &encoder) const;
|
|
virtual void decode(Decoder &decoder);
|
|
virtual void decodeWrappingAttributes(Decoder &decoder);
|
|
virtual string buildVariableName(const Address &addr,
|
|
const Address &pc,
|
|
Datatype *ct,
|
|
int4 &index,uint4 flags) const;
|
|
void resetLocalWindow(void); ///< Reset the set of addresses that are considered mapped by the scope to the default
|
|
void restructureVarnode(bool aliasyes); ///< Layout mapped symbols based on Varnode information
|
|
SymbolEntry *remapSymbol(Symbol *sym,const Address &addr,const Address &usepoint);
|
|
SymbolEntry *remapSymbolDynamic(Symbol *sym,uint8 hash,const Address &usepoint);
|
|
void recoverNameRecommendationsForSymbols(void);
|
|
void applyTypeRecommendations(void); ///< Try to apply recommended data-type information
|
|
bool hasTypeRecommendations(void) const { return !typeRecommend.empty(); } ///< Are there data-type recommendations
|
|
void addTypeRecommendation(const Address &addr,Datatype *dt); ///< Add a new data-type recommendation
|
|
};
|
|
|
|
} // End namespace ghidra
|
|
#endif
|