/*************************************************************************/
/* Copyright (c) 2015 dx, http://kaimi.ru                                */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person           */
/* obtaining a copy of this software and associated documentation        */
/* files (the "Software"), to deal in the Software without               */
/* restriction, including without limitation the rights to use,          */
/* copy, modify, merge, publish, distribute, sublicense, and/or          */
/* sell copies of the Software, and to permit persons to whom the        */
/* Software is furnished to do so, subject to the following conditions:  */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#include "pe_exception_directory.h"

namespace pe_bliss
{
using namespace pe_win;

//EXCEPTION DIRECTORY (exists on PE+ only)
//Default constructor
exception_entry::exception_entry()
	:begin_address_(0), end_address_(0), unwind_info_address_(0),
	unwind_info_version_(0),
	flags_(0),
	size_of_prolog_(0),
	count_of_codes_(0),
	frame_register_(0),
	frame_offset_(0)
{}

//Constructor from data
exception_entry::exception_entry(const image_runtime_function_entry& entry, const unwind_info& unwind_info)
	:begin_address_(entry.BeginAddress), end_address_(entry.EndAddress), unwind_info_address_(entry.UnwindInfoAddress),
	unwind_info_version_(unwind_info.Version),
	flags_(unwind_info.Flags),
	size_of_prolog_(unwind_info.SizeOfProlog),
	count_of_codes_(unwind_info.CountOfCodes),
	frame_register_(unwind_info.FrameRegister),
	frame_offset_(unwind_info.FrameOffset)
{}

//Returns starting address of function, affected by exception unwinding
uint32_t exception_entry::get_begin_address() const
{
	return begin_address_;
}

//Returns ending address of function, affected by exception unwinding
uint32_t exception_entry::get_end_address() const
{
	return end_address_;
}

//Returns unwind info address
uint32_t exception_entry::get_unwind_info_address() const
{
	return unwind_info_address_;
}

//Returns UNWIND_INFO structure version
uint8_t exception_entry::get_unwind_info_version() const
{
	return unwind_info_version_;
}

//Returns unwind info flags
uint8_t exception_entry::get_flags() const
{
	return flags_;
}

//The function has an exception handler that should be called
//when looking for functions that need to examine exceptions
bool exception_entry::has_exception_handler() const
{
	return (flags_ & unw_flag_ehandler) ? true : false;
}

//The function has a termination handler that should be called
//when unwinding an exception
bool exception_entry::has_termination_handler() const
{
	return (flags_ & unw_flag_uhandler) ? true : false;
}

//The unwind info structure is not the primary one for the procedure
bool exception_entry::is_chaininfo() const
{
	return (flags_ & unw_flag_chaininfo) ? true : false;
}

//Returns size of function prolog
uint8_t exception_entry::get_size_of_prolog() const
{
	return size_of_prolog_;
}

//Returns number of unwind slots
uint8_t exception_entry::get_number_of_unwind_slots() const
{
	return count_of_codes_;
}

//If the function uses frame pointer
bool exception_entry::uses_frame_pointer() const
{
	return frame_register_ != 0;
}

//Number of the nonvolatile register used as the frame pointer,
//using the same encoding for the operation info field of UNWIND_CODE nodes
uint8_t exception_entry::get_frame_pointer_register_number() const
{
	return frame_register_;
}

//The scaled offset from RSP that is applied to the FP reg when it is established.
//The actual FP reg is set to RSP + 16 * this number, allowing offsets from 0 to 240
uint8_t exception_entry::get_scaled_rsp_offset() const
{
	return frame_offset_;
}

//Returns exception directory data (exists on PE+ only)
//Unwind opcodes are not listed, because their format and list are subject to change
const exception_entry_list get_exception_directory_data(const pe_base& pe)
{
	exception_entry_list ret;

	//If image doesn't have exception directory, return empty list
	if(!pe.has_exception_directory())
		return ret;

	//Check the length in bytes of the section containing exception directory
	if(pe.section_data_length_from_rva(pe.get_directory_rva(image_directory_entry_exception), pe.get_directory_rva(image_directory_entry_exception), section_data_virtual, true)
		< sizeof(image_runtime_function_entry))
		throw pe_exception("Incorrect exception directory", pe_exception::incorrect_exception_directory);

	unsigned long current_pos = pe.get_directory_rva(image_directory_entry_exception);

	//Check if structures are DWORD-aligned
	if(current_pos % sizeof(uint32_t))
		throw pe_exception("Incorrect exception directory", pe_exception::incorrect_exception_directory);

	//First IMAGE_RUNTIME_FUNCTION_ENTRY table
	image_runtime_function_entry exception_table = pe.section_data_from_rva<image_runtime_function_entry>(current_pos, section_data_virtual, true);

	//todo: virtual addresses BeginAddress and EndAddress are not checked to be inside image
	while(exception_table.BeginAddress)
	{
		//Check addresses
		if(exception_table.BeginAddress > exception_table.EndAddress)
			throw pe_exception("Incorrect exception directory", pe_exception::incorrect_exception_directory);

		//Get unwind information
		unwind_info info = pe.section_data_from_rva<unwind_info>(exception_table.UnwindInfoAddress, section_data_virtual, true);

		//Create exception entry and save it
		ret.push_back(exception_entry(exception_table, info));

		//Go to next exception entry
		current_pos += sizeof(image_runtime_function_entry);
		exception_table = pe.section_data_from_rva<image_runtime_function_entry>(current_pos, section_data_virtual, true);
	}

	return ret;
}
}
