/***************************************************************************
 * 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 <string>
#include <cstring>
#include <algorithm>
#include "unpin.h"
#include <memory>
#include <inttypes.h>


using namespace IRDB_SDK;
using namespace std;
using namespace Zipr_SDK;


#define ALLOF(a) begin(a),end(a)

bool Unpin_t::should_cfi_pin(Instruction_t* insn)
{
	// add command line option that:
	// 	1) return false if !has_cfi_reloc(insn)
	// 	2) return true if option is on.
	return *m_should_cfi_pin;
}

// CAN BE DELETED, left in just for stats? (Would speed up zipr step to delete)
void Unpin_t::DoUnpin()
{
	DoUnpinForScoops();
	DoUnpinForFixedCalls();
}

// CAN BE DELETED, left in just for stats?
// scan instructions and process instruction relocs that can be unpinned.
void Unpin_t::DoUnpinForFixedCalls()
{
	if((int64_t)*m_max_unpins != (int64_t)-1 && (int64_t)unpins>=(int64_t)*m_max_unpins)
		return;
	auto insn_unpins=0;
	auto missed_unpins=0;

	for(auto from_insn : zo->getFileIR()->getInstructions())
	{
		for(auto reloc : from_insn->getRelocations())
		{
			// this probably won't work on shared objects.
			// complicated with the push64-reloc plugin also rewriting these things?
			if(reloc->getType()==string("32-bit") || reloc->getType()==string("push64"))
			{
				// skip if there's no WRT, that means it's unpinned for something besides a fixed call.
				if(reloc->getWRT()==NULL)
					continue;

				// getWRT returns an BaseObj, but this reloc type expects an instruction
				// safe cast and check.
				auto wrt_insn=dynamic_cast<Instruction_t*>(reloc->getWRT());
				assert(wrt_insn);
		
				unpins++;
				insn_unpins++;
				if((int64_t)*m_max_unpins != (int64_t)-1 && (int64_t)unpins>=(int64_t)*m_max_unpins)
					return;
			}
		}
	}

	cout<<"# ATTRIBUTE Zipr_Unpinning::insn_unpin_total_unpins="<<dec<<insn_unpins<<endl;
	cout<<"# ATTRIBUTE Zipr_Unpinning::insn_unpin_missed_unpins="<<dec<<missed_unpins<<endl;
}

// CAN BE DELETED, left in just for stats?
void Unpin_t::DoUnpinForScoops()
{
	if((int64_t)*m_max_unpins != (int64_t)-1 && (int64_t)unpins>=(int64_t)*m_max_unpins)
		return;

	auto missed_unpins=0;
	auto scoop_unpins=0;

	for(auto scoop : zo->getFileIR()->getDataScoops())
	{
		for(auto reloc : scoop->getRelocations())
		{
			if(reloc->getType()==string("data_to_insn_ptr"))
			{
				auto insn=dynamic_cast<Instruction_t*>(reloc->getWRT());
				// getWRT returns an BaseObj, but this reloc type expects an instruction
				// safe cast and check.
				assert(insn);

				unpins++;
				scoop_unpins++;
				if((int64_t)*m_max_unpins != (int64_t)-1 && (int64_t)unpins>=(int64_t)*m_max_unpins)
					return;
			}
		}
	}

	cout<<"# ATTRIBUTE Zipr_Unpinning::scoop_unpin_total_unpins="<<dec<<scoop_unpins<<endl;
	cout<<"# ATTRIBUTE Zipr_Unpinning::scoop_unpin_missed_unpins="<<dec<<missed_unpins<<endl;
}

Zipr_SDK::ZiprPreference Unpin_t::retargetCallback(
	const RangeAddress_t &callback_address,
	const DollopEntry_t *callback_entry,
	RangeAddress_t &target_address)
{
	if(!*m_on) return Zipr_SDK::ZiprPluginInterface_t::retargetCallback(callback_address, callback_entry, target_address);

	unpins++;// unpinning a call to a scoop.
	if((int64_t)*m_max_unpins != (int64_t)-1 && (int64_t)unpins>=(int64_t)*m_max_unpins)
		return Zipr_SDK::ZiprPluginInterface_t::retargetCallback(callback_address, callback_entry, target_address);


	auto  insn = callback_entry->getInstruction();
	for(auto reloc : insn->getRelocations())
	{
		if (reloc->getType()==string("callback_to_scoop"))
		{
			auto wrt = dynamic_cast<DataScoop_t*>(reloc->getWRT());
			auto addend = reloc->getAddend();

			target_address = wrt->getStart()->getVirtualOffset() + addend;
		
			if (*m_verbose) {
				cout << "Unpin::callback_to_scoop: target_addr "
				     << std::hex << target_address << endl;
			}
		}
	}
	return Must;
}

void Unpin_t::DoUpdate()
{
	DoUpdateForScoops();
	DoUpdateForInstructions();
}


// scan for instructions that were placed, and now need an update.
void Unpin_t::DoUpdateForInstructions()
{
	for(auto from_insn : zo->getFileIR()->getInstructions())
	{
		for(auto reloc : from_insn->getRelocations())
		{
			// this probably won't work on shared objects.
			// complicated with the push64-reloc plugin also rewriting these things?
			if(reloc->getType()==string("32-bit") || reloc->getType()==string("push64"))
				HandleRetAddrReloc(from_insn,reloc);

			// instruction has a pcrel memory operand.
			else if(reloc->getType()==string("pcrel")) //  && reloc->getWRT()!=NULL)
				HandlePcrelReloc(from_insn,reloc);

			// instruction has a absolute  memory operand that needs it's displacement updated.
			else if(reloc->getType()==string("absoluteptr_to_scoop"))
				HandleAbsptrReloc(from_insn,reloc);

			// instruction has an immediate that needs an update.
			else if(reloc->getType()==string("immedptr_to_scoop"))
				HandleImmedptrReloc(from_insn,reloc);

			// deal with a callback, think this isn't used anymore
			else if(reloc->getType()==string("callback_to_scoop"))
				HandleCallbackReloc(from_insn,reloc);
		}
	}
}

void Unpin_t::DoUpdateForScoops()
{
	auto byte_width=zo->getFileIR()->getArchitectureBitWidth()/8;
	for(auto scoop : zo->getFileIR()->getDataScoops())
	{
		if(scoop->isExecuteable()) continue;
		assert(scoop->getEnd()->getVirtualOffset() - scoop->getStart()->getVirtualOffset()+1 == scoop->getSize());
		assert(scoop->getContents().size() == scoop->getSize());
		auto scoop_contents=scoop->getContents();

		for(auto reloc : scoop->getRelocations())
		{
			if(reloc->getType()==string("data_to_insn_ptr"))
			{
				VirtualOffset_t reloff=reloc->getOffset();
				Instruction_t* insn=dynamic_cast<Instruction_t*>(reloc->getWRT());
				// getWRT returns an BaseObj, but this reloc type expects an instruction
				// safe cast and check.
				assert(insn);
				Zipr_SDK::InstructionLocationMap_t &locMap=*(zo->getLocationMap());
				IRDB_SDK::VirtualOffset_t newLoc=locMap[insn];

				cout<<"Unpin::Unpinned data_to_insn_ptr insn ("<<hex<<insn->getBaseID()<<":"
				    <<insn->getDisassembly()<<") with offset="<<hex<<reloc->getOffset()
				    <<".  Insn moved to "<<hex<<newLoc<<endl;

				bool found=should_cfi_pin(insn);

				/* don't unpin if we found one */
				if(found)
				{
					cout<<"Unpin::Skipping update because CFI is requesting a nonce."<<endl;
				}
				else
				{
					// determine how big the ptr is.
					const int ptrsize=zo->getFileIR()->getArchitectureBitWidth()/8;
					char addr[ptrsize];
					memset(addr,0,ptrsize);
		
					// convert it to bytes.
					switch(ptrsize)
					{
						case 4:
						{
							const auto newVal=(int)newLoc;
							memcpy(addr,&newVal,ptrsize);
							break;
						}
						case 8:
						{
							const auto newVal=(long long)newLoc;
							memcpy(addr,&newVal,ptrsize);
							break;
						}
						default:
							assert(0);
					}
					// copy in new ptr.
					for(int i=0;i<ptrsize;i++)
						scoop_contents[reloff+i]=addr[i];
				}
				
			}
			else if(reloc->getType()==string("dataptr_to_scoop"))
			{
				DataScoop_t *wrt=dynamic_cast<DataScoop_t*>(reloc->getWRT());
				assert(wrt);

				VirtualOffset_t val_to_patch=0;
                		const char* data=scoop_contents.c_str();

				if(byte_width==4)
				{
					auto newVal=(int)0;
					memcpy(&newVal, &data[reloc->getOffset()], byte_width);
					val_to_patch=newVal;
				}
				else if(byte_width==8)
				{
					auto newVal=(long long)0;
					memcpy(&newVal, &data[reloc->getOffset()], byte_width);
					val_to_patch=newVal;
				}
				else
					assert(0);

				// wrt scoop should be placed.
				assert(wrt->getStart()->getVirtualOffset() !=0 );
				VirtualOffset_t new_val_to_patch=val_to_patch + wrt->getStart()->getVirtualOffset();

                                if(byte_width==4)
                                {
                                        unsigned int intnewval=(unsigned int)new_val_to_patch;     // 64->32 narrowing OK. 
                                        scoop_contents.replace(reloc->getOffset(), byte_width, (char*)&intnewval, byte_width);
                                }
                                else if(byte_width==8)
                                {
                                        scoop_contents.replace(reloc->getOffset(), byte_width, (char*)&new_val_to_patch, byte_width);
                                }
                                else
                                        assert(0);

				cout<<"Patched "<<scoop->getName()<<"+"<<hex<<reloc->getOffset()<<" to value "<<hex<<new_val_to_patch<<endl;
			}
		}
		scoop->setContents(scoop_contents);
	}
}


extern "C" 
Zipr_SDK::ZiprPluginInterface_t* GetPluginInterface(
	Zipr_SDK::Zipr_t* zipr_object)
{
	const auto mt=zipr_object->getFileIR()->getArchitecture()->getMachineType();

	return 
		mt==admtX86_64  ? (Unpin_t*)new UnpinX86_t    (zipr_object) :
		mt==admtI386    ? (Unpin_t*)new UnpinX86_t    (zipr_object) :
		mt==admtAarch64 ? (Unpin_t*)new UnpinAarch64_t(zipr_object) :
		mt==admtArm32   ? (Unpin_t*)new UnpinArm32_t  (zipr_object) :
		mt==admtMips32  ? (Unpin_t*)new UnpinMips32_t (zipr_object) :
		throw invalid_argument("Cannot determine machine type");
}