Merge remote-tracking branch 'origin/GP-4790_CopyForceForm'

This commit is contained in:
Ryan Kurtz 2024-08-26 12:31:44 -04:00
commit bc1f6b38be
7 changed files with 358 additions and 44 deletions

View File

@ -21,6 +21,7 @@ src/decompile/datatests/deindirect.xml||GHIDRA||||END|
src/decompile/datatests/deindirect2.xml||GHIDRA||||END|
src/decompile/datatests/displayformat.xml||GHIDRA||||END|
src/decompile/datatests/divopt.xml||GHIDRA||||END|
src/decompile/datatests/doublemove.xml||GHIDRA||||END|
src/decompile/datatests/dupptr.xml||GHIDRA||||END|
src/decompile/datatests/elseif.xml||GHIDRA||||END|
src/decompile/datatests/floatcast.xml||GHIDRA||||END|

View File

@ -5545,6 +5545,7 @@ void ActionDatabase::universalAction(Architecture *conf)
actprop->addRule( new RuleDoubleLoad("doubleload") );
actprop->addRule( new RuleDoubleStore("doubleprecis") );
actprop->addRule( new RuleDoubleIn("doubleprecis") );
actprop->addRule( new RuleDoubleOut("doubleprecis") );
for(iter=conf->extra_pool_rules.begin();iter!=conf->extra_pool_rules.end();++iter)
actprop->addRule( *iter ); // Add CPU specific rules
conf->extra_pool_rules.clear(); // Rules are now absorbed into universal

View File

@ -4,9 +4,9 @@
* 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.
@ -1220,6 +1220,13 @@ int4 SplitVarnode::applyRuleIn(SplitVarnode &in,Funcdata &data)
return 1;
}
break;
case CPUI_COPY:
if (workop->getOut()->isAddrForce()) {
CopyForceForm copyform;
if (copyform.applyRule(in, workop, workishi, data))
return 1;
}
break;
default:
break;
}
@ -1385,6 +1392,47 @@ void SplitVarnode::replaceIndirectOp(Funcdata &data,SplitVarnode &out,SplitVarno
out.buildHiFromWhole(data);
}
/// \brief Rewrite the double precision version of a COPY to an address forced Varnode
///
/// This assumes that we have checked that the transformation is possible. The logical input must already
/// exist, and after this method is called, the logical output will also exist. The original COPY pieces
/// are explicitly destroyed.
/// \param data is the function owning the COPYs
/// \param addr is the storage address being COPYed
/// \param in is the input to the COPYs
/// \param copylo is the original least significant COPY
/// \param copyhi is the original most significant COPY
void SplitVarnode::replaceCopyForce(Funcdata &data,const Address &addr,SplitVarnode &in,PcodeOp *copylo,PcodeOp *copyhi)
{
Varnode *inVn = in.getWhole();
bool returnForm = copyhi->stopsCopyPropagation();
if (returnForm && inVn->getAddr() != addr) {
// Placeholder for global propagation past a RETURN needs an additional COPY
PcodeOp *otherPoint1 = copyhi->getIn(0)->getDef();
PcodeOp *otherPoint2 = copylo->getIn(0)->getDef();
// We know these are COPYs in the same basic block. Compute the later one.
if (otherPoint1->getSeqNum().getOrder() < otherPoint2->getSeqNum().getOrder())
otherPoint1 = otherPoint2;
PcodeOp *otherCopy = data.newOp(1, otherPoint1->getAddr());
data.opSetOpcode(otherCopy, CPUI_COPY);
Varnode *vn = data.newVarnodeOut(in.getSize(), addr, otherCopy);
data.opSetInput(otherCopy,inVn,0);
data.opInsertBefore(otherCopy, otherPoint1);
inVn = vn;
}
PcodeOp *wholeCopy = data.newOp(1, copyhi->getAddr());
data.opSetOpcode(wholeCopy, CPUI_COPY);
Varnode *outVn = data.newVarnodeOut(in.getSize(), addr, wholeCopy);
outVn->setAddrForce();
if (returnForm)
wholeCopy->setStopCopyPropagation();
data.opSetInput(wholeCopy,inVn,0);
data.opInsertBefore(wholeCopy, copyhi);
data.opDestroy(copyhi); // Destroy the original COPYs. Outputs have no descendants.
data.opDestroy(copylo);
}
bool AddForm::checkForCarry(PcodeOp *op)
{ // If -op- matches a CARRY construction based on lo1 (i.e. CARRY(x,lo1) )
@ -3151,6 +3199,73 @@ bool IndirectForm::applyRule(SplitVarnode &i,PcodeOp *ind,bool workishi,Funcdata
return true;
}
/// Starting with the input pieces, identify the matching COPYs and verify that they act as a single
/// address forced COPY with no descendants.
/// \param h is the most significant input piece
/// \param l is the least significant input piece
/// \param w is the preexisting logical whole
/// \param cpy is the COPY of the most significant piece
bool CopyForceForm::verify(Varnode *h,Varnode *l,Varnode *w,PcodeOp *cpy)
{
if (w == (Varnode *)0)
return false;
copyhi = cpy;
if (copyhi->getIn(0) != h) return false;
reshi = copyhi->getOut();
if (!reshi->isAddrForce() || !reshi->hasNoDescend())
return false;
list<PcodeOp *>::const_iterator iter,enditer;
iter = l->beginDescend();
enditer = l->endDescend();
while(iter != enditer) {
copylo = *iter;
++iter;
if (copylo->code() != CPUI_COPY || copylo->getParent() != copyhi->getParent())
continue;
reslo = copylo->getOut();
if (!reslo->isAddrForce() || !reslo->hasNoDescend())
continue;
if (!SplitVarnode::isAddrTiedContiguous(reslo, reshi, addrOut)) // Output MUST be contiguous addresses
continue;
if (copyhi->stopsCopyPropagation()) { // Special form has additional requirements
if (h->loneDescend() == (PcodeOp *)0)
continue;
if (l->loneDescend() == (PcodeOp *)0)
continue;
if (w->getAddr() != addrOut) { // Input whole MUST also be the same address
// Unless there are addition COPYs from the same basic block
if (!h->isWritten() || !l->isWritten())
continue;
PcodeOp *otherLo = l->getDef();
PcodeOp *otherHi = h->getDef();
if (otherLo->code() != CPUI_COPY || otherHi->code() != CPUI_COPY)
continue;
if (otherLo->getParent() != otherHi->getParent())
continue;
}
}
return true;
}
return false;
}
/// \param i is the putative input to the COPYs
/// \param cpy is a putative COPY
/// \param workishi is \b true if the COPY is of the most significant piece
/// \param data is the function
bool CopyForceForm::applyRule(SplitVarnode &i,PcodeOp *cpy,bool workishi,Funcdata &data)
{
if (!workishi) return false;
if (!i.hasBothPieces()) return false;
in = i;
if (!verify(in.getHi(),in.getLo(),in.getWhole(),cpy))
return false;
SplitVarnode::replaceCopyForce(data, addrOut, in, copylo, copyhi);
return true;
}
void RuleDoubleIn::reset(Funcdata &data)
{
@ -3168,10 +3283,10 @@ void RuleDoubleIn::getOpList(vector<uint4> &oplist) const
/// If the given Varnode looks like the most significant piece, there is another SUBPIECE that looks
/// like the least significant piece, and the whole is from an operation that produces a logical whole,
/// then mark the Varnode (and its companion) as double precision pieces and return 1.
/// \param data is the function owning the Varnode
/// \param vn is the given Varnode
/// \param subpieceOp is the SUBPIECE PcodeOp producing the Varnode
int4 RuleDoubleIn::attemptMarking(Funcdata &data,Varnode *vn,PcodeOp *subpieceOp)
/// \return 1 if the pieces are marked, 0 otherwise
int4 RuleDoubleIn::attemptMarking(Varnode *vn,PcodeOp *subpieceOp)
{
Varnode *whole = subpieceOp->getIn(0);
@ -3190,36 +3305,10 @@ int4 RuleDoubleIn::attemptMarking(Funcdata &data,Varnode *vn,PcodeOp *subpieceOp
}
else {
// Categorize opcodes as "producing a logical whole"
switch(whole->getDef()->code()) {
case CPUI_INT_ADD:
// Its hard to tell if the bit operators are really being used to act on the "logical whole"
// case CPUI_INT_AND:
// case CPUI_INT_OR:
// case CPUI_INT_XOR:
// case CPUI_INT_NEGATE:
case CPUI_INT_MULT:
case CPUI_INT_DIV:
case CPUI_INT_SDIV:
case CPUI_INT_REM:
case CPUI_INT_SREM:
case CPUI_INT_2COMP:
case CPUI_FLOAT_ADD:
case CPUI_FLOAT_DIV:
case CPUI_FLOAT_MULT:
case CPUI_FLOAT_SUB:
case CPUI_FLOAT_NEG:
case CPUI_FLOAT_ABS:
case CPUI_FLOAT_SQRT:
case CPUI_FLOAT_INT2FLOAT:
case CPUI_FLOAT_FLOAT2FLOAT:
case CPUI_FLOAT_TRUNC:
case CPUI_FLOAT_CEIL:
case CPUI_FLOAT_FLOOR:
case CPUI_FLOAT_ROUND:
break;
default:
return 0;
}
// Its hard to tell if a logical op is really being used to act on the "logical whole"
TypeOp *typeop = whole->getDef()->getOpcode();
if (!typeop->isArithmeticOp() && !typeop->isFloatingPointOp())
return 0;
}
Varnode *vnLo = (Varnode *)0;
list<PcodeOp *>::const_iterator iter;
@ -3240,11 +3329,11 @@ int4 RuleDoubleIn::attemptMarking(Funcdata &data,Varnode *vn,PcodeOp *subpieceOp
int4 RuleDoubleIn::applyOp(PcodeOp *op,Funcdata &data)
{ // Try to push double precision object "down" one level from input
{
Varnode *outvn = op->getOut();
if (!outvn->isPrecisLo()) {
if (outvn->isPrecisHi()) return 0;
return attemptMarking(data, outvn, op);
return attemptMarking(outvn, op);
}
if (data.hasUnreachableBlocks()) return 0;
@ -3260,6 +3349,82 @@ int4 RuleDoubleIn::applyOp(PcodeOp *op,Funcdata &data)
return 0;
}
void RuleDoubleOut::getOpList(vector<uint4> &oplist) const
{
oplist.push_back(CPUI_PIECE);
}
/// \brief Determine if the given inputs to a PIECE should marked as double precision pieces
///
/// If the concatenation of the pieces is used as a logical whole by other ops, the two pieces
/// are marked and 1 is returned.
/// \param vnhi is the most significant input to the PIECE
/// \param vnlo is the least significant input
/// \param pieceOp is the op reading the pieces
/// \return 1 if the pieces are marked, 0 otherwise
int4 RuleDoubleOut::attemptMarking(Varnode *vnhi,Varnode *vnlo,PcodeOp *pieceOp)
{
Varnode *whole = pieceOp->getOut();
if (whole->isTypeLock()) {
if (!whole->getType()->isPrimitiveWhole())
return 0; // Don't mark for double precision if not a primitive type
}
if (vnhi->getSize() != vnlo->getSize())
return 0;
SymbolEntry *entryhi = vnhi->getSymbolEntry();
SymbolEntry *entrylo = vnlo->getSymbolEntry();
if (entryhi != (SymbolEntry *)0 || entrylo != (SymbolEntry *)0) {
if (entryhi == (SymbolEntry *)0 || entrylo == (SymbolEntry *)0)
return 0; // One has a symbol, one doesn't
if (entryhi->getSymbol() != entrylo->getSymbol())
return 0; // Not from the same symbol
}
bool isWhole = false;
list<PcodeOp *>::const_iterator iter;
for(iter=whole->beginDescend();iter!=whole->endDescend();++iter) {
TypeOp *typeop = (*iter)->getOpcode();
// Categorize op as "reading a logical whole"
if (typeop->isArithmeticOp() || typeop->isFloatingPointOp()) {
isWhole = true;
break;
}
}
if (!isWhole)
return 0;
vnhi->setPrecisHi();
vnlo->setPrecisLo();
return 1;
}
int4 RuleDoubleOut::applyOp(PcodeOp *op,Funcdata &data)
{
Varnode *vnhi= op->getIn(0);
Varnode *vnlo = op->getIn(1);
// Currently this only implements collapsing input varnodes read by CPUI_PIECE
// So we put the test for this particular case early
if (!vnhi->isInput() || !vnlo->isInput())
return 0;
if (!vnhi->isPersist() || !vnlo->isPersist())
return 0;
if (!vnhi->isPrecisHi() || !vnlo->isPrecisLo()) {
return attemptMarking(vnhi,vnlo,op);
}
if (data.hasUnreachableBlocks()) return 0;
Address addr;
if (!SplitVarnode::isAddrTiedContiguous(vnlo, vnhi, addr))
return 0;
data.combineInputVarnodes(vnhi,vnlo);
return 1;
}
/// \brief Scan for conflicts between two LOADs or STOREs that would prevent them from being combined
///
/// The PcodeOps must be in the same basic block. Each PcodeOp that falls in between is examined

View File

@ -4,9 +4,9 @@
* 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.
@ -95,6 +95,7 @@ public:
PcodeOp *existop);
static bool prepareIndirectOp(SplitVarnode &in,PcodeOp *affector);
static void replaceIndirectOp(Funcdata &data,SplitVarnode &out,SplitVarnode &in,PcodeOp *affector);
static void replaceCopyForce(Funcdata &data,const Address &addr,SplitVarnode &in,PcodeOp *copylo,PcodeOp *copyhi);
static int4 applyRuleIn(SplitVarnode &in,Funcdata &data);
};
@ -299,14 +300,30 @@ public:
bool applyRule(SplitVarnode &i,PcodeOp *ind,bool workishi,Funcdata &data);
};
/// \brief Simply a double precision operation, starting from a marked double precision input.
/// \brief Collapse two COPYs into contiguous address forced Varnodes
///
/// The inputs must be pieces of a logical whole and outputs must be address forced with no descendants.
/// Take into account special form of COPYs holding global variables upto/past a RETURN.
class CopyForceForm {
SplitVarnode in; ///< Incoming pieces to COPY
Varnode *reslo; ///< Least significant result of global COPY
Varnode *reshi; ///< Most significant result of global COPY
PcodeOp *copylo; ///< Partial COPY of least significant piece
PcodeOp *copyhi; ///< Partial COPY of most significant piece
Address addrOut; ///< Storage address
public:
bool verify(Varnode *h,Varnode *l,Varnode *w,PcodeOp *cpy); ///< Make sure the COPYs have the correct form
bool applyRule(SplitVarnode &i,PcodeOp *cpy,bool workishi,Funcdata &data); /// Verify and then collapse COPYs
};
/// \brief Simply a double precision operation, pushing down one level, starting from a marked double precision input.
///
/// This rule starts by trying to find a pair of Varnodes that are SUBPIECE from a whole,
/// are marked as double precision, and that are then used in some double precision operation.
/// The various operation \e forms are overlayed on the data-flow until a matching one is found. The
/// pieces of the double precision operation are then transformed into a single logical operation on the whole.
class RuleDoubleIn : public Rule {
int4 attemptMarking(Funcdata &data,Varnode *vn,PcodeOp *subpieceOp);
int4 attemptMarking(Varnode *vn,PcodeOp *subpieceOp);
public:
RuleDoubleIn(const string &g) : Rule(g, 0, "doublein") {} ///< Constructor
virtual Rule *clone(const ActionGroupList &grouplist) const {
@ -318,6 +335,20 @@ public:
virtual int4 applyOp(PcodeOp *op,Funcdata &data);
};
/// \brief Simplify a double precision operation, pulling back one level, starting from inputs to a PIECE operation
class RuleDoubleOut : public Rule {
int4 attemptMarking(Varnode *vnhi,Varnode *vnlo,PcodeOp *pieceOp);
public:
RuleDoubleOut(const string &g) : Rule(g, 0, "doubleout") {} ///< Constructor
virtual Rule *clone(const ActionGroupList &grouplist) const {
if (!grouplist.contains(getGroup())) return (Rule *)0;
return new RuleDoubleOut(getGroup());
}
virtual void getOpList(vector<uint4> &oplist) const;
virtual int4 applyOp(PcodeOp *op,Funcdata &data);
};
/// \brief Collapse contiguous loads: `x = CONCAT44(*(ptr+4),*ptr) => x = *ptr`
class RuleDoubleLoad : public Rule {
public:
RuleDoubleLoad(const string &g) : Rule( g, 0, "doubleload") {}
@ -330,6 +361,7 @@ public:
static PcodeOp *noWriteConflict(PcodeOp *op1,PcodeOp *op2,AddrSpace *spc,vector<PcodeOp *> *indirects);
};
/// \brief Collapse contiguous stores: `*ptr = SUB(x,0); *(ptr + 4) = SUB(x,4) => *ptr = x`
class RuleDoubleStore : public Rule {
public:
RuleDoubleStore(const string &g) : Rule( g, 0, "doublestore") {}

View File

@ -4,9 +4,9 @@
* 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.
@ -288,6 +288,7 @@ public:
Varnode *newUnique(int4 s,Datatype *ct=(Datatype *)0); ///< Create a new \e temporary Varnode
Varnode *newCodeRef(const Address &m); ///< Create a code address \e annotation Varnode
Varnode *setInputVarnode(Varnode *vn); ///< Mark a Varnode as an input to the function
void combineInputVarnodes(Varnode *vnHi,Varnode *vnLo); ///< Combine two contiguous input Varnodes into one
Varnode *newExtendedConstant(int4 s,uint8 *val,PcodeOp *op); ///< Create extended precision constant
void adjustInputVarnodes(const Address &addr,int4 sz);
void deleteVarnode(Varnode *vn) { vbank.destroy(vn); } ///< Delete the given varnode

View File

@ -4,9 +4,9 @@
* 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.
@ -372,6 +372,78 @@ Varnode *Funcdata::setInputVarnode(Varnode *vn)
return vn;
}
/// A new Varnode that covers both the original Varnodes is created and is itself marked as a function input.
/// Any CPUI_PIECE reading the original Varnodes is converted to a CPUI_COPY reading the new Varnode. If there
/// are other ops reading the original Varnodes, they are changed to read replacement Varnodes, which are
/// defined as SUBPIECEs of the new Varnode. The original Varnodes are destroyed.
/// \param vnHi is the most significant Varnode to combine
/// \param vnLo is the least significant Varnode
void Funcdata::combineInputVarnodes(Varnode *vnHi,Varnode *vnLo)
{
if (!vnHi->isInput() || !vnLo->isInput())
throw LowlevelError("Varnodes being combined are not inputs");
bool isContiguous;
Address addr = vnLo->getAddr();
if (addr.isBigEndian()) {
addr = vnHi->getAddr();
Address otheraddr = addr + vnHi->getSize();
isContiguous = (otheraddr == vnLo->getAddr());
}
else {
Address otheraddr = addr + vnLo->getSize();
isContiguous = (otheraddr == vnHi->getAddr());
}
if (!isContiguous)
throw LowlevelError("Input varnodes being combined are not contiguous");
vector<PcodeOp *> pieceList;
bool otherOps = false;
list<PcodeOp *>::const_iterator iter;
for(iter=vnHi->beginDescend();iter!=vnHi->endDescend();++iter) {
PcodeOp *op = *iter;
if (op->code() == CPUI_PIECE && op->getIn(0) == vnHi && op->getIn(1) == vnLo)
pieceList.push_back(op);
else
otherOps = true;
}
for(int4 i=0;i<pieceList.size();++i) {
opRemoveInput(pieceList[i], 1);
opUnsetInput(pieceList[i], 0);
}
PcodeOp *subHi = (PcodeOp *)0;
PcodeOp *subLo = (PcodeOp *)0;
if (otherOps) {
// If there are other PcodeOps besides PIECEs that are directly combining vnHi and vnLo
// create replacement Varnodes constructed as SUBPIECEs of the new combined Varnode
BlockBasic *bb = (BlockBasic *)bblocks.getBlock(0);
subHi = newOp(2,bb->getStart());
opSetOpcode(subHi, CPUI_SUBPIECE);
opSetInput(subHi,newConstant(4, vnLo->getSize()),1);
Varnode *newHi = newVarnodeOut(vnHi->getSize(),vnHi->getAddr(),subHi);
opInsertBegin(subHi, bb);
subLo = newOp(2,bb->getStart());
opSetOpcode(subLo, CPUI_SUBPIECE);
opSetInput(subLo,newConstant(4, 0),1);
Varnode *newLo = newVarnodeOut(vnLo->getSize(),vnLo->getAddr(),subLo);
opInsertBegin(subLo, bb);
totalReplace(vnHi, newHi);
totalReplace(vnLo, newLo);
}
int4 outSize = vnHi->getSize() + vnLo->getSize();
vbank.destroy(vnHi);
vbank.destroy(vnLo);
Varnode *inVn = newVarnode(outSize, addr);
inVn = setInputVarnode(inVn);
for(int4 i=0;i<pieceList.size();++i) {
opSetInput(pieceList[i],inVn,0);
opSetOpcode(pieceList[i], CPUI_COPY);
}
if (otherOps) {
opSetInput(subHi,inVn,0);
opSetInput(subLo,inVn,0);
}
}
/// Construct a constant Varnode up to 128 bits, using INT_ZEXT and PIECE if necessary.
/// This method is temporary until we have full extended precision constants.
/// \param s is the size of the Varnode in bytes

View File

@ -0,0 +1,42 @@
<decompilertest>
<!--
Functions with variables that are LOADed are STOREed in two stages,
but also used as a logical whole.
-->
<binaryimage arch="MIPS:BE:32:default:default">
<bytechunk space="ram" offset="0x400000" readonly="true">
3c020042d4424318afa40000
c4414318c440431cafa60008afa7000c
03e0000846201000
</bytechunk>
<bytechunk space="ram" offset="0x410000" readonly="true">
3c020042d7a20028afa40000
c4414320c440432446201000e4414308
e440430c03e0000846201000
</bytechunk>
<bytechunk space="ram" offset="0x424320" readonly="true">
401c000000000000
</bytechunk>
<symbol space="ram" offset="0x400000" name="loaddouble"/>
<symbol space="ram" offset="0x410000" name="storedouble"/>
</binaryimage>
<script>
<com>option readonly on</com>
<com>map addr r0x424318 float8 glob1</com>
<com>map addr r0x424308 float8 glob2</com>
<com>lo fu loaddouble</com>
<com>dec</com>
<com>print C</com>
<com>print raw</com>
<com>lo fu storedouble</com>
<com>dec</com>
<com>print C</com>
<com>print raw</com>
</script>
<stringmatch name="Double precision move #1" min="1" max="1">return glob1 \+ glob1;</stringmatch>
<stringmatch name="Double precision move #2" min="1" max="1">r0x00424318:8\(i\) \+ r0x00424318:8\(i\)</stringmatch>
<stringmatch name="Double precision move #3" min="1" max="1">r0x00424318:8\(0x.*\) = r0x00424318:8\(i\)</stringmatch>
<stringmatch name="Double precision move #4" min="1" max="1">glob2 = in_stack_00000028 \+ 7\.0;</stringmatch>
<stringmatch name="Double precision move #5" min="1" max="1">r0x00424308.* = f0_1.*</stringmatch>
<stringmatch name="Double precision move #6" min="1" max="1">r0x00424308.* = r0x00424308.*</stringmatch>
</decompilertest>