//------------------------------------------------------------------------------
//	Copyright (c) 2003, Ingo Weinhold
//
//	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.
//
//	File Name:		ElfSymbolPatcher.cpp
//	Author:			Ingo Weinhold (bonefish@users.sf.net)
//	Description:	Interface declaration of classes used for patching ELF
//					symbols. Central class is ElfSymbolPatcher. It is a kind of
//					roster, managing all necessary infos (loaded images) and
//					being able to fill ElfSymbolPatchInfos with life.
//					An ElfSymbolPatchInfo represents a symbol and is able to
//					patch/restore it. An ElfSymbolPatchGroup bundles several
//					ElfSymbolPatchInfos and can update their data, e.g.
//					when images are loaded/unloaded. It uses a
//					ElfSymbolPatcher internally and provides a more convenient
//					API for the user.
//------------------------------------------------------------------------------

#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ElfImage.h"
#include "ElfSymbolPatcher.h"

/////////////////////////////
// ElfSymbolPatchInfo::Entry
//

class ElfSymbolPatchInfo::Entry {
public:
	static	Entry*				Create(image_id image, void*** targets,
									   int32 targetCount);
			void				Delete();

			image_id			GetImage() const	{ return fImage; }

			void				Patch(void* newAddress);

private:
								Entry();
								Entry(const Entry&);
								Entry(image_id image, void*** targets,
									  int32 targetCount);
								~Entry();

private:
			image_id			fImage;
			int32				fPatchTargetCount;
			void**				fPatchTargets[1];
};

// Create
ElfSymbolPatchInfo::Entry*
ElfSymbolPatchInfo::Entry::Create(image_id image, void*** targets,
								  int32 targetCount)
{
	if (!targets || targetCount <= 0)
		return NULL;
	void* buffer = malloc(sizeof(Entry) + sizeof(void**) * (targetCount - 1));
	Entry* entry = NULL;
	if (buffer)
		entry = new(buffer) Entry(image, targets, targetCount);
	return entry;
}

// Delete
void
ElfSymbolPatchInfo::Entry::Delete()
{
	this->~Entry();
	free(this);
}

// Patch
void
ElfSymbolPatchInfo::Entry::Patch(void* newAddress)
{
//printf("ElfSymbolPatchInfo::Entry::Patch(): patching %ld addresses\n",
//fPatchTargetCount);
	for (int i = 0; i < fPatchTargetCount; i++)
		*fPatchTargets[i] = newAddress;
}

// constructor
ElfSymbolPatchInfo::Entry::Entry(image_id image, void*** targets,
								 int32 targetCount)
	: fImage(image),
	  fPatchTargetCount(targetCount)
{
	memcpy(fPatchTargets + 0, targets, targetCount * sizeof(void**));
}

// destructor
ElfSymbolPatchInfo::Entry::~Entry()
{
}


//////////////////////
// ElfSymbolPatchInfo
//

// constructor
ElfSymbolPatchInfo::ElfSymbolPatchInfo()
	: fSymbolName(),
	  fOriginalAddress(NULL),
	  fOriginalAddressImage(-1),
	  fEntries()
{
}

// destructor
ElfSymbolPatchInfo::~ElfSymbolPatchInfo()
{
	Unset();
}

// InitCheck
status_t
ElfSymbolPatchInfo::InitCheck() const
{
	return (fOriginalAddress && fSymbolName.Length() ? B_OK : B_NO_INIT);
}

// GetSymbolName
const char*
ElfSymbolPatchInfo::GetSymbolName() const
{
	return fSymbolName.String();
}

// GetOriginalAddress
void*
ElfSymbolPatchInfo::GetOriginalAddress() const
{
	return fOriginalAddress;
}

// GetOriginalAddressImage
image_id
ElfSymbolPatchInfo::GetOriginalAddressImage() const
{
	return fOriginalAddressImage;
}

// Patch
status_t
ElfSymbolPatchInfo::Patch(void* newAddress)
{
//printf("ElfSymbolPatchInfo::Patch(): patching %ld images\n",
//fEntries.CountItems());
	status_t error = InitCheck();
	if (error == B_OK) {
		for (int i = 0; Entry* entry = EntryAt(i); i++)
			entry->Patch(newAddress);
	}
	return error;
}

// Restore
status_t
ElfSymbolPatchInfo::Restore()
{
	return Patch(fOriginalAddress);
}

// Unset
void
ElfSymbolPatchInfo::Unset()
{
	for (int i = 0; Entry* entry = EntryAt(i); i++)
		entry->Delete();
	fEntries.MakeEmpty();
	fSymbolName.SetTo("");
	fOriginalAddress = NULL;
	fOriginalAddressImage = -1;
}

// SetSymbolName
status_t
ElfSymbolPatchInfo::SetSymbolName(const char* name)
{
	fSymbolName.SetTo(name);
	if (name && fSymbolName != name)
		return B_NO_MEMORY;
	return B_OK;
}

// SetOriginalAddress
void
ElfSymbolPatchInfo::SetOriginalAddress(void* address, image_id image)
{
	fOriginalAddress = address;
	fOriginalAddressImage = image;
}

// CreateEntry
status_t
ElfSymbolPatchInfo::CreateEntry(image_id image, BList* targets)
{
	if (!targets || targets->CountItems() == 0)
		return B_BAD_VALUE;
	Entry* entry = Entry::Create(image, (void***)targets->Items(),
								 targets->CountItems());
	if (!entry)
		return B_NO_MEMORY;
	if (!fEntries.AddItem(entry)) {
		entry->Delete();
		return B_NO_MEMORY;
	}
	return B_OK;
}

// DeleteEntry
bool
ElfSymbolPatchInfo::DeleteEntry(image_id image)
{
	for (int i = 0; Entry* entry = EntryAt(i); i++) {
		if (entry->GetImage() == image) {
			fEntries.RemoveItem(i);
			entry->Delete();
			return true;
		}
	}
	return false;
}

// EntryAt
ElfSymbolPatchInfo::Entry*
ElfSymbolPatchInfo::EntryAt(int32 index)
{
	return (Entry*)fEntries.ItemAt(index);
}

// EntryFor
ElfSymbolPatchInfo::Entry*
ElfSymbolPatchInfo::EntryFor(image_id image)
{
	for (int i = 0; Entry* entry = EntryAt(i); i++) {
		if (entry->GetImage() == image)
			return entry;
	}
	return NULL;
}


/////////////////
// UpdateAdapter
//

// constructor
ElfSymbolPatcher::UpdateAdapter::UpdateAdapter()
{
}

// destructor
ElfSymbolPatcher::UpdateAdapter::~UpdateAdapter()
{
}

// ImageAdded
void
ElfSymbolPatcher::UpdateAdapter::ImageAdded(ElfImage* image)
{
}

// ImageRemoved
void
ElfSymbolPatcher::UpdateAdapter::ImageRemoved(ElfImage* image)
{
}


////////////////////
// ElfSymbolPatcher
//

// constructor
ElfSymbolPatcher::ElfSymbolPatcher()
	: fImages(),
	  fInitStatus(B_NO_INIT)
{
	fInitStatus = _Init();
	if (fInitStatus != B_OK)
		_Cleanup();
}

// destructor
ElfSymbolPatcher::~ElfSymbolPatcher()
{
	_Cleanup();
}

// InitCheck
status_t
ElfSymbolPatcher::InitCheck() const
{
	return fInitStatus;
}

// Update
status_t
ElfSymbolPatcher::Update(UpdateAdapter* updateAdapter)
{
	if (InitCheck() != B_OK)
		return B_NO_INIT;
	// remove obsolete images
	int32 count = fImages.CountItems();
	for (int i = count - 1; i >= 0; i--) {
		ElfImage* image = _ImageAt(i);
		image_info info;
		if (get_image_info(image->GetID(), &info) != B_OK) {
			if (updateAdapter)
				updateAdapter->ImageRemoved(image);
			fImages.RemoveItem(i);
			delete image;
		}
	}
	// add new images
	status_t error = B_OK;
	image_info info;
	int32 cookie = 0;
	while (get_next_image_info(0, &cookie, &info) == B_OK) {
		ElfImage* image = _ImageForID(info.id);
		if (image)
			continue;
		image = new(nothrow) ElfImage;
		if (!image)
			return B_NO_MEMORY;
		if (!fImages.AddItem(image)) {
			delete image;
			return B_NO_MEMORY;
		}
		error = image->SetTo(info.id);
		if (updateAdapter)
			updateAdapter->ImageAdded(image);
	}
	return error;
}

// Unload
void
ElfSymbolPatcher::Unload()
{
	for (int i = 0; ElfImage* image = _ImageAt(i); i++)
		image->Unload();
}

// GetSymbolPatchInfo
status_t
ElfSymbolPatcher::GetSymbolPatchInfo(const char* symbolName,
									 ElfSymbolPatchInfo* info)
{
	// check parameters and intialization
	if (!symbolName || !info)
		return B_BAD_VALUE;
	if (InitCheck() != B_OK)
		return B_NO_INIT;
	// set the symbol name
	info->Unset();
	status_t error = info->SetSymbolName(symbolName);
	if (error != B_OK)
		return error;
	for (int i = 0; ElfImage* image = _ImageAt(i); i++) {
//printf("searching in image: %ld\n", image->GetID());
		// get the symbol's relocations
		BList patchTargets;
		error = image->GetSymbolRelocations(symbolName, &patchTargets);
		if (error != B_OK)
			break;
		if (patchTargets.CountItems() > 0) {
			error = info->CreateEntry(image->GetID(), &patchTargets);
			if (error != B_OK)
				break;
		}
		// get the symbol's address
		void* address = NULL;
		if (image->FindSymbol(symbolName, &address) == B_OK && address) {
			if (info->GetOriginalAddress()) {
				// A symbol with that name lives in at least two images.
				// Better bail out.
//printf("Found the symbol in more than one image!\n");
				error = B_ERROR;
				break;
			} else
				info->SetOriginalAddress(address, image->GetID());
		}
	}
	// set the symbol address
	if (!info->GetOriginalAddress())
{
//printf("Symbol not found in any image!\n");
		error = B_ERROR;
}
	// cleanup on error
	if (error != B_OK)
		info->Unset();
	return error;
}

// UpdateSymbolPatchInfo
status_t
ElfSymbolPatcher::UpdateSymbolPatchInfo(ElfSymbolPatchInfo* info,
										ElfImage* image)
{
	if (!info || !image || !info->GetSymbolName())
		return B_BAD_VALUE;
	// get the symbol's relocations
	BList patchTargets;
	status_t error
		= image->GetSymbolRelocations(info->GetSymbolName(), &patchTargets);
	if (error == B_OK)
		error = info->CreateEntry(image->GetID(), &patchTargets);
	return error;
}

// _Init
status_t
ElfSymbolPatcher::_Init()
{
	status_t error = B_OK;
	image_info info;
	int32 cookie = 0;
	while (get_next_image_info(0, &cookie, &info) == B_OK) {
		ElfImage* image = new(nothrow) ElfImage;
		if (!image)
			return B_NO_MEMORY;
		if (!fImages.AddItem(image)) {
			delete image;
			return B_NO_MEMORY;
		}
		error = image->SetTo(info.id);
	}
	return error;
}

// _Cleanup
void
ElfSymbolPatcher::_Cleanup()
{
	for (int i = 0; ElfImage* image = _ImageAt(i); i++)
		delete image;
	fImages.MakeEmpty();
}

// _ImageAt
ElfImage*
ElfSymbolPatcher::_ImageAt(int32 index) const
{
	return (ElfImage*)fImages.ItemAt(index);
}

// _ImageForID
ElfImage*
ElfSymbolPatcher::_ImageForID(image_id id) const
{
	for (int i = 0; ElfImage* image = _ImageAt(i); i++) {
		if (image->GetID() == id)
			return image;
	}
	return NULL;
}


///////////////////////
// ElfSymbolPatchGroup
//

// constructor
ElfSymbolPatchGroup::ElfSymbolPatchGroup(ElfSymbolPatcher* patcher)
	: fPatcher(patcher),
	  fPatchInfos(),
	  fOwnsPatcher(false),
	  fPatched(false)
{
	// create a patcher if none has been supplied
	if (!fPatcher) {
		fPatcher = new(nothrow) ElfSymbolPatcher;
		if (fPatcher) {
			if (fPatcher->InitCheck() == B_OK)
				fOwnsPatcher = true;
			else {
				delete fPatcher;
				fPatcher = NULL;
			}
		}
	}
}

// destructor
ElfSymbolPatchGroup::~ElfSymbolPatchGroup()
{
	RemoveAllPatches();
	if (fPatcher && fOwnsPatcher)
		delete fPatcher;
}

// AddPatch
status_t
ElfSymbolPatchGroup::AddPatch(const char* symbolName, void* newAddress,
							  void** originalAddress)
{
	// check initialization and parameters
	if (!fPatcher)
		return B_NO_INIT;
	if (!symbolName || !originalAddress)
		return B_BAD_VALUE;
	// allocate patch info
	PatchInfo* patchInfo = new(nothrow) PatchInfo;
	if (!patchInfo)
		return B_NO_MEMORY;
	// init and add the patch info
	status_t error = fPatcher->GetSymbolPatchInfo(symbolName, patchInfo);
	if (error == B_OK) {
		if (fPatchInfos.AddItem(patchInfo)) {
			patchInfo->fNewAddress = newAddress;
			*originalAddress = patchInfo->GetOriginalAddress();
		} else
			error = B_NO_MEMORY;
	}
	// cleanup on failure
	if (error != B_OK && patchInfo) {
		fPatchInfos.RemoveItem(patchInfo);
		delete patchInfo;
	}
	return error;
}

// RemoveAllPatches
void
ElfSymbolPatchGroup::RemoveAllPatches()
{
	for (int i = 0; PatchInfo* info = (PatchInfo*)fPatchInfos.ItemAt(i); i++)
		delete info;
	fPatchInfos.MakeEmpty();
	fPatched = false;
}

// Patch
status_t
ElfSymbolPatchGroup::Patch()
{
//printf("ElfSymbolPatchGroup::Patch(): patching %ld symbols\n",
//fPatchInfos.CountItems());
	if (!fPatcher)
		return B_NO_INIT;
	if (fPatched)
		return B_OK;
	for (int i = 0; PatchInfo* info = (PatchInfo*)fPatchInfos.ItemAt(i); i++)
		info->Patch(info->fNewAddress);
	fPatched = true;
	return B_OK;
}

// Restore
status_t
ElfSymbolPatchGroup::Restore()
{
	if (!fPatcher)
		return B_NO_INIT;
	if (!fPatched)
		return B_OK;
	for (int i = 0; PatchInfo* info = (PatchInfo*)fPatchInfos.ItemAt(i); i++)
		info->Restore();
	fPatched = false;
	return B_OK;
}

// Update
status_t
ElfSymbolPatchGroup::Update()
{
	if (!fPatcher)
		return B_NO_INIT;
	return fPatcher->Update(this);
}

// ImageAdded
void
ElfSymbolPatchGroup::ImageAdded(ElfImage* image)
{
	for (int i = 0; PatchInfo* info = (PatchInfo*)fPatchInfos.ItemAt(i); i++) {
		fPatcher->UpdateSymbolPatchInfo(info, image);
		if (fPatched) {
			ElfSymbolPatchInfo::Entry* entry = info->EntryFor(image->GetID());
			if (entry)
				info->Patch(info->fNewAddress);
		}
	}
}

// ImageRemoved
void
ElfSymbolPatchGroup::ImageRemoved(ElfImage* image)
{
	int32 count = fPatchInfos.CountItems();
	for (int i = count - 1; i >= 0; i--) {
		PatchInfo* info = (PatchInfo*)fPatchInfos.ItemAt(i);
		if (info->GetOriginalAddressImage() == image->GetID()) {
			// the image the symbol lives in, has been removed: remove the
			// complete patch info
			fPatchInfos.RemoveItem(i);
			delete info;
		} else
			info->DeleteEntry(image->GetID());
	}
}

