/*************************************************************************** * 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); }