From 92b90165ef647e7342d42d62dac9ef84b48ff923 Mon Sep 17 00:00:00 2001 From: whh8b <whh8b@git.zephyr-software.com> Date: Thu, 2 Oct 2014 15:30:58 +0000 Subject: [PATCH] Handle the case where chained two byte jumps are necessary --- include/range.h | 10 + include/unresolved.h | 37 +++- include/zipr.h | 4 + src/zipr.cpp | 473 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 471 insertions(+), 53 deletions(-) diff --git a/include/range.h b/include/range.h index 4bb6fb3..e0ec3b7 100644 --- a/include/range.h +++ b/include/range.h @@ -38,10 +38,20 @@ class Range_t { public: Range_t(RangeAddress_t p_s, RangeAddress_t p_e) : m_start(p_s), m_end(p_e) { } + Range_t() : m_start(0), m_end(0) { } RangeAddress_t GetStart() { return m_start; } RangeAddress_t GetEnd() { return m_end; } + bool Is2ByteRange() + { + return (m_end - m_start) == 2; + }; + bool Is5ByteRange() + { + return (m_end - m_start) == 5; + }; + protected: RangeAddress_t m_start, m_end; diff --git a/include/unresolved.h b/include/unresolved.h index fa58014..49c5d62 100644 --- a/include/unresolved.h +++ b/include/unresolved.h @@ -71,10 +71,45 @@ inline bool operator< (const UnresolvedUnpinned_t& lhs, const UnresolvedUnpinned class UnresolvedPinned_t : public UnresolvedInfo_t { public: - UnresolvedPinned_t(libIRDB::Instruction_t* p_from) : from_instruction(p_from) {} + UnresolvedPinned_t(libIRDB::Instruction_t* p_from) : from_instruction(p_from), m_range(0,0), m_updated_address(0) {} + UnresolvedPinned_t(libIRDB::Instruction_t* p_from, Range_t range) : from_instruction(p_from), m_range(range) {} libIRDB::Instruction_t* GetInstruction() const { return from_instruction; } + + /* + * Use the range to store the place where + * reserved space is held for this + * instruction. + */ + Range_t GetRange() const { return m_range; }; + void SetRange(Range_t range) { m_range = range; }; + bool HasRange() + { + return m_range.GetStart() != 0 || m_range.GetEnd() != 0; + }; + + /* + * Store an address with the UnresolvedPinned + * in case this instruction needs to float + * through the program based on a chain + * of two-byte calls. + */ + bool HasUpdatedAddress() + { + return m_updated_address != 0; + }; + void SetUpdatedAddress(RangeAddress_t address) + { + m_updated_address = address; + }; + RangeAddress_t GetUpdatedAddress() + { + return m_updated_address; + }; + private: libIRDB::Instruction_t* from_instruction; + Range_t m_range; + RangeAddress_t m_updated_address; friend bool operator< (const UnresolvedPinned_t& lhs, const UnresolvedPinned_t& rhs); diff --git a/include/zipr.h b/include/zipr.h index 52cf61c..fed0657 100644 --- a/include/zipr.h +++ b/include/zipr.h @@ -65,6 +65,7 @@ class Zipr_t void AddPinnedInstructions(); void ResolvePinnedInstructions(); void ReservePinnedInstructions(); + void PreReserve2ByteJumpTargets(); void ExpandPinnedInstructions(); void Fix2BytePinnedInstructions(); void OptimizePinnedInstructions(); @@ -74,6 +75,7 @@ class Zipr_t // range operatations void SplitFreeRange(RangeAddress_t addr); + void MergeFreeRange(RangeAddress_t addr); std::list<Range_t>::iterator FindFreeRange(RangeAddress_t addr); Range_t GetFreeRange(int size); @@ -107,6 +109,8 @@ class Zipr_t // helpers. void ProcessUnpinnedInstruction(const UnresolvedUnpinned_t &uu, const Patch_t &p); void InsertNewSegmentIntoExe(std::string old_file, std::string new_file, RangeAddress_t sec_start); + libIRDB::Instruction_t *FindPinnedInsnAtAddr(RangeAddress_t addr); + bool ShouldPinImmediately(libIRDB::Instruction_t *upinsn); private: diff --git a/src/zipr.cpp b/src/zipr.cpp index 34d097d..363a29b 100644 --- a/src/zipr.cpp +++ b/src/zipr.cpp @@ -65,11 +65,30 @@ void Zipr_t::CreateBinaryFile(const std::string &name) // reserve space for pins ReservePinnedInstructions(); + PreReserve2ByteJumpTargets(); + // expand 2-byte pins into 4-byte pins ExpandPinnedInstructions(); - // Allocate space near 2-byte pins for a 5-byte pin - Fix2BytePinnedInstructions(); + while (!two_byte_pins.empty()) + { + /* + * Put down the five byte targets + * for two byte jumps, if any exist. + */ + printf("Going to Fix2BytePinnedInstructions.\n"); + Fix2BytePinnedInstructions(); + + /* + * If there are still two byte pins, + * try the dance again. + */ + if (!two_byte_pins.empty()) + { + printf("Going to Re PreReserve2ByteJumpTargets.\n"); + PreReserve2ByteJumpTargets(); + } + } // Convert all 5-byte pins into full fragments OptimizePinnedInstructions(); @@ -97,16 +116,55 @@ void Zipr_t::FindFreeRanges(const std::string &name) RangeAddress_t last_end=0; RangeAddress_t max_addr=0; - // For all sections + std::map<RangeAddress_t, int> ordered_sections; + + /* + * Make an ordered list of the sections + * by their starting address. + */ Elf_Half n = elfiop->sections.size(); for ( Elf_Half i = 0; i < n; ++i ) { section* sec = elfiop->sections[i]; assert(sec); + ordered_sections.insert(std::pair<RangeAddress_t,int>(sec->get_address(), i)); + } + + std::map<RangeAddress_t, int>::iterator it = ordered_sections.begin(); + for (;it!=ordered_sections.end();) + { + section* sec = elfiop->sections[it->second]; + assert(sec); RangeAddress_t start=sec->get_address(); RangeAddress_t end=sec->get_size()+start-1; + printf("Section %s:\n", sec->get_name().c_str()); + + ++it; + if (false) + //if ((++it) != ordered_sections.end()) + { + /* + * TODO: This works. However, the updated + * section size is not properly handled + * in OutputBinaryFile. So, it is disabled + * until that is handled. + */ + section *next_section = elfiop->sections[it->second]; + + printf("Using %s as the next section (%p).\n", + next_section->get_name().c_str(), + (void*)next_section->get_address()); + printf("Modifying the section end. Was %p.", (void*)end); + + end = next_section->get_address() - 1; + sec->set_size(end - start); + + printf(". Is %p.\n", (void*)end); + + } + printf("max_addr is %p, end is %p\n", (void*)max_addr, (void*)end); if(start && end>max_addr) { @@ -186,6 +244,100 @@ list<Range_t>::iterator Zipr_t::FindFreeRange(RangeAddress_t addr) return free_ranges.end(); } +void Zipr_t::MergeFreeRange(RangeAddress_t addr) +{ + /* + * Make a new range of one byte. + * + * Then, look to see whether or not it + * can be merged with another range. + * + * If not, add it as a one byte range. + */ + + Range_t nr(addr, addr); + bool merged = false; + list<Range_t>::iterator it=free_ranges.begin(); + for(;it!=free_ranges.end();++it) + { + Range_t r=*it; + if ((addr+1) == r.GetStart()) { + /* + * Make the beginning of this range + * one byte smaller! + */ + Range_t nnr(addr, r.GetEnd()); + printf("Expanded range:\n"); + printf("from: %p to %p\n", (void*)r.GetStart(), (void*)r.GetEnd()); + printf("to: %p to %p\n", (void*)nnr.GetStart(), (void*)nnr.GetEnd()); + free_ranges.insert(it, nnr); + free_ranges.erase(it); + nr = nnr; + merged = true; + break; + } else if ((addr-1) == r.GetEnd()) { + /* + * Make the end of this range one byte + * bigger + */ + Range_t nnr(r.GetStart(), addr); + printf("Expanded range:\n"); + printf("from: %p to %p\n", (void*)r.GetStart(), (void*)r.GetEnd()); + printf("to: %p to %p\n", (void*)nnr.GetStart(), (void*)nnr.GetEnd()); + free_ranges.insert(it, nnr); + free_ranges.erase(it); + nr = nnr; + merged = true; + break; + } + } + + if (!merged) + free_ranges.insert(it, nr); + + /* + * Correctness: + * Take a pass through and see if there are + * free ranges that can now be merged. This + * is important because it's possible that + * we added the byte to the end of a range + * where it could also have gone at the + * beginning of another. + */ + for(it=free_ranges.begin();it!=free_ranges.end();++it) + { + Range_t r = *it; + if (( + /* + * <--r--> + * <--nr--> + */ + (r.GetEnd() >= nr.GetStart() && + r.GetEnd() <= nr.GetEnd()) || + /* + * <--r--> + * <--nr--> + */ + (r.GetStart() <= nr.GetEnd() && + r.GetEnd() >= nr.GetEnd()) + ) && + (r.GetStart() != nr.GetStart() || + r.GetEnd() != nr.GetEnd())) + { + /* + * merge. + */ + Range_t merged_range(std::min(r.GetStart(), nr.GetStart()), std::max(r.GetEnd(), nr.GetEnd())); + printf("Merged two ranges:\n"); + printf("1: %p to %p\n", (void*)r.GetStart(), (void*)r.GetEnd()); + printf("2: %p to %p\n", (void*)nr.GetStart(), (void*)nr.GetEnd()); + free_ranges.insert(it, merged_range); + free_ranges.erase(it); + return; + } + } +} + void Zipr_t::SplitFreeRange(RangeAddress_t addr) { list<Range_t>::iterator it=FindFreeRange(addr); @@ -216,11 +368,36 @@ void Zipr_t::SplitFreeRange(RangeAddress_t addr) } } +Instruction_t *Zipr_t::FindPinnedInsnAtAddr(RangeAddress_t addr) +{ + for( + set<Instruction_t*>::const_iterator it=m_firp->GetInstructions().begin(); + it!=m_firp->GetInstructions().end(); + ++it + ) + { + RangeAddress_t ibta_addr; + Instruction_t* insn=*it; + assert(insn); -static bool should_pin_immediately(Instruction_t *upinsn) + if(!insn->GetIndirectBranchTargetAddress()) { + continue; + } + ibta_addr=(unsigned)insn-> + GetIndirectBranchTargetAddress()-> + GetVirtualOffset(); + + if (addr == ibta_addr) + return insn; + } + return NULL; +} + +bool Zipr_t::ShouldPinImmediately(Instruction_t *upinsn) { DISASM d; upinsn->Disassemble(d); + Instruction_t *pin_at_next_byte = NULL; if(d.Instruction.BranchType==RetType) return true; @@ -238,9 +415,124 @@ static bool should_pin_immediately(Instruction_t *upinsn) return true; } + /* + * lock cmpxchange op1 op2 [pinned at x] + * x x+1 x+2 x+3 + * + * pin at x and pin at x+1 + * + * x should become nop, put down immediately + * x+1 should become the entire lock command. + */ + if ((pin_at_next_byte = + FindPinnedInsnAtAddr(upinsn_ibta->GetVirtualOffset() + 1)) != NULL) + { + printf("Using pin_at_next_byte special case.\n"); + /* + * Because upinsn is longer than + * 1 byte, we must be somehow + * pinned into ourselves. Fix! + */ + + /* + * Make pin_at_next_byte look like upinsn. + */ + pin_at_next_byte->SetDataBits(upinsn->GetDataBits()); + pin_at_next_byte->SetComment(upinsn->GetComment()); + pin_at_next_byte->SetCallback(upinsn->GetCallback()); + pin_at_next_byte->SetFallthrough(upinsn->GetFallthrough()); + pin_at_next_byte->SetTarget(upinsn->GetTarget()); + /* + * Convert upins to nop. + */ + string dataBits = upinsn->GetDataBits(); + dataBits.resize(1); + dataBits[0] = 0x90; + upinsn->SetDataBits(dataBits); + + return true; + } return false; } +void Zipr_t::PreReserve2ByteJumpTargets() +{ + for(set<UnresolvedPinned_t>::const_iterator it=two_byte_pins.begin(); + it!=two_byte_pins.end(); + ) + { + UnresolvedPinned_t up=*it; + bool found_close_target = false; + Instruction_t* upinsn=up.GetInstruction(); + + RangeAddress_t addr; + + if (up.HasUpdatedAddress()) + { + addr = up.GetUpdatedAddress(); + } + else + { + addr=upinsn->GetIndirectBranchTargetAddress()->GetVirtualOffset(); + } + + /* + * Check for near branch instructions + * by starting far away! + * Note: two byte jump range is 127 bytes, + * but that's from the pc after it's been + * inc, etc. complicated goo. 120 is a + * safe estimate of range. + */ + for(int size=5;size>0;size-=3) + { + printf("Looking for %d-byte jump targets to pre-reserve.\n", size); + for(int i=120;i>=-120;i--) + { + if(AreBytesFree(addr+i,size)) + { + printf("Found location for 2-byte->%d-byte conversion " + "(%p-%p)->(%p-%p) (orig: %p)\n", + size, + (void*)addr, + (void*)(addr+1), + (void*)(addr+i), + (void*)(addr+i+size), + (void*)upinsn->GetIndirectBranchTargetAddress()->GetVirtualOffset()); + + up.SetRange(Range_t(addr+i, addr+i+size)); + for (int j = up.GetRange().GetStart(); j<up.GetRange().GetEnd(); j++) + { + SplitFreeRange(j); + } + found_close_target = true; + break; + } + } + if (found_close_target) + break; + } + + if (!found_close_target) + { + printf("FATAL: No location for near jump reserved.\n"); + assert(false); + ++it; + } + else + { + UnresolvedPinned_t new_up = UnresolvedPinned_t(up.GetInstruction(), up.GetRange()); + if (up.HasUpdatedAddress()) + { + new_up.SetUpdatedAddress(up.GetUpdatedAddress()); + } + two_byte_pins.erase(it++); + two_byte_pins.insert(new_up); + + } + } +} + void Zipr_t::ReservePinnedInstructions() { set<UnresolvedPinned_t> reserved_pins; @@ -268,7 +560,7 @@ void Zipr_t::ReservePinnedInstructions() * so, we attempt to pin any 1-byte instructions with no fallthrough (returns are most common) immediately. * we also attempt to pin any 1-byte insn that falls through to the next pinned address (nops are common). */ - if(should_pin_immediately(upinsn)) + if(ShouldPinImmediately(upinsn)) { printf("Final pinning %p-%p. fid=%d\n", (void*)addr, (void*)(addr+upinsn->GetDataBits().size()-1), upinsn->GetAddress()->GetFileID()); @@ -282,9 +574,13 @@ void Zipr_t::ReservePinnedInstructions() } char bytes[]={0xeb,0}; // jmp rel8 - printf("Two-byte Pinning %p-%p. fid=%d\n", (void*)addr, (void*)(addr+sizeof(bytes)-1), - upinsn->GetAddress()->GetFileID()); - + + printf("Two-byte Pinning %p-%p. fid=%d\n", + (void*)addr, + (void*)(addr+sizeof(bytes)-1), + upinsn->GetAddress()->GetFileID()); + printf("%s\n", upinsn->GetComment().c_str()); + two_byte_pins.insert(up); for(int i=0;i<sizeof(bytes);i++) { @@ -293,30 +589,37 @@ void Zipr_t::ReservePinnedInstructions() SplitFreeRange(addr+i); } } - } - void Zipr_t::ExpandPinnedInstructions() { /* now, all insns have 2-byte pins. See which ones we can make 5-byte pins */ - for( - set<UnresolvedPinned_t>::const_iterator it=two_byte_pins.begin(); - it!=two_byte_pins.end(); - ) + for( + set<UnresolvedPinned_t>::iterator it=two_byte_pins.begin(); + it!=two_byte_pins.end(); + ) { UnresolvedPinned_t up=*it; Instruction_t* upinsn=up.GetInstruction(); RangeAddress_t addr=upinsn->GetIndirectBranchTargetAddress()->GetVirtualOffset(); - char bytes[]={0xe9,0,0,0,0}; // jmp rel8 bool can_update=AreBytesFree(addr+2,sizeof(bytes)-2); if(can_update) { printf("Found %p can be updated to 5-byte jmp\n", (void*)addr); PlopJump(addr); + + /* + * Unreserve those bytes that we reserved before! + */ + for (int j = up.GetRange().GetStart(); j<up.GetRange().GetEnd(); j++) + { + MergeFreeRange(j); + } + up.SetRange(Range_t(0,0)); + five_byte_pins[up]=addr; two_byte_pins.erase(it++); total_5byte_pins++; @@ -324,7 +627,7 @@ void Zipr_t::ExpandPinnedInstructions() } else { - ++it; + ++it; printf("Found %p can NOT be updated to 5-byte jmp\n", (void*)addr); total_2byte_pins++; total_trampolines++; @@ -338,46 +641,92 @@ void Zipr_t::ExpandPinnedInstructions() void Zipr_t::Fix2BytePinnedInstructions() { - for( + for( set<UnresolvedPinned_t>::const_iterator it=two_byte_pins.begin(); - it!=two_byte_pins.end(); - ) + it!=two_byte_pins.end(); + ) { UnresolvedPinned_t up=*it; Instruction_t* upinsn=up.GetInstruction(); - RangeAddress_t addr=upinsn->GetIndirectBranchTargetAddress()->GetVirtualOffset(); + RangeAddress_t addr; + + if (up.HasUpdatedAddress()) + { + addr = up.GetUpdatedAddress(); + } + else + { + addr=upinsn->GetIndirectBranchTargetAddress()->GetVirtualOffset(); + } - // check for near branch instructions - for(int i=0;i<120;i++) // two byte jump range is 127 bytes, but that's from the pc after it's been inc, etc. complicated goo. 120 is a safe estimate of range. + if (up.HasRange()) { - if(AreBytesFree(addr-i,5)) + /* + * Always clear out the previously reserved space. + */ + for (int j = up.GetRange().GetStart(); j<up.GetRange().GetEnd(); j++) { - printf("Found location for 2-byte->5-byte conversion (%p-%p)->(%p-%p)\n", - (void*)addr,(void*)(addr+1), (void*)(addr-i),(void*)(addr-i+4)); - - five_byte_pins[up]=addr-i; - PlopJump(addr-i); - PatchJump(addr, addr-i); - break; + MergeFreeRange(j); } - else if(AreBytesFree(addr+i,5)) - { - printf("Found location for 2-byte->5-byte conversion (%p-%p)->(%p-%p)\n", - (void*)addr,(void*)(addr+1), (void*)(addr+i),(void*)(addr+i+5)); - five_byte_pins[up]=addr+i; - PlopJump(addr+i); - PatchJump(addr, addr+i); - break; + if (up.GetRange().Is5ByteRange()) + { + printf("Using previously reserved spot of 2-byte->5-byte conversion " + "(%p-%p)->(%p-%p) (orig: %p)\n", + (void*)addr, + (void*)(addr+1), + (void*)(up.GetRange().GetStart()), + (void*)(up.GetRange().GetEnd()), + (void*)upinsn->GetIndirectBranchTargetAddress()->GetVirtualOffset()); + + five_byte_pins[up] = up.GetRange().GetStart(); + PlopJump(up.GetRange().GetStart()); + PatchJump(addr, up.GetRange().GetStart()); + + two_byte_pins.erase(it++); } - else + else if (up.HasRange() && up.GetRange().Is2ByteRange()) { -// printf("Not free at %p or %p\n", (void*)(addr-i),(void*)(addr+i)); + /* + * Add jump to the reserved space. + * Make an updated up that has a new + * "addr" so that addr is handled + * correctly the next time through. + * + * Ie tell two_byte_pins list that + * the instruction is now at the jump + * target location. + */ + UnresolvedPinned_t new_up = + UnresolvedPinned_t(up.GetInstruction()); + new_up.SetUpdatedAddress(up.GetRange().GetStart()); + + char bytes[]={0xeb,0}; // jmp rel8 + for(int i=0;i<sizeof(bytes);i++) + { + assert(byte_map.find(up.GetRange().GetStart()+i) == byte_map.end() ); + byte_map[up.GetRange().GetStart()+i]=bytes[i]; + SplitFreeRange(up.GetRange().GetStart()+i); + assert(!IsByteFree(up.GetRange().GetStart()+i)); + } + + printf("Patching 2 byte to 2 byte: %p to %p (orig: %p)\n", + (void*)addr, + (void*)up.GetRange().GetStart(), + (void*)upinsn->GetIndirectBranchTargetAddress()->GetVirtualOffset()); + + PatchJump(addr, up.GetRange().GetStart()); + two_byte_pins.erase(it++); + two_byte_pins.insert(new_up); } } - two_byte_pins.erase(it++); + else + { + printf("FATAL: Two byte pin without reserved range: %p\n", (void*)addr); + assert(false); + it++; + } } - } @@ -579,19 +928,41 @@ void Zipr_t::ProcessUnpinnedInstruction(const UnresolvedUnpinned_t &uu, const Pa DISASM d; cur_insn->Disassemble(d); int id=cur_insn->GetBaseID(); - printf("Emitting %d:%s at %p until ", id, d.CompleteInstr, (void*)cur_addr); - cur_addr=PlopInstruction(cur_insn,cur_addr); - printf("%p\n", (void*)cur_addr); - cur_insn=cur_insn->GetFallthrough(); - insn_count++; + RangeAddress_t to_addr; + /* + * Check to see if id is already plopped somewhere. + * If so, emit a jump to it and break. + * TODO: Test and enable. + */ + if (false) + //if ((to_addr=final_insn_locations[to_insn]) != 0) + { + printf("Fallthrough loop detected. " + "Emitting jump from %p to %p.\n", + (void*)cur_addr, + (void*)to_addr); + PlopJump(cur_addr); + PatchJump(cur_addr, to_addr); + cur_insn = NULL; + cur_addr+=5; + break; + } + else + { + printf("Emitting %d:%s at %p until ", id, d.CompleteInstr, (void*)cur_addr); + cur_addr=PlopInstruction(cur_insn,cur_addr); + printf("%p\n", (void*)cur_addr); + cur_insn=cur_insn->GetFallthrough(); + insn_count++; + } } if(cur_insn) { // Mark this insn as needing a patch since we couldn't completely empty // the 'fragment' we are translating into the elf section. - UnresolvedUnpinned_t uu(cur_insn); - Patch_t thepatch(cur_addr,UncondJump_rel32); - patch_list.insert(pair<UnresolvedUnpinned_t,Patch_t>(uu,thepatch)); + UnresolvedUnpinned_t uu(cur_insn); + Patch_t thepatch(cur_addr,UncondJump_rel32); + patch_list.insert(pair<UnresolvedUnpinned_t,Patch_t>(uu,thepatch)); PlopJump(cur_addr); truncated="truncated due to lack of space."; total_tramp_space+=5; @@ -947,8 +1318,6 @@ void Zipr_t::OutputBinaryFile(const string &name) { b=byte_map[i]; } - - if(i-start_of_new_space<200)// keep verbose output short enough. printf("Writing byte %#2x at %p, fileoffset=%lld\n", ((unsigned)b)&0xff, (void*)i, (long long)(i-start_of_new_space)); -- GitLab