mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
643 lines
18 KiB
C++
643 lines
18 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 "cover.hh"
|
|
#include "block.hh"
|
|
|
|
namespace ghidra {
|
|
|
|
const CoverBlock Cover::emptyBlock;
|
|
|
|
/// PcodeOp objects and a CoverBlock start/stop boundaries have
|
|
/// a natural ordering that can be used to tell if a PcodeOp falls
|
|
/// between boundary points and if CoverBlock objects intersect.
|
|
/// Ordering is determined by comparing the values returned by this method.
|
|
/// \param op is the PcodeOp and/or boundary point
|
|
/// \return a value for comparison
|
|
uintm CoverBlock::getUIndex(const PcodeOp *op)
|
|
|
|
{
|
|
uintp switchval = (uintp)op;
|
|
switch(switchval) {
|
|
case 0: // Special marker for very beginning of block
|
|
return (uintm)0;
|
|
case 1: // Special marker for very end of block
|
|
return ~((uintm)0);
|
|
case 2: // Special marker for input
|
|
return (uintm)0;
|
|
}
|
|
if (op->isMarker()) {
|
|
if (op->code() == CPUI_MULTIEQUAL) // MULTIEQUALs are considered very beginning
|
|
return (uintm)0;
|
|
else if (op->code() == CPUI_INDIRECT) // INDIRECTs are considered to be at
|
|
// the location of the op they are indirect for
|
|
return PcodeOp::getOpFromConst(op->getIn(1)->getAddr())->getSeqNum().getOrder();
|
|
}
|
|
return op->getSeqNum().getOrder();
|
|
}
|
|
|
|
/// Characterize the intersection of \b this range with another CoverBlock.
|
|
/// Return:
|
|
/// - 0 if there is no intersection
|
|
/// - 1 if only the intersection is at boundary points
|
|
/// - 2 if a whole interval intersects
|
|
///
|
|
/// \param op2 is the other CoverBlock to compare
|
|
/// \return the intersection characterization
|
|
int4 CoverBlock::intersect(const CoverBlock &op2) const
|
|
|
|
{
|
|
uintm ustart,ustop;
|
|
uintm u2start,u2stop;
|
|
|
|
if (empty()) return 0;
|
|
if (op2.empty()) return 0;
|
|
|
|
ustart = getUIndex(start);
|
|
ustop = getUIndex(stop);
|
|
u2start = getUIndex(op2.start);
|
|
u2stop = getUIndex(op2.stop);
|
|
if (ustart <= ustop) {
|
|
if (u2start <= u2stop) { // We are both one piece
|
|
if ((ustop<=u2start)||(u2stop<=ustart)) {
|
|
if ((ustart==u2stop)||(ustop==u2start))
|
|
return 1; // Boundary intersection
|
|
else
|
|
return 0; // No intersection
|
|
}
|
|
}
|
|
else { // They are two-piece, we are one-piece
|
|
if ((ustart>=u2stop)&&(ustop<=u2start)) {
|
|
if ((ustart==u2stop)||(ustop==u2start))
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (u2start <= u2stop) { // They are one piece, we are two-piece
|
|
if ((u2start>=ustop)&&(u2stop<=ustart)) {
|
|
if ((u2start==ustop)||(u2stop==ustart))
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
// If both are two-pieces, then the intersection must be an interval
|
|
}
|
|
return 2; // Interval intersection
|
|
}
|
|
|
|
/// If the given PcodeOp or boundary point is contained in \b this range, return true.
|
|
/// \param point is the given PcodeOp
|
|
/// \return \b true if the point is contained
|
|
bool CoverBlock::contain(const PcodeOp *point) const
|
|
|
|
{
|
|
uintm ustart,ustop,upoint;
|
|
|
|
if (empty()) return false;
|
|
upoint = getUIndex(point);
|
|
ustart = getUIndex(start);
|
|
ustop = getUIndex(stop);
|
|
|
|
if (ustart<=ustop)
|
|
return ((upoint>=ustart)&&(upoint<=ustop));
|
|
return ((upoint<=ustop)||(upoint>=ustart));
|
|
}
|
|
|
|
/// Return:
|
|
/// - 0 if point not on boundary
|
|
/// - 1 if on tail
|
|
/// - 2 if on the defining point
|
|
///
|
|
/// \param point is the given PcodeOp point
|
|
/// \return the characterization
|
|
int4 CoverBlock::boundary(const PcodeOp *point) const
|
|
|
|
{
|
|
uintm val;
|
|
|
|
if (empty()) return 0;
|
|
val = getUIndex(point);
|
|
if (getUIndex(start)==val) {
|
|
if (start!=(const PcodeOp *)0)
|
|
return 2;
|
|
}
|
|
if (getUIndex(stop)==val) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/// Compute the union of \b this with the other given CoverBlock,
|
|
/// replacing \b this in place.
|
|
/// \param op2 is the other given CoverBlock
|
|
void CoverBlock::merge(const CoverBlock &op2)
|
|
|
|
{
|
|
bool internal1,internal2,internal3,internal4;
|
|
uintm ustart,u2start;
|
|
|
|
if (op2.empty()) return; // Nothing to merge in
|
|
if (empty()) {
|
|
start = op2.start;
|
|
stop = op2.stop;
|
|
return;
|
|
}
|
|
ustart = getUIndex(start);
|
|
u2start = getUIndex(op2.start);
|
|
// Is start contained in op2
|
|
internal4 = ((ustart==(uintm)0)&&(op2.stop==(const PcodeOp *)1));
|
|
internal1 = internal4 || op2.contain(start);
|
|
// Is op2.start contained in this
|
|
internal3 = ((u2start==0)&&(stop==(const PcodeOp *)1));
|
|
internal2 = internal3 || contain(op2.start);
|
|
|
|
if (internal1&&internal2)
|
|
if ((ustart!=u2start)|| internal3 || internal4) { // Covered entire block
|
|
setAll();
|
|
return;
|
|
}
|
|
if (internal1)
|
|
start = op2.start; // Pick non-internal start
|
|
else if ((!internal1)&&(!internal2)) { // Disjoint intervals
|
|
if (ustart < u2start) // Pick earliest start
|
|
stop = op2.stop; // then take other stop
|
|
else
|
|
start = op2.start;
|
|
return;
|
|
}
|
|
if (internal3 || op2.contain(stop)) // Pick non-internal stop
|
|
stop = op2.stop;
|
|
}
|
|
|
|
/// Print a description of the covered range of ops in this block
|
|
/// \param s is the output stream
|
|
void CoverBlock::print(ostream &s) const
|
|
|
|
{
|
|
uintm ustart,ustop;
|
|
|
|
if (empty()) {
|
|
s << "empty";
|
|
return;
|
|
}
|
|
|
|
ustart = getUIndex(start);
|
|
ustop = getUIndex(stop);
|
|
if (ustart==(uintm)0)
|
|
s << "begin";
|
|
else if (ustart==~((uintm)0))
|
|
s << "end";
|
|
else
|
|
s << start->getSeqNum();
|
|
|
|
s << '-';
|
|
|
|
if (ustop==(uintm)0)
|
|
s << "begin";
|
|
else if (ustop==~((uintm)0))
|
|
s << "end";
|
|
else
|
|
s << stop->getSeqNum();
|
|
}
|
|
|
|
/// Compare \b this with another Cover by comparing just
|
|
/// the indices of the first blocks respectively that are partly covered.
|
|
/// Return -1, 0, or 1 if \b this Cover's first block has a
|
|
/// smaller, equal, or bigger index than the other Cover's first block.
|
|
/// \param op2 is the other Cover
|
|
/// \return the comparison value
|
|
int4 Cover::compareTo(const Cover &op2) const
|
|
|
|
{
|
|
int4 a,b;
|
|
|
|
map<int4,CoverBlock>::const_iterator iter;
|
|
iter = cover.begin();
|
|
if (iter==cover.end())
|
|
a = 1000000;
|
|
else
|
|
a = (*iter).first;
|
|
iter = op2.cover.begin();
|
|
if (iter==op2.cover.end())
|
|
b = 1000000;
|
|
else
|
|
b = (*iter).first;
|
|
|
|
if ( a < b ) {
|
|
return -1;
|
|
}
|
|
else if ( a == b ) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// Return a representative CoverBlock describing how much of the given block
|
|
/// is covered by \b this
|
|
/// \param i is the index of the given block
|
|
/// \return a reference to the corresponding CoverBlock
|
|
const CoverBlock &Cover::getCoverBlock(int4 i) const
|
|
|
|
{
|
|
map<int4,CoverBlock>::const_iterator iter = cover.find(i);
|
|
if (iter == cover.end())
|
|
return emptyBlock;
|
|
return (*iter).second;
|
|
}
|
|
|
|
/// Return
|
|
/// - 0 if there is no intersection
|
|
/// - 1 if the only intersection is on a boundary point
|
|
/// - 2 if the intersection contains a range of p-code ops
|
|
///
|
|
/// \param op2 is the other Cover
|
|
/// \return the intersection characterization
|
|
int4 Cover::intersect(const Cover &op2) const
|
|
|
|
{
|
|
map<int4,CoverBlock>::const_iterator iter,iter2;
|
|
int4 res,newres;
|
|
|
|
res = 0;
|
|
iter = cover.begin();
|
|
iter2 = op2.cover.begin();
|
|
|
|
for(;;) {
|
|
if (iter == cover.end()) return res;
|
|
if (iter2 == op2.cover.end()) return res;
|
|
|
|
if ((*iter).first < (*iter2).first)
|
|
++iter;
|
|
else if ((*iter).first > (*iter2).first)
|
|
++iter2;
|
|
else {
|
|
newres = (*iter).second.intersect((*iter2).second);
|
|
if (newres == 2) return 2;
|
|
if (newres == 1)
|
|
res = 1; // At least a point intersection
|
|
++iter;
|
|
++iter2;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/// \brief Generate a list of blocks that intersect
|
|
///
|
|
/// For each block for which \b this and another Cover intersect,
|
|
/// and the block's index to a result list if the type of intersection
|
|
/// exceeds a characterization level.
|
|
/// \param listout will hold the list of intersecting block indices
|
|
/// \param op2 is the other Cover
|
|
/// \param level is the characterization threshold which must be exceeded
|
|
void Cover::intersectList(vector<int4> &listout,const Cover &op2,int4 level) const
|
|
|
|
{
|
|
map<int4,CoverBlock>::const_iterator iter,iter2;
|
|
int4 val;
|
|
|
|
listout.clear();
|
|
|
|
iter = cover.begin();
|
|
iter2 = op2.cover.begin();
|
|
|
|
for(;;) {
|
|
if (iter == cover.end()) return;
|
|
if (iter2 == op2.cover.end()) return;
|
|
|
|
if ((*iter).first < (*iter2).first)
|
|
++iter;
|
|
else if ((*iter).first > (*iter2).first)
|
|
++iter2;
|
|
else {
|
|
val = (*iter).second.intersect((*iter2).second);
|
|
if (val >= level)
|
|
listout.push_back((*iter).first);
|
|
++iter;
|
|
++iter2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// If any PcodeOp in the set falls inside \b this Cover, a secondary test that the PcodeOp
|
|
/// affects the representative Varnode is performed. If the test returns \b true, this is considered
|
|
/// a full intersection and this method returns \b true. Otherwise it returns \b false.
|
|
/// \param opSet is the given set of PcodeOps
|
|
/// \param rep is the representative Varnode to use for secondary testing
|
|
/// \return \b true is there is an intersection with \b this
|
|
bool Cover::intersect(const PcodeOpSet &opSet,Varnode *rep) const
|
|
|
|
{
|
|
if (opSet.opList.empty()) return false;
|
|
int4 setBlock = 0;
|
|
int4 opIndex = opSet.blockStart[setBlock];
|
|
int4 setIndex = opSet.opList[opIndex]->getParent()->getIndex();
|
|
map<int4,CoverBlock>::const_iterator coverIter = cover.lower_bound(opSet.opList[0]->getParent()->getIndex());
|
|
while(coverIter != cover.end()) {
|
|
int4 coverIndex = (*coverIter).first;
|
|
if (coverIndex < setIndex) {
|
|
++coverIter;
|
|
}
|
|
else if (coverIndex > setIndex) {
|
|
setBlock += 1;
|
|
if (setBlock >= opSet.blockStart.size()) break;
|
|
opIndex = opSet.blockStart[setBlock];
|
|
setIndex = opSet.opList[opIndex]->getParent()->getIndex();
|
|
}
|
|
else {
|
|
const CoverBlock &coverBlock( (*coverIter).second );
|
|
++coverIter;
|
|
int4 opMax = opSet.opList.size();
|
|
setBlock += 1;
|
|
if (setBlock < opSet.blockStart.size())
|
|
opMax = opSet.blockStart[setBlock];
|
|
do {
|
|
PcodeOp *op = opSet.opList[opIndex];
|
|
if (coverBlock.contain(op)) { // Does range contain the call?
|
|
if (coverBlock.boundary(op) == 0) { // Is the call on the boundary
|
|
if (opSet.affectsTest(op, rep)) // Do secondary testing
|
|
return true;
|
|
}
|
|
}
|
|
opIndex += 1;
|
|
} while(opIndex < opMax);
|
|
if (setBlock >= opSet.blockStart.size()) break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Looking only at the given block, Return
|
|
/// - 0 if there is no intersection
|
|
/// - 1 if the only intersection is on a boundary point
|
|
/// - 2 if the intersection contains a range of p-code ops
|
|
///
|
|
/// \param blk is the index of the given block
|
|
/// \param op2 is the other Cover
|
|
/// \return the characterization
|
|
int4 Cover::intersectByBlock(int4 blk,const Cover &op2) const
|
|
|
|
{
|
|
map<int4,CoverBlock>::const_iterator iter;
|
|
|
|
iter = cover.find(blk);
|
|
if (iter == cover.end()) return 0;
|
|
|
|
map<int4,CoverBlock>::const_iterator iter2;
|
|
|
|
iter2 = op2.cover.find(blk);
|
|
if (iter2 == op2.cover.end()) return 0;
|
|
|
|
return (*iter).second.intersect((*iter2).second);
|
|
}
|
|
|
|
/// \brief Does \b this contain the given PcodeOp
|
|
///
|
|
/// \param op is the given PcodeOp
|
|
/// \param max is 1 to test for any containment, 2 to force interior containment
|
|
/// \return true if there is containment
|
|
bool Cover::contain(const PcodeOp *op,int4 max) const
|
|
|
|
{
|
|
map<int4,CoverBlock>::const_iterator iter;
|
|
|
|
iter = cover.find(op->getParent()->getIndex());
|
|
if (iter == cover.end()) return false;
|
|
if ((*iter).second.contain(op)) {
|
|
if (max==1) return true;
|
|
if (0==(*iter).second.boundary(op)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// \brief Check the definition of a Varnode for containment
|
|
///
|
|
/// If the given Varnode has a defining PcodeOp this is
|
|
/// checked for containment. If the Varnode is an input,
|
|
/// check if \b this covers the start of the function.
|
|
///
|
|
/// Return:
|
|
/// - 0 if cover does not contain varnode definition
|
|
/// - 1 if there if it is contained in interior
|
|
/// - 2 if the defining points intersect
|
|
/// - 3 if Cover's tail is the varnode definition
|
|
///
|
|
/// \param vn is the given Varnode
|
|
/// \return the containment characterization
|
|
int4 Cover::containVarnodeDef(const Varnode *vn) const
|
|
|
|
{
|
|
const PcodeOp *op = vn->getDef();
|
|
int4 blk;
|
|
|
|
if (op == (const PcodeOp *)0) {
|
|
op = (const PcodeOp *)2;
|
|
blk = 0;
|
|
}
|
|
else
|
|
blk = op->getParent()->getIndex();
|
|
map<int4,CoverBlock>::const_iterator iter = cover.find(blk);
|
|
if (iter == cover.end()) return 0;
|
|
if ((*iter).second.contain(op)) {
|
|
int4 boundtype = (*iter).second.boundary(op);
|
|
if (boundtype == 0) return 1;
|
|
if (boundtype == 2) return 2;
|
|
return 3;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// \param op2 is the other Cover
|
|
void Cover::merge(const Cover &op2)
|
|
|
|
{
|
|
map<int4,CoverBlock>::const_iterator iter;
|
|
|
|
for(iter=op2.cover.begin();iter!=op2.cover.end();++iter)
|
|
cover[(*iter).first].merge((*iter).second);
|
|
}
|
|
|
|
/// The cover is set to all p-code ops between the point where
|
|
/// the Varnode is defined and all the points where it is read
|
|
/// \param vn is the single Varnode
|
|
void Cover::rebuild(const Varnode *vn)
|
|
|
|
{
|
|
list<PcodeOp *>::const_iterator iter;
|
|
|
|
addDefPoint(vn);
|
|
for(iter=vn->beginDescend();iter!=vn->endDescend();++iter)
|
|
addRefPoint(*iter,vn);
|
|
}
|
|
|
|
/// Any previous cover is removed. Calling this with an
|
|
/// input Varnode still produces a valid Cover.
|
|
/// \param vn is the Varnode
|
|
void Cover::addDefPoint(const Varnode *vn)
|
|
|
|
{
|
|
const PcodeOp *def;
|
|
|
|
cover.clear();
|
|
|
|
def = vn->getDef();
|
|
if (def != (const PcodeOp *)0) {
|
|
CoverBlock &block( cover[def->getParent()->getIndex() ] );
|
|
block.setBegin(def); // Set the point topology
|
|
block.setEnd(def);
|
|
}
|
|
else if (vn->isInput()) {
|
|
CoverBlock &block( cover[0] );
|
|
block.setBegin( (const PcodeOp *)2 ); // Special mark for input
|
|
block.setEnd( (const PcodeOp *)2 );
|
|
}
|
|
}
|
|
|
|
/// Add to \b this Cover recursively, starting at bottom of the given block
|
|
/// and filling in backward until we run into existing cover.
|
|
/// \param bl is the starting block to add
|
|
void Cover::addRefRecurse(const FlowBlock *bl)
|
|
|
|
{
|
|
int4 j;
|
|
uintm ustart,ustop;
|
|
|
|
CoverBlock &block(cover[bl->getIndex()]);
|
|
if (block.empty()) {
|
|
block.setAll(); // No cover encountered, fill in entire block
|
|
// if (bl->InSize()==0)
|
|
// throw LowlevelError("Ref point is not in flow of defpoint");
|
|
for(j=0;j<bl->sizeIn();++j) // Recurse to all blocks that fall into bl
|
|
addRefRecurse(bl->getIn(j));
|
|
}
|
|
else {
|
|
const PcodeOp *op = block.getStop();
|
|
ustart = CoverBlock::getUIndex(block.getStart());
|
|
ustop = CoverBlock::getUIndex(op);
|
|
if ((ustop != ~((uintm)0))&&( ustop >= ustart))
|
|
block.setEnd((const PcodeOp *)1); // Fill in to the bottom
|
|
|
|
|
|
if ((ustop==(uintm)0)&&(block.getStart() == (const PcodeOp *)0)) {
|
|
if ((op != (const PcodeOp *)0)&&(op->code()==CPUI_MULTIEQUAL)) {
|
|
// This block contains only an infinitesimal tip
|
|
// of cover through one branch of a MULTIEQUAL
|
|
// we still need to traverse through branches
|
|
for(j=0;j<bl->sizeIn();++j)
|
|
addRefRecurse(bl->getIn(j));
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
/// Given a Varnode being read and the PcodeOp which reads it,
|
|
/// add the point of the read to \b this and recursively fill in backwards until
|
|
/// we run into existing cover.
|
|
/// \param ref is the reading PcodeOp
|
|
/// \param vn is the Varnode being read
|
|
void Cover::addRefPoint(const PcodeOp *ref,const Varnode *vn)
|
|
|
|
{
|
|
int4 j;
|
|
const FlowBlock *bl;
|
|
uintm ustop;
|
|
|
|
bl = ref->getParent();
|
|
CoverBlock &block(cover[bl->getIndex()]);
|
|
if (block.empty()) {
|
|
block.setEnd(ref);
|
|
}
|
|
else {
|
|
if (block.contain(ref)) {
|
|
if (ref->code() != CPUI_MULTIEQUAL) return;
|
|
// Even if MULTIEQUAL ref is contained
|
|
// we may be adding new cover because we are
|
|
// looking at a different branch. So don't return
|
|
}
|
|
else {
|
|
const PcodeOp *op = block.getStop();
|
|
const PcodeOp *startop = block.getStart();
|
|
block.setEnd(ref); // Otherwise update endpoint
|
|
ustop = CoverBlock::getUIndex(block.getStop());
|
|
if (ustop >= CoverBlock::getUIndex(startop)) {
|
|
if ((op!=(const PcodeOp *)0)&&(op!=(const PcodeOp *)2)&&
|
|
(op->code()==CPUI_MULTIEQUAL)&&(startop==(const PcodeOp *)0)) {
|
|
// This block contains only an infinitesimal tip
|
|
// of cover through one branch of a MULTIEQUAL
|
|
// we still need to traverse through branches
|
|
for(j=0;j<bl->sizeIn();++j)
|
|
addRefRecurse(bl->getIn(j));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// if (bl->InSize()==0)
|
|
// throw LowlevelError("Ref point is not in flow of defpoint");
|
|
if (ref->code() == CPUI_MULTIEQUAL) {
|
|
for(j=0;j<ref->numInput();++j)
|
|
if (ref->getIn(j)==vn)
|
|
addRefRecurse(bl->getIn(j));
|
|
}
|
|
else
|
|
for(j=0;j<bl->sizeIn();++j)
|
|
addRefRecurse(bl->getIn(j));
|
|
}
|
|
|
|
/// \param s is the output stream
|
|
void Cover::print(ostream &s) const
|
|
|
|
{
|
|
map<int4,CoverBlock>::const_iterator iter;
|
|
|
|
for(iter=cover.begin();iter!=cover.end();++iter) {
|
|
s << dec << (*iter).first << ": ";
|
|
(*iter).second.print(s);
|
|
s << endl;
|
|
}
|
|
}
|
|
|
|
void PcodeOpSet::finalize(void)
|
|
|
|
{
|
|
sort(opList.begin(),opList.end(),compareByBlock);
|
|
int4 blockNum = -1;
|
|
for(int4 i=0;i<opList.size();++i) {
|
|
int4 newBlockNum = opList[i]->getParent()->getIndex();
|
|
if (newBlockNum > blockNum) {
|
|
blockStart.push_back(i);
|
|
blockNum = newBlockNum;
|
|
}
|
|
}
|
|
is_pop = true;
|
|
}
|
|
|
|
/// Compare first by index of the containing basic blocks, then by SeqNum ordering (within the block)
|
|
/// \param a is the first PcodeOp to compare
|
|
/// \param b is the second PcodeOp to compare
|
|
/// \return \b true if the first PcodeOp should be ordered before the second
|
|
bool PcodeOpSet::compareByBlock(const PcodeOp *a,const PcodeOp *b)
|
|
|
|
{
|
|
if (a->getParent() != b->getParent())
|
|
return (a->getParent()->getIndex() < b->getParent()->getIndex());
|
|
return a->getSeqNum().getOrder() < b->getSeqNum().getOrder();
|
|
}
|
|
|
|
} // End namespace ghidra
|