-
Jason Hiser authoredJason Hiser authored
zipr.cpp 71.06 KiB
/***************************************************************************
* Copyright (c) 2014 Zephyr Software LLC. All rights reserved.
*
* This software is furnished under a license and/or other restrictive
* terms and may be used and copied only in accordance with such terms
* and the inclusion of the above copyright notice. This software or
* any other copies thereof may not be provided or otherwise made
* available to any other person without the express written consent
* of an authorized representative of Zephyr Software LCC. Title to,
* ownership of, and all rights in the software is retained by
* Zephyr Software LCC.
*
* Zephyr Software LLC. Proprietary Information
*
* Unless otherwise specified, the information contained in this
* directory, following this legend, and/or referenced herein is
* Zephyr Software LLC. (Zephyr) Proprietary Information.
*
* CONTACT
*
* For technical assistance, contact Zephyr Software LCC. at:
*
*
* Zephyr Software, LLC
* 2040 Tremont Rd
* Charlottesville, VA 22911
*
* E-mail: jwd@zephyr-software.com
**************************************************************************/
#include <zipr_all.h>
#include <irdb-core>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <assert.h>
#include <sys/mman.h>
#include <ctype.h>
#include <iostream> // std::cout
#include <string> // std::string, std::to_string
#include <fstream>
#define ALLOF(a) begin(a),end(a)
using namespace IRDB_SDK;
using namespace std;
using namespace zipr;
using namespace EXEIO;
using namespace Zipr_SDK;
inline uintptr_t page_round_up(uintptr_t x)
{
return ( (((uintptr_t)(x)) + PAGE_SIZE-1) & (~(PAGE_SIZE-1)) );
}
inline uintptr_t page_round_down(uintptr_t x)
{
return ( (((uintptr_t)(x)) - (PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) );
}
template < typename T > std::string to_hex_string( const T& n )
{
std::ostringstream stm ;
stm << std::hex<< "0x"<< n ;
return stm.str() ;
}
template < typename T > std::string to_string( const T& n )
{
std::ostringstream stm ;
stm << n ;
return stm.str() ;
}
void ZiprImpl_t::Init()
{
// allocate stats
m_stats = new Stats_t();
bss_needed=0;
use_stratafier_mode=false;
ostream *error = &cout, *warn = nullptr;
registerOptions();
/*
* Parse once to read the global and zipr options.
*/
m_zipr_options.parse(nullptr, nullptr);
if (m_variant->areRequirementMet())
{
/* setup the interface to the sql server */
BaseObj_t::setInterface(m_pqxx_interface.get());
m_variant_id_p=VariantID_t::factory(*m_variant);
m_variant_id=m_variant_id_p.get();
assert(m_variant_id);
assert(m_variant_id->isRegistered()==true);
if (*m_verbose)
cout<<"New Variant, after reading registration, is: "<<*m_variant_id << endl;
auto this_file=m_variant_id->getMainFile();
// read the db
m_firp_p=FileIR_t::factory(m_variant_id, this_file);
m_firp=m_firp_p.get();
assert(m_firp);
}
plugman = ZiprPluginManager_t(this, &m_zipr_options);
// need a file IR to create the arch-specific stuff.
// without it, we won't run anything anyhow
if(m_firp)
{
archhelper=ZiprArchitectureHelperBase_t::factory(this);
pinner =archhelper->getPinner ();
patcher=archhelper->getPatcher();
sizer =archhelper->getSizer ();
}
/*
* Parse again now that the plugins registered something.
*/
if (*m_verbose)
warn = &cout;
m_zipr_options.parse(error, warn);
if (!m_zipr_options.areRequirementsMet()) {
m_zipr_options.printUsage(cout);
m_error = true;
return;
}
}
ZiprImpl_t::~ZiprImpl_t()
{
// delete m_firp; // taken care of by the unique_ptr the factory returns.
try
{
m_pqxx_interface->commit();
}
catch (DatabaseError_t pnide)
{
cout<<"Unexpected database error: "<<pnide<<endl;
}
}
void ZiprImpl_t::registerOptions()
{
auto zipr_namespace = m_zipr_options.getNamespace("zipr");
auto glbl_namespace = m_zipr_options.getNamespace("global");
m_output_filename = zipr_namespace->getStringOption ("output", "Output file name.", "b.out");
m_callbacks = zipr_namespace->getStringOption ("callbacks", "Set the path of the file which contains any required callbacks.");
m_objcopy = zipr_namespace->getStringOption ("objcopy", "Set the path of objcopy to use.", "/usr/bin/objcopy");
m_dollop_map_filename = zipr_namespace->getStringOption ("dollop_map_filename", "Specify filename to save dollop map.", "dollop.map");
m_replop = zipr_namespace->getBooleanOption("replop", "Replop all dollops.", false);
m_verbose = glbl_namespace->getBooleanOption("verbose", "Enable verbose output", false);
m_vverbose = glbl_namespace->getBooleanOption("very_verbose", "Be very verry verbose, I'm hunting wabbits.", false);
m_apply_nop = glbl_namespace->getBooleanOption("apply_nop", "This flag needs documentation.", false);
m_add_sections = glbl_namespace->getBooleanOption("add-sections", "Add sections to the output binary", true);
m_bss_opts = glbl_namespace->getBooleanOption("bss-opts", "Use BSS optimizationg when genreating output file", true);
m_variant = glbl_namespace->getIntegerOption("variant", "Which IRDB variant to Zipr.");
m_architecture = zipr_namespace->getIntegerOption("architecture", "Override default system architecture detection");
m_seed = zipr_namespace->getIntegerOption("seed", "Specify a seed for randomization", getpid());
m_paddable_minimum_distance = zipr_namespace->getIntegerOption("paddable_minimum_distance", "Specify the minimum size of a gap to be filled.", 5*1024);
m_variant->setRequired(true);
memory_space.registerOptions(&m_zipr_options);
}
void ZiprImpl_t::CreateBinaryFile()
{
exeiop->load("a.ncexe");
if (*m_architecture == 0)
{
if (*m_verbose)
cout << "Doing architecture autodetection." << endl;
m_architecture->setValue(IRDB_SDK::FileIR_t::getArchitectureBitWidth());
if (*m_verbose)
cout << "Autodetected to " << (int)*m_architecture << endl;
}
/*
* Take the seed and initialize the random number
* generator.
*/
std::srand((unsigned)*m_seed);
if (*m_verbose)
cout << "Seeded the random number generator with " << *m_seed << "." << endl;
FixTwoByteWithPrefix(); // have to do this before multi-fallthrough in case it creaates some.
FixNoFallthroughs(); // have to do this before multi-fallthrough in case it creaates some.
FixMultipleFallthroughs();
// create ranges, including extra range that's def. big enough.
FindFreeRanges(*m_output_filename);
plugman.PinningBegin();
// allocate and execute a pinning algorithm.
assert(pinner);
pinner->doPinning();
// tell plugins we are done pinning.
plugman.PinningEnd();
/*
* Let's go through all the instructions
* and determine if there are going to be
* plugins that want to plop an instruction!
*/
AskPluginsAboutPlopping();
CreateDollops();
RecalculateDollopSizes();
plugman.DollopBegin();
PlaceDollops();
plugman.DollopEnd();
WriteDollops();
ReplopDollopEntriesWithTargets();
UpdatePins();
// tell plugins we are done plopping and about to link callbacks.
plugman.CallbackLinkingBegin();
// now that all instructions are put down, we can figure out where the callbacks for this file wil go.
// go ahead and update any callback sites with the new locations
UpdateCallbacks();
// ask plugman to inform the plugins we are done linking callbacks
plugman.CallbackLinkingEnd();
m_stats->total_free_ranges = memory_space.GetRangeCount();
// Update any scoops that were written by Zipr.
UpdateScoops();
// write binary file to disk
OutputBinaryFile(*m_output_filename);
// print relevant information
PrintStats();
}
#if 0
static bool in_same_segment(EXEIO::section* sec1, EXEIO::section* sec2, EXEIO::exeio* exeiop)
{
auto n = exeiop->segments.size();
for ( auto i = 0; i < n; ++i )
{
uintptr_t segstart=exeiop->segments[i]->get_virtual_address();
uintptr_t segsize=exeiop->segments[i]->get_file_size();
/* sec1 in segment i? */
if(segstart <= sec1->get_address() && sec1->get_address() < (segstart+segsize))
{
/* return true if sec2 also in segment */
/* else return false */
return (segstart <= sec2->get_address() && sec2->get_address() < (segstart+segsize));
}
}
return false;
}
#endif
//
// check if there's padding we can use between this section and the next section.
//
RangeAddress_t ZiprImpl_t::extend_section(EXEIO::section *sec, EXEIO::section *next_sec)
{
assert(0);
#if 0
RangeAddress_t start=sec->get_address();
RangeAddress_t end=sec->get_size()+start;
if( (next_sec->get_flags() & SHF_ALLOC) != 0 && in_same_segment(sec,next_sec,elfiop))
{
end=next_sec->get_address()-1;
cout<<"Extending range of " << sec->get_name() <<" to "<<std::hex<<end<<endl;
sec->set_size(next_sec->get_address() - sec->get_address() - 1);
}
return end;
#endif
}
void ZiprImpl_t::CreateExecutableScoops(const std::map<RangeAddress_t, int> &ordered_sections)
{
int count=0;
/*
* For each section, ...
*/
for(auto it = ordered_sections.begin(); it!=ordered_sections.end(); /* empty */ )
{
auto sec = exeiop->sections[it->second];
assert(sec);
// skip non-exec and non-alloc sections.
// if( (sec->get_flags() & SHF_ALLOC) ==0 || (sec->get_flags() & SHF_EXECINSTR) ==0 )
if(!sec->isLoadable() || !sec->isExecutable())
{
++it;
continue;
}
// setup start of scoop.
auto text_start=m_firp->addNewAddress(m_firp->getFile()->getBaseID(), sec->get_address());
/*
* ... walk the subsequent sections and coalesce as many as possible
* into a single executable address range.
*/
while(1)
{
sec = exeiop->sections[it->second];
// skip non-alloc sections.
// if( (sec->get_flags() & SHF_ALLOC) ==0)
if(!sec->isLoadable())
{
++it;
continue;
}
// stop if not executable.
// if( (sec->get_flags() & SHF_EXECINSTR) ==0 )
if(!sec->isExecutable())
break;
// try next
++it;
if(it==ordered_sections.end())
break;
}
// setup end of scoop address
/*
auto text_end=new AddressID_t();
// insert into IR
m_firp->getAddresses().insert(text_end);
*/
auto text_end=m_firp->addNewAddress(m_firp->getFile()->getBaseID(),0);
// two cases for end-of-scoop
if (it==ordered_sections.end())
// 1 ) this is the last section
text_end->setVirtualOffset(page_round_up(sec->get_address()+sec->get_size()-1)-1);
else
// 2 ) another section gets in the way.
text_end->setVirtualOffset(sec->get_address()-1);
// setup a scoop for this section.
// zero init is OK, after zipring we'll update with the right bytes.
string text_contents;
string text_name=string(".zipr_text_")+to_string(count++);
if(count==1)
text_name=".text"; // use the name .text first.
text_contents.resize(text_end->getVirtualOffset() - text_start->getVirtualOffset()+1);
//
// DataScoop_t* text_scoop=new DataScoop_t(m_firp->GetMaxBaseID()+1, text_name, text_start, text_end, nullptr, 5 /*R-X*/, false, text_contents);
// m_firp->getDataScoops().insert(text_scoop);
//
auto text_scoop=m_firp->addNewDataScoop(text_name, text_start, text_end, nullptr, 5 /*R-X*/, false, text_contents);
cout<<"Adding scoop "<<text_scoop->getName()<<hex<<" at "<<hex<<text_start->getVirtualOffset()<<" - "<<text_end->getVirtualOffset()<<endl;
m_zipr_scoops.insert(text_scoop);
memory_space.AddFreeRange(Range_t(text_start->getVirtualOffset(),text_end->getVirtualOffset()), true);
}
}
RangeAddress_t ZiprImpl_t::PlaceUnplacedScoops(RangeAddress_t max_addr)
{
max_addr=plugman.PlaceScoopsBegin(max_addr);
auto scoops_by_perms= map<int,DataScoopSet_t>();
for(auto scoop : m_firp->getDataScoops())
{
// check if placed.
if(scoop->getStart()->getVirtualOffset()==0)
scoops_by_perms[scoop->isRelRo() << 16 | scoop->getRawPerms()].insert(scoop);
}
// for(auto pit=scoops_by_perms.begin(); pit!=scoops_by_perms.end(); ++pit)
for(auto &p : scoops_by_perms )
{
// start by rounding up to a page boundary so that page perms don't get unioned.
max_addr=page_round_up(max_addr);
for(auto &scoop : p.second)
{
max_addr=align_up_to(max_addr,(RangeAddress_t)16); // 16 byte align.
scoop->getStart()->setVirtualOffset(scoop->getStart()->getVirtualOffset()+max_addr);
scoop->getEnd()->setVirtualOffset(scoop->getEnd()->getVirtualOffset()+max_addr);
// update so we actually place things at diff locations.
max_addr=scoop->getEnd()->getVirtualOffset()+1;
cout<<"Placing scoop "<<scoop->getName()<<" at "
<<hex<<scoop->getStart()->getVirtualOffset()<<"-"
<<hex<<scoop->getEnd()->getVirtualOffset()<<endl;
}
}
// assert we unpinned everything
for(const auto s : m_firp->getDataScoops())
assert(s->getStart()->getVirtualOffset()!=0);
max_addr=plugman.PlaceScoopsEnd(max_addr);
return max_addr;
}
void ZiprImpl_t::FindFreeRanges(const std::string &name)
{
RangeAddress_t max_addr=0;
std::map<RangeAddress_t, int> ordered_sections;
DataScoopByAddressSet_t sorted_scoop_set;
/*
* This function should be the *only* place where
* scoops are added to m_zipr_scoops. This assert
* is here to maintain that variant.
*/
assert(m_zipr_scoops.empty());
/*
* Make an ordered list of the sections
* by their starting address.
*/
auto n = exeiop->sections.size();
for ( auto i = 0; i < n; ++i )
{
auto sec = exeiop->sections[i];
assert(sec);
ordered_sections.insert({sec->get_address(), i});
}
CreateExecutableScoops(ordered_sections);
// scan sections for a max-addr.
for (auto p : ordered_sections )
{
section* sec = exeiop->sections[p.second];
assert(sec);
RangeAddress_t start=sec->get_address();
RangeAddress_t end=sec->get_size()+start-1;
if (*m_verbose)
printf("max_addr is %p, end is %p\n", (void*)max_addr, (void*)end);
if(start && end>max_addr)
{
if (*m_verbose)
printf("new max_addr is %p\n", (void*)max_addr);
max_addr=end;
}
// if( (sec->get_flags() & SHF_ALLOC) ==0 )
if(!sec->isLoadable())
continue;
}
/*
* First things first: Let's put empty scoops
* in all the gaps.
*/
if (*m_verbose)
cout << "Filling gaps that are larger than " << std::dec
<< *m_paddable_minimum_distance << " bytes." << endl;
/*
* Only put pinned data scoops into the list of
* scoops to consider for adding gap filling.
*/
copy_if(ALLOF(m_firp->getDataScoops()),
inserter(sorted_scoop_set, sorted_scoop_set.begin()),
[](DataScoop_t* ds)
{
return ds->getStart()->getVirtualOffset() != 0;
}
);
for( auto it=sorted_scoop_set.begin(); it!=sorted_scoop_set.end(); ++it )
{
const auto this_scoop = *it;
auto next_scoop = (DataScoop_t*)nullptr;
auto this_end = this_scoop->getEnd()->getVirtualOffset();
auto next_start = RangeAddress_t(0);
assert(this_scoop->getStart()->getVirtualOffset()!=0);
if (*m_verbose)
cout << hex
<< "There's a scoop between " << this_scoop->getStart()->getVirtualOffset()
<< " and " << this_scoop->getEnd()->getVirtualOffset()
<< " with permissions " << +this_scoop->getRawPerms() // print as int, not char
<< endl;
/*
* Never pad after the last scoop.
*/
if (std::next(it,1) != sorted_scoop_set.end())
{
next_scoop = *std::next(it,1);
next_start = next_scoop->getStart()->getVirtualOffset();
unsigned int new_padding_scoop_size = 0;
RangeAddress_t new_padding_scoop_start = this_end + 1;
RangeAddress_t new_padding_scoop_end = next_start - 1;
if (this_end > next_start) {
/*
* It's possible that sections overlap
* one another. Computing the distance
* as an unsigned (as below) causes problems.
* So, we make a special check here.
*/
if (*m_verbose)
cout << "Not considering this section because it "
<< "does not end before the next one starts." << endl;
continue;
}
if (*m_verbose)
cout << "Considering a gap between: 0x" << std::hex
<< new_padding_scoop_start << "-0x"
<< std::hex << new_padding_scoop_end
<< endl;
/*
* If the adjacent scoop is writable, we
* do not want to put an executable scoop
* in the same page.
*/
if (this_scoop->isWriteable())
{
new_padding_scoop_start = page_round_up(new_padding_scoop_start);
if (*m_verbose)
cout << "Adjacent scoop is writable. Adjusting start up to 0x"
<< std::hex << new_padding_scoop_start << "." << endl;
}
/*
* If the next scoop is writable, we
* do not want to put an executable scoop
* in the same page.
*/
if (next_scoop->isWriteable())
{
new_padding_scoop_end = page_round_down(new_padding_scoop_end);
if (*m_verbose)
cout << "Next scoop is writable. Adjusting end down to 0x"
<< std::hex << new_padding_scoop_end << "." << endl;
}
/*
* After making the proper adjustments, we know
* the size of the gap. So, now we have to determine
* whether to pad or not:
*
* 1. Is the gap bigger than the user-defined gap criteria
* 2. One or both of the surrounding segments are not
* writable (a policy decision not to pad between
* writable segments.
*/
new_padding_scoop_size = new_padding_scoop_start - new_padding_scoop_end;
if ((new_padding_scoop_size>(unsigned int)*m_paddable_minimum_distance) &&
(!this_scoop->isWriteable() || !next_scoop->isWriteable())
)
{
string new_padding_scoop_contents, new_padding_scoop_name;
int new_padding_scoop_perms = 0x5 /* r-x */;
new_padding_scoop_name = "zipr_scoop_"+
to_string(new_padding_scoop_start);
/*
DataScoop_t *new_padding_scoop = nullptr;
AddressID_t *new_padding_scoop_start_addr = nullptr,
*new_padding_scoop_end_addr = nullptr;
new_padding_scoop_start_addr = new AddressID_t();
new_padding_scoop_start_addr->setVirtualOffset(new_padding_scoop_start);
m_firp->getAddresses().insert(new_padding_scoop_start_addr);
*/
auto new_padding_scoop_start_addr=m_firp->addNewAddress(m_firp->getFile()->getBaseID(), new_padding_scoop_start);
/*
new_padding_scoop_end_addr = new AddressID_t();
new_padding_scoop_end_addr->setVirtualOffset(new_padding_scoop_end);
m_firp->getAddresses().insert(new_padding_scoop_end_addr);
*/
auto new_padding_scoop_end_addr =m_firp->addNewAddress(m_firp->getFile()->getBaseID(), new_padding_scoop_end);
cout << "Gap filling with a scoop between 0x"
<< std::hex << new_padding_scoop_start << " and 0x"
<< std::hex << new_padding_scoop_end
<< endl;
auto new_padding_scoop = m_firp->addNewDataScoop(
new_padding_scoop_name,
new_padding_scoop_start_addr,
new_padding_scoop_end_addr,
nullptr,
new_padding_scoop_perms,
false,
new_padding_scoop_contents);
new_padding_scoop_contents.resize(new_padding_scoop->getSize());
new_padding_scoop->setContents(new_padding_scoop_contents);
/*
* Insert this scoop into a list of scoops that Zipr added.
*/
m_zipr_scoops.insert(new_padding_scoop);
/*
* Tell Zipr that it can put executable code in this section.
*/
memory_space.AddFreeRange(Range_t(new_padding_scoop_start,
new_padding_scoop_end), true);
}
}
}
/*
* Scan the scoops that we added to see if we went beyond
* the previously highest known address. This should never
* happen because we never pad after the last scoop.
*/
auto max_addr_zipr_scoops_result = max_element(ALLOF(m_zipr_scoops),
[](DataScoop_t *a, DataScoop_t *b)
{
return a->getEnd()->getVirtualOffset() <
b->getEnd()->getVirtualOffset();
}
);
assert(max_addr>=(*max_addr_zipr_scoops_result)->getEnd()->getVirtualOffset());
max_addr=PlaceUnplacedScoops(max_addr);
// now that we've looked at the sections, add a (mysterious) extra section in case we need to overflow
// the sections existing in the ELF.
RangeAddress_t new_free_page=page_round_up(max_addr);
/*
* TODO
*
* Make a scoop out of this. Insert it into m_zipr_scoops
* and m_firp->getDataScoops()
*/
auto textra_start = RangeAddress_t(new_free_page);
auto textra_end = (RangeAddress_t)-1;
auto textra_name = string("textra");
auto textra_start_addr = m_firp->addNewAddress(m_firp->getFile()->getBaseID(), textra_start);
auto textra_end_addr = m_firp->addNewAddress(m_firp->getFile()->getBaseID(), textra_end);
cout << "New free space: 0x" << std::hex << textra_start
<< "-0x"
<< std::hex << textra_end
<< endl;
auto textra_contents=string();
auto textra_scoop = m_firp->addNewDataScoop(
textra_name,
textra_start_addr,
textra_end_addr,
nullptr,
/* r-x */5,
false,
textra_contents);
/*
* Normally we would have to resize the underlying contents here.
* Unfortunately that's not a smart idea since it will be really big.
* Instead, we are going to do a batch resizing below.
*/
m_zipr_scoops.insert(textra_scoop);
memory_space.AddFreeRange(Range_t(new_free_page,(RangeAddress_t)-1), true);
if (*m_verbose)
printf("Adding (mysterious) free range 0x%p to EOF\n", (void*)new_free_page);
start_of_new_space=new_free_page;
for(auto scoop : m_firp->getDataScoops())
{
// skip the scoops we just added.
if(scoop->getBaseID()==BaseObj_t::NOT_IN_DATABASE) continue;
// put scoops in memory to make sure they are busy,
// just in case they overlap with free ranges.
// this came up on Aarch64 because data is in the .text segment.
cout<<"Pre-allocating scoop "<<scoop->getName() << "=("
<< scoop->getStart()->getVirtualOffset() << "-"
<< scoop->getEnd() ->getVirtualOffset() << ")"<<endl;
memory_space.PlopBytes(scoop->getStart()->getVirtualOffset(),
scoop->getContents().c_str(),
scoop->getContents().size()
);
}
}
Instruction_t *ZiprImpl_t::FindPatchTargetAtAddr(RangeAddress_t addr)
{
std::map<RangeAddress_t,UnresolvedUnpinnedPatch_t>::iterator it=m_PatchAtAddrs.find(addr);
if(it!=m_PatchAtAddrs.end())
return it->second.first.getInstrution();
return nullptr;
}
void ZiprImpl_t::WriteDollops()
{
for (auto & dollop_to_write : m_dollop_mgr.getDollops() )
{
assert(dollop_to_write != nullptr);
// skip unplaced dollops as they aren't necessary
if (!dollop_to_write->isPlaced())
continue;
// write each entry in the dollop
for (auto &entry_to_write : *dollop_to_write)
{
assert(entry_to_write != nullptr);
// plop it.
const auto de_end_loc = _PlopDollopEntry(entry_to_write);
// sanity check that we didn't go passed the worst case size we calculate for this entry
const auto de_start_loc = entry_to_write->getPlace();
const auto should_end_at = de_start_loc + DetermineDollopEntrySize(entry_to_write, false);
assert(de_end_loc == should_end_at);
/*
* Build up a list of those dollop entries that we have
* just written that have a target. See comment above
* ReplopDollopEntriesWithTargets() for the reason that
* we have to do this.
*/
const auto will_replop=entry_to_write->getTargetDollop()!=nullptr;
if (will_replop)
m_des_to_replop.push_back(entry_to_write);
}
}
}
/*
* We have to potentially replop dollop entries with targets
* because:
*
* A plugin that writes dollop entries may put the instructions
* NOT in the first position. This is particularly common in CFI:
*
* 0x...01: f4
* 0x...02: INSN
*
* However, the writer cannot know every place where that happens
* until after the entire WriteDollops() function has completed.
* So, we go back and do another pass here once we know all those
* actual instruction addresses (which are completely and fully
* assigned during the call to _PlopDollopEntry.).
*/
void ZiprImpl_t::ReplopDollopEntriesWithTargets()
{
for (auto entry_to_write : m_des_to_replop)
{
Instruction_t *src_insn = nullptr;
RangeAddress_t src_insn_addr;
src_insn = entry_to_write->getInstruction();
src_insn_addr = final_insn_locations[src_insn];
_PlopDollopEntry(entry_to_write, src_insn_addr);
}
}
void ZiprImpl_t::PlaceDollops()
{
auto count_pins=0u;
/*
* Build up initial placement q with destinations of pins.
*/
for (auto p : patch_list)
{
const auto uu = p.first;
const auto patch = p.second;
auto target_insn = uu.getInstrution();
auto target_dollop = m_dollop_mgr.getContainingDollop(target_insn);
assert(target_dollop);
placement_queue.insert({target_dollop,patch.getAddress()});
if (*m_verbose)
{
cout << "Original: " << hex << target_insn-> getAddress()-> getVirtualOffset() << " "
<< "vs. Patch: " << patch.getAddress() << endl;
}
count_pins++;
}
assert(getenv("SELF_VALIDATE")==nullptr || count_pins > 3 ) ;
assert(getenv("SELF_VALIDATE")==nullptr || placement_queue.size() > 15 ) ;
cout<<"# ATTRIBUTE Zipr::pins_detected="<<dec<<count_pins<<endl;
cout<<"# ATTRIBUTE Zipr::placement_queue_size="<<dec<<placement_queue.size()<<endl;
/*
* used to check if a reference dollop needs to be added to the placement queue
*/
const auto ensure_insn_is_placed=[&](Instruction_t* insn)
{
if(insn != nullptr)
{
auto containing=m_dollop_mgr.addNewDollops(insn);
assert(containing!=nullptr);
if(!containing->isPlaced())
{
placement_queue.insert({containing, insn->getAddress()->getVirtualOffset()});
}
}
};
// Make sure each instruction referenced in a relocation (regardless
// of if that relocation is on an instruction or a scoop) gets placed.
for(const auto &reloc : m_firp->getRelocations())
ensure_insn_is_placed(dynamic_cast<Instruction_t*>(reloc->getWRT()));
// Make sure each landing pad in a program gets placed.
for(const auto &cs : m_firp->getAllEhCallSites())
ensure_insn_is_placed(cs->getLandingPad());
m_dollop_mgr.UpdateAllTargets();
while (!placement_queue.empty())
{
auto placement=Range_t();
auto placer = DLFunctionHandle_t(nullptr);
auto placed = false;
auto cur_addr = RangeAddress_t(0);
auto has_fallthrough = false;
auto fallthrough = (Zipr_SDK::Dollop_t*)(nullptr);
auto continue_placing = false;
auto initial_placement_abuts_pin = false;
auto initial_placement_abuts_fallthrough = false;
auto fits_entirely = false;
auto fallthrough_dollop_place = RangeAddress_t(0);
auto fallthrough_has_preplacement = false;
auto pq_entry = *(placement_queue.begin());
placement_queue.erase(placement_queue.begin());
auto to_place = pq_entry.first;
auto from_address = pq_entry.second;
if (*m_vverbose)
{
cout << "Placing dollop with original starting address: " << hex
<< to_place->front()->getInstruction()->getAddress()->getVirtualOffset() << endl;
}
if (to_place->isPlaced())
continue;
to_place->reCalculateSize();
auto minimum_valid_req_size = std::min(
DetermineDollopEntrySize(to_place->front(), true),
sizer->DetermineDollopSizeInclFallthrough(to_place));
/*
* Ask the plugin manager if there are any plugins
* that want to tell us where to place this dollop.
*/
auto am_coalescing = false;
auto allowed_coalescing = true;
auto allowed_fallthrough = true;
if (plugman.DoesPluginAddress(to_place, from_address, placement, allowed_coalescing, allowed_fallthrough, placer))
{
placed = true;
if (*m_verbose)
cout << placer->toString() << " placed this dollop between "
<< hex << placement.getStart() << " and " << placement.getEnd()
<< endl;
/*
* Check if the size that we got back is enough to hold
* at least a little bit of what we wanted.
*
* (1) We want to make sure that there is enough room for at least
* the first instruction of the dollop and space for a jump
* to the remainder of the dollop.
*
* (2) However, it's possible that the entirety of this dollop, plus
* any fallthroughs are going to fit. So, we need to check that
* possibility too.
*
* (3) Then there's the possibility that the dollop *has* a fallthrough
* but that the fallthrough is actually pinned and
* that pin is abutting the end of the dollop in which
* case we elide (I hate that term) the fallthrough jump.
*
* (4) Then there's the possibility that the dollop has a
* fallthrough but that the fallthrough is actually abutting
* the beginning of it's fallthrough dollop in which case we elide
* (still hate that term) the fallthrough jump. Very similar
* to case (3).
*
* TODO: Consider that allowed_coalescing may invalidate the
* possibility of the validity of the placement in (2).
*/
const auto has_fallthrough = to_place->getFallthroughDollop() != nullptr;
const auto ibta=has_fallthrough ? to_place->getFallthroughDollop()-> front()-> getInstruction()-> getIndirectBranchTargetAddress() : 0;
initial_placement_abuts_pin = has_fallthrough &&
ibta &&
ibta -> getVirtualOffset()!=0 &&
ibta-> getVirtualOffset() == (placement.getStart() + to_place->getSize() - sizer->TRAMPOLINE_SIZE);
/*
* If this dollop has a fallthrough, find out where that
* fallthrough is (or is going to be) placed. That way
* we can determine if the current dollop is (or is going to be)
* adjacent to the place of the fallthrough. That means
* that we can keep from placing a jump to the dollo
* and instead just fallthrough.
*/
if (to_place->getFallthroughDollop() && allowed_fallthrough)
{
/*
* Find out where the fallthrough dollop is placed.
*/
if (to_place->getFallthroughDollop()->isPlaced())
{
fallthrough_dollop_place = to_place->getFallthroughDollop()->getPlace();
fallthrough_has_preplacement = true;
}
/*
* Find out where the fallthrough dollop is
* going to be placed. We only have to ask
* plugins about this since we know that zipr-proper
* does not preallocate placements like plugins
* are known to do.
*/
else
{
Range_t fallthrough_placement;
bool fallthrough_allowed_coalescing = false;
bool fallthrough_allowed_fallthrough = false;
DLFunctionHandle_t fallthrough_placer = nullptr;
/*
* Prospectively get the place for this dollop. That way
* we can determine whether or not we need to use a fallthrough!
*/
if (plugman.DoesPluginAddress(to_place->getFallthroughDollop(),
from_address,
fallthrough_placement,
fallthrough_allowed_coalescing,
fallthrough_allowed_fallthrough,
fallthrough_placer))
{
fallthrough_dollop_place = fallthrough_placement.getStart();
fallthrough_has_preplacement = true;
}
}
}
initial_placement_abuts_fallthrough = to_place->getFallthroughDollop() &&
fallthrough_has_preplacement &&
fallthrough_dollop_place == (placement.getStart() + to_place->getSize() - sizer->TRAMPOLINE_SIZE);
auto fits_entirely = (to_place->getSize() <= (placement.getEnd()-placement.getStart()));
if (*m_verbose)
{
cout << "initial_placement_abuts_pin : "
<<initial_placement_abuts_pin << endl
<< "initial_placement_abuts_fallthrough: "
<< initial_placement_abuts_fallthrough << endl
<< "fits_entirely : "
<< fits_entirely << endl;
}
if ( ((placement.getEnd()-placement.getStart()) < minimum_valid_req_size) &&
!(initial_placement_abuts_pin || initial_placement_abuts_fallthrough || fits_entirely)
)
{
if (*m_verbose)
cout << "Bad getNearbyFreeRange() result." << endl;
placed = false;
}
}
if (!placed)
{
// cout << "Using default place locator." << endl;
/*
* TODO: Re-enable this ONCE we figure out why the dollop
* sizes are not being recalculated correctly.
*/
//placement = memory_space.getFreeRange(to_place->getSize());
placement = sizer->DoPlacement(minimum_valid_req_size);
/*
* Reset allowed_coalescing because DoesPluginAddress
* may have reset it and we may have rejected the results
* of that addressing.
*/
allowed_coalescing = true;
}
cur_addr = placement.getStart();
//cout << "Adjusting cur_addr to " << std::hex << cur_addr << " at A." << endl;
has_fallthrough = (to_place->getFallthroughDollop() != nullptr);
if (*m_vverbose)
{
cout << "Dollop size=" << dec << to_place->getSize() << ". Placing in hole size="
<< (placement.getEnd() - placement.getStart()) << " hole at " << hex << cur_addr << endl;
cout << "Dollop " << ((has_fallthrough) ? "has " : "does not have ")
<< "a fallthrough" << endl;
}
const auto has_pinned_ibta=
to_place->front()->getInstruction()->getIndirectBranchTargetAddress() &&
to_place->front()->getInstruction()->getIndirectBranchTargetAddress()->getVirtualOffset()!=0 ;
const auto pinned_ibta_addr = has_pinned_ibta ?
to_place-> front()->getInstruction()-> getIndirectBranchTargetAddress()-> getVirtualOffset() :
VirtualOffset_t(0);
if (has_pinned_ibta && cur_addr == pinned_ibta_addr)
{
unsigned int space_to_clear = sizer->SHORT_PIN_SIZE;
/*
* We have placed this dollop at the location where
* its first instruction was pinned in memory.
*/
if (*m_verbose)
cout << "Placed atop its own pin!" << endl;
if (memory_space[cur_addr] == (char)0xe9)
space_to_clear = sizer->LONG_PIN_SIZE;
for (unsigned int j = cur_addr; j<(cur_addr+space_to_clear); j++)
{
memory_space.mergeFreeRange(j);
}
/*
* Remove the replaced pin from the patch list.
*/
UnresolvedUnpinned_t uu(to_place->front()->getInstruction());
Patch_t emptypatch(cur_addr, UncondJump_rel32);
auto found_patch = patch_list.find(uu);
assert(found_patch != patch_list.end());
patch_list.erase(found_patch);
}
/*
* Handle the case where the placer put us atop the fallthrough
* link from it's FallbackDollop()
*/
else if ( // has dollop that falls through to us.
to_place->getFallbackDollop() &&
// and it's already placed.
to_place->getFallbackDollop()->isPlaced() &&
// and the place is adjacent to us
( to_place->getFallbackDollop()->getPlace() + to_place->getFallbackDollop()->getSize() - sizer->TRAMPOLINE_SIZE) == placement.getStart()
)
{
/*
* We have placed this dollop at the location where
* the fallthrough jump to this dollop was placed.
*/
if (*m_verbose)
cout << "Placed atop its own fallthrough!" << endl;
/*
* Note: We do NOT have to clear any pre-reserved
* memory here now that we have pre-checks on
* whether the dollop is placed. Because of that
* precheck, this range will never be unnecessarily
* reserved for a jump.
*/
}
assert(to_place->getSize() != 0);
do
{
bool all_fallthroughs_fit = false;
size_t wcds = 0;
if (am_coalescing)
{
/*
* Only reset this if we are on a
* second, third, fourth ... go-round.
*/
fits_entirely = false;
}
/*
* TODO: From here, we want to place the dollop
* that we just got a placement for, and subsequently
* place any dollops that are fallthroughs!
*/
/*
* Assume that we will stop placing after this dollop.
*/
continue_placing = false;
to_place->reCalculateSize();
/*
* Calculate before we place this dollop.
*/
wcds = sizer->DetermineDollopSizeInclFallthrough(to_place);
to_place->Place(cur_addr);
// cout << "to_place->getSize(): " << to_place->getSize() << endl;
fits_entirely = (to_place->getSize() <= (placement.getEnd()-cur_addr));
all_fallthroughs_fit = (wcds <= (placement.getEnd()-cur_addr));
auto dit = to_place->begin();
auto dit_end = to_place->end();
for ( /* empty */; dit != dit_end; dit++)
{
auto dollop_entry = *dit;
/*
* There are several ways that a dollop could end:
* 1. There is no more fallthrough (handled above with
* the iterator through the dollop entries)
* 2. There is no more room in this range.
* a. Must account for a link between split dollops
* b. Must account for a possible fallthrough.
* So, we can put this dollop entry here if any of
* the following are true:
* 1. There is enough room for the instruction AND fallthrough.
* Call this the de_and_fallthrough_fit case.
* 2. There is enough room for the instruction AND it's the
* last instruction in the dollop AND there is no
* fallthrough.
* Call this the last_de_fits case.
* 3. This dollop has no fallthrough and fits entirely
* within the space allotted.
* Call this the fits_entirely case.
* 4. The dollop and all of its fallthroughs will fit
* "[A]ll of its fallthoughs will fit" encompasses
* the possibility that one of those is already
* placed -- we use the trampoline size at that point.
* See DetermineDollopSizeInclFallthrough().
* Call this the all_fallthroughs_fit case.
* 5. NOT (All fallthroughs fit is the only way that we are
* allowed to proceed placing this dollop but we are not
* allowed to coalesce and we are out of space for the
* jump to the fallthrough.)
* Call this the !allowed_override case
* 6. There is enough room for this instruction AND it is
* the last entry of this dollop AND the dollop has a
* fallthrough AND that fallthrough is to a pin that
* immediately follows this instruction in memory.
* Call this initial_placement_abuts (calculated above).
*/
const auto de_and_fallthrough_fit =
// does this fit, i.e., end>current+rest_of_dollop
(placement.getEnd()>= (cur_addr+DetermineDollopEntrySize(dollop_entry, true)));
const auto is_last_insn = next(dit)==dit_end; /* last */
const auto has_fallthrough_dollop = to_place->getFallthroughDollop()!=nullptr ;
const auto fits_with_fallthrough = placement.getEnd()>=(cur_addr+ DetermineDollopEntrySize(dollop_entry, has_fallthrough_dollop));
const auto last_de_fits = is_last_insn && fits_with_fallthrough;
const auto could_fit_here =
de_and_fallthrough_fit ||
fits_entirely ||
last_de_fits ||
initial_placement_abuts_pin ||
initial_placement_abuts_fallthrough ;
const auto tramp_fits =
(placement.getEnd() - (cur_addr + DetermineDollopEntrySize( dollop_entry, false))) < sizer->TRAMPOLINE_SIZE;
const auto allowed_override =
allowed_coalescing ||
could_fit_here ||
!all_fallthroughs_fit ||
!tramp_fits ;
const auto beneficial_to_override =
de_and_fallthrough_fit ||
last_de_fits ||
fits_entirely ||
initial_placement_abuts_fallthrough ||
initial_placement_abuts_pin ||
all_fallthroughs_fit ;
if (*m_vverbose)
{
struct custom_bool : numpunct<char>
{
protected:
string do_truename() const override { return "t" ; }
string do_falsename() const override { return "f" ; }
};
static struct custom_bool *cb=new custom_bool;
// set cout to print t/f
cout.imbue( { cout.getloc(), cb } );
cout << "Placement stats: "
<< de_and_fallthrough_fit << ", "
<< last_de_fits << ", "
<< fits_entirely << ", "
<< all_fallthroughs_fit << ", "
<< initial_placement_abuts_pin << ", "
<< initial_placement_abuts_fallthrough << ", "
<< initial_placement_abuts_pin << ", "
<< allowed_override << noboolalpha << endl;
}
if ( beneficial_to_override && allowed_override )
{
dollop_entry->Place(cur_addr);
const auto wcsz=DetermineDollopEntrySize(dollop_entry, false);
const auto next_cur_addr=cur_addr+wcsz;
if (*m_vverbose)
{
auto d=DecodedInstruction_t::factory(dollop_entry->getInstruction());
cout << "Placing " << hex << dollop_entry->getInstruction()->getBaseID()
<< ":" << d->getDisassembly() << " at "
<< cur_addr << "-" << next_cur_addr << endl;
}
cur_addr=next_cur_addr;
if (dollop_entry->getTargetDollop())
{
if (*m_vverbose)
cout << "Adding " << std::hex << dollop_entry->getTargetDollop()
<< " to placement queue." << endl;
placement_queue.insert({dollop_entry->getTargetDollop(), cur_addr});
}
}
else
{
/*
* We cannot fit all the instructions. Let's quit early.
*/
break;
}
}
if (dit != dit_end)
{
/*
* Split the dollop where we stopped being able to place it.
* In this case, splitting the dollop will give it a fallthrough.
* That will be used below to put in the necessary patch.
*
* However ... (see below)
*/
auto split_dollop = to_place->split((*dit)->getInstruction());
m_dollop_mgr.AddDollops(split_dollop);
to_place->setTruncated(true);
if (am_coalescing)
m_stats->truncated_dollops_during_coalesce++;
if (*m_vverbose)
cout << "Split a "
<< ((am_coalescing) ? "coalesced " : " ")
<< "dollop because it didn't fit. Fallthrough to "
<< std::hex << split_dollop << "." << endl;
}
/*
* (from above) ... we do not want to "jump" to the
* fallthrough if we can simply place it following
* this one!
*/
fallthrough = to_place->getFallthroughDollop();
if ( fallthrough != nullptr && !to_place->wasCoalesced() )
{
size_t fallthroughs_wcds, fallthrough_wcis, remaining_size;
/*
* We do not care about the fallthrough dollop if its
* first instruction is pinned AND the last entry of this
* dollop abuts that pin.
*/
const auto has_ibta = fallthrough-> front()-> getInstruction()-> getIndirectBranchTargetAddress();
const auto pinned_ibta_addr = has_ibta ? fallthrough-> front()-> getInstruction()-> getIndirectBranchTargetAddress()-> getVirtualOffset() : VirtualOffset_t(0);
const auto is_pinned_ibta_addr = has_ibta && pinned_ibta_addr!=0;
const auto is_pinned_here = (cur_addr == pinned_ibta_addr ) ;
if ( has_ibta && is_pinned_ibta_addr && is_pinned_here )
{
if (*m_verbose)
cout << "Dollop had a fallthrough dollop and "
<< "was placed abutting the fallthrough "
<< "dollop's pinned first instruction. "
<< endl;
/*
* Because the fallthrough dollop is pinned, we
* know that it is already in the placement q. That's
* the reason that we do not have to add it here. See
* below for a contrast.
*/
m_stats->total_did_not_coalesce++;
break;
}
/*
* If the fallthrough is placed and it is immediately after
* this instruction, then we don't want to write anything else!
*
* TODO: This calculation is only valid if we are NOT coalescing.
* We need to change this condition or reset some of the variables
* so that we do not rely on !am_coalescing as a condition.
* Actually, we should make it work correctly -- ie, make sure that
* even if we do coaelesce something its fallthrough could
* be preplaced ...
*/
if (!am_coalescing && to_place->getFallthroughDollop() && fallthrough_has_preplacement && fallthrough_dollop_place == cur_addr)
{
if (*m_verbose)
cout << "Dollop had a fallthrough dollop and "
<< "was placed abutting the fallthrough "
<< "dollop's first instruction. "
<< endl;
/*
* We are not coalescing, but we want to make sure that
* the fallthrough does get placed if zipr hasn't already
* done so. See above for a contrast.
*/
if (!to_place->getFallthroughDollop()->isPlaced())
{
placement_queue.insert({to_place->getFallthroughDollop(), cur_addr});
}
m_stats->total_did_not_coalesce++;
break;
}
/*
* We could fit the entirety of the dollop (and
* fallthroughs) ...
*/
fallthroughs_wcds = sizer->DetermineDollopSizeInclFallthrough(fallthrough);
/*
* ... or maybe we just want to start the next dollop.
*/
fallthrough_wcis=DetermineDollopEntrySize(fallthrough-> front(),
true);
remaining_size = placement.getEnd() - cur_addr;
/*
* We compare remaining_size to min(fallthroughs_wdcs,
* fallthrough_wcis) since the entirety of the dollop
* and its fallthroughs could (its unlikely) be
* smaller than the first instruction fallthrough
* in the fallthrough dollop and the trampoline size.
*/
if (*m_vverbose)
cout << "Determining whether to coalesce: "
<< "Remaining: " << std::dec << remaining_size
<< " vs Needed: " << std::dec
<< std::min(fallthrough_wcis,fallthroughs_wcds) << endl;
if (remaining_size < std::min(fallthrough_wcis,fallthroughs_wcds) ||
fallthrough->isPlaced() ||
!allowed_coalescing
)
{
string patch_jump_string;
auto patch = archhelper->createNewJumpInstruction(m_firp, nullptr);
auto patch_de = new DollopEntry_t(patch, to_place);
patch_de->setTargetDollop(fallthrough);
patch_de->Place(cur_addr);
cur_addr+=DetermineDollopEntrySize(patch_de, false);
//cout << "Adjusting cur_addr to " << std::hex << cur_addr << " at C." << endl;
to_place->push_back(patch_de);
to_place->setFallthroughPatched(true);
if (*m_vverbose)
cout << "Not coalescing"
<< string((fallthrough->isPlaced()) ? " because fallthrough is placed" : "")
<< string((!allowed_coalescing) ? " because I am not allowed" : "")
<< "; Added jump (via " << std::hex << patch_de
<< " at " << std::hex << patch_de->getPlace() << ") "
<< "to fallthrough dollop (" << std::hex
<< fallthrough << ")." << endl;
placement_queue.insert({fallthrough, cur_addr});
/*
* Since we inserted a new instruction, we should
* check to see whether a plugin wants to plop it.
*/
AskPluginsAboutPlopping(patch_de->getInstruction());
m_stats->total_did_not_coalesce++;
/*
* Quit the do-while-true loop that is placing
* as many dollops in-a-row as possible.
*/
break;
}
else
{
if (*m_vverbose)
cout << "Coalescing fallthrough dollop." << endl;
to_place->setCoalesced(true);
/*
* Fallthrough is not placed and there is enough room to
* put (at least some of) it right below the previous one.
*/
to_place = fallthrough;
continue_placing = true;
m_stats->total_did_coalesce++;
am_coalescing = true;
}
}
} while (continue_placing);
/*
* This is the end of the do-while-true loop
* that will place as many fallthrough-linked
* dollops as possible.
*/
/*
* Reserve the range that we just used.
*/
if (*m_vverbose)
cout << "Reserving " << std::hex << placement.getStart()
<< ", " << std::hex << cur_addr << "." << endl;
memory_space.splitFreeRange(Range_t(placement.getStart(), cur_addr));
}
}
void ZiprImpl_t::RecalculateDollopSizes()
{
for (auto &dollop : m_dollop_mgr.getDollops())
dollop->reCalculateSize();
}
void ZiprImpl_t::CreateDollops()
{
if (*m_verbose)
cout<< "Attempting to create "
<< patch_list.size()
<< " dollops for the pins."
<< endl;
for (auto patch : patch_list )
m_dollop_mgr.AddNewDollops(patch.first.getInstrution());
if (*m_verbose)
cout << "Done creating dollops for the pins! Updating all Targets" << endl;
m_dollop_mgr.UpdateAllTargets();
if (*m_verbose)
cout << "Created " <<std::dec << m_dollop_mgr.Size() << " total dollops." << endl;
}
void ZiprImpl_t::CallToNop(RangeAddress_t at_addr)
{
assert(patcher);
patcher->CallToNop(at_addr);
return;
}
void ZiprImpl_t::PatchCall(RangeAddress_t at_addr, RangeAddress_t to_addr)
{
assert(patcher);
patcher->PatchCall(at_addr,to_addr);
}
size_t ZiprImpl_t::DetermineDollopEntrySize(Zipr_SDK::DollopEntry_t *entry, bool account_for_fallthrough)
{
std::map<Instruction_t*,unique_ptr<list<DLFunctionHandle_t>>>::const_iterator plop_it;
size_t opening_size = 0, closing_size = 0;
size_t wcis = DetermineInsnSize(entry->getInstruction(), account_for_fallthrough);
plop_it = plopping_plugins.find(entry->getInstruction());
if (plop_it != plopping_plugins.end())
{
for (auto handle : *(plop_it->second))
{
ZiprPluginInterface_t *zpi = dynamic_cast<ZiprPluginInterface_t*>(handle);
opening_size += zpi->getDollopEntryOpeningSize(entry);
closing_size += zpi->getDollopEntryClosingSize(entry);
}
}
if (*m_verbose)
{
}
return wcis+opening_size+closing_size;
}
size_t ZiprImpl_t::DetermineInsnSize(Instruction_t* insn, bool account_for_fallthrough)
{
std::map<Instruction_t*,unique_ptr<list<DLFunctionHandle_t>>>::const_iterator plop_it;
size_t worst_case_size = 0;
size_t default_worst_case_size = 0;
default_worst_case_size = sizer->DetermineInsnSize(insn, account_for_fallthrough);
plop_it = plopping_plugins.find(insn);
if (plop_it != plopping_plugins.end())
{
for (auto handle : *(plop_it->second))
{
ZiprPluginInterface_t *zpi = dynamic_cast<ZiprPluginInterface_t*>(handle);
worst_case_size =std::max(zpi->getInsnSize(insn, account_for_fallthrough), worst_case_size);
}
}
else
{
worst_case_size = default_worst_case_size;
}
if (worst_case_size == 0)
{
if (*m_verbose)
cout << "Asked plugins about WCIS, but none responded." << endl;
worst_case_size = default_worst_case_size;
}
if (*m_vverbose)
{
const auto inc_jmp=((account_for_fallthrough) ? " (including jump)" : "");
cout << "Worst case size" << inc_jmp << ": " << worst_case_size << endl;
}
return worst_case_size;
}
bool ZiprImpl_t::AskPluginsAboutPlopping(Instruction_t *insn)
{
/*
* Plopping plugins should hold a set.
*/
unique_ptr<list<DLFunctionHandle_t>> found_plopping_plugins =
unique_ptr<list<DLFunctionHandle_t>>(new std::list<DLFunctionHandle_t>());
if (plugman.DoPluginsPlop(insn, *found_plopping_plugins))
{
if (*m_verbose)
for (auto pp : *found_plopping_plugins)
{
ZiprPluginInterface_t *zipr_plopping_plugin =
dynamic_cast<ZiprPluginInterface_t*>(pp);
cout << zipr_plopping_plugin->toString()
<< " will plop "<<dec<<insn->getBaseID() << ":"
<< insn->getDisassembly() << endl;
}
plopping_plugins[insn] = std::move(found_plopping_plugins);
return true;
}
return false;
}
void ZiprImpl_t::AskPluginsAboutPlopping()
{
for(auto &insn : m_firp->getInstructions())
AskPluginsAboutPlopping(insn);
}
void ZiprImpl_t::UpdatePins()
{
while(!patch_list.empty())
{
UnresolvedUnpinned_t uu=(*patch_list.begin()).first;
Patch_t p=(*patch_list.begin()).second;
Zipr_SDK::Dollop_t *target_dollop = nullptr;
Zipr_SDK::DollopEntry_t *target_dollop_entry = nullptr;
Instruction_t *target_dollop_entry_instruction = nullptr;
RangeAddress_t patch_addr, target_addr;
target_dollop = m_dollop_mgr.getContainingDollop(uu.getInstrution());
assert(target_dollop != nullptr);
DLFunctionHandle_t patcher = nullptr;
target_dollop_entry = target_dollop->front();
assert(target_dollop_entry != nullptr);
target_dollop_entry_instruction = target_dollop_entry->getInstruction();
assert(target_dollop_entry_instruction != nullptr &&
target_dollop_entry_instruction == uu.getInstrution());
patch_addr = p.getAddress();
target_addr = target_dollop_entry->getPlace();
if (final_insn_locations.end() != final_insn_locations.find(target_dollop_entry->getInstruction()))
target_addr = final_insn_locations[target_dollop_entry->getInstruction()];
if (plugman.DoesPluginRetargetPin(patch_addr, target_dollop, target_addr, patcher))
{
if (*m_verbose)
{
cout << "Patching retargeted pin at " << hex<<patch_addr << " to "
<< patcher->toString() << "-assigned address: " << target_addr << endl;
}
}
else
{
/*
* Even though DoesPluginRetargetPin() returned something other than
* Must, it could have still changed target_address. So, we have to
* reset it here, just in case.
*/
target_addr = target_dollop_entry->getPlace();
if (final_insn_locations.end() != final_insn_locations.find(target_dollop_entry->getInstruction()))
target_addr = final_insn_locations[target_dollop_entry->getInstruction()];
if (*m_verbose)
{
const auto d=DecodedInstruction_t::factory(target_dollop_entry_instruction);
cout << "Patching pin at " << hex << patch_addr << " to "
<< target_addr << ": " << d->getDisassembly() << endl;
}
assert(target_dollop_entry_instruction != nullptr &&
target_dollop_entry_instruction == uu.getInstrution());
}
PatchJump(patch_addr, target_addr);
patch_list.erase(patch_list.begin());
}
}
void ZiprImpl_t::PatchInstruction(RangeAddress_t from_addr, Instruction_t* to_insn)
{
// addr needs to go to insn, but insn has not yet been been pinned.
// patch the instruction at address addr to go to insn. if insn does not yet have a concrete address,
// register that it's patch needs to be applied later.
UnresolvedUnpinned_t uu(to_insn);
const auto thepatch=Patch_t(from_addr,UncondJump_rel32);
const auto it=final_insn_locations.find(to_insn);
if(it==final_insn_locations.end())
{
if (*m_verbose)
printf("Instruction cannot be patch yet, as target is unknown.\n");
patch_list.insert({uu,thepatch});
}
else
{
const auto to_addr=final_insn_locations[to_insn];
assert(to_addr!=0);
/*
* TODO: This debugging output is not really exactly correct.
*/
if (*m_verbose)
printf("Found a patch for %p -> %p\n", (void*)from_addr, (void*)to_addr);
// Apply Patch
ApplyPatch(from_addr, to_addr);
}
}
RangeAddress_t ZiprImpl_t::_PlopDollopEntry(Zipr_SDK::DollopEntry_t *entry, RangeAddress_t override_address)
{
const auto insn = entry->getInstruction();
const auto insn_wcis = DetermineInsnSize(insn, false);
RangeAddress_t updated_addr = 0;
RangeAddress_t target_address = 0;
auto placed_insn = false;
const auto target_dollop=entry->getTargetDollop();
if (target_dollop && target_dollop->front())
{
const auto entry_target_head_insn=entry-> getTargetDollop()-> front()-> getInstruction();
const auto target_address_iter = final_insn_locations.find(entry_target_head_insn);
if (target_address_iter != final_insn_locations.end())
{
target_address = target_address_iter->second;
if (*m_verbose)
cout << "Found an updated target address location: "
<< std::hex << target_address << endl;
}
}
auto placed_address = override_address == 0 ? entry->getPlace() : override_address;
const auto plop_it = plopping_plugins.find(insn);
if (plop_it != plopping_plugins.end())
{
for (auto pp : *(plop_it->second))
{
auto pp_placed_insn = false;
const auto handle = pp;
const auto zpi = dynamic_cast<ZiprPluginInterface_t*>(handle);
const auto plugin_ret=zpi->plopDollopEntry(entry, placed_address, target_address, insn_wcis, pp_placed_insn);
updated_addr = std::max(plugin_ret, updated_addr);
if (*m_verbose)
{
cout << zpi->toString() << " placed entry "
<< std::hex << entry
<< " at address: " << std::hex << placed_address
<< " " << (pp_placed_insn ? "and placed" : "but did not place")
<< " the instruction."
<< endl;
}
placed_insn |= pp_placed_insn;
}
}
/*
* If no plugin actually placed the instruction,
* then we are going to do it ourselves.
*/
if (!placed_insn)
{
/* Some plugins, like scfi, may place the entry but leave it
up to zipr to place the instruction. This does assume that
zipr will place the instruction in a way that is compatible
with what the plugin is trying to do.
TODO: Should we continue to allow this?
*/
const auto zipr_ret = PlopDollopEntry(entry, placed_address, target_address);
updated_addr = std::max(zipr_ret, updated_addr);
}
// sanity check that we aren't moving an instruction that's already been placed.
const auto old_loc=final_insn_locations[insn];
if(old_loc != 0 && old_loc != placed_address )
{
static int count=0;
cout<<"Warning, Moving instruction "<<hex<<insn->getBaseID()<<":"<<insn->getComment()
<<" from "<<hex<<old_loc<<" to "<<placed_address<<endl;
cout<<"Happened for "<<dec<<count++<<" out of "<<m_firp->getInstructions().size()<<" instructions"<<endl;
}
final_insn_locations[insn] = placed_address;
return updated_addr;
}
RangeAddress_t ZiprImpl_t::PlopDollopEntry(
Zipr_SDK::DollopEntry_t *entry,
RangeAddress_t override_place,
RangeAddress_t override_target)
{
Instruction_t *insn = entry->getInstruction();
RangeAddress_t ret = entry->getPlace(), addr = entry->getPlace();
assert(insn);
if (override_place != 0)
addr = ret = override_place;
const auto d=DecodedInstruction_t::factory(insn);
string raw_data = insn->getDataBits();
string orig_data = insn->getDataBits();
if(entry->getTargetDollop() && entry->getInstruction()->getCallback()=="")
{
RangeAddress_t target_address = 0;
auto target_insn = entry->getTargetDollop()->front()->getInstruction();
if (override_target == 0)
{
if (final_insn_locations.end() != final_insn_locations.find(target_insn))
target_address = final_insn_locations[target_insn];
}
else
{
if (*m_verbose)
cout << "Plopping with overriden target: Was: "
<< hex << target_address << " Is: " << override_target << endl;
target_address = override_target;
}
if (*m_verbose)
{
const auto print_target=((target_address != 0) ? target_address : entry->getTargetDollop()->getPlace());
cout << "Plopping '"<<entry->getInstruction()->getDisassembly() <<"' at " << hex << addr
<< " with target " << print_target << endl;
}
ret=PlopDollopEntryWithTarget(entry, addr, target_address);
}
else if(entry->getInstruction()->getCallback()!="")
{
if (*m_verbose)
cout << "Plopping at " << hex << addr << " with callback to "
<< entry->getInstruction()->getCallback() << endl;
ret=PlopDollopEntryWithCallback(entry, addr);
}
else
{
if (*m_verbose)
cout << "Plopping non-ctl "<<insn->getDisassembly()<<" at " << hex << addr << endl;
memory_space.PlopBytes(addr, insn->getDataBits().c_str(), insn->getDataBits().length());
ret+=insn->getDataBits().length();
}
/* Reset the data bits for the instruction back to th
* need to re-plop this instruction later. we need t
* so we can replop appropriately.
*/
insn->setDataBits(orig_data);
return ret;
}
RangeAddress_t ZiprImpl_t::PlopDollopEntryWithTarget(
Zipr_SDK::DollopEntry_t *entry,
RangeAddress_t override_place,
RangeAddress_t override_target)
{
return sizer->PlopDollopEntryWithTarget(entry,override_place,override_target);
}
RangeAddress_t ZiprImpl_t::PlopDollopEntryWithCallback(
Zipr_SDK::DollopEntry_t *entry,
RangeAddress_t override_place)
{
auto at = entry->getPlace();
auto originalAt = entry->getPlace();
if (override_place != 0)
at = originalAt = override_place;
// emit call <callback>
{
char bytes[]={(char)0xe8,(char)0,(char)0,(char)0,(char)0}; // call rel32
memory_space.PlopBytes(at, bytes, sizeof(bytes));
unpatched_callbacks.insert({entry,at});
at+=sizeof(bytes);
}
// pop bogus ret addr
if(m_firp->getArchitectureBitWidth()==64)
{
char bytes[]={(char)0x48,(char)0x8d,(char)0x64,(char)0x24,(char)(m_firp->getArchitectureBitWidth()/0x08)}; // lea rsp, [rsp+8]
memory_space.PlopBytes(at, bytes, sizeof(bytes));
at+=sizeof(bytes);
}
else if(m_firp->getArchitectureBitWidth()==32)
{
char bytes[]={(char)0x8d,(char)0x64,(char)0x24,(char)(m_firp->getArchitectureBitWidth()/0x08)}; // lea esp, [esp+4]
memory_space.PlopBytes(at, bytes, sizeof(bytes));
at+=sizeof(bytes);
}
else
assert(0);
assert(sizer->CALLBACK_TRAMPOLINE_SIZE<=(at-originalAt));
return at;
}
DataScoop_t* ZiprImpl_t::FindScoop(const RangeAddress_t &addr)
{
const auto find_it=find_if(ALLOF(m_firp->getDataScoops()),
[&](const DataScoop_t* scoop)
{
return scoop->getStart()->getVirtualOffset() <= addr &&
addr < scoop->getEnd()->getVirtualOffset() ;
});
return find_it==m_firp->getDataScoops().end() ? nullptr : *find_it;
}
void ZiprImpl_t::OutputBinaryFile(const string &name)
{
// now that the textra scoop has been crated and setup, we have the info we need to
// re-generate the eh information.
RelayoutEhInfo();
const auto file_type = m_firp->getArchitecture()->getFileType();
const auto is_elf = file_type == IRDB_SDK::adftELFEXE || file_type == IRDB_SDK::adftELFSO;
const auto is_pe = file_type == IRDB_SDK::adftPE;
const auto bit_width = m_firp->getArchitectureBitWidth();
const auto output_filename="c.out";
auto ew=unique_ptr<ExeWriter>(
is_pe && bit_width == 64 ? (ExeWriter*)new PeWriter64(exeiop, m_firp, *m_add_sections, *m_bss_opts) :
is_elf && bit_width == 64 ? (ExeWriter*)new ElfWriter64(exeiop, m_firp, *m_add_sections, *m_bss_opts) :
is_elf && bit_width == 32 ? (ExeWriter*)new ElfWriter32(exeiop, m_firp, *m_add_sections, *m_bss_opts) :
throw invalid_argument("Unknown file type/machine width combo")
);
ew->Write(output_filename, "a.ncexe");
ew.reset(nullptr); // explicitly free ew as we're done with it
// change permissions on output file
auto chmod_cmd=string("chmod +x ")+output_filename;
auto res=system(chmod_cmd.c_str());
assert(res!=-1);
}
void ZiprImpl_t::PrintStats()
{
// do something like print stats as #ATTRIBUTES.
m_dollop_mgr.PrintStats(cout);
m_dollop_mgr.PrintPlacementMap(memory_space, *m_dollop_map_filename);
m_stats->PrintStats(cout);
// and dump a map file of where we placed instructions. maybe guard with an option.
// default to dumping to zipr.map
dump_scoop_map();
dump_instruction_map();
}
void ZiprImpl_t::UpdateCallbacks()
{
// first byte of this range is the last used byte.
const auto range_it=memory_space.FindFreeRange((RangeAddress_t) -1);
assert(memory_space.IsValidRange(range_it));
for(const auto &p : unpatched_callbacks)
{
auto entry=p.first;
Instruction_t *insn = entry->getInstruction();
RangeAddress_t at=p.second;
RangeAddress_t to=0x0;//FindCallbackAddress(end_of_new_space,start_addr,insn->getCallback());
DLFunctionHandle_t patcher = nullptr;
if (plugman.DoesPluginRetargetCallback(at, entry, to, patcher))
{
if (*m_verbose)
{
cout << "Patching retargeted callback at " << std::hex << at << " to "
<< patcher->toString() << "-assigned address: "
<< std::hex << to << endl;
}
}
if(to)
{
cout<<"Patching callback "<< insn->getCallback()<<" at "<<std::hex<<at<<" to jump to "<<to<<endl;
PatchCall(at,to);
}
else
{
CallToNop(at);
}
}
}
void ZiprImpl_t::dump_scoop_map()
{
string filename="scoop.map"; // parameterize later.
std::ofstream ofs(filename.c_str(), ios_base::out);
ofs <<left<<setw(10)<<"ID"
<<left<<setw(10)<<"StartAddr"
<<left<<setw(10)<<"Size"
<<left<<setw(10)<<"Perms"
<<left<<setw(10)<<"Name"<<endl;
for(const auto &scoop : m_firp->getDataScoops())
{
ofs << hex << setw(10) << scoop->getBaseID()
<< hex << left << setw(10) << scoop->getStart()->getVirtualOffset()
<< hex << left << setw(10) << scoop->getSize()
<< hex << left << setw(10) << +scoop->getRawPerms() // print as int, not char
<< hex << left << setw(10) << scoop->getName()
<< endl;
}
}
void ZiprImpl_t::dump_instruction_map()
{
string filename="zipr.map"; // parameterize later.
std::ofstream ofs(filename.c_str(), ios_base::out);
ofs <<left<<setw(10)<<"ID"
<<left<<setw(10)<<"OrigAddr"
<<left<<setw(10)<<"IBTA"
<<left<<setw(10)<<"NewAddr"
<<left<<setw(10)<<"FuncID"
<<left<<"Disassembly"<<endl;
for(std::map<IRDB_SDK::Instruction_t*,RangeAddress_t>::iterator it=final_insn_locations.begin();
it!=final_insn_locations.end(); ++it)
{
Instruction_t* insn=it->first;
AddressID_t* ibta=insn->getIndirectBranchTargetAddress();
RangeAddress_t addr=it->second;
ofs << hex << setw(10)<<insn->getBaseID()
<<hex<<left<<setw(10)<<insn->getAddress()->getVirtualOffset()
<<hex<<left<<setw(10)<< (ibta ? ibta->getVirtualOffset() : 0)
<<hex<<left<<setw(10)<<addr
<<hex<<left<<setw(10)<<( insn->getFunction() ? insn->getFunction()->getBaseID() : -1 )
<< left<<insn->getDisassembly()<<endl;
}
}
void ZiprImpl_t::UpdateScoops()
{
for(
DataScoopSet_t::iterator it=m_zipr_scoops.begin();
it!=m_zipr_scoops.end();
)
{
DataScoop_t* scoop=*it;
VirtualOffset_t first_valid_address=0;
VirtualOffset_t last_valid_address=0;
if(!scoop->isExecuteable())
{
++it;
continue;
}
assert(m_zipr_scoops.find(scoop)!=m_zipr_scoops.end());
/*
* Yes, I know that this adds another iteration, but we need to know
* beforehand about shrinking.
*/
if (scoop->getName() == "textra")
{
/*
* We have to do special handling for the textra scoop.
* If we do not, then the sheer scale of the default size
* of the textra scoop will cause Zipr to bomb during the
* next loop.
*/
auto frit=memory_space.FindFreeRange((RangeAddress_t) -1);
assert(memory_space.IsValidRange(frit));
scoop->getEnd()->setVirtualOffset(frit->getStart());
}
for(auto i=scoop->getStart()->getVirtualOffset();
i<= scoop->getEnd()->getVirtualOffset();
i++ )
{
if( ! memory_space.IsByteFree(i) )
{
// record beginning if not already recorded.
if(first_valid_address==0)
first_valid_address=i;
// record that this address was valid.
last_valid_address=i;
}
}
if(last_valid_address==0 || first_valid_address==0)
{
if (*m_verbose)
cout << "Removing an empty scoop (" << scoop->getName() << ")." << endl;
/*
assert(first_valid_address==0);
assert(last_valid_address==0);
m_firp->getAddresses().erase(scoop->getStart());
m_firp->getAddresses().erase(scoop->getEnd());
m_firp->getDataScoops().erase(*it);
// Delete addresses and then the scoop itself.
delete scoop->getStart();
delete scoop->getEnd();
delete scoop;
*/
it = m_zipr_scoops.erase(it);
m_firp->removeScoop(scoop);
scoop=nullptr;
}
else
{
if ((scoop->getStart()->getVirtualOffset() != first_valid_address ||
scoop->getEnd()->getVirtualOffset() != last_valid_address) &&
*m_verbose)
{
cout <<"Shrinking scoop "<<scoop->getName()
<<" to "
<< std::hex << first_valid_address << "-"
<< std::hex << last_valid_address << endl;
}
else if (*m_verbose)
{
cout<<"Leaving scoop "<<scoop->getName()<<" alone. "<<endl;
}
cout << "Updating a scoop named " << scoop->getName() << endl;
assert(first_valid_address!=0);
assert(last_valid_address!=0);
scoop->getStart()->setVirtualOffset(first_valid_address);
scoop->getEnd()->setVirtualOffset(last_valid_address);
/*
* Resize the contents.
*/
auto scoop_contents = scoop->getContents();
scoop_contents.resize(scoop->getEnd()->getVirtualOffset() -
scoop->getStart()->getVirtualOffset() + 1);
assert(scoop->getSize() == scoop_contents.size());
/*
* And now update the contents.
*/
for(auto i=scoop->getStart()->getVirtualOffset();
i<= scoop->getEnd()->getVirtualOffset();
i++)
{
scoop_contents[i-scoop->getStart()->getVirtualOffset()]=memory_space[i];
}
scoop->setContents(scoop_contents);
// m_firp->getDataScoops().insert(scoop); we added this earlier when we created the obj.
// jdh -- a bit worried that this'll break assumptions in other places
++it;
}
}
return;
}
void ZiprImpl_t::FixNoFallthroughs()
{
auto hlt=archhelper->createNewHaltInstruction(m_firp, nullptr);
auto jmp=archhelper->createNewJumpInstruction(m_firp, nullptr);
hlt->setFallthrough(jmp);
jmp->setTarget(hlt);
for(const auto insn : m_firp->getInstructions())
{
if(insn==hlt) continue;
if(insn==jmp) continue;
if(insn->getFallthrough()==nullptr)
{
const auto d=DecodedInstruction_t::factory(insn);
if(d->isConditionalBranch())
insn->setFallthrough(hlt);
}
if(insn->getTarget()==nullptr)
{
const auto d=DecodedInstruction_t::factory(insn);
if(d->isBranch() && !d->isReturn() && d->hasOperand(0) && d->getOperand(0)->isConstant())
insn->setTarget(hlt);
}
}
}
void ZiprImpl_t::FixTwoByteWithPrefix()
{
for(const auto insn : m_firp->getInstructions())
{
const auto d=DecodedInstruction_t::factory(insn);
if(!d->isBranch()) continue; // skip non-branches
if(d->isReturn()) continue; // skip returns
if(d->getOperands().size()!=1) continue; // skip branches that have no operands or more than one
if(!d->getOperand(0)->isConstant()) continue; // skip anything where the operand isn't a constant
if(d->getPrefixCount()==0) continue; // prevents arm instructions from being xformed.
while (true)
{
const auto b=insn->getDataBits()[0];
// basic prefix check
const auto prefixes=set<uint8_t>({0x2e, 0x3e, 0x64, 0x65, 0xf2, 0xf3});
if(prefixes.find(b)!=end(prefixes))
{
// remove prefix
insn->setDataBits(insn->getDataBits().erase(0,1));
}
// remove rex prefix when unnecessary
else if(m_firp->getArchitectureBitWidth()==64 && (b&0xf0)==0x40 /* has rex prefix */)
{
insn->setDataBits(insn->getDataBits().erase(0,1));
}
else
break;
}
}
}
void ZiprImpl_t::FixMultipleFallthroughs()
{
auto count=0;
auto fallthrough_from=map<Instruction_t*, InstructionSet_t>();
for(auto & insn : m_firp->getInstructions())
{
auto ft=insn->getFallthrough();
if(ft)
fallthrough_from[ft].insert(insn);
};
for(auto &p : fallthrough_from)
{
auto ft=p.first;
if(p.second.size()>1)
{
// skip the first one, because something can fallthrough, just not everything.
for_each(next(p.second.begin()), p.second.end(), [&](Instruction_t* from)
{
auto newjmp=archhelper->createNewJumpInstruction(m_firp,nullptr);
count++;
newjmp->setTarget(ft);
from->setFallthrough(newjmp);
});
};
}
// after we've inserted all the jumps, assemble them.
m_firp->assembleRegistry();
cout<<"# ATTRIBUTE Zipr::jumps_inserted_for_multiple_fallthroughs="<<dec<<count<<endl;
}
void ZiprImpl_t::RelayoutEhInfo()
{
if(m_firp->getAllEhPrograms().size() == 0 && m_firp->getAllEhCallSites().size() ==0)
return;
EhWriter::EhWriter_t::factory(*this) -> GenerateNewEhInfo();
}
void ZiprImpl_t::ApplyNopToPatch(RangeAddress_t addr)
{
if (!*m_apply_nop)
{
if (*m_verbose)
cout << "Skipping chance to apply nop to fallthrough patch." << endl;
return;
}
assert(patcher);
patcher->ApplyNopToPatch(addr);
}
void ZiprImpl_t::ApplyPatch(RangeAddress_t from_addr, RangeAddress_t to_addr)
{
assert(patcher);
patcher->ApplyPatch(from_addr,to_addr);
}
void ZiprImpl_t::PatchJump(RangeAddress_t at_addr, RangeAddress_t to_addr)
{
assert(patcher);
patcher->PatchJump(at_addr,to_addr);
}