GP-4370 Internal Storage

This commit is contained in:
caheckman 2024-04-19 16:54:30 +00:00
parent f1e2c8db04
commit 05818c5c3a
17 changed files with 219 additions and 63 deletions

View file

@ -28,6 +28,7 @@ src/decompile/datatests/forloop_loaditer.xml||GHIDRA||||END|
src/decompile/datatests/forloop_thruspecial.xml||GHIDRA||||END|
src/decompile/datatests/forloop_varused.xml||GHIDRA||||END|
src/decompile/datatests/forloop_withskip.xml||GHIDRA||||END|
src/decompile/datatests/gp.xml||GHIDRA||||END|
src/decompile/datatests/ifswitch.xml||GHIDRA||||END|
src/decompile/datatests/impliedfield.xml||GHIDRA||||END|
src/decompile/datatests/indproto.xml||GHIDRA||||END|

View file

@ -3857,54 +3857,6 @@ uintb ActionDeadCode::gatherConsumedReturn(Funcdata &data)
return consumeVal;
}
/// \brief Determine if the given Varnode may eventually collapse to a constant
///
/// Recursively check if the Varnode is either:
/// - Copied from a constant
/// - The result of adding constants
/// - Loaded from a pointer that is a constant
///
/// \param vn is the given Varnode
/// \param addCount is the number of CPUI_INT_ADD operations seen so far
/// \param loadCount is the number of CPUI_LOAD operations seen so far
/// \return \b true if the Varnode (might) collapse to a constant
bool ActionDeadCode::isEventualConstant(Varnode *vn,int4 addCount,int4 loadCount)
{
if (vn->isConstant()) return true;
if (!vn->isWritten()) return false;
PcodeOp *op = vn->getDef();
while(op->code() == CPUI_COPY) {
vn = op->getIn(0);
if (vn->isConstant()) return true;
if (!vn->isWritten()) return false;
op = vn->getDef();
}
switch(op->code()) {
case CPUI_INT_ADD:
if (addCount > 0) return false;
if (!isEventualConstant(op->getIn(0),addCount+1,loadCount))
return false;
return isEventualConstant(op->getIn(1),addCount+1,loadCount);
case CPUI_LOAD:
if (loadCount > 0) return false;
return isEventualConstant(op->getIn(1),0,loadCount+1);
case CPUI_INT_LEFT:
case CPUI_INT_RIGHT:
case CPUI_INT_SRIGHT:
case CPUI_INT_MULT:
if (!op->getIn(1)->isConstant())
return false;
return isEventualConstant(op->getIn(0),addCount,loadCount);
case CPUI_INT_ZEXT:
case CPUI_INT_SEXT:
return isEventualConstant(op->getIn(0),addCount,loadCount);
default:
break;
}
return false;
}
/// \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
@ -3927,7 +3879,7 @@ bool ActionDeadCode::lastChanceLoad(Funcdata &data,vector<Varnode *> &worklist)
if (op->isDead()) continue;
Varnode *vn = op->getOut();
if (vn->isConsumeVacuous()) continue;
if (isEventualConstant(op->getIn(1), 0, 0)) {
if (op->getIn(1)->isEventualConstant(3, 1)) {
pushConsumed(~(uintb)0, vn, worklist);
vn->setAutoLiveHold();
res = true;
@ -4817,6 +4769,37 @@ int4 ActionPrototypeWarnings::apply(Funcdata &data)
return 0;
}
int4 ActionInternalStorage::apply(Funcdata &data)
{
FuncProto &proto( data.getFuncProto() );
vector<VarnodeData>::const_iterator iter = proto.internalBegin();
vector<VarnodeData>::const_iterator enditer = proto.internalEnd();
while(iter != enditer) {
Address addr = (*iter).getAddr();
int4 sz = (*iter).size;
++iter;
VarnodeLocSet::const_iterator viter = data.beginLoc(sz, addr);
VarnodeLocSet::const_iterator endviter = data.endLoc(sz, addr);
while(viter != endviter) {
Varnode *vn = *viter;
++viter;
list<PcodeOp *>::const_iterator oiter = vn->beginDescend();
while(oiter != vn->endDescend()) {
PcodeOp *op = *oiter;
++oiter;
if (op->code() == CPUI_STORE) {
if (vn->isEventualConstant(3,0)) {
op->setStoreUnmapped();
}
}
}
}
}
return 0;
}
#ifdef TYPEPROP_DEBUG
/// \brief Log a particular data-type propagation action.
///
@ -5399,6 +5382,7 @@ void ActionDatabase::universalAction(Architecture *conf)
actmainloop->addAction( new ActionHeritage("base") );
actmainloop->addAction( new ActionParamDouble("protorecovery") );
actmainloop->addAction( new ActionSegmentize("base"));
actmainloop->addAction( new ActionInternalStorage("base") );
actmainloop->addAction( new ActionForceGoto("blockrecovery") );
actmainloop->addAction( new ActionDirectWrite("protorecovery_a", true) );
actmainloop->addAction( new ActionDirectWrite("protorecovery_b", false) );

View file

@ -555,7 +555,6 @@ class ActionDeadCode : public Action {
static bool neverConsumed(Varnode *vn,Funcdata &data);
static void markConsumedParameters(FuncCallSpecs *fc,vector<Varnode *> &worklist);
static uintb gatherConsumedReturn(Funcdata &data);
static bool isEventualConstant(Varnode *vn,int4 addCount,int4 loadCount);
static bool lastChanceLoad(Funcdata &data,vector<Varnode *> &worklist);
public:
ActionDeadCode(const string &g) : Action(0,"deadcode",g) {} ///< Constructor
@ -1036,6 +1035,19 @@ public:
virtual int4 apply(Funcdata &data);
};
/// \brief Check for constants getting written to the stack from \e internal \e storage registers
///
/// The constant is internal to the compiler and its storage location on the stack should not be addressable.
class ActionInternalStorage : public Action {
public:
ActionInternalStorage(const string &g) : Action(rule_onceperfunc,"internalstorage",g) {} ///< Constructor
virtual Action *clone(const ActionGroupList &grouplist) const {
if (!grouplist.contains(getGroup())) return (Action *)0;
return new ActionInternalStorage(getGroup());
}
virtual int4 apply(Funcdata &data);
};
/// \brief A class that holds a data-type traversal state during type propagation
///
/// For a given Varnode, this class iterates all the possible edges its

View file

@ -49,6 +49,7 @@ ElementId ELEM_RESOLVEPROTOTYPE = ElementId("resolveprototype",170);
ElementId ELEM_RETPARAM = ElementId("retparam",171);
ElementId ELEM_RETURNSYM = ElementId("returnsym",172);
ElementId ELEM_UNAFFECTED = ElementId("unaffected",173);
ElementId ELEM_INTERNAL_STORAGE = ElementId("internal_storage",286);
/// \brief Find a ParamEntry matching the given storage Varnode
///
@ -2324,6 +2325,7 @@ ProtoModel::ProtoModel(const string &nm,const ProtoModel &op2)
effectlist = op2.effectlist;
likelytrash = op2.likelytrash;
internalstorage = op2.internalstorage;
injectUponEntry = op2.injectUponEntry;
injectUponReturn = op2.injectUponReturn;
@ -2515,6 +2517,7 @@ void ProtoModel::decode(Decoder &decoder)
injectUponEntry = -1;
injectUponReturn = -1;
likelytrash.clear();
internalstorage.clear();
uint4 elemId = decoder.openElement(ELEM_PROTOTYPE);
for(;;) {
uint4 attribId = decoder.getNextAttributeId();
@ -2612,6 +2615,14 @@ void ProtoModel::decode(Decoder &decoder)
}
decoder.closeElement(subId);
}
else if (subId == ELEM_INTERNAL_STORAGE) {
decoder.openElement();
while(decoder.peekElement() != 0) {
internalstorage.emplace_back();
internalstorage.back().decode(decoder);
}
decoder.closeElement(subId);
}
else if (subId == ELEM_PCODE) {
int4 injectId = glb->pcodeinjectlib->decodeInject("Protomodel : "+name, name,
InjectPayload::CALLMECHANISM_TYPE,decoder);
@ -2631,6 +2642,7 @@ void ProtoModel::decode(Decoder &decoder)
}
sort(effectlist.begin(),effectlist.end(),EffectRecord::compareByAddress);
sort(likelytrash.begin(),likelytrash.end());
sort(internalstorage.begin(),internalstorage.end());
if (!sawlocalrange)
defaultLocalRange();
if (!sawparamrange)
@ -2740,19 +2752,20 @@ void ProtoModelMerged::intersectEffects(const vector<EffectRecord> &efflist)
effectlist.swap(newlist);
}
/// The \e likely-trash locations are intersected. Anything in \b this that is not also in the
/// given \e likely-trash list is removed.
/// \param trashlist is the given \e likely-trash list
void ProtoModelMerged::intersectLikelyTrash(const vector<VarnodeData> &trashlist)
/// The intersection of two containers of register Varnodes is calculated, and the result is
/// placed in the first container, replacing the original contents. The containers must already be sorted.
/// \param regList1 is the first container
/// \param regList2 is the second container
void ProtoModelMerged::intersectRegisters(vector<VarnodeData> &regList1,const vector<VarnodeData> &regList2)
{
vector<VarnodeData> newlist;
int4 i=0;
int4 j=0;
while((i<likelytrash.size())&&(j<trashlist.size())) {
const VarnodeData &trs1( likelytrash[i] );
const VarnodeData &trs2( trashlist[j] );
while((i<regList1.size())&&(j<regList2.size())) {
const VarnodeData &trs1( regList1[i] );
const VarnodeData &trs2( regList2[j] );
if (trs1 < trs2)
i += 1;
@ -2764,7 +2777,7 @@ void ProtoModelMerged::intersectLikelyTrash(const vector<VarnodeData> &trashlist
j += 1;
}
}
likelytrash = newlist;
regList1.swap(newlist);
}
/// \param model is the new prototype model to add to the merge
@ -2795,7 +2808,8 @@ void ProtoModelMerged::foldIn(ProtoModel *model)
if ((injectUponEntry != model->injectUponEntry)||(injectUponReturn != model->injectUponReturn))
throw LowlevelError("Cannot merge prototype models with different inject ids");
intersectEffects(model->effectlist);
intersectLikelyTrash(model->likelytrash);
intersectRegisters(likelytrash,model->likelytrash);
intersectRegisters(internalstorage,model->internalstorage);
// Take the union of the localrange and paramrange
set<Range>::const_iterator iter;
for(iter=model->localrange.begin();iter!=model->localrange.end();++iter)

View file

@ -58,6 +58,7 @@ extern ElementId ELEM_RESOLVEPROTOTYPE; ///< Marshaling element \<resolveprototy
extern ElementId ELEM_RETPARAM; ///< Marshaling element \<retparam>
extern ElementId ELEM_RETURNSYM; ///< Marshaling element \<returnsym>
extern ElementId ELEM_UNAFFECTED; ///< Marshaling element \<unaffected>
extern ElementId ELEM_INTERNAL_STORAGE; ///< Marshaling element \<internal_storage>
/// \brief Exception thrown when a prototype can't be modeled properly
struct ParamUnassignedError : public LowlevelError {
@ -749,6 +750,7 @@ class ProtoModel {
const ProtoModel *compatModel; ///< The model \b this is a copy of
vector<EffectRecord> effectlist; ///< List of side-effects
vector<VarnodeData> likelytrash; ///< Storage locations potentially carrying \e trash values
vector<VarnodeData> internalstorage; ///< Registers that hold internal compiler constants
int4 injectUponEntry; ///< Id of injection to perform at beginning of function (-1 means not used)
int4 injectUponReturn; ///< Id of injection to perform after a call to this function (-1 means not used)
RangeList localrange; ///< Memory range(s) of space-based locals
@ -834,6 +836,8 @@ public:
vector<EffectRecord>::const_iterator effectEnd(void) const { return effectlist.end(); } ///< Get an iterator to the last EffectRecord
vector<VarnodeData>::const_iterator trashBegin(void) const { return likelytrash.begin(); } ///< Get an iterator to the first \e likelytrash
vector<VarnodeData>::const_iterator trashEnd(void) const { return likelytrash.end(); } ///< Get an iterator to the last \e likelytrash
vector<VarnodeData>::const_iterator internalBegin(void) const { return internalstorage.begin(); } ///< Get an iterator to the first \e internalstorage
vector<VarnodeData>::const_iterator internalEnd(void) const { return internalstorage.end(); } ///< Get an iterator to the last \e internalstorage
/// \brief Characterize whether the given range overlaps parameter storage
///
@ -1068,7 +1072,7 @@ public:
class ProtoModelMerged : public ProtoModel {
vector<ProtoModel *> modellist; ///< Constituent models being merged
void intersectEffects(const vector<EffectRecord> &efflist); ///< Fold EffectRecords into \b this model
void intersectLikelyTrash(const vector<VarnodeData> &trashlist); ///< Fold \e likelytrash locations into \b this model
static void intersectRegisters(vector<VarnodeData> &regList1,const vector<VarnodeData> &regList2);
public:
ProtoModelMerged(Architecture *g) : ProtoModel(g) {} ///< Constructor
virtual ~ProtoModelMerged(void) {} ///< Destructor
@ -1539,6 +1543,8 @@ public:
vector<EffectRecord>::const_iterator effectEnd(void) const; ///< Get iterator to end of EffectRecord list
vector<VarnodeData>::const_iterator trashBegin(void) const; ///< Get iterator to front of \e likelytrash list
vector<VarnodeData>::const_iterator trashEnd(void) const; ///< Get iterator to end of \e likelytrash list
vector<VarnodeData>::const_iterator internalBegin(void) const { return model->internalBegin(); } ///< Get iterator to front of \e internalstorage list
vector<VarnodeData>::const_iterator internalEnd(void) const { return model->internalEnd(); } ///< Get iterator to end of \e internalstorage list
int4 characterizeAsInputParam(const Address &addr,int4 size) const;
int4 characterizeAsOutput(const Address &addr,int4 size) const;
bool possibleInputParam(const Address &addr,int4 size) const;

View file

@ -1972,6 +1972,8 @@ int4 AncestorRealistic::enterNode(void)
if (!vn->isDirectWrite())
return pop_fail;
}
if (op->isStoreUnmapped())
return pop_fail;
op = vn->getDef();
if (op == (PcodeOp *)0) break;
OpCode opc = op->code();

View file

@ -1266,6 +1266,6 @@ ElementId ELEM_VAL = ElementId("val",8);
ElementId ELEM_VALUE = ElementId("value",9);
ElementId ELEM_VOID = ElementId("void",10);
ElementId ELEM_UNKNOWN = ElementId("XMLunknown",286); // Number serves as next open index
ElementId ELEM_UNKNOWN = ElementId("XMLunknown",287); // Number serves as next open index
} // End namespace ghidra

View file

@ -114,7 +114,8 @@ public:
stop_type_propagation = 0x40, ///< Stop data-type propagation into output from descendants
hold_output = 0x80, ///< Output varnode (of call) should not be removed if it is unread
concat_root = 0x100, ///< Output of \b this is root of a CONCAT tree
no_indirect_collapse = 0x200 ///< Do not collapse \b this INDIRECT (via RuleIndirectCollapse)
no_indirect_collapse = 0x200, ///< Do not collapse \b this INDIRECT (via RuleIndirectCollapse)
store_unmapped = 0x400 ///< If STORE collapses to a stack Varnode, force it to be unmapped
};
private:
TypeOp *opcode; ///< Pointer to class providing behavioral details of the operation
@ -221,6 +222,8 @@ public:
void setStopCopyPropagation(void) { flags |= no_copy_propagation; } ///< Stop COPY propagation through inputs
bool noIndirectCollapse(void) const { return ((addlflags & no_indirect_collapse)!=0); } ///< Check if INDIRECT collapse is possible
void setNoIndirectCollapse(void) { addlflags |= no_indirect_collapse; } ///< Prevent collapse of INDIRECT
bool isStoreUnmapped(void) const { return ((addlflags & store_unmapped)!=0); } ///< Is STORE location supposed to be unmapped
void setStoreUnmapped(void) const { addlflags |= store_unmapped; } ///< Mark that STORE location should be unmapped
/// \brief Return \b true if this LOADs or STOREs from a dynamic \e spacebase pointer
bool usesSpacebasePtr(void) const { return ((flags&PcodeOp::spacebase_ptr)!=0); }
uintm getCseHash(void) const; ///< Return hash indicating possibility of common subexpression elimination

View file

@ -4010,6 +4010,9 @@ int4 RuleStoreVarnode::applyOp(PcodeOp *op,Funcdata &data)
data.opRemoveInput(op,1);
data.opRemoveInput(op,0);
data.opSetOpcode(op, CPUI_COPY );
if (op->isStoreUnmapped()) {
data.getScopeLocal()->markNotMapped(baseoff, offoff, size, false);
}
return 1;
}

View file

@ -831,6 +831,55 @@ bool Varnode::isConstantExtended(uint8 *val) const
return false;
}
/// Recursively check if the Varnode is either:
/// - Copied or extended from a constant
/// - The result of arithmetic or logical operations on constants
/// - Loaded from a pointer that is a constant
///
/// \param maxBinary is the maximum depth of binary operations to inspect (before giving up)
/// \param maxLoad is the maximum number of CPUI_LOAD operations to allow in a sequence
/// \return \b true if the Varnode (might) collapse to a constant
bool Varnode::isEventualConstant(int4 maxBinary,int4 maxLoad) const
{
const Varnode *curVn = this;
while(!curVn->isConstant()) {
if (!curVn->isWritten()) return false;
const PcodeOp *op = curVn->getDef();
switch(op->code()) {
case CPUI_LOAD:
if (maxLoad == 0) return false;
maxLoad -= 1;
curVn = op->getIn(1);
break;
case CPUI_INT_ADD:
case CPUI_INT_SUB:
case CPUI_INT_XOR:
case CPUI_INT_OR:
case CPUI_INT_AND:
if (maxBinary == 0) return false;
if (!op->getIn(0)->isEventualConstant(maxBinary-1,maxLoad))
return false;
return op->getIn(1)->isEventualConstant(maxBinary-1,maxLoad);
case CPUI_INT_ZEXT:
case CPUI_INT_SEXT:
case CPUI_COPY:
curVn = op->getIn(0);
break;
case CPUI_INT_LEFT:
case CPUI_INT_RIGHT:
case CPUI_INT_SRIGHT:
case CPUI_INT_MULT:
if (!op->getIn(1)->isConstant()) return false;
curVn = op->getIn(0);
break;
default:
return false;
}
}
return true;
}
/// Make an initial determination of the Datatype of this Varnode. If a Datatype is already
/// set and locked return it. Otherwise look through all the read PcodeOps and the write PcodeOp
/// to determine if the Varnode is getting used as an \b int, \b float, or \b pointer, etc.

View file

@ -291,6 +291,8 @@ public:
}
bool isConstantExtended(uint8 *val) const; ///< Is \b this an (extended) constant
bool isEventualConstant(int4 maxBinary,int4 maxLoad) const; ///< Will \b this Varnode ultimately collapse to a constant
/// Return \b true if this Varnode is linked into the SSA tree
bool isHeritageKnown(void) const { return ((flags&(Varnode::insert|Varnode::constant|Varnode::annotation))!=0); }
bool isTypeLock(void) const { return ((flags&Varnode::typelock)!=0); } ///< Does \b this have a locked Datatype?

View file

@ -0,0 +1,35 @@
<decompilertest>
<!-- Example of MIPS -fPIC code storing the gp register on the stack.
The decompiler needs to be able to propagate the constant through the stack location.
This allows the printf call and "Hello" string to be recovered. -->
<binaryimage arch="MIPS:BE:32:default:default">
<bytechunk space="ram" offset="0x410040" readonly="true">
3c1c0041279c00200399e02127bdffd0
8f990094afbf0024afb00028afbc0020
008080250320f80927a400188fbc0020
020028258f8400908f99009800000000
0320f809248400008fbf00248fa2001c
8fb0002003e0000827bd0028
</bytechunk>
<bytechunk space="ram" offset="0x4100a0" readonly="true">
48656c6c6f00
</bytechunk>
<bytechunk space="ram" offset="0x4100b0" readonly="true">
004100a00041003000410024
</bytechunk>
<symbol space="ram" offset="0x410024" name="printf"/>
<symbol space="ram" offset="0x410030" name="populate"/>
<symbol space="ram" offset="0x410040" name="test_gp"/>
</binaryimage>
<script>
<com>option readonly on</com>
<com>set track t9 0x0 0x410040 0x410041</com>
<com>parse line extern void printf(char *,...);</com>
<com>lo fu test_gp</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Gp Test #1" min="1" max="1">populate\(axStack_18\);</stringmatch>
<stringmatch name="Gp Test #2" min="1" max="1">printf\("Hello",param_1\);</stringmatch>
</decompilertest>

View file

@ -544,6 +544,14 @@
</element>
</optional>
<optional>
<element name="internal_storage">
<oneOrMore>
<ref name="varnode_tags_type"/>
</oneOrMore>
</element>
</optional>
<optional>
<element name="localrange">
<ref name="rangelist_type"/>

View file

@ -50,6 +50,7 @@ public class PrototypeModel {
private Varnode[] killedbycall; // Memory ranges definitely affected by calls
private Varnode[] returnaddress; // Memory used to store the return address
private Varnode[] likelytrash; // Memory likely to be meaningless on input
private Varnode[] internalstorage; // Registers holding internal compiler constants
private PrototypeModel compatModel; // The model this is an alias of
private AddressSet localRange; // Range on the stack considered for local storage
private AddressSet paramRange; // Range on the stack considered for parameter storage
@ -81,6 +82,7 @@ public class PrototypeModel {
killedbycall = model.killedbycall;
returnaddress = model.returnaddress;
likelytrash = model.likelytrash;
internalstorage = model.internalstorage;
compatModel = model;
localRange = new AddressSet(model.localRange);
paramRange = new AddressSet(model.paramRange);
@ -101,6 +103,7 @@ public class PrototypeModel {
killedbycall = null;
returnaddress = null;
likelytrash = null;
internalstorage = null;
compatModel = null;
localRange = null;
paramRange = null;
@ -140,6 +143,16 @@ public class PrototypeModel {
return likelytrash;
}
/**
* @return list of registers used to store internal compiler constants
*/
public Varnode[] getInternalStorage() {
if (internalstorage == null) {
internalstorage = new Varnode[0];
}
return internalstorage;
}
/**
* @return list of registers/memory used to store the return address
*/
@ -456,6 +469,11 @@ public class PrototypeModel {
encodeVarnodes(encoder, likelytrash);
encoder.closeElement(ELEM_LIKELYTRASH);
}
if (internalstorage != null) {
encoder.openElement(ELEM_INTERNAL_STORAGE);
encodeVarnodes(encoder, internalstorage);
encoder.closeElement(ELEM_INTERNAL_STORAGE);
}
if (returnaddress != null) {
encoder.openElement(ELEM_RETURNADDRESS);
encodeVarnodes(encoder, returnaddress);
@ -633,6 +651,9 @@ public class PrototypeModel {
else if (elName.equals("likelytrash")) {
likelytrash = readVarnodes(parser, cspec);
}
else if (elName.equals("internal_storage")) {
internalstorage = readVarnodes(parser, cspec);
}
else if (elName.equals("localrange")) {
localRange = readAddressSet(parser, cspec);
}
@ -741,6 +762,9 @@ public class PrototypeModel {
if (!SystemUtilities.isArrayEqual(likelytrash, obj.likelytrash)) {
return false;
}
if (!SystemUtilities.isArrayEqual(internalstorage, obj.internalstorage)) {
return false;
}
String compatName = (compatModel != null) ? compatModel.getName() : "";
String compatNameOp2 = (obj.compatModel != null) ? obj.compatModel.getName() : "";
if (!compatName.equals(compatNameOp2)) {

View file

@ -268,6 +268,7 @@ public record ElementId(String name, int id) {
public static final ElementId ELEM_RETPARAM = new ElementId("retparam", 171);
public static final ElementId ELEM_RETURNSYM = new ElementId("returnsym", 172);
public static final ElementId ELEM_UNAFFECTED = new ElementId("unaffected", 173);
public static final ElementId ELEM_INTERNAL_STORAGE = new ElementId("internal_storage", 286);
// options
public static final ElementId ELEM_ALIASBLOCK = new ElementId("aliasblock", 174);
@ -456,5 +457,5 @@ public record ElementId(String name, int id) {
new ElementId("join_per_primitive", 283);
public static final ElementId ELEM_JOIN_DUAL_CLASS = new ElementId("join_dual_class", 285);
public static final ElementId ELEM_UNKNOWN = new ElementId("XMLunknown", 286);
public static final ElementId ELEM_UNKNOWN = new ElementId("XMLunknown", 287);
}

View file

@ -78,6 +78,9 @@
<register name="f28"/>
<register name="f30"/>
</unaffected>
<internal_storage>
<register name="gp"/> <!-- Compilers may save gp to the stack before a call and restore it afterward -->
</internal_storage>
<localrange>
<range space="stack" first="0xfff0bdc0" last="0xffffffff"/>
<range space="stack" first="0" last="15"/> <!-- This is backup storage space for register params, but we treat as locals -->
@ -101,6 +104,9 @@
<unaffected>
<register name="sp"/>
</unaffected>
<internal_storage>
<register name="gp"/> <!-- Compilers may save gp to the stack before a call and restore it afterward -->
</internal_storage>
</prototype>

View file

@ -78,6 +78,9 @@
<register name="f28"/>
<register name="f30"/>
</unaffected>
<internal_storage>
<register name="gp"/> <!-- Compilers may save gp to the stack before a call and restore it afterward -->
</internal_storage>
<localrange>
<range space="stack" first="0xfff0bdc0" last="0xffffffff"/>
<range space="stack" first="0" last="15"/> <!-- This is backup storage space for register params, but we treat as locals -->
@ -101,6 +104,9 @@
<unaffected>
<register name="sp"/>
</unaffected>
<internal_storage>
<register name="gp"/> <!-- Compilers may save gp to the stack before a call and restore it afterward -->
</internal_storage>
</prototype>
</compiler_spec>